src/jdk/nashorn/internal/runtime/DebuggerSupport.java

Thu, 24 May 2018 16:39:31 +0800

author
aoqi
date
Thu, 24 May 2018 16:39:31 +0800
changeset 1959
61ffdd1b89f2
parent 1720
c09b105e7be5
parent 1490
d85f981c8cf8
permissions
-rw-r--r--

Merge

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

mercurial