diff --git a/js/src/jit-test/tests/basic/testMissingProperties.js b/js/src/jit-test/tests/basic/testMissingProperties.js new file mode 100644 index 000000000000..71512c6f5c7f --- /dev/null +++ b/js/src/jit-test/tests/basic/testMissingProperties.js @@ -0,0 +1,49 @@ +function isnan(n) { return n !== n } + +function f(x) { + var sum = 0; + for (var i = 0; i < 100; ++i) + sum += x.x; + return sum; +} +var o = {}; +assertEq(isnan(f(o)), true); +o.x = 1; +assertEq(f(o), 100); +var o = {a:1, b:2}; +assertEq(isnan(f(o)), true); +o.x = 2; +assertEq(f(o), 200); + +function g(x) { + var sum = 0; + for (var i = 0; i < 100; ++i) + sum += x.x; + return sum; +} +var o = {c:1, x:1}; +assertEq(g(o), 100); +var o = {}; +assertEq(isnan(g(o)), true); + +function h(x) { + var sum = 0; + for (var i = 0; i < 100; ++i) + sum += x.x; + return sum; +} + +var proto1 = {}; +var proto2 = Object.create(proto1); +var o = Object.create(proto2); +assertEq(isnan(f(o)), true); +assertEq(isnan(g(o)), true); +assertEq(isnan(h(o)), true); +proto2.x = 2; +assertEq(f(o), 200); +assertEq(g(o), 200); +assertEq(h(o), 200); +var o = {} +assertEq(isnan(f(o)), true); +assertEq(isnan(g(o)), true); +assertEq(isnan(h(o)), true); diff --git a/js/src/methodjit/PolyIC.cpp b/js/src/methodjit/PolyIC.cpp index cb15f6c3a8ee..a7282f5aac02 100644 --- a/js/src/methodjit/PolyIC.cpp +++ b/js/src/methodjit/PolyIC.cpp @@ -650,8 +650,26 @@ struct GetPropHelper { if (monitor.recompiled()) return Lookup_Uncacheable; - if (!prop) - return ic.disable(f, "lookup failed"); + if (!prop) { + /* + * Just because we didn't find the property on the object doesn't + * mean it won't magically appear through various engine hacks: + */ + if (obj->getClass()->getProperty && obj->getClass()->getProperty != JS_PropertyStub) + return Lookup_Uncacheable; + +#if JS_HAS_NO_SUCH_METHOD + /* + * The __noSuchMethod__ hook may substitute in a valid method. + * Since, if o.m is missing, o.m() will probably be an error, + * just mark all missing callprops as uncacheable. + */ + if (*f.pc() == JSOP_CALLPROP) + return Lookup_Uncacheable; +#endif + + return Lookup_NoProperty; + } if (!IsCacheableProtoChain(obj, holder)) return ic.disable(f, "non-native holder"); shape = prop; @@ -1215,22 +1233,39 @@ class GetPropCompiler : public PICStubCompiler return error(); } - // Bake in the holder identity. Careful not to clobber |objReg|, since we can't remat it. - holderReg = pic.shapeReg; - masm.move(ImmPtr(holder), holderReg); - pic.shapeRegHasBaseShape = false; + if (holder) { + // Bake in the holder identity. Careful not to clobber |objReg|, since we can't remat it. + holderReg = pic.shapeReg; + masm.move(ImmPtr(holder), holderReg); + pic.shapeRegHasBaseShape = false; - // Guard on the holder's shape. - Jump j = masm.guardShape(holderReg, holder); - if (!shapeMismatches.append(j)) - return error(); + // Guard on the holder's shape. + Jump j = masm.guardShape(holderReg, holder); + if (!shapeMismatches.append(j)) + return error(); + } else { + // Like when we add a property, we need to guard on the shape of + // everything on the prototype chain. + JSObject *proto = obj->getProto(); + RegisterID lastReg = pic.objReg; + while (proto) { + masm.loadPtr(Address(lastReg, JSObject::offsetOfType()), pic.shapeReg); + masm.loadPtr(Address(pic.shapeReg, offsetof(types::TypeObject, proto)), pic.shapeReg); + Jump protoGuard = masm.guardShape(pic.shapeReg, proto); + if (!shapeMismatches.append(protoGuard)) + return error(); + + proto = proto->getProto(); + lastReg = pic.shapeReg; + } + } pic.secondShapeGuard = masm.distanceOf(masm.label()) - masm.distanceOf(start); } else { pic.secondShapeGuard = 0; } - if (!shape->hasDefaultGetter()) { + if (shape && !shape->hasDefaultGetter()) { if (shape->hasGetterValue()) { generateNativeGetterStub(masm, shape, start, shapeMismatches); } else { @@ -1244,10 +1279,19 @@ class GetPropCompiler : public PICStubCompiler return Lookup_Cacheable; } - /* Load the value out of the object. */ - masm.loadObjProp(holder, holderReg, shape, pic.shapeReg, pic.objReg); - Jump done = masm.jump(); + /* + * A non-null 'shape' tells us where to find the property value in the + * holder object. A null shape means that the above checks guard the + * absence of the property, so the get-prop returns 'undefined'. A + * missing property guarantees a type barrier below so we don't have to + * check type information. + */ + if (shape) + masm.loadObjProp(holder, holderReg, shape, pic.shapeReg, pic.objReg); + else + masm.loadValueAsComponents(UndefinedValue(), pic.shapeReg, pic.objReg); + Jump done = masm.jump(); pic.updatePCCounters(f, masm); PICLinker buffer(masm, pic); @@ -1319,12 +1363,13 @@ class GetPropCompiler : public PICStubCompiler GetPropHelper getprop(cx, obj, name, *this, f); LookupStatus status = getprop.lookupAndTest(); - if (status != Lookup_Cacheable) + if (status != Lookup_Cacheable && status != Lookup_NoProperty) return status; if (hadGC()) return Lookup_Uncacheable; - if (obj == getprop.holder && + if (status == Lookup_Cacheable && + obj == getprop.holder && getprop.shape->hasDefaultGetter() && !pic.inlinePathPatched) { return patchInline(getprop.holder, getprop.shape); @@ -2457,7 +2502,7 @@ ic::GetElement(VMFrame &f, ic::GetElementIC *ic) f.regs.sp[-2] = MagicValue(JS_GENERIC_MAGIC); #endif LookupStatus status = ic->update(f, obj, idval_, id, res); - if (status != Lookup_Uncacheable) { + if (status != Lookup_Uncacheable && status != Lookup_NoProperty) { if (status == Lookup_Error) THROW(); diff --git a/js/src/methodjit/PolyIC.h b/js/src/methodjit/PolyIC.h index 41190a011ea2..04739d68f648 100644 --- a/js/src/methodjit/PolyIC.h +++ b/js/src/methodjit/PolyIC.h @@ -31,7 +31,8 @@ static const uint32_t MAX_GETELEM_IC_STUBS = 17; enum LookupStatus { Lookup_Error = 0, Lookup_Uncacheable, - Lookup_Cacheable + Lookup_Cacheable, + Lookup_NoProperty }; struct BaseIC : public MacroAssemblerTypedefs {