зеркало из https://github.com/mozilla/gecko-dev.git
1132 строки
40 KiB
C++
1132 строки
40 KiB
C++
/* -*- 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 jsscope_h___
|
|
#define jsscope_h___
|
|
/*
|
|
* JS symbol tables.
|
|
*/
|
|
#include <new>
|
|
#ifdef DEBUG
|
|
#include <stdio.h>
|
|
#endif
|
|
|
|
#include "jsobj.h"
|
|
#include "jspropertytree.h"
|
|
#include "jstypes.h"
|
|
|
|
#include "js/HashTable.h"
|
|
#include "gc/Root.h"
|
|
#include "mozilla/Attributes.h"
|
|
|
|
#ifdef _MSC_VER
|
|
#pragma warning(push)
|
|
#pragma warning(disable:4800)
|
|
#pragma warning(push)
|
|
#pragma warning(disable:4100) /* Silence unreferenced formal parameter warnings */
|
|
#endif
|
|
|
|
/*
|
|
* In isolation, a Shape represents a property that exists in one or more
|
|
* objects; it has an id, flags, etc. (But it doesn't represent the property's
|
|
* value.) However, Shapes are always stored in linked linear sequence of
|
|
* Shapes, called "shape lineages". Each shape lineage represents the layout of
|
|
* an entire object.
|
|
*
|
|
* Every JSObject has a pointer, |shape_|, accessible via lastProperty(), to
|
|
* the last Shape in a shape lineage, which identifies the property most
|
|
* recently added to the object. This pointer permits fast object layout
|
|
* tests. The shape lineage order also dictates the enumeration order for the
|
|
* object; ECMA requires no particular order but this implementation has
|
|
* promised and delivered property definition order.
|
|
*
|
|
* Shape lineages occur in two kinds of data structure.
|
|
*
|
|
* 1. N-ary property trees. Each path from a non-root node to the root node in
|
|
* a property tree is a shape lineage. Property trees permit full (or
|
|
* partial) sharing of Shapes between objects that have fully (or partly)
|
|
* identical layouts. The root is an EmptyShape whose identity is determined
|
|
* by the object's class, compartment and prototype. These Shapes are shared
|
|
* and immutable.
|
|
*
|
|
* 2. Dictionary mode lists. Shapes in such lists are said to be "in
|
|
* dictionary mode", as are objects that point to such Shapes. These Shapes
|
|
* are unshared, private to a single object, and immutable except for their
|
|
* links in the dictionary list.
|
|
*
|
|
* All shape lineages are bi-directionally linked, via the |parent| and
|
|
* |kids|/|listp| members.
|
|
*
|
|
* Shape lineages start out life in the property tree. They can be converted
|
|
* (by copying) to dictionary mode lists in the following circumstances.
|
|
*
|
|
* 1. The shape lineage's size reaches MAX_HEIGHT. This reasonable limit avoids
|
|
* potential worst cases involving shape lineage mutations.
|
|
*
|
|
* 2. A property represented by a non-last Shape in a shape lineage is removed
|
|
* from an object. (In the last Shape case, obj->shape_ can be easily
|
|
* adjusted to point to obj->shape_->parent.) We originally tried lazy
|
|
* forking of the property tree, but this blows up for delete/add
|
|
* repetitions.
|
|
*
|
|
* 3. A property represented by a non-last Shape in a shape lineage has its
|
|
* attributes modified.
|
|
*
|
|
* To find the Shape for a particular property of an object initially requires
|
|
* a linear search. But if the number of searches starting at any particular
|
|
* Shape in the property tree exceeds MAX_LINEAR_SEARCHES and the Shape's
|
|
* lineage has (excluding the EmptyShape) at least MIN_ENTRIES, we create an
|
|
* auxiliary hash table -- the PropertyTable -- that allows faster lookup.
|
|
* Furthermore, a PropertyTable is always created for dictionary mode lists,
|
|
* and it is attached to the last Shape in the lineage. Property tables for
|
|
* property tree Shapes never change, but property tables for dictionary mode
|
|
* Shapes can grow and shrink.
|
|
*
|
|
* There used to be a long, math-heavy comment here explaining why property
|
|
* trees are more space-efficient than alternatives. This was removed in bug
|
|
* 631138; see that bug for the full details.
|
|
*
|
|
* Because many Shapes have similar data, there is actually a secondary type
|
|
* called a BaseShape that holds some of a Shape's data. Many shapes can share
|
|
* a single BaseShape.
|
|
*/
|
|
|
|
namespace js {
|
|
|
|
/* Limit on the number of slotful properties in an object. */
|
|
static const uint32_t SHAPE_INVALID_SLOT = JS_BIT(24) - 1;
|
|
static const uint32_t SHAPE_MAXIMUM_SLOT = JS_BIT(24) - 2;
|
|
|
|
/*
|
|
* Shapes use multiplicative hashing, but specialized to
|
|
* minimize footprint.
|
|
*/
|
|
struct PropertyTable {
|
|
static const uint32_t HASH_BITS = tl::BitSize<HashNumber>::result;
|
|
static const uint32_t MIN_ENTRIES = 7;
|
|
static const uint32_t MIN_SIZE_LOG2 = 4;
|
|
static const uint32_t MIN_SIZE = JS_BIT(MIN_SIZE_LOG2);
|
|
|
|
int hashShift; /* multiplicative hash shift */
|
|
|
|
uint32_t entryCount; /* number of entries in table */
|
|
uint32_t removedCount; /* removed entry sentinels in table */
|
|
uint32_t freelist; /* SHAPE_INVALID_SLOT or head of slot
|
|
freelist in owning dictionary-mode
|
|
object */
|
|
js::Shape **entries; /* table of ptrs to shared tree nodes */
|
|
|
|
PropertyTable(uint32_t nentries)
|
|
: hashShift(HASH_BITS - MIN_SIZE_LOG2),
|
|
entryCount(nentries),
|
|
removedCount(0),
|
|
freelist(SHAPE_INVALID_SLOT)
|
|
{
|
|
/* NB: entries is set by init, which must be called. */
|
|
}
|
|
|
|
~PropertyTable() {
|
|
js::UnwantedForeground::free_(entries);
|
|
}
|
|
|
|
/* By definition, hashShift = HASH_BITS - log2(capacity). */
|
|
uint32_t capacity() const { return JS_BIT(HASH_BITS - hashShift); }
|
|
|
|
/* Computes the size of the entries array for a given capacity. */
|
|
static size_t sizeOfEntries(size_t cap) { return cap * sizeof(Shape *); }
|
|
|
|
/*
|
|
* This counts the PropertyTable object itself (which must be
|
|
* heap-allocated) and its |entries| array.
|
|
*/
|
|
size_t sizeOfIncludingThis(JSMallocSizeOfFun mallocSizeOf) const {
|
|
return mallocSizeOf(this) + mallocSizeOf(entries);
|
|
}
|
|
|
|
/* Whether we need to grow. We want to do this if the load factor is >= 0.75 */
|
|
bool needsToGrow() const {
|
|
uint32_t size = capacity();
|
|
return entryCount + removedCount >= size - (size >> 2);
|
|
}
|
|
|
|
/*
|
|
* Try to grow the table. On failure, reports out of memory on cx
|
|
* and returns false. This will make any extant pointers into the
|
|
* table invalid. Don't call this unless needsToGrow() is true.
|
|
*/
|
|
bool grow(JSContext *cx);
|
|
|
|
/*
|
|
* NB: init and change are fallible but do not report OOM, so callers can
|
|
* cope or ignore. They do however use JSRuntime's calloc_ method in order
|
|
* to update the malloc counter on success.
|
|
*/
|
|
bool init(JSRuntime *rt, js::Shape *lastProp);
|
|
bool change(int log2Delta, JSContext *cx);
|
|
js::Shape **search(jsid id, bool adding);
|
|
};
|
|
|
|
} /* namespace js */
|
|
|
|
struct JSObject;
|
|
|
|
namespace js {
|
|
|
|
class PropertyTree;
|
|
|
|
/*
|
|
* Reuse the API-only JSPROP_INDEX attribute to mean shadowability.
|
|
*/
|
|
#define JSPROP_SHADOWABLE JSPROP_INDEX
|
|
|
|
/*
|
|
* Shapes encode information about both a property lineage *and* a particular
|
|
* property. This information is split across the Shape and the BaseShape
|
|
* at shape->base(). Both Shape and BaseShape can be either owned or unowned
|
|
* by, respectively, the Object or Shape referring to them.
|
|
*
|
|
* Owned Shapes are used in dictionary objects, and form a doubly linked list
|
|
* whose entries are all owned by that dictionary. Unowned Shapes are all in
|
|
* the property tree.
|
|
*
|
|
* Owned BaseShapes are used for shapes which have property tables, including
|
|
* the last properties in all dictionaries. Unowned BaseShapes compactly store
|
|
* information common to many shapes. In a given compartment there is a single
|
|
* BaseShape for each combination of BaseShape information. This information
|
|
* is cloned in owned BaseShapes so that information can be quickly looked up
|
|
* for a given object or shape without regard to whether the base shape is
|
|
* owned or not.
|
|
*
|
|
* All combinations of owned/unowned Shapes/BaseShapes are possible:
|
|
*
|
|
* Owned Shape, Owned BaseShape:
|
|
*
|
|
* Last property in a dictionary object. The BaseShape is transferred from
|
|
* property to property as the object's last property changes.
|
|
*
|
|
* Owned Shape, Unowned BaseShape:
|
|
*
|
|
* Property in a dictionary object other than the last one.
|
|
*
|
|
* Unowned Shape, Owned BaseShape:
|
|
*
|
|
* Property in the property tree which has a property table.
|
|
*
|
|
* Unowned Shape, Unowned BaseShape:
|
|
*
|
|
* Property in the property tree which does not have a property table.
|
|
*
|
|
* BaseShapes additionally encode some information about the referring object
|
|
* itself. This includes the object's class, parent and various flags that may
|
|
* be set for the object. Except for the class, this information is mutable and
|
|
* may change when the object has an established property lineage. On such
|
|
* changes the entire property lineage is not updated, but rather only the
|
|
* last property (and its base shape). This works because only the object's
|
|
* last property is used to query information about the object. Care must be
|
|
* taken to call JSObject::canRemoveLastProperty when unwinding an object to
|
|
* an earlier property, however.
|
|
*/
|
|
|
|
class UnownedBaseShape;
|
|
|
|
class BaseShape : public js::gc::Cell
|
|
{
|
|
public:
|
|
friend struct Shape;
|
|
friend struct StackBaseShape;
|
|
friend struct StackShape;
|
|
|
|
enum Flag {
|
|
/* Owned by the referring shape. */
|
|
OWNED_SHAPE = 0x1,
|
|
|
|
/* getterObj/setterObj are active in unions below. */
|
|
HAS_GETTER_OBJECT = 0x2,
|
|
HAS_SETTER_OBJECT = 0x4,
|
|
|
|
/*
|
|
* Flags set which describe the referring object. Once set these cannot
|
|
* be unset, and are transferred from shape to shape as the object's
|
|
* last property changes.
|
|
*/
|
|
|
|
EXTENSIBLE_PARENTS = 0x8,
|
|
DELEGATE = 0x10,
|
|
SYSTEM = 0x20,
|
|
NOT_EXTENSIBLE = 0x40,
|
|
INDEXED = 0x80,
|
|
BOUND_FUNCTION = 0x100,
|
|
VAROBJ = 0x200,
|
|
WATCHED = 0x400,
|
|
ITERATED_SINGLETON = 0x800,
|
|
NEW_TYPE_UNKNOWN = 0x1000,
|
|
UNCACHEABLE_PROTO = 0x2000,
|
|
|
|
OBJECT_FLAG_MASK = 0x3ff8
|
|
};
|
|
|
|
private:
|
|
Class *clasp; /* Class of referring object. */
|
|
HeapPtrObject parent; /* Parent of referring object. */
|
|
uint32_t flags; /* Vector of above flags. */
|
|
uint32_t slotSpan_; /* Object slot span for BaseShapes at
|
|
* dictionary last properties. */
|
|
|
|
union {
|
|
js::PropertyOp rawGetter; /* getter hook for shape */
|
|
JSObject *getterObj; /* user-defined callable "get" object or
|
|
null if shape->hasGetterValue() */
|
|
};
|
|
|
|
union {
|
|
js::StrictPropertyOp rawSetter; /* setter hook for shape */
|
|
JSObject *setterObj; /* user-defined callable "set" object or
|
|
null if shape->hasSetterValue() */
|
|
};
|
|
|
|
/* For owned BaseShapes, the canonical unowned BaseShape. */
|
|
HeapPtr<UnownedBaseShape> unowned_;
|
|
|
|
/* For owned BaseShapes, the shape's property table. */
|
|
PropertyTable *table_;
|
|
|
|
public:
|
|
void finalize(FreeOp *fop);
|
|
|
|
inline BaseShape(Class *clasp, JSObject *parent, uint32_t objectFlags);
|
|
inline BaseShape(Class *clasp, JSObject *parent, uint32_t objectFlags,
|
|
uint8_t attrs, PropertyOp rawGetter, StrictPropertyOp rawSetter);
|
|
inline BaseShape(const StackBaseShape &base);
|
|
|
|
/* Not defined: BaseShapes must not be stack allocated. */
|
|
~BaseShape();
|
|
|
|
inline BaseShape &operator=(const BaseShape &other);
|
|
|
|
bool isOwned() const { return !!(flags & OWNED_SHAPE); }
|
|
|
|
inline bool matchesGetterSetter(PropertyOp rawGetter,
|
|
StrictPropertyOp rawSetter) const;
|
|
|
|
inline void adoptUnowned(UnownedBaseShape *other);
|
|
inline void setOwned(UnownedBaseShape *unowned);
|
|
|
|
JSObject *getObjectParent() const { return parent; }
|
|
uint32_t getObjectFlags() const { return flags & OBJECT_FLAG_MASK; }
|
|
|
|
bool hasGetterObject() const { return !!(flags & HAS_GETTER_OBJECT); }
|
|
JSObject *getterObject() const { JS_ASSERT(hasGetterObject()); return getterObj; }
|
|
|
|
bool hasSetterObject() const { return !!(flags & HAS_SETTER_OBJECT); }
|
|
JSObject *setterObject() const { JS_ASSERT(hasSetterObject()); return setterObj; }
|
|
|
|
bool hasTable() const { JS_ASSERT_IF(table_, isOwned()); return table_ != NULL; }
|
|
PropertyTable &table() const { JS_ASSERT(table_ && isOwned()); return *table_; }
|
|
void setTable(PropertyTable *table) { JS_ASSERT(isOwned()); table_ = table; }
|
|
|
|
uint32_t slotSpan() const { JS_ASSERT(isOwned()); return slotSpan_; }
|
|
void setSlotSpan(uint32_t slotSpan) { JS_ASSERT(isOwned()); slotSpan_ = slotSpan; }
|
|
|
|
/* Lookup base shapes from the compartment's baseShapes table. */
|
|
static UnownedBaseShape *getUnowned(JSContext *cx, const StackBaseShape &base);
|
|
|
|
/* Get the canonical base shape. */
|
|
inline UnownedBaseShape *unowned();
|
|
|
|
/* Get the canonical base shape for an owned one. */
|
|
inline UnownedBaseShape *baseUnowned();
|
|
|
|
/* Get the canonical base shape for an unowned one (i.e. identity). */
|
|
inline UnownedBaseShape *toUnowned();
|
|
|
|
/* Check that an owned base shape is consistent with its unowned base. */
|
|
inline void assertConsistency();
|
|
|
|
/* For JIT usage */
|
|
static inline size_t offsetOfClass() { return offsetof(BaseShape, clasp); }
|
|
static inline size_t offsetOfParent() { return offsetof(BaseShape, parent); }
|
|
static inline size_t offsetOfFlags() { return offsetof(BaseShape, flags); }
|
|
|
|
static inline void writeBarrierPre(BaseShape *shape);
|
|
static inline void writeBarrierPost(BaseShape *shape, void *addr);
|
|
static inline void readBarrier(BaseShape *shape);
|
|
|
|
static inline ThingRootKind rootKind() { return THING_ROOT_BASE_SHAPE; }
|
|
|
|
inline void markChildren(JSTracer *trc);
|
|
|
|
private:
|
|
static void staticAsserts() {
|
|
JS_STATIC_ASSERT(offsetof(BaseShape, clasp) == offsetof(js::shadow::BaseShape, clasp));
|
|
}
|
|
};
|
|
|
|
class UnownedBaseShape : public BaseShape {};
|
|
|
|
UnownedBaseShape *
|
|
BaseShape::unowned()
|
|
{
|
|
return isOwned() ? baseUnowned() : toUnowned();
|
|
}
|
|
|
|
UnownedBaseShape *
|
|
BaseShape::toUnowned()
|
|
{
|
|
JS_ASSERT(!isOwned() && !unowned_); return static_cast<UnownedBaseShape *>(this);
|
|
}
|
|
|
|
UnownedBaseShape *
|
|
BaseShape::baseUnowned()
|
|
{
|
|
JS_ASSERT(isOwned() && unowned_); return unowned_;
|
|
}
|
|
|
|
/* Entries for the per-compartment baseShapes set of unowned base shapes. */
|
|
struct StackBaseShape
|
|
{
|
|
typedef const StackBaseShape *Lookup;
|
|
|
|
uint32_t flags;
|
|
Class *clasp;
|
|
JSObject *parent;
|
|
PropertyOp rawGetter;
|
|
StrictPropertyOp rawSetter;
|
|
|
|
StackBaseShape(BaseShape *base)
|
|
: flags(base->flags & BaseShape::OBJECT_FLAG_MASK),
|
|
clasp(base->clasp),
|
|
parent(base->parent),
|
|
rawGetter(NULL),
|
|
rawSetter(NULL)
|
|
{}
|
|
|
|
StackBaseShape(Class *clasp, JSObject *parent, uint32_t objectFlags)
|
|
: flags(objectFlags),
|
|
clasp(clasp),
|
|
parent(parent),
|
|
rawGetter(NULL),
|
|
rawSetter(NULL)
|
|
{}
|
|
|
|
inline StackBaseShape(Shape *shape);
|
|
|
|
inline void updateGetterSetter(uint8_t attrs,
|
|
PropertyOp rawGetter,
|
|
StrictPropertyOp rawSetter);
|
|
|
|
static inline HashNumber hash(const StackBaseShape *lookup);
|
|
static inline bool match(UnownedBaseShape *key, const StackBaseShape *lookup);
|
|
};
|
|
|
|
typedef HashSet<ReadBarriered<UnownedBaseShape>,
|
|
StackBaseShape,
|
|
SystemAllocPolicy> BaseShapeSet;
|
|
|
|
struct Shape : public js::gc::Cell
|
|
{
|
|
friend struct ::JSObject;
|
|
friend struct ::JSFunction;
|
|
friend class js::Bindings;
|
|
friend class js::ObjectImpl;
|
|
friend class js::PropertyTree;
|
|
friend class js::StaticBlockObject;
|
|
friend struct js::StackShape;
|
|
friend struct js::StackBaseShape;
|
|
|
|
protected:
|
|
HeapPtrBaseShape base_;
|
|
HeapId propid_;
|
|
|
|
JS_ENUM_HEADER(SlotInfo, uint32_t)
|
|
{
|
|
/* Number of fixed slots in objects with this shape. */
|
|
FIXED_SLOTS_MAX = 0x1f,
|
|
FIXED_SLOTS_SHIFT = 27,
|
|
FIXED_SLOTS_MASK = uint32_t(FIXED_SLOTS_MAX << FIXED_SLOTS_SHIFT),
|
|
|
|
/*
|
|
* numLinearSearches starts at zero and is incremented initially on
|
|
* search() calls. Once numLinearSearches reaches LINEAR_SEARCHES_MAX,
|
|
* the table is created on the next search() call. The table can also
|
|
* be created when hashifying for dictionary mode.
|
|
*/
|
|
LINEAR_SEARCHES_MAX = 0x7,
|
|
LINEAR_SEARCHES_SHIFT = 24,
|
|
LINEAR_SEARCHES_MASK = LINEAR_SEARCHES_MAX << LINEAR_SEARCHES_SHIFT,
|
|
|
|
/*
|
|
* Mask to get the index in object slots for shapes which hasSlot().
|
|
* For !hasSlot() shapes in the property tree with a parent, stores the
|
|
* parent's slot index (which may be invalid), and invalid for all
|
|
* other shapes.
|
|
*/
|
|
SLOT_MASK = JS_BIT(24) - 1
|
|
} JS_ENUM_FOOTER(SlotInfo);
|
|
|
|
uint32_t slotInfo; /* mask of above info */
|
|
uint8_t attrs; /* attributes, see jsapi.h JSPROP_* */
|
|
uint8_t flags; /* flags, see below for defines */
|
|
int16_t shortid_; /* tinyid, or local arg/var index */
|
|
|
|
HeapPtrShape parent; /* parent node, reverse for..in order */
|
|
/* kids is valid when !inDictionary(), listp is valid when inDictionary(). */
|
|
union {
|
|
KidsPointer kids; /* null, single child, or a tagged ptr
|
|
to many-kids data structure */
|
|
HeapPtrShape *listp; /* dictionary list starting at shape_
|
|
has a double-indirect back pointer,
|
|
either to the next shape's parent if not
|
|
last, else to obj->shape_ */
|
|
};
|
|
|
|
static inline Shape *search(JSContext *cx, Shape *start, jsid id,
|
|
Shape ***pspp, bool adding = false);
|
|
|
|
#ifdef DEBUG
|
|
static inline Shape *searchNoAllocation(JSContext *cx, Shape *start, jsid id);
|
|
#endif
|
|
|
|
inline void removeFromDictionary(JSObject *obj);
|
|
inline void insertIntoDictionary(HeapPtrShape *dictp);
|
|
|
|
inline void initDictionaryShape(const StackShape &child, uint32_t nfixed,
|
|
HeapPtrShape *dictp);
|
|
|
|
Shape *getChildBinding(JSContext *cx, const StackShape &child);
|
|
|
|
/* Replace the base shape of the last shape in a non-dictionary lineage with base. */
|
|
static Shape *replaceLastProperty(JSContext *cx, const StackBaseShape &base,
|
|
JSObject *proto, Shape *shape);
|
|
|
|
bool hashify(JSContext *cx);
|
|
void handoffTableTo(Shape *newShape);
|
|
|
|
inline void setParent(js::Shape *p);
|
|
|
|
bool ensureOwnBaseShape(JSContext *cx) {
|
|
if (base()->isOwned())
|
|
return true;
|
|
return makeOwnBaseShape(cx);
|
|
}
|
|
|
|
bool makeOwnBaseShape(JSContext *cx);
|
|
|
|
public:
|
|
bool hasTable() const { return base()->hasTable(); }
|
|
js::PropertyTable &table() const { return base()->table(); }
|
|
|
|
void sizeOfExcludingThis(JSMallocSizeOfFun mallocSizeOf,
|
|
size_t *propTableSize, size_t *kidsSize) const {
|
|
*propTableSize = hasTable() ? table().sizeOfIncludingThis(mallocSizeOf) : 0;
|
|
*kidsSize = !inDictionary() && kids.isHash()
|
|
? kids.toHash()->sizeOfIncludingThis(mallocSizeOf)
|
|
: 0;
|
|
}
|
|
|
|
bool isNative() const {
|
|
JS_ASSERT(!(flags & NON_NATIVE) == getObjectClass()->isNative());
|
|
return !(flags & NON_NATIVE);
|
|
}
|
|
|
|
const HeapPtrShape &previous() const {
|
|
return parent;
|
|
}
|
|
|
|
class Range {
|
|
protected:
|
|
friend struct Shape;
|
|
const Shape *cursor;
|
|
|
|
public:
|
|
Range(const Shape *shape) : cursor(shape) { }
|
|
|
|
bool empty() const {
|
|
return cursor->isEmptyShape();
|
|
}
|
|
|
|
const Shape &front() const {
|
|
JS_ASSERT(!empty());
|
|
return *cursor;
|
|
}
|
|
|
|
void popFront() {
|
|
JS_ASSERT(!empty());
|
|
cursor = cursor->parent;
|
|
}
|
|
|
|
class Root {
|
|
js::Root<const Shape*> cursorRoot;
|
|
public:
|
|
Root(JSContext *cx, Range *range)
|
|
: cursorRoot(cx, &range->cursor)
|
|
{}
|
|
};
|
|
};
|
|
|
|
Range all() const {
|
|
return Range(this);
|
|
}
|
|
|
|
Class *getObjectClass() const { return base()->clasp; }
|
|
JSObject *getObjectParent() const { return base()->parent; }
|
|
|
|
static Shape *setObjectParent(JSContext *cx, JSObject *obj, JSObject *proto, Shape *last);
|
|
static Shape *setObjectFlag(JSContext *cx, BaseShape::Flag flag, JSObject *proto, Shape *last);
|
|
|
|
uint32_t getObjectFlags() const { return base()->getObjectFlags(); }
|
|
bool hasObjectFlag(BaseShape::Flag flag) const {
|
|
JS_ASSERT(!(flag & ~BaseShape::OBJECT_FLAG_MASK));
|
|
return !!(base()->flags & flag);
|
|
}
|
|
|
|
protected:
|
|
/*
|
|
* Implementation-private bits stored in shape->flags. See public: enum {}
|
|
* flags further below, which were allocated FCFS over time, so interleave
|
|
* with these bits.
|
|
*/
|
|
enum {
|
|
/* Property is placeholder for a non-native class. */
|
|
NON_NATIVE = 0x01,
|
|
|
|
/* Property stored in per-object dictionary, not shared property tree. */
|
|
IN_DICTIONARY = 0x02,
|
|
|
|
UNUSED_BITS = 0x3C
|
|
};
|
|
|
|
/* Get a shape identical to this one, without parent/kids information. */
|
|
Shape(const StackShape &other, uint32_t nfixed);
|
|
|
|
/* Used by EmptyShape (see jsscopeinlines.h). */
|
|
Shape(UnownedBaseShape *base, uint32_t nfixed);
|
|
|
|
/* Copy constructor disabled, to avoid misuse of the above form. */
|
|
Shape(const Shape &other) MOZ_DELETE;
|
|
|
|
/*
|
|
* Whether this shape has a valid slot value. This may be true even if
|
|
* !hasSlot() (see SlotInfo comment above), and may be false even if
|
|
* hasSlot() if the shape is being constructed and has not had a slot
|
|
* assigned yet. After construction, hasSlot() implies !hasMissingSlot().
|
|
*/
|
|
bool hasMissingSlot() const { return maybeSlot() == SHAPE_INVALID_SLOT; }
|
|
|
|
public:
|
|
/* Public bits stored in shape->flags. */
|
|
enum {
|
|
HAS_SHORTID = 0x40,
|
|
PUBLIC_FLAGS = HAS_SHORTID
|
|
};
|
|
|
|
bool inDictionary() const { return (flags & IN_DICTIONARY) != 0; }
|
|
unsigned getFlags() const { return flags & PUBLIC_FLAGS; }
|
|
bool hasShortID() const { return (flags & HAS_SHORTID) != 0; }
|
|
|
|
PropertyOp getter() const { return base()->rawGetter; }
|
|
bool hasDefaultGetter() const { return !base()->rawGetter; }
|
|
PropertyOp getterOp() const { JS_ASSERT(!hasGetterValue()); return base()->rawGetter; }
|
|
JSObject *getterObject() const { JS_ASSERT(hasGetterValue()); return base()->getterObj; }
|
|
|
|
// Per ES5, decode null getterObj as the undefined value, which encodes as null.
|
|
Value getterValue() const {
|
|
JS_ASSERT(hasGetterValue());
|
|
return base()->getterObj ? js::ObjectValue(*base()->getterObj) : js::UndefinedValue();
|
|
}
|
|
|
|
Value getterOrUndefined() const {
|
|
return (hasGetterValue() && base()->getterObj)
|
|
? ObjectValue(*base()->getterObj)
|
|
: UndefinedValue();
|
|
}
|
|
|
|
StrictPropertyOp setter() const { return base()->rawSetter; }
|
|
bool hasDefaultSetter() const { return !base()->rawSetter; }
|
|
StrictPropertyOp setterOp() const { JS_ASSERT(!hasSetterValue()); return base()->rawSetter; }
|
|
JSObject *setterObject() const { JS_ASSERT(hasSetterValue()); return base()->setterObj; }
|
|
|
|
// Per ES5, decode null setterObj as the undefined value, which encodes as null.
|
|
Value setterValue() const {
|
|
JS_ASSERT(hasSetterValue());
|
|
return base()->setterObj ? js::ObjectValue(*base()->setterObj) : js::UndefinedValue();
|
|
}
|
|
|
|
Value setterOrUndefined() const {
|
|
return (hasSetterValue() && base()->setterObj)
|
|
? ObjectValue(*base()->setterObj)
|
|
: UndefinedValue();
|
|
}
|
|
|
|
void update(js::PropertyOp getter, js::StrictPropertyOp setter, uint8_t attrs);
|
|
|
|
inline bool matches(const Shape *other) const;
|
|
inline bool matches(const StackShape &other) const;
|
|
inline bool matchesParamsAfterId(BaseShape *base,
|
|
uint32_t aslot, unsigned aattrs, unsigned aflags,
|
|
int ashortid) const;
|
|
|
|
bool get(JSContext* cx, JSObject *receiver, JSObject *obj, JSObject *pobj, js::Value* vp) const;
|
|
bool set(JSContext* cx, JSObject *obj, bool strict, js::Value* vp) const;
|
|
|
|
BaseShape *base() const { return base_; }
|
|
|
|
bool hasSlot() const { return (attrs & JSPROP_SHARED) == 0; }
|
|
uint32_t slot() const { JS_ASSERT(hasSlot() && !hasMissingSlot()); return maybeSlot(); }
|
|
uint32_t maybeSlot() const { return slotInfo & SLOT_MASK; }
|
|
|
|
bool isEmptyShape() const {
|
|
JS_ASSERT_IF(JSID_IS_EMPTY(propid_), hasMissingSlot());
|
|
return JSID_IS_EMPTY(propid_);
|
|
}
|
|
|
|
uint32_t slotSpan() const {
|
|
JS_ASSERT(!inDictionary());
|
|
uint32_t free = JSSLOT_FREE(getObjectClass());
|
|
return hasMissingSlot() ? free : Max(free, maybeSlot() + 1);
|
|
}
|
|
|
|
void setSlot(uint32_t slot) {
|
|
JS_ASSERT(slot <= SHAPE_INVALID_SLOT);
|
|
slotInfo = slotInfo & ~Shape::SLOT_MASK;
|
|
slotInfo = slotInfo | slot;
|
|
}
|
|
|
|
uint32_t numFixedSlots() const {
|
|
return (slotInfo >> FIXED_SLOTS_SHIFT);
|
|
}
|
|
|
|
void setNumFixedSlots(uint32_t nfixed) {
|
|
JS_ASSERT(nfixed < FIXED_SLOTS_MAX);
|
|
slotInfo = slotInfo & ~FIXED_SLOTS_MASK;
|
|
slotInfo = slotInfo | (nfixed << FIXED_SLOTS_SHIFT);
|
|
}
|
|
|
|
uint32_t numLinearSearches() const {
|
|
return (slotInfo & LINEAR_SEARCHES_MASK) >> LINEAR_SEARCHES_SHIFT;
|
|
}
|
|
|
|
void incrementNumLinearSearches() {
|
|
uint32_t count = numLinearSearches();
|
|
JS_ASSERT(count < LINEAR_SEARCHES_MAX);
|
|
slotInfo = slotInfo & ~LINEAR_SEARCHES_MASK;
|
|
slotInfo = slotInfo | ((count + 1) << LINEAR_SEARCHES_SHIFT);
|
|
}
|
|
|
|
const HeapId &propid() const {
|
|
JS_ASSERT(!isEmptyShape());
|
|
JS_ASSERT(!JSID_IS_VOID(propid_));
|
|
return propid_;
|
|
}
|
|
HeapId &propidRef() { JS_ASSERT(!JSID_IS_VOID(propid_)); return propid_; }
|
|
|
|
int16_t shortid() const { JS_ASSERT(hasShortID()); return maybeShortid(); }
|
|
int16_t maybeShortid() const { return shortid_; }
|
|
|
|
/*
|
|
* If SHORTID is set in shape->flags, we use shape->shortid rather
|
|
* than id when calling shape's getter or setter.
|
|
*/
|
|
jsid getUserId() const {
|
|
return hasShortID() ? INT_TO_JSID(shortid()) : propid();
|
|
}
|
|
|
|
uint8_t attributes() const { return attrs; }
|
|
bool configurable() const { return (attrs & JSPROP_PERMANENT) == 0; }
|
|
bool enumerable() const { return (attrs & JSPROP_ENUMERATE) != 0; }
|
|
bool writable() const {
|
|
// JS_ASSERT(isDataDescriptor());
|
|
return (attrs & JSPROP_READONLY) == 0;
|
|
}
|
|
bool hasGetterValue() const { return attrs & JSPROP_GETTER; }
|
|
bool hasSetterValue() const { return attrs & JSPROP_SETTER; }
|
|
|
|
bool isDataDescriptor() const {
|
|
return (attrs & (JSPROP_SETTER | JSPROP_GETTER)) == 0;
|
|
}
|
|
bool isAccessorDescriptor() const {
|
|
return (attrs & (JSPROP_SETTER | JSPROP_GETTER)) != 0;
|
|
}
|
|
|
|
/*
|
|
* For ES5 compatibility, we allow properties with PropertyOp-flavored
|
|
* setters to be shadowed when set. The "own" property thereby created in
|
|
* the directly referenced object will have the same getter and setter as
|
|
* the prototype property. See bug 552432.
|
|
*/
|
|
bool shadowable() const {
|
|
JS_ASSERT_IF(isDataDescriptor(), writable());
|
|
return hasSlot() || (attrs & JSPROP_SHADOWABLE);
|
|
}
|
|
|
|
/*
|
|
* Sometimes call objects and run-time block objects need unique shapes, but
|
|
* sometimes they don't.
|
|
*
|
|
* Property cache entries only record the shapes of the first and last
|
|
* objects along the search path, so if the search traverses more than those
|
|
* two objects, then those first and last shapes must determine the shapes
|
|
* of everything else along the path. The js_PurgeScopeChain stuff takes
|
|
* care of making this work, but that suffices only because we require that
|
|
* start points with the same shape have the same successor object in the
|
|
* search path --- a cache hit means the starting shapes were equal, which
|
|
* means the search path tail (everything but the first object in the path)
|
|
* was shared, which in turn means the effects of a purge will be seen by
|
|
* all affected starting search points.
|
|
*
|
|
* For call and run-time block objects, the "successor object" is the scope
|
|
* chain parent. Unlike prototype objects (of which there are usually few),
|
|
* scope chain parents are created frequently (possibly on every call), so
|
|
* following the shape-implies-parent rule blindly would lead one to give
|
|
* every call and block its own shape.
|
|
*
|
|
* In many cases, however, it's not actually necessary to give call and
|
|
* block objects their own shapes, and we can do better. If the code will
|
|
* always be used with the same global object, and none of the enclosing
|
|
* call objects could have bindings added to them at runtime (by direct eval
|
|
* calls or function statements), then we can use a fixed set of shapes for
|
|
* those objects. You could think of the shapes in the functions' bindings
|
|
* and compile-time blocks as uniquely identifying the global object(s) at
|
|
* the end of the scope chain.
|
|
*
|
|
* (In fact, some JSScripts we do use against multiple global objects (see
|
|
* bug 618497), and using the fixed shapes isn't sound there.)
|
|
*
|
|
* In deciding whether a call or block has any extensible parents, we
|
|
* actually only need to consider enclosing calls; blocks are never
|
|
* extensible, and the other sorts of objects that appear in the scope
|
|
* chains ('with' blocks, say) are not CacheableNonGlobalScopes.
|
|
*
|
|
* If the hasExtensibleParents flag is set for the last property in a
|
|
* script's bindings or a compiler-generated Block object, then created
|
|
* Call or Block objects need unique shapes. If the flag is clear, then we
|
|
* can use lastBinding's shape.
|
|
*/
|
|
static Shape *setExtensibleParents(JSContext *cx, Shape *shape);
|
|
bool extensibleParents() const { return !!(base()->flags & BaseShape::EXTENSIBLE_PARENTS); }
|
|
|
|
uint32_t entryCount() const {
|
|
if (hasTable())
|
|
return table().entryCount;
|
|
|
|
const js::Shape *shape = this;
|
|
uint32_t count = 0;
|
|
for (js::Shape::Range r = shape->all(); !r.empty(); r.popFront())
|
|
++count;
|
|
return count;
|
|
}
|
|
|
|
bool isBigEnoughForAPropertyTable() const {
|
|
JS_ASSERT(!hasTable());
|
|
const js::Shape *shape = this;
|
|
uint32_t count = 0;
|
|
for (js::Shape::Range r = shape->all(); !r.empty(); r.popFront()) {
|
|
++count;
|
|
if (count >= PropertyTable::MIN_ENTRIES)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
void dump(JSContext *cx, FILE *fp) const;
|
|
void dumpSubtree(JSContext *cx, int level, FILE *fp) const;
|
|
#endif
|
|
|
|
void finalize(FreeOp *fop);
|
|
void removeChild(js::Shape *child);
|
|
|
|
static inline void writeBarrierPre(const Shape *shape);
|
|
static inline void writeBarrierPost(const Shape *shape, void *addr);
|
|
|
|
/*
|
|
* All weak references need a read barrier for incremental GC. This getter
|
|
* method implements the read barrier. It's used to obtain initial shapes
|
|
* from the compartment.
|
|
*/
|
|
static inline void readBarrier(const Shape *shape);
|
|
|
|
static inline ThingRootKind rootKind() { return THING_ROOT_SHAPE; }
|
|
|
|
inline void markChildren(JSTracer *trc);
|
|
|
|
/* For JIT usage */
|
|
static inline size_t offsetOfBase() { return offsetof(Shape, base_); }
|
|
|
|
private:
|
|
static void staticAsserts() {
|
|
JS_STATIC_ASSERT(offsetof(Shape, base_) == offsetof(js::shadow::Shape, base));
|
|
JS_STATIC_ASSERT(offsetof(Shape, slotInfo) == offsetof(js::shadow::Shape, slotInfo));
|
|
JS_STATIC_ASSERT(FIXED_SLOTS_SHIFT == js::shadow::Shape::FIXED_SLOTS_SHIFT);
|
|
}
|
|
};
|
|
|
|
class RootGetterSetter
|
|
{
|
|
mozilla::Maybe<RootObject> getterRoot;
|
|
mozilla::Maybe<RootObject> setterRoot;
|
|
|
|
public:
|
|
RootGetterSetter(JSContext *cx, uint8_t attrs, PropertyOp *pgetter, StrictPropertyOp *psetter)
|
|
{
|
|
if (attrs & JSPROP_GETTER)
|
|
getterRoot.construct(cx, (JSObject **) pgetter);
|
|
if (attrs & JSPROP_SETTER)
|
|
setterRoot.construct(cx, (JSObject **) psetter);
|
|
}
|
|
};
|
|
|
|
struct EmptyShape : public js::Shape
|
|
{
|
|
EmptyShape(UnownedBaseShape *base, uint32_t nfixed);
|
|
|
|
/*
|
|
* Lookup an initial shape matching the given parameters, creating an empty
|
|
* shape if none was found.
|
|
*/
|
|
static Shape *getInitialShape(JSContext *cx, Class *clasp, JSObject *proto,
|
|
JSObject *parent, gc::AllocKind kind, uint32_t objectFlags = 0);
|
|
|
|
/*
|
|
* Reinsert an alternate initial shape, to be returned by future
|
|
* getInitialShape calls, until the new shape becomes unreachable in a GC
|
|
* and the table entry is purged.
|
|
*/
|
|
static void insertInitialShape(JSContext *cx, Shape *shape, JSObject *proto);
|
|
};
|
|
|
|
/*
|
|
* Entries for the per-compartment initialShapes set indexing initial shapes
|
|
* for objects in the compartment and the associated types.
|
|
*/
|
|
struct InitialShapeEntry
|
|
{
|
|
/*
|
|
* Initial shape to give to the object. This is an empty shape, except for
|
|
* certain classes (e.g. String, RegExp) which may add certain baked-in
|
|
* properties.
|
|
*/
|
|
ReadBarriered<Shape> shape;
|
|
|
|
/*
|
|
* Matching prototype for the entry. The shape of an object determines its
|
|
* prototype, but the prototype cannot be determined from the shape itself.
|
|
*/
|
|
JSObject *proto;
|
|
|
|
/* State used to determine a match on an initial shape. */
|
|
struct Lookup {
|
|
Class *clasp;
|
|
JSObject *proto;
|
|
JSObject *parent;
|
|
uint32_t nfixed;
|
|
uint32_t baseFlags;
|
|
Lookup(Class *clasp, JSObject *proto, JSObject *parent, uint32_t nfixed,
|
|
uint32_t baseFlags)
|
|
: clasp(clasp), proto(proto), parent(parent),
|
|
nfixed(nfixed), baseFlags(baseFlags)
|
|
{}
|
|
};
|
|
|
|
static inline HashNumber hash(const Lookup &lookup);
|
|
static inline bool match(const InitialShapeEntry &key, const Lookup &lookup);
|
|
};
|
|
|
|
typedef HashSet<InitialShapeEntry, InitialShapeEntry, SystemAllocPolicy> InitialShapeSet;
|
|
|
|
struct StackShape
|
|
{
|
|
UnownedBaseShape *base;
|
|
jsid propid;
|
|
uint32_t slot_;
|
|
uint8_t attrs;
|
|
uint8_t flags;
|
|
int16_t shortid;
|
|
|
|
StackShape(UnownedBaseShape *base, jsid propid, uint32_t slot,
|
|
uint32_t nfixed, unsigned attrs, unsigned flags, int shortid)
|
|
: base(base),
|
|
propid(propid),
|
|
slot_(slot),
|
|
attrs(uint8_t(attrs)),
|
|
flags(uint8_t(flags)),
|
|
shortid(int16_t(shortid))
|
|
{
|
|
JS_ASSERT(base);
|
|
JS_ASSERT(!JSID_IS_VOID(propid));
|
|
JS_ASSERT(slot <= SHAPE_INVALID_SLOT);
|
|
}
|
|
|
|
StackShape(const Shape *shape)
|
|
: base(shape->base()->unowned()),
|
|
propid(const_cast<Shape *>(shape)->propidRef()),
|
|
slot_(shape->slotInfo & Shape::SLOT_MASK),
|
|
attrs(shape->attrs),
|
|
flags(shape->flags),
|
|
shortid(shape->shortid_)
|
|
{}
|
|
|
|
bool hasSlot() const { return (attrs & JSPROP_SHARED) == 0; }
|
|
bool hasMissingSlot() const { return maybeSlot() == SHAPE_INVALID_SLOT; }
|
|
|
|
uint32_t slot() const { JS_ASSERT(hasSlot() && !hasMissingSlot()); return slot_; }
|
|
uint32_t maybeSlot() const { return slot_; }
|
|
|
|
uint32_t slotSpan() const {
|
|
uint32_t free = JSSLOT_FREE(base->clasp);
|
|
return hasMissingSlot() ? free : (maybeSlot() + 1);
|
|
}
|
|
|
|
void setSlot(uint32_t slot) {
|
|
JS_ASSERT(slot <= SHAPE_INVALID_SLOT);
|
|
slot_ = slot;
|
|
}
|
|
|
|
inline HashNumber hash() const;
|
|
};
|
|
|
|
/* Rooter for stack allocated shapes. */
|
|
class RootStackShape
|
|
{
|
|
Root<const UnownedBaseShape*> baseShapeRoot;
|
|
Root<const jsid> propidRoot;
|
|
|
|
public:
|
|
RootStackShape(JSContext *cx, const StackShape *shape)
|
|
: baseShapeRoot(cx, &shape->base),
|
|
propidRoot(cx, &shape->propid)
|
|
{}
|
|
};
|
|
|
|
} /* namespace js */
|
|
|
|
/* js::Shape pointer tag bit indicating a collision. */
|
|
#define SHAPE_COLLISION (uintptr_t(1))
|
|
#define SHAPE_REMOVED ((js::Shape *) SHAPE_COLLISION)
|
|
|
|
/* Macros to get and set shape pointer values and collision flags. */
|
|
#define SHAPE_IS_FREE(shape) ((shape) == NULL)
|
|
#define SHAPE_IS_REMOVED(shape) ((shape) == SHAPE_REMOVED)
|
|
#define SHAPE_IS_LIVE(shape) ((shape) > SHAPE_REMOVED)
|
|
#define SHAPE_FLAG_COLLISION(spp,shape) (*(spp) = (js::Shape *) \
|
|
(uintptr_t(shape) | SHAPE_COLLISION))
|
|
#define SHAPE_HAD_COLLISION(shape) (uintptr_t(shape) & SHAPE_COLLISION)
|
|
#define SHAPE_FETCH(spp) SHAPE_CLEAR_COLLISION(*(spp))
|
|
|
|
#define SHAPE_CLEAR_COLLISION(shape) \
|
|
((js::Shape *) (uintptr_t(shape) & ~SHAPE_COLLISION))
|
|
|
|
#define SHAPE_STORE_PRESERVING_COLLISION(spp, shape) \
|
|
(*(spp) = (js::Shape *) (uintptr_t(shape) | SHAPE_HAD_COLLISION(*(spp))))
|
|
|
|
namespace js {
|
|
|
|
inline Shape *
|
|
Shape::search(JSContext *cx, Shape *start, jsid id, Shape ***pspp, bool adding)
|
|
{
|
|
if (start->inDictionary()) {
|
|
*pspp = start->table().search(id, adding);
|
|
return SHAPE_FETCH(*pspp);
|
|
}
|
|
|
|
*pspp = NULL;
|
|
|
|
if (start->hasTable()) {
|
|
Shape **spp = start->table().search(id, adding);
|
|
return SHAPE_FETCH(spp);
|
|
}
|
|
|
|
if (start->numLinearSearches() == LINEAR_SEARCHES_MAX) {
|
|
if (start->isBigEnoughForAPropertyTable()) {
|
|
RootShape startRoot(cx, &start);
|
|
RootId idRoot(cx, &id);
|
|
if (start->hashify(cx)) {
|
|
Shape **spp = start->table().search(id, adding);
|
|
return SHAPE_FETCH(spp);
|
|
}
|
|
}
|
|
/*
|
|
* No table built -- there weren't enough entries, or OOM occurred.
|
|
* Don't increment numLinearSearches, to keep hasTable() false.
|
|
*/
|
|
JS_ASSERT(!start->hasTable());
|
|
} else {
|
|
start->incrementNumLinearSearches();
|
|
}
|
|
|
|
for (Shape *shape = start; shape; shape = shape->parent) {
|
|
if (shape->propidRef() == id)
|
|
return shape;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
/* static */ inline Shape *
|
|
Shape::searchNoAllocation(JSContext *cx, Shape *start, jsid id)
|
|
{
|
|
if (start->hasTable()) {
|
|
Shape **spp = start->table().search(id, false);
|
|
return SHAPE_FETCH(spp);
|
|
}
|
|
|
|
for (Shape *shape = start; shape; shape = shape->parent) {
|
|
if (shape->propidRef() == id)
|
|
return shape;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
#endif /* DEBUG */
|
|
|
|
} // namespace js
|
|
|
|
#ifdef _MSC_VER
|
|
#pragma warning(pop)
|
|
#pragma warning(pop)
|
|
#endif
|
|
|
|
namespace JS {
|
|
template<> class AnchorPermitted<js::Shape *> { };
|
|
template<> class AnchorPermitted<const js::Shape *> { };
|
|
}
|
|
|
|
#endif /* jsscope_h___ */
|