1 /*
   2  * Copyright (c) 2012, 2018, 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 
  43         boolean isEmitted() {
  44             return emitted;
  45         }
  46 
  47         private boolean emitted;
  48 
  49         IndentImpl(IndentImpl parentIndent) {
  50             this.parentIndent = parentIndent;
  51             this.indent = (parentIndent == null ? "" : parentIndent.indent + INDENTATION_INCREMENT);
  52         }
  53 
  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          */
  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 
  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 
 108         IndentImpl indent() {
 109             lastUsedIndent = new IndentImpl(this);
 110             return lastUsedIndent;
 111         }
 112 
 113         @Override
 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 
 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 
 150     ScopeImpl(DebugContext owner, Thread thread) {
 151         this(owner, thread.getName(), null, false);
 152     }
 153 
 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
 175     public void close() {
 176         owner.currentScope = parent;
 177         owner.lastClosedScope = this;
 178     }
 179 
 180     boolean isTopLevel() {
 181         return parent == null;
 182     }
 183 
 184     boolean isDumpEnabled(int dumpLevel) {
 185         assert dumpLevel >= 0;
 186         return currentDumpLevel >= dumpLevel;
 187     }
 188 
 189     boolean isVerifyEnabled() {
 190         return verifyEnabled;
 191     }
 192 
 193     boolean isLogEnabled(int logLevel) {
 194         assert logLevel > 0;
 195         return currentLogLevel >= logLevel;
 196     }
 197 
 198     boolean isCountEnabled() {
 199         return countEnabled;
 200     }
 201 
 202     boolean isTimeEnabled() {
 203         return timeEnabled;
 204     }
 205 
 206     boolean isMemUseTrackingEnabled() {
 207         return memUseTrackingEnabled;
 208     }
 209 
 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 
 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 
 229     private DebugConfig getConfig() {
 230         return owner.currentConfig;
 231     }
 232 
 233     /**
 234      * @see DebugContext#verify(Object, String)
 235      */
 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      */
 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"})
 270     private static <E extends Exception> RuntimeException silenceException(Class<E> type, Throwable ex) throws E {
 271         throw (E) ex;
 272     }
 273 
 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 
 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 
 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 }