Browse Source

Merge branch 'es6-proxy-subset'

pull/7/head
Sami Vaarala 11 years ago
parent
commit
00981a41c2
  1. 4
      Makefile
  2. 8
      RELEASES.txt
  3. 22
      api-testcases/test-bug-push-buffer-maxsize.c
  4. 12
      api-testcases/test-bug-push-string-maxsize.c
  5. 4
      api-testcases/test-bug-set-top-wrap.c
  6. 18
      api-testcases/test-del-prop.c
  7. 12
      api-testcases/test-get-prop.c
  8. 12
      api-testcases/test-has-prop.c
  9. 10
      api-testcases/test-normalize-index.c
  10. 2
      api-testcases/test-pcall-prop.c
  11. 155
      api-testcases/test-proxy-basic.c
  12. 6
      api-testcases/test-substring.c
  13. 4
      api-testcases/test-to-boolean.c
  14. 4
      api-testcases/test-to-buffer.c
  15. 4
      api-testcases/test-to-defaultvalue.c
  16. 6
      api-testcases/test-to-fixed-buffer.c
  17. 4
      api-testcases/test-to-lstring.c
  18. 8
      api-testcases/test-to-object.c
  19. 8
      api-testcases/test-to-pointer.c
  20. 8
      api-testcases/test-to-primitive.c
  21. 8
      api-testcases/test-to-string.c
  22. 6
      api-testcases/test-trim.c
  23. 10
      api-testcases/test-validate-index.c
  24. 85
      ecmascript-testcases/test-bi-proxy-internal-keys.js
  25. 816
      ecmascript-testcases/test-bi-proxy-subset.js
  26. 28
      ecmascript-testcases/test-bug-rejected-delete-property.js
  27. 6
      src/duk_bi_protos.h
  28. 60
      src/duk_bi_proxy.c
  29. 8
      src/duk_features.h
  30. 8
      src/duk_hobject.h
  31. 305
      src/duk_hobject_props.c
  32. 27
      src/genbuiltins.py
  33. 21
      src/genstrings.py
  34. 1
      util/make_dist.sh
  35. 2
      website/buildsite.py
  36. 12
      website/guide/compiling.html
  37. 1
      website/guide/duktapebuiltins.html
  38. 38
      website/guide/es6features.html
  39. 10
      website/guide/intro.html
  40. 8
      website/guide/luacomparison.html
  41. 218
      website/guide/propertyvirtualization.html
  42. 7
      website/index/index.html

4
Makefile

@ -131,6 +131,7 @@ DUKTAPE_SOURCES_SEPARATE = \
$(DISTSRCSEP)/duk_bi_object.c \
$(DISTSRCSEP)/duk_bi_regexp.c \
$(DISTSRCSEP)/duk_bi_string.c \
$(DISTSRCSEP)/duk_bi_proxy.c \
$(DISTSRCSEP)/duk_bi_buffer.c \
$(DISTSRCSEP)/duk_bi_pointer.c \
$(DISTSRCSEP)/duk_bi_logger.c \
@ -185,9 +186,10 @@ CCOPTS_SHARED += -DDUK_OPT_DEBUG_BUFSIZE=512
#CCOPTS_SHARED += -DDUK_OPT_NO_JSONC
#CCOPTS_SHARED += -DDUK_OPT_FUNC_NONSTD_CALLER_PROPERTY
#CCOPTS_SHARED += -DDUK_OPT_FUNC_NONSTD_SOURCE_PROPERTY
#CCOPTS_SHARED += -DDUK_OPT_NO_ARRAY_SPLICE_NONSTD_DELCOUNT
#CCOPTS_SHARED += -DDUK_OPT_NO_OBJECT_ES6_PROTO_PROPERTY
#CCOPTS_SHARED += -DDUK_OPT_NO_OBJECT_ES6_SETPROTOTYPEOF
#CCOPTS_SHARED += -DDUK_OPT_NO_ARRAY_SPLICE_NONSTD_DELCOUNT
#CCOPTS_SHARED += -DDUK_OPT_NO_ES6_PROXY
#CCOPTS_SHARED += -DDUK_OPT_NO_ZERO_BUFFER_DATA
#CCOPTS_SHARED += -DDUK_CMDLINE_BAREBONES
CCOPTS_NONDEBUG = $(CCOPTS_SHARED) -Os -fomit-frame-pointer

8
RELEASES.txt

@ -306,6 +306,14 @@ Planned
* Add Object.setPrototypeOf() and Object.prototype.__proto__, both borrowed
from ES6 draft, to improve internal prototype handling
* Add proxy objects borrowed from ES6 draft to allow property virtualization
(subset implementation limited to 'get', 'set', and 'deleteProperty'
handler methods)
* Fix a 'delete' bug: if delete target was a string and key was 'length' or
a valid character index, value stack was left in an inconsistent state in
non-strict mode (caused an assertion failure)
* C typing wrapped throughout to allow porting to more exotic platforms,
e.g. platforms where "int" is a 16-bit type

22
api-testcases/test-bug-push-buffer-maxsize.c

@ -1,25 +1,26 @@
/*===
*** test_1a
fixed size buffer, maximum size_t (should fail)
rc=1, result='Error: failed to allocate buffer'
rc=1, result='RangeError: buffer too long'
*** test_1b
fixed size buffer, maximum size_t - 8 (should fail)
rc=1, result='Error: failed to allocate buffer'
rc=1, result='RangeError: buffer too long'
*** test_2a
dynamic size buffer, maximum size_t (should fail)
rc=1, result='Error: failed to allocate buffer'
rc=1, result='RangeError: buffer too long'
*** test_2b
dynamic size buffer, maximum size_t - 8 (should fail)
rc=1, result='Error: failed to allocate buffer'
rc=1, result='RangeError: buffer too long'
===*/
/* Attempt to allocate a buffer of maximum size_t (or anything so close that
* when the heap header size is added, the result overflows) causes a spurious
* successful allocation now. The allocation will in fact be too little to
* even contain the heap header but will appear to succeed.
/* Before Duktape 0.9.0, an attempt to allocate a buffer of maximum size_t
* (or anything so close that when the heap header size is added, the result
* overflows) causes a spurious successful allocation now. The allocation
* will in fact be too little to even contain the heap header but will appear
* to succeed.
*
* The fix is to check for a maximum size before adding the header size to
* the requested size.
* The proper behavior is to check for a maximum size before adding the header
* size to the requested size (this is done now).
*/
#ifndef SIZE_MAX
@ -96,4 +97,3 @@ void test(duk_context *ctx) {
TEST(test_2a);
TEST(test_2b);
}

12
api-testcases/test-bug-push-string-maxsize.c

@ -1,10 +1,15 @@
/*===
FIXME: check when fixed
*** test_1a
push string with maximum size_t (should fail)
rc=1, result='RangeError: string too long'
*** test_1b
push string with maximum size_t - 8 (should fail)
rc=1, result='RangeError: string too long'
===*/
/* Same as test-bug-push-buffer-maxsize.c but for string pushing.
*
* There are actually two bugs in the current implementation:
* There were actually two bugs in the implementation previously:
* (1) the size computation for the header plus string may overflow,
* and (2) the string size is passed as a duk_u32 internally which
* clamps incorrectly on 64-bit platforms.
@ -12,6 +17,9 @@ FIXME: check when fixed
* The attempt to push a string of SIZE_MAX (or close) should fail
* before the string data is actually read (there isn't enough data,
* of course, if that were to happen).
*
* The fix, now implemented, is to check for string maximum size
* explicitly.
*/
#ifndef SIZE_MAX

4
api-testcases/test-bug-set-top-wrap.c

@ -1,8 +1,8 @@
/*===
top=0
rc=1, result=Error: invalid index: 536870912
rc=1, result=Error: invalid index
top=0
rc=1, result=Error: invalid index: 357913942
rc=1, result=Error: invalid index
===*/
int test_1(duk_context *ctx) {

18
api-testcases/test-del-prop.c

@ -10,7 +10,7 @@ delete 'test_string'['5'] -> rc=0
delete 'test_string'.length -> rc=0
final object: {"bar":"barval"}
final array: ["foo","bar",null]
final top: 7
final top: 3
==> rc=0, result='undefined'
*** test_1b (duk_pcall)
==> rc=1, result='TypeError: property not configurable'
@ -33,16 +33,16 @@ delete 'test_string'['5'] -> rc=0
delete 'test_string'.length -> rc=0
final object: {"bar":"barval"}
final array: ["foo","bar",null]
final top: 7
final top: 3
==> rc=0, result='undefined'
*** test_2b (duk_pcall)
==> rc=1, result='TypeError: property not configurable'
*** test_2c (duk_pcall)
==> rc=1, result='TypeError: property not configurable'
*** test_2d (duk_safe_call)
==> rc=1, result='Error: invalid index: 234'
==> rc=1, result='Error: invalid index'
*** test_2e (duk_safe_call)
==> rc=1, result='Error: invalid index: -2147483648'
==> rc=1, result='Error: invalid index'
*** test_3a (duk_safe_call)
delete obj[31337] -> rc=1
delete obj[123] -> rc=1
@ -51,14 +51,14 @@ delete arr[2] -> rc=1
delete 'test_string'[5] -> rc=0
final object: {"foo":"fooval","bar":"barval"}
final array: ["foo","bar",null]
final top: 5
final top: 3
==> rc=0, result='undefined'
*** test_3b (duk_pcall)
==> rc=1, result='TypeError: property not configurable'
*** test_3c (duk_safe_call)
==> rc=1, result='Error: invalid index: 234'
==> rc=1, result='Error: invalid index'
*** test_3d (duk_safe_call)
==> rc=1, result='Error: invalid index: -2147483648'
==> rc=1, result='Error: invalid index'
===*/
void prep(duk_context *ctx) {
@ -389,15 +389,11 @@ void test(duk_context *ctx) {
TEST_PCALL(test_2b);
TEST_PCALL(test_2c);
TEST_SAFE_CALL(test_2d);
/* FIXME: currently error message contains the actual DUK_INVALID_INDEX
* value, nonportable */
TEST_SAFE_CALL(test_2e);
TEST_SAFE_CALL(test_3a);
TEST_PCALL(test_3b);
TEST_SAFE_CALL(test_3c);
/* FIXME: currently error message contains the actual DUK_INVALID_INDEX
* value, nonportable */
TEST_SAFE_CALL(test_3d);
}

12
api-testcases/test-get-prop.c

@ -33,9 +33,9 @@ arr.length -> rc=1, result='3'
final top: 3
==> rc=0, result='undefined'
*** test_2b (duk_safe_call)
==> rc=1, result='Error: invalid index: 234'
==> rc=1, result='Error: invalid index'
*** test_2c (duk_safe_call)
==> rc=1, result='Error: invalid index: -2147483648'
==> rc=1, result='Error: invalid index'
*** test_3a (duk_safe_call)
obj[31337] -> rc=0, result='undefined'
obj[123] -> rc=1, result='123val'
@ -45,9 +45,9 @@ arr[2] -> rc=1, result='quux'
final top: 3
==> rc=0, result='undefined'
*** test_3b (duk_safe_call)
==> rc=1, result='Error: invalid index: 234'
==> rc=1, result='Error: invalid index'
*** test_3c (duk_safe_call)
==> rc=1, result='Error: invalid index: -2147483648'
==> rc=1, result='Error: invalid index'
===*/
void prep(duk_context *ctx) {
@ -334,14 +334,10 @@ void test(duk_context *ctx) {
TEST_SAFE_CALL(test_2a);
TEST_SAFE_CALL(test_2b);
/* FIXME: currently error message contains the actual DUK_INVALID_INDEX
* value, nonportable */
TEST_SAFE_CALL(test_2c);
TEST_SAFE_CALL(test_3a);
TEST_SAFE_CALL(test_3b);
/* FIXME: currently error message contains the actual DUK_INVALID_INDEX
* value, nonportable */
TEST_SAFE_CALL(test_3c);
}

12
api-testcases/test-has-prop.c

@ -26,9 +26,9 @@ arr.length -> rc=1
final top: 3
==> rc=0, result='undefined'
*** test_2b (duk_safe_call)
==> rc=1, result='Error: invalid index: 234'
==> rc=1, result='Error: invalid index'
*** test_2c (duk_safe_call)
==> rc=1, result='Error: invalid index: -2147483648'
==> rc=1, result='Error: invalid index'
*** test_3a (duk_safe_call)
obj[31337] -> rc=0
obj[123] -> rc=1
@ -37,9 +37,9 @@ arr[2] -> rc=1
final top: 3
==> rc=0, result='undefined'
*** test_3b (duk_safe_call)
==> rc=1, result='Error: invalid index: 234'
==> rc=1, result='Error: invalid index'
*** test_3c (duk_safe_call)
==> rc=1, result='Error: invalid index: -2147483648'
==> rc=1, result='Error: invalid index'
===*/
void prep(duk_context *ctx) {
@ -260,14 +260,10 @@ void test(duk_context *ctx) {
TEST_SAFE_CALL(test_2a);
TEST_SAFE_CALL(test_2b);
/* FIXME: currently error message contains the actual DUK_INVALID_INDEX
* value, nonportable */
TEST_SAFE_CALL(test_2c);
TEST_SAFE_CALL(test_3a);
TEST_SAFE_CALL(test_3b);
/* FIXME: currently error message contains the actual DUK_INVALID_INDEX
* value, nonportable */
TEST_SAFE_CALL(test_3c);
}

10
api-testcases/test-normalize-index.c

@ -11,9 +11,9 @@ top=3, idx=3, duk_normalize_index -> DUK_INVALID_INDEX
top=3, idx=4, duk_normalize_index -> DUK_INVALID_INDEX
top=3, idx=5, duk_normalize_index -> DUK_INVALID_INDEX
req_norm_idx: top 3 after popping arg
idx=-5 -> duk_require_normalize_index -> Error: invalid index: -5
idx=-5 -> duk_require_normalize_index -> Error: invalid index
req_norm_idx: top 3 after popping arg
idx=-4 -> duk_require_normalize_index -> Error: invalid index: -4
idx=-4 -> duk_require_normalize_index -> Error: invalid index
req_norm_idx: top 3 after popping arg
idx=-3 -> duk_require_normalize_index -> 0
req_norm_idx: top 3 after popping arg
@ -27,11 +27,11 @@ idx=1 -> duk_require_normalize_index -> 1
req_norm_idx: top 3 after popping arg
idx=2 -> duk_require_normalize_index -> 2
req_norm_idx: top 3 after popping arg
idx=3 -> duk_require_normalize_index -> Error: invalid index: 3
idx=3 -> duk_require_normalize_index -> Error: invalid index
req_norm_idx: top 3 after popping arg
idx=4 -> duk_require_normalize_index -> Error: invalid index: 4
idx=4 -> duk_require_normalize_index -> Error: invalid index
req_norm_idx: top 3 after popping arg
idx=5 -> duk_require_normalize_index -> Error: invalid index: 5
idx=5 -> duk_require_normalize_index -> Error: invalid index
===*/
int req_norm_idx(duk_context *ctx) {

2
api-testcases/test-pcall-prop.c

@ -21,7 +21,7 @@ rc=1, result='TypeError: invalid base reference for property read'
rc=1, result='RangeError: getter error'
==> rc=0, result='undefined'
*** test_7 (duk_safe_call)
rc=1, result='Error: invalid index: -6'
rc=1, result='Error: invalid index'
==> rc=0, result='undefined'
*** test_8 (duk_safe_call)
rc=1, result='TypeError: call target not callable'

155
api-testcases/test-proxy-basic.c

@ -0,0 +1,155 @@
/*
* ES6 Proxy handlers can also be native Duktape/C functions.
*
* Just a very basic test to ensure proxy handlers work as expected.
*/
/*===
*** test_1 (duk_safe_call)
top: 0
top: 2
handle_get: key=getTest
get result: rc=1, value=123
top: 2
handle_get: key=_getTest
get result: rc=1, value=fake_value
top: 2
handle_set: key=setTest, val=testValue
set result: rc=1
top: 2
handle_set: key=_setTest, val=testValue
set result: rc=0
top: 2
handle_delete: key=deleteTest
delete result: rc=1
top: 2
handle_delete: key=_deleteTest
delete result: rc=0
top: 2
final top: 0
==> rc=0, result='undefined'
===*/
static int handle_get(duk_context *ctx) {
/* 'this' binding: handler
* [0]: target
* [1]: key
* [2]: receiver (proxy)
*/
const char *key = duk_to_string(ctx, 1);
printf("handle_get: key=%s\n", key);
if (key != NULL && key[0] == '_') {
/* Provide a fake value for properties beginning with an underscore. */
duk_push_string(ctx, "fake_value");
} else {
/* For others, read from target. */
duk_dup(ctx, 1);
duk_get_prop(ctx, 0);
}
return 1;
}
static int handle_set(duk_context *ctx) {
/* 'this' binding: handler
* [0]: target
* [1]: key
* [2]: val
* [3]: receiver (proxy)
*/
const char *key = duk_to_string(ctx, 1);
const char *val = duk_to_string(ctx, 2);
printf("handle_set: key=%s, val=%s\n", key, val);
if (key != NULL && key[0] == '_') {
/* Indicate set failure for properties beginning with underscore. */
duk_push_false(ctx);
} else {
duk_push_true(ctx);
}
return 1;
}
static int handle_delete(duk_context *ctx) {
/* 'this' binding: handler
* [0]: target
* [1]: key
*/
const char *key = duk_to_string(ctx, 1);
printf("handle_delete: key=%s\n", key);
if (key != NULL && key[0] == '_') {
/* Indicate delete failure for properties beginning with underscore. */
duk_push_false(ctx);
} else {
duk_push_true(ctx);
}
return 1;
}
static const duk_function_list_entry handler_funcs[] = {
{ "get", handle_get, 3 },
{ "set", handle_set, 4 },
{ "deleteProperty", handle_delete, 2 },
{ NULL, NULL, 0 }
};
static int test_1(duk_context *ctx) {
int rc;
printf("top: %d\n", duk_get_top(ctx));
/* new Proxy(target, handler) */
duk_push_global_object(ctx);
duk_get_prop_string(ctx, -1, "Proxy");
duk_push_object(ctx); /* target */
duk_push_number(ctx, 123);
duk_put_prop_string(ctx, -2, "getTest");
duk_push_object(ctx); /* handler */
duk_put_function_list(ctx, -1, handler_funcs);
duk_new(ctx, 2); /* [ global Proxy target handler ] -> [ global proxy_object ] */
printf("top: %d\n", duk_get_top(ctx));
rc = duk_get_prop_string(ctx, -1, "getTest");
printf("get result: rc=%d, value=%s\n", rc, duk_to_string(ctx, -1));
duk_pop(ctx);
printf("top: %d\n", duk_get_top(ctx));
rc = duk_get_prop_string(ctx, -1, "_getTest");
printf("get result: rc=%d, value=%s\n", rc, duk_to_string(ctx, -1));
duk_pop(ctx);
printf("top: %d\n", duk_get_top(ctx));
duk_push_string(ctx, "testValue");
rc = duk_put_prop_string(ctx, -2, "setTest");
printf("set result: rc=%d\n", rc);
printf("top: %d\n", duk_get_top(ctx));
duk_push_string(ctx, "testValue");
rc = duk_put_prop_string(ctx, -2, "_setTest");
printf("set result: rc=%d\n", rc);
printf("top: %d\n", duk_get_top(ctx));
rc = duk_del_prop_string(ctx, -1, "deleteTest");
printf("delete result: rc=%d\n", rc);
printf("top: %d\n", duk_get_top(ctx));
rc = duk_del_prop_string(ctx, -1, "_deleteTest");
printf("delete result: rc=%d\n", rc);
printf("top: %d\n", duk_get_top(ctx));
duk_pop_2(ctx);
printf("final top: %d\n", duk_get_top(ctx));
return 0;
}
void test(duk_context *ctx) {
TEST_SAFE_CALL(test_1);
}

6
api-testcases/test-substring.c

@ -9,9 +9,9 @@ final top: 1
*** test_2 (duk_safe_call)
==> rc=1, result='TypeError: incorrect type, expected tag 5'
*** test_3 (duk_safe_call)
==> rc=1, result='Error: invalid index: -2'
==> rc=1, result='Error: invalid index'
*** test_4 (duk_safe_call)
==> rc=1, result='Error: invalid index: -2147483648'
==> rc=1, result='Error: invalid index'
===*/
void dump_string(duk_context *ctx) {
@ -112,6 +112,6 @@ void test(duk_context *ctx) {
TEST_SAFE_CALL(test_1);
TEST_SAFE_CALL(test_2);
TEST_SAFE_CALL(test_3);
TEST_SAFE_CALL(test_4); /* FIXME: exposes value of DUK_INVALID_INDEX */
TEST_SAFE_CALL(test_4);
}

4
api-testcases/test-to-boolean.c

@ -21,9 +21,9 @@ index 16, boolean: 0
index 17, boolean: 1
==> rc=0, result='undefined'
*** test_2 (duk_safe_call)
==> rc=1, result='Error: invalid index: 3'
==> rc=1, result='Error: invalid index'
*** test_3 (duk_safe_call)
==> rc=1, result='Error: invalid index: -2147483648'
==> rc=1, result='Error: invalid index'
===*/
int test_1(duk_context *ctx) {

4
api-testcases/test-to-buffer.c

@ -41,9 +41,9 @@ index 18, type 8 -> 7, ptr-is-NULL 0, size 10
buffer: dynamic=0, size=10: 0xdeadbeef
==> rc=0, result='undefined'
*** test_2 (duk_safe_call)
==> rc=1, result='Error: invalid index: 3'
==> rc=1, result='Error: invalid index'
*** test_3 (duk_safe_call)
==> rc=1, result='Error: invalid index: -2147483648'
==> rc=1, result='Error: invalid index'
===*/
void dump_buffer(duk_context *ctx) {

4
api-testcases/test-to-defaultvalue.c

@ -8,9 +8,9 @@ index 2, type 6 -> 3, result: true
*** test_2 (duk_safe_call)
==> rc=1, result='TypeError: not object'
*** test_3 (duk_safe_call)
==> rc=1, result='Error: invalid index: 3'
==> rc=1, result='Error: invalid index'
*** test_4 (duk_safe_call)
==> rc=1, result='Error: invalid index: -2147483648'
==> rc=1, result='Error: invalid index'
===*/
/* FIXME: this test is missing a lot of coverage, like different hints,

6
api-testcases/test-to-fixed-buffer.c

@ -13,9 +13,9 @@ final top: 1
*** test_3 (duk_safe_call)
==> rc=1, result='TypeError: incorrect type, expected tag 7'
*** test_4 (duk_safe_call)
==> rc=1, result='Error: invalid index: 3'
==> rc=1, result='Error: invalid index'
*** test_5 (duk_safe_call)
==> rc=1, result='Error: invalid index: -2147483648'
==> rc=1, result='Error: invalid index'
===*/
void dump_buffer(duk_context *ctx) {
@ -106,6 +106,6 @@ void test(duk_context *ctx) {
TEST_SAFE_CALL(test_2);
TEST_SAFE_CALL(test_3);
TEST_SAFE_CALL(test_4);
TEST_SAFE_CALL(test_5); /* FIXME: exposes DUK_INVALID_INDEX number constant */
TEST_SAFE_CALL(test_5);
}

4
api-testcases/test-to-lstring.c

@ -43,9 +43,9 @@ index 19, string: '0xdeadbeef', length 10
index 19, string: '0xdeadbeef'
==> rc=0, result='undefined'
*** test_2 (duk_safe_call)
==> rc=1, result='Error: invalid index: 3'
==> rc=1, result='Error: invalid index'
*** test_3 (duk_safe_call)
==> rc=1, result='Error: invalid index: -2147483648'
==> rc=1, result='Error: invalid index'
===*/
int test_1(duk_context *ctx) {

8
api-testcases/test-to-object.c

@ -35,9 +35,9 @@ index 0 OK
index 0 OK
==> rc=0, result='undefined'
*** test_3 (duk_safe_call)
==> rc=1, result='Error: invalid index: 3'
==> rc=1, result='Error: invalid index'
*** test_4 (duk_safe_call)
==> rc=1, result='Error: invalid index: -2147483648'
==> rc=1, result='Error: invalid index'
===*/
int test_1(duk_context *ctx) {
@ -151,9 +151,5 @@ void test(duk_context *ctx) {
TEST_SAFE_CALL(test_2g);
TEST_SAFE_CALL(test_2h);
TEST_SAFE_CALL(test_3);
/* FIXME: this testcase currently exposes the DUK_INVALID_INDEX
* constant in the error message and is thus not portable.
*/
TEST_SAFE_CALL(test_4);
}

8
api-testcases/test-to-pointer.c

@ -23,9 +23,9 @@ index 17, ptr-is-NULL: 0, type: 8 -> 8
pointer: 0xdeadbeef
==> rc=0, result='undefined'
*** test_2 (duk_safe_call)
==> rc=1, result='Error: invalid index: 3'
==> rc=1, result='Error: invalid index'
*** test_3 (duk_safe_call)
==> rc=1, result='Error: invalid index: -2147483648'
==> rc=1, result='Error: invalid index'
===*/
int test_1(duk_context *ctx) {
@ -89,9 +89,5 @@ int test_3(duk_context *ctx) {
void test(duk_context *ctx) {
TEST_SAFE_CALL(test_1);
TEST_SAFE_CALL(test_2);
/* FIXME: this test now results in an error string containing the
* exact index, which is platform dependent.
*/
TEST_SAFE_CALL(test_3);
}

8
api-testcases/test-to-primitive.c

@ -22,9 +22,9 @@ index 17, ToString(result): 'null', type: 8 -> 8
index 18, ToString(result): '0xdeadbeef', type: 8 -> 8
==> rc=0, result='undefined'
*** test_2 (duk_safe_call)
==> rc=1, result='Error: invalid index: 3'
==> rc=1, result='Error: invalid index'
*** test_3 (duk_safe_call)
==> rc=1, result='Error: invalid index: -2147483648'
==> rc=1, result='Error: invalid index'
===*/
/* FIXME: coverage is pretty poor, e.g. different hints are not tested.
@ -89,10 +89,6 @@ int test_3(duk_context *ctx) {
void test(duk_context *ctx) {
TEST_SAFE_CALL(test_1);
TEST_SAFE_CALL(test_2);
/* FIXME: this test now results in an error string containing the
* exact index, which is platform dependent.
*/
TEST_SAFE_CALL(test_3);
}

8
api-testcases/test-to-string.c

@ -23,9 +23,9 @@ index 18, string: 'null'
index 19, string: '0xdeadbeef'
==> rc=0, result='undefined'
*** test_2 (duk_safe_call)
==> rc=1, result='Error: invalid index: 3'
==> rc=1, result='Error: invalid index'
*** test_3 (duk_safe_call)
==> rc=1, result='Error: invalid index: -2147483648'
==> rc=1, result='Error: invalid index'
===*/
int test_1(duk_context *ctx) {
@ -86,10 +86,6 @@ int test_3(duk_context *ctx) {
void test(duk_context *ctx) {
TEST_SAFE_CALL(test_1);
TEST_SAFE_CALL(test_2);
/* FIXME: this testcase currently exposes the DUK_INVALID_INDEX
* constant in the error message and is thus not portable.
*/
TEST_SAFE_CALL(test_3);
}

6
api-testcases/test-trim.c

@ -7,9 +7,9 @@ final top: 2
*** test_2 (duk_safe_call)
==> rc=1, result='TypeError: incorrect type, expected tag 5'
*** test_3 (duk_safe_call)
==> rc=1, result='Error: invalid index: 4'
==> rc=1, result='Error: invalid index'
*** test_4 (duk_safe_call)
==> rc=1, result='Error: invalid index: -2147483648'
==> rc=1, result='Error: invalid index'
===*/
int test_1(duk_context *ctx) {
@ -84,7 +84,5 @@ void test(duk_context *ctx) {
TEST_SAFE_CALL(test_1);
TEST_SAFE_CALL(test_2);
TEST_SAFE_CALL(test_3);
/* FIXME: this test prints DUK_INVALID_INDEX value in its error string now */
TEST_SAFE_CALL(test_4);
}

10
api-testcases/test-validate-index.c

@ -11,9 +11,9 @@ top=3, idx=3, duk_is_valid_index -> 0
top=3, idx=4, duk_is_valid_index -> 0
top=3, idx=5, duk_is_valid_index -> 0
req_valid_idx: top 3 after popping arg
idx=-5, duk_require_valid_index -> Error: invalid index: -5
idx=-5, duk_require_valid_index -> Error: invalid index
req_valid_idx: top 3 after popping arg
idx=-4, duk_require_valid_index -> Error: invalid index: -4
idx=-4, duk_require_valid_index -> Error: invalid index
req_valid_idx: top 3 after popping arg
idx=-3, duk_require_valid_index -> true
req_valid_idx: top 3 after popping arg
@ -27,11 +27,11 @@ idx=1, duk_require_valid_index -> true
req_valid_idx: top 3 after popping arg
idx=2, duk_require_valid_index -> true
req_valid_idx: top 3 after popping arg
idx=3, duk_require_valid_index -> Error: invalid index: 3
idx=3, duk_require_valid_index -> Error: invalid index
req_valid_idx: top 3 after popping arg
idx=4, duk_require_valid_index -> Error: invalid index: 4
idx=4, duk_require_valid_index -> Error: invalid index
req_valid_idx: top 3 after popping arg
idx=5, duk_require_valid_index -> Error: invalid index: 5
idx=5, duk_require_valid_index -> Error: invalid index
===*/
int req_valid_idx(duk_context *ctx) {

85
ecmascript-testcases/test-bi-proxy-internal-keys.js

@ -0,0 +1,85 @@
/*
* The current ES6 Proxy subset behavior is skipped entirely for Duktape
* internal keys. Any property read/write/delete operations on internal
* keys behave as if the proxy handler did not exist, so that the operations
* are applied to the target object instead.
*
* Duktape accesses internal properties like _finalizer with ordinary
* property reads and writes, which causes some side effects when combining
* proxies and finalizers. In essence, you can only set a finalizer to the
* target object.
*/
/*===
get for key: "foo"
target finalized (2)
===*/
function test1() {
var target = {};
var proxy = new Proxy(target, {
get: function(targ, key, receiver) {
print('get for key:', Duktape.enc('jsonx', key));
return targ[key];
}
});
Duktape.fin(target, function () { print('target finalized (1)'); });
// Because there is no 'set' handler, the _finalizer write will go into
// the target object and overwrites the previous finalizer.
Duktape.fin(proxy, function () { print('target finalized (2)'); });
// When proxy is made unreachable, there is no _finalizer read because
// the *proxy* does not have a finalizer property from Duktape's perspective
// (there is no proxy handler for property existence check now, and the
// current finalizer code uses duk_hobject_hasprop_raw() which ignores proxies).
target = null; // reachable through proxy
void proxy.foo;
proxy = null;
}
try {
test1();
} catch (e) {
print(e);
}
/*===
target finalized (2)
===*/
function test2() {
var target = {};
var proxy = new Proxy(target, {
set: function(targ, key, val, receiver) {
print('set for key:', Duktape.enc('jsonx', key));
targ[key] = val;
return true;
}
});
Duktape.fin(target, function () { print('target finalized (1)'); });
// The 'set' handler is skipped for internal keys, so the finalizer is set
// into the target again, overwriting the previous finalizer. Nothing is
// logged by the 'set' handler.
Duktape.fin(proxy, function () { print('target finalized (2)'); });
// Like in test1(), no finalizer read for proxy.
target = null; // reachable through proxy
void proxy.foo;
proxy = null;
}
try {
test2();
} catch (e) {
print(e);
}
/*===
finished
===*/
print('finished');

816
ecmascript-testcases/test-bi-proxy-subset.js

@ -0,0 +1,816 @@
/*
* Proxy (ES6 draft) subset
*/
/*===
proxy existence
Proxy exists: true function
Proxy.length: 2
Proxy.name: Proxy
Proxy desc: writable=true enumerable=false configurable=true
Proxy.revocable exists: false undefined
===*/
function proxyExistenceTest() {
var pd;
print('Proxy exists:', 'Proxy' in this, typeof this.Proxy);
print('Proxy.length:', this.Proxy.length);
print('Proxy.name:', this.Proxy.name);
pd = Object.getOwnPropertyDescriptor(this, 'Proxy');
if (pd) {
print('Proxy desc:', 'writable=' + pd.writable, 'enumerable=' + pd.enumerable,
'configurable=' + pd.configurable);
}
print('Proxy.revocable exists:', 'revocable' in this.Proxy, typeof this.Proxy.revocable);
/*
print('Proxy.revocable.length:', this.Proxy.revocable.length);
print('Proxy.revocable.name:', this.Proxy.revocable.name);
pd = Object.getOwnPropertyDescriptor(this.Proxy, 'revocable');
if (pd) {
print('Proxy.revocable desc:', 'writable=' + pd.writable, 'enumerable=' + pd.enumerable,
'configurable=' + pd.configurable);
}
*/
}
print('proxy existence');
try {
proxyExistenceTest();
} catch (e) {
print(e);
}
/*===
subset proxy
object
[object Object]
get foo: 123
get 1000: thousand
get special: specialValue
counts: get=0 set=0 del=0
handler.get: true true string foo true
get foo: fake-for-key-foo
handler.get: true true string bar true
get bar: fake-for-key-bar
handler.get: true true string 1000 true
get 1000 (string): fake-for-key-1000
handler.get: true true number 1000 true
get 1000 (number): 2000
counts: get=4 set=0 del=0
handler.get: true true string special true
get special: specialValue
handler.get: true true string special true
get special: uncaughtValue
target.special: uncaughtValue
counts: get=6 set=0 del=0
handler.set: true true string foo number 1001 true
handler.set: true true string bar number 1002 true
handler.set: true true string quux number 1003 true
handler.set: true true number 123 string foo true
handler.set: true true string rejectSet1 string reject true
handler.set: true true string rejectSet2 string reject true
handler.set: true true string rejectSet1 string throw true
TypeError
handler.set: true true string rejectSet2 string throw true
TypeError
counts: get=6 set=8 del=0
target.foo: 123
target.bar: 1002
target.quux: 1003
target[123]: foo
target.rejectSet1: undefined
target.rejectSet2: undefined
counts: get=6 set=8 del=0
true
target.foo: undefined
target.bar: 1002
counts: get=6 set=8 del=0
handler.deleteProperty: true true string rejectDel1
false
handler.deleteProperty: true true string rejectDel2
false
handler.deleteProperty: true true string rejectDel1
TypeError
handler.deleteProperty: true true string rejectDel2
TypeError
handler.deleteProperty: true true string foo
true
handler.deleteProperty: true true number 1234
true
counts: get=6 set=8 del=6
target.rejectDel1: reject1
target.rejectDel2: reject2
target.foo 123
target[1234] undefined
===*/
/* Test simple usage of the current Proxy subset. Does not exercise the
* hook behaviors related to checking for conflicting properties in the
* target object.
*/
function subsetProxyTest() {
var getCount = 0;
var setCount = 0;
var deleteCount = 0;
var target = { foo: 123, '1000': 'thousand', special: 'specialValue' };
var handler = {};
var proxy = new Proxy(target, handler);
function printCounts() {
print('counts:', 'get=' + getCount, 'set=' + setCount, 'del=' + deleteCount);
}
print(typeof proxy);
print(Object.prototype.toString.call(proxy)); // XXX: now class is 'Object'
// without a 'get' hook, reads go through
print('get foo:', proxy.foo);
print('get 1000:', proxy[1000]);
print('get special:', proxy.special);
// handler 'get' hook
handler.get = function(targ, key, receiver) {
print('handler.get:', this === handler, targ === target, typeof key, key, receiver === proxy);
getCount++;
if (typeof key === 'number') {
return 2 * (+key);
} else if (key === 'special') {
return targ.special;
} else {
return 'fake-for-key-' + key;
}
};
// Get tests
printCounts();
print('get foo:', proxy.foo);
print('get bar:', proxy.bar);
print('get 1000 (string):', proxy['1000']);
print('get 1000 (number):', proxy[1000]);
printCounts();
// without a 'set' hook, writes go through
print('get special:', proxy.special);
proxy.special = 'uncaughtValue'; // goes into 'target'
print('get special:', proxy.special);
print('target.special:', target.special);
// handler 'set' hook
handler.set = function(targ, key, val, receiver) {
print('handler.set:', this === handler, targ === target, typeof key, key, typeof val, val, receiver === proxy);
setCount++;
if (key === 'rejectSet1') {
// False: indicate that property set is rejected (TypeError in strict mode).
// No change happens to the target object.
return false;
}
if (key === 'rejectSet2') {
// Same for any 'falsy' value.
return 0;
}
if (key === 'foo') {
// True: indicate that property set is allowed, but no change happens
// to the target object if we don't do it explicitly here.
return true;
}
// Setting to target must be done explicitly.
targ[key] = val;
return true;
};
// Set tests
printCounts();
proxy.foo = 1001;
proxy.bar = 1002;
proxy.quux = 1003;
proxy[123] = 'foo';
proxy.rejectSet1 = 'reject'; // reject silently in non-strict mode
proxy.rejectSet2 = 'reject';
try {
(function () { 'use strict'; proxy.rejectSet1 = 'throw'; })();
} catch (e) {
print(e.name);
}
try {
(function () { 'use strict'; proxy.rejectSet2 = 'throw'; })();
} catch (e) {
print(e.name);
}
printCounts();
print('target.foo:', target.foo);
print('target.bar:', target.bar);
print('target.quux:', target.quux);
print('target[123]:', target[123]);
print('target.rejectSet1:', target.rejectSet1);
print('target.rejectSet2:', target.rejectSet2);
// without a 'deleteProperty' hook, deletes go through
printCounts();
print(delete proxy.foo);
print('target.foo:', target.foo);
print('target.bar:', target.bar);
printCounts();
// handler 'deleteProperty' hook
handler.deleteProperty = function(targ, key) {
print('handler.deleteProperty:', this === handler, targ === target, typeof key, key);
deleteCount++;
if (key === 'rejectDel1') {
// False return code indicates delete is rejected.
return false;
}
if (key === 'rejectDel2') {
// Same for any 'falsy' value.
return 0;
}
if (key === 'foo') {
// True return code indicates delete is accepted (but it has no
// effect on the target unless we delete the property from the
// target here).
return true;
}
// Deletion to target must be done explicitly.
delete targ[key];
return true;
};
target.rejectDel1 = 'reject1';
target.rejectDel2 = 'reject2';
target.foo = 123;
target[1234] = 4321;
print(delete proxy.rejectDel1);
print(delete proxy.rejectDel2);
try {
(function () { 'use strict'; print(delete proxy.rejectDel1); })();
} catch (e) {
print(e.name);
}
try {
(function () { 'use strict'; print(delete proxy.rejectDel2); })();
} catch (e) {
print(e.name);
}
print(delete proxy.foo); // allowed, but no effect on target
print(delete proxy[1234]); // allowed, deletes value on target
printCounts();
print('target.rejectDel1:', target.rejectDel1);
print('target.rejectDel2:', target.rejectDel2);
print('target.foo', target.foo);
print('target[1234]', target[1234]);
}
print('subset proxy');
try {
subsetProxyTest();
} catch (e) {
print(e);
}
/*===
hook post-checks
get hook
property1: success, value: whatever
property2: success, value: whatever
property3: success, value: whatever
property4: success, value: whatever
property5: success, value: 42
property6: success, value: NaN
property7: success, value: 0
property1: success, value: whatever
property2: success, value: whatever
property3: success, value: whatever
property4: success, value: whatever
property5: TypeError
property6: TypeError
property7: TypeError
accessor1: success, value: undefined
accessor2: success, value: undefined
accessor3: success, value: undefined
accessor4: success, value: undefined
accessor5: success, value: undefined
accessor6: success, value: undefined
accessor7: success, value: undefined
accessor1: success, value: 123
accessor2: success, value: 123
accessor3: success, value: 123
accessor4: success, value: 123
accessor5: TypeError
accessor6: TypeError
accessor7: success, value: 123
set hook
property1: success, property1 set to 42
property2: success, property2 set to 42
property3: success, property3 set to 42
property4: success, property4 set to 42
property5: success, property5 set to 42
property6: success, property6 set to NaN
property7: success, property7 set to 0
property1: success, property1 set to 142
property2: success, property2 set to 142
property3: success, property3 set to 142
property4: success, property4 set to 142
property5: TypeError trying to set value to 142
property6: TypeError trying to set value to Infinity
property7: TypeError trying to set value to 0
accessor1: success, accessor1 set to whatever
accessor2: success, accessor2 set to whatever
accessor3: success, accessor3 set to whatever
accessor4: success, accessor4 set to whatever
accessor5: TypeError trying to set value to whatever
accessor6: success, accessor6 set to whatever
accessor7: TypeError trying to set value to whatever
deleteProperty hook
property1: success, result: true
property2: success, result: true
property3: TypeError
property4: TypeError
property5: TypeError
property6: TypeError
property7: TypeError
accessor1: success, result: true
accessor2: success, result: true
accessor3: TypeError
accessor4: success, result: true
accessor5: TypeError
accessor6: TypeError
accessor7: TypeError
===*/
/* If a hook exists and is successfully called, ES6 specifies interesting
* post-hook behavior where a TypeError may be raised if the hook return
* value conflicts in some way with a property of the same name in the
* target object.
*/
function makeDataTestObject() {
var obj = {};
Object.defineProperties(obj, {
// Various properties whose behavior to compare against the hooks
property1: {
writable: true, enumerable: true, configurable: true, value: 42
},
property2: {
writable: false, enumerable: true, configurable: true, value: 42
},
property3: {
writable: true, enumerable: true, configurable: false, value: 42
},
property4: {
writable: true, enumerable: false, configurable: false, value: 42
},
property5: {
writable: false, enumerable: true, configurable: false, value: 42
},
property6: {
writable: false, enumerable: true, configurable: false, value: NaN
},
property7: {
writable: false, enumerable: true, configurable: false, value: +0
}
});
return obj;
}
function makeAccessorTestObject() {
var obj = {};
function getter() {
print('getter called');
return 'getter-value';
}
function setter(v) {
print('setter called:', v);
}
Object.defineProperties(obj, {
// Various properties whose behavior to compare against the hooks
accessor1: {
enumerable: true, configurable: true, set: setter, get: getter
},
accessor2: {
enumerable: false, configurable: true, set: setter, get: getter
},
accessor3: {
enumerable: true, configurable: false, set: setter, get: getter
},
accessor4: {
enumerable: true, configurable: true, set: undefined, get: undefined
},
accessor5: {
enumerable: true, configurable: false, set: undefined, get: undefined
},
accessor6: {
enumerable: true, configurable: false, set: setter, get: undefined
},
accessor7: {
enumerable: true, configurable: false, set: undefined, get: getter
}
});
return obj;
}
function getHookPostChecksTest() {
var target, handler, proxy;
function getTest(proxy, propname) {
try {
var val = proxy[propname];
print(propname + ': success, value:', val);
} catch (e) {
print(propname + ':', e.name);
}
}
/* 'get' return value is rejected if the target has a property of the
* same name and the property:
*
* - Is a data property, is not configurable, is not writable, and
* hook provided value does not match with the current value
* (as compared with SameValue).
*
* - Is an accessor property, is not configurable, getter is not
* defined, and hook provided value is not undefined.
*/
/*
* Data properties
*/
target = makeDataTestObject();
handler = {};
handler.get = function (targ, key, receiver) {
if (key === 'property5') { return 42; } // same value, no error
if (key === 'property6') { return NaN; } // same value, no error
if (key === 'property7') { return +0; } // same value, no error
return 'whatever'; // differs from all values
};
proxy = new Proxy(target, handler);
Object.getOwnPropertyNames(target).forEach(function (propname) {
getTest(proxy, propname);
});
handler = {};
handler.get = function (targ, key, receiver) {
if (key === 'property5') { return 41; } // different value, error
if (key === 'property6') { return undefined; } // different value, error
if (key === 'property7') { return -0; } // different value, error
return 'whatever'; // differs from all values
};
proxy = new Proxy(target, handler);
Object.getOwnPropertyNames(target).forEach(function (propname) {
getTest(proxy, propname);
});
/*
* Accessor properties
*/
target = makeAccessorTestObject();
handler = {};
proxy = new Proxy(target, handler);
handler.get = function (targ, key, receiver) {
// If trapResult is undefined, post-hook checks always pass
return undefined;
}
Object.getOwnPropertyNames(target).forEach(function (propname) {
getTest(proxy, propname);
});
handler = {};
proxy = new Proxy(target, handler);
handler.get = function (targ, key, receiver) {
// If trapResult is not undefined, post-hook checks cause a TypeError
// if property is non-configurable and getter is undefined.
return 123;
}
Object.getOwnPropertyNames(target).forEach(function (propname) {
getTest(proxy, propname);
});
}
function setHookPostChecksTest() {
var target, handler, proxy;
function setTest(proxy, propname, propvalue) {
try {
proxy[propname] = propvalue;
print(propname + ': success,', propname, 'set to', propvalue);
} catch (e) {
print(propname + ':', e.name, 'trying to set value to', propvalue);
}
}
/* 'set' is rejected if the target has a property of the same name
* and the property:
*
* - Is a data property, is not configurable, is not writable, and
* the assignment value does not match with the current value
* (as compared with SameValue).
*
* - Is an accessor property, is not configurable, and setter is not
* defined. Unlike for 'get' the value does not matter.
*/
/*
* Data properties
*/
target = makeDataTestObject();
handler = {};
proxy = new Proxy(target, handler);
handler.set = function (targ, key, val, receiver) {
// If 'false' is returned, property write is rejected and the post-hook
// behavior doesn't activate at all, so always return true here.
return true;
}
Object.getOwnPropertyNames(target).forEach(function (propname) {
// Choose test value to match current value (with SameValue).
// No TypeError is triggered even if other conditions are met.
var propval = {
property1: 42,
property2: 42,
property3: 42,
property4: 42,
property5: 42,
property6: NaN,
property7: +0
}[propname];
setTest(proxy, propname, propval);
});
handler.set = function (targ, key, val, receiver) {
return true;
};
Object.getOwnPropertyNames(target).forEach(function (propname) {
// Choose test value to match current value (with SameValue).
// No TypeError is triggered even if other conditions are met.
var propval = {
property1: 142,
property2: 142,
property3: 142,
property4: 142,
property5: 142,
property6: 1/0,
property7: -0 // SameValue, even sign matters
}[propname];
setTest(proxy, propname, propval);
});
/*
* Accessor properties
*/
target = makeAccessorTestObject();
handler = {};
proxy = new Proxy(target, handler);
handler.set = function (targ, key, val, receiver) {
// If 'false' is returned, property write is rejected and the post-hook
// behavior doesn't activate at all, so always return true here.
return true;
}
Object.getOwnPropertyNames(target).forEach(function (propname) {
// For accessor + 'set' hook, property value does not matter.
setTest(proxy, propname, 'whatever');
});
}
function deleteHookPostChecksTest() {
var target, handler, proxy;
function deleteTest(proxy, propname) {
try {
print(propname + ': success, result:', delete proxy[propname]);
} catch (e) {
print(propname + ':', e.name);
}
}
/* 'deleteProperty' is rejected if the target has a property of the
* same name and the property:
*
* - Is not configurable
*/
/*
* Data properties
*/
target = makeDataTestObject();
handler = {};
proxy = new Proxy(target, handler);
handler.deleteProperty = function (targ, key, val, receiver) {
// If 'false' is returned, property delete is rejected and the post-hook
// behavior doesn't activate at all, so always return true here.
return true;
}
Object.getOwnPropertyNames(target).forEach(function (propname) {
deleteTest(proxy, propname);
});
/*
* Accessor properties
*/
target = makeAccessorTestObject();
handler = {};
proxy = new Proxy(target, handler);
handler.deleteProperty = function (targ, key, val, receiver) {
// If 'false' is returned, property delete is rejected and the post-hook
// behavior doesn't activate at all, so always return true here.
return true;
}
Object.getOwnPropertyNames(target).forEach(function (propname) {
deleteTest(proxy, propname);
});
}
print('hook post-checks');
try {
print('get hook');
getHookPostChecksTest();
print('set hook');
setHookPostChecksTest();
print('deleteProperty hook');
deleteHookPostChecksTest();
} catch (e) {
print(e);
}
/*===
recursive proxies
TypeError
TypeError
===*/
/* Currently Duktape doesn't allow a proxy as either a handler or a target.
* This makes it easier to implement because there is no arbitrary depth C
* recursion when doing proxy lookups.
*/
function proxyHandlerTest() {
var target = { foo: 123 };
var handler = new Proxy({}, {});
var proxy = new Proxy(target, handler);
}
function proxyTargetTest() {
var target = new Proxy({}, {});
var handler = {};
var proxy = new Proxy(target, handler);
}
print('recursive proxies');
try {
proxyHandlerTest();
} catch (e) {
print(e.name);
}
try {
proxyTargetTest();
} catch (e) {
print(e.name);
}
/*===
getter handler
handler.get: true true foo true
proxy.foo: dummy-value
===*/
/* A getter as a handler property. No reason why this wouldn't work but
* test just in case.
*/
function getterHandlerTest() {
var target = { foo: 123 };
var handler = {};
var proxy = new Proxy(target, handler);
Object.defineProperty(handler, 'get', {
enumerable: true,
configurable: true,
get: function () {
return function (targ, key, receiver) {
print('handler.get:', this === handler, targ === target, key, receiver === proxy);
return 'dummy-value';
};
}
});
print('proxy.foo:', proxy.foo);
}
print('getter handler');
try {
getterHandlerTest();
} catch (e) {
print(e);
}
/*===
non-callable handler
TypeError
===*/
/* A non-callable handler property. This is not checked during proxy creation
* and should cause a TypeError.
*/
function nonCallableHandlerTest() {
var target = { foo: 123 };
var handler = { get: 'dummy' };
var proxy = new Proxy(target, handler);
print('proxy.foo:', proxy.foo);
}
print('non-callable handler');
try {
nonCallableHandlerTest();
} catch (e) {
print(e.name);
}
/*===
throwing handler
handler.get: about to throw
URIError: fake error
===*/
/* Handler function throws. Nothing special here. */
function throwingHandlerTest() {
var target = { foo: 123 };
var handler = {
get: function() {
print('handler.get: about to throw');
throw new URIError('fake error');
}
};
var proxy = new Proxy(target, handler);
print('proxy.foo:', proxy.foo);
}
print('throwing handler');
try {
throwingHandlerTest();
} catch (e) {
print(e);
}
/*===
proxy revocation
handler.get: true true foo true
proxy.foo: dummy-value
===*/
/* Revoked proxy. */
function proxyRevocationTest() {
var target = { foo: 123 };
var handler = {
get: function(targ, key, receiver) {
print('handler.get:', this === handler, targ === target, key, receiver === proxy);
return 'dummy-value';
}
};
var proxy = new Proxy(target, handler);
print('proxy.foo:', proxy.foo);
// FIXME: unimplemented
}
print('proxy revocation');
try {
proxyRevocationTest();
} catch (e) {
print(e.name);
}

28
ecmascript-testcases/test-bug-rejected-delete-property.js

@ -0,0 +1,28 @@
/*
* Rejected delete property had a bug which caused the value stack to be out
* of balance when returning to the bytecode executor. This test used to fail
* with assertions enabled, when the base object for deletion was a string
* (e.g. attempt to delete str.length or a virtual index).
*/
/*===
true
false
false
===*/
function test() {
// must be non-strict so that delete failure is silent
var str = 'foo';
print(delete str.nonexistent);
print(delete str.length);
print(delete str[1]);
}
try {
test();
} catch (e) {
print(e);
}

6
src/duk_bi_protos.h

@ -174,6 +174,12 @@ duk_ret_t duk_bi_string_prototype_trim(duk_context *ctx);
#ifdef DUK_USE_SECTION_B
duk_ret_t duk_bi_string_prototype_substr(duk_context *ctx);
#endif
duk_ret_t duk_bi_proxy_constructor(duk_context *ctx);
#if 0 /* unimplemented now */
duk_ret_t duk_bi_proxy_constructor_revocable(duk_context *ctx);
#endif
duk_ret_t duk_bi_thread_constructor(duk_context *ctx);
duk_ret_t duk_bi_thread_resume(duk_context *ctx);
duk_ret_t duk_bi_thread_yield(duk_context *ctx);

60
src/duk_bi_proxy.c

@ -0,0 +1,60 @@
/*
* Proxy built-in (ES6 draft)
*/
#include "duk_internal.h"
#if defined(DUK_USE_ES6_PROXY)
duk_ret_t duk_bi_proxy_constructor(duk_context *ctx) {
duk_hobject *h_target;
duk_hobject *h_handler;
if (!duk_is_constructor_call(ctx)) {
return DUK_RET_TYPE_ERROR;
}
/* Reject a proxy object as the target because it would need
* special handler in property lookups. (ES6 has no such restriction)
*/
h_target = duk_require_hobject(ctx, 0);
DUK_ASSERT(h_target != NULL);
if (DUK_HOBJECT_HAS_SPECIAL_PROXYOBJ(h_target)) {
return DUK_RET_TYPE_ERROR;
}
/* Reject a proxy object as the handler because it would cause
* potentially unbounded recursion. (ES6 has no such restriction)
*/
h_handler = duk_require_hobject(ctx, 1);
DUK_ASSERT(h_handler != NULL);
if (DUK_HOBJECT_HAS_SPECIAL_PROXYOBJ(h_handler)) {
return DUK_RET_TYPE_ERROR;
}
/* XXX: the returned value is exotic in ES6 (draft), but we use a
* simple object here with no prototype. Without a prototype,
* [[DefaultValue]] coercion fails which is abit confusing.
* No callable check/handling in the current Proxy subset.
*/
(void) duk_push_object_helper_proto(ctx,
DUK_HOBJECT_FLAG_SPECIAL_PROXYOBJ |
DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_OBJECT),
NULL);
DUK_ASSERT_TOP(ctx, 3);
/* Proxy target */
duk_dup(ctx, 0);
duk_def_prop_stridx(ctx, -2, DUK_STRIDX_INT_TARGET, DUK_PROPDESC_FLAGS_WC);
/* Proxy handler */
duk_dup(ctx, 1);
duk_def_prop_stridx(ctx, -2, DUK_STRIDX_INT_HANDLER, DUK_PROPDESC_FLAGS_WC);
return 1; /* replacement handler */
}
#else /* DUK_USE_ES6_PROXY */
duk_ret_t duk_bi_proxy_constructor(duk_context *ctx) {
DUK_UNREF(ctx);
return DUK_RET_UNSUPPORTED_ERROR;
}
#endif /* DUK_USE_ES6_PROXY */

8
src/duk_features.h

@ -1723,11 +1723,19 @@ typedef FILE duk_file;
#undef DUK_USE_OBJECT_ES6_SETPROTOTYPEOF
#endif
/* ES6 Proxy object (subset for now). */
#define DUK_USE_ES6_PROXY
#if defined(DUK_OPT_NO_ES6_PROXY)
#undef DUK_USE_ES6_PROXY
#endif
/* Record pc-to-line information. */
#define DUK_USE_PC2LINE
#if defined(DUK_OPT_NO_PC2LINE)
#undef DUK_USE_PC2LINE
#endif
/* Non-standard function 'source' property. */
#undef DUK_USE_FUNC_NONSTD_SOURCE_PROPERTY
#if defined(DUK_OPT_FUNC_NONSTD_SOURCE_PROPERTY)
#define DUK_USE_FUNC_NONSTD_SOURCE_PROPERTY

8
src/duk_hobject.h

@ -50,7 +50,7 @@
#define DUK_HOBJECT_FLAG_SPECIAL_ARGUMENTS DUK_HEAPHDR_USER_FLAG(15) /* 'Arguments' object and has arguments special behavior (non-strict callee) */
#define DUK_HOBJECT_FLAG_SPECIAL_DUKFUNC DUK_HEAPHDR_USER_FLAG(16) /* Duktape/C (nativefunction) object, special 'length' */
#define DUK_HOBJECT_FLAG_SPECIAL_BUFFEROBJ DUK_HEAPHDR_USER_FLAG(17) /* 'Buffer' object, array index special behavior, virtual 'length' */
/* bit 18 unused */
#define DUK_HOBJECT_FLAG_SPECIAL_PROXYOBJ DUK_HEAPHDR_USER_FLAG(18) /* 'Proxy' object */
/* bit 19 unused */
/* bit 20 unused */
@ -113,7 +113,8 @@
DUK_HOBJECT_FLAG_SPECIAL_ARGUMENTS | \
DUK_HOBJECT_FLAG_SPECIAL_STRINGOBJ | \
DUK_HOBJECT_FLAG_SPECIAL_DUKFUNC | \
DUK_HOBJECT_FLAG_SPECIAL_BUFFEROBJ)
DUK_HOBJECT_FLAG_SPECIAL_BUFFEROBJ | \
DUK_HOBJECT_FLAG_SPECIAL_PROXYOBJ)
#define DUK_HOBJECT_HAS_SPECIAL_BEHAVIOR(h) DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_SPECIAL_BEHAVIOR_FLAGS)
@ -134,6 +135,7 @@
#define DUK_HOBJECT_HAS_SPECIAL_ARGUMENTS(h) DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_SPECIAL_ARGUMENTS)
#define DUK_HOBJECT_HAS_SPECIAL_DUKFUNC(h) DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_SPECIAL_DUKFUNC)
#define DUK_HOBJECT_HAS_SPECIAL_BUFFEROBJ(h) DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_SPECIAL_BUFFEROBJ)
#define DUK_HOBJECT_HAS_SPECIAL_PROXYOBJ(h) DUK_HEAPHDR_CHECK_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_SPECIAL_PROXYOBJ)
#define DUK_HOBJECT_SET_EXTENSIBLE(h) DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_EXTENSIBLE)
#define DUK_HOBJECT_SET_CONSTRUCTABLE(h) DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_CONSTRUCTABLE)
@ -152,6 +154,7 @@
#define DUK_HOBJECT_SET_SPECIAL_ARGUMENTS(h) DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_SPECIAL_ARGUMENTS)
#define DUK_HOBJECT_SET_SPECIAL_DUKFUNC(h) DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_SPECIAL_DUKFUNC)
#define DUK_HOBJECT_SET_SPECIAL_BUFFEROBJ(h) DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_SPECIAL_BUFFEROBJ)
#define DUK_HOBJECT_SET_SPECIAL_PROXYOBJ(h) DUK_HEAPHDR_SET_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_SPECIAL_PROXYOBJ)
#define DUK_HOBJECT_CLEAR_EXTENSIBLE(h) DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_EXTENSIBLE)
#define DUK_HOBJECT_CLEAR_CONSTRUCTABLE(h) DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_CONSTRUCTABLE)
@ -170,6 +173,7 @@
#define DUK_HOBJECT_CLEAR_SPECIAL_ARGUMENTS(h) DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_SPECIAL_ARGUMENTS)
#define DUK_HOBJECT_CLEAR_SPECIAL_DUKFUNC(h) DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_SPECIAL_DUKFUNC)
#define DUK_HOBJECT_CLEAR_SPECIAL_BUFFEROBJ(h) DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_SPECIAL_BUFFEROBJ)
#define DUK_HOBJECT_CLEAR_SPECIAL_PROXYOBJ(h) DUK_HEAPHDR_CLEAR_FLAG_BITS(&(h)->hdr, DUK_HOBJECT_FLAG_SPECIAL_PROXYOBJ)
/* flags used for property attributes in duk_propdesc and packed flags */
#define DUK_PROPDESC_FLAG_WRITABLE (1 << 0) /* E5 Section 8.6.1 */

305
src/duk_hobject_props.c

@ -50,11 +50,16 @@
#define DUK__HASH_UNUSED DUK_HOBJECT_HASHIDX_UNUSED
#define DUK__HASH_DELETED DUK_HOBJECT_HASHIDX_DELETED
/* assert value that suffices for all local calls, including recursion of
* other than Duktape calls (getters etc)
/* valstack space that suffices for all local calls, including recursion
* of other than Duktape calls (getters etc)
*/
#define DUK__VALSTACK_SPACE 10
/* valstack space allocated especially for proxy lookup which does a
* recursive property lookup
*/
#define DUK__VALSTACK_PROXY_LOOKUP 20
/*
* Local prototypes
*/
@ -252,7 +257,83 @@ static int duk__abandon_array_slow_check_required(duk_uint32_t arr_idx, duk_uint
return (arr_idx > DUK_HOBJECT_A_FAST_RESIZE_LIMIT * ((old_size + 7) >> 3));
}
/*
* Proxy helpers
*/
#if defined(DUK_USE_ES6_PROXY)
static duk_small_int_t duk__proxy_check(duk_hthread *thr, duk_hobject *obj, duk_small_int_t stridx_funcname, duk_tval *tv_key, duk_hobject **out_target) {
duk_context *ctx = (duk_context *) thr;
duk_tval *tv_target;
duk_tval *tv_handler;
DUK_ASSERT(thr != NULL);
DUK_ASSERT(obj != NULL);
DUK_ASSERT(out_target != NULL);
tv_handler = duk_hobject_find_existing_entry_tval_ptr(obj, DUK_HTHREAD_STRING_INT_HANDLER(thr));
if (!tv_handler) {
DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "proxy revoked");
return 0;
}
DUK_ASSERT(DUK_TVAL_IS_OBJECT(tv_handler));
tv_target = duk_hobject_find_existing_entry_tval_ptr(obj, DUK_HTHREAD_STRING_INT_TARGET(thr));
if (!tv_target) {
DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "proxy revoked");
return 0;
}
DUK_ASSERT(DUK_TVAL_IS_OBJECT(tv_target));
*out_target = DUK_TVAL_GET_OBJECT(tv_target);
DUK_ASSERT(*out_target != NULL);
/* XXX: At the moment Duktape accesses internal keys like _finalizer using a
* normal property set/get which would allow a proxy handler to interfere with
* such behavior and to get access to internal key strings. This is not a problem
* as such because internal key strings can be created in other ways too (e.g.
* through buffers). The best fix is to change Duktape internal lookups to
* skip proxy behavior. Until that, internal property accesses bypass the
* proxy and are applied to the target (as if the handler did not exist).
* This has some side effects, see test-bi-proxy-internal-keys.js.
*/
if (DUK_TVAL_IS_STRING(tv_key)) {
duk_hstring *h_key = (duk_hstring *) DUK_TVAL_GET_STRING(tv_key);
DUK_ASSERT(h_key != NULL);
if (DUK_HSTRING_HAS_INTERNAL(h_key)) {
DUK_DDDPRINT("internal key, skip proxy handler and apply to target");
return 0;
}
}
/* The handler is looked up with a normal property lookup; it may be an
* accessor or the handler object itself may be a proxy object. If the
* handler is a proxy, we need to extend the valstack as we make a
* recursive proxy check without a function call in between (in fact
* there is no limit to the potential recursion here).
*
* (For sanity, proxy creation rejects another proxy object as either
* the handler or the target at the moment so recursive proxy cases
* are not realized now.)
*/
/* XXX: C recursion limit if proxies are allowed as handler/target values */
duk_require_stack(ctx, DUK__VALSTACK_PROXY_LOOKUP);
duk_push_tval(ctx, tv_handler);
if (duk_get_prop_stridx(ctx, -1, stridx_funcname)) {
duk_remove(ctx, -2);
duk_push_tval(ctx, tv_handler);
/* stack prepped for func call: [ ... func handler ] */
return 1;
} else {
duk_pop_2(ctx);
return 0;
}
}
#endif /* DUK_USE_ES6_PROXY */
/*
* Reallocate property allocation, moving properties to the new allocation.
*
@ -1882,6 +1963,58 @@ int duk_hobject_getprop(duk_hthread *thr, duk_tval *tv_obj, duk_tval *tv_key) {
curr = DUK_TVAL_GET_OBJECT(tv_obj);
DUK_ASSERT(curr != NULL);
#if defined(DUK_USE_ES6_PROXY)
if (DUK_UNLIKELY(DUK_HOBJECT_HAS_SPECIAL_PROXYOBJ(curr))) {
duk_hobject *h_target;
if (duk__proxy_check(thr, curr, DUK_STRIDX_GET, tv_key, &h_target)) {
/* -> [ ... func handler ] */
DUK_DDDPRINT("-> proxy object 'get' for key %!T", tv_key);
duk_push_hobject(ctx, h_target); /* target */
duk_push_tval(ctx, tv_key); /* P */
duk_push_tval(ctx, tv_obj); /* Receiver: Proxy object */
duk_call_method(ctx, 3 /*nargs*/);
/* Target object must be checked for a conflicting
* non-configurable property.
*/
arr_idx = duk__push_tval_to_hstring_arr_idx(ctx, tv_key, &key);
DUK_ASSERT(key != NULL);
if (duk__get_own_property_desc_raw(thr, h_target, key, arr_idx, &desc, 1 /*push_value*/)) {
duk_tval *tv_hook = duk_require_tval(ctx, -3); /* value from hook */
duk_tval *tv_targ = duk_require_tval(ctx, -1); /* value from target */
DUK_DPRINT("proxy 'get': target has matching property %!O, check for "
"conflicting property; tv_hook=%!T, tv_targ=%!T, desc.flags=0x%08x, "
"desc.get=%p, desc.set=%p",
key, tv_hook, tv_targ, (int) desc.flags,
(void *) desc.get, (void *) desc.set);
int datadesc_reject = !(desc.flags & DUK_PROPDESC_FLAG_ACCESSOR) &&
!(desc.flags & DUK_PROPDESC_FLAG_CONFIGURABLE) &&
!(desc.flags & DUK_PROPDESC_FLAG_WRITABLE) &&
!duk_js_samevalue(tv_hook, tv_targ);
int accdesc_reject = (desc.flags & DUK_PROPDESC_FLAG_ACCESSOR) &&
!(desc.flags & DUK_PROPDESC_FLAG_CONFIGURABLE) &&
(desc.get == NULL) &&
!DUK_TVAL_IS_UNDEFINED(tv_hook);
if (datadesc_reject || accdesc_reject) {
DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "proxy get rejected");
}
duk_pop_2(ctx);
} else {
duk_pop(ctx);
}
return 1; /* return value */
}
curr = h_target; /* resume lookup from target */
DUK_TVAL_SET_OBJECT(tv_obj, curr);
}
#endif /* DUK_USE_ES6_PROXY */
tmp = duk__shallow_fast_path_array_check_tval(curr, tv_key);
if (tmp) {
duk_push_tval(ctx, tmp);
@ -2608,6 +2741,64 @@ int duk_hobject_putprop(duk_hthread *thr, duk_tval *tv_obj, duk_tval *tv_key, du
/* Note: no fast paths for property put now */
orig = DUK_TVAL_GET_OBJECT(tv_obj);
DUK_ASSERT(orig != NULL);
#if defined(DUK_USE_ES6_PROXY)
if (DUK_UNLIKELY(DUK_HOBJECT_HAS_SPECIAL_PROXYOBJ(orig))) {
duk_hobject *h_target;
int tmp_bool;
if (duk__proxy_check(thr, orig, DUK_STRIDX_SET, tv_key, &h_target)) {
/* -> [ ... func handler ] */
DUK_DDDPRINT("-> proxy object 'set' for key %!T", tv_key);
duk_push_hobject(ctx, h_target); /* target */
duk_push_tval(ctx, tv_key); /* P */
duk_push_tval(ctx, tv_val); /* V */
duk_push_tval(ctx, tv_obj); /* Receiver: Proxy object */
duk_call_method(ctx, 4 /*nargs*/);
tmp_bool = duk_to_boolean(ctx, -1);
duk_pop(ctx);
if (!tmp_bool) {
goto fail_proxy_rejected;
}
/* Target object must be checked for a conflicting
* non-configurable property.
*/
arr_idx = duk__push_tval_to_hstring_arr_idx(ctx, tv_key, &key);
DUK_ASSERT(key != NULL);
if (duk__get_own_property_desc_raw(thr, h_target, key, arr_idx, &desc, 1 /*push_value*/)) {
duk_tval *tv_targ = duk_require_tval(ctx, -1);
DUK_DPRINT("proxy 'set': target has matching property %!O, check for "
"conflicting property; tv_val=%!T, tv_targ=%!T, desc.flags=0x%08x, "
"desc.get=%p, desc.set=%p",
key, tv_val, tv_targ, (int) desc.flags,
(void *) desc.get, (void *) desc.set);
int datadesc_reject = !(desc.flags & DUK_PROPDESC_FLAG_ACCESSOR) &&
!(desc.flags & DUK_PROPDESC_FLAG_CONFIGURABLE) &&
!(desc.flags & DUK_PROPDESC_FLAG_WRITABLE) &&
!duk_js_samevalue(tv_val, tv_targ);
int accdesc_reject = (desc.flags & DUK_PROPDESC_FLAG_ACCESSOR) &&
!(desc.flags & DUK_PROPDESC_FLAG_CONFIGURABLE) &&
(desc.set == NULL);
if (datadesc_reject || accdesc_reject) {
DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "proxy set rejected");
}
duk_pop_2(ctx);
} else {
duk_pop(ctx);
}
return 1; /* success */
}
orig = h_target; /* resume write to target */
DUK_TVAL_SET_OBJECT(tv_obj, orig);
}
#endif /* DUK_USE_ES6_PROXY */
curr = orig;
break;
}
@ -3153,6 +3344,14 @@ int duk_hobject_putprop(duk_hthread *thr, duk_tval *tv_obj, duk_tval *tv_key, du
duk_pop(ctx); /* remove key */
return 1;
fail_proxy_rejected:
DUK_DDDPRINT("result: error, proxy rejects");
if (throw_flag) {
DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "proxy rejected");
}
/* Note: no key on stack */
return 0;
fail_base_primitive:
DUK_DDDPRINT("result: error, base primitive");
if (throw_flag) {
@ -3343,6 +3542,10 @@ int duk_hobject_delprop_raw(duk_hthread *thr, duk_hobject *obj, duk_hstring *key
int duk_hobject_delprop(duk_hthread *thr, duk_tval *tv_obj, duk_tval *tv_key, int throw_flag) {
duk_context *ctx = (duk_context *) thr;
duk_hstring *key = NULL;
#if defined(DUK_USE_ES6_PROXY)
duk_propdesc desc;
#endif
duk_int_t entry_top;
duk_uint32_t arr_idx = DUK__NO_ARRAY_INDEX;
int rc;
@ -3357,36 +3560,85 @@ int duk_hobject_delprop(duk_hthread *thr, duk_tval *tv_obj, duk_tval *tv_key, in
DUK_ASSERT_VALSTACK_SPACE(thr, DUK__VALSTACK_SPACE);
/* Storing the entry top is cheaper here to ensure stack is correct at exit,
* as there are several paths out.
*/
entry_top = duk_get_top(ctx);
if (DUK_TVAL_IS_UNDEFINED(tv_obj) ||
DUK_TVAL_IS_NULL(tv_obj)) {
/* Note: unconditional throw */
DUK_DDDPRINT("base object is undefined or null -> reject");
DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "invalid base reference for property delete");
goto fail_invalid_base_uncond;
}
/* FIXME: because we need to do this, just take args through stack? */
duk_push_tval(ctx, tv_obj);
duk_push_tval(ctx, tv_key);
duk_to_string(ctx, -1);
key = duk_get_hstring(ctx, -1);
DUK_ASSERT(key != NULL);
tv_obj = duk_get_tval(ctx, -2);
if (DUK_TVAL_IS_OBJECT(tv_obj)) {
duk_hobject *obj = DUK_TVAL_GET_OBJECT(tv_obj);
DUK_ASSERT(obj != NULL);
rc = duk_hobject_delprop_raw(thr, obj, key, throw_flag);
#if defined(DUK_USE_ES6_PROXY)
if (DUK_UNLIKELY(DUK_HOBJECT_HAS_SPECIAL_PROXYOBJ(obj))) {
duk_hobject *h_target;
int tmp_bool;
/* Note: proxy handling must happen before key is string coerced. */
if (duk__proxy_check(thr, obj, DUK_STRIDX_DELETE_PROPERTY, tv_key, &h_target)) {
/* -> [ ... func handler ] */
DUK_DDDPRINT("-> proxy object 'deleteProperty' for key %!T", tv_key);
duk_push_hobject(ctx, h_target); /* target */
duk_push_tval(ctx, tv_key); /* P */
duk_call_method(ctx, 2 /*nargs*/);
tmp_bool = duk_to_boolean(ctx, -1);
duk_pop(ctx);
if (!tmp_bool) {
goto fail_proxy_rejected; /* retval indicates delete failed */
}
/* Target object must be checked for a conflicting
* non-configurable property.
*/
arr_idx = duk__push_tval_to_hstring_arr_idx(ctx, tv_key, &key);
DUK_ASSERT(key != NULL);
if (duk__get_own_property_desc_raw(thr, h_target, key, arr_idx, &desc, 0 /*push_value*/)) {
DUK_DPRINT("proxy 'deleteProperty': target has matching property %!O, check for "
"conflicting property; desc.flags=0x%08x, "
"desc.get=%p, desc.set=%p",
key, (int) desc.flags, (void *) desc.get, (void *) desc.set);
int desc_reject = !(desc.flags & DUK_PROPDESC_FLAG_CONFIGURABLE);
if (desc_reject) {
/* unconditional */
DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "proxy deleteProperty rejected");
}
}
rc = 1; /* success */
goto done_rc;
}
duk_pop_2(ctx); /* [obj key] -> [] */
return rc;
obj = h_target; /* resume delete to target */
}
#endif /* DUK_USE_ES6_PROXY */
duk_to_string(ctx, -1);
key = duk_get_hstring(ctx, -1);
DUK_ASSERT(key != NULL);
rc = duk_hobject_delprop_raw(thr, obj, key, throw_flag);
goto done_rc;
} else if (DUK_TVAL_IS_STRING(tv_obj)) {
duk_hstring *h = DUK_TVAL_GET_STRING(tv_obj);
DUK_ASSERT(h != NULL);
duk_to_string(ctx, -1);
key = duk_get_hstring(ctx, -1);
DUK_ASSERT(key != NULL);
if (key == DUK_HTHREAD_STRING_LENGTH(thr)) {
goto fail_not_configurable;
}
@ -3398,16 +3650,34 @@ int duk_hobject_delprop(duk_hthread *thr, duk_tval *tv_obj, duk_tval *tv_key, in
goto fail_not_configurable;
}
}
/* FIXME: buffer virtual properties? */
/* string without matching properties, or any other primitive base */
/* non-object base, no offending virtual property */
rc = 1;
goto done_rc;
duk_pop_2(ctx); /* [obj key] -> [] */
return 1;
done_rc:
duk_set_top(ctx, entry_top);
return rc;
fail_invalid_base_uncond:
/* Note: unconditional throw */
DUK_ASSERT(duk_get_top(ctx) == entry_top);
DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "invalid base reference for property delete");
return 0;
fail_proxy_rejected:
if (throw_flag) {
DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "proxy rejected");
}
duk_set_top(ctx, entry_top);
return 0;
fail_not_configurable:
if (throw_flag) {
DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "property not configurable");
}
duk_set_top(ctx, entry_top);
return 0;
}
@ -4967,4 +5237,3 @@ int duk_hobject_object_is_sealed_frozen_helper(duk_hobject *obj, int is_frozen)
#undef DUK__HASH_UNUSED
#undef DUK__HASH_DELETED
#undef DUK__VALSTACK_SPACE

27
src/genbuiltins.py

@ -255,7 +255,10 @@ bi_global = {
{ 'name': 'Math', 'value': { 'type': 'builtin', 'id': 'bi_math' } },
{ 'name': 'JSON', 'value': { 'type': 'builtin', 'id': 'bi_json' } },
# DUK specific
# ES6 (draft)
{ 'name': 'Proxy', 'value': { 'type': 'builtin', 'id': 'bi_proxy_constructor' } },
# Duktape specific
{ 'name': 'Duktape', 'value': { 'type': 'builtin', 'id': 'bi_duktape' } },
],
'functions': [
@ -1092,6 +1095,25 @@ bi_type_error_thrower = {
'functions': [],
}
# ES6 (draft)
bi_proxy_constructor = {
'internal_prototype': 'bi_function_prototype',
# no external prototype
'class': 'Function',
'name': 'Proxy',
'length': 2,
'native': 'duk_bi_proxy_constructor',
'callable': True,
'constructable': True,
'values': [],
'functions': [
# { 'name': 'revocable', 'native': 'duk_bi_proxy_constructor_revocable', 'length': 2 },
],
}
bi_duktape = {
'internal_prototype': 'bi_object_prototype',
'class': 'Object',
@ -1319,6 +1341,9 @@ builtins_orig = [
{ 'id': 'bi_json', 'info': bi_json },
{ 'id': 'bi_type_error_thrower', 'info': bi_type_error_thrower },
# es6
{ 'id': 'bi_proxy_constructor', 'info': bi_proxy_constructor },
# custom
{ 'id': 'bi_duktape', 'info': bi_duktape },
{ 'id': 'bi_thread_constructor', 'info': bi_thread_constructor },

21
src/genstrings.py

@ -155,7 +155,6 @@ standard_builtin_string_list = [
mkstr("length"),
mkstr("prototype"),
mkstr("getPrototypeOf"),
mkstr("setPrototypeOf", es6=True),
mkstr("getOwnPropertyDescriptor"),
mkstr("getOwnPropertyNames"),
mkstr("create"),
@ -185,7 +184,6 @@ standard_builtin_string_list = [
mkstr("hasOwnProperty"),
mkstr("isPrototypeOf"),
mkstr("propertyIsEnumerable"),
mkstr("__proto__", es6=True),
# Object instances
# no special properties
@ -477,6 +475,20 @@ standard_other_string_list = [
mkstr("set"),
]
# ES6 (draft) specific strings
es6_string_list = [
mkstr("Proxy", es6=True),
#mkstr("revocable", es6=True),
# Proxy handler methods
mkstr("set", es6=True),
mkstr("get", es6=True),
mkstr("deleteProperty", es6=True),
mkstr("setPrototypeOf", es6=True),
mkstr("__proto__", es6=True),
]
# Duktape specific strings
duk_string_list = [
# non-standard global properties
@ -543,6 +555,10 @@ duk_string_list = [
#mkstr("metatable", internal=True, custom=True),
mkstr("finalizer", internal=True, custom=True),
# internal properties for Proxy objects
mkstr("target", internal=True, custom=True), # [[ProxyTarget]]
mkstr("handler", internal=True, custom=True), # [[ProxyHandler]]
# internal properties for declarative environment records
mkstr("callee", internal=True, custom=True), # to access varmap
mkstr("thread", internal=True, custom=True), # to identify valstack
@ -988,6 +1004,7 @@ def gen_string_list():
str_lists = [ standard_builtin_string_list,
standard_other_string_list,
es6_string_list,
duk_string_list ]
for lst in str_lists:

1
util/make_dist.sh

@ -88,6 +88,7 @@ for i in \
duk_bi_protos.h \
duk_bi_regexp.c \
duk_bi_string.c \
duk_bi_proxy.c \
duk_bi_thread.c \
duk_bi_thrower.c \
duk_dblunion.h \

2
website/buildsite.py

@ -776,6 +776,7 @@ def generateGuide():
navlinks.append(['#functionobjects', 'Function objects'])
navlinks.append(['#finalization', 'Finalization'])
navlinks.append(['#coroutines', 'Coroutines'])
navlinks.append(['#propertyvirtualization', 'Property virtualization'])
navlinks.append(['#compiling', 'Compiling'])
navlinks.append(['#performance', 'Performance'])
navlinks.append(['#portability', 'Portability'])
@ -811,6 +812,7 @@ def generateGuide():
res += processRawDoc('guide/functionobjects.html')
res += processRawDoc('guide/finalization.html')
res += processRawDoc('guide/coroutines.html')
res += processRawDoc('guide/propertyvirtualization.html')
res += processRawDoc('guide/compiling.html')
res += processRawDoc('guide/performance.html')
res += processRawDoc('guide/portability.html')

12
website/guide/compiling.html

@ -21,11 +21,12 @@ in your compiler):</p>
<p>Duktape feature defaults are, at a high level:</p>
<ul>
<li>Full Ecmascript compliance
<li>Full Ecmascript E5/E5.1 compliance
(including the optional
<a href="http://www.ecma-international.org/ecma-262/5.1/#sec-B">Annex B</a>
features), except for intentional real world compatibility deviations
(see <a href="#custombehavior">Custom behavior</a>)</li>
<li>Some <a href="#es6features">features borrowed from ES6</a></li>
<li>Packed value representation (8 bytes per value) when available,
unpacked value representation (12-16 bytes per value) when not</li>
<li>Reference counting and mark-and-sweep garbage collection</li>
@ -237,6 +238,11 @@ The table below summarizes the available options, in no particular order:</p>
method which is enabled by default.</td>
</tr>
<tr>
<td class="propname">DUK_OPT_NO_ES6_PROXY</td>
<td>Disable the non-standard (ES6 draft) <code>Proxy</code> object which
is enabled by default.</td>
</tr>
<tr>
<td class="definename">DUK_OPT_NO_JSONX</td>
<td>Disable support for the JSONX format. Reduces code footprint. Causes
JSONX calls to throw an error.</td>
@ -361,6 +367,10 @@ The table below summarizes the available options, in no particular order:</p>
<code>DUK_OPT_NO_PC2LINE</code>.</li>
<li>If you don't need the Duktape-specific additional JSONX/JSONC formats,
use both <code>DUK_OPT_NO_JSONX</code> and <code>DUK_OPT_NO_JSONC</code>.</li>
<li>Features borrowed from Ecmascript E6 can usually be disabled:
<code>DUK_OPT_NO_OBJECT_ES6_SETPROTOTYPEOF</code>,
<code>DUK_OPT_NO_OBJECT_ES6_PROTO_PROPERTY</code>,
<code>DUK_OPT_NO_ES6_PROXY</code>.</li>
<li>If you don't need regexp support, use <code>DUK_OPT_NO_REGEXP_SUPPORT</code>.</li>
<li>Duktape debug code uses a large, static temporary buffer for formatting debug
log lines. Use e.g. <code class="nobreak">-DDUK_OPT_DEBUG_BUFSIZE=2048</code> to reduce

1
website/guide/duktapebuiltins.html

@ -13,6 +13,7 @@ values.</p>
</thead>
<tbody>
<tr><td class="propname">Duktape</td><td>The Duktape built-in object. Contains miscellaneous implementation specific stuff.</td></tr>
<tr><td class="propname">Proxy</td><td>Proxy constructor borrowed from ES6 draft (not part of Ecmascript E5/E5.1).</td></tr>
<tr><td class="propname">print</td><td>Non-standard, browser-like function for writing to <code>stdout</code>.</td></tr>
<tr><td class="propname">alert</td><td>Non-standard, browser-like function for writing to <code>stderr</code>.</td></tr>
</tbody>

38
website/guide/es6features.html

@ -1,23 +1,47 @@
<h1 id="es6features">Ecmascript E6 features</h1>
<p>This section describes the small set of features Duktape borrows from the
<a href="https://people.mozilla.org/~jorendorff/es6-draft.html">current ES6 draft</a>.
These features are not fully compliant; the intent is to minimize custom features
and to align with the coming ES6 specification.</p>
<a href="https://people.mozilla.org/~jorendorff/es6-draft.html">current ES6 draft</a>
("Version: Rev 23, April 5, 2014 Draft"). These features are not fully compliant;
the intent is to minimize custom features and to align with the coming ES6 specification.</p>
<h2 id="es6-proto">Object.setPrototypeOf and Object.prototype.__proto__</h2>
<p><a href="https://people.mozilla.org/~jorendorff/es6-draft.html#sec-object.setprototypeof">Object.setPrototypeOf</a>
allows user to set the internal prototype of an object which is not supported in
Ecmascript E5. The Ecmascript E6 draft also provides
<a href="https://people.mozilla.org/~jorendorff/es6-draft.html#sec-object.prototype.__proto__">Object.prototype.__proto__</a>
which is an accessor property (setter/getter) which provides the same functionality
<a href="https://people.mozilla.org/~jorendorff/es6-draft.html#sec-object.prototype.__proto__">Object.prototype.__proto__</a>,
an accessor property (setter/getter) which provides the same functionality
but is compatible with existing code base which has relied on a non-standard
<code>__proto__</code> property for a while. Duktape does not support the
<a href="https://people.mozilla.org/~jorendorff/es6-draft.html#sec-__proto___-property-names-in-object-initializers">__proto__ property name in an object initializer</a>.</p>
<p>These custom features can be disabled with feature options.</p>
<p>These custom features can be disabled with the feature options
<code>DUK_OPT_NO_OBJECT_ES6_SETPROTOTYPEOF</code> and
<code>DUK_OPT_NO_OBJECT_ES6_PROTO_PROPERTY</code>.</p>
<h2 id="es6-proxy">Proxy object (subset)</h2>
<p>FIXME.</p>
<p>The Ecmascript E6 <code>Proxy</code> object allows property virtualization
and fine-grained access control for accessing an underlying plain object.
Duktape implements a strict subset of the <code>Proxy</code> object from the
ES6 draft (Rev 23).</p>
<p>Limitations include:</p>
<ul>
<li>Limited to <code>get</code>, <code>set</code>, <code>deleteProperty</code>
handler methods; ES6 draft allows a lot more, e.g. to interact with
properties being called or constructed, enumeration, etc. This causes
odd behavior if you enumerate a proxy object, call
<code>Object.defineProperty()</code> with the proxy object as an argument,
etc.</li>
<li>Proxy revocation feature of ES6 draft is not supported.</li>
<li>The target and handler objects given to <code>new Proxy()</code> cannot
be proxy objects themselves. ES6 poses no such limitation, but Duktape
enforces it to simplify the internal implementation.</li>
<li>As ES6 is still in a draft phase so the subset implemented by Duktape
may not be forwards compatible.</li>
</ul>
<p>This custom feature can be disabled with the feature option
<code>DUK_OPT_NO_ES6_PROXY</code>.</p>

10
website/guide/intro.html

@ -39,8 +39,10 @@ language implemented by Duktape.</p>
</ul>
<p>The upcoming Ecmascript Edition 6 standard is not yet final. Duktape borrows
<a href="#es6features">a few features</a> from the E6 draft to minimize Duktape
custom features and aligning with the upcoming standard.</p>
<a href="#es6features">a few features</a> from the E6 draft:</p>
<ul>
<li><a href="https://people.mozilla.org/~jorendorff/es6-draft.html">Edition 6 (E6) draft</a> (Rev 23, April 5, 2014)</li>
</ul>
<h2>Features</h2>
@ -49,8 +51,8 @@ features (some are visible to applications, while others are internal):</p>
<ul>
<li>Borrowed from ES6: <code>setPrototypeOf</code>/<code>__proto__</code>
and a subset of <code>Proxy</code> objects</li>
<li>Additional built-ins: <code>print()</code> and <code>alert()</code> borrowed from
browsers, Duktape specific built-ins in the <code>Duktape</code> global object</li>
<li>Borrowed from browsers: <code>print()</code> and <code>alert()</code></li>
<li>Duktape specific built-ins: provided by the <code>Duktape</code> global object</li>
<li>Extended types: custom "buffer" and "pointer" types, extended string type
which supports arbitary binary strings and
non-<a href="http://en.wikipedia.org/wiki/Plane_(Unicode)#Basic_Multilingual_Plane">BMP</a>

8
website/guide/luacomparison.html

@ -80,10 +80,10 @@ bytecode file. Similarly, there is no equivalent to e.g. <code>lua_dump()</code
<h2>Metatables</h2>
<p>There is currently no equivalent of Lua metatables in Duktape (or
Ecmascript). The
<a href="http://people.mozilla.org/~jorendorff/es6-draft.html#sec-8.5">ES6 proxy object</a>
concept will most likely provide similar functionality at some point.</p>
<p>There is no equivalent of Lua metatables in Ecmascript E5/E5.1, but
<a href="https://people.mozilla.org/~jorendorff/es6-draft.html#sec-proxy-objects">Ecmascript E6 Proxy objects</a>
provide similar functionality. To allow property virtualization better than available in
E5/E5.1, Duktape implements an <a href="#es6-proxy">ES6 Proxy subset</a>.</p>.
<h2>lua_next() vs. duk_next()</h2>

218
website/guide/propertyvirtualization.html

@ -0,0 +1,218 @@
<h2 id="propertyvirtualization">Property virtualization</h2>
<p>This section describes the two different mechanisms Duktape provides
for interacting with property accesses programmatically.</p>
<h3 id="virtualization-accessors">Ecmascript E5 accessor properties (getters and setters)</h3>
<p>Ecmascript Edition 5 provides <b>accessor properties</b> (also called
"setters and getters") which allow property read/write operations to be
captured by a user function.</p>
<p>For example, to capture writes to <code>obj.color</code> so that you
can validate the color value and trigger a redraw as a side effect:</p>
<pre class="ecmascript-code">
var obj = {};
Object.defineProperty(obj, 'color', {
enumerable: false,
configurable: false,
get: function () {
// current color is stored in the raw _color property here
return this._color;
},
set: function (v) {
if (!validateColor(v)) {
// only allow valid color formats to be assigned
throw new TypeError('invalid color: ' + v);
}
this._color = v;
redraw();
}
});
// Change to red and trigger a redraw.
obj.color = '#ff0000';
</pre>
<p>Setters and getters have the advantage of being part of the E5 standard
and of being widely implemented. However, they have significant limitations:</p>
<ul>
<li>They are limited to interacting with property read and write operations.
You cannot capture property deletions, interact with object enumeration, etc.</li>
<li>They only apply to individual properties which you set up as accessors
beforehand. You cannot capture all property accesses of a certain object,
which limits their usefulness in some scenarios.</li>
</ul>
<h3 id="virtualization-proxy-object">Ecmascript E6 (draft) Proxy subset</h3>
<p>In addition to accessors, Duktape provides a subset implementation of the
Ecmascript E6 (draft) <code>Proxy</code> concept (see
<a href="https://people.mozilla.org/~jorendorff/es6-draft.html#sec-proxy-objects">Proxy Objects (E6 draft)</a>
and <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy">Proxy (Mozilla)</a>).
The current subset is limited to <code>get</code>, <code>set</code>, and
<code>deleteProperty</code> handler methods.</p>
<p>For example, to simply print a line whenever any property is accessed:</p>
<pre class="ecmascript-code">
// Underlying plain object.
var target = { foo: 'bar' };
// Handler table, provides methods for interaction (can be modified on-the-fly).
var handler = {
get: function (targ, key, recv) {
print('get called for key=' + key);
return targ[key]; // return unmodified value
},
set: function (targ, key, val, recv) {
print('set called for key=' + key + ', val=' + val);
targ[key] = val; // must perform write to target manually if 'set' defined
return true; // true: indicate that property write was allowed
},
deleteProperty: function (targ, key) {
print('deleteProperty called for key=' + key);
delete targ[key]; // must perform delete to target manually if 'deleteProperty' defined
return true; // true: indicate that property delete was allowed
}
};
// Create proxy object.
var proxy = new Proxy(target, handler);
// Proxy object is then accessed normally.
proxy.foo = 321;
print(proxy.foo);
delete proxy.foo;
</pre>
<p>A Proxy object can also be used to create a read-only version of an
underlying object (which is quite tedious otherwise):</p>
<pre class="ecmascript-code">
var proxy = new Proxy(target, {
// get is omitted: reads go through to the target object automatically
// set returns false: rejects write
set: function () { return false; },
// deleteProperty returns false: rejects delete
deleteProperty: function () { return false; }
});
</pre>
<p>You can also create a write-only version of an object (which is not
possible otherwise):</p>
<pre class="ecmascript-code">
var proxy = new Proxy(target, {
get: function() { throw new TypeError('read not allowed'); }
// set and deleteProperty are omitted: write/delete operations
// are allowed and go through to the target automatically
});
</pre>
<p>The following is a more convoluted example combining multiple (somewhat
artificial) behaviors:</p>
<pre class="ecmascript-code">
var target = { foo: 'bar' };
/*
* - 'color' behaves like in the getter/setter example, cannot be deleted
* (attempt to do so causes a TypeError)
*
* - all string values are uppercased when read
*
* - property names beginning with an underscore are read/write/delete
* protected in a few different ways
*/
var handler = {
get: function (targ, key, recv) {
// this binding: handler table
// targ: underlying plain object (= target, above)
// key: key (can be any value, not just a string)
// recv: object being read from (= the proxy object)
if (typeof key === 'string' &amp;&amp; key[0] === '_') {
throw new TypeError('attempt to access a read-protected property');
}
// Return value: value provided as property lookup result.
var val = targ[key];
return (typeof val === 'string' ? val.toUpperCase() : val);
},
set: function (targ, key, val, recv) {
// this binding: handler table
// targ: underlying plain object (= target, above)
// key: key (can be any value, not just a string)
// val: value
// recv: object being read from (= the proxy object)
if (typeof key === 'string') {
if (key === 'color') {
if (!validateColor(val)) {
throw new TypeError('invalid color: ' + val);
}
targ.color = val;
redraw();
// True: indicates to caller that property write allowed.
return true;
} else if (key[0] === '_') {
// False: indicates to caller that property write rejected.
// In non-strict mode this is ignored silently, but in strict
// mode a TypeError is thrown.
return false;
}
}
// Write to target. We could also return true without writing to the
// target to simulate a successful write without changing the target.
targ[key] = val;
return true;
},
deleteProperty: function (targ, key) {
// this binding: handler table
// targ: underlying plain object (= target, above)
// key: key (can be any value, not just a string)
if (typeof key === 'string') {
if (key === 'color') {
// For 'color' a delete attempt causes an explicit error.
throw new TypeError('attempt to delete the color property');
} else if (key[0] === '_') {
// False: indicates to caller that property delete rejected.
// In non-strict mode this is ignored silently, but in strict
// mode a TypeError is thrown.
return false;
}
}
// Delete from target. We could also return true without deleting
// from the target to simulate a successful delete without changing
// the target.
delete targ[key];
return true;
}
};
</pre>
<p>The ES6 draft semantics reject some property accesses even if the handler
method would allow it. This happens if the proxy's target object has a non-configurable
conflicting property; see E6 draft Sections 9.5.8, 9.5.9, and 9.5.10 for details.
You can easily avoid any such behaviors by keeping the target object empty and,
if necessary, backing the virtual properties in an unrelated plain object.</p>
<p>Also see:</p>
<ul>
<li><a href="#es6-proxy">Proxy object (subset)</a>:
discussion of current limitations in Duktape's <code>Proxy</code> implementation.</li>
<li><a href="https://people.mozilla.org/~jorendorff/es6-draft.html#sec-proxy-objects">Proxy Objects (E6 draft)</a>:
ES6 draft specification for the <code>Proxy</code> object.</li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy">Proxy (Mozilla)</a>:
Mozilla's description of the <code>Proxy</code> implemented in Firefox, contains a lot of examples.</li>
</ul>

7
website/index/index.html

@ -12,14 +12,17 @@ functions from C code and vice versa.</p>
<p>Main features:</p>
<ul>
<li>Embeddable, portable, compact;<br />
about 210kB code, 80kB memory, 40k<a href="http://cloc.sourceforge.net/">LoC</a> source (excluding comments etc)</li>
<li>Ecmascript E5/E5.1 compliant</li>
about 210kB code, 80kB memory, 41k<a href="http://cloc.sourceforge.net/">LoC</a> source (excluding comments etc)</li>
<li><a href="http://www.ecma-international.org/ecma-262/5.1/">Ecmascript E5/E5.1</a>
compliant, some features borrowed from
<a href="https://people.mozilla.org/~jorendorff/es6-draft.html">E6 draft</a></li>
<li>Built-in regular expression engine</li>
<li>Built-in Unicode support</li>
<li>Minimal platform dependencies</li>
<li>Combined reference counting and mark-and-sweep garbage collection
with finalization</li>
<li>Custom features like coroutines and built-in logging framework</li>
<li>Property virtualization using a subset of Ecmascript E6 Proxy object</li>
<li>Liberal license (MIT)</li>
</ul>

Loading…
Cancel
Save