#define njs_extern_object(vm, ext) \
(*(void **) nxt_array_item((vm)->external_objects, (ext)->external.index))
+#define njs_extern_index(vm, idx) \
+ (*(void **) nxt_array_item((vm)->external_objects, idx))
+
struct njs_extern_s {
/* A hash of inclusive njs_extern_t. */
static nxt_int_t njs_object_hash_test(nxt_lvlhsh_query_t *lhq, void *data);
static njs_ret_t njs_object_property_query(njs_vm_t *vm,
- njs_property_query_t *pq, njs_value_t *value, njs_object_t *object);
+ njs_property_query_t *pq, njs_object_t *object,
+ const njs_value_t *property);
static njs_ret_t njs_array_property_query(njs_vm_t *vm,
+ njs_property_query_t *pq, njs_array_t *array, uint32_t index);
+static njs_ret_t njs_string_property_query(njs_vm_t *vm,
njs_property_query_t *pq, njs_value_t *object, uint32_t index);
+static njs_ret_t njs_external_property_query(njs_vm_t *vm,
+ njs_property_query_t *pq, njs_value_t *object);
+static njs_ret_t njs_external_property_set(njs_vm_t *vm, njs_value_t *value,
+ njs_value_t *setval, njs_value_t *retval);
+static njs_ret_t njs_external_property_delete(njs_vm_t *vm, njs_value_t *value,
+ njs_value_t *setval, njs_value_t *retval);
static njs_ret_t njs_object_query_prop_handler(njs_property_query_t *pq,
njs_object_t *object);
static njs_ret_t njs_define_property(njs_vm_t *vm, njs_object_t *object,
/*
+ * ES5.1, 8.12.1: [[GetOwnProperty]], [[GetProperty]].
* The njs_property_query() returns values
* NXT_OK property has been found in object,
+ * retval of type njs_object_prop_t * is in pq->lhq.value.
+ * in NJS_PROPERTY_QUERY_GET
+ * prop->type is NJS_PROPERTY, NJS_METHOD or NJS_PROPERTY_HANDLER.
+ * in NJS_PROPERTY_QUERY_SET, NJS_PROPERTY_QUERY_DELETE
+ * prop->type is NJS_PROPERTY, NJS_PROPERTY_REF, NJS_METHOD or
+ * NJS_PROPERTY_HANDLER.
* NXT_DECLINED property was not found in object,
- * NJS_PRIMITIVE_VALUE property operation was applied to a numeric
- * or boolean value,
- * NJS_STRING_VALUE property operation was applied to a string,
- * NJS_ARRAY_VALUE object is array,
- * NJS_EXTERNAL_VALUE object is external entity,
+ * if pq->lhq.value != NULL it contains retval of type
+ * njs_object_prop_t * where prop->type is NJS_WHITEOUT
* NJS_TRAP the property trap must be called,
* NXT_ERROR exception has been thrown.
+ *
+ * TODO:
+ * Object.create([1,2]).length
+ * Object.defineProperty([1,2], '1', {configurable:false})
*/
njs_ret_t
njs_property_query(njs_vm_t *vm, njs_property_query_t *pq, njs_value_t *object,
- njs_value_t *property)
+ const njs_value_t *property)
{
- uint32_t index;
- uint32_t (*hash)(const void *, size_t);
- njs_ret_t ret;
- njs_object_t *obj;
- njs_function_t *function;
- const njs_extern_t *ext_proto;
+ uint32_t index;
+ uint32_t (*hash)(const void *, size_t);
+ njs_ret_t ret;
+ njs_object_t *obj;
+ njs_function_t *function;
+
+ if (nxt_slow_path(!njs_is_primitive(property))) {
+ return njs_trap(vm, NJS_TRAP_PROPERTY);
+ }
hash = nxt_djb_hash;
case NJS_BOOLEAN:
case NJS_NUMBER:
- if (pq->query != NJS_PROPERTY_QUERY_GET) {
- return NJS_PRIMITIVE_VALUE;
- }
-
index = njs_primitive_prototype_index(object->type);
obj = &vm->prototypes[index].object;
break;
case NJS_STRING:
- if (pq->query == NJS_PROPERTY_QUERY_DELETE) {
- return NXT_DECLINED;
+ if (nxt_fast_path(!njs_is_null_or_void_or_boolean(property))) {
+ index = njs_value_to_index(property);
+
+ if (nxt_fast_path(index < NJS_STRING_MAX_LENGTH)) {
+ return njs_string_property_query(vm, pq, object, index);
+ }
}
obj = &vm->prototypes[NJS_PROTOTYPE_STRING].object;
break;
- case NJS_ARRAY:
+ case NJS_OBJECT_STRING:
if (nxt_fast_path(!njs_is_null_or_void_or_boolean(property))) {
+ index = njs_value_to_index(property);
- if (nxt_fast_path(njs_is_primitive(property))) {
- index = njs_value_to_index(property);
+ if (nxt_fast_path(index < NJS_STRING_MAX_LENGTH)) {
+ ret = njs_string_property_query(vm, pq,
+ &object->data.u.object_value->value,
+ index);
- if (nxt_fast_path(index < NJS_ARRAY_MAX_LENGTH)) {
- return njs_array_property_query(vm, pq, object, index);
+ if (nxt_fast_path(ret != NXT_DECLINED)) {
+ return ret;
}
+ }
+ }
- } else {
- return njs_trap(vm, NJS_TRAP_PROPERTY);
+ obj = object->data.u.object;
+ break;
+
+ case NJS_ARRAY:
+ if (nxt_fast_path(!njs_is_null_or_void_or_boolean(property))) {
+ index = njs_value_to_index(property);
+
+ if (nxt_fast_path(index < NJS_ARRAY_MAX_LENGTH)) {
+ return njs_array_property_query(vm, pq, object->data.u.array,
+ index);
}
}
case NJS_OBJECT:
case NJS_OBJECT_BOOLEAN:
case NJS_OBJECT_NUMBER:
- case NJS_OBJECT_STRING:
case NJS_REGEXP:
case NJS_DATE:
case NJS_OBJECT_ERROR:
break;
case NJS_EXTERNAL:
- ext_proto = object->external.proto;
-
- if (ext_proto->type == NJS_EXTERN_CASELESS_OBJECT) {
- hash = nxt_djb_hash_lowcase;
- }
-
obj = NULL;
break;
case NJS_VOID:
case NJS_NULL:
default:
- if (nxt_fast_path(njs_is_primitive(property))) {
-
- ret = njs_primitive_value_to_string(vm, &pq->value, property);
+ ret = njs_primitive_value_to_string(vm, &pq->value, property);
- if (nxt_fast_path(ret == NXT_OK)) {
- njs_string_get(&pq->value, &pq->lhq.key);
- njs_type_error(vm, "cannot get property '%.*s' of undefined",
- (int) pq->lhq.key.length, pq->lhq.key.start);
- return NXT_ERROR;
- }
+ if (nxt_fast_path(ret == NXT_OK)) {
+ njs_string_get(&pq->value, &pq->lhq.key);
+ njs_type_error(vm, "cannot get property '%.*s' of undefined",
+ (int) pq->lhq.key.length, pq->lhq.key.start);
+ return NXT_ERROR;
}
njs_type_error(vm, "cannot get property 'unknown' of undefined");
return NXT_ERROR;
}
- if (nxt_fast_path(njs_is_primitive(property))) {
-
- ret = njs_primitive_value_to_string(vm, &pq->value, property);
-
- if (nxt_fast_path(ret == NXT_OK)) {
-
- njs_string_get(&pq->value, &pq->lhq.key);
- pq->lhq.key_hash = hash(pq->lhq.key.start, pq->lhq.key.length);
+ ret = njs_primitive_value_to_string(vm, &pq->value, property);
- if (obj == NULL) {
- pq->lhq.proto = &njs_extern_hash_proto;
+ if (nxt_fast_path(ret == NXT_OK)) {
- return NJS_EXTERNAL_VALUE;
- }
+ njs_string_get(&pq->value, &pq->lhq.key);
+ pq->lhq.key_hash = hash(pq->lhq.key.start, pq->lhq.key.length);
- return njs_object_property_query(vm, pq, object, obj);
+ if (obj == NULL) {
+ return njs_external_property_query(vm, pq, object);
}
- return ret;
+ return njs_object_property_query(vm, pq, obj, property);
}
- return njs_trap(vm, NJS_TRAP_PROPERTY);
+ return ret;
}
njs_ret_t
njs_object_property_query(njs_vm_t *vm, njs_property_query_t *pq,
- njs_value_t *value, njs_object_t *object)
+ njs_object_t *object, const njs_value_t *property)
{
- njs_ret_t ret;
- njs_object_prop_t *prop;
+ uint32_t index;
+ njs_ret_t ret;
+ njs_array_t *array;
+ njs_object_t *proto;
+ njs_object_prop_t *prop;
+ njs_object_value_t *ov;
pq->lhq.proto = &njs_object_hash_proto;
}
}
- do {
- pq->prototype = object;
-
- ret = nxt_lvlhsh_find(&object->hash, &pq->lhq);
+ proto = object;
- if (ret == NXT_OK) {
- prop = pq->lhq.value;
+ do {
+ pq->prototype = proto;
- if (prop->type != NJS_WHITEOUT) {
- pq->shared = 0;
+ /* length and other shared properties should be Own property */
- return ret;
- }
+ if (nxt_fast_path(!pq->own || proto == object)) {
+ ret = nxt_lvlhsh_find(&proto->hash, &pq->lhq);
- goto next;
- }
+ if (ret == NXT_OK) {
+ prop = pq->lhq.value;
- if (pq->query > NJS_PROPERTY_QUERY_IN) {
- /* NXT_DECLINED */
- return ret;
- }
+ if (prop->type != NJS_WHITEOUT) {
+ pq->shared = 0;
- ret = nxt_lvlhsh_find(&object->shared_hash, &pq->lhq);
+ return ret;
+ }
- if (ret == NXT_OK) {
- pq->shared = 1;
+ goto next;
+ }
- if (pq->query == NJS_PROPERTY_QUERY_GET) {
- prop = pq->lhq.value;
+ if (proto != object && !njs_is_null_or_void_or_boolean(property)) {
+ switch (proto->type) {
+ case NJS_ARRAY:
+ index = njs_value_to_index(property);
+ if (nxt_fast_path(index < NJS_ARRAY_MAX_LENGTH)) {
+ array = (njs_array_t *) proto;
+ return njs_array_property_query(vm, pq, array, index);
+ }
- if (prop->type == NJS_PROPERTY_HANDLER) {
- pq->scratch = *prop;
- prop = &pq->scratch;
- ret = prop->value.data.u.prop_handler(vm, value, NULL,
- &prop->value);
+ break;
- if (nxt_fast_path(ret == NXT_OK)) {
- prop->type = NJS_PROPERTY;
- pq->lhq.value = prop;
+ case NJS_OBJECT_STRING:
+ index = njs_value_to_index(property);
+ if (nxt_fast_path(index < NJS_STRING_MAX_LENGTH)) {
+ ov = (njs_object_value_t *) proto;
+ return njs_string_property_query(vm, pq, &ov->value,
+ index);
}
+
+ default:
+ break;
}
}
-
- return ret;
}
- if (pq->query > NJS_PROPERTY_QUERY_IN) {
- /* NXT_DECLINED */
+ ret = nxt_lvlhsh_find(&proto->shared_hash, &pq->lhq);
+
+ if (ret == NXT_OK) {
+ pq->shared = 1;
+
return ret;
}
- next:
-
- object = object->__proto__;
+ if (pq->query > NJS_PROPERTY_QUERY_GET) {
+ return NXT_DECLINED;
+ }
- } while (object != NULL);
+next:
- if (njs_is_string(value)) {
- return NJS_STRING_VALUE;
- }
+ proto = proto->__proto__;
- /* NXT_DECLINED */
+ } while (proto != NULL);
- return ret;
+ return NXT_DECLINED;
}
static njs_ret_t
njs_array_property_query(njs_vm_t *vm, njs_property_query_t *pq,
- njs_value_t *object, uint32_t index)
+ njs_array_t *array, uint32_t index)
{
- uint32_t size;
- njs_ret_t ret;
- njs_value_t *value;
- njs_array_t *array;
-
- array = object->data.u.array;
+ uint32_t size;
+ njs_ret_t ret;
+ njs_value_t *value;
+ njs_object_prop_t *prop;
if (index >= array->length) {
if (pq->query != NJS_PROPERTY_QUERY_SET) {
array->length = index + 1;
}
- pq->lhq.value = &array->start[index];
+ prop = &pq->scratch;
+
+ if (pq->query == NJS_PROPERTY_QUERY_GET) {
+ if (!njs_is_valid(&array->start[index])) {
+ return NXT_DECLINED;
+ }
+
+ prop->value = array->start[index];
+ prop->type = NJS_PROPERTY;
+
+ } else {
+ prop->value.data.u.value = &array->start[index];
+ prop->type = NJS_PROPERTY_REF;
+ }
- return NJS_ARRAY_VALUE;
+ prop->configurable = 1;
+ prop->enumerable = 1;
+ prop->writable = 1;
+
+ pq->lhq.value = prop;
+
+ return NXT_OK;
+}
+
+
+static njs_ret_t
+njs_string_property_query(njs_vm_t *vm, njs_property_query_t *pq,
+ njs_value_t *object, uint32_t index)
+{
+ njs_slice_prop_t slice;
+ njs_object_prop_t *prop;
+ njs_string_prop_t string;
+
+ prop = &pq->scratch;
+
+ slice.start = index;
+ slice.length = 1;
+ slice.string_length = njs_string_prop(&string, object);
+
+ if (slice.start < slice.string_length) {
+ /*
+ * A single codepoint string fits in retval
+ * so the function cannot fail.
+ */
+ (void) njs_string_slice(vm, &prop->value, &string, &slice);
+ prop->type = NJS_PROPERTY;
+ prop->configurable = 0;
+ prop->enumerable = 1;
+ prop->writable = 0;
+
+ pq->lhq.value = prop;
+
+ if (pq->query != NJS_PROPERTY_QUERY_GET) {
+ /* pq->lhq.key is used by njs_vmcode_property_set for TypeError */
+ njs_uint32_to_string(&pq->value, index);
+ njs_string_get(&pq->value, &pq->lhq.key);
+ }
+
+ return NXT_OK;
+ }
+
+ return NXT_DECLINED;
+}
+
+
+static njs_ret_t
+njs_external_property_query(njs_vm_t *vm, njs_property_query_t *pq,
+ njs_value_t *object)
+{
+ void *obj;
+ njs_ret_t ret;
+ uintptr_t data;
+ njs_object_prop_t *prop;
+ const njs_extern_t *ext_proto;
+
+ prop = &pq->scratch;
+
+ prop->type = NJS_PROPERTY;
+ prop->configurable = 0;
+ prop->enumerable = 1;
+ prop->writable = 0;
+
+ ext_proto = object->external.proto;
+
+ pq->lhq.proto = &njs_extern_hash_proto;
+ ret = nxt_lvlhsh_find(&ext_proto->hash, &pq->lhq);
+
+ if (ret == NXT_OK) {
+ ext_proto = pq->lhq.value;
+
+ prop->value.type = NJS_EXTERNAL;
+ prop->value.data.truth = 1;
+ prop->value.external.proto = ext_proto;
+ prop->value.external.index = object->external.index;
+
+ if ((ext_proto->type & NJS_EXTERN_OBJECT) != 0) {
+ goto done;
+ }
+
+ data = ext_proto->data;
+
+ } else {
+ data = (uintptr_t) &pq->lhq.key;
+ }
+
+ switch (pq->query) {
+
+ case NJS_PROPERTY_QUERY_GET:
+ if (ext_proto->get != NULL) {
+ obj = njs_extern_object(vm, object);
+ ret = ext_proto->get(vm, &prop->value, obj, data);
+ if (nxt_slow_path(ret != NXT_OK)) {
+ return ret;
+ }
+ }
+
+ break;
+
+ case NJS_PROPERTY_QUERY_SET:
+ case NJS_PROPERTY_QUERY_DELETE:
+
+ prop->type = NJS_PROPERTY_HANDLER;
+ prop->name = *object;
+
+ if (pq->query == NJS_PROPERTY_QUERY_SET) {
+ prop->writable = (ext_proto->set != NULL);
+ prop->value.data.u.prop_handler = njs_external_property_set;
+
+ } else {
+ prop->configurable = (ext_proto->find != NULL);
+ prop->value.data.u.prop_handler = njs_external_property_delete;
+ }
+
+ pq->ext_data = data;
+ pq->ext_proto = ext_proto;
+ pq->ext_index = object->external.index;
+
+ pq->lhq.value = prop;
+
+ vm->stash = (uintptr_t) pq;
+
+ return NXT_OK;
+ }
+
+done:
+
+ if (ext_proto->type == NJS_EXTERN_METHOD) {
+ prop->value.type = NJS_FUNCTION;
+ prop->value.data.u.function = ext_proto->function;
+ prop->value.data.truth = 1;
+ }
+
+ pq->lhq.value = prop;
+
+ return ret;
+}
+
+
+static njs_ret_t
+njs_external_property_set(njs_vm_t *vm, njs_value_t *value, njs_value_t *setval,
+ njs_value_t *retval)
+{
+ void *obj;
+ njs_ret_t ret;
+ nxt_str_t s;
+ njs_property_query_t *pq;
+
+ pq = (njs_property_query_t *) vm->stash;
+
+ ret = njs_vm_value_to_ext_string(vm, &s, setval, 0);
+ if (nxt_slow_path(ret != NXT_OK)) {
+ return ret;
+ }
+
+ *retval = *setval;
+
+ obj = njs_extern_index(vm, pq->ext_index);
+
+ return pq->ext_proto->set(vm, obj, pq->ext_data, &s);
+}
+
+
+static njs_ret_t
+njs_external_property_delete(njs_vm_t *vm, njs_value_t *value,
+ njs_value_t *unused, njs_value_t *unused2)
+{
+ void *obj;
+ njs_property_query_t *pq;
+
+ pq = (njs_property_query_t *) vm->stash;
+
+ obj = njs_extern_index(vm, pq->ext_index);
+
+ return pq->ext_proto->find(vm, obj, pq->ext_data, 1);
}
}
+njs_ret_t
+njs_method_private_copy(njs_vm_t *vm, njs_property_query_t *pq)
+{
+ njs_function_t *function;
+ njs_object_prop_t *prop, *shared;
+
+ prop = nxt_mem_cache_alloc(vm->mem_cache_pool, sizeof(njs_object_prop_t));
+ if (nxt_slow_path(prop == NULL)) {
+ njs_memory_error(vm);
+ return NXT_ERROR;
+ }
+
+ shared = pq->lhq.value;
+ *prop = *shared;
+
+ function = njs_function_value_copy(vm, &prop->value);
+ if (nxt_slow_path(function == NULL)) {
+ return NXT_ERROR;
+ }
+
+ pq->lhq.replace = 0;
+ pq->lhq.value = prop;
+ pq->lhq.pool = vm->mem_cache_pool;
+
+ return nxt_lvlhsh_insert(&pq->prototype->hash, &pq->lhq);
+}
+
+
njs_ret_t
njs_object_constructor(njs_vm_t *vm, njs_value_t *args, nxt_uint_t nargs,
njs_index_t unused)
njs_object_get_own_property_descriptor(njs_vm_t *vm, njs_value_t *args,
nxt_uint_t nargs, njs_index_t unused)
{
- double num;
- uint32_t index;
nxt_int_t ret;
- njs_array_t *array;
njs_object_t *descriptor;
- njs_object_prop_t *pr, *prop, array_prop;
+ njs_object_prop_t *pr, *prop;
const njs_value_t *value, *property, *setval;
nxt_lvlhsh_query_t lhq;
njs_property_query_t pq;
value = njs_arg(args, nargs, 1);
- if (!njs_is_object(value)) {
- if (njs_is_null_or_void(value)) {
- njs_type_error(vm, "cannot convert %s argument to object",
- njs_type_string(value->type));
- return NXT_ERROR;
- }
-
- vm->retval = njs_value_void;
- return NXT_OK;
+ if (njs_is_null_or_void(value)) {
+ njs_type_error(vm, "cannot convert %s argument to object",
+ njs_type_string(value->type));
+ return NXT_ERROR;
}
- prop = NULL;
property = njs_arg(args, nargs, 2);
- if (njs_is_array(value)) {
- array = value->data.u.array;
- num = njs_string_to_index(property);
- index = num;
+ njs_property_query_init(&pq, NJS_PROPERTY_QUERY_GET, 1);
- if ((double) index == num
- && index < array->length
- && njs_is_valid(&array->start[index]))
- {
- prop = &array_prop;
+ ret = njs_property_query(vm, &pq, (njs_value_t *) value, property);
- array_prop.name = *property;
- array_prop.value = array->start[index];
+ switch (ret) {
+ case NXT_OK:
+ break;
- array_prop.configurable = 1;
- array_prop.enumerable = 1;
- array_prop.writable = 1;
- }
+ case NXT_DECLINED:
+ vm->retval = njs_value_void;
+ return NXT_OK;
+
+ case NJS_TRAP:
+ case NXT_ERROR:
+ default:
+ return ret;
}
- lhq.proto = &njs_object_hash_proto;
+ prop = pq.lhq.value;
+
+ switch (prop->type) {
+ case NJS_PROPERTY:
+ break;
+
+ case NJS_PROPERTY_HANDLER:
+ pq.scratch = *prop;
+ prop = &pq.scratch;
+ ret = prop->value.data.u.prop_handler(vm, (njs_value_t *) value,
+ NULL, &prop->value);
+ if (nxt_slow_path(ret != NXT_OK)) {
+ return ret;
+ }
+
+ break;
- if (prop == NULL) {
- pq.query = NJS_PROPERTY_QUERY_GET;
- pq.lhq.key.length = 0;
- pq.lhq.key.start = NULL;
+ case NJS_METHOD:
+ if (pq.shared) {
+ ret = njs_method_private_copy(vm, &pq);
- ret = njs_property_query(vm, &pq, (njs_value_t *) value,
- (njs_value_t *) property);
+ if (nxt_slow_path(ret != NXT_OK)) {
+ return ret;
+ }
- if (ret != NXT_OK) {
- vm->retval = njs_value_void;
- return NXT_OK;
+ prop = pq.lhq.value;
}
- prop = pq.lhq.value;
+ break;
+
+ default:
+ njs_type_error(vm, "unexpected property type: %s",
+ njs_prop_type_string(prop->type));
+ return NXT_ERROR;
}
descriptor = njs_object_alloc(vm);
return NXT_ERROR;
}
+ lhq.proto = &njs_object_hash_proto;
lhq.replace = 0;
lhq.pool = vm->mem_cache_pool;
lhq.proto = &njs_object_hash_proto;
njs_object_prototype_properties,
nxt_nitems(njs_object_prototype_properties),
};
+
+
+const char *
+njs_prop_type_string(njs_object_property_type_t type)
+{
+ switch (type) {
+ case NJS_PROPERTY_REF:
+ return "property_ref";
+
+ case NJS_METHOD:
+ return "method";
+
+ case NJS_PROPERTY_HANDLER:
+ return "property handler";
+
+ case NJS_WHITEOUT:
+ return "whiteout";
+
+ case NJS_PROPERTY:
+ return "property";
+
+ default:
+ return "unknown";
+ }
+}
typedef enum {
NJS_PROPERTY = 0,
- NJS_GETTER,
- NJS_SETTER,
+ NJS_PROPERTY_REF,
NJS_METHOD,
NJS_PROPERTY_HANDLER,
NJS_WHITEOUT,
/* scratch is used to get the value of an NJS_PROPERTY_HANDLER property. */
njs_object_prop_t scratch;
+ /* These three fields are used for NJS_EXTERNAL setters. */
+ uintptr_t ext_data;
+ const njs_extern_t *ext_proto;
+ uint32_t ext_index;
+
njs_value_t value;
njs_object_t *prototype;
uint8_t query;
uint8_t shared;
+ uint8_t own;
} njs_property_query_t;
+#define njs_property_query_init(pq, _query, _own) \
+ do { \
+ (pq)->lhq.key.length = 0; \
+ (pq)->lhq.value = NULL; \
+ (pq)->query = _query; \
+ (pq)->own = _own; \
+ } while (0)
+
struct njs_object_init_s {
nxt_str_t name;
njs_object_t *njs_object_value_alloc(njs_vm_t *vm, const njs_value_t *value,
nxt_uint_t type);
njs_array_t *njs_object_keys_array(njs_vm_t *vm, const njs_value_t *object);
+njs_ret_t njs_value_property(njs_vm_t *vm, njs_value_t *value,
+ const njs_value_t *property, njs_value_t *retval);
njs_object_prop_t *njs_object_property(njs_vm_t *vm, const njs_object_t *obj,
nxt_lvlhsh_query_t *lhq);
njs_ret_t njs_property_query(njs_vm_t *vm, njs_property_query_t *pq,
- njs_value_t *object, njs_value_t *property);
+ njs_value_t *object, const njs_value_t *property);
nxt_int_t njs_object_hash_create(njs_vm_t *vm, nxt_lvlhsh_t *hash,
const njs_object_prop_t *prop, nxt_uint_t n);
njs_ret_t njs_object_constructor(njs_vm_t *vm, njs_value_t *args,
njs_ret_t njs_object_prototype_to_string(njs_vm_t *vm, njs_value_t *args,
nxt_uint_t nargs, njs_index_t unused);
+njs_ret_t njs_method_private_copy(njs_vm_t *vm, njs_property_query_t *pq);
+const char * njs_prop_type_string(njs_object_property_type_t type);
+
extern const njs_object_init_t njs_object_constructor_init;
extern const njs_object_init_t njs_object_prototype_init;
static nxt_noinline njs_ret_t njs_string_concat(njs_vm_t *vm,
njs_value_t *val1, njs_value_t *val2);
-static njs_ret_t njs_method_private_copy(njs_vm_t *vm,
- njs_property_query_t *pq);
static nxt_noinline njs_ret_t njs_values_equal(njs_vm_t *vm,
const njs_value_t *val1, const njs_value_t *val2);
static nxt_noinline njs_ret_t njs_values_compare(njs_vm_t *vm,
njs_vmcode_property_get(njs_vm_t *vm, njs_value_t *object,
njs_value_t *property)
{
- void *obj;
- int32_t index;
- uintptr_t data;
- njs_ret_t ret;
- njs_value_t *val, ext_val;
- njs_slice_prop_t slice;
- njs_string_prop_t string;
- njs_object_prop_t *prop;
- const njs_value_t *retval;
- const njs_extern_t *ext_proto;
- njs_property_query_t pq;
-
- pq.query = NJS_PROPERTY_QUERY_GET;
-
- ret = njs_property_query(vm, &pq, object, property);
-
- retval = &njs_value_void;
-
- switch (ret) {
-
- case NXT_OK:
- prop = pq.lhq.value;
-
- switch (prop->type) {
-
- case NJS_METHOD:
- if (pq.shared) {
- ret = njs_method_private_copy(vm, &pq);
-
- if (nxt_slow_path(ret != NXT_OK)) {
- return ret;
- }
-
- prop = pq.lhq.value;
- }
-
- /* Fall through. */
-
- case NJS_PROPERTY:
- retval = &prop->value;
- break;
-
- default:
- nxt_thread_log_alert("invalid property get type:%d", prop->type);
-
- return NXT_ERROR;
- }
-
- break;
-
- case NXT_DECLINED:
- case NJS_PRIMITIVE_VALUE:
- break;
-
- case NJS_STRING_VALUE:
-
- /* string[n]. */
-
- index = (int32_t) njs_value_to_index(property);
-
- if (nxt_fast_path(index >= 0)) {
- slice.start = index;
- slice.length = 1;
- slice.string_length = njs_string_prop(&string, object);
-
- if (slice.start < slice.string_length) {
- /*
- * A single codepoint string fits in vm->retval
- * so the function cannot fail.
- */
- (void) njs_string_slice(vm, &vm->retval, &string, &slice);
-
- return sizeof(njs_vmcode_prop_get_t);
- }
- }
-
- break;
-
- case NJS_ARRAY_VALUE:
- val = pq.lhq.value;
-
- if (njs_is_valid(val)) {
- retval = val;
- }
-
- break;
-
- case NJS_EXTERNAL_VALUE:
- ext_proto = object->external.proto;
-
- ret = nxt_lvlhsh_find(&ext_proto->hash, &pq.lhq);
-
- if (ret == NXT_OK) {
- ext_proto = pq.lhq.value;
-
- ext_val.type = NJS_EXTERNAL;
- ext_val.data.truth = 1;
- ext_val.external.proto = ext_proto;
- ext_val.external.index = object->external.index;
-
- if ((ext_proto->type & NJS_EXTERN_OBJECT) != 0) {
- retval = &ext_val;
- break;
- }
-
- data = ext_proto->data;
-
- } else {
- data = (uintptr_t) &pq.lhq.key;
- }
-
- vm->retval = njs_value_void;
-
- if (ext_proto->get != NULL) {
- obj = njs_extern_object(vm, object);
-
- ret = ext_proto->get(vm, &vm->retval, obj, data);
- if (nxt_slow_path(ret != NXT_OK)) {
- return ret;
- }
-
- /* The vm->retval is already retained by ext_proto->get(). */
- }
-
- if (ext_proto->type == NJS_EXTERN_METHOD) {
- vm->retval.data.u.function = ext_proto->function;
- vm->retval.type = NJS_FUNCTION;
- vm->retval.data.truth = 1;
- }
+ njs_ret_t ret;
+ ret = njs_value_property(vm, object, property, &vm->retval);
+ if (ret == NXT_OK || ret == NXT_DECLINED) {
return sizeof(njs_vmcode_prop_get_t);
-
- case NJS_TRAP:
- case NXT_ERROR:
- default:
-
- return ret;
}
- vm->retval = *retval;
-
- /* GC: njs_retain(retval) */
-
- return sizeof(njs_vmcode_prop_get_t);
+ return ret;
}
njs_vmcode_property_set(njs_vm_t *vm, njs_value_t *object,
njs_value_t *property)
{
- void *obj;
- uintptr_t data;
- nxt_str_t s;
njs_ret_t ret;
- njs_value_t *p, *value;
+ njs_value_t *value;
njs_object_prop_t *prop;
- const njs_extern_t *ext_proto;
njs_property_query_t pq;
njs_vmcode_prop_set_t *code;
code = (njs_vmcode_prop_set_t *) vm->current;
value = njs_vmcode_operand(vm, code->value);
- pq.lhq.key.length = 0;
- pq.query = NJS_PROPERTY_QUERY_SET;
+ njs_property_query_init(&pq, NJS_PROPERTY_QUERY_SET, 0);
ret = njs_property_query(vm, &pq, object, property);
case NXT_OK:
prop = pq.lhq.value;
- if (prop->type == NJS_PROPERTY_HANDLER && prop->writable) {
- ret = prop->value.data.u.prop_handler(vm, object, value,
- &vm->retval);
- if (nxt_slow_path(ret != NXT_OK)) {
- return ret;
- }
+ switch (prop->type) {
+ case NJS_PROPERTY:
+ break;
+ case NJS_PROPERTY_REF:
+ *prop->value.data.u.value = *value;
return sizeof(njs_vmcode_prop_set_t);
+
+ case NJS_PROPERTY_HANDLER:
+ if (prop->writable) {
+ ret = prop->value.data.u.prop_handler(vm, object, value,
+ &vm->retval);
+ if (nxt_slow_path(ret != NXT_OK)) {
+ return ret;
+ }
+
+ return sizeof(njs_vmcode_prop_set_t);
+ }
+
+ break;
+
+ default:
+ njs_internal_error(vm, "unexpected property type '%s' "
+ "while setting",
+ njs_prop_type_string(prop->type));
+
+ return NXT_ERROR;
}
break;
ret = nxt_lvlhsh_insert(&object->data.u.object->hash, &pq.lhq);
if (nxt_slow_path(ret != NXT_OK)) {
- /* Only NXT_ERROR can be returned here. */
- return ret;
+ njs_internal_error(vm, "lvlhsh insert failed");
+ return NXT_ERROR;
}
break;
- case NJS_PRIMITIVE_VALUE:
- case NJS_STRING_VALUE:
- return sizeof(njs_vmcode_prop_set_t);
-
- case NJS_ARRAY_VALUE:
- p = pq.lhq.value;
- *p = *value;
-
- return sizeof(njs_vmcode_prop_set_t);
-
- case NJS_EXTERNAL_VALUE:
- ext_proto = object->external.proto;
-
- ret = nxt_lvlhsh_find(&ext_proto->hash, &pq.lhq);
-
- if (ret == NXT_OK) {
- ext_proto = pq.lhq.value;
- data = ext_proto->data;
-
- } else {
- data = (uintptr_t) &pq.lhq.key;
- }
-
- if (ext_proto->set != NULL) {
- ret = njs_vm_value_to_ext_string(vm, &s, value, 0);
- if (nxt_slow_path(ret != NXT_OK)) {
- return ret;
- }
-
- /* TODO retain value if it is string. */
-
- obj = njs_extern_object(vm, object);
-
- ret = ext_proto->set(vm, obj, data, &s);
- if (nxt_slow_path(ret != NXT_OK)) {
- return ret;
- }
- }
-
- return sizeof(njs_vmcode_prop_set_t);
-
case NJS_TRAP:
case NXT_ERROR:
default:
njs_ret_t
njs_vmcode_property_in(njs_vm_t *vm, njs_value_t *object, njs_value_t *property)
{
- void *obj;
- uintptr_t data;
njs_ret_t ret;
- njs_value_t *value;
+ njs_object_prop_t *prop;
const njs_value_t *retval;
- const njs_extern_t *ext_proto;
njs_property_query_t pq;
retval = &njs_value_false;
- pq.query = NJS_PROPERTY_QUERY_IN;
+ njs_property_query_init(&pq, NJS_PROPERTY_QUERY_GET, 0);
ret = njs_property_query(vm, &pq, object, property);
switch (ret) {
case NXT_OK:
- retval = &njs_value_true;
- break;
-
- case NXT_DECLINED:
- break;
-
- case NJS_PRIMITIVE_VALUE:
- case NJS_STRING_VALUE:
- njs_type_error(vm, "property in on a primitive value");
-
- return NXT_ERROR;
-
- case NJS_ARRAY_VALUE:
- value = pq.lhq.value;
+ prop = pq.lhq.value;
- if (njs_is_valid(value)) {
- retval = &njs_value_true;
+ if (!njs_is_valid(&prop->value)) {
+ break;
}
+ retval = &njs_value_true;
break;
- case NJS_EXTERNAL_VALUE:
- ext_proto = object->external.proto;
-
- ret = nxt_lvlhsh_find(&ext_proto->hash, &pq.lhq);
-
- if (ret == NXT_OK) {
- retval = &njs_value_true;
-
- } else {
- data = (uintptr_t) &pq.lhq.key;
-
- if (ext_proto->find != NULL) {
- obj = njs_extern_object(vm, object);
-
- ret = ext_proto->find(vm, obj, data, 0);
-
- if (nxt_slow_path(ret == NXT_ERROR)) {
- return ret;
- }
+ case NXT_DECLINED:
+ if (!njs_is_object(object) && !njs_is_external(object)) {
+ njs_type_error(vm, "property in on a primitive value");
- if (ret == NXT_OK) {
- retval = &njs_value_true;
- }
- }
+ return NXT_ERROR;
}
break;
njs_vmcode_property_delete(njs_vm_t *vm, njs_value_t *object,
njs_value_t *property)
{
- void *obj;
- uintptr_t data;
njs_ret_t ret;
- njs_value_t *value, ext_val;
const njs_value_t *retval;
njs_object_prop_t *prop;
- const njs_extern_t *ext_proto;
njs_property_query_t pq;
retval = &njs_value_false;
- pq.lhq.key.length = 0;
- pq.query = NJS_PROPERTY_QUERY_DELETE;
+ njs_property_query_init(&pq, NJS_PROPERTY_QUERY_DELETE, 1);
ret = njs_property_query(vm, &pq, object, property);
case NXT_OK:
prop = pq.lhq.value;
+ switch (prop->type) {
+ case NJS_PROPERTY:
+ case NJS_METHOD:
+ break;
+
+ case NJS_PROPERTY_REF:
+ njs_set_invalid(prop->value.data.u.value);
+ retval = &njs_value_true;
+ goto done;
+
+ case NJS_PROPERTY_HANDLER:
+ if (prop->configurable) {
+ ret = prop->value.data.u.prop_handler(vm, object, NULL, NULL);
+ if (nxt_slow_path(ret != NXT_OK)) {
+ return ret;
+ }
+
+ retval = &njs_value_true;
+ goto done;
+ }
+
+ break;
+
+ default:
+ njs_internal_error(vm, "unexpected property type '%s' "
+ "while deleting",
+ njs_prop_type_string(prop->type));
+
+ return NXT_ERROR;
+ }
+
if (nxt_slow_path(!prop->configurable)) {
njs_type_error(vm, "Cannot delete property '%.*s' of %s",
pq.lhq.key.length, pq.lhq.key.start,
break;
case NXT_DECLINED:
- case NJS_PRIMITIVE_VALUE:
- case NJS_STRING_VALUE:
- break;
-
- case NJS_ARRAY_VALUE:
- value = pq.lhq.value;
- njs_set_invalid(value);
- retval = &njs_value_true;
- break;
-
- case NJS_EXTERNAL_VALUE:
-
- ext_proto = object->external.proto;
-
- ret = nxt_lvlhsh_find(&ext_proto->hash, &pq.lhq);
-
- if (ret == NXT_OK) {
- ext_proto = pq.lhq.value;
-
- if ((ext_proto->type & NJS_EXTERN_OBJECT) != 0) {
-
- ext_val.type = NJS_EXTERNAL;
- ext_val.data.truth = 1;
- ext_val.external.proto = ext_proto;
- ext_val.external.index = object->external.index;
-
- data = (uintptr_t) &ext_val;
-
- } else {
- data = ext_proto->data;
- }
-
- } else {
- data = (uintptr_t) &pq.lhq.key;
- }
-
- if (ext_proto->find != NULL) {
- obj = njs_extern_object(vm, object);
-
- ret = ext_proto->find(vm, obj, data, 1);
-
- if (nxt_slow_path(ret == NXT_ERROR)) {
- return ret;
- }
-
- if (ret == NXT_OK) {
- retval = &njs_value_true;
- }
- }
-
break;
case NJS_TRAP:
return ret;
}
+done:
+
vm->retval = *retval;
return sizeof(njs_vmcode_3addr_t);
}
-static njs_ret_t
-njs_method_private_copy(njs_vm_t *vm, njs_property_query_t *pq)
-{
- njs_function_t *function;
- njs_object_prop_t *prop, *shared;
-
- prop = nxt_mem_cache_alloc(vm->mem_cache_pool, sizeof(njs_object_prop_t));
- if (nxt_slow_path(prop == NULL)) {
- njs_memory_error(vm);
- return NXT_ERROR;
- }
-
- shared = pq->lhq.value;
- *prop = *shared;
-
- function = njs_function_value_copy(vm, &prop->value);
- if (nxt_slow_path(function == NULL)) {
- return NXT_ERROR;
- }
-
- pq->lhq.replace = 0;
- pq->lhq.value = prop;
- pq->lhq.pool = vm->mem_cache_pool;
-
- return nxt_lvlhsh_insert(&pq->prototype->hash, &pq->lhq);
-}
-
-
njs_ret_t
njs_vmcode_property_foreach(njs_vm_t *vm, njs_value_t *object,
njs_value_t *invld)
njs_vmcode_instance_of(njs_vm_t *vm, njs_value_t *object,
njs_value_t *constructor)
{
- nxt_int_t ret;
- njs_value_t *value;
- njs_object_t *prototype, *proto;
- njs_object_prop_t *prop;
- const njs_value_t *retval;
- njs_property_query_t pq;
+ nxt_int_t ret;
+ njs_value_t value;
+ njs_object_t *prototype, *proto;
+ const njs_value_t *retval;
static njs_value_t prototype_string = njs_string("prototype");
retval = &njs_value_false;
if (njs_is_object(object)) {
- pq.query = NJS_PROPERTY_QUERY_GET;
-
- ret = njs_property_query(vm, &pq, constructor, &prototype_string);
+ value = njs_value_void;
+ ret = njs_value_property(vm, constructor, &prototype_string, &value);
if (nxt_fast_path(ret == NXT_OK)) {
- prop = pq.lhq.value;
- value = &prop->value;
- /* TODO: test prop->value is object. */
+ if (nxt_slow_path(!njs_is_object(&value))) {
+ njs_internal_error(vm, "prototype is not an object");
+ return NXT_ERROR;
+ }
- prototype = value->data.u.object;
+ prototype = value.data.u.object;
proto = object->data.u.object;
do {
njs_vmcode_method_frame(njs_vm_t *vm, njs_value_t *object, njs_value_t *name)
{
njs_ret_t ret;
+ nxt_str_t string;
njs_value_t *value;
njs_object_prop_t *prop;
njs_property_query_t pq;
- const njs_extern_t *ext_proto;
njs_vmcode_method_frame_t *method;
+ value = NULL;
method = (njs_vmcode_method_frame_t *) vm->current;
- pq.lhq.key.length = 0;
- pq.lhq.key.start = NULL;
- pq.query = NJS_PROPERTY_QUERY_GET;
+ njs_property_query_init(&pq, NJS_PROPERTY_QUERY_GET, 0);
ret = njs_property_query(vm, &pq, object, name);
case NXT_OK:
prop = pq.lhq.value;
- ret = njs_function_frame_create(vm, &prop->value, object, method->nargs,
- method->code.ctor);
- break;
-
- case NJS_ARRAY_VALUE:
- value = pq.lhq.value;
+ switch (prop->type) {
+ case NJS_PROPERTY:
+ case NJS_METHOD:
+ break;
- ret = njs_function_frame_create(vm, value, object, method->nargs,
- method->code.ctor);
- break;
+ case NJS_PROPERTY_HANDLER:
+ pq.scratch = *prop;
+ prop = &pq.scratch;
+ ret = prop->value.data.u.prop_handler(vm, object, NULL,
+ &prop->value);
+ if (nxt_slow_path(ret != NXT_OK)) {
+ return ret;
+ }
- case NJS_EXTERNAL_VALUE:
- ext_proto = object->external.proto;
+ break;
- ret = nxt_lvlhsh_find(&ext_proto->hash, &pq.lhq);
+ default:
+ njs_internal_error(vm, "unexpected property type '%s' "
+ "while getting method",
+ njs_prop_type_string(prop->type));
- if (nxt_slow_path(ret != NXT_OK)) {
- njs_type_error(vm,
- "cannot find property '%.*s' of an external object",
- (int) pq.lhq.key.length, pq.lhq.key.start);
return NXT_ERROR;
-
}
- ext_proto = pq.lhq.value;
+ value = &prop->value;
- if (nxt_slow_path(ext_proto->type != NJS_EXTERN_METHOD)) {
- njs_type_error(vm,
- "method '%.*s' of an external object is not callable",
- (int) pq.lhq.key.length, pq.lhq.key.start);
- return NXT_ERROR;
- }
+ break;
- ret = njs_function_native_frame(vm, ext_proto->function, object, NULL,
- method->nargs, 0, method->code.ctor);
+ case NXT_DECLINED:
break;
+ case NJS_TRAP:
case NXT_ERROR:
- /* An exception was set in njs_property_query(). */
- return NXT_ERROR;
-
default:
- njs_internal_error(vm, "method '%.*s' query failed:%d",
- (int) pq.lhq.key.length, pq.lhq.key.start, ret);
+
+ return ret;
+ }
+
+ if (value == NULL || !njs_is_function(value)) {
+ njs_string_get(name, &string);
+ njs_type_error(vm, "'%.*s' is not a function", (int) string.length,
+ string.start);
return NXT_ERROR;
}
+ ret = njs_function_frame_create(vm, value, object, method->nargs,
+ method->code.ctor);
+
if (nxt_fast_path(ret == NXT_OK)) {
return sizeof(njs_vmcode_method_frame_t);
}
}
+static njs_ret_t
+njs_vmcode_restart(njs_vm_t *vm, njs_value_t *invld1, njs_value_t *invld2)
+{
+ u_char *restart;
+ njs_ret_t ret;
+ njs_value_t *retval, *value1;
+ njs_native_frame_t *frame;
+ njs_vmcode_generic_t *vmcode;
+
+ frame = vm->top_frame;
+ restart = frame->trap_restart;
+ frame->trap_restart = NULL;
+ vm->current = restart;
+ vmcode = (njs_vmcode_generic_t *) restart;
+
+ value1 = &frame->trap_values[0];
+
+ if (frame->trap_reference) {
+ value1 = value1->data.u.value;
+ }
+
+ ret = vmcode->code.operation(vm, value1, &frame->trap_values[1]);
+
+ if (nxt_slow_path(ret == NJS_TRAP)) {
+ /* Trap handlers are not reentrant. */
+ njs_internal_error(vm, "trap inside restart instruction");
+ return NXT_ERROR;
+ }
+
+ retval = njs_vmcode_operand(vm, vmcode->operand1);
+
+ //njs_release(vm, retval);
+
+ *retval = vm->retval;
+
+ return ret;
+}
+
+
/*
* A hint value is 0 for numbers and 1 for strings. The value chooses
* method calls order specified by ECMAScript 5.1: "valueOf", "toString"
}
-static njs_ret_t
-njs_vmcode_restart(njs_vm_t *vm, njs_value_t *invld1, njs_value_t *invld2)
+/*
+ * ES5.1, 8.12.3: [[Get]].
+ * NXT_OK property has been found in object,
+ * retval will contain the property's value
+ *
+ * NXT_DECLINED property was not found in object,
+ * NJS_TRAP the property trap must be called,
+ * NXT_ERROR exception has been thrown.
+ * retval will contain undefined
+ */
+njs_ret_t
+njs_value_property(njs_vm_t *vm, njs_value_t *value,
+ const njs_value_t *property, njs_value_t *retval)
{
- u_char *restart;
njs_ret_t ret;
- njs_value_t *retval, *value1;
- njs_native_frame_t *frame;
- njs_vmcode_generic_t *vmcode;
+ njs_object_prop_t *prop;
+ njs_property_query_t pq;
- frame = vm->top_frame;
- restart = frame->trap_restart;
- frame->trap_restart = NULL;
- vm->current = restart;
- vmcode = (njs_vmcode_generic_t *) restart;
+ njs_property_query_init(&pq, NJS_PROPERTY_QUERY_GET, 0);
- value1 = &frame->trap_values[0];
+ ret = njs_property_query(vm, &pq, value, property);
- if (frame->trap_reference) {
- value1 = value1->data.u.value;
- }
+ switch (ret) {
- ret = vmcode->code.operation(vm, value1, &frame->trap_values[1]);
+ case NXT_OK:
+ prop = pq.lhq.value;
- if (nxt_slow_path(ret == NJS_TRAP)) {
- /* Trap handlers are not reentrant. */
- njs_internal_error(vm, "trap inside restart instruction");
- return NXT_ERROR;
- }
+ switch (prop->type) {
- retval = njs_vmcode_operand(vm, vmcode->operand1);
+ case NJS_METHOD:
+ if (pq.shared) {
+ ret = njs_method_private_copy(vm, &pq);
- //njs_release(vm, retval);
+ if (nxt_slow_path(ret != NXT_OK)) {
+ return ret;
+ }
- *retval = vm->retval;
+ prop = pq.lhq.value;
+ }
- return ret;
+ /* Fall through. */
+
+ case NJS_PROPERTY:
+ *retval = prop->value;
+ break;
+
+ case NJS_PROPERTY_HANDLER:
+ pq.scratch = *prop;
+ prop = &pq.scratch;
+ ret = prop->value.data.u.prop_handler(vm, value, NULL,
+ &prop->value);
+
+ if (nxt_slow_path(ret != NXT_OK)) {
+ return ret;
+ }
+
+ *retval = prop->value;
+ break;
+
+ default:
+ njs_internal_error(vm, "unexpected property type '%s' "
+ "while getting",
+ njs_prop_type_string(prop->type));
+
+ return NXT_ERROR;
+ }
+
+ break;
+
+ case NXT_DECLINED:
+ *retval = njs_value_void;
+
+ return NXT_DECLINED;
+
+ case NJS_TRAP:
+ case NXT_ERROR:
+ default:
+
+ return ret;
+ }
+
+ return NXT_OK;
}
#define NJS_APPLIED NXT_DONE
-/* The values must be greater than NXT_OK. */
-#define NJS_PRIMITIVE_VALUE 1
-#define NJS_STRING_VALUE 2
-#define NJS_ARRAY_VALUE 3
-#define NJS_EXTERNAL_VALUE 4
-
-
/*
- * NJS_PROPERTY_QUERY_GET must be less or equal to NJS_PROPERTY_QUERY_IN,
- * NJS_PROPERTY_QUERY_SET and NJS_PROPERTY_QUERY_DELETE must be greater
- * than NJS_PROPERTY_QUERY_IN.
+ * NJS_PROPERTY_QUERY_GET must be less to NJS_PROPERTY_QUERY_SET
+ * and NJS_PROPERTY_QUERY_DELETE.
*/
#define NJS_PROPERTY_QUERY_GET 0
-#define NJS_PROPERTY_QUERY_IN 1
-#define NJS_PROPERTY_QUERY_SET 2
-#define NJS_PROPERTY_QUERY_DELETE 3
+#define NJS_PROPERTY_QUERY_SET 1
+#define NJS_PROPERTY_QUERY_DELETE 2
/*
nxt_array_t *backtrace;
njs_trap_t trap:8;
+
+ /*
+ * njs_property_query() uses it to store reference to a temporary
+ * PROPERTY_HANDLERs for NJS_EXTERNAL values in NJS_PROPERTY_QUERY_SET
+ * and NJS_PROPERTY_QUERY_DELETE modes.
+ */
+ uintptr_t stash; /* njs_property_query_t * */
};
njs_test {
{"console.ll()\r\n"
- "console.ll()\r\nTypeError: cannot find property 'll' of an external object"}
+ "console.ll()\r\nTypeError: 'll' is not a function"}
}
njs_test {
*/
#include <njs_core.h>
+#include <nxt_lvlhsh.h>
+#include <nxt_djb_hash.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
{ nxt_string("var a = {}; a.b.c"),
nxt_string("TypeError: cannot get property 'c' of undefined") },
+ { nxt_string("'a'[0]"),
+ nxt_string("a") },
+
+ { nxt_string("'a'[undefined]"),
+ nxt_string("undefined") },
+
+ { nxt_string("'a'[null]"),
+ nxt_string("undefined") },
+
+ { nxt_string("'a'[false]"),
+ nxt_string("undefined") },
+
{ nxt_string("'a'.b = 1"),
nxt_string("TypeError: property set on primitive string type") },
+ { nxt_string("'a'[2] = 1"),
+ nxt_string("TypeError: property set on primitive string type") },
+
{ nxt_string("var a = {}; a.b = 1; a.b"),
nxt_string("1") },
{ nxt_string("delete --[][1]"),
nxt_string("true") },
+ { nxt_string("var a = [1,2,3]; a.x = 10; delete a[1]"),
+ nxt_string("true") },
+
+ { nxt_string("var o = Object.create({a:1}); o.a = 2; delete o.a; o.a"),
+ nxt_string("1") },
+
+ { nxt_string("function Foo() {this.bar = 10;}; Foo.prototype.bar = 42; "
+ "var v = new Foo(); delete v.bar; v.bar"),
+ nxt_string("42") },
+
+ /* Math object is immutable. */
+
+ { nxt_string("delete Math.max"),
+ nxt_string("TypeError: Cannot delete property 'max' of object") },
+
+ { nxt_string("Math.E = 1"),
+ nxt_string("TypeError: Cannot assign to read-only property 'E' of object") },
+
{ nxt_string("var a = {}; 1 in a"),
nxt_string("false") },
+ { nxt_string("'a' in {a:1}"),
+ nxt_string("true") },
+
+ { nxt_string("'a' in Object.create({a:1})"),
+ nxt_string("true") },
+
{ nxt_string("var a = 1; 1 in a"),
nxt_string("TypeError: property in on a primitive value") },
{ nxt_string("var a = [1,2]; a.length"),
nxt_string("2") },
+#if 0
+ { nxt_string("Object.create([1,2]).length"),
+ nxt_string("2") },
+#endif
+
+ { nxt_string("Object.create(['α','β'])[1]"),
+ nxt_string("β") },
+
+ { nxt_string("Object.create(['α','β'])[false]"),
+ nxt_string("undefined") },
+
/* Array.length setter */
{ nxt_string("[].length = {}"),
{ nxt_string("var p1 = $r.props, p2 = $r2.props; '' + p1.a + p2.a"),
nxt_string("12") },
+ { nxt_string("var p = $r3.props; p.a = 1"),
+ nxt_string("TypeError: Cannot assign to read-only property 'a' of external") },
+ { nxt_string("var p = $r3.props; delete p.a"),
+ nxt_string("TypeError: Cannot delete property 'a' of external") },
+
+ { nxt_string("$r.vars.p + $r2.vars.q + $r3.vars.k"),
+ nxt_string("pvalqvalkval") },
+
+ { nxt_string("$r.vars.unset"),
+ nxt_string("undefined") },
+
+ { nxt_string("var v = $r3.vars; v.k"),
+ nxt_string("kval") },
+
+ { nxt_string("var v = $r3.vars; v.unset = 1; v.unset"),
+ nxt_string("1") },
+
+ { nxt_string("$r.vars.unset = 'a'; $r2.vars.unset = 'b';"
+ "$r.vars.unset + $r2.vars.unset"),
+ nxt_string("ab") },
+
+ { nxt_string("$r.vars.unset = 1; $r2.vars.unset = 2;"
+ "$r.vars.unset + $r2.vars.unset"),
+ nxt_string("12") },
+
+ { nxt_string("$r3.vars.p = 'a'; $r3.vars.p2 = 'b';"
+ "$r3.vars.p + $r3.vars.p2"),
+ nxt_string("ab") },
+
+ { nxt_string("$r3.vars.p = 'a'; delete $r3.vars.p; $r3.vars.p"),
+ nxt_string("undefined") },
+
+ { nxt_string("$r3.vars.p = 'a'; delete $r3.vars.p; $r3.vars.p = 'b'; $r3.vars.p"),
+ nxt_string("b") },
+
+ { nxt_string("$r3.vars.error = 1"),
+ nxt_string("Error: cannot set 'error' prop") },
+
+ { nxt_string("delete $r3.vars.error"),
+ nxt_string("Error: cannot delete 'error' prop") },
+
+ { nxt_string("delete $r3.vars.e"),
+ nxt_string("true") },
+
+ { nxt_string("$r3.consts.k"),
+ nxt_string("kval") },
+
+ { nxt_string("$r3.consts.k = 1"),
+ nxt_string("TypeError: Cannot assign to read-only property 'k' of external") },
+
+ { nxt_string("delete $r3.consts.k"),
+ nxt_string("TypeError: Cannot delete property 'k' of external") },
+
+ { nxt_string("delete $r3.vars.p; $r3.vars.p"),
+ nxt_string("undefined") },
+
{ nxt_string("var a = $r.host; a +' '+ a.length +' '+ a"),
nxt_string("АБВГДЕЁЖЗИЙ 22 АБВГДЕЁЖЗИЙ") },
"sr.uri + sr2.uri"),
nxt_string("ZZZYYY") },
+ { nxt_string("var sr = $r.create('XXX'); sr.vars.p = 'a'; sr.vars.p"),
+ nxt_string("a") },
+
{ nxt_string("var p; for (p in $r.some_method);"),
nxt_string("undefined") },
nxt_string("true") },
{ nxt_string("delete $r.uri"),
- nxt_string("false") },
+ nxt_string("TypeError: Cannot delete property 'uri' of external") },
{ nxt_string("delete $r.one"),
- nxt_string("false") },
+ nxt_string("TypeError: Cannot delete property 'one' of external") },
{ nxt_string("$r.some_method.call($r, 'YES')"),
nxt_string("АБВ") },
nxt_string("undefined") },
{ nxt_string("$r.error = 'OK'"),
- nxt_string("OK") },
+ nxt_string("TypeError: Cannot assign to read-only property 'error' of external") },
{ nxt_string("var a = { toString: function() { return 1 } }; a"),
nxt_string("1") },
nxt_string("TypeError: object is not callable") },
{ nxt_string("var o = {a:1}; o.a()"),
- nxt_string("TypeError: object is not callable") },
+ nxt_string("TypeError: 'a' is not a function") },
{ nxt_string("(function(){})()"),
nxt_string("undefined") },
nxt_string("SyntaxError: Unexpected token \"null\" in 1") },
{ nxt_string("'a'.f()"),
- nxt_string("InternalError: method 'f' query failed:2") },
+ nxt_string("TypeError: 'f' is not a function") },
{ nxt_string("1..f()"),
- nxt_string("InternalError: method 'f' query failed:-3") },
+ nxt_string("TypeError: 'f' is not a function") },
{ nxt_string("try {}"),
nxt_string("SyntaxError: Missing catch or finally after try in 1") },
{ nxt_string("new String([1,2,3])"),
nxt_string("1,2,3") },
+ { nxt_string("var s = new String('αβ'); s.one = 1; 'one' in s"),
+ nxt_string("true") },
+
+ { nxt_string("var s = new String('αβ'); 'one' in s"),
+ nxt_string("false") },
+
+ { nxt_string("var s = new String('αβ'); s.one = 1; '1' in s"),
+ nxt_string("true") },
+
+ { nxt_string("var s = new String('αβ'); s.one = 1; 1 in s"),
+ nxt_string("true") },
+
+ { nxt_string("var s = new String('αβ'); s.one = 1; 2 in s"),
+ nxt_string("false") },
+
+ { nxt_string("var s = new String('αβ'); s[1]"),
+ nxt_string("β") },
+
+ { nxt_string("Object.create(new String('αβ'))[1]"),
+ nxt_string("β") },
+
+ { nxt_string("var s = new String('αβ'); s[1] = 'b'"),
+ nxt_string("TypeError: Cannot assign to read-only property '1' of object string") },
+
+ { nxt_string("var s = new String('αβ'); s[4] = 'ab'; s[4]"),
+ nxt_string("ab") },
+
+
+#if 0
+ { nxt_string("Object.create(new String('αβ')).length"),
+ nxt_string("2") },
+#endif
+
+ { nxt_string("var s = new String('αβ'); s.valueOf()[1]"),
+ nxt_string("β") },
+
{ nxt_string("var o = { toString: function() { return 'OK' } };"
"String(o)"),
nxt_string("OK") },
{ nxt_string("Object.getOwnPropertyDescriptor({}, 'a')"),
nxt_string("undefined") },
+ { nxt_string("Object.getOwnPropertyDescriptor(Object.create({a:1}), 'a')"),
+ nxt_string("undefined") },
+
{ nxt_string("Object.getOwnPropertyDescriptor([3,4], '1').value"),
nxt_string("4") },
{ nxt_string("Object.getOwnPropertyDescriptor([], 'length').value"),
nxt_string("0") },
- { nxt_string("JSON.stringify(Object.getOwnPropertyDescriptor([3,4], 'length'))"),
- nxt_string("{\"value\":2,\"configurable\":false,\"enumerable\":false,\"writable\":true}") },
+ { nxt_string("Object.getOwnPropertyDescriptor([], '0')"),
+ nxt_string("undefined") },
+
+ { nxt_string("Object.getOwnPropertyDescriptor([1,2], '1').value"),
+ nxt_string("2") },
+
+ { nxt_string("Object.getOwnPropertyDescriptor([1,2], new String('1')).value"),
+ nxt_string("2") },
+
+ { nxt_string("Object.getOwnPropertyDescriptor({undefined:1}, void 0).value"),
+ nxt_string("1") },
+
+ { nxt_string("Object.getOwnPropertyDescriptor([1,2], 1).value"),
+ nxt_string("2") },
- { nxt_string("Object.getOwnPropertyDescriptor([3,4], '3')"),
+ { nxt_string("Object.getOwnPropertyDescriptor([1,,,3], '1')"),
nxt_string("undefined") },
- { nxt_string("Object.getOwnPropertyDescriptor([], '0')"),
+ { nxt_string("Object.getOwnPropertyDescriptor([1,2], '3')"),
nxt_string("undefined") },
+ { nxt_string("JSON.stringify(Object.getOwnPropertyDescriptor([3,4], 'length'))"),
+ nxt_string("{\"value\":2,\"configurable\":false,\"enumerable\":false,\"writable\":true}") },
+
+ { nxt_string("Object.getOwnPropertyDescriptor(Array.of, 'length').value"),
+ nxt_string("0") },
+
+ { nxt_string("Object.getOwnPropertyDescriptor('αβγδ', '1').value"),
+ nxt_string("β") },
+
+ { nxt_string("Object.getOwnPropertyDescriptor(new String('αβγδ'), '1').value"),
+ nxt_string("β") },
+
+ { nxt_string("var s = new String('αβγδ'); s.a = 1;"
+ "Object.getOwnPropertyDescriptor(s, 'a').value"),
+ nxt_string("1") },
+
+ { nxt_string("JSON.stringify(Object.getOwnPropertyDescriptor('αβγδ', '2'))"),
+ nxt_string("{\"value\":\"γ\",\"configurable\":false,\"enumerable\":true,\"writable\":false}") },
+
+ { nxt_string("JSON.stringify(Object.getOwnPropertyDescriptor(new String('abc'), 'length'))"),
+ nxt_string("{\"value\":3,\"configurable\":false,\"enumerable\":false,\"writable\":false}") },
+
{ nxt_string("Object.getOwnPropertyDescriptor(1, '0')"),
nxt_string("undefined") },
+ { nxt_string("var min = Object.getOwnPropertyDescriptor(Math, 'min').value;"
+ "[min(1,2), min(2,1), min(-1,1)]"),
+ nxt_string("1,1,-1") },
+
{ nxt_string("Object.getOwnPropertyDescriptor()"),
nxt_string("TypeError: cannot convert void argument to object") },
typedef struct {
- nxt_str_t uri;
- uint32_t a;
- nxt_mem_cache_pool_t *mem_cache_pool;
+ nxt_lvlhsh_t hash;
const njs_extern_t *proto;
+ nxt_mem_cache_pool_t *mem_cache_pool;
+
+ uint32_t a;
+ nxt_str_t uri;
njs_opaque_value_t value;
} njs_unit_test_req_t;
+typedef struct {
+ njs_value_t name;
+ njs_value_t value;
+} njs_unit_test_prop_t;
+
+
+static nxt_int_t
+lvlhsh_unit_test_key_test(nxt_lvlhsh_query_t *lhq, void *data)
+{
+ nxt_str_t name;
+ njs_unit_test_prop_t *prop;
+
+ prop = data;
+ njs_string_get(&prop->name, &name);
+
+ if (name.length != lhq->key.length) {
+ return NXT_DECLINED;
+ }
+
+ if (memcmp(name.start, lhq->key.start, lhq->key.length) == 0) {
+ return NXT_OK;
+ }
+
+ return NXT_DECLINED;
+}
+
+
+static void *
+lvlhsh_unit_test_pool_alloc(void *pool, size_t size, nxt_uint_t nalloc)
+{
+ return nxt_mem_cache_align(pool, size, size);
+}
+
+
+static void
+lvlhsh_unit_test_pool_free(void *pool, void *p, size_t size)
+{
+ nxt_mem_cache_free(pool, p);
+}
+
+
+static const nxt_lvlhsh_proto_t lvlhsh_proto nxt_aligned(64) = {
+ NXT_LVLHSH_LARGE_SLAB,
+ 0,
+ lvlhsh_unit_test_key_test,
+ lvlhsh_unit_test_pool_alloc,
+ lvlhsh_unit_test_pool_free,
+};
+
+
+static njs_unit_test_prop_t *
+lvlhsh_unit_test_alloc(nxt_mem_cache_pool_t *pool, const njs_value_t *name,
+ const njs_value_t *value)
+{
+ njs_unit_test_prop_t *prop;
+
+ prop = nxt_mem_cache_alloc(pool, sizeof(njs_unit_test_prop_t));
+ if (prop == NULL) {
+ return NULL;
+ }
+
+ prop->name = *name;
+ prop->value = *value;
+
+ return prop;
+}
+
+
+static nxt_int_t
+lvlhsh_unit_test_add(njs_unit_test_req_t *r, njs_unit_test_prop_t *prop)
+{
+ nxt_lvlhsh_query_t lhq;
+
+ njs_string_get(&prop->name, &lhq.key);
+ lhq.key_hash = nxt_djb_hash(lhq.key.start, lhq.key.length);
+
+ lhq.replace = 1;
+ lhq.value = (void *) prop;
+ lhq.proto = &lvlhsh_proto;
+ lhq.pool = r->mem_cache_pool;
+
+ switch (nxt_lvlhsh_insert(&r->hash, &lhq)) {
+
+ case NXT_OK:
+ return NXT_OK;
+
+ case NXT_DECLINED:
+ default:
+ return NXT_ERROR;
+ }
+}
+
+
static njs_ret_t
njs_unit_test_r_get_uri_external(njs_vm_t *vm, njs_value_t *value, void *obj,
uintptr_t data)
{
- njs_unit_test_req_t *r;
+ char *p = obj;
- r = (njs_unit_test_req_t *) obj;
+ nxt_str_t *field;
+
+ field = (nxt_str_t *) (p + data);
- return njs_string_create(vm, value, r->uri.start, r->uri.length, 0);
+ return njs_string_create(vm, value, field->start, field->length, 0);
}
njs_unit_test_r_set_uri_external(njs_vm_t *vm, void *obj, uintptr_t data,
nxt_str_t *value)
{
- njs_unit_test_req_t *r;
+ char *p = obj;
- r = (njs_unit_test_req_t *) obj;
+ nxt_str_t *field;
- r->uri = *value;
+ field = (nxt_str_t *) (p + data);
+
+ *field = *value;
return NXT_OK;
}
}
+static njs_ret_t
+njs_unit_test_r_get_vars(njs_vm_t *vm, njs_value_t *value, void *obj,
+ uintptr_t data)
+{
+ nxt_int_t ret;
+ nxt_str_t *key;
+ nxt_lvlhsh_query_t lhq;
+ njs_unit_test_req_t *r;
+ njs_unit_test_prop_t *prop;
+
+ r = (njs_unit_test_req_t *) obj;
+ key = (nxt_str_t *) data;
+
+ lhq.key = *key;
+ lhq.key_hash = nxt_djb_hash(key->start, key->length);
+ lhq.proto = &lvlhsh_proto;
+
+ ret = nxt_lvlhsh_find(&r->hash, &lhq);
+
+ prop = lhq.value;
+
+ if (ret == NXT_OK && njs_is_valid(&prop->value)) {
+ *value = prop->value;
+ return NXT_OK;
+ }
+
+ njs_value_void_set(value);
+
+ return NXT_OK;
+}
+
+
+static njs_ret_t
+njs_unit_test_r_set_vars(njs_vm_t *vm, void *obj, uintptr_t data,
+ nxt_str_t *value)
+{
+ nxt_int_t ret;
+ nxt_str_t *key;
+ njs_value_t name, val;
+ njs_unit_test_req_t *r;
+ njs_unit_test_prop_t *prop;
+
+ r = (njs_unit_test_req_t *) obj;
+ key = (nxt_str_t *) data;
+
+ if (key->length == 5 && memcmp(key->start, "error", 5) == 0) {
+ njs_vm_error(vm, "cannot set 'error' prop");
+ return NXT_ERROR;
+ }
+
+ njs_string_create(vm, &name, key->start, key->length, 0);
+ njs_string_create(vm, &val, value->start, value->length, 0);
+
+ prop = lvlhsh_unit_test_alloc(vm->mem_cache_pool, &name, &val);
+ if (prop == NULL) {
+ njs_memory_error(vm);
+ return NXT_ERROR;
+ }
+
+ ret = lvlhsh_unit_test_add(r, prop);
+ if (ret != NXT_OK) {
+ njs_vm_error(vm, "lvlhsh_unit_test_add() failed");
+ return NXT_ERROR;
+ }
+
+ return NXT_OK;
+}
+
+
+static njs_ret_t
+njs_unit_test_r_del_vars(njs_vm_t *vm, void *obj, uintptr_t data,
+ nxt_bool_t delete)
+{
+ nxt_int_t ret;
+ nxt_str_t *key;
+ nxt_lvlhsh_query_t lhq;
+ njs_unit_test_req_t *r;
+ njs_unit_test_prop_t *prop;
+
+ r = (njs_unit_test_req_t *) obj;
+ key = (nxt_str_t *) data;
+
+ if (key->length == 5 && memcmp(key->start, "error", 5) == 0) {
+ njs_vm_error(vm, "cannot delete 'error' prop");
+ return NXT_ERROR;
+ }
+
+ lhq.key = *key;
+ lhq.key_hash = nxt_djb_hash(key->start, key->length);
+ lhq.proto = &lvlhsh_proto;
+
+ ret = nxt_lvlhsh_find(&r->hash, &lhq);
+
+ prop = lhq.value;
+
+ if (ret == NXT_OK) {
+ njs_set_invalid(&prop->value);
+ }
+
+ return NXT_OK;
+}
+
+
static njs_ret_t
njs_unit_test_header_external(njs_vm_t *vm, njs_value_t *value, void *obj,
uintptr_t data)
NULL,
NULL,
NULL,
- 0 },
+ offsetof(njs_unit_test_req_t, uri) },
{ nxt_string("host"),
NJS_EXTERN_PROPERTY,
NULL,
0 },
+ { nxt_string("vars"),
+ NJS_EXTERN_OBJECT,
+ NULL,
+ 0,
+ njs_unit_test_r_get_vars,
+ njs_unit_test_r_set_vars,
+ njs_unit_test_r_del_vars,
+ NULL,
+ NULL,
+ NULL,
+ 0 },
+
+ { nxt_string("consts"),
+ NJS_EXTERN_OBJECT,
+ NULL,
+ 0,
+ njs_unit_test_r_get_vars,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ 0 },
+
{ nxt_string("header"),
NJS_EXTERN_OBJECT,
NULL,
typedef struct {
nxt_str_t name;
njs_unit_test_req_t request;
+ njs_unit_test_prop_t props[2];
} njs_unit_test_req_t_init_t;
static const njs_unit_test_req_t_init_t nxt_test_requests[] = {
- { nxt_string("$r"), {.uri = nxt_string("АБВ"), .a = 1}},
- { nxt_string("$r2"), {.uri = nxt_string("αβγ"), .a = 2}},
- { nxt_string("$r3"), {.uri = nxt_string("abc"), .a = 3}},
+
+ { nxt_string("$r"),
+ {
+ .uri = nxt_string("АБВ"),
+ .a = 1
+ },
+ {
+ { njs_string("p"), njs_string("pval") },
+ { njs_string("p2"), njs_string("p2val") },
+ }
+ },
+
+ { nxt_string("$r2"),
+ {
+ .uri = nxt_string("αβγ"),
+ .a = 2
+ },
+ {
+ { njs_string("q"), njs_string("qval") },
+ { njs_string("q2"), njs_string("q2val") },
+ }
+ },
+
+ { nxt_string("$r3"),
+ {
+ .uri = nxt_string("abc"),
+ .a = 3
+ },
+ {
+ { njs_string("k"), njs_string("kval") },
+ { njs_string("k2"), njs_string("k2val") },
+ }
+ },
};
static nxt_int_t
njs_externals_init(njs_vm_t *vm)
{
- nxt_int_t ret;
- nxt_uint_t i;
- const njs_extern_t *proto;
- njs_unit_test_req_t *requests;
+ nxt_int_t ret;
+ nxt_uint_t i, j;
+ const njs_extern_t *proto;
+ njs_unit_test_req_t *requests;
+ njs_unit_test_prop_t *prop;
proto = njs_vm_external_prototype(vm, &nxt_test_external[0]);
if (proto == NULL) {
printf("njs_vm_external_bind() failed\n");
return NXT_ERROR;
}
+
+ for (j = 0; j < nxt_nitems(nxt_test_requests[i].props); j++) {
+ prop = lvlhsh_unit_test_alloc(vm->mem_cache_pool,
+ &nxt_test_requests[i].props[j].name,
+ &nxt_test_requests[i].props[j].value);
+
+ if (prop == NULL) {
+ printf("lvlhsh_unit_test_alloc() failed\n");
+ return NXT_ERROR;
+ }
+
+ ret = lvlhsh_unit_test_add(&requests[i], prop);
+ if (ret != NXT_OK) {
+ printf("lvlhsh_unit_test_add() failed\n");
+ return NXT_ERROR;
+ }
+ }
}
return NXT_OK;