1 /*
   2  * Copyright (c) 2016, 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.graph;
  26 
  27 import static org.graalvm.compiler.graph.NodeSourcePosition.Marker.None;
  28 import static org.graalvm.compiler.graph.NodeSourcePosition.Marker.Placeholder;
  29 import static org.graalvm.compiler.graph.NodeSourcePosition.Marker.Substitution;
  30 
  31 import java.util.Iterator;
  32 import java.util.Objects;
  33 
  34 import org.graalvm.compiler.bytecode.BytecodeDisassembler;
  35 import org.graalvm.compiler.bytecode.Bytecodes;
  36 
  37 import jdk.vm.ci.code.BytecodeFrame;
  38 import jdk.vm.ci.code.BytecodePosition;
  39 import jdk.vm.ci.code.CodeUtil;
  40 import jdk.vm.ci.meta.JavaMethod;
  41 import jdk.vm.ci.meta.MetaUtil;
  42 import jdk.vm.ci.meta.ResolvedJavaMethod;
  43 import jdk.vm.ci.services.Services;
  44 
  45 public class NodeSourcePosition extends BytecodePosition implements Iterable<NodeSourcePosition> {
  46 
  47     private static final boolean STRICT_SOURCE_POSITION = Boolean.parseBoolean(Services.getSavedProperties().get("debug.graal.SourcePositionStrictChecks"));
  48     private static final boolean SOURCE_POSITION_BYTECODES = Boolean.parseBoolean(Services.getSavedProperties().get("debug.graal.SourcePositionDisassemble"));
  49 
  50     private final int hashCode;
  51     private final Marker marker;
  52     private final SourceLanguagePosition sourceLanguagePosition;
  53 
  54     /**
  55      * Remove marker frames.
  56      */
  57     public NodeSourcePosition trim() {
  58         NodeSourcePosition lastMarker = null;
  59         for (NodeSourcePosition current = this; current != null; current = current.getCaller()) {
  60             if (current.marker != None) {
  61                 lastMarker = current;
  62             }
  63         }
  64         if (lastMarker == null) {
  65             return this;
  66         }
  67         return lastMarker.getCaller();
  68     }
  69 
  70     public ResolvedJavaMethod getRootMethod() {
  71         NodeSourcePosition cur = this;
  72         while (cur.getCaller() != null) {
  73             cur = cur.getCaller();
  74         }
  75         return cur.getMethod();
  76     }
  77 
  78     public boolean verifyRootMethod(ResolvedJavaMethod root) {
  79         JavaMethod currentRoot = getRootMethod();
  80         assert root.equals(currentRoot) || root.getName().equals(currentRoot.getName()) && root.getSignature().toMethodDescriptor().equals(currentRoot.getSignature().toMethodDescriptor()) &&
  81                         root.getDeclaringClass().getName().equals(currentRoot.getDeclaringClass().getName()) : root + " " + currentRoot;
  82         return true;
  83     }
  84 
  85     @Override
  86     public Iterator<NodeSourcePosition> iterator() {
  87         return new Iterator<NodeSourcePosition>() {
  88             private NodeSourcePosition currentPosition = NodeSourcePosition.this;
  89 
  90             @Override
  91             public boolean hasNext() {
  92                 return currentPosition != null;
  93             }
  94 
  95             @Override
  96             public NodeSourcePosition next() {
  97                 NodeSourcePosition current = currentPosition;
  98                 currentPosition = currentPosition.getCaller();
  99                 return current;
 100             }
 101         };
 102     }
 103 
 104     enum Marker {
 105         None,
 106         Placeholder,
 107         Substitution
 108     }
 109 
 110     public NodeSourcePosition(NodeSourcePosition caller, ResolvedJavaMethod method, int bci) {
 111         this(caller, method, bci, None);
 112     }
 113 
 114     public NodeSourcePosition(NodeSourcePosition caller, ResolvedJavaMethod method, int bci, Marker marker) {
 115         this(null, caller, method, bci, marker);
 116 
 117     }
 118 
 119     public NodeSourcePosition(SourceLanguagePosition sourceLanguagePosition, NodeSourcePosition caller, ResolvedJavaMethod method, int bci) {
 120         this(sourceLanguagePosition, caller, method, bci, None);
 121     }
 122 
 123     public NodeSourcePosition(SourceLanguagePosition sourceLanguagePosition, NodeSourcePosition caller, ResolvedJavaMethod method, int bci, Marker marker) {
 124         super(caller, method, bci);
 125         if (caller == null) {
 126             this.hashCode = 31 * bci + method.hashCode();
 127         } else {
 128             this.hashCode = caller.hashCode * 7 + 31 * bci + method.hashCode();
 129         }
 130         this.marker = marker;
 131         this.sourceLanguagePosition = sourceLanguagePosition;
 132     }
 133 
 134     public static NodeSourcePosition placeholder(ResolvedJavaMethod method) {
 135         return new NodeSourcePosition(null, method, BytecodeFrame.INVALID_FRAMESTATE_BCI, Placeholder);
 136     }
 137 
 138     public static NodeSourcePosition placeholder(ResolvedJavaMethod method, int bci) {
 139         return new NodeSourcePosition(null, method, bci, Placeholder);
 140     }
 141 
 142     public boolean isPlaceholder() {
 143         return marker == Placeholder;
 144     }
 145 
 146     public static NodeSourcePosition substitution(ResolvedJavaMethod method) {
 147         return substitution(null, method, BytecodeFrame.INVALID_FRAMESTATE_BCI);
 148     }
 149 
 150     public static NodeSourcePosition substitution(ResolvedJavaMethod method, int bci) {
 151         return substitution(null, method, bci);
 152     }
 153 
 154     public static NodeSourcePosition substitution(NodeSourcePosition caller, ResolvedJavaMethod method) {
 155         return substitution(caller, method, BytecodeFrame.INVALID_FRAMESTATE_BCI);
 156     }
 157 
 158     public static NodeSourcePosition substitution(NodeSourcePosition caller, ResolvedJavaMethod method, int bci) {
 159         return new NodeSourcePosition(caller, method, bci, Substitution);
 160     }
 161 
 162     public boolean isSubstitution() {
 163         return marker == Substitution;
 164     }
 165 
 166     @Override
 167     public boolean equals(Object obj) {
 168         if (obj == this) {
 169             return true;
 170         }
 171         if (obj != null && getClass() == obj.getClass()) {
 172             NodeSourcePosition that = (NodeSourcePosition) obj;
 173             if (hashCode != that.hashCode) {
 174                 return false;
 175             }
 176             if (this.getBCI() == that.getBCI() && Objects.equals(this.getMethod(), that.getMethod()) && Objects.equals(this.getCaller(), that.getCaller()) &&
 177                             Objects.equals(this.sourceLanguagePosition, that.sourceLanguagePosition)) {
 178                 return true;
 179             }
 180         }
 181         return false;
 182     }
 183 
 184     @Override
 185     public int hashCode() {
 186         return hashCode;
 187     }
 188 
 189     public int depth() {
 190         int d = 0;
 191         NodeSourcePosition pos = this;
 192         while (pos != null) {
 193             d++;
 194             pos = pos.getCaller();
 195         }
 196         return d;
 197     }
 198 
 199     public SourceLanguagePosition getSourceLanguage() {
 200         return sourceLanguagePosition;
 201     }
 202 
 203     @Override
 204     public NodeSourcePosition getCaller() {
 205         return (NodeSourcePosition) super.getCaller();
 206     }
 207 
 208     public NodeSourcePosition addCaller(SourceLanguagePosition newSourceLanguagePosition, NodeSourcePosition link) {
 209         return addCaller(newSourceLanguagePosition, link, false);
 210     }
 211 
 212     public NodeSourcePosition addCaller(NodeSourcePosition link) {
 213         return addCaller(null, link, false);
 214     }
 215 
 216     public NodeSourcePosition addCaller(NodeSourcePosition link, boolean isSubstitution) {
 217         return addCaller(null, link, isSubstitution);
 218     }
 219 
 220     public NodeSourcePosition addCaller(SourceLanguagePosition newSourceLanguagePosition, NodeSourcePosition link, boolean isSubstitution) {
 221         if (getCaller() == null) {
 222             if (isPlaceholder()) {
 223                 return new NodeSourcePosition(newSourceLanguagePosition, link, getMethod(), 0);
 224             }
 225             assert link == null || isSubstitution || verifyCaller(this, link) : link;
 226             assert !isSubstitution || marker == None;
 227             return new NodeSourcePosition(newSourceLanguagePosition, link, getMethod(), getBCI(), isSubstitution ? Substitution : None);
 228         } else {
 229             return new NodeSourcePosition(getCaller().addCaller(newSourceLanguagePosition, link, isSubstitution), getMethod(), getBCI(), marker);
 230         }
 231     }
 232 
 233     @Override
 234     public String toString() {
 235         StringBuilder sb = new StringBuilder(100);
 236         NodeSourcePosition pos = this;
 237         while (pos != null) {
 238             format(sb, pos);
 239             if (pos.sourceLanguagePosition != null) {
 240                 sb.append(" source=" + pos.sourceLanguagePosition.toShortString());
 241             }
 242             pos = pos.getCaller();
 243             if (pos != null) {
 244                 sb.append(CodeUtil.NEW_LINE);
 245             }
 246         }
 247         return sb.toString();
 248     }
 249 
 250     private static void format(StringBuilder sb, NodeSourcePosition pos) {
 251         MetaUtil.appendLocation(sb.append("at "), pos.getMethod(), pos.getBCI());
 252         if (pos.marker != None) {
 253             sb.append(" " + pos.marker);
 254         }
 255         if (SOURCE_POSITION_BYTECODES) {
 256             String disassembly = BytecodeDisassembler.disassembleOne(pos.getMethod(), pos.getBCI());
 257             if (disassembly != null && disassembly.length() > 0) {
 258                 sb.append(" // ");
 259                 sb.append(disassembly);
 260             }
 261         }
 262     }
 263 
 264     String shallowToString() {
 265         StringBuilder sb = new StringBuilder(100);
 266         format(sb, this);
 267         return sb.toString();
 268     }
 269 
 270     public boolean verify() {
 271         NodeSourcePosition current = this;
 272         NodeSourcePosition caller = getCaller();
 273         while (caller != null) {
 274             assert verifyCaller(current, caller) : current;
 275             current = caller;
 276             caller = caller.getCaller();
 277         }
 278         return true;
 279     }
 280 
 281     private static boolean verifyCaller(NodeSourcePosition current, NodeSourcePosition caller) {
 282         if (!STRICT_SOURCE_POSITION) {
 283             return true;
 284         }
 285         if (BytecodeFrame.isPlaceholderBci(caller.getBCI())) {
 286             return true;
 287         }
 288         int opcode = BytecodeDisassembler.getBytecodeAt(caller.getMethod(), caller.getBCI());
 289         JavaMethod method = BytecodeDisassembler.getInvokedMethodAt(caller.getMethod(), caller.getBCI());
 290         /*
 291          * It's not really possible to match the declaring classes since this might be an interface
 292          * invoke. Matching name and signature probably provides enough accuracy.
 293          */
 294         assert method == null || (method.getName().equals(current.getMethod().getName()) &&
 295                         method.getSignature().equals(current.getMethod().getSignature())) ||
 296                         caller.getMethod().getName().equals("linkToTargetMethod") ||
 297                         opcode == Bytecodes.INVOKEDYNAMIC ||
 298                         caller.getMethod().getDeclaringClass().getName().startsWith("Ljava/lang/invoke/LambdaForm$") ||
 299                         current.getMethod().getName().equals("callInlined") : "expected " + method + " but found " +
 300                                         current.getMethod();
 301         return true;
 302     }
 303 }