1 /*
2 * Error built-ins
3 */
4
5 #include "duk_internal.h"
6
duk_bi_error_constructor_shared(duk_hthread * thr)7 DUK_INTERNAL duk_ret_t duk_bi_error_constructor_shared(duk_hthread *thr) {
8 /* Behavior for constructor and non-constructor call is
9 * the same except for augmenting the created error. When
10 * called as a constructor, the caller (duk_new()) will handle
11 * augmentation; when called as normal function, we need to do
12 * it here.
13 */
14
15 duk_small_int_t bidx_prototype = duk_get_current_magic(thr);
16
17 /* same for both error and each subclass like TypeError */
18 duk_uint_t flags_and_class = DUK_HOBJECT_FLAG_EXTENSIBLE |
19 DUK_HOBJECT_FLAG_FASTREFS |
20 DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_ERROR);
21
22 (void) duk_push_object_helper(thr, flags_and_class, bidx_prototype);
23
24 /* If message is undefined, the own property 'message' is not set at
25 * all to save property space. An empty message is inherited anyway.
26 */
27 if (!duk_is_undefined(thr, 0)) {
28 duk_to_string(thr, 0);
29 duk_dup_0(thr); /* [ message error message ] */
30 duk_xdef_prop_stridx_short(thr, -2, DUK_STRIDX_MESSAGE, DUK_PROPDESC_FLAGS_WC);
31 }
32
33 /* Augment the error if called as a normal function. __FILE__ and __LINE__
34 * are not desirable in this case.
35 */
36
37 #if defined(DUK_USE_AUGMENT_ERROR_CREATE)
38 if (!duk_is_constructor_call(thr)) {
39 duk_err_augment_error_create(thr, thr, NULL, 0, DUK_AUGMENT_FLAG_NOBLAME_FILELINE);
40 }
41 #endif
42
43 return 1;
44 }
45
duk_bi_error_prototype_to_string(duk_hthread * thr)46 DUK_INTERNAL duk_ret_t duk_bi_error_prototype_to_string(duk_hthread *thr) {
47 /* XXX: optimize with more direct internal access */
48
49 duk_push_this(thr);
50 (void) duk_require_hobject_promote_mask(thr, -1, DUK_TYPE_MASK_LIGHTFUNC | DUK_TYPE_MASK_BUFFER);
51
52 /* [ ... this ] */
53
54 duk_get_prop_stridx_short(thr, -1, DUK_STRIDX_NAME);
55 if (duk_is_undefined(thr, -1)) {
56 duk_pop(thr);
57 duk_push_literal(thr, "Error");
58 } else {
59 duk_to_string(thr, -1);
60 }
61
62 /* [ ... this name ] */
63
64 /* XXX: Are steps 6 and 7 in E5 Section 15.11.4.4 duplicated by
65 * accident or are they actually needed? The first ToString()
66 * could conceivably return 'undefined'.
67 */
68 duk_get_prop_stridx_short(thr, -2, DUK_STRIDX_MESSAGE);
69 if (duk_is_undefined(thr, -1)) {
70 duk_pop(thr);
71 duk_push_hstring_empty(thr);
72 } else {
73 duk_to_string(thr, -1);
74 }
75
76 /* [ ... this name message ] */
77
78 if (duk_get_length(thr, -2) == 0) {
79 /* name is empty -> return message */
80 return 1;
81 }
82 if (duk_get_length(thr, -1) == 0) {
83 /* message is empty -> return name */
84 duk_pop(thr);
85 return 1;
86 }
87 duk_push_literal(thr, ": ");
88 duk_insert(thr, -2); /* ... name ': ' message */
89 duk_concat(thr, 3);
90
91 return 1;
92 }
93
94 #if defined(DUK_USE_TRACEBACKS)
95
96 /*
97 * Traceback handling
98 *
99 * The unified helper decodes the traceback and produces various requested
100 * outputs. It should be optimized for size, and may leave garbage on stack,
101 * only the topmost return value matters. For instance, traceback separator
102 * and decoded strings are pushed even when looking for filename only.
103 *
104 * NOTE: although _Tracedata is an internal property, user code can currently
105 * write to the array (or replace it with something other than an array).
106 * The code below must tolerate arbitrary _Tracedata. It can throw errors
107 * etc, but cannot cause a segfault or memory unsafe behavior.
108 */
109
110 /* constants arbitrary, chosen for small loads */
111 #define DUK__OUTPUT_TYPE_TRACEBACK (-1)
112 #define DUK__OUTPUT_TYPE_FILENAME 0
113 #define DUK__OUTPUT_TYPE_LINENUMBER 1
114
duk__error_getter_helper(duk_hthread * thr,duk_small_int_t output_type)115 DUK_LOCAL duk_ret_t duk__error_getter_helper(duk_hthread *thr, duk_small_int_t output_type) {
116 duk_idx_t idx_td;
117 duk_small_int_t i; /* traceback depth fits into 16 bits */
118 duk_small_int_t t; /* stack type fits into 16 bits */
119 duk_small_int_t count_func = 0; /* traceback depth ensures fits into 16 bits */
120 const char *str_tailcall = " tailcall";
121 const char *str_strict = " strict";
122 const char *str_construct = " construct";
123 const char *str_prevyield = " preventsyield";
124 const char *str_directeval = " directeval";
125 const char *str_empty = "";
126
127 DUK_ASSERT_TOP(thr, 0); /* fixed arg count */
128
129 duk_push_this(thr);
130 duk_xget_owndataprop_stridx_short(thr, -1, DUK_STRIDX_INT_TRACEDATA);
131 idx_td = duk_get_top_index(thr);
132
133 duk_push_hstring_stridx(thr, DUK_STRIDX_NEWLINE_4SPACE);
134 duk_push_this(thr);
135
136 /* [ ... this tracedata sep this ] */
137
138 /* XXX: skip null filename? */
139
140 if (duk_check_type(thr, idx_td, DUK_TYPE_OBJECT)) {
141 /* Current tracedata contains 2 entries per callstack entry. */
142 for (i = 0; ; i += 2) {
143 duk_int_t pc;
144 duk_uint_t line;
145 duk_uint_t flags;
146 duk_double_t d;
147 const char *funcname;
148 const char *filename;
149 duk_hobject *h_func;
150 duk_hstring *h_name;
151
152 duk_require_stack(thr, 5);
153 duk_get_prop_index(thr, idx_td, (duk_uarridx_t) i);
154 duk_get_prop_index(thr, idx_td, (duk_uarridx_t) (i + 1));
155 d = duk_to_number_m1(thr);
156 pc = duk_double_to_int_t(DUK_FMOD(d, DUK_DOUBLE_2TO32));
157 flags = duk_double_to_uint_t(DUK_FLOOR(d / DUK_DOUBLE_2TO32));
158 t = (duk_small_int_t) duk_get_type(thr, -2);
159
160 if (t == DUK_TYPE_OBJECT || t == DUK_TYPE_LIGHTFUNC) {
161 /*
162 * ECMAScript/native function call or lightfunc call
163 */
164
165 count_func++;
166
167 /* [ ... v1(func) v2(pc+flags) ] */
168
169 /* These may be systematically omitted by Duktape
170 * with certain config options, but allow user to
171 * set them on a case-by-case basis.
172 */
173 duk_get_prop_stridx_short(thr, -2, DUK_STRIDX_NAME);
174 duk_get_prop_stridx_short(thr, -3, DUK_STRIDX_FILE_NAME);
175
176 #if defined(DUK_USE_PC2LINE)
177 line = (duk_uint_t) duk_hobject_pc2line_query(thr, -4, (duk_uint_fast32_t) pc);
178 #else
179 line = 0;
180 #endif
181
182 /* [ ... v1 v2 name filename ] */
183
184 /* When looking for .fileName/.lineNumber, blame first
185 * function which has a .fileName.
186 */
187 if (duk_is_string_notsymbol(thr, -1)) {
188 if (output_type == DUK__OUTPUT_TYPE_FILENAME) {
189 return 1;
190 } else if (output_type == DUK__OUTPUT_TYPE_LINENUMBER) {
191 duk_push_uint(thr, line);
192 return 1;
193 }
194 }
195
196 /* XXX: Change 'anon' handling here too, to use empty string for anonymous functions? */
197 /* XXX: Could be improved by coercing to a readable duk_tval (especially string escaping) */
198 h_name = duk_get_hstring_notsymbol(thr, -2); /* may be NULL */
199 funcname = (h_name == NULL || h_name == DUK_HTHREAD_STRING_EMPTY_STRING(thr)) ?
200 "[anon]" : (const char *) DUK_HSTRING_GET_DATA(h_name);
201 filename = duk_get_string_notsymbol(thr, -1);
202 filename = filename ? filename : "";
203 DUK_ASSERT(funcname != NULL);
204 DUK_ASSERT(filename != NULL);
205
206 h_func = duk_get_hobject(thr, -4); /* NULL for lightfunc */
207
208 if (h_func == NULL) {
209 duk_push_sprintf(thr, "at %s light%s%s%s%s%s",
210 (const char *) funcname,
211 (const char *) ((flags & DUK_ACT_FLAG_STRICT) ? str_strict : str_empty),
212 (const char *) ((flags & DUK_ACT_FLAG_TAILCALLED) ? str_tailcall : str_empty),
213 (const char *) ((flags & DUK_ACT_FLAG_CONSTRUCT) ? str_construct : str_empty),
214 (const char *) ((flags & DUK_ACT_FLAG_DIRECT_EVAL) ? str_directeval : str_empty),
215 (const char *) ((flags & DUK_ACT_FLAG_PREVENT_YIELD) ? str_prevyield : str_empty));
216 } else if (DUK_HOBJECT_HAS_NATFUNC(h_func)) {
217 duk_push_sprintf(thr, "at %s (%s) native%s%s%s%s%s",
218 (const char *) funcname,
219 (const char *) filename,
220 (const char *) ((flags & DUK_ACT_FLAG_STRICT) ? str_strict : str_empty),
221 (const char *) ((flags & DUK_ACT_FLAG_TAILCALLED) ? str_tailcall : str_empty),
222 (const char *) ((flags & DUK_ACT_FLAG_CONSTRUCT) ? str_construct : str_empty),
223 (const char *) ((flags & DUK_ACT_FLAG_DIRECT_EVAL) ? str_directeval : str_empty),
224 (const char *) ((flags & DUK_ACT_FLAG_PREVENT_YIELD) ? str_prevyield : str_empty));
225 } else {
226 duk_push_sprintf(thr, "at %s (%s:%lu)%s%s%s%s%s",
227 (const char *) funcname,
228 (const char *) filename,
229 (unsigned long) line,
230 (const char *) ((flags & DUK_ACT_FLAG_STRICT) ? str_strict : str_empty),
231 (const char *) ((flags & DUK_ACT_FLAG_TAILCALLED) ? str_tailcall : str_empty),
232 (const char *) ((flags & DUK_ACT_FLAG_CONSTRUCT) ? str_construct : str_empty),
233 (const char *) ((flags & DUK_ACT_FLAG_DIRECT_EVAL) ? str_directeval : str_empty),
234 (const char *) ((flags & DUK_ACT_FLAG_PREVENT_YIELD) ? str_prevyield : str_empty));
235 }
236 duk_replace(thr, -5); /* [ ... v1 v2 name filename str ] -> [ ... str v2 name filename ] */
237 duk_pop_3(thr); /* -> [ ... str ] */
238 } else if (t == DUK_TYPE_STRING) {
239 const char *str_file;
240
241 /*
242 * __FILE__ / __LINE__ entry, here 'pc' is line number directly.
243 * Sometimes __FILE__ / __LINE__ is reported as the source for
244 * the error (fileName, lineNumber), sometimes not.
245 */
246
247 /* [ ... v1(filename) v2(line+flags) ] */
248
249 /* When looking for .fileName/.lineNumber, blame compilation
250 * or C call site unless flagged not to do so.
251 */
252 if (!(flags & DUK_TB_FLAG_NOBLAME_FILELINE)) {
253 if (output_type == DUK__OUTPUT_TYPE_FILENAME) {
254 duk_pop(thr);
255 return 1;
256 } else if (output_type == DUK__OUTPUT_TYPE_LINENUMBER) {
257 duk_push_int(thr, pc);
258 return 1;
259 }
260 }
261
262 /* Tracedata is trusted but avoid any risk of using a NULL
263 * for %s format because it has undefined behavior. Symbols
264 * don't need to be explicitly rejected as they pose no memory
265 * safety issues.
266 */
267 str_file = (const char *) duk_get_string(thr, -2);
268 duk_push_sprintf(thr, "at [anon] (%s:%ld) internal",
269 (const char *) (str_file ? str_file : "null"), (long) pc);
270 duk_replace(thr, -3); /* [ ... v1 v2 str ] -> [ ... str v2 ] */
271 duk_pop(thr); /* -> [ ... str ] */
272 } else {
273 /* unknown, ignore */
274 duk_pop_2(thr);
275 break;
276 }
277 }
278
279 if (count_func >= DUK_USE_TRACEBACK_DEPTH) {
280 /* Possibly truncated; there is no explicit truncation
281 * marker so this is the best we can do.
282 */
283
284 duk_push_hstring_stridx(thr, DUK_STRIDX_BRACKETED_ELLIPSIS);
285 }
286 }
287
288 /* [ ... this tracedata sep this str1 ... strN ] */
289
290 if (output_type != DUK__OUTPUT_TYPE_TRACEBACK) {
291 return 0;
292 } else {
293 /* The 'this' after 'sep' will get ToString() coerced by
294 * duk_join() automatically. We don't want to do that
295 * coercion when providing .fileName or .lineNumber (GH-254).
296 */
297 duk_join(thr, duk_get_top(thr) - (idx_td + 2) /*count, not including sep*/);
298 return 1;
299 }
300 }
301
302 /* XXX: Output type could be encoded into native function 'magic' value to
303 * save space. For setters the stridx could be encoded into 'magic'.
304 */
305
duk_bi_error_prototype_stack_getter(duk_hthread * thr)306 DUK_INTERNAL duk_ret_t duk_bi_error_prototype_stack_getter(duk_hthread *thr) {
307 return duk__error_getter_helper(thr, DUK__OUTPUT_TYPE_TRACEBACK);
308 }
309
duk_bi_error_prototype_filename_getter(duk_hthread * thr)310 DUK_INTERNAL duk_ret_t duk_bi_error_prototype_filename_getter(duk_hthread *thr) {
311 return duk__error_getter_helper(thr, DUK__OUTPUT_TYPE_FILENAME);
312 }
313
duk_bi_error_prototype_linenumber_getter(duk_hthread * thr)314 DUK_INTERNAL duk_ret_t duk_bi_error_prototype_linenumber_getter(duk_hthread *thr) {
315 return duk__error_getter_helper(thr, DUK__OUTPUT_TYPE_LINENUMBER);
316 }
317
318 #else /* DUK_USE_TRACEBACKS */
319
320 /*
321 * Traceback handling when tracebacks disabled.
322 *
323 * The fileName / lineNumber stubs are now necessary because built-in
324 * data will include the accessor properties in Error.prototype. If those
325 * are removed for builds without tracebacks, these can also be removed.
326 * 'stack' should still be present and produce a ToString() equivalent:
327 * this is useful for user code which prints a stacktrace and expects to
328 * see something useful. A normal stacktrace also begins with a ToString()
329 * of the error so this makes sense.
330 */
331
duk_bi_error_prototype_stack_getter(duk_hthread * thr)332 DUK_INTERNAL duk_ret_t duk_bi_error_prototype_stack_getter(duk_hthread *thr) {
333 /* XXX: remove this native function and map 'stack' accessor
334 * to the toString() implementation directly.
335 */
336 return duk_bi_error_prototype_to_string(thr);
337 }
338
duk_bi_error_prototype_filename_getter(duk_hthread * thr)339 DUK_INTERNAL duk_ret_t duk_bi_error_prototype_filename_getter(duk_hthread *thr) {
340 DUK_UNREF(thr);
341 return 0;
342 }
343
duk_bi_error_prototype_linenumber_getter(duk_hthread * thr)344 DUK_INTERNAL duk_ret_t duk_bi_error_prototype_linenumber_getter(duk_hthread *thr) {
345 DUK_UNREF(thr);
346 return 0;
347 }
348
349 #endif /* DUK_USE_TRACEBACKS */
350
duk__error_setter_helper(duk_hthread * thr,duk_small_uint_t stridx_key)351 DUK_LOCAL duk_ret_t duk__error_setter_helper(duk_hthread *thr, duk_small_uint_t stridx_key) {
352 /* Attempt to write 'stack', 'fileName', 'lineNumber' works as if
353 * user code called Object.defineProperty() to create an overriding
354 * own property. This allows user code to overwrite .fileName etc
355 * intuitively as e.g. "err.fileName = 'dummy'" as one might expect.
356 * See https://github.com/svaarala/duktape/issues/387.
357 */
358
359 DUK_ASSERT_TOP(thr, 1); /* fixed arg count: value */
360
361 duk_push_this(thr);
362 duk_push_hstring_stridx(thr, stridx_key);
363 duk_dup_0(thr);
364
365 /* [ ... obj key value ] */
366
367 DUK_DD(DUK_DDPRINT("error setter: %!T %!T %!T",
368 duk_get_tval(thr, -3), duk_get_tval(thr, -2), duk_get_tval(thr, -1)));
369
370 duk_def_prop(thr, -3, DUK_DEFPROP_HAVE_VALUE |
371 DUK_DEFPROP_HAVE_WRITABLE | DUK_DEFPROP_WRITABLE |
372 DUK_DEFPROP_HAVE_ENUMERABLE | /*not enumerable*/
373 DUK_DEFPROP_HAVE_CONFIGURABLE | DUK_DEFPROP_CONFIGURABLE);
374 return 0;
375 }
376
duk_bi_error_prototype_stack_setter(duk_hthread * thr)377 DUK_INTERNAL duk_ret_t duk_bi_error_prototype_stack_setter(duk_hthread *thr) {
378 return duk__error_setter_helper(thr, DUK_STRIDX_STACK);
379 }
380
duk_bi_error_prototype_filename_setter(duk_hthread * thr)381 DUK_INTERNAL duk_ret_t duk_bi_error_prototype_filename_setter(duk_hthread *thr) {
382 return duk__error_setter_helper(thr, DUK_STRIDX_FILE_NAME);
383 }
384
duk_bi_error_prototype_linenumber_setter(duk_hthread * thr)385 DUK_INTERNAL duk_ret_t duk_bi_error_prototype_linenumber_setter(duk_hthread *thr) {
386 return duk__error_setter_helper(thr, DUK_STRIDX_LINE_NUMBER);
387 }
388