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