forked from svaarala/duktape
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtest-dev-prototype-loop.c
More file actions
311 lines (254 loc) · 8.34 KB
/
test-dev-prototype-loop.c
File metadata and controls
311 lines (254 loc) · 8.34 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
/*
* Prototype loop is tricky to handle internally and must not cause e.g.
* GC failures. Exercise all prototype walk sites, and a few other
* specific cases.
*
* At the moment prototype loops cause an error to be thrown so that
* they are easily noticed (used code should never intentionally create
* one). Internal stuff, like GC handling, must -avoid- throwing errors
* in critical situations so that e.g. GC doesn't terminate with an
* uncaught error. This testcase documents the current behavior.
*
* Sites can be found with DUK_HOBJECT_PROTOTYPE_CHAIN_SANITY and then
* looking at possible callers:
*
* - duk_hobject_prototype_chain_contains(): see below
*
* - duk__get_property_desc: can be exercised through duk_hobject_hasprop
*
* - duk_hobject_getprop
*
* - duk_hobject_putprop
*
* - duk_js_instanceof
*
* - duk__get_identifier_reference: walks environments chained using the
* prototype reference; these cannot be in a loop and user code cannot
* access these
*
* duk_hobject_prototype_chain_contains() call sites:
*
* - Object.prototype.isPrototypeOf()
*
* - duk_err_augment_error_create(): can be exercised by throwing an
* object with a prototype loop
*/
/*===
*** test_gc (duk_safe_call)
first gc
make unreachable
second gc
==> rc=0, result='undefined'
*** test_is_prototype_of (duk_safe_call)
Object.prototype.isPrototypeOf result: false
Object.prototype.isPrototypeOf result: true
==> rc=1, result='RangeError: prototype chain limit'
*** test_error_augment (duk_safe_call)
ret=1
throw value .foo=123
==> rc=0, result='undefined'
*** test_hasprop (duk_safe_call)
hasprop foo: 1
hasprop bar: 1
==> rc=1, result='RangeError: prototype chain limit'
*** test_getprop (duk_safe_call)
getprop foo: 123
getprop bar: 321
==> rc=1, result='RangeError: prototype chain limit'
*** test_putprop (duk_safe_call)
putprop foo done
putprop bar done
==> rc=1, result='RangeError: prototype chain limit'
*** test_instanceof (duk_safe_call)
object function
true
object function
true
object function
==> rc=1, result='RangeError: prototype chain limit'
still here
===*/
static void prep(duk_context *ctx) {
duk_set_top(ctx, 0);
duk_push_object(ctx);
duk_push_object(ctx);
duk_push_int(ctx, 123);
duk_put_prop_string(ctx, 0, "foo");
duk_push_int(ctx, 321);
duk_put_prop_string(ctx, 1, "bar");
duk_dup(ctx, 0);
duk_set_prototype(ctx, 1);
duk_dup(ctx, 1);
duk_set_prototype(ctx, 0);
/* Two objects on stack, in a prototype loop with each other.
* One object has a 'foo' property, the other a 'bar' property.
*/
}
static duk_ret_t test_gc(duk_context *ctx, void *udata) {
(void) udata;
prep(ctx);
/* Both objects are now in a prototype loop. Force garbage
* collection to ensure nothing breaks.
*/
printf("first gc\n"); fflush(stdout);
duk_gc(ctx, 0);
/* Make the objects unreachable and re-run GC. This triggers
* e.g. finalizer checks.
*/
printf("make unreachable\n"); fflush(stdout);
duk_set_top(ctx, 0);
printf("second gc\n"); fflush(stdout);
duk_gc(ctx, 0);
return 0;
}
static duk_ret_t test_is_prototype_of(duk_context *ctx, void *udata) {
(void) udata;
prep(ctx);
/* obj0.isPrototypeOf(dummy) -> false, traverses prototype chain of dummy */
duk_eval_string(ctx, "Object.prototype.isPrototypeOf");
duk_dup(ctx, 0);
duk_push_object(ctx);
duk_call_method(ctx, 1);
printf("Object.prototype.isPrototypeOf result: %s\n", duk_safe_to_string(ctx, -1));
duk_pop(ctx);
/* obj0.isPrototypeOf(obj1) -> true, traverses prototype chain of obj1 */
duk_eval_string(ctx, "Object.prototype.isPrototypeOf");
duk_dup(ctx, 0);
duk_dup(ctx, 1);
duk_call_method(ctx, 1);
printf("Object.prototype.isPrototypeOf result: %s\n", duk_safe_to_string(ctx, -1));
duk_pop(ctx);
/* dummy.isPrototypeOf(obj0) -> traverses prototype chain of obj0 and throws */
duk_eval_string(ctx, "Object.prototype.isPrototypeOf");
duk_push_object(ctx);
duk_dup(ctx, 0);
duk_call_method(ctx, 1);
printf("Object.prototype.isPrototypeOf result: %s\n", duk_safe_to_string(ctx, -1));
duk_pop(ctx);
return 0;
}
static duk_ret_t augment_raw(duk_context *ctx, void *udata) {
(void) udata;
duk_throw(ctx);
return 0;
}
static duk_ret_t test_error_augment(duk_context *ctx, void *udata) {
duk_int_t ret;
(void) udata;
prep(ctx);
/* This case is a bit tricky. There used to be a problem where the
* error augmentation process itself failed when checking whether or
* not the throw value inherited from Error. This has now been fixed
* and the error value no longer gets augmented and is thrown correctly.
*
* The TEST_SAFE_CALL() wrapper uses duk_safe_to_string() to coerce
* the throw result. Since the object doesn't have a toString()
* function, this coercion will fail and generate a prototype loop
* error!
*
* So, use a separate duk_safe_call() wrapping here to ensure we treat
* the final result value carefully. We print out 'foo' to be sure
* the correct value was thrown.
*/
duk_dup(ctx, 0);
ret = duk_safe_call(ctx, augment_raw, NULL, 0 /*nargs*/, 1 /*nrets*/);
printf("ret=%d\n", (int) ret);
duk_get_prop_string(ctx, -1, "foo");
printf("throw value .foo=%d\n", duk_get_int(ctx, -1));
duk_pop_2(ctx);
return 0;
}
static duk_ret_t test_hasprop(duk_context *ctx, void *udata) {
duk_bool_t ret;
(void) udata;
prep(ctx);
/* Property exists, own property */
ret = duk_has_prop_string(ctx, 0, "foo");
printf("hasprop foo: %d\n", (int) ret);
/* Property exists, inherited property */
ret = duk_has_prop_string(ctx, 0, "bar");
printf("hasprop bar: %d\n", (int) ret);
/* Property doesn't exist, terminate with error */
ret = duk_has_prop_string(ctx, 0, "quux");
printf("hasprop quux: %d\n", (int) ret);
return 0;
}
static duk_ret_t test_getprop(duk_context *ctx, void *udata) {
(void) udata;
prep(ctx);
/* Property exists, own property */
duk_get_prop_string(ctx, 0, "foo");
printf("getprop foo: %s\n", duk_safe_to_string(ctx, -1));
duk_pop(ctx);
/* Property exists, inherited property */
duk_get_prop_string(ctx, 0, "bar");
printf("getprop bar: %s\n", duk_safe_to_string(ctx, -1));
duk_pop(ctx);
/* Property doesn't exist, terminate with error */
duk_get_prop_string(ctx, 0, "quux");
printf("getprop quux: %s\n", duk_safe_to_string(ctx, -1));
duk_pop(ctx);
return 0;
}
static duk_ret_t test_putprop(duk_context *ctx, void *udata) {
(void) udata;
prep(ctx);
/* Property exists, own property */
duk_push_int(ctx, 1001);
duk_put_prop_string(ctx, 0, "foo");
printf("putprop foo done\n");
/* Property exists, inherited property */
duk_push_int(ctx, 1002);
duk_put_prop_string(ctx, 0, "bar");
printf("putprop bar done\n");
/* Property doesn't exist, terminate with error */
duk_push_int(ctx, 1003);
duk_put_prop_string(ctx, 0, "quux");
printf("putprop quux done\n");
return 0;
}
static duk_ret_t test_instanceof(duk_context *ctx, void *udata) {
(void) udata;
prep(ctx);
/* For 'a instanceof b', the [[HasInstance]] algorithm looks up
* b.prototype and then walks the internal prototype chain of 'a'
* looking for b.prototype. The rvalue must also be a Function.
* So we need a temporary object wrapping one of the objects
* created by prep().
*/
/* obj0 instanceof { prototype: obj0 } -> true, found */
duk_eval_string(ctx, "(function (a,b) { print(typeof a, typeof b); print(a instanceof b); })");
duk_dup(ctx, 0);
duk_eval_string(ctx, "(function() {})");
duk_dup(ctx, 0);
duk_put_prop_string(ctx, -2, "prototype");
duk_call(ctx, 2);
duk_pop(ctx);
/* obj0 instanceof { prototype: obj1 } -> true, found */
duk_eval_string(ctx, "(function (a,b) { print(typeof a, typeof b); print(a instanceof b); })");
duk_dup(ctx, 0);
duk_eval_string(ctx, "(function() {})");
duk_dup(ctx, 0);
duk_put_prop_string(ctx, -2, "prototype");
duk_call(ctx, 2);
duk_pop(ctx);
/* obj0 instanceof { prototype: dummy } -> error, loop */
duk_eval_string(ctx, "(function (a,b) { print(typeof a, typeof b); print(a instanceof b); })");
duk_dup(ctx, 0);
duk_eval_string(ctx, "(function() {})");
duk_push_object(ctx);
duk_put_prop_string(ctx, -2, "prototype");
duk_call(ctx, 2);
duk_pop(ctx);
return 0;
}
void test(duk_context *ctx) {
TEST_SAFE_CALL(test_gc);
TEST_SAFE_CALL(test_is_prototype_of);
TEST_SAFE_CALL(test_error_augment);
TEST_SAFE_CALL(test_hasprop);
TEST_SAFE_CALL(test_getprop);
TEST_SAFE_CALL(test_putprop);
TEST_SAFE_CALL(test_instanceof);
printf("still here\n"); fflush(stdout);
}