njs_object_value_copy(njs_vm_t *vm, njs_value_t *value)
{
size_t size;
- njs_object_t *object;
+ njs_object_t *object, *proto;
object = njs_object(value);
return object;
}
- size = njs_is_object_value(value) ? sizeof(njs_object_value_t)
- : sizeof(njs_object_t);
+ switch (object->type) {
+ case NJS_OBJECT:
+ size = sizeof(njs_object_t);
+ proto = (object->__proto__ != NULL)
+ ? njs_vm_proto(vm, NJS_OBJ_TYPE_OBJECT)
+ : NULL;
+ break;
+ case NJS_ARRAY:
+ size = sizeof(njs_array_t);
+ njs_assert_msg(!object->fast_array,
+ "shared fast_array is not supported");
+ proto = (object->__proto__ != NULL)
+ ? njs_vm_proto(vm, NJS_OBJ_TYPE_ARRAY)
+ : NULL;
+ break;
+ case NJS_OBJECT_VALUE:
+ size = sizeof(njs_object_value_t);
+ proto = (object->__proto__ != NULL)
+ ? njs_vm_proto(vm, NJS_OBJ_TYPE_OBJECT)
+ : NULL;
+ break;
+ default:
+ njs_internal_error(vm, "unexpected object type to copy");
+ return NULL;
+ }
+
object = njs_mp_alloc(vm->mem_pool, size);
if (njs_fast_path(object != NULL)) {
memcpy(object, njs_object(value), size);
- object->__proto__ = njs_vm_proto(vm, NJS_OBJ_TYPE_OBJECT);
+ object->__proto__ = proto;
object->shared = 0;
value->data.u.object = object;
return object;
njs_lvlhsh_each_init(&lhe, &njs_object_hash_proto);
hash = &object->shared_hash;
+ if (flags & NJS_ENUM_NON_SHARED_ONLY) {
+ goto local_hash;
+ }
+
for ( ;; ) {
prop = njs_lvlhsh_each(hash, &lhe);
}
}
+local_hash:
+
njs_lvlhsh_each_init(&lhe, &njs_object_hash_proto);
hash = &object->hash;
}
+static njs_int_t
+njs_object_copy_shared_hash(njs_vm_t *vm, njs_object_t *object)
+{
+ njs_int_t ret;
+ njs_flathsh_t new_hash, *shared_hash;
+ njs_object_prop_t *prop;
+ njs_flathsh_each_t fhe;
+ njs_flathsh_query_t fhq;
+
+ fhq.replace = 0;
+ fhq.proto = &njs_object_hash_proto;
+ fhq.pool = vm->mem_pool;
+
+ njs_flathsh_init(&new_hash);
+ shared_hash = &object->shared_hash;
+
+ njs_flathsh_each_init(&fhe, &njs_object_hash_proto);
+
+ for ( ;; ) {
+ prop = njs_flathsh_each(shared_hash, &fhe);
+
+ if (prop == NULL) {
+ break;
+ }
+
+ if (njs_is_symbol(&prop->name)) {
+ fhq.key_hash = njs_symbol_key(&prop->name);
+ fhq.key.start = NULL;
+
+ } else {
+ njs_string_get(&prop->name, &fhq.key);
+ fhq.key_hash = njs_djb_hash(fhq.key.start, fhq.key.length);
+ }
+
+ fhq.value = prop;
+
+ ret = njs_flathsh_insert(&new_hash, &fhq);
+ if (njs_slow_path(ret != NJS_OK)) {
+ njs_internal_error(vm, "flathsh insert failed");
+ return NJS_ERROR;
+ }
+ }
+
+ object->shared_hash = new_hash;
+
+ return NJS_OK;
+}
+
+
+njs_int_t
+njs_object_make_shared(njs_vm_t *vm, njs_object_t *object)
+{
+ njs_int_t ret;
+ njs_arr_t visited;
+ njs_object_t **start;
+ njs_value_t value, *key;
+ njs_traverse_t *s;
+ njs_object_prop_t *prop;
+ njs_property_query_t pq;
+ njs_traverse_t state[NJS_TRAVERSE_MAX_DEPTH];
+
+ s = &state[0];
+ s->parent = NULL;
+ s->index = 0;
+ njs_set_object(&s->value, object);
+
+ s->keys = njs_value_own_enumerate(vm, &s->value, NJS_ENUM_KEYS
+ | NJS_ENUM_STRING
+ | NJS_ENUM_NON_SHARED_ONLY);
+ if (njs_slow_path(s->keys == NULL)) {
+ return NJS_ERROR;
+ }
+
+ if (s->keys->length != 0
+ && !njs_flathsh_is_empty(&object->shared_hash))
+ {
+ /*
+ * object->shared_hash can be shared with other objects
+ * and we do not want to modify other objects.
+ */
+
+ ret = njs_object_copy_shared_hash(vm, object);
+ if (njs_slow_path(ret != NJS_OK)) {
+ return NJS_ERROR;
+ }
+ }
+
+ start = njs_arr_init(vm->mem_pool, &visited, NULL, 8, sizeof(void *));
+ if (njs_slow_path(start == NULL)) {
+ return NJS_ERROR;
+ }
+
+ (void) njs_traverse_visit(&visited, &s->value);
+
+ pq.lhq.replace = 0;
+ pq.lhq.pool = vm->mem_pool;
+
+ for ( ;; ) {
+
+ if (s->index >= s->keys->length) {
+ njs_flathsh_init(&njs_object(&s->value)->hash);
+ njs_object(&s->value)->shared = 1;
+ njs_array_destroy(vm, s->keys);
+ s->keys = NULL;
+
+ if (s == &state[0]) {
+ goto done;
+ }
+
+ s--;
+ continue;
+ }
+
+
+ njs_property_query_init(&pq, NJS_PROPERTY_QUERY_GET, 0, 0);
+ key = &s->keys->start[s->index++];
+
+ ret = njs_property_query(vm, &pq, &s->value, key);
+ if (njs_slow_path(ret != NJS_OK)) {
+ if (ret == NJS_DECLINED) {
+ continue;
+ }
+
+ return NJS_ERROR;
+ }
+
+
+ prop = pq.lhq.value;
+
+ ret = njs_flathsh_insert(&njs_object(&s->value)->shared_hash, &pq.lhq);
+ if (njs_slow_path(ret != NJS_OK)) {
+ njs_internal_error(vm, "flathsh insert failed");
+ return NJS_ERROR;
+ }
+
+ njs_value_assign(&value, njs_prop_value(prop));
+
+ if (njs_is_object(&value)
+ && !njs_object(&value)->shared
+ && !njs_traverse_visited(&visited, &value))
+ {
+ ret = njs_traverse_visit(&visited, &value);
+ if (njs_slow_path(ret != NJS_OK)) {
+ return NJS_ERROR;
+ }
+
+ if (s == &state[NJS_TRAVERSE_MAX_DEPTH - 1]) {
+ njs_type_error(vm, "njs_object_traverse() recursion limit:%d",
+ NJS_TRAVERSE_MAX_DEPTH);
+ return NJS_ERROR;
+ }
+
+ s++;
+ s->prop = NULL;
+ s->parent = &s[-1];
+ s->index = 0;
+ njs_value_assign(&s->value, &value);
+ s->keys = njs_value_own_enumerate(vm, &s->value, NJS_ENUM_KEYS
+ | NJS_ENUM_STRING
+ | NJS_ENUM_NON_SHARED_ONLY);
+ if (njs_slow_path(s->keys == NULL)) {
+ return NJS_ERROR;
+ }
+
+ if (s->keys->length != 0
+ && !njs_flathsh_is_empty(&njs_object(&s->value)->shared_hash))
+ {
+ ret = njs_object_copy_shared_hash(vm, njs_object(&s->value));
+ if (njs_slow_path(ret != NJS_OK)) {
+ return NJS_ERROR;
+ }
+ }
+ }
+ }
+
+done:
+
+ njs_arr_destroy(&visited);
+
+ return NJS_OK;
+}
+
+
njs_int_t
njs_object_traverse(njs_vm_t *vm, njs_object_t *object, void *ctx,
njs_object_traverse_cb_t cb)
"cr.abc = 1; cr.abc"),
njs_str("1") },
#endif
+
+ { njs_str("JSON.stringify(preload)"),
+ njs_str("{\"a\":[1,{\"b\":2,\"c\":3}]}") },
+
+ { njs_str("preload.a.prop = 1"),
+ njs_str("TypeError: Cannot add property \"prop\", object is not extensible\n"
+ " at main (:1)\n") },
+
+ { njs_str("preload.a[0] = 2"),
+ njs_str("TypeError: Cannot assign to read-only property \"0\" of array\n"
+ " at main (:1)\n") },
+
+ { njs_str("preload.a.push(2)"),
+ njs_str("TypeError: (intermediate value)[\"push\"] is not a function\n"
+ " at main (:1)\n") },
+
+ { njs_str("Array.prototype.push.call(preload.a, 'waka')"),
+ njs_str("TypeError: Cannot add property \"2\", object is not extensible\n"
+ " at Array.prototype.push (native)\n"
+ " at Function.prototype.call (native)\n"
+ " at main (:1)\n") },
};
njs_bool_t backtrace;
njs_bool_t handler;
njs_bool_t async;
+ njs_bool_t preload;
unsigned seed;
} njs_opts_t;
njs_stat_t prev;
njs_vm_opt_t options;
njs_runtime_t *rt;
+ njs_opaque_value_t retval;
njs_external_state_t *state;
+ njs_str_t preload = njs_str(
+ "globalThis.preload = JSON.parse("
+ "'{\"a\": [1, {\"b\": 2, \"c\": 3}]}',"
+ "function (k, v) {"
+ "if (v instanceof Object) {"
+ "Object.freeze(Object.setPrototypeOf(v, null));"
+ "}"
+ "return v;"
+ "}"
+ ");"
+ );
+
vm = NULL;
rt = NULL;
njs_vm_opt_init(&options);
+ options.init = opts->preload;
options.module = opts->module;
options.unsafe = opts->unsafe;
options.backtrace = opts->backtrace;
goto done;
}
+ if (opts->preload) {
+ start = preload.start;
+ end = start + preload.length;
+
+ ret = njs_vm_compile(vm, &start, end);
+ if (ret != NJS_OK) {
+ njs_printf("njs_vm_compile() preload failed\n");
+ goto done;
+ }
+
+ ret = njs_vm_start(vm, njs_value_arg(&retval));
+ if (ret != NJS_OK) {
+ njs_printf("njs_vm_start() preload failed\n");
+ goto done;
+ }
+
+ ret = njs_vm_reuse(vm);
+ if (ret != NJS_OK) {
+ njs_printf("njs_vm_reuse() failed\n");
+ goto done;
+ }
+ }
+
start = tests[i].script.start;
end = start + tests[i].script.length;
static njs_test_suite_t njs_suites[] =
{
{ njs_str("script"),
- { .repeat = 1, .unsafe = 1 },
+ { .repeat = 1, .unsafe = 1, .preload = 1 },
njs_test,
njs_nitems(njs_test),
njs_unit_test },
njs_unit_test },
{ njs_str("shared"),
- { .externals = 1, .repeat = 128, .seed = 42, .unsafe = 1, .backtrace = 1 },
+ { .externals = 1, .repeat = 128, .seed = 42, .unsafe = 1, .preload = 1, .backtrace = 1 },
njs_shared_test,
njs_nitems(njs_shared_test),
njs_unit_test },