mirror of https://github.com/svaarala/duktape.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
563 lines
15 KiB
563 lines
15 KiB
/*
|
|
* Set global object
|
|
*
|
|
* The duk_set_global_object() API call replaces the global object with the
|
|
* specified object. It also modifies the internal global lexical environment
|
|
* object so that it variable lookups are bound to the new global object.
|
|
*
|
|
* This is a basic sandboxing primitive. You can use this API call to build
|
|
* a subset global object with no access to dangerous primitives like eval(),
|
|
* the Duktape object, etc. Because you are building a new object, you can
|
|
* set the configurability, enumerability, and writability flags of every
|
|
* value as you wish, even if the original value is write protected.
|
|
*
|
|
* NOTE: don't call duk_set_global_object() for the initial 'ctx' given to
|
|
* test(), as it makes the tests order dependent.
|
|
*/
|
|
|
|
/*===
|
|
*** test_invalid_index (duk_safe_call)
|
|
==> rc=1, result='TypeError: unexpected type'
|
|
*** test_invalid_target (duk_safe_call)
|
|
==> rc=1, result='TypeError: unexpected type'
|
|
*** test_basic (duk_safe_call)
|
|
build replacement global object
|
|
top before: 1
|
|
top after: 0
|
|
key: print
|
|
key: JSON
|
|
key: eval
|
|
key: newGlobal
|
|
key: testName
|
|
indirect eval
|
|
key: print
|
|
key: JSON
|
|
key: eval
|
|
key: newGlobal
|
|
key: testName
|
|
key: myEval
|
|
true
|
|
access through this.xxx and variable lookup xxx
|
|
this.testName: my new global
|
|
testName: my new global
|
|
final top: 0
|
|
==> rc=0, result='undefined'
|
|
*** test_noeval (duk_safe_call)
|
|
top before: 1
|
|
top after: 0
|
|
key: print
|
|
hello from C eval
|
|
result: 123
|
|
result: ReferenceError: identifier 'eval' undefined
|
|
final top: 0
|
|
==> rc=0, result='undefined'
|
|
*** test_regexp_literals (duk_safe_call)
|
|
result: ReferenceError: identifier 'RegExp' undefined
|
|
/foo/
|
|
function
|
|
result: undefined
|
|
key: print
|
|
key: re
|
|
final top: 0
|
|
==> rc=0, result='undefined'
|
|
*** test_regexp_prototype_shared (duk_safe_call)
|
|
ctx1
|
|
result: ReferenceError: identifier 'RegExp' undefined
|
|
ctx2
|
|
result: ReferenceError: identifier 'RegExp' undefined
|
|
ctx1
|
|
object
|
|
set proto foo to quux
|
|
set proto bar to proto itself for comparison
|
|
result: /(?:)/
|
|
ctx2
|
|
object
|
|
foo: quux
|
|
bar equals getProto(re2): true
|
|
result: undefined
|
|
globals of ctx1 at end
|
|
key: name
|
|
key: print
|
|
key: getProto
|
|
key: re1
|
|
globals of ctx2 at end
|
|
key: name
|
|
key: print
|
|
key: getProto
|
|
key: re2
|
|
final top ctx1: 0
|
|
final top ctx2: 0
|
|
==> rc=0, result='undefined'
|
|
*** test_set_after_thread_create (duk_safe_call)
|
|
global object keys for ctx1 before change
|
|
global object keys for ctx2 before change
|
|
replace global object for ctx1
|
|
global object keys for ctx1 after change
|
|
key: newScope
|
|
key: print
|
|
global object keys for ctx2 after change
|
|
Duktape lookup through 'this' and directly
|
|
undefined
|
|
result: undefined
|
|
result: ReferenceError: identifier 'Duktape' undefined
|
|
[object Object]
|
|
result: undefined
|
|
[object Object]
|
|
result: undefined
|
|
newScope lookup through 'this' and directly
|
|
my new scope
|
|
result: undefined
|
|
my new scope
|
|
result: undefined
|
|
undefined
|
|
result: undefined
|
|
result: ReferenceError: identifier 'newScope' undefined
|
|
final top ctx1: 0
|
|
final top ctx2: 0
|
|
==> rc=0, result='undefined'
|
|
*** test_set_before_thread_create (duk_safe_call)
|
|
global object keys for ctx1 before change
|
|
replace global object for ctx1
|
|
global object keys for ctx1 after change
|
|
key: newScope1
|
|
key: print
|
|
create ctx2 from ctx1, with copied globals
|
|
global object keys for ctx2 after creation
|
|
key: newScope1
|
|
key: print
|
|
replace global object for ctx2
|
|
global object keys for ctx1
|
|
key: newScope1
|
|
key: print
|
|
global object keys for ctx2
|
|
key: newScope2
|
|
key: print
|
|
create ctx3 from ctx1, with fresh globals
|
|
global object keys for ctx1
|
|
key: newScope1
|
|
key: print
|
|
global object keys for ctx2
|
|
key: newScope2
|
|
key: print
|
|
global object keys for ctx3
|
|
final top ctx1: 2
|
|
final top ctx2: 0
|
|
final top ctx3: 0
|
|
==> rc=0, result='undefined'
|
|
===*/
|
|
|
|
static void dump_global_object_keys(duk_context *ctx) {
|
|
/* Prints only non-enumerable keys. We can't use e.g.
|
|
* Object.getOwnPropertyNames() here because we might
|
|
* not have 'Object' any more.
|
|
*/
|
|
duk_eval_string_noresult(ctx,
|
|
"(function () {\n"
|
|
" for (var k in this) { print('key:', k); }\n"
|
|
"})()\n");
|
|
}
|
|
|
|
static duk_ret_t test_invalid_index(duk_context *ctx_root) {
|
|
duk_context *ctx;
|
|
|
|
duk_push_thread(ctx_root);
|
|
ctx = duk_require_context(ctx_root, -1);
|
|
|
|
duk_set_top(ctx, 0);
|
|
duk_set_global_object(ctx);
|
|
return 0;
|
|
}
|
|
|
|
static duk_ret_t test_invalid_target(duk_context *ctx_root) {
|
|
duk_context *ctx;
|
|
|
|
duk_push_thread(ctx_root);
|
|
ctx = duk_require_context(ctx_root, -1);
|
|
|
|
duk_push_int(ctx, 123);
|
|
duk_set_global_object(ctx);
|
|
return 0;
|
|
}
|
|
|
|
static duk_ret_t test_basic(duk_context *ctx_root) {
|
|
duk_context *ctx;
|
|
|
|
duk_push_thread(ctx_root);
|
|
ctx = duk_require_context(ctx_root, -1);
|
|
|
|
/*
|
|
* First, build a new global object which contains a few keys we
|
|
* want to see.
|
|
*/
|
|
|
|
printf("build replacement global object\n");
|
|
duk_eval_string(ctx,
|
|
"({\n"
|
|
" print: this.print,\n"
|
|
" JSON: this.JSON,\n"
|
|
" eval: this.eval,\n"
|
|
" newGlobal: true,\n"
|
|
" testName: 'my new global'\n"
|
|
"})\n");
|
|
|
|
printf("top before: %ld\n", (long) duk_get_top(ctx));
|
|
duk_set_global_object(ctx);
|
|
printf("top after: %ld\n", (long) duk_get_top(ctx));
|
|
|
|
/*
|
|
* Print available keys. This exercises access to the global object
|
|
* directly.
|
|
*/
|
|
dump_global_object_keys(ctx);
|
|
|
|
/*
|
|
* Test that indirect eval executes in the new global object too.
|
|
*/
|
|
printf("indirect eval\n");
|
|
duk_eval_string_noresult(ctx,
|
|
"var myEval = eval; // writes 'myEval' to global object as a side effect\n"
|
|
);
|
|
dump_global_object_keys(ctx);
|
|
duk_eval_string_noresult(ctx,
|
|
"myEval('print(this.newGlobal)');"
|
|
);
|
|
|
|
/*
|
|
* Test access to global object through an object environment.
|
|
*/
|
|
printf("access through this.xxx and variable lookup xxx\n");
|
|
duk_eval_string_noresult(ctx,
|
|
"print('this.testName:', this.testName);\n"
|
|
);
|
|
duk_eval_string_noresult(ctx,
|
|
"print('testName:', testName);\n"
|
|
);
|
|
|
|
printf("final top: %ld\n", (long) duk_get_top(ctx));
|
|
return 0;
|
|
}
|
|
|
|
static duk_ret_t test_noeval(duk_context *ctx_root) {
|
|
duk_context *ctx;
|
|
|
|
duk_push_thread(ctx_root);
|
|
ctx = duk_require_context(ctx_root, -1);
|
|
|
|
/*
|
|
* Build a global environment with no eval - check that we can't
|
|
* eval stuff anymore from Ecmascript code. The C eval APIs still
|
|
* work.
|
|
*/
|
|
|
|
duk_eval_string(ctx,
|
|
"({\n"
|
|
" print: this.print\n"
|
|
"})\n");
|
|
|
|
printf("top before: %ld\n", (long) duk_get_top(ctx));
|
|
duk_set_global_object(ctx);
|
|
printf("top after: %ld\n", (long) duk_get_top(ctx));
|
|
dump_global_object_keys(ctx);
|
|
|
|
/*
|
|
* Eval with C API.
|
|
*/
|
|
(void) duk_peval_string(ctx,
|
|
"print('hello from C eval'); 123\n"
|
|
);
|
|
printf("result: %s\n", duk_safe_to_string(ctx, -1));
|
|
duk_pop(ctx);
|
|
|
|
/*
|
|
* Eval with Ecmascript.
|
|
*/
|
|
(void) duk_peval_string(ctx,
|
|
"eval('print(\"hello from Ecmascript eval\")')\n"
|
|
);
|
|
printf("result: %s\n", duk_safe_to_string(ctx, -1));
|
|
duk_pop(ctx);
|
|
|
|
printf("final top: %ld\n", (long) duk_get_top(ctx));
|
|
return 0;
|
|
}
|
|
|
|
static duk_ret_t test_regexp_literals(duk_context *ctx_root) {
|
|
duk_context *ctx;
|
|
|
|
duk_push_thread(ctx_root);
|
|
ctx = duk_require_context(ctx_root, -1);
|
|
|
|
/*
|
|
* Despite having no RegExp constructor, the built-in RegExp
|
|
* constructor can be accessed through regexp instances
|
|
* created through regexp literals.
|
|
*/
|
|
|
|
duk_eval_string(ctx,
|
|
"({\n"
|
|
" print: this.print\n"
|
|
"})\n");
|
|
duk_set_global_object(ctx);
|
|
|
|
(void) duk_peval_string(ctx, "print('RegExp:', RegExp)");
|
|
printf("result: %s\n", duk_safe_to_string(ctx, -1));
|
|
duk_pop(ctx);
|
|
|
|
(void) duk_peval_string(ctx,
|
|
"var re = /foo/;\n"
|
|
"print(re);\n"
|
|
"print(typeof re.exec)\n");
|
|
printf("result: %s\n", duk_safe_to_string(ctx, -1));
|
|
duk_pop(ctx);
|
|
|
|
dump_global_object_keys(ctx);
|
|
|
|
printf("final top: %ld\n", (long) duk_get_top(ctx));
|
|
return 0;
|
|
}
|
|
|
|
static duk_ret_t test_regexp_prototype_shared(duk_context *ctx_root) {
|
|
duk_context *ctx1;
|
|
duk_context *ctx2;
|
|
|
|
/*
|
|
* The RegExp constructor (built-in) is shared between two
|
|
* threads which have been created with the same global
|
|
* environment, even if the global object is replaced after
|
|
* thread creation.
|
|
*
|
|
* The RegExp instance will be the same for both. To avoid
|
|
* this, use duk_push_thread_new_globalenv().
|
|
*/
|
|
|
|
duk_push_thread(ctx_root);
|
|
ctx1 = duk_require_context(ctx_root, -1);
|
|
duk_push_thread(ctx_root);
|
|
ctx2 = duk_require_context(ctx_root, -1);
|
|
|
|
duk_eval_string(ctx1,
|
|
"({\n"
|
|
" name: 'ctx1',\n"
|
|
" print: this.print,\n"
|
|
" getProto: Object.getPrototypeOf\n"
|
|
"})");
|
|
duk_set_global_object(ctx1);
|
|
|
|
duk_eval_string(ctx2,
|
|
"({\n"
|
|
" name: 'ctx2',\n"
|
|
" print: this.print,\n"
|
|
" getProto: Object.getPrototypeOf\n"
|
|
"})");
|
|
duk_set_global_object(ctx2);
|
|
|
|
/* no direct access to RegExp */
|
|
|
|
(void) duk_peval_string(ctx1, "print(name); print('RegExp:', RegExp)");
|
|
printf("result: %s\n", duk_safe_to_string(ctx1, -1));
|
|
duk_pop(ctx1);
|
|
|
|
(void) duk_peval_string(ctx2, "print(name); print('RegExp:', RegExp)");
|
|
printf("result: %s\n", duk_safe_to_string(ctx2, -1));
|
|
duk_pop(ctx2);
|
|
|
|
/* access shared RegExp.prototype through a regexp instance */
|
|
|
|
duk_eval_string_noresult(ctx1, "var re1 = /foo/;\n");
|
|
duk_eval_string_noresult(ctx2, "var re2 = /bar/;\n");
|
|
|
|
(void) duk_peval_string(ctx1,
|
|
"print(name);\n"
|
|
"print(typeof getProto(re1));\n"
|
|
"print('set proto foo to quux');\n"
|
|
"getProto(re1).foo = 'quux';\n"
|
|
"print('set proto bar to proto itself for comparison');\n"
|
|
"getProto(re1).bar = getProto(re1);\n");
|
|
printf("result: %s\n", duk_safe_to_string(ctx1, -1));
|
|
duk_pop(ctx1);
|
|
|
|
(void) duk_peval_string(ctx2,
|
|
"print(name);\n"
|
|
"print(typeof getProto(re2));\n"
|
|
"print('foo:', getProto(re2).foo);\n"
|
|
"print('bar equals getProto(re2):', getProto(re2).bar === getProto(re2));\n");
|
|
printf("result: %s\n", duk_safe_to_string(ctx2, -1));
|
|
duk_pop(ctx2);
|
|
|
|
/* dump global objects at the end */
|
|
|
|
printf("globals of ctx1 at end\n");
|
|
dump_global_object_keys(ctx1);
|
|
printf("globals of ctx2 at end\n");
|
|
dump_global_object_keys(ctx2);
|
|
|
|
printf("final top ctx1: %ld\n", (long) duk_get_top(ctx1));
|
|
printf("final top ctx2: %ld\n", (long) duk_get_top(ctx2));
|
|
return 0;
|
|
}
|
|
|
|
static duk_ret_t test_set_after_thread_create(duk_context *ctx_root) {
|
|
duk_context *ctx1;
|
|
duk_context *ctx2;
|
|
|
|
duk_push_thread(ctx_root);
|
|
ctx1 = duk_require_context(ctx_root, -1);
|
|
duk_push_thread(ctx_root);
|
|
ctx2 = duk_require_context(ctx_root, -1);
|
|
|
|
/*
|
|
* Setting the global object after creating a new thread using
|
|
* duk_push_thread() has no effect on the other thread which
|
|
* originally shared the global environment.
|
|
*
|
|
* Access the global object directly ("this.foo") and through
|
|
* the global scope ("foo") to ensure both are updated properly
|
|
* and that there's no "cross talk".
|
|
*
|
|
* The built-in objects (like RegExp, Number, Duktape, etc) are
|
|
* still shared by the threads if they are accessible. To avoid
|
|
* this, use duk_push_thread_new_globalenv().
|
|
*/
|
|
|
|
printf("global object keys for ctx1 before change\n");
|
|
dump_global_object_keys(ctx1);
|
|
printf("global object keys for ctx2 before change\n");
|
|
dump_global_object_keys(ctx2);
|
|
|
|
printf("replace global object for ctx1\n");
|
|
duk_eval_string(ctx1,
|
|
"({\n"
|
|
" newScope: 'my new scope',\n"
|
|
" print: this.print\n"
|
|
"})");
|
|
duk_set_global_object(ctx1);
|
|
|
|
printf("global object keys for ctx1 after change\n");
|
|
dump_global_object_keys(ctx1);
|
|
printf("global object keys for ctx2 after change\n");
|
|
dump_global_object_keys(ctx2);
|
|
|
|
printf("Duktape lookup through 'this' and directly\n");
|
|
(void) duk_peval_string(ctx1, "print(this.Duktape);\n");
|
|
printf("result: %s\n", duk_safe_to_string(ctx1, -1));
|
|
duk_pop(ctx1);
|
|
(void) duk_peval_string(ctx1, "print(Duktape);\n");
|
|
printf("result: %s\n", duk_safe_to_string(ctx1, -1));
|
|
duk_pop(ctx1);
|
|
(void) duk_peval_string(ctx2, "print(this.Duktape);\n");
|
|
printf("result: %s\n", duk_safe_to_string(ctx2, -1));
|
|
duk_pop(ctx2);
|
|
(void) duk_peval_string(ctx2, "print(Duktape);\n");
|
|
printf("result: %s\n", duk_safe_to_string(ctx2, -1));
|
|
duk_pop(ctx2);
|
|
|
|
printf("newScope lookup through 'this' and directly\n");
|
|
(void) duk_peval_string(ctx1, "print(this.newScope);\n");
|
|
printf("result: %s\n", duk_safe_to_string(ctx1, -1));
|
|
duk_pop(ctx1);
|
|
(void) duk_peval_string(ctx1, "print(newScope);\n");
|
|
printf("result: %s\n", duk_safe_to_string(ctx1, -1));
|
|
duk_pop(ctx1);
|
|
(void) duk_peval_string(ctx2, "print(this.newScope);\n");
|
|
printf("result: %s\n", duk_safe_to_string(ctx2, -1));
|
|
duk_pop(ctx2);
|
|
(void) duk_peval_string(ctx2, "print(newScope);\n");
|
|
printf("result: %s\n", duk_safe_to_string(ctx2, -1));
|
|
duk_pop(ctx2);
|
|
|
|
printf("final top ctx1: %ld\n", (long) duk_get_top(ctx1));
|
|
printf("final top ctx2: %ld\n", (long) duk_get_top(ctx2));
|
|
return 0;
|
|
}
|
|
|
|
static duk_ret_t test_set_before_thread_create(duk_context *ctx_root) {
|
|
duk_context *ctx1;
|
|
duk_context *ctx2;
|
|
duk_context *ctx3;
|
|
|
|
/*
|
|
* Creating a new thread using duk_push_thread() after setting
|
|
* globals on the current thread causes the new thread to inherit
|
|
* the new global object.
|
|
*/
|
|
|
|
duk_push_thread(ctx_root);
|
|
ctx1 = duk_require_context(ctx_root, -1);
|
|
printf("global object keys for ctx1 before change\n");
|
|
dump_global_object_keys(ctx1);
|
|
|
|
printf("replace global object for ctx1\n");
|
|
duk_eval_string(ctx1,
|
|
"({\n"
|
|
" newScope1: 'my new scope 1',\n"
|
|
" print: this.print\n"
|
|
"})");
|
|
duk_set_global_object(ctx1);
|
|
|
|
printf("global object keys for ctx1 after change\n");
|
|
dump_global_object_keys(ctx1);
|
|
|
|
/* NOTE: here it is critical that duk_push_thread() is called for ctx1,
|
|
* not ctx_root!
|
|
*/
|
|
printf("create ctx2 from ctx1, with copied globals\n");
|
|
duk_push_thread(ctx1);
|
|
ctx2 = duk_require_context(ctx1, -1);
|
|
|
|
/* Here ctx2 will have the replaced global object of ctx1. */
|
|
printf("global object keys for ctx2 after creation\n");
|
|
dump_global_object_keys(ctx2);
|
|
|
|
/*
|
|
* You can set another global object for the new thread; the two
|
|
* thread are not linked in any way. Inheritance of the global
|
|
* object happens only during thread creation.
|
|
*/
|
|
|
|
printf("replace global object for ctx2\n");
|
|
duk_eval_string(ctx2,
|
|
"({\n"
|
|
" newScope2: 'my new scope 2',\n"
|
|
" print: this.print\n"
|
|
"})");
|
|
duk_set_global_object(ctx2);
|
|
|
|
printf("global object keys for ctx1\n");
|
|
dump_global_object_keys(ctx1);
|
|
printf("global object keys for ctx2\n");
|
|
dump_global_object_keys(ctx2);
|
|
|
|
/*
|
|
* However, if you create a thread with duk_push_thread_new_globalenv()
|
|
* it gets fresh globals regardless of the previous context.
|
|
*/
|
|
|
|
printf("create ctx3 from ctx1, with fresh globals\n");
|
|
|
|
/* NOTE: again, push on ctx1, not ctx_root. */
|
|
duk_push_thread_new_globalenv(ctx1);
|
|
ctx3 = duk_require_context(ctx1, -1);
|
|
|
|
printf("global object keys for ctx1\n");
|
|
dump_global_object_keys(ctx1);
|
|
printf("global object keys for ctx2\n");
|
|
dump_global_object_keys(ctx2);
|
|
printf("global object keys for ctx3\n");
|
|
dump_global_object_keys(ctx3);
|
|
|
|
printf("final top ctx1: %ld\n", (long) duk_get_top(ctx1));
|
|
printf("final top ctx2: %ld\n", (long) duk_get_top(ctx2));
|
|
printf("final top ctx3: %ld\n", (long) duk_get_top(ctx3));
|
|
return 0;
|
|
}
|
|
|
|
void test(duk_context *ctx) {
|
|
TEST_SAFE_CALL(test_invalid_index);
|
|
TEST_SAFE_CALL(test_invalid_target);
|
|
TEST_SAFE_CALL(test_basic);
|
|
TEST_SAFE_CALL(test_noeval);
|
|
TEST_SAFE_CALL(test_regexp_literals);
|
|
TEST_SAFE_CALL(test_regexp_prototype_shared);
|
|
TEST_SAFE_CALL(test_set_after_thread_create);
|
|
TEST_SAFE_CALL(test_set_before_thread_create);
|
|
}
|
|
|