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