Rework properties
authorLars Knoll <lars.knoll@digia.com>
Sun, 28 Oct 2012 20:56:15 +0000 (21:56 +0100)
committerSimon Hausmann <simon.hausmann@digia.com>
Wed, 31 Oct 2012 11:46:49 +0000 (12:46 +0100)
This brings the basic structure or accessing properties
more in line with the EcmaScript 5.1 specification.

There's however still quite some work to be done to
make things fully compliant.

Change-Id: If55afd7ae6e4f7aa5ce06afe49b1453b537ac98b
Reviewed-by: Simon Hausmann <simon.hausmann@digia.com>
qmljs_objects.cpp
qmljs_objects.h
qmljs_runtime.cpp
qmljs_runtime.h
qv4ecmaobjects.cpp
qv4ecmaobjects_p.h

index eccee64..f7ba720 100644 (file)
@@ -69,55 +69,86 @@ void Object::setProperty(Context *ctx, const QString &name, void (*code)(Context
     setProperty(ctx, name, Value::fromObject(ctx->engine->newNativeFunction(ctx, code)));
 }
 
-Value Object::getProperty(Context *ctx, String *name, PropertyAttributes *attributes)
+Value Object::getProperty(Context *ctx, String *name)
 {
     if (name->isEqualTo(ctx->engine->id___proto__))
         return Value::fromObject(prototype);
-    else if (Value *v = getPropertyDescriptor(ctx, name, attributes))
-        return *v;
+
+    PropertyDescriptor tmp;
+    if (PropertyDescriptor *p = getPropertyDescriptor(ctx, name, &tmp)) {
+        if (p->isData())
+            return p->value;
+        if (!p->get)
+            return Value::undefinedValue();
+        FunctionObject *f = p->get->asFunctionObject();
+        if (f) {
+            f->call(ctx);
+            return ctx->result;
+        }
+    }
     return Value::undefinedValue();
 }
 
-Value *Object::getOwnProperty(Context *, String *name, PropertyAttributes *attributes)
+// Section 8.12.1
+PropertyDescriptor *Object::getOwnProperty(Context *, String *name)
 {
-    if (members) {
-        if (Property *prop = members->find(name)) {
-            if (attributes)
-                *attributes = prop->attributes;
-            return &prop->value;
-        }
-    }
+    if (members)
+        return members->find(name);
     return 0;
 }
 
-Value *Object::getPropertyDescriptor(Context *ctx, String *name, PropertyAttributes *attributes)
+PropertyDescriptor *Object::getPropertyDescriptor(Context *ctx, String *name, PropertyDescriptor *to_fill)
 {
-    if (Value *prop = getOwnProperty(ctx, name, attributes))
-        return prop;
-    else if (prototype)
-        return prototype->getPropertyDescriptor(ctx, name, attributes);
+    if (PropertyDescriptor *p = getOwnProperty(ctx, name))
+        return p;
+
+    if (prototype)
+        return prototype->getPropertyDescriptor(ctx, name, to_fill);
     return 0;
 }
 
-void Object::setProperty(Context *, String *name, const Value &value, bool flag)
+// Section 8.12.5
+void Object::setProperty(Context *ctx, String *name, const Value &value, bool throwException)
 {
-    Q_UNUSED(flag);
+    if (!canSetProperty(ctx, name)) {
+        if (throwException)
+            __qmljs_throw_type_error(ctx);
+        return;
+    }
 
     if (! members)
-        members = new Table();
+        members = new PropertyTable();
 
-    members->insert(name, value);
+    PropertyDescriptor *pd = getOwnProperty(ctx, name);
+    if (pd) {
+        if (pd->isData()) {
+            pd->value = value;
+            return;
+        }
+    }
+    PropertyDescriptor *p = members->insert(name);
+    *p = PropertyDescriptor::fromValue(value);
 }
 
+// Section 8.12.4
 bool Object::canSetProperty(Context *ctx, String *name)
 {
-    PropertyAttributes attrs = PropertyAttributes();
-    if (getOwnProperty(ctx, name, &attrs)) {
-        return attrs & WritableAttribute;
-    } else if (! prototype) {
+    if (PropertyDescriptor *p = getOwnProperty(ctx, name)) {
+        if (p->isAccessor())
+            return p->get != 0;
+        return p->isWritable();
+    }
+
+    if (! prototype)
         return extensible;
-    } else if (prototype->getPropertyDescriptor(ctx, name, &attrs)) {
-        return attrs & WritableAttribute;
+
+    PropertyDescriptor tmp;
+    if (PropertyDescriptor *p = prototype->getPropertyDescriptor(ctx, name, &tmp)) {
+        if (p->isAccessor())
+            return p->get != 0;
+        if (!extensible)
+            return false;
+        return p->isWritable();
     } else {
         return extensible;
     }
@@ -142,17 +173,26 @@ bool Object::deleteProperty(Context *, String *name, bool flag)
     return false;
 }
 
-void Object::defineOwnProperty(Context *ctx, const Value &getter, const Value &setter, bool flag)
+bool Object::defineOwnProperty(Context *ctx, String *name, const Value &getter, const Value &setter, bool flag)
 {
-    Q_UNUSED(getter);
-    Q_UNUSED(setter);
-    Q_UNUSED(flag);
-    ctx->throwUnimplemented(QStringLiteral("defineOwnProperty"));
+    if (!members)
+        members = new PropertyTable();
+
+    PropertyDescriptor *p = getOwnProperty(ctx, name);
+    if (!p) {
+        if (!extensible)
+            goto reject;
+    }
+
+  reject:
+    if (flag)
+        __qmljs_throw_type_error(ctx);
+    return false;
 }
 
 String *ForEachIteratorObject::nextPropertyName()
 {
-    Property *p = 0;
+    PropertyTableEntry *p = 0;
     while (1) {
         if (!current)
             return 0;
@@ -171,11 +211,11 @@ String *ForEachIteratorObject::nextPropertyName()
     }
 }
 
-Value ArrayObject::getProperty(Context *ctx, String *name, PropertyAttributes *attributes)
+Value ArrayObject::getProperty(Context *ctx, String *name)
 {
     if (name->isEqualTo(ctx->engine->id_length))
         return Value::fromDouble(value.size());
-    return Object::getProperty(ctx, name, attributes);
+    return Object::getProperty(ctx, name);
 }
 
 bool FunctionObject::hasInstance(Context *ctx, const Value &value)
@@ -250,7 +290,7 @@ void ScriptFunction::call(VM::Context *ctx)
     function->code(ctx, function->codeData);
 }
 
-Value RegExpObject::getProperty(Context *ctx, String *name, PropertyAttributes *attributes)
+Value RegExpObject::getProperty(Context *ctx, String *name)
 {
     QString n = name->toQString();
     if (n == QLatin1String("source"))
@@ -263,7 +303,7 @@ Value RegExpObject::getProperty(Context *ctx, String *name, PropertyAttributes *
         return Value::fromBoolean(value.patternOptions() & QRegularExpression::MultilineOption);
     else if (n == QLatin1String("lastIndex"))
         return lastIndex;
-    return Object::getProperty(ctx, name, attributes);
+    return Object::getProperty(ctx, name);
 }
 
 
@@ -277,23 +317,23 @@ void ScriptFunction::construct(VM::Context *ctx)
     function->code(ctx, function->codeData);
 }
 
-Value *ActivationObject::getPropertyDescriptor(Context *ctx, String *name, PropertyAttributes *attributes)
+PropertyDescriptor *ActivationObject::getPropertyDescriptor(Context *ctx, String *name, PropertyDescriptor *to_fill)
 {
     if (context) {
         for (unsigned int i = 0; i < context->varCount; ++i) {
             String *var = context->vars[i];
             if (__qmljs_string_equal(context, var, name)) {
-                if (attributes)
-                    *attributes = PropertyAttributes(*attributes | WritableAttribute);
-                return &context->locals[i];
+                *to_fill = PropertyDescriptor::fromValue(context->locals[i]);
+                to_fill->writable = PropertyDescriptor::Set;
+                return to_fill;
             }
         }
         for (unsigned int i = 0; i < context->formalCount; ++i) {
             String *formal = context->formals[i];
             if (__qmljs_string_equal(context, formal, name)) {
-                if (attributes)
-                    *attributes = PropertyAttributes(*attributes | WritableAttribute);
-                return &context->arguments[i];
+                *to_fill = PropertyDescriptor::fromValue(context->arguments[i]);
+                to_fill->writable = PropertyDescriptor::Set;
+                return to_fill;
             }
         }
         if (name->isEqualTo(ctx->engine->id_arguments)) {
@@ -302,29 +342,32 @@ Value *ActivationObject::getPropertyDescriptor(Context *ctx, String *name, Prope
                 arguments.objectValue()->prototype = ctx->engine->objectPrototype;
             }
 
-            return &arguments;
+            *to_fill = PropertyDescriptor::fromValue(arguments);
+            return to_fill;
         }
     }
-    if (Value *prop = Object::getPropertyDescriptor(ctx, name, attributes))
-        return prop;
-    return 0;
+
+    return Object::getPropertyDescriptor(ctx, name, to_fill);
 }
 
-Value ArgumentsObject::getProperty(Context *ctx, String *name, PropertyAttributes *attributes)
+Value ArgumentsObject::getProperty(Context *ctx, String *name)
 {
     if (name->isEqualTo(ctx->engine->id_length))
         return Value::fromDouble(context->argumentCount);
-    return Object::getProperty(ctx, name, attributes);
+    return Object::getProperty(ctx, name);
 }
 
-Value *ArgumentsObject::getPropertyDescriptor(Context *ctx, String *name, PropertyAttributes *attributes)
+PropertyDescriptor *ArgumentsObject::getPropertyDescriptor(Context *ctx, String *name, PropertyDescriptor *to_fill)
 {
     if (context) {
         const quint32 i = Value::fromString(name).toUInt32(ctx);
-        if (i < context->argumentCount)
-            return &context->arguments[i];
+        if (i < context->argumentCount) {
+            *to_fill = PropertyDescriptor::fromValue(context->arguments[i]);
+            return to_fill;
+        }
     }
-    return Object::getPropertyDescriptor(ctx, name, attributes);
+
+    return Object::getPropertyDescriptor(ctx, name, to_fill);
 }
 
 ExecutionEngine::ExecutionEngine()
index 61f43b1..425cbd5 100644 (file)
@@ -111,39 +111,80 @@ private:
     mutable unsigned _hashValue;
 };
 
-struct Property {
-    String *name;
-    Value value;
-    PropertyAttributes attributes;
-    Property *next;
-    int index;
+struct PropertyDescriptor {
+    enum Type {
+        Generic,
+        Data,
+        Accessor
+    };
+    enum State {
+        Undefined,
+        Unset,
+        Set
+    };
+    union {
+        Value value;
+        struct {
+            Object *get;
+            Object *set;
+        };
+    };
+    uint type : 8;
+    uint writable : 8;
+    uint enumberable : 8;
+    uint configurable : 8;
+
+    static inline PropertyDescriptor fromValue(Value v) {
+        PropertyDescriptor pd;
+        pd.value = v;
+        pd.type = Data;
+        pd.writable = Set;
+        pd.enumberable = Set;
+        pd.configurable = Set;
+        return pd;
+    }
+    static inline PropertyDescriptor fromAccessor(Object *getter, Object *setter) {
+        PropertyDescriptor pd;
+        pd.get = getter;
+        pd.set = setter;
+        pd.type = Accessor;
+        pd.writable = Undefined;
+        pd.enumberable = Set;
+        pd.configurable = Set;
+        return pd;
+    }
 
-    inline Property(String *name, const Value &value, PropertyAttributes flags = NoAttributes)
-    { init(name, value, flags); }
+    inline bool isData() const { return type == Data; }
+    inline bool isAccessor() const { return type == Accessor; }
+    inline bool isGeneric() const { return type == Generic; }
 
-    inline void init(String *name, const Value &value, PropertyAttributes flags = NoAttributes)
-    {
-        this->name = name;
-        this->value = value;
-        this->attributes = flags;
-        this->next = 0;
-        this->index = -1;
-    }
+    inline bool isWritable() const { return writable == Set; }
+    inline bool isEnumerable() const { return enumberable == Set; }
+    inline bool isConfigurable() const { return configurable == Set; }
+};
 
-    inline bool isWritable() const { return attributes & WritableAttribute; }
-    inline bool isEnumerable() const { return attributes & EnumerableAttribute; }
-    inline bool isConfigurable() const { return attributes & ConfigurableAttribute; }
+struct PropertyTableEntry {
+    PropertyDescriptor descriptor;
+    String *name;
+    PropertyTableEntry *next;
+    int index;
+
+    inline PropertyTableEntry(String *name)
+        : name(name),
+          next(0),
+          index(-1)
+    { }
 
     inline bool hasName(String *n) const { return name->isEqualTo(n); }
     inline unsigned hashValue() const { return name->hashValue(); }
 };
 
-class Table
+class PropertyTable
 {
-    Q_DISABLE_COPY(Table)
+    Q_DISABLE_COPY(PropertyTable)
 
 public:
-    Table()
+    PropertyTable()
         : _properties(0)
         , _buckets(0)
         , _freeList(0)
@@ -151,7 +192,7 @@ public:
         , _bucketCount(0)
         , _allocated(0) {}
 
-    ~Table()
+    ~PropertyTable()
     {
         qDeleteAll(_properties, _properties + _propertyCount + 1);
         delete[] _properties;
@@ -160,20 +201,20 @@ public:
 
     inline bool isEmpty() const { return _propertyCount == -1; }
 
-    typedef Property **iterator;
+    typedef PropertyTableEntry **iterator;
     inline iterator begin() const { return _properties; }
     inline iterator end() const { return _properties + (_propertyCount + 1); }
 
     bool remove(String *name)
     {
-        if (Property *prop = find(name)) {
+        if (PropertyTableEntry *prop = findEntry(name)) {
             // ### TODO check if the property can be removed
 
-            Property *bucket = _buckets[prop->hashValue() % _bucketCount];
+            PropertyTableEntry *bucket = _buckets[prop->hashValue() % _bucketCount];
             if (bucket == prop) {
                 bucket = bucket->next;
             } else {
-                for (Property *it = bucket; it; it = it->next) {
+                for (PropertyTableEntry *it = bucket; it; it = it->next) {
                     if (it->next == prop) {
                         it->next = it->next->next;
                         break;
@@ -189,10 +230,10 @@ public:
         return true;
     }
 
-    Property *find(String *name) const
+    PropertyTableEntry *findEntry(String *name) const
     {
         if (_properties) {
-            for (Property *prop = _buckets[name->hashValue() % _bucketCount]; prop; prop = prop->next) {
+            for (PropertyTableEntry *prop = _buckets[name->hashValue() % _bucketCount]; prop; prop = prop->next) {
                 if (prop && (prop->name == name || prop->hasName(name)))
                     return prop;
             }
@@ -201,32 +242,41 @@ public:
         return 0;
     }
 
-    Property *insert(String *name, const Value &value)
+    PropertyDescriptor *find(String *name) const
     {
-        if (Property *prop = find(name)) {
-            prop->value = value;
-            return prop;
+        if (_properties) {
+            for (PropertyTableEntry *prop = _buckets[name->hashValue() % _bucketCount]; prop; prop = prop->next) {
+                if (prop && (prop->name == name || prop->hasName(name)))
+                    return &prop->descriptor;
+            }
         }
 
+        return 0;
+    }
+
+    PropertyDescriptor *insert(String *name)
+    {
+        if (PropertyTableEntry *prop = findEntry(name))
+            return &prop->descriptor;
+
         if (++_propertyCount == _allocated) {
             if (! _allocated)
                 _allocated = 4;
             else
                 _allocated *= 2;
 
-            Property **properties = new Property*[_allocated];
+            PropertyTableEntry **properties = new PropertyTableEntry*[_allocated];
             std::copy(_properties, _properties + _propertyCount, properties);
             delete[] _properties;
             _properties = properties;
         }
 
-        Property *prop;
+        PropertyTableEntry *prop;
         if (_freeList) {
             prop = _freeList;
             _freeList = _freeList->next;
-            prop->init(name, value);
         } else {
-            prop = new Property(name, value);
+            prop = new PropertyTableEntry(name);
         }
 
         prop->index = _propertyCount;
@@ -235,12 +285,12 @@ public:
         if (! _buckets || 3 * _propertyCount >= 2 * _bucketCount) {
             rehash();
         } else {
-            Property *&bucket = _buckets[prop->hashValue() % _bucketCount];
+            PropertyTableEntry *&bucket = _buckets[prop->hashValue() % _bucketCount];
             prop->next = bucket;
             bucket = prop;
         }
 
-        return prop;
+        return &prop->descriptor;
     }
 
 private:
@@ -252,12 +302,12 @@ private:
             _bucketCount = 11;
 
         delete[] _buckets;
-        _buckets = new Property *[_bucketCount];
-        std::fill(_buckets, _buckets + _bucketCount, (Property *) 0);
+        _buckets = new PropertyTableEntry *[_bucketCount];
+        std::fill(_buckets, _buckets + _bucketCount, (PropertyTableEntry *) 0);
 
         for (int i = 0; i <= _propertyCount; ++i) {
-            Property *prop = _properties[i];
-            Property *&bucket = _buckets[prop->hashValue() % _bucketCount];
+            PropertyTableEntry *prop = _properties[i];
+            PropertyTableEntry *&bucket = _buckets[prop->hashValue() % _bucketCount];
             prop->next = bucket;
             bucket = prop;
         }
@@ -265,9 +315,9 @@ private:
 
 private:
     friend struct ForEachIteratorObject;
-    Property **_properties;
-    Property **_buckets;
-    Property *_freeList;
+    PropertyTableEntry **_properties;
+    PropertyTableEntry **_buckets;
+    PropertyTableEntry *_freeList;
     int _propertyCount;
     int _bucketCount;
     int _allocated;
@@ -276,7 +326,7 @@ private:
 struct Object {
     Object *prototype;
     String *klass;
-    Table *members;
+    PropertyTable *members;
     bool extensible;
 
     Object()
@@ -299,14 +349,14 @@ struct Object {
     virtual ActivationObject *asActivationObject() { return 0; }
     virtual ArgumentsObject *asArgumentsObject() { return 0; }
 
-    virtual Value getProperty(Context *ctx, String *name, PropertyAttributes *attributes = 0);
-    virtual Value *getOwnProperty(Context *ctx, String *name, PropertyAttributes *attributes = 0);
-    virtual Value *getPropertyDescriptor(Context *ctx, String *name, PropertyAttributes *attributes = 0);
-    virtual void setProperty(Context *ctx, String *name, const Value &value, bool flag = false);
+    virtual Value getProperty(Context *ctx, String *name);
+    virtual PropertyDescriptor *getOwnProperty(Context *ctx, String *name);
+    virtual PropertyDescriptor *getPropertyDescriptor(Context *ctx, String *name, PropertyDescriptor *to_fill);
+    virtual void setProperty(Context *ctx, String *name, const Value &value, bool throwException = false);
     virtual bool canSetProperty(Context *ctx, String *name);
     virtual bool hasProperty(Context *ctx, String *name) const;
     virtual bool deleteProperty(Context *ctx, String *name, bool flag);
-    virtual void defineOwnProperty(Context *ctx, const Value &getter, const Value &setter, bool flag = false);
+    virtual bool defineOwnProperty(Context *ctx, String *name, const Value &getter, const Value &setter, bool flag = false);
 
     //
     // helpers
@@ -359,7 +409,7 @@ struct ArrayObject: Object {
     ArrayObject(const Array &value): value(value) {}
     virtual QString className() { return QStringLiteral("Array"); }
     virtual ArrayObject *asArrayObject() { return this; }
-    virtual Value getProperty(Context *ctx, String *name, PropertyAttributes *attributes);
+    virtual Value getProperty(Context *ctx, String *name);
 };
 
 struct FunctionObject: Object {
@@ -412,7 +462,7 @@ struct RegExpObject: Object {
     RegExpObject(const QRegularExpression &value, bool global): value(value), lastIndex(Value::fromInt32(0)), global(global) {}
     virtual QString className() { return QStringLiteral("RegExp"); }
     virtual RegExpObject *asRegExpObject() { return this; }
-    virtual Value getProperty(Context *ctx, String *name, PropertyAttributes *attributes);
+    virtual Value getProperty(Context *ctx, String *name);
 };
 
 struct ErrorObject: Object {
@@ -428,7 +478,7 @@ struct ActivationObject: Object {
     ActivationObject(Context *context): context(context), arguments(Value::undefinedValue()) {}
     virtual QString className() { return QStringLiteral("Activation"); }
     virtual ActivationObject *asActivationObject() { return this; }
-    virtual Value *getPropertyDescriptor(Context *ctx, String *name, PropertyAttributes *attributes);
+    virtual PropertyDescriptor *getPropertyDescriptor(Context *ctx, String *name, PropertyDescriptor *to_fill);
 };
 
 struct ArgumentsObject: Object {
@@ -436,8 +486,8 @@ struct ArgumentsObject: Object {
     ArgumentsObject(Context *context): context(context) {}
     virtual QString className() { return QStringLiteral("Arguments"); }
     virtual ArgumentsObject *asArgumentsObject() { return this; }
-    virtual Value getProperty(Context *ctx, String *name, PropertyAttributes *attributes);
-    virtual Value *getPropertyDescriptor(Context *ctx, String *name, PropertyAttributes *attributes);
+    virtual Value getProperty(Context *ctx, String *name);
+    virtual PropertyDescriptor *getPropertyDescriptor(Context *ctx, String *name, PropertyDescriptor *to_fill);
 };
 
 struct ExecutionEngine
index 2abaf11..904a7b0 100644 (file)
@@ -235,11 +235,6 @@ Value Value::property(Context *ctx, String *name) const
     return isObject() ? objectValue()->getProperty(ctx, name) : undefinedValue();
 }
 
-Value *Value::getPropertyDescriptor(Context *ctx, String *name) const
-{
-    return isObject() ? objectValue()->getPropertyDescriptor(ctx, name) : 0;
-}
-
 void Context::init(ExecutionEngine *eng)
 {
     engine = eng;
@@ -261,8 +256,9 @@ Value *Context::lookupPropertyDescriptor(String *name)
 {
     for (Context *ctx = this; ctx; ctx = ctx->parent) {
         if (ctx->activation.isObject()) {
-            if (Value *prop = ctx->activation.objectValue()->getPropertyDescriptor(this, name)) {
-                return prop;
+            PropertyDescriptor tmp;
+            if (PropertyDescriptor *pd = ctx->activation.objectValue()->getPropertyDescriptor(this, name, &tmp)) {
+                return &pd->value;
             }
         }
     }
index 508396d..c521673 100644 (file)
@@ -66,16 +66,9 @@ enum TypeHint {
     STRING_HINT
 };
 
-enum PropertyAttributes {
-    NoAttributes          = 0,
-    ValueAttribute        = 1,
-    WritableAttribute     = 2,
-    EnumerableAttribute   = 4,
-    ConfigurableAttribute = 8
-};
-
 struct Object;
 struct String;
+struct PropertyDescriptor;
 struct Context;
 struct FunctionObject;
 struct BooleanObject;
@@ -248,7 +241,6 @@ struct Value
     ActivationObject *asArgumentsObject() const;
 
     Value property(Context *ctx, String *name) const;
-    Value *getPropertyDescriptor(Context *ctx, String *name) const;
 };
 
 extern "C" {
index ef211ea..1e75ef9 100644 (file)
@@ -530,11 +530,11 @@ void ObjectCtor::call(Context *ctx)
     ctx->result = Value::fromObject(ctx->engine->newObject());
 }
 
-Value ObjectCtor::getProperty(Context *ctx, String *name, PropertyAttributes *attributes)
+Value ObjectCtor::getProperty(Context *ctx, String *name)
 {
     if (name == ctx->engine->id_length)
         return Value::fromDouble(1);
-    return Object::getProperty(ctx, name, attributes);
+    return Object::getProperty(ctx, name);
 }
 
 void ObjectPrototype::init(Context *ctx, const Value &ctor)
@@ -586,9 +586,9 @@ void ObjectPrototype::method_getOwnPropertyNames(Context *ctx)
     else {
         ArrayObject *array = ctx->engine->newArrayObject()->asArrayObject();
         Array &a = array->value;
-        if (Table *members = O.objectValue()->members) {
-            for (Property **it = members->begin(), **end = members->end(); it != end; ++it) {
-                if (Property *prop = *it) {
+        if (PropertyTable *members = O.objectValue()->members) {
+            for (PropertyTableEntry **it = members->begin(), **end = members->end(); it != end; ++it) {
+                if (PropertyTableEntry *prop = *it) {
                     a.push(Value::fromString(prop->name));
                 }
             }
index b918973..b048404 100644 (file)
@@ -53,7 +53,7 @@ struct ObjectCtor: FunctionObject
 
     virtual void construct(Context *ctx);
     virtual void call(Context *ctx);
-    virtual Value getProperty(Context *ctx, String *name, PropertyAttributes *attributes);
+    virtual Value getProperty(Context *ctx, String *name);
 };
 
 struct ObjectPrototype: Object