src/jdk.internal.vm.compiler/share/classes/org.graalvm.compiler.debug/src/org/graalvm/compiler/debug/ScopeImpl.java
author dlong
Thu, 31 May 2018 10:38:05 -0700
changeset 50330 2cbc42a5764b
parent 47216 71c04702a3d5
child 50858 2d3e99a72541
permissions -rw-r--r--
8202670: Update Graal Reviewed-by: kvn, aph

/*
 * 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;
    }
}