Browse Source

Merge pull request #1544 from svaarala/add-new-target

Add minimal support for new.target
pull/1531/head
Sami Vaarala 8 years ago
committed by GitHub
parent
commit
d298f6f3fe
  1. 5
      RELEASES.rst
  2. 3
      debugger/duk_opcodes.yaml
  3. 3
      doc/testcase-known-issues.yaml
  4. 15
      src-input/duk_bi_global.c
  5. 2
      src-input/duk_debug_vsnprintf.c
  6. 1
      src-input/duk_hstring.h
  7. 16
      src-input/duk_hstring_misc.c
  8. 2
      src-input/duk_js_bytecode.h
  9. 29
      src-input/duk_js_compiler.c
  10. 44
      src-input/duk_js_executor.c
  11. 1
      src-input/duk_strings.h
  12. 66
      tests/ecmascript/test-expr-newtarget-eval-code.js
  13. 15
      tests/ecmascript/test-expr-newtarget-function-code.js
  14. 18
      tests/ecmascript/test-expr-newtarget-lhs.js
  15. 9
      tests/ecmascript/test-expr-newtarget-newfoo.js
  16. 17
      tests/ecmascript/test-expr-newtarget-program-code.js
  17. 80
      tests/ecmascript/test-expr-newtarget.js
  18. 18
      tests/knownissues/test-expr-newtarget-eval-code-1.txt

5
RELEASES.rst

@ -2868,6 +2868,11 @@ Planned
* Add duk_push_proxy() API call which allows a Proxy to be created from C
code (GH-1500, GH-837)
* Add minimal new.target support, evaluates to undefined for non-constructor
calls and final non-bound constructor function in constructor calls;
explicit newTarget not yet supported and handling of new.target in eval()
code is not yet fully correct (GH-1544)
* Add an internal type for representing Proxy instances (duk_hproxy) to
simplify Proxy operations and improve performance (GH-1500, GH-1136)

3
debugger/duk_opcodes.yaml

@ -927,7 +927,8 @@ opcodes:
- name: INVALID
args:
- ABC_I
- name: UNUSED194
- name: NEWTARGET
- BC_R
- name: UNUSED195
- name: UNUSED196
- name: UNUSED197

3
doc/testcase-known-issues.yaml

@ -120,6 +120,9 @@
test: "test-bi-typedarray-misc-inherited-accessors.js"
knownissue: "typed array .length etc not yet inherited accessors (ES2015 requirement)"
# Moved
-
test: "test-expr-newtarget-eval-code.js"
knownissue: "new.target eval handling limitations"
# Ecmascript testcases that need special options or environment to work

15
src-input/duk_bi_global.c

@ -427,6 +427,7 @@ DUK_INTERNAL duk_ret_t duk_bi_global_object_eval(duk_context *ctx) {
duk_bool_t this_to_global = 1;
duk_small_uint_t comp_flags;
duk_int_t level = -2;
duk_small_uint_t call_flags;
DUK_ASSERT(duk_get_top(ctx) == 1 || duk_get_top(ctx) == 2); /* 2 when called by debugger */
DUK_ASSERT(thr->callstack_top >= 1); /* at least this function exists */
@ -579,7 +580,19 @@ DUK_INTERNAL duk_ret_t duk_bi_global_object_eval(duk_context *ctx) {
/* [ env? source template closure this ] */
duk_call_method(ctx, 0);
call_flags = 0;
if (act_eval->flags & DUK_ACT_FLAG_DIRECT_EVAL) {
/* Set DIRECT_EVAL flag for the call; it's not strictly
* needed for the 'inner' eval call (the eval body) but
* current new.target implementation expects to find it
* so it can traverse direct eval chains up to the real
* calling function.
*/
call_flags |= DUK_CALL_FLAG_DIRECT_EVAL;
}
duk_handle_call_unprotected(thr, /* thread */
0, /* num_stack_args */
call_flags); /* call_flags */
/* [ env? source template result ] */

2
src-input/duk_debug_vsnprintf.c

@ -104,7 +104,7 @@ DUK_LOCAL const char *duk__bc_optab[256] = {
"CALL", "TAILCALL", "NEW", "NEWOBJ", "NEWARR", "MPUTOBJ", "MPUTOBJI", "INITSET",
"INITGET", "MPUTARR", "MPUTARRI", "SETALEN", "INITENUM", "NEXTENUM", "INVLHS", "DEBUGGER",
"NOP", "INVALID", "UNUSED194", "UNUSED195", "UNUSED196", "UNUSED197", "UNUSED198", "UNUSED199",
"NOP", "INVALID", "NEWTARGET", "UNUSED195", "UNUSED196", "UNUSED197", "UNUSED198", "UNUSED199",
"UNUSED200", "UNUSED201", "UNUSED202", "UNUSED203", "UNUSED204", "UNUSED205", "UNUSED206", "UNUSED207",
"UNUSED208", "UNUSED209", "UNUSED210", "UNUSED211", "UNUSED212", "UNUSED213", "UNUSED214", "UNUSED215",
"UNUSED216", "UNUSED217", "UNUSED218", "UNUSED219", "UNUSED220", "UNUSED221", "UNUSED222", "UNUSED223",

1
src-input/duk_hstring.h

@ -222,5 +222,6 @@ struct duk_hstring_external {
DUK_INTERNAL_DECL duk_ucodepoint_t duk_hstring_char_code_at_raw(duk_hthread *thr, duk_hstring *h, duk_uint_t pos, duk_bool_t surrogate_aware);
DUK_INTERNAL_DECL duk_size_t duk_hstring_get_charlen(duk_hstring *h);
DUK_INTERNAL_DECL duk_bool_t duk_hstring_equals_ascii_cstring(duk_hstring *h, const char *cstr);
#endif /* DUK_HSTRING_H_INCLUDED */

16
src-input/duk_hstring_misc.c

@ -136,3 +136,19 @@ DUK_INTERNAL DUK_HOT duk_size_t duk_hstring_get_charlen(duk_hstring *h) {
return duk__hstring_get_charlen_slowpath(h);
}
#endif /* DUK_USE_HSTRING_CLEN */
DUK_INTERNAL duk_bool_t duk_hstring_equals_ascii_cstring(duk_hstring *h, const char *cstr) {
duk_size_t len;
DUK_ASSERT(h != NULL);
DUK_ASSERT(cstr != NULL);
len = DUK_STRLEN(cstr);
if (len != DUK_HSTRING_GET_BYTELEN(h)) {
return 0;
}
if (DUK_MEMCMP((const void *) cstr, (const void *) DUK_HSTRING_GET_DATA(h), len) == 0) {
return 1;
}
return 0;
}

2
src-input/duk_js_bytecode.h

@ -387,7 +387,7 @@ typedef duk_uint32_t duk_instr_t;
#define DUK_OP_DEBUGGER 191
#define DUK_OP_NOP 192
#define DUK_OP_INVALID 193
#define DUK_OP_UNUSED194 194
#define DUK_OP_NEWTARGET 194
#define DUK_OP_UNUSED195 195
#define DUK_OP_UNUSED196 196
#define DUK_OP_UNUSED197 197

29
src-input/duk_js_compiler.c

@ -3410,6 +3410,28 @@ DUK_LOCAL void duk__expr_nud(duk_compiler_ctx *comp_ctx, duk_ivalue *res) {
DUK_DDD(DUK_DDDPRINT("begin parsing new expression"));
reg_target = DUK__ALLOCTEMP(comp_ctx);
#if defined(DUK_USE_ES6)
if (comp_ctx->curr_token.t == DUK_TOK_PERIOD) {
/* new.target */
DUK_DDD(DUK_DDDPRINT("new.target"));
duk__advance(comp_ctx);
if (comp_ctx->curr_token.t_nores != DUK_TOK_IDENTIFIER ||
!duk_hstring_equals_ascii_cstring(comp_ctx->curr_token.str1, "target")) {
goto syntax_error_newtarget;
}
if (comp_ctx->curr_func.is_global) {
goto syntax_error_newtarget;
}
duk__advance(comp_ctx);
duk__emit_bc(comp_ctx,
DUK_OP_NEWTARGET,
reg_target);
duk__ivalue_regconst(res, (duk_regconst_t) reg_target);
return;
}
#endif /* DUK_USE_ES6 */
duk__expr_toforcedreg(comp_ctx, res, DUK__BP_CALL /*rbp_flags*/, reg_target /*forced_reg*/);
DUK__SETTEMP(comp_ctx, reg_target + 1);
@ -3757,6 +3779,11 @@ DUK_LOCAL void duk__expr_nud(duk_compiler_ctx *comp_ctx, duk_ivalue *res) {
return;
}
#if defined(DUK_USE_ES6)
syntax_error_newtarget:
DUK_ERROR_SYNTAX(thr, DUK_STR_INVALID_NEWTARGET);
#endif
syntax_error:
DUK_ERROR_SYNTAX(thr, DUK_STR_INVALID_EXPRESSION);
}
@ -7421,7 +7448,7 @@ DUK_LOCAL void duk__parse_func_formals(duk_compiler_ctx *comp_ctx) {
*/
if (comp_ctx->curr_token.t != DUK_TOK_IDENTIFIER) {
DUK_ERROR_SYNTAX(thr, "expected identifier");
DUK_ERROR_SYNTAX(thr, DUK_STR_EXPECTED_IDENTIFIER);
}
DUK_ASSERT(comp_ctx->curr_token.t == DUK_TOK_IDENTIFIER);
DUK_ASSERT(comp_ctx->curr_token.str1 != NULL);

44
src-input/duk_js_executor.c

@ -5023,6 +5023,46 @@ DUK_LOCAL DUK_NOINLINE DUK_HOT void duk__js_execute_bytecode_inner(duk_hthread *
break;
}
#if defined(DUK_USE_ES6)
case DUK_OP_NEWTARGET: {
/* https://www.ecma-international.org/ecma-262/6.0/#sec-meta-properties-runtime-semantics-evaluation
* https://www.ecma-international.org/ecma-262/6.0/#sec-getnewtarget
*
* No newTarget support now, so as a first approximation
* use the resolved (non-bound) target function.
*/
/* XXX: C API: push_new_target()? */
duk_activation *act;
act = thr->callstack_curr;
DUK_ASSERT(act != NULL);
/* Check CONSTRUCT flag from current function, or if running
* direct eval, from a non-direct-eval parent (with possibly
* more than one nested direct eval). An alternative to this
* would be to store [[NewTarget]] as a hidden symbol of the
* lexical scope, and then just look up that variable.
*/
for (;;) {
if (act == NULL) {
duk_push_undefined((duk_context *) thr);
break;
}
if (act->flags & DUK_ACT_FLAG_CONSTRUCT) {
duk_push_tval((duk_context *) thr, &act->tv_func);
break;
} else if (act->flags & DUK_ACT_FLAG_DIRECT_EVAL) {
act = act->parent;
} else {
duk_push_undefined((duk_context *) thr);
break;
}
}
DUK__REPLACE_TOP_BC_BREAK();
}
#endif /* DUK_USE_ES6 */
#if !defined(DUK_USE_EXEC_PREFER_SIZE)
#if !defined(DUK_USE_ES7_EXP_OPERATOR)
case DUK_OP_EXP_RR:
@ -5030,7 +5070,9 @@ DUK_LOCAL DUK_NOINLINE DUK_HOT void duk__js_execute_bytecode_inner(duk_hthread *
case DUK_OP_EXP_RC:
case DUK_OP_EXP_CC:
#endif
case DUK_OP_UNUSED194:
#if !defined(DUK_USE_ES6)
case DUK_OP_NEWTARGET:
#endif
case DUK_OP_UNUSED195:
case DUK_OP_UNUSED196:
case DUK_OP_UNUSED197:

1
src-input/duk_strings.h

@ -108,6 +108,7 @@
#define DUK_STR_CANNOT_DELETE_IDENTIFIER "cannot delete identifier"
#define DUK_STR_INVALID_EXPRESSION "invalid expression"
#define DUK_STR_INVALID_LVALUE "invalid lvalue"
#define DUK_STR_INVALID_NEWTARGET "invalid new.target"
#define DUK_STR_EXPECTED_IDENTIFIER "expected identifier"
#define DUK_STR_EMPTY_EXPR_NOT_ALLOWED "empty expression not allowed"
#define DUK_STR_INVALID_FOR "invalid for statement"

66
tests/ecmascript/test-expr-newtarget-eval-code.js

@ -0,0 +1,66 @@
/*
* new.target in eval code
*/
/*===
direct eval in program code
SyntaxError
indirect eval in program code
SyntaxError
direct eval in function code
undefined
false
function
true
indirect eval in function code
SyntaxError
eval-in-eval in function code
undefined
false
function
true
===*/
var myEval = eval; // indirect eval call
// Not allowed in direct or indirect eval outside of a function.
try {
print('direct eval in program code');
eval('print(typeof new.target)');
} catch (e) {
print(e.name);
}
try {
print('indirect eval in program code');
myEval('print(typeof new.target)');
} catch (e) {
print(e.name);
}
// Allowed in direct eval inside a function call.
try {
print('direct eval in function code');
eval('(function test() { eval("print(typeof new.target); print(new.target === test);"); })()');
eval('new (function test() { eval("print(typeof new.target); print(new.target === test);"); })');
} catch (e) {
print(e.name);
}
// Not allowed in indirect eval inside a function call.
try {
print('indirect eval in function code');
eval('new (function test() { myEval("print(typeof new.target);"); })');
} catch (e) {
print(e.name);
}
// This should be allowed (Firefox allows this) because GetNewTarget() just
// looks up [[NewTarget]] from the lexical environment and nested direct
// eval() calls just inherit the surrounding function's environment.
try {
print('eval-in-eval in function code');
eval('(function test() { eval("eval(\\"print(typeof new.target); print(new.target === test);\\");"); })()');
eval('new (function test() { eval("eval(\\"print(typeof new.target); print(new.target === test);\\");"); })');
} catch (e) {
print(e.stack);
}

15
tests/ecmascript/test-expr-newtarget-function-code.js

@ -0,0 +1,15 @@
/*===
undefined
function
===*/
function test() {
print(typeof new.target);
}
try {
test();
new test();
} catch (e) {
print(e.stack || e);
}

18
tests/ecmascript/test-expr-newtarget-lhs.js

@ -0,0 +1,18 @@
/*===
start
ReferenceError
===*/
function test() {
new.target = 123;
}
// Currently causes a runtime ReferenceError.
// XXX: in Firefox the error is compile time.
try {
print('start');
test();
print('done');
} catch (e) {
print(e.name);
}

9
tests/ecmascript/test-expr-newtarget-newfoo.js

@ -0,0 +1,9 @@
/*===
SyntaxError
===*/
try {
eval('new (function test() { print(typeof new.foo); })');
} catch (e) {
print(e.name);
}

17
tests/ecmascript/test-expr-newtarget-program-code.js

@ -0,0 +1,17 @@
/*
* new.target not allowed in program code; this fails without print()
* calls due to script SyntaxError.
*/
/*---
{
"intended_uncaught": true
}
---*/
/*===
===*/
print('start');
print(typeof new.target);
print('done');

80
tests/ecmascript/test-expr-newtarget.js

@ -0,0 +1,80 @@
/*
* new.target
*/
/*===
MyFunc called
undefined
false
false
MyFunc called
function
true
false
MyFunc called
undefined
false
false
MyFunc called
function
true
false
foo called
undefined
foo called
target-prop-value
outer function
inner undefined
===*/
var bound;
function MyFunc() {
print('MyFunc called');
print(typeof new.target);
print(new
.
target === MyFunc);
print(new /* comment */ . // another comment
target === bound);
}
function test() {
bound = MyFunc.bind(null, 123);
MyFunc();
new MyFunc();
bound();
new bound();
function foo() {
print('foo called');
if (new.target) {
print(new.target.target);
} else {
print('undefined');
}
}
foo.target = 'target-prop-value';
foo();
new foo();
// Inner function doesn't see outer function new.target.
function outer() {
print('outer', typeof new.target);
function inner() {
print('inner', typeof new.target);
}
inner();
}
new outer();
}
try {
test();
} catch (e) {
print(e.stack || e);
}

18
tests/knownissues/test-expr-newtarget-eval-code-1.txt

@ -0,0 +1,18 @@
summary: new.target eval handling limitations
---
direct eval in program code
undefined
indirect eval in program code
undefined
direct eval in function code
undefined
false
function
true
indirect eval in function code
undefined
eval-in-eval in function code
undefined
false
function
true
Loading…
Cancel
Save