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