1 /* 2 * Copyright (c) 2012, 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. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 25 package org.graalvm.compiler.debug; 26 27 import java.io.PrintStream; 28 import java.util.Iterator; 29 30 import org.graalvm.compiler.debug.DebugContext.DisabledScope; 31 32 import jdk.vm.ci.meta.JavaMethod; 33 34 public final class ScopeImpl implements DebugContext.Scope { 35 36 private final class IndentImpl implements Indent { 37 38 private static final String INDENTATION_INCREMENT = " "; 39 40 final String indent; 41 final IndentImpl parentIndent; 42 isEmitted()43 boolean isEmitted() { 44 return emitted; 45 } 46 47 private boolean emitted; 48 IndentImpl(IndentImpl parentIndent)49 IndentImpl(IndentImpl parentIndent) { 50 this.parentIndent = parentIndent; 51 this.indent = (parentIndent == null ? "" : parentIndent.indent + INDENTATION_INCREMENT); 52 } 53 printScopeName(StringBuilder str, boolean isCurrent)54 private void printScopeName(StringBuilder str, boolean isCurrent) { 55 if (!emitted) { 56 boolean mustPrint = true; 57 if (parentIndent != null) { 58 if (!parentIndent.isEmitted()) { 59 parentIndent.printScopeName(str, false); 60 mustPrint = false; 61 } 62 } 63 /* 64 * Always print the current scope, scopes with context and any scope whose parent 65 * didn't print. This ensure the first new scope always shows up. 66 */ 67 if (isCurrent || printContext(null) != 0 || mustPrint) { 68 str.append(indent).append("[thread:").append(Thread.currentThread().getId()).append("] scope: ").append(getQualifiedName()).append(System.lineSeparator()); 69 } 70 printContext(str); 71 emitted = true; 72 } 73 } 74 75 /** 76 * Print or count the context objects for the current scope. 77 */ printContext(StringBuilder str)78 private int printContext(StringBuilder str) { 79 int count = 0; 80 if (context != null && context.length > 0) { 81 // Include some context in the scope output 82 for (Object contextObj : context) { 83 if (contextObj instanceof JavaMethodContext || contextObj instanceof JavaMethod) { 84 if (str != null) { 85 str.append(indent).append("Context: ").append(contextObj).append(System.lineSeparator()); 86 } 87 count++; 88 } 89 } 90 } 91 return count; 92 } 93 log(int logLevel, String msg, Object... args)94 public void log(int logLevel, String msg, Object... args) { 95 if (isLogEnabled(logLevel)) { 96 StringBuilder str = new StringBuilder(); 97 printScopeName(str, true); 98 str.append(indent); 99 String result = args.length == 0 ? msg : String.format(msg, args); 100 String lineSep = System.lineSeparator(); 101 str.append(result.replace(lineSep, lineSep.concat(indent))); 102 str.append(lineSep); 103 output.append(str); 104 lastUsedIndent = this; 105 } 106 } 107 indent()108 IndentImpl indent() { 109 lastUsedIndent = new IndentImpl(this); 110 return lastUsedIndent; 111 } 112 113 @Override close()114 public void close() { 115 if (parentIndent != null) { 116 lastUsedIndent = parentIndent; 117 } 118 } 119 } 120 121 private final DebugContext owner; 122 private final ScopeImpl parent; 123 private final boolean sandbox; 124 private IndentImpl lastUsedIndent; 125 isEmptyScope()126 private boolean isEmptyScope() { 127 return emptyScope; 128 } 129 130 private final boolean emptyScope; 131 132 private final Object[] context; 133 134 private String qualifiedName; 135 private final String unqualifiedName; 136 137 private static final char SCOPE_SEP = '.'; 138 139 private boolean countEnabled; 140 private boolean timeEnabled; 141 private boolean memUseTrackingEnabled; 142 private boolean verifyEnabled; 143 144 private int currentDumpLevel; 145 private int currentLogLevel; 146 147 private PrintStream output; 148 private boolean interceptDisabled; 149 ScopeImpl(DebugContext owner, Thread thread)150 ScopeImpl(DebugContext owner, Thread thread) { 151 this(owner, thread.getName(), null, false); 152 } 153 ScopeImpl(DebugContext owner, String unqualifiedName, ScopeImpl parent, boolean sandbox, Object... context)154 private ScopeImpl(DebugContext owner, String unqualifiedName, ScopeImpl parent, boolean sandbox, Object... context) { 155 this.owner = owner; 156 this.parent = parent; 157 this.sandbox = sandbox; 158 this.context = context; 159 this.unqualifiedName = unqualifiedName; 160 if (parent != null) { 161 emptyScope = unqualifiedName.equals(""); 162 this.interceptDisabled = parent.interceptDisabled; 163 } else { 164 if (unqualifiedName.isEmpty()) { 165 throw new IllegalArgumentException("root scope name must be non-empty"); 166 } 167 emptyScope = false; 168 } 169 170 this.output = TTY.out; 171 assert context != null; 172 } 173 174 @Override close()175 public void close() { 176 owner.currentScope = parent; 177 owner.lastClosedScope = this; 178 } 179 isTopLevel()180 boolean isTopLevel() { 181 return parent == null; 182 } 183 isDumpEnabled(int dumpLevel)184 boolean isDumpEnabled(int dumpLevel) { 185 assert dumpLevel >= 0; 186 return currentDumpLevel >= dumpLevel; 187 } 188 isVerifyEnabled()189 boolean isVerifyEnabled() { 190 return verifyEnabled; 191 } 192 isLogEnabled(int logLevel)193 boolean isLogEnabled(int logLevel) { 194 assert logLevel > 0; 195 return currentLogLevel >= logLevel; 196 } 197 isCountEnabled()198 boolean isCountEnabled() { 199 return countEnabled; 200 } 201 isTimeEnabled()202 boolean isTimeEnabled() { 203 return timeEnabled; 204 } 205 isMemUseTrackingEnabled()206 boolean isMemUseTrackingEnabled() { 207 return memUseTrackingEnabled; 208 } 209 log(int logLevel, String msg, Object... args)210 public void log(int logLevel, String msg, Object... args) { 211 assert owner.checkNoConcurrentAccess(); 212 if (isLogEnabled(logLevel)) { 213 getLastUsedIndent().log(logLevel, msg, args); 214 } 215 } 216 dump(int dumpLevel, Object object, String formatString, Object... args)217 public void dump(int dumpLevel, Object object, String formatString, Object... args) { 218 assert isDumpEnabled(dumpLevel); 219 if (isDumpEnabled(dumpLevel)) { 220 DebugConfig config = getConfig(); 221 if (config != null) { 222 for (DebugDumpHandler dumpHandler : config.dumpHandlers()) { 223 dumpHandler.dump(owner, object, formatString, args); 224 } 225 } 226 } 227 } 228 getConfig()229 private DebugConfig getConfig() { 230 return owner.currentConfig; 231 } 232 233 /** 234 * @see DebugContext#verify(Object, String) 235 */ verify(Object object, String formatString, Object... args)236 public void verify(Object object, String formatString, Object... args) { 237 if (isVerifyEnabled()) { 238 DebugConfig config = getConfig(); 239 if (config != null) { 240 String message = String.format(formatString, args); 241 for (DebugVerifyHandler handler : config.verifyHandlers()) { 242 handler.verify(owner, object, message); 243 } 244 } 245 } 246 } 247 248 /** 249 * Creates and enters a new scope which is either a child of the current scope or a disjoint top 250 * level scope. 251 * 252 * @param name the name of the new scope 253 * @param sandboxConfig the configuration to use for a new top level scope, or null if the new 254 * scope should be a child scope 255 * @param newContextObjects objects to be appended to the debug context 256 * @return the new scope which will be exited when its {@link #close()} method is called 257 */ scope(CharSequence name, DebugConfig sandboxConfig, Object... newContextObjects)258 public ScopeImpl scope(CharSequence name, DebugConfig sandboxConfig, Object... newContextObjects) { 259 ScopeImpl newScope = null; 260 if (sandboxConfig != null) { 261 newScope = new ScopeImpl(owner, name.toString(), this, true, newContextObjects); 262 } else { 263 newScope = this.createChild(name.toString(), newContextObjects); 264 } 265 newScope.updateFlags(owner.currentConfig); 266 return newScope; 267 } 268 269 @SuppressWarnings({"unchecked", "unused"}) silenceException(Class<E> type, Throwable ex)270 private static <E extends Exception> RuntimeException silenceException(Class<E> type, Throwable ex) throws E { 271 throw (E) ex; 272 } 273 handle(Throwable e)274 public RuntimeException handle(Throwable e) { 275 try { 276 if (owner.lastClosedScope instanceof ScopeImpl) { 277 ScopeImpl lastClosed = (ScopeImpl) owner.lastClosedScope; 278 assert lastClosed.parent == this : "DebugContext.handle() used without closing a scope opened by DebugContext.scope(...) or DebugContext.sandbox(...) " + 279 "or an exception occurred while opening a scope"; 280 if (e != owner.lastExceptionThrown) { 281 RuntimeException newException = null; 282 // Make the scope in which the exception was thrown 283 // the current scope again. 284 owner.currentScope = lastClosed; 285 286 // When this try block exits, the above action will be undone 287 try (ScopeImpl s = lastClosed) { 288 newException = s.interceptException(e); 289 } 290 291 // Checks that the action really is undone 292 assert owner.currentScope == this; 293 assert lastClosed == owner.lastClosedScope; 294 295 if (newException == null) { 296 owner.lastExceptionThrown = e; 297 } else { 298 owner.lastExceptionThrown = newException; 299 throw newException; 300 } 301 } 302 } else if (owner.lastClosedScope == null) { 303 throw new AssertionError("DebugContext.handle() used without closing a scope opened by DebugContext.scope(...) or DebugContext.sandbox(...) " + 304 "or an exception occurred while opening a scope"); 305 } else { 306 assert owner.lastClosedScope instanceof DisabledScope : owner.lastClosedScope; 307 } 308 } catch (Throwable t) { 309 t.initCause(e); 310 throw t; 311 } 312 313 if (e instanceof Error) { 314 throw (Error) e; 315 } 316 if (e instanceof RuntimeException) { 317 throw (RuntimeException) e; 318 } 319 throw silenceException(RuntimeException.class, e); 320 } 321 updateFlags(DebugConfigImpl config)322 void updateFlags(DebugConfigImpl config) { 323 if (config == null) { 324 countEnabled = false; 325 memUseTrackingEnabled = false; 326 timeEnabled = false; 327 verifyEnabled = false; 328 currentDumpLevel = -1; 329 // Be pragmatic: provide a default log stream to prevent a crash if the stream is not 330 // set while logging 331 output = TTY.out; 332 } else if (isEmptyScope()) { 333 countEnabled = parent.countEnabled; 334 memUseTrackingEnabled = parent.memUseTrackingEnabled; 335 timeEnabled = parent.timeEnabled; 336 verifyEnabled = parent.verifyEnabled; 337 output = parent.output; 338 currentDumpLevel = parent.currentDumpLevel; 339 currentLogLevel = parent.currentLogLevel; 340 } else { 341 countEnabled = config.isCountEnabled(this); 342 memUseTrackingEnabled = config.isMemUseTrackingEnabled(this); 343 timeEnabled = config.isTimeEnabled(this); 344 verifyEnabled = config.isVerifyEnabled(this); 345 output = config.output(); 346 currentDumpLevel = config.getDumpLevel(this); 347 currentLogLevel = config.getLogLevel(this); 348 } 349 } 350 disableIntercept()351 DebugCloseable disableIntercept() { 352 boolean previous = interceptDisabled; 353 interceptDisabled = true; 354 return new DebugCloseable() { 355 @Override 356 public void close() { 357 interceptDisabled = previous; 358 } 359 }; 360 } 361 362 @SuppressWarnings("try") 363 private RuntimeException interceptException(final Throwable e) { 364 if (!interceptDisabled && owner.currentConfig != null) { 365 try (ScopeImpl s = scope("InterceptException", null, e)) { 366 return owner.currentConfig.interceptException(owner, e); 367 } catch (Throwable t) { 368 return new RuntimeException("Exception while intercepting exception", t); 369 } 370 } 371 return null; 372 } 373 374 private ScopeImpl createChild(String newName, Object[] newContext) { 375 return new ScopeImpl(owner, newName, this, false, newContext); 376 } 377 378 @Override 379 public Iterable<Object> getCurrentContext() { 380 final ScopeImpl scope = this; 381 return new Iterable<Object>() { 382 383 @Override 384 public Iterator<Object> iterator() { 385 return new Iterator<Object>() { 386 387 ScopeImpl currentScope = scope; 388 int objectIndex; 389 390 @Override 391 public boolean hasNext() { 392 selectScope(); 393 return currentScope != null; 394 } 395 396 private void selectScope() { 397 while (currentScope != null && currentScope.context.length <= objectIndex) { 398 currentScope = currentScope.sandbox ? null : currentScope.parent; 399 objectIndex = 0; 400 } 401 } 402 403 @Override 404 public Object next() { 405 selectScope(); 406 if (currentScope != null) { 407 return currentScope.context[objectIndex++]; 408 } 409 throw new IllegalStateException("May only be called if there is a next element."); 410 } 411 412 @Override 413 public void remove() { 414 throw new UnsupportedOperationException("This iterator is read only."); 415 } 416 }; 417 } 418 }; 419 } 420 421 @Override 422 public String getQualifiedName() { 423 if (qualifiedName == null) { 424 if (parent == null) { 425 qualifiedName = unqualifiedName; 426 } else { 427 qualifiedName = parent.getQualifiedName(); 428 if (!isEmptyScope()) { 429 qualifiedName += SCOPE_SEP + unqualifiedName; 430 } 431 } 432 } 433 return qualifiedName; 434 } 435 436 Indent pushIndentLogger() { 437 lastUsedIndent = getLastUsedIndent().indent(); 438 return lastUsedIndent; 439 } 440 441 private IndentImpl getLastUsedIndent() { 442 if (lastUsedIndent == null) { 443 if (parent != null) { 444 lastUsedIndent = new IndentImpl(parent.getLastUsedIndent()); 445 } else { 446 lastUsedIndent = new IndentImpl(null); 447 } 448 } 449 return lastUsedIndent; 450 } 451 } 452