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 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 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 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 java.util.HashSet; 29 import java.util.Set; 30 31 /** 32 * This class provides support for external debuggers. Its primary purpose is 33 * is to simplify the debugger tasks and provide better performance. 34 */ 35 final class DebuggerSupport { 36 /** 37 * Hook to force the loading of the DebuggerSupport class so that it is 38 * available to external debuggers. 39 */ 40 static boolean FORCELOAD = true; 41 42 static { 43 /** 44 * Hook to force the loading of the DebuggerValueDesc class so that it is 45 * available to external debuggers. 46 */ 47 @SuppressWarnings("unused") 48 DebuggerValueDesc forceLoad = new DebuggerValueDesc(null, false, null, null); 49 } 50 51 /** This class is used to send a bulk description of a value. */ 52 static class DebuggerValueDesc { 53 /** Property key (or index) or field name. */ 54 final String key; 55 56 /** If the value is expandable. */ 57 final boolean expandable; 58 59 /** Property or field value as object. */ 60 final Object valueAsObject; 61 62 /** Property or field value as string. */ 63 final String valueAsString; 64 65 DebuggerValueDesc(final String key, final boolean expandable, final Object valueAsObject, final String valueAsString) { 66 this.key = key; 67 this.expandable = expandable; 68 this.valueAsObject = valueAsObject; 69 this.valueAsString = valueAsString; 70 } 71 } 72 73 /** 74 * Return the current context global. 75 * @return context global. 76 */ 77 static Object getGlobal() { 78 return Context.getGlobalTrusted(); 79 } 80 81 /** 82 * Call eval on the current global. 83 * @param scope Scope to use. 84 * @param self Receiver to use. 85 * @param string String to evaluate. 86 * @param returnException true if exceptions are to be returned. 87 * @return Result of eval as string, or, an exception or null depending on returnException. 88 */ 89 static Object eval(final ScriptObject scope, final Object self, final String string, final boolean returnException) { 90 final ScriptObject global = Context.getGlobalTrusted(); 91 final ScriptObject initialScope = scope != null ? scope : global; 92 final Object callThis = self != null ? self : global; 93 final Context context = global.getContext(); 94 95 try { 96 return context.eval(initialScope, string, callThis, ScriptRuntime.UNDEFINED, false); 97 } catch (Throwable ex) { 98 return returnException ? ex : null; 99 } 100 } 101 102 /** 103 * This method returns a bulk description of an object's properties. 104 * @param object Script object to be displayed by the debugger. 105 * @param all true if to include non-enumerable values. 106 * @return An array of DebuggerValueDesc. 107 */ 108 static DebuggerValueDesc[] valueInfos(final Object object, final boolean all) { 109 assert object instanceof ScriptObject; 110 return getDebuggerValueDescs((ScriptObject)object, all, new HashSet<>()); 111 } 112 113 /** 114 * This method returns a debugger description of the value. 115 * @param name Name of value (property name). 116 * @param value Data value. 117 * @param all true if to include non-enumerable values. 118 * @return A DebuggerValueDesc. 119 */ 120 static DebuggerValueDesc valueInfo(final String name, final Object value, final boolean all) { 121 return valueInfo(name, value, all, new HashSet<>()); 122 } 123 124 /** 125 * This method returns a debugger description of the value. 126 * @param name Name of value (property name). 127 * @param value Data value. 128 * @param all true if to include non-enumerable values. 129 * @param duplicates Duplication set to avoid cycles. 130 * @return A DebuggerValueDesc. 131 */ 132 private static DebuggerValueDesc valueInfo(final String name, final Object value, final boolean all, final Set<Object> duplicates) { 133 if (value instanceof ScriptObject && !(value instanceof ScriptFunction)) { 134 final ScriptObject object = (ScriptObject)value; 135 return new DebuggerValueDesc(name, !object.isEmpty(), value, objectAsString(object, all, duplicates)); 136 } 137 return new DebuggerValueDesc(name, false, value, valueAsString(value)); 138 } 139 140 /** 141 * Generate the descriptions for an object's properties. 142 * @param object Object to introspect. 143 * @param all true if to include non-enumerable values. 144 * @param duplicates Duplication set to avoid cycles. 145 * @return An array of DebuggerValueDesc. 146 */ 147 private static DebuggerValueDesc[] getDebuggerValueDescs(final ScriptObject object, final boolean all, final Set<Object> duplicates) { 148 if (duplicates.contains(object)) { 149 return null; 150 } 151 152 duplicates.add(object); 153 154 final String[] keys = object.getOwnKeys(all); 155 final DebuggerValueDesc[] descs = new DebuggerValueDesc[keys.length]; 156 157 for (int i = 0; i < keys.length; i++) { 158 final String key = keys[i]; 159 descs[i] = valueInfo(key, object.get(key), all, duplicates); 160 } 161 162 duplicates.remove(object); 163 164 return descs; 165 } 166 167 /** 168 * Generate a string representation of a Script object. 169 * @param object Script object to represent. 170 * @param all true if to include non-enumerable values. 171 * @param duplicates Duplication set to avoid cycles. 172 * @return String representation. 173 */ 174 private static String objectAsString(final ScriptObject object, final boolean all, final Set<Object> duplicates) { 175 final StringBuilder sb = new StringBuilder(); 176 177 if (ScriptObject.isArray(object)) { 178 sb.append('['); 179 final long length = object.getLong("length"); 180 181 for (long i = 0; i < length; i++) { 182 if (object.has(i)) { 183 final Object valueAsObject = object.get(i); 184 final boolean isUndefined = JSType.of(valueAsObject) == JSType.UNDEFINED; 185 186 if (isUndefined) { 187 if (i != 0) { 188 sb.append(","); 189 } 190 } else { 191 if (i != 0) { 192 sb.append(", "); 193 } 194 195 if (valueAsObject instanceof ScriptObject && !(valueAsObject instanceof ScriptFunction)) { 196 final String objectString = objectAsString((ScriptObject)valueAsObject, all, duplicates); 197 sb.append(objectString != null ? objectString : "{...}"); 198 } else { 199 sb.append(valueAsString(valueAsObject)); 200 } 201 } 202 } else { 203 if (i != 0) { 204 sb.append(','); 205 } 206 } 207 } 208 209 sb.append(']'); 210 } else { 211 sb.append('{'); 212 final DebuggerValueDesc[] descs = getDebuggerValueDescs(object, all, duplicates); 213 214 if (descs != null) { 215 for (int i = 0; i < descs.length; i++) { 216 if (i != 0) { 217 sb.append(", "); 218 } 219 220 final String valueAsString = descs[i].valueAsString; 221 sb.append(descs[i].key); 222 sb.append(": "); 223 sb.append(valueAsString); 224 } 225 } 226 227 sb.append('}'); 228 } 229 230 return sb.toString(); 231 } 232 233 /** 234 * This method returns a string representation of a value. 235 * @param value Arbitrary value to be displayed by the debugger. 236 * @return A string representation of the value or an array of DebuggerValueDesc. 237 */ 238 private static String valueAsString(final Object value) { 239 final JSType type = JSType.of(value); 240 241 switch (type) { 242 case BOOLEAN: 243 return value.toString(); 244 245 case STRING: 246 return escape(value.toString()); 247 248 case NUMBER: 249 return JSType.toString(((Number)value).doubleValue()); 250 251 case NULL: 252 return "null"; 253 254 case UNDEFINED: 255 return "undefined"; 256 257 case OBJECT: 258 return ScriptRuntime.safeToString(value); 259 260 case FUNCTION: 261 if (value instanceof ScriptFunction) { 262 return ((ScriptFunction)value).toSource(); 263 } 264 return value.toString(); 265 266 default: 267 return value.toString(); 268 } 269 } 270 271 /** 272 * Escape a string into a form that can be parsed by JavaScript. 273 * @param value String to be escaped. 274 * @return Escaped string. 275 */ 276 private static String escape(final String value) { 277 final StringBuilder sb = new StringBuilder(); 278 279 sb.append("\""); 280 281 for (final char ch : value.toCharArray()) { 282 switch (ch) { 283 case '\\': 284 sb.append("\\\\"); 285 break; 286 case '"': 287 sb.append("\\\""); 288 break; 289 case '\'': 290 sb.append("\\\'"); 291 break; 292 case '\b': 293 sb.append("\\b"); 294 break; 295 case '\f': 296 sb.append("\\f"); 297 break; 298 case '\n': 299 sb.append("\\n"); 300 break; 301 case '\r': 302 sb.append("\\r"); 303 break; 304 case '\t': 305 sb.append("\\t"); 306 break; 307 default: 308 if (ch < ' ' || ch >= 0xFF) { 309 sb.append("\\u"); 310 311 final String hex = Integer.toHexString(ch); 312 for (int i = hex.length(); i < 4; i++) { 313 sb.append('0'); 314 } 315 sb.append(hex); 316 } else { 317 sb.append(ch); 318 } 319 320 break; 321 } 322 } 323 324 sb.append("\""); 325 326 return sb.toString(); 327 } 328 } 329 330