Rework properties

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>
This commit is contained in:
Lars Knoll 2012-10-28 21:56:15 +01:00 committed by Simon Hausmann
parent 6ace5d1a2b
commit aa96410f3f
6 changed files with 220 additions and 139 deletions

View 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;
}
PropertyDescriptor *Object::getPropertyDescriptor(Context *ctx, String *name, PropertyDescriptor *to_fill)
{
if (PropertyDescriptor *p = getOwnProperty(ctx, name))
return p;
if (prototype)
return prototype->getPropertyDescriptor(ctx, name, to_fill);
return 0;
}
// Section 8.12.5
void Object::setProperty(Context *ctx, String *name, const Value &value, bool throwException)
{
if (!canSetProperty(ctx, name)) {
if (throwException)
__qmljs_throw_type_error(ctx);
return;
}
return 0;
}
Value *Object::getPropertyDescriptor(Context *ctx, String *name, PropertyAttributes *attributes)
{
if (Value *prop = getOwnProperty(ctx, name, attributes))
return prop;
else if (prototype)
return prototype->getPropertyDescriptor(ctx, name, attributes);
return 0;
}
void Object::setProperty(Context *, String *name, const Value &value, bool flag)
{
Q_UNUSED(flag);
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()

View 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;
inline Property(String *name, const Value &value, PropertyAttributes flags = NoAttributes)
{ init(name, value, flags); }
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;
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 bool isWritable() const { return attributes & WritableAttribute; }
inline bool isEnumerable() const { return attributes & EnumerableAttribute; }
inline bool isConfigurable() const { return attributes & ConfigurableAttribute; }
inline bool isData() const { return type == Data; }
inline bool isAccessor() const { return type == Accessor; }
inline bool isGeneric() const { return type == Generic; }
inline bool isWritable() const { return writable == Set; }
inline bool isEnumerable() const { return enumberable == Set; }
inline bool isConfigurable() const { return configurable == Set; }
};
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

View 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;
}
}
}

View 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" {

View 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));
}
}

View 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