Bug 505523 - Property cache can skip JSClass::resolve or JSClass::addProperty hooks. r=graydon, r=brendan.

--HG--
extra : rebase_source : a77b0e81203010aabe07b92b639762dce83200c0
This commit is contained in:
Jason Orendorff 2009-11-18 14:29:58 -06:00
parent e5ccb0b9ff
commit 4d092db314
12 changed files with 196 additions and 123 deletions

View File

@ -194,6 +194,7 @@ INSTALLED_HEADERS = \
jsmath.h \
jsnum.h \
jsobj.h \
jsobjinlines.h \
json.h \
jsopcode.tbl \
jsopcode.h \

View File

@ -25,7 +25,6 @@ BEGIN_TEST(testPropCache_bug505798)
EXEC("var arr = [x, y];\n"
"for (var i = 0; i < arr.length; i++)\n"
" arr[i].p = 1;\n");
knownFail = true;
CHECK(g_counter == 1);
return true;
}

View File

@ -1297,9 +1297,9 @@ js_MakeArraySlow(JSContext *cx, JSObject *obj)
/* obj is Array.prototype. */
emptyShape = js_GenerateShape(cx, false);
} else {
/* arrayProto is Array.prototype. */
JS_ASSERT(arrayProto->getClass() == &js_SlowArrayClass);
if (!OBJ_SCOPE(arrayProto)->getEmptyScopeShape(cx, &js_SlowArrayClass, &emptyShape))
return JS_FALSE;
emptyShape = OBJ_SCOPE(arrayProto)->emptyScope->shape;
}
JSScope *scope = JSScope::create(cx, &js_SlowArrayObjectOps, &js_SlowArrayClass, obj,
emptyShape);

View File

@ -61,6 +61,7 @@
#include "jsvector.h"
#include "jsatominlines.h"
#include "jsobjinlines.h"
#include "jsscopeinlines.h"
using namespace avmplus;
@ -414,15 +415,8 @@ js_NewNullClosure(JSContext* cx, JSObject* funobj, JSObject* proto, JSObject* pa
if (!closure)
return NULL;
JSScope *scope = OBJ_SCOPE(proto)->getEmptyScope(cx, &js_FunctionClass);
if (!scope) {
JS_ASSERT(!closure->map);
return NULL;
}
closure->map = scope;
closure->init(&js_FunctionClass, proto, parent,
reinterpret_cast<jsval>(fun));
closure->initSharingEmptyScope(&js_FunctionClass, proto, parent,
reinterpret_cast<jsval>(fun));
return closure;
}
JS_DEFINE_CALLINFO_4(extern, OBJECT, js_NewNullClosure, CONTEXT, OBJECT, OBJECT, OBJECT, 0, 0)

View File

@ -295,8 +295,7 @@ js_FillPropertyCache(JSContext *cx, JSObject *obj,
return JS_NO_PROP_CACHE_FILL;
JSScope *protoscope = OBJ_SCOPE(proto);
if (!protoscope->emptyScope ||
!js_ObjectIsSimilarToProto(cx, obj, obj->map->ops, OBJ_GET_CLASS(cx, obj),
proto)) {
protoscope->emptyScope->clasp != obj->getClass()) {
return JS_NO_PROP_CACHE_FILL;
}
kshape = protoscope->emptyScope->shape;

View File

@ -97,6 +97,7 @@
#endif
#include "jsatominlines.h"
#include "jsobjinlines.h"
#include "jsscriptinlines.h"
#include "jsautooplen.h"
@ -2194,8 +2195,9 @@ InitScopeForObject(JSContext* cx, JSObject* obj, JSObject* proto, JSObjectOps* o
/* Share proto's emptyScope only if obj is similar to proto. */
JSClass *clasp = OBJ_GET_CLASS(cx, obj);
JSScope *scope;
if (proto && js_ObjectIsSimilarToProto(cx, obj, ops, clasp, proto)) {
scope = OBJ_SCOPE(proto)->getEmptyScope(cx, clasp);
if (proto && OBJ_IS_NATIVE(proto) &&
(scope = OBJ_SCOPE(proto))->canProvideEmptyScope(ops, clasp)) {
scope = scope->getEmptyScope(cx, clasp);
if (!scope)
goto bad;
} else {
@ -2346,6 +2348,31 @@ js_Object(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
#ifdef JS_TRACER
JSObject*
js_NewObjectWithClassProto(JSContext *cx, JSClass *clasp, JSObject *proto,
jsval privateSlotValue)
{
JS_ASSERT(!clasp->getObjectOps);
JS_ASSERT(proto->map->ops == &js_ObjectOps);
JSObject* obj = js_NewGCObject(cx);
if (!obj)
return NULL;
obj->initSharingEmptyScope(clasp, proto, proto->getParent(), privateSlotValue);
return obj;
}
JSObject* FASTCALL
js_Object_tn(JSContext* cx, JSObject* proto)
{
JS_ASSERT(!(js_ObjectClass.flags & JSCLASS_HAS_PRIVATE));
return js_NewObjectWithClassProto(cx, &js_ObjectClass, proto, JSVAL_VOID);
}
JS_DEFINE_TRCINFO_1(js_Object,
(2, (extern, CONSTRUCTOR_RETRY, js_Object_tn, CONTEXT, CALLEE_PROTOTYPE, 0, 0)))
static inline JSObject*
NewNativeObject(JSContext* cx, JSClass* clasp, JSObject* proto,
JSObject *parent, jsval privateSlotValue)
@ -2359,17 +2386,6 @@ NewNativeObject(JSContext* cx, JSClass* clasp, JSObject* proto,
return InitScopeForObject(cx, obj, proto, &js_ObjectOps) ? obj : NULL;
}
JSObject* FASTCALL
js_Object_tn(JSContext* cx, JSObject* proto)
{
JS_ASSERT(!(js_ObjectClass.flags & JSCLASS_HAS_PRIVATE));
return NewNativeObject(cx, &js_ObjectClass, proto, proto->getParent(),
JSVAL_VOID);
}
JS_DEFINE_TRCINFO_1(js_Object,
(2, (extern, CONSTRUCTOR_RETRY, js_Object_tn, CONTEXT, CALLEE_PROTOTYPE, 0, 0)))
JSObject* FASTCALL
js_NewInstance(JSContext *cx, JSClass *clasp, JSObject *ctor)
{
@ -3118,6 +3134,21 @@ js_InitClass(JSContext *cx, JSObject *obj, JSObject *parent_proto,
goto bad;
}
/*
* Make sure proto's scope's emptyScope is available to be shared by
* objects of this class. JSScope::emptyScope is a one-slot cache. If we
* omit this, some other class could snap it up. (The risk is particularly
* great for Object.prototype.)
*
* All callers of JSObject::initSharingEmptyScope depend on this.
*/
{
JSScope *scope = OBJ_SCOPE(proto)->getEmptyScope(cx, clasp);
if (!scope)
goto bad;
scope->drop(cx, NULL);
}
/* If this is a standard class, cache its prototype. */
if (key != JSProto_Null && !js_SetClassObject(cx, obj, key, ctor))
goto bad;
@ -3282,28 +3313,6 @@ js_GetClassId(JSContext *cx, JSClass *clasp, jsid *idp)
return JS_TRUE;
}
JSObject*
js_NewNativeObject(JSContext *cx, JSClass *clasp, JSObject *proto,
jsval privateSlotValue)
{
JS_ASSERT(!clasp->getObjectOps);
JS_ASSERT(proto->map->ops == &js_ObjectOps);
JS_ASSERT(OBJ_GET_CLASS(cx, proto) == clasp);
JSObject* obj = js_NewGCObject(cx);
if (!obj)
return NULL;
JSScope *scope = OBJ_SCOPE(proto)->getEmptyScope(cx, clasp);
if (!scope) {
JS_ASSERT(!obj->map);
return NULL;
}
obj->map = scope;
obj->init(clasp, proto, proto->getParent(), privateSlotValue);
return obj;
}
JS_BEGIN_EXTERN_C
static JSObject *

View File

@ -145,8 +145,8 @@ const uintptr_t JSSLOT_CLASS_MASK_BITS = 3;
* records the number of available slots.
*/
struct JSObject {
JSObjectMap *map; /* propery map, see jsscope.h */
jsuword classword; /* classword, see above */
JSObjectMap *map; /* property map, see jsscope.h */
jsuword classword; /* JSClass ptr | bits, see above */
jsval fslots[JS_INITIAL_NSLOTS]; /* small number of fixed slots */
jsval *dslots; /* dynamically allocated slots */
@ -251,6 +251,13 @@ struct JSObject {
dslots = NULL;
}
/*
* Like init, but also initializes map. The catch: proto must be the result
* of a call to js_InitClass(...clasp, ...).
*/
inline void initSharingEmptyScope(JSClass *clasp, JSObject *proto, JSObject *parent,
jsval privateSlotValue);
JSBool lookupProperty(JSContext *cx, jsid id,
JSObject **objp, JSProperty **propp) {
return map->ops->lookupProperty(cx, this, id, objp, propp);
@ -622,14 +629,17 @@ js_NewObjectWithGivenProto(JSContext *cx, JSClass *clasp, JSObject *proto,
* Allocate a new native object with the given value of the proto and private
* slots. The parent slot is set to the value of proto's parent slot.
*
* clasp must be a native class. proto must be the result of a call to
* js_InitClass(...clasp, ...).
*
* Note that this is the correct global object for native class instances, but
* not for user-defined functions called as constructors. Functions used as
* constructors must create instances parented by the parent of the function
* object, not by the parent of its .prototype object value.
*/
extern JSObject*
js_NewNativeObject(JSContext *cx, JSClass *clasp, JSObject *proto,
jsval privateSlotValue);
js_NewObjectWithClassProto(JSContext *cx, JSClass *clasp, JSObject *proto,
jsval privateSlotValue);
/*
* Fast access to immutable standard objects (constructors and prototypes).
@ -986,36 +996,6 @@ js_ReportGetterOnlyAssignment(JSContext *cx);
extern JS_FRIEND_API(JSBool)
js_GetterOnlyPropertyStub(JSContext *cx, JSObject *obj, jsval id, jsval *vp);
/*
* If an object is "similar" to its prototype, it can share OBJ_SCOPE(proto)->emptyScope.
* Similar objects have the same JSObjectOps and the same private and reserved slots.
*
* We assume that if prototype and object are of the same class, they always
* have the same number of computed reserved slots (returned via
* clasp->reserveSlots). This is true for builtin classes (except Block, and
* for this reason among others Blocks must never be exposed to scripts).
*
* Otherwise, prototype and object classes must have the same (null or not)
* reserveSlots hook.
*
* FIXME: This fails to distinguish between objects with different addProperty
* hooks. See bug 505523.
*/
static inline bool
js_ObjectIsSimilarToProto(JSContext *cx, JSObject *obj, const JSObjectOps *ops,
JSClass *clasp, JSObject *proto)
{
JS_ASSERT(proto == OBJ_GET_PROTO(cx, obj));
JSClass *protoclasp;
return (proto->map->ops == ops &&
((protoclasp = OBJ_GET_CLASS(cx, proto)) == clasp ||
(!((protoclasp->flags ^ clasp->flags) &
(JSCLASS_HAS_PRIVATE |
(JSCLASS_RESERVED_SLOTS_MASK << JSCLASS_RESERVED_SLOTS_SHIFT))) &&
protoclasp->reserveSlots == clasp->reserveSlots)));
}
#ifdef DEBUG
JS_FRIEND_API(void) js_DumpChars(const jschar *s, size_t n);
JS_FRIEND_API(void) js_DumpString(JSString *str);

59
js/src/jsobjinlines.h Normal file
View File

@ -0,0 +1,59 @@
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* vim: set ts=8 sw=4 et tw=99:
*
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Mozilla Communicator client code, released
* March 31, 1998.
*
* The Initial Developer of the Original Code is
* Netscape Communications Corporation.
* Portions created by the Initial Developer are Copyright (C) 1998
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#ifndef jsobjinlines_h___
#define jsobjinlines_h___
#include "jsobj.h"
#include "jsscope.h"
inline void
JSObject::initSharingEmptyScope(JSClass *clasp, JSObject *proto, JSObject *parent,
jsval privateSlotValue)
{
init(clasp, proto, parent, privateSlotValue);
JSEmptyScope *emptyScope = OBJ_SCOPE(proto)->emptyScope;
JS_ASSERT(emptyScope->clasp == clasp);
emptyScope->hold();
map = emptyScope;
}
#endif /* jsobjinlines_h___ */

View File

@ -211,12 +211,12 @@ JSScope::create(JSContext *cx, const JSObjectOps *ops, JSClass *clasp,
return scope;
}
JSScope *
JSEmptyScope *
JSScope::createEmptyScope(JSContext *cx, JSClass *clasp)
{
JS_ASSERT(!emptyScope);
JSScope *scope = cx->create<JSScope>(ops);
JSEmptyScope *scope = cx->create<JSEmptyScope>(ops, clasp);
if (!scope)
return NULL;
@ -1582,13 +1582,13 @@ JSScope::clear(JSContext *cx)
JSClass *clasp = object->getClass();
JSObject *proto = object->getProto();
uint32 newShape = 0;
if (proto && clasp == proto->getClass()) {
#ifdef DEBUG
bool ok =
#endif
OBJ_SCOPE(proto)->getEmptyScopeShape(cx, clasp, &newShape);
JS_ASSERT(ok);
JSEmptyScope *emptyScope;
uint32 newShape;
if (proto &&
OBJ_IS_NATIVE(proto) &&
(emptyScope = OBJ_SCOPE(proto)->emptyScope) &&
emptyScope->clasp == clasp) {
newShape = emptyScope->shape;
} else {
newShape = js_GenerateShape(cx, false);
}

View File

@ -200,6 +200,8 @@ JS_BEGIN_EXTERN_C
* to find a given id, and save on the space overhead of a hash table.
*/
struct JSEmptyScope;
struct JSScope : public JSObjectMap
{
#ifdef JS_THREADSAFE
@ -208,7 +210,7 @@ struct JSScope : public JSObjectMap
JSObject *object; /* object that owns this scope */
jsrefcount nrefs; /* count of all referencing objects */
uint32 freeslot; /* index of next free slot in object */
JSScope *emptyScope; /* cache for getEmptyScope below */
JSEmptyScope *emptyScope; /* cache for getEmptyScope below */
uint8 flags; /* flags, see below */
int8 hashShift; /* multiplicative hash shift */
@ -226,7 +228,7 @@ struct JSScope : public JSObjectMap
void generateOwnShape(JSContext *cx);
JSScopeProperty **searchTable(jsid id, bool adding);
inline JSScopeProperty **search(jsid id, bool adding);
JSScope *createEmptyScope(JSContext *cx, JSClass *clasp);
JSEmptyScope *createEmptyScope(JSContext *cx, JSClass *clasp);
public:
explicit JSScope(const JSObjectOps *ops, JSObject *obj = NULL)
@ -238,6 +240,9 @@ struct JSScope : public JSObjectMap
static void destroy(JSContext *cx, JSScope *scope);
inline void hold();
inline bool drop(JSContext *cx, JSObject *obj);
/*
* Return an immutable, shareable, empty scope with the same ops as this
* and the same freeslot as this had when empty.
@ -245,30 +250,10 @@ struct JSScope : public JSObjectMap
* If |this| is the scope of an object |proto|, the resulting scope can be
* used as the scope of a new object whose prototype is |proto|.
*/
JSScope *getEmptyScope(JSContext *cx, JSClass *clasp) {
if (emptyScope) {
emptyScope->hold();
return emptyScope;
}
return createEmptyScope(cx, clasp);
}
bool getEmptyScopeShape(JSContext *cx, JSClass *clasp, uint32 *shapep) {
if (emptyScope) {
*shapep = emptyScope->shape;
return true;
}
JSScope *e = getEmptyScope(cx, clasp);
if (!e)
return false;
*shapep = e->shape;
e->drop(cx, NULL);
return true;
}
inline void hold();
inline bool drop(JSContext *cx, JSObject *obj);
inline JSEmptyScope *getEmptyScope(JSContext *cx, JSClass *clasp);
inline bool canProvideEmptyScope(JSObjectOps *ops, JSClass *clasp);
JSScopeProperty *lookup(jsid id);
bool has(JSScopeProperty *sprop);
@ -405,6 +390,14 @@ struct JSScope : public JSObjectMap
bool owned() { return object != NULL; }
};
struct JSEmptyScope : public JSScope
{
JSClass * const clasp;
explicit JSEmptyScope(const JSObjectOps *ops, JSClass *clasp)
: JSScope(ops), clasp(clasp) {}
};
inline bool
JS_IS_SCOPE_LOCKED(JSContext *cx, JSScope *scope)
{
@ -631,6 +624,23 @@ JSScope::search(jsid id, bool adding)
#undef METER
inline bool
JSScope::canProvideEmptyScope(JSObjectOps *ops, JSClass *clasp)
{
return this->ops == ops && (!emptyScope || emptyScope->clasp == clasp);
}
inline JSEmptyScope *
JSScope::getEmptyScope(JSContext *cx, JSClass *clasp)
{
if (emptyScope) {
JS_ASSERT(clasp == emptyScope->clasp);
emptyScope->hold();
return emptyScope;
}
return createEmptyScope(cx, clasp);
}
inline void
JSScope::hold()
{

View File

@ -2912,7 +2912,7 @@ JSObject* FASTCALL
js_String_tn(JSContext* cx, JSObject* proto, JSString* str)
{
JS_ASSERT(JS_ON_TRACE(cx));
return js_NewNativeObject(cx, &js_StringClass, proto, STRING_TO_JSVAL(str));
return js_NewObjectWithClassProto(cx, &js_StringClass, proto, STRING_TO_JSVAL(str));
}
JS_DEFINE_CALLINFO_3(extern, OBJECT, js_String_tn, CONTEXT, CALLEE_PROTOTYPE, STRING, 0, 0)

View File

@ -10237,7 +10237,11 @@ TraceRecorder::record_JSOP_OBJTOP()
RecordingStatus
TraceRecorder::getClassPrototype(JSObject* ctor, LIns*& proto_ins)
{
// ctor must be a function created via js_InitClass.
#ifdef DEBUG
JSClass *clasp = FUN_CLASP(GET_FUNCTION_PRIVATE(cx, ctor));
JS_ASSERT(clasp);
JSTraceMonitor &localtm = JS_TRACE_MONITOR(cx);
#endif
@ -10245,11 +10249,10 @@ TraceRecorder::getClassPrototype(JSObject* ctor, LIns*& proto_ins)
if (!ctor->getProperty(cx, ATOM_TO_JSID(cx->runtime->atomState.classPrototypeAtom), &pval))
RETURN_ERROR("error getting prototype from constructor");
/* For functions, this shold not reenter */
// ctor.prototype is a permanent data property, so this lookup cannot have
// deep-aborted.
JS_ASSERT(localtm.recorder);
if (JSVAL_TAG(pval) != JSVAL_OBJECT)
RETURN_STOP("got primitive prototype from constructor");
#ifdef DEBUG
JSBool ok, found;
uintN attrs;
@ -10258,7 +10261,14 @@ TraceRecorder::getClassPrototype(JSObject* ctor, LIns*& proto_ins)
JS_ASSERT(found);
JS_ASSERT((~attrs & (JSPROP_READONLY | JSPROP_PERMANENT)) == 0);
#endif
proto_ins = INS_CONSTOBJ(JSVAL_TO_OBJECT(pval));
// Since ctor was built by js_InitClass, we can assert (rather than check)
// that pval is usable.
JS_ASSERT(!JSVAL_IS_PRIMITIVE(pval));
JSObject *proto = JSVAL_TO_OBJECT(pval);
JS_ASSERT_IF(clasp != &js_ArrayClass, OBJ_SCOPE(proto)->emptyScope->clasp == clasp);
proto_ins = INS_CONSTOBJ(proto);
return RECORD_CONTINUE;
}
@ -10273,9 +10283,21 @@ TraceRecorder::getClassPrototype(JSProtoKey key, LIns*& proto_ins)
if (!js_GetClassPrototype(cx, globalObj, INT_TO_JSID(key), &proto))
RETURN_ERROR("error in js_GetClassPrototype");
/* For functions, this shold not reenter */
// This should not have reentered.
JS_ASSERT(localtm.recorder);
// If we might end up passing the proto to JSObject::initSharingEmptyScope,
// we must check here that proto has a matching emptyScope. We skip the
// check for Array.prototype because new arrays, being non-native, are
// never initialized using initSharingEmptyScope.
if (key != JSProto_Array) {
if (!OBJ_IS_NATIVE(proto))
RETURN_STOP("non-native class prototype");
JSEmptyScope *emptyScope = OBJ_SCOPE(proto)->emptyScope;
if (!emptyScope || JSCLASS_CACHED_PROTO_KEY(emptyScope->clasp) != key)
RETURN_STOP("class prototype is not the standard one");
}
proto_ins = INS_CONSTOBJ(proto);
return RECORD_CONTINUE;
}