blob: f2aae5af1ae5f9f23fde011759d266591c5f9670 [file] [log] [blame]
/*
* Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package org.graalvm.compiler.debug;
import java.io.PrintStream;
import java.util.Iterator;
import org.graalvm.compiler.debug.DebugContext.DisabledScope;
import jdk.vm.ci.meta.JavaMethod;
public final class ScopeImpl implements DebugContext.Scope {
private final class IndentImpl implements Indent {
private static final String INDENTATION_INCREMENT = " ";
final String indent;
final IndentImpl parentIndent;
boolean isEmitted() {
return emitted;
}
private boolean emitted;
IndentImpl(IndentImpl parentIndent) {
this.parentIndent = parentIndent;
this.indent = (parentIndent == null ? "" : parentIndent.indent + INDENTATION_INCREMENT);
}
private void printScopeName(StringBuilder str, boolean isCurrent) {
if (!emitted) {
boolean mustPrint = true;
if (parentIndent != null) {
if (!parentIndent.isEmitted()) {
parentIndent.printScopeName(str, false);
mustPrint = false;
}
}
/*
* Always print the current scope, scopes with context and any scope whose parent
* didn't print. This ensure the first new scope always shows up.
*/
if (isCurrent || printContext(null) != 0 || mustPrint) {
str.append(indent).append("[thread:").append(Thread.currentThread().getId()).append("] scope: ").append(getQualifiedName()).append(System.lineSeparator());
}
printContext(str);
emitted = true;
}
}
/**
* Print or count the context objects for the current scope.
*/
private int printContext(StringBuilder str) {
int count = 0;
if (context != null && context.length > 0) {
// Include some context in the scope output
for (Object contextObj : context) {
if (contextObj instanceof JavaMethodContext || contextObj instanceof JavaMethod) {
if (str != null) {
str.append(indent).append("Context: ").append(contextObj).append(System.lineSeparator());
}
count++;
}
}
}
return count;
}
public void log(int logLevel, String msg, Object... args) {
if (isLogEnabled(logLevel)) {
StringBuilder str = new StringBuilder();
printScopeName(str, true);
str.append(indent);
String result = args.length == 0 ? msg : String.format(msg, args);
String lineSep = System.lineSeparator();
str.append(result.replace(lineSep, lineSep.concat(indent)));
str.append(lineSep);
output.append(str);
lastUsedIndent = this;
}
}
IndentImpl indent() {
lastUsedIndent = new IndentImpl(this);
return lastUsedIndent;
}
@Override
public void close() {
if (parentIndent != null) {
lastUsedIndent = parentIndent;
}
}
}
private final DebugContext owner;
private final ScopeImpl parent;
private final boolean sandbox;
private IndentImpl lastUsedIndent;
private boolean isEmptyScope() {
return emptyScope;
}
private final boolean emptyScope;
private final Object[] context;
private String qualifiedName;
private final String unqualifiedName;
private static final char SCOPE_SEP = '.';
private boolean countEnabled;
private boolean timeEnabled;
private boolean memUseTrackingEnabled;
private boolean verifyEnabled;
private int currentDumpLevel;
private int currentLogLevel;
private PrintStream output;
private boolean interceptDisabled;
ScopeImpl(DebugContext owner, Thread thread) {
this(owner, thread.getName(), null, false);
}
private ScopeImpl(DebugContext owner, String unqualifiedName, ScopeImpl parent, boolean sandbox, Object... context) {
this.owner = owner;
this.parent = parent;
this.sandbox = sandbox;
this.context = context;
this.unqualifiedName = unqualifiedName;
if (parent != null) {
emptyScope = unqualifiedName.equals("");
this.interceptDisabled = parent.interceptDisabled;
} else {
if (unqualifiedName.isEmpty()) {
throw new IllegalArgumentException("root scope name must be non-empty");
}
emptyScope = false;
}
this.output = TTY.out;
assert context != null;
}
@Override
public void close() {
owner.currentScope = parent;
owner.lastClosedScope = this;
}
boolean isTopLevel() {
return parent == null;
}
boolean isDumpEnabled(int dumpLevel) {
assert dumpLevel >= 0;
return currentDumpLevel >= dumpLevel;
}
boolean isVerifyEnabled() {
return verifyEnabled;
}
boolean isLogEnabled(int logLevel) {
assert logLevel > 0;
return currentLogLevel >= logLevel;
}
boolean isCountEnabled() {
return countEnabled;
}
boolean isTimeEnabled() {
return timeEnabled;
}
boolean isMemUseTrackingEnabled() {
return memUseTrackingEnabled;
}
public void log(int logLevel, String msg, Object... args) {
assert owner.checkNoConcurrentAccess();
if (isLogEnabled(logLevel)) {
getLastUsedIndent().log(logLevel, msg, args);
}
}
public void dump(int dumpLevel, Object object, String formatString, Object... args) {
assert isDumpEnabled(dumpLevel);
if (isDumpEnabled(dumpLevel)) {
DebugConfig config = getConfig();
if (config != null) {
for (DebugDumpHandler dumpHandler : config.dumpHandlers()) {
dumpHandler.dump(owner, object, formatString, args);
}
}
}
}
private DebugConfig getConfig() {
return owner.currentConfig;
}
/**
* @see DebugContext#verify(Object, String)
*/
public void verify(Object object, String formatString, Object... args) {
if (isVerifyEnabled()) {
DebugConfig config = getConfig();
if (config != null) {
String message = String.format(formatString, args);
for (DebugVerifyHandler handler : config.verifyHandlers()) {
handler.verify(owner, object, message);
}
}
}
}
/**
* Creates and enters a new scope which is either a child of the current scope or a disjoint top
* level scope.
*
* @param name the name of the new scope
* @param sandboxConfig the configuration to use for a new top level scope, or null if the new
* scope should be a child scope
* @param newContextObjects objects to be appended to the debug context
* @return the new scope which will be exited when its {@link #close()} method is called
*/
public ScopeImpl scope(CharSequence name, DebugConfig sandboxConfig, Object... newContextObjects) {
ScopeImpl newScope = null;
if (sandboxConfig != null) {
newScope = new ScopeImpl(owner, name.toString(), this, true, newContextObjects);
} else {
newScope = this.createChild(name.toString(), newContextObjects);
}
newScope.updateFlags(owner.currentConfig);
return newScope;
}
@SuppressWarnings({"unchecked", "unused"})
private static <E extends Exception> RuntimeException silenceException(Class<E> type, Throwable ex) throws E {
throw (E) ex;
}
public RuntimeException handle(Throwable e) {
try {
if (owner.lastClosedScope instanceof ScopeImpl) {
ScopeImpl lastClosed = (ScopeImpl) owner.lastClosedScope;
assert lastClosed.parent == this : "DebugContext.handle() used without closing a scope opened by DebugContext.scope(...) or DebugContext.sandbox(...) " +
"or an exception occurred while opening a scope";
if (e != owner.lastExceptionThrown) {
RuntimeException newException = null;
// Make the scope in which the exception was thrown
// the current scope again.
owner.currentScope = lastClosed;
// When this try block exits, the above action will be undone
try (ScopeImpl s = lastClosed) {
newException = s.interceptException(e);
}
// Checks that the action really is undone
assert owner.currentScope == this;
assert lastClosed == owner.lastClosedScope;
if (newException == null) {
owner.lastExceptionThrown = e;
} else {
owner.lastExceptionThrown = newException;
throw newException;
}
}
} else if (owner.lastClosedScope == null) {
throw new AssertionError("DebugContext.handle() used without closing a scope opened by DebugContext.scope(...) or DebugContext.sandbox(...) " +
"or an exception occurred while opening a scope");
} else {
assert owner.lastClosedScope instanceof DisabledScope : owner.lastClosedScope;
}
} catch (Throwable t) {
t.initCause(e);
throw t;
}
if (e instanceof Error) {
throw (Error) e;
}
if (e instanceof RuntimeException) {
throw (RuntimeException) e;
}
throw silenceException(RuntimeException.class, e);
}
void updateFlags(DebugConfigImpl config) {
if (config == null) {
countEnabled = false;
memUseTrackingEnabled = false;
timeEnabled = false;
verifyEnabled = false;
currentDumpLevel = -1;
// Be pragmatic: provide a default log stream to prevent a crash if the stream is not
// set while logging
output = TTY.out;
} else if (isEmptyScope()) {
countEnabled = parent.countEnabled;
memUseTrackingEnabled = parent.memUseTrackingEnabled;
timeEnabled = parent.timeEnabled;
verifyEnabled = parent.verifyEnabled;
output = parent.output;
currentDumpLevel = parent.currentDumpLevel;
currentLogLevel = parent.currentLogLevel;
} else {
countEnabled = config.isCountEnabled(this);
memUseTrackingEnabled = config.isMemUseTrackingEnabled(this);
timeEnabled = config.isTimeEnabled(this);
verifyEnabled = config.isVerifyEnabled(this);
output = config.output();
currentDumpLevel = config.getDumpLevel(this);
currentLogLevel = config.getLogLevel(this);
}
}
DebugCloseable disableIntercept() {
boolean previous = interceptDisabled;
interceptDisabled = true;
return new DebugCloseable() {
@Override
public void close() {
interceptDisabled = previous;
}
};
}
@SuppressWarnings("try")
private RuntimeException interceptException(final Throwable e) {
if (!interceptDisabled && owner.currentConfig != null) {
try (ScopeImpl s = scope("InterceptException", null, e)) {
return owner.currentConfig.interceptException(owner, e);
} catch (Throwable t) {
return new RuntimeException("Exception while intercepting exception", t);
}
}
return null;
}
private ScopeImpl createChild(String newName, Object[] newContext) {
return new ScopeImpl(owner, newName, this, false, newContext);
}
@Override
public Iterable<Object> getCurrentContext() {
final ScopeImpl scope = this;
return new Iterable<Object>() {
@Override
public Iterator<Object> iterator() {
return new Iterator<Object>() {
ScopeImpl currentScope = scope;
int objectIndex;
@Override
public boolean hasNext() {
selectScope();
return currentScope != null;
}
private void selectScope() {
while (currentScope != null && currentScope.context.length <= objectIndex) {
currentScope = currentScope.sandbox ? null : currentScope.parent;
objectIndex = 0;
}
}
@Override
public Object next() {
selectScope();
if (currentScope != null) {
return currentScope.context[objectIndex++];
}
throw new IllegalStateException("May only be called if there is a next element.");
}
@Override
public void remove() {
throw new UnsupportedOperationException("This iterator is read only.");
}
};
}
};
}
@Override
public String getQualifiedName() {
if (qualifiedName == null) {
if (parent == null) {
qualifiedName = unqualifiedName;
} else {
qualifiedName = parent.getQualifiedName();
if (!isEmptyScope()) {
qualifiedName += SCOPE_SEP + unqualifiedName;
}
}
}
return qualifiedName;
}
Indent pushIndentLogger() {
lastUsedIndent = getLastUsedIndent().indent();
return lastUsedIndent;
}
private IndentImpl getLastUsedIndent() {
if (lastUsedIndent == null) {
if (parent != null) {
lastUsedIndent = new IndentImpl(parent.getLastUsedIndent());
} else {
lastUsedIndent = new IndentImpl(null);
}
}
return lastUsedIndent;
}
}