forked from mozilla/rhino
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathNativeError.java
More file actions
420 lines (363 loc) · 15 KB
/
Copy pathNativeError.java
File metadata and controls
420 lines (363 loc) · 15 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
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.javascript;
import java.io.Serializable;
import java.lang.reflect.Method;
/**
*
* The class of error objects
*
* ECMA 15.11
*/
final class NativeError extends IdScriptableObject
{
private static final long serialVersionUID = -5338413581437645187L;
private static final Object ERROR_TAG = "Error";
private static final Method ERROR_DELEGATE_GET_STACK;
private static final Method ERROR_DELEGATE_SET_STACK;
static {
try {
// Pre-cache methods to be called via reflection
ERROR_DELEGATE_GET_STACK = NativeError.class.getMethod("getStackDelegated", Scriptable.class);
ERROR_DELEGATE_SET_STACK = NativeError.class.getMethod("setStackDelegated", Scriptable.class, Object.class);
} catch (NoSuchMethodException nsm) {
throw new RuntimeException(nsm);
}
}
/** Default stack limit is set to "Infinity", here represented as a negative int */
public static final int DEFAULT_STACK_LIMIT = -1;
// This is used by "captureStackTrace"
private static final String STACK_HIDE_KEY = "_stackHide";
private RhinoException stackProvider;
static void init(Scriptable scope, boolean sealed)
{
NativeError obj = new NativeError();
ScriptableObject.putProperty(obj, "name", "Error");
ScriptableObject.putProperty(obj, "message", "");
ScriptableObject.putProperty(obj, "fileName", "");
ScriptableObject.putProperty(obj, "lineNumber", Integer.valueOf(0));
obj.setAttributes("name", ScriptableObject.DONTENUM);
obj.setAttributes("message", ScriptableObject.DONTENUM);
obj.exportAsJSClass(MAX_PROTOTYPE_ID, scope, sealed);
NativeCallSite.init(obj, sealed);
}
static NativeError make(Context cx, Scriptable scope,
IdFunctionObject ctorObj, Object[] args)
{
Scriptable proto = (Scriptable)(ctorObj.get("prototype", ctorObj));
NativeError obj = new NativeError();
obj.setPrototype(proto);
obj.setParentScope(scope);
int arglen = args.length;
if (arglen >= 1) {
if (args[0] != Undefined.instance) {
ScriptableObject.putProperty(obj, "message",
ScriptRuntime.toString(args[0]));
}
if (arglen >= 2) {
ScriptableObject.putProperty(obj, "fileName", args[1]);
if (arglen >= 3) {
int line = ScriptRuntime.toInt32(args[2]);
ScriptableObject.putProperty(obj, "lineNumber",
Integer.valueOf(line));
}
}
}
return obj;
}
@Override
protected void fillConstructorProperties(IdFunctionObject ctor)
{
addIdFunctionProperty(ctor, ERROR_TAG, ConstructorId_captureStackTrace,
"captureStackTrace", 2);
// This is running on the global "Error" object. Associate an object there that can store
// default stack trace, etc.
// This prevents us from having to add two additional fields to every Error object.
ProtoProps protoProps = new ProtoProps();
associateValue(ProtoProps.KEY, protoProps);
// Define constructor properties that delegate to the ProtoProps object.
ctor.defineProperty("stackTraceLimit", protoProps,
ProtoProps.GET_STACK_LIMIT, ProtoProps.SET_STACK_LIMIT, 0);
ctor.defineProperty("prepareStackTrace", protoProps,
ProtoProps.GET_PREPARE_STACK, ProtoProps.SET_PREPARE_STACK, 0);
super.fillConstructorProperties(ctor);
}
@Override
public String getClassName()
{
return "Error";
}
@Override
public String toString()
{
// According to spec, Error.prototype.toString() may return undefined.
Object toString = js_toString(this);
return toString instanceof String ? (String) toString : super.toString();
}
@Override
protected void initPrototypeId(int id)
{
String s;
int arity;
switch (id) {
case Id_constructor: arity=1; s="constructor"; break;
case Id_toString: arity=0; s="toString"; break;
case Id_toSource: arity=0; s="toSource"; break;
default: throw new IllegalArgumentException(String.valueOf(id));
}
initPrototypeMethod(ERROR_TAG, id, s, arity);
}
@Override
public Object execIdCall(IdFunctionObject f, Context cx, Scriptable scope,
Scriptable thisObj, Object[] args)
{
if (!f.hasTag(ERROR_TAG)) {
return super.execIdCall(f, cx, scope, thisObj, args);
}
int id = f.methodId();
switch (id) {
case Id_constructor:
return make(cx, scope, f, args);
case Id_toString:
return js_toString(thisObj);
case Id_toSource:
return js_toSource(cx, scope, thisObj);
case ConstructorId_captureStackTrace:
js_captureStackTrace(cx, thisObj, args);
return Undefined.instance;
}
throw new IllegalArgumentException(String.valueOf(id));
}
public void setStackProvider(RhinoException re) {
// We go some extra miles to make sure the stack property is only
// generated on demand, is cached after the first access, and is
// overwritable like an ordinary property. Hence this setup with
// the getter and setter below.
if (stackProvider == null) {
stackProvider = re;
defineProperty("stack", this,
ERROR_DELEGATE_GET_STACK, ERROR_DELEGATE_SET_STACK,
DONTENUM);
}
}
public Object getStackDelegated(Scriptable target) {
if (stackProvider == null) {
return NOT_FOUND;
}
// Get the object where prototype stuff is stored.
int limit = DEFAULT_STACK_LIMIT;
Function prepare = null;
NativeError cons = (NativeError)getPrototype();
ProtoProps pp = (ProtoProps)cons.getAssociatedValue(ProtoProps.KEY);
if (pp != null) {
limit = pp.getStackTraceLimit();
prepare = pp.getPrepareStackTrace();
}
// This key is only set by captureStackTrace
String hideFunc = (String)getAssociatedValue(STACK_HIDE_KEY);
ScriptStackElement[] stack = stackProvider.getScriptStack(limit, hideFunc);
// Determine whether to format the stack trace ourselves, or call the user's code to do it
Object value;
if (prepare == null) {
value = RhinoException.formatStackTrace(stack, stackProvider.details());
} else {
value = callPrepareStack(prepare, stack);
}
// We store the stack as local property both to cache it
// and to make the property writable
setStackDelegated(target, value);
return value;
}
public void setStackDelegated(Scriptable target, Object value) {
target.delete("stack");
stackProvider = null;
target.put("stack", target, value);
}
private Object callPrepareStack(Function prepare, ScriptStackElement[] stack)
{
Context cx = Context.getCurrentContext();
Object[] elts = new Object[stack.length];
// The "prepareStackTrace" function takes an array of CallSite objects.
for (int i = 0; i < stack.length; i++) {
NativeCallSite site = (NativeCallSite)cx.newObject(this, "CallSite");
site.setElement(stack[i]);
elts[i] = site;
}
Scriptable eltArray = cx.newArray(this, elts);
return prepare.call(cx, prepare, this, new Object[] { this, eltArray });
}
private static Object js_toString(Scriptable thisObj) {
Object name = ScriptableObject.getProperty(thisObj, "name");
if (name == NOT_FOUND || name == Undefined.instance) {
name = "Error";
} else {
name = ScriptRuntime.toString(name);
}
Object msg = ScriptableObject.getProperty(thisObj, "message");
if (msg == NOT_FOUND || msg == Undefined.instance) {
msg = "";
} else {
msg = ScriptRuntime.toString(msg);
}
if (name.toString().length() == 0) {
return msg;
} else if (msg.toString().length() == 0) {
return name;
} else {
return ((String) name) + ": " + ((String) msg);
}
}
private static String js_toSource(Context cx, Scriptable scope,
Scriptable thisObj)
{
// Emulation of SpiderMonkey behavior
Object name = ScriptableObject.getProperty(thisObj, "name");
Object message = ScriptableObject.getProperty(thisObj, "message");
Object fileName = ScriptableObject.getProperty(thisObj, "fileName");
Object lineNumber = ScriptableObject.getProperty(thisObj, "lineNumber");
StringBuilder sb = new StringBuilder();
sb.append("(new ");
if (name == NOT_FOUND) {
name = Undefined.instance;
}
sb.append(ScriptRuntime.toString(name));
sb.append("(");
if (message != NOT_FOUND
|| fileName != NOT_FOUND
|| lineNumber != NOT_FOUND)
{
if (message == NOT_FOUND) {
message = "";
}
sb.append(ScriptRuntime.uneval(cx, scope, message));
if (fileName != NOT_FOUND || lineNumber != NOT_FOUND) {
sb.append(", ");
if (fileName == NOT_FOUND) {
fileName = "";
}
sb.append(ScriptRuntime.uneval(cx, scope, fileName));
if (lineNumber != NOT_FOUND) {
int line = ScriptRuntime.toInt32(lineNumber);
if (line != 0) {
sb.append(", ");
sb.append(ScriptRuntime.toString(line));
}
}
}
}
sb.append("))");
return sb.toString();
}
private static void js_captureStackTrace(Context cx, Scriptable thisObj, Object[] args)
{
ScriptableObject obj = (ScriptableObject)ScriptRuntime.toObjectOrNull(cx, args[0], thisObj);
Function func = null;
if (args.length > 1) {
func = (Function)ScriptRuntime.toObjectOrNull(cx, args[1], thisObj);
}
// Create a new error that will have the correct prototype so we can re-use "getStackTrace"
NativeError err = (NativeError)cx.newObject(thisObj, "Error");
// Wire it up so that it will have an actual exception with a stack trace
err.setStackProvider(new EvaluatorException("[object Object]"));
// Figure out if they passed a function used to hide part of the stack
if (func != null) {
Object funcName = func.get("name", func);
if ((funcName != null) && !Undefined.instance.equals(funcName)) {
err.associateValue(STACK_HIDE_KEY, Context.toString(funcName));
}
}
// Define a property on the specified object to get that stack
// that delegates to our new error. Build the stack trace lazily
// using the "getStack" code from NativeError.
obj.defineProperty("stack", err,
ERROR_DELEGATE_GET_STACK, ERROR_DELEGATE_SET_STACK, 0);
}
@Override
protected int findPrototypeId(String s)
{
int id;
// #string_id_map#
// #generated# Last update: 2007-05-09 08:15:45 EDT
L0: { id = 0; String X = null; int c;
int s_length = s.length();
if (s_length==8) {
c=s.charAt(3);
if (c=='o') { X="toSource";id=Id_toSource; }
else if (c=='t') { X="toString";id=Id_toString; }
}
else if (s_length==11) { X="constructor";id=Id_constructor; }
if (X!=null && X!=s && !X.equals(s)) id = 0;
break L0;
}
// #/generated#
return id;
}
private static final int
Id_constructor = 1,
Id_toString = 2,
Id_toSource = 3,
ConstructorId_captureStackTrace = -1,
MAX_PROTOTYPE_ID = 3;
// #/string_id_map#
/**
* We will attch this object to the constructor and use it solely to store the constructor properties
* that are "global." We can't make them static because there can be many contexts in the same JVM.
*/
private static final class ProtoProps
implements Serializable
{
static final String KEY = "_ErrorPrototypeProps";
static final Method GET_STACK_LIMIT;
static final Method SET_STACK_LIMIT;
static final Method GET_PREPARE_STACK;
static final Method SET_PREPARE_STACK;
static {
try {
GET_STACK_LIMIT = ProtoProps.class.getMethod("getStackTraceLimit", Scriptable.class);
SET_STACK_LIMIT = ProtoProps.class.getMethod("setStackTraceLimit", Scriptable.class, Object.class);
GET_PREPARE_STACK = ProtoProps.class.getMethod("getPrepareStackTrace", Scriptable.class);
SET_PREPARE_STACK = ProtoProps.class.getMethod("setPrepareStackTrace", Scriptable.class, Object.class);
} catch (NoSuchMethodException nsm) {
throw new RuntimeException(nsm);
}
}
private static final long serialVersionUID = 1907180507775337939L;
private int stackTraceLimit = DEFAULT_STACK_LIMIT;
private Function prepareStackTrace;
public Object getStackTraceLimit(Scriptable thisObj) {
if (stackTraceLimit >= 0) {
return stackTraceLimit;
}
return Double.POSITIVE_INFINITY;
}
public int getStackTraceLimit() {
return stackTraceLimit;
}
public void setStackTraceLimit(Scriptable thisObj, Object value) {
double limit = Context.toNumber(value);
if (Double.isNaN(limit) || Double.isInfinite(limit)) {
stackTraceLimit = -1;
} else {
stackTraceLimit = (int)limit;
}
}
public Object getPrepareStackTrace(Scriptable thisObj)
{
Object ps = getPrepareStackTrace();
return (ps == null ? Undefined.instance : ps);
}
public Function getPrepareStackTrace() {
return prepareStackTrace;
}
public void setPrepareStackTrace(Scriptable thisObj, Object value) {
if ((value == null) || Undefined.instance.equals(value)) {
prepareStackTrace = null;
} else if (value instanceof Function) {
prepareStackTrace = (Function)value;
}
}
}
}