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