1 /*
2 * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26 package jdk.nashorn.internal.runtime;
27
28 import static jdk.nashorn.internal.codegen.CompilerConstants.SOURCE;
29 import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.INVALID_PROGRAM_POINT;
30
31 import java.lang.invoke.MethodHandle;
32 import java.lang.reflect.Field;
33 import java.net.URL;
34 import java.util.HashSet;
35 import java.util.Set;
36 import jdk.nashorn.internal.scripts.JS;
37
38 /**
39 * This class provides support for external debuggers. Its primary purpose is
40 * is to simplify the debugger tasks and provide better performance.
41 * Even though the methods are not public, there are still part of the
42 * external debugger interface.
43 */
44 final class DebuggerSupport {
45 /**
46 * Hook to force the loading of the DebuggerSupport class so that it is
47 * available to external debuggers.
48 */
49 static boolean FORCELOAD = true;
50
51 static {
52 /**
53 * Hook to force the loading of the DebuggerValueDesc class so that it is
54 * available to external debuggers.
55 */
56 @SuppressWarnings("unused")
57 final
58 DebuggerValueDesc forceLoad = new DebuggerValueDesc(null, false, null, null);
59
60 // Hook to force the loading of the SourceInfo class
61 @SuppressWarnings("unused")
62 final
63 SourceInfo srcInfo = new SourceInfo(null, 0, null, null);
64 }
65
66 /** This class is used to send a bulk description of a value. */
67 static class DebuggerValueDesc {
68 /** Property key (or index) or field name. */
69 final String key;
70
71 /** If the value is expandable. */
72 final boolean expandable;
73
74 /** Property or field value as object. */
75 final Object valueAsObject;
76
77 /** Property or field value as string. */
78 final String valueAsString;
79
80 DebuggerValueDesc(final String key, final boolean expandable, final Object valueAsObject, final String valueAsString) {
81 this.key = key;
82 this.expandable = expandable;
83 this.valueAsObject = valueAsObject;
84 this.valueAsString = valueAsString;
85 }
86 }
87
88 static class SourceInfo {
89 final String name;
90 final URL url;
91 final int hash;
92 final char[] content;
93
94 SourceInfo(final String name, final int hash, final URL url, final char[] content) {
95 this.name = name;
96 this.hash = hash;
97 this.url = url;
98 this.content = content;
99 }
100 }
101
102 /**
103 * Hook that is called just before invoking method handle
104 * from ScriptFunctionData via invoke, constructor method calls.
105 *
106 * @param mh script class method about to be invoked.
107 */
108 static void notifyInvoke(final MethodHandle mh) {
109 // Do nothing here. This is placeholder method on which a
110 // debugger can place a breakpoint so that it can access the
111 // (script class) method handle that is about to be invoked.
112 // See ScriptFunctionData.invoke and ScriptFunctionData.construct.
113 }
114
115 /**
116 * Return the script source info for the given script class.
117 *
118 * @param clazz compiled script class
119 * @return SourceInfo
120 */
121 static SourceInfo getSourceInfo(final Class<?> clazz) {
122 if (JS.class.isAssignableFrom(clazz)) {
123 try {
124 final Field sourceField = clazz.getDeclaredField(SOURCE.symbolName());
125 sourceField.setAccessible(true);
126 final Source src = (Source) sourceField.get(null);
127 return src.getSourceInfo();
128 } catch (final IllegalAccessException | NoSuchFieldException ignored) {
129 return null;
130 }
131 }
132
133 return null;
134 }
135
136 /**
137 * Return the current context global.
138 * @return context global.
139 */
140 static Object getGlobal() {
141 return Context.getGlobal();
142 }
143
144 /**
145 * Call eval on the current global.
146 * @param scope Scope to use.
147 * @param self Receiver to use.
148 * @param string String to evaluate.
149 * @param returnException true if exceptions are to be returned.
150 * @return Result of eval, or, an exception or null depending on returnException.
151 */
152 static Object eval(final ScriptObject scope, final Object self, final String string, final boolean returnException) {
153 final ScriptObject global = Context.getGlobal();
154 final ScriptObject initialScope = scope != null ? scope : global;
155 final Object callThis = self != null ? self : global;
156 final Context context = global.getContext();
157
158 try {
159 return context.eval(initialScope, string, callThis, ScriptRuntime.UNDEFINED);
160 } catch (final Throwable ex) {
161 return returnException ? ex : null;
162 }
163 }
164
165 /**
166 * This method returns a bulk description of an object's properties.
167 * @param object Script object to be displayed by the debugger.
168 * @param all true if to include non-enumerable values.
169 * @return An array of DebuggerValueDesc.
170 */
171 static DebuggerValueDesc[] valueInfos(final Object object, final boolean all) {
172 assert object instanceof ScriptObject;
173 return getDebuggerValueDescs((ScriptObject)object, all, new HashSet<>());
174 }
175
176 /**
177 * This method returns a debugger description of the value.
178 * @param name Name of value (property name).
179 * @param value Data value.
180 * @param all true if to include non-enumerable values.
181 * @return A DebuggerValueDesc.
182 */
183 static DebuggerValueDesc valueInfo(final String name, final Object value, final boolean all) {
184 return valueInfo(name, value, all, new HashSet<>());
185 }
186
187 /**
188 * This method returns a debugger description of the value.
189 * @param name Name of value (property name).
190 * @param value Data value.
191 * @param all true if to include non-enumerable values.
192 * @param duplicates Duplication set to avoid cycles.
193 * @return A DebuggerValueDesc.
194 */
195 private static DebuggerValueDesc valueInfo(final String name, final Object value, final boolean all, final Set<Object> duplicates) {
196 if (value instanceof ScriptObject && !(value instanceof ScriptFunction)) {
197 final ScriptObject object = (ScriptObject)value;
198 return new DebuggerValueDesc(name, !object.isEmpty(), value, objectAsString(object, all, duplicates));
199 }
200 return new DebuggerValueDesc(name, false, value, valueAsString(value));
201 }
202
203 /**
204 * Generate the descriptions for an object's properties.
205 * @param object Object to introspect.
206 * @param all true if to include non-enumerable values.
207 * @param duplicates Duplication set to avoid cycles.
208 * @return An array of DebuggerValueDesc.
209 */
210 private static DebuggerValueDesc[] getDebuggerValueDescs(final ScriptObject object, final boolean all, final Set<Object> duplicates) {
211 if (duplicates.contains(object)) {
212 return null;
213 }
214
215 duplicates.add(object);
216
217 final String[] keys = object.getOwnKeys(all);
218 final DebuggerValueDesc[] descs = new DebuggerValueDesc[keys.length];
219
220 for (int i = 0; i < keys.length; i++) {
221 final String key = keys[i];
222 descs[i] = valueInfo(key, object.get(key), all, duplicates);
223 }
224
225 duplicates.remove(object);
226
227 return descs;
228 }
229
230 /**
231 * Generate a string representation of a Script object.
232 * @param object Script object to represent.
233 * @param all true if to include non-enumerable values.
234 * @param duplicates Duplication set to avoid cycles.
235 * @return String representation.
236 */
237 private static String objectAsString(final ScriptObject object, final boolean all, final Set<Object> duplicates) {
238 final StringBuilder sb = new StringBuilder();
239
240 if (ScriptObject.isArray(object)) {
241 sb.append('[');
242 final long length = (long) object.getDouble("length", INVALID_PROGRAM_POINT);
243
244 for (long i = 0; i < length; i++) {
245 if (object.has(i)) {
246 final Object valueAsObject = object.get(i);
247 final boolean isUndefined = valueAsObject == ScriptRuntime.UNDEFINED;
248
249 if (isUndefined) {
250 if (i != 0) {
251 sb.append(",");
252 }
253 } else {
254 if (i != 0) {
255 sb.append(", ");
256 }
257
258 if (valueAsObject instanceof ScriptObject && !(valueAsObject instanceof ScriptFunction)) {
259 final String objectString = objectAsString((ScriptObject)valueAsObject, all, duplicates);
260 sb.append(objectString != null ? objectString : "{...}");
261 } else {
262 sb.append(valueAsString(valueAsObject));
263 }
264 }
265 } else {
266 if (i != 0) {
267 sb.append(',');
268 }
269 }
270 }
271
272 sb.append(']');
273 } else {
274 sb.append('{');
275 final DebuggerValueDesc[] descs = getDebuggerValueDescs(object, all, duplicates);
276
277 if (descs != null) {
278 for (int i = 0; i < descs.length; i++) {
279 if (i != 0) {
280 sb.append(", ");
281 }
282
283 final String valueAsString = descs[i].valueAsString;
284 sb.append(descs[i].key);
285 sb.append(": ");
286 sb.append(valueAsString);
287 }
288 }
289
290 sb.append('}');
291 }
292
293 return sb.toString();
294 }
295
296 /**
297 * This method returns a string representation of a value.
298 * @param value Arbitrary value to be displayed by the debugger.
299 * @return A string representation of the value or an array of DebuggerValueDesc.
300 */
301 static String valueAsString(final Object value) {
302 final JSType type = JSType.of(value);
303
304 switch (type) {
305 case BOOLEAN:
306 return value.toString();
307
308 case STRING:
309 return escape(value.toString());
310
311 case NUMBER:
312 return JSType.toString(((Number)value).doubleValue());
313
314 case NULL:
315 return "null";
316
317 case UNDEFINED:
318 return "undefined";
319
320 case OBJECT:
321 return ScriptRuntime.safeToString(value);
322
323 case FUNCTION:
324 if (value instanceof ScriptFunction) {
325 return ((ScriptFunction)value).toSource();
326 }
327 return value.toString();
328
329 default:
330 return value.toString();
331 }
332 }
333
334 /**
335 * Escape a string into a form that can be parsed by JavaScript.
336 * @param value String to be escaped.
337 * @return Escaped string.
338 */
339 private static String escape(final String value) {
340 final StringBuilder sb = new StringBuilder();
341
342 sb.append("\"");
343
344 for (final char ch : value.toCharArray()) {
345 switch (ch) {
346 case '\\':
347 sb.append("\\\\");
348 break;
349 case '"':
350 sb.append("\\\"");
351 break;
352 case '\'':
353 sb.append("\\\'");
354 break;
355 case '\b':
356 sb.append("\\b");
357 break;
358 case '\f':
359 sb.append("\\f");
360 break;
361 case '\n':
362 sb.append("\\n");
363 break;
364 case '\r':
365 sb.append("\\r");
366 break;
367 case '\t':
368 sb.append("\\t");
369 break;
370 default:
371 if (ch < ' ' || ch >= 0xFF) {
372 sb.append("\\u");
373
374 final String hex = Integer.toHexString(ch);
375 for (int i = hex.length(); i < 4; i++) {
376 sb.append('0');
377 }
378 sb.append(hex);
379 } else {
380 sb.append(ch);
381 }
382
383 break;
384 }
385 }
386
387 sb.append("\"");
388
389 return sb.toString();
390 }
391 }
392
393
--- EOF ---