1 /*
   2  * Copyright (c) 2013, 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 package org.graalvm.compiler.phases.verify;
  24 
  25 import java.util.ArrayList;
  26 import java.util.List;
  27 
  28 import org.graalvm.compiler.debug.Debug;
  29 import org.graalvm.compiler.debug.DebugMethodMetrics;
  30 import org.graalvm.compiler.debug.GraalError;
  31 import org.graalvm.compiler.graph.Node;
  32 import org.graalvm.compiler.graph.NodeInputList;
  33 import org.graalvm.compiler.nodes.CallTargetNode;
  34 import org.graalvm.compiler.nodes.Invoke;
  35 import org.graalvm.compiler.nodes.StructuredGraph;
  36 import org.graalvm.compiler.nodes.java.MethodCallTargetNode;
  37 import org.graalvm.compiler.nodes.java.NewArrayNode;
  38 import org.graalvm.compiler.nodes.java.StoreIndexedNode;
  39 import org.graalvm.compiler.phases.VerifyPhase;
  40 import org.graalvm.compiler.phases.tiers.PhaseContext;
  41 
  42 import jdk.vm.ci.meta.ResolvedJavaMethod;
  43 import jdk.vm.ci.meta.ResolvedJavaType;
  44 
  45 /**
  46  *
  47  * Verifies that call sites calling one of the methods in {@link Debug} use them correctly. Correct
  48  * usage of the methods in {@link Debug} requires call sites to not eagerly evaluate their
  49  * arguments. Additionally this phase verifies that no argument is the result of a call to
  50  * {@link StringBuilder#toString()} or {@link StringBuffer#toString()}. Ideally the parameters at
  51  * call sites of {@link Debug} are eliminated, and do not produce additional allocations, if
  52  * {@link Debug#isDumpEnabled(int)} (or {@link Debug#isLogEnabled(int)}, ...) is {@code false}.
  53  *
  54  * Methods in {@link Debug} checked by this phase are various different versions of
  55  * {@link Debug#log(String)} , {@link Debug#dump(int, Object, String)},
  56  * {@link Debug#logAndIndent(String)} and {@link Debug#verify(Object, String)}.
  57  */
  58 public class VerifyDebugUsage extends VerifyPhase<PhaseContext> {
  59 
  60     @Override
  61     public boolean checkContract() {
  62         return false;
  63     }
  64 
  65     @Override
  66     protected boolean verify(StructuredGraph graph, PhaseContext context) {
  67         ResolvedJavaType debugType = context.getMetaAccess().lookupJavaType(Debug.class);
  68         ResolvedJavaType nodeType = context.getMetaAccess().lookupJavaType(Node.class);
  69         ResolvedJavaType stringType = context.getMetaAccess().lookupJavaType(String.class);
  70         ResolvedJavaType debugMethodMetricsType = context.getMetaAccess().lookupJavaType(DebugMethodMetrics.class);
  71         ResolvedJavaType graalErrorType = context.getMetaAccess().lookupJavaType(GraalError.class);
  72 
  73         for (MethodCallTargetNode t : graph.getNodes(MethodCallTargetNode.TYPE)) {
  74             ResolvedJavaMethod callee = t.targetMethod();
  75             String calleeName = callee.getName();
  76             if (callee.getDeclaringClass().equals(debugType)) {
  77                 if (calleeName.equals("log") || calleeName.equals("logAndIndent") || calleeName.equals("verify") || calleeName.equals("dump")) {
  78                     verifyParameters(t, graph, t.arguments(), stringType, calleeName.equals("dump") ? 2 : 1);
  79                 }
  80             }
  81             if (callee.getDeclaringClass().isAssignableFrom(nodeType)) {
  82                 if (calleeName.equals("assertTrue") || calleeName.equals("assertFalse")) {
  83                     verifyParameters(t, graph, t.arguments(), stringType, 1);
  84                 }
  85             }
  86             if (callee.getDeclaringClass().equals(debugMethodMetricsType)) {
  87                 if (calleeName.equals("addToMetric") || calleeName.equals("getCurrentMetricValue") || calleeName.equals("incrementMetric")) {
  88                     verifyParameters(t, graph, t.arguments(), stringType, 1);
  89                 }
  90             }
  91             if (callee.getDeclaringClass().isAssignableFrom(graalErrorType) && !graph.method().getDeclaringClass().isAssignableFrom(graalErrorType)) {
  92                 if (calleeName.equals("guarantee")) {
  93                     verifyParameters(t, graph, t.arguments(), stringType, 0);
  94                 }
  95                 if (calleeName.equals("<init>") && callee.getSignature().getParameterCount(false) == 2) {
  96                     verifyParameters(t, graph, t.arguments(), stringType, 1);
  97                 }
  98             }
  99         }
 100         return true;
 101     }
 102 
 103     private static void verifyParameters(MethodCallTargetNode callTarget, StructuredGraph callerGraph, NodeInputList<? extends Node> args, ResolvedJavaType stringType, int startArgIdx) {
 104         if (callTarget.targetMethod().isVarArgs() && args.get(args.count() - 1) instanceof NewArrayNode) {
 105             // unpack the arguments to the var args
 106             List<Node> unpacked = new ArrayList<>(args.snapshot());
 107             NewArrayNode varArgParameter = (NewArrayNode) unpacked.remove(unpacked.size() - 1);
 108             int firstVarArg = unpacked.size();
 109             for (Node usage : varArgParameter.usages()) {
 110                 if (usage instanceof StoreIndexedNode) {
 111                     StoreIndexedNode si = (StoreIndexedNode) usage;
 112                     unpacked.add(si.value());
 113                 }
 114             }
 115             verifyParameters(callerGraph, callTarget.targetMethod(), unpacked, stringType, startArgIdx, firstVarArg);
 116         } else {
 117             verifyParameters(callerGraph, callTarget.targetMethod(), args, stringType, startArgIdx, -1);
 118         }
 119     }
 120 
 121     private static void verifyParameters(StructuredGraph callerGraph, ResolvedJavaMethod verifiedCallee, List<? extends Node> args, ResolvedJavaType stringType, int startArgIdx, int varArgsIndex) {
 122         int argIdx = startArgIdx;
 123         int varArgsElementIndex = 0;
 124         boolean reportVarArgs = false;
 125         for (int i = 0; i < args.size(); i++) {
 126             Node arg = args.get(i);
 127             if (arg instanceof Invoke) {
 128                 reportVarArgs = varArgsIndex >= 0 && argIdx >= varArgsIndex;
 129                 Invoke invoke = (Invoke) arg;
 130                 CallTargetNode callTarget = invoke.callTarget();
 131                 if (callTarget instanceof MethodCallTargetNode) {
 132                     ResolvedJavaMethod m = ((MethodCallTargetNode) callTarget).targetMethod();
 133                     if (m.getName().equals("toString")) {
 134                         int bci = invoke.bci();
 135                         int nonVarArgIdx = reportVarArgs ? argIdx - varArgsElementIndex : argIdx;
 136                         verifyStringConcat(callerGraph, verifiedCallee, bci, nonVarArgIdx, reportVarArgs ? varArgsElementIndex : -1, m);
 137                         verifyToStringCall(callerGraph, verifiedCallee, stringType, m, bci, nonVarArgIdx, reportVarArgs ? varArgsElementIndex : -1);
 138                     } else if (m.getName().equals("format")) {
 139                         int bci = invoke.bci();
 140                         int nonVarArgIdx = reportVarArgs ? argIdx - varArgsElementIndex : argIdx;
 141                         verifyFormatCall(callerGraph, verifiedCallee, stringType, m, bci, nonVarArgIdx, reportVarArgs ? varArgsElementIndex : -1);
 142 
 143                     }
 144                 }
 145             }
 146             if (varArgsIndex >= 0 && i >= varArgsIndex) {
 147                 varArgsElementIndex++;
 148             }
 149             argIdx++;
 150         }
 151     }
 152 
 153     /**
 154      * Checks that a given call is not to {@link StringBuffer#toString()} or
 155      * {@link StringBuilder#toString()}.
 156      */
 157     private static void verifyStringConcat(StructuredGraph callerGraph, ResolvedJavaMethod verifiedCallee, int bci, int argIdx, int varArgsElementIndex, ResolvedJavaMethod callee) {
 158         if (callee.getDeclaringClass().getName().equals("Ljava/lang/StringBuilder;") || callee.getDeclaringClass().getName().equals("Ljava/lang/StringBuffer;")) {
 159             StackTraceElement e = callerGraph.method().asStackTraceElement(bci);
 160             if (varArgsElementIndex >= 0) {
 161                 throw new VerificationError(
 162                                 "In %s: element %d of parameter %d of call to %s appears to be a String concatenation expression.%n", e, varArgsElementIndex, argIdx,
 163                                 verifiedCallee.format("%H.%n(%p)"));
 164             } else {
 165                 throw new VerificationError(
 166                                 "In %s: parameter %d of call to %s appears to be a String concatenation expression.%n", e, argIdx, verifiedCallee.format("%H.%n(%p)"));
 167             }
 168         }
 169     }
 170 
 171     /**
 172      * Checks that a given call is not to {@link Object#toString()}.
 173      */
 174     private static void verifyToStringCall(StructuredGraph callerGraph, ResolvedJavaMethod verifiedCallee, ResolvedJavaType stringType, ResolvedJavaMethod callee, int bci, int argIdx,
 175                     int varArgsElementIndex) {
 176         if (callee.getSignature().getParameterCount(false) == 0 && callee.getSignature().getReturnType(callee.getDeclaringClass()).equals(stringType)) {
 177             StackTraceElement e = callerGraph.method().asStackTraceElement(bci);
 178             if (varArgsElementIndex >= 0) {
 179                 throw new VerificationError(
 180                                 "In %s: element %d of parameter %d of call to %s is a call to toString() which is redundant (the callee will do it) and forces unnecessary eager evaluation.",
 181                                 e, varArgsElementIndex, argIdx, verifiedCallee.format("%H.%n(%p)"));
 182             } else {
 183                 throw new VerificationError("In %s: parameter %d of call to %s is a call to toString() which is redundant (the callee will do it) and forces unnecessary eager evaluation.", e, argIdx,
 184                                 verifiedCallee.format("%H.%n(%p)"));
 185             }
 186         }
 187     }
 188 
 189     /**
 190      * Checks that a given call is not to {@link String#format(String, Object...)} or
 191      * {@link String#format(java.util.Locale, String, Object...)}.
 192      */
 193     private static void verifyFormatCall(StructuredGraph callerGraph, ResolvedJavaMethod verifiedCallee, ResolvedJavaType stringType, ResolvedJavaMethod callee, int bci, int argIdx,
 194                     int varArgsElementIndex) {
 195         if (callee.getDeclaringClass().equals(stringType) && callee.getSignature().getReturnType(callee.getDeclaringClass()).equals(stringType)) {
 196             StackTraceElement e = callerGraph.method().asStackTraceElement(bci);
 197             if (varArgsElementIndex >= 0) {
 198                 throw new VerificationError(
 199                                 "In %s: element %d of parameter %d of call to %s is a call to String.format() which is redundant (%s does formatting) and forces unnecessary eager evaluation.",
 200                                 e, varArgsElementIndex, argIdx, verifiedCallee.format("%H.%n(%p)"), verifiedCallee.format("%h.%n"));
 201             } else {
 202                 throw new VerificationError("In %s: parameter %d of call to %s is a call to String.format() which is redundant (%s does formatting) and forces unnecessary eager evaluation.", e,
 203                                 argIdx,
 204                                 verifiedCallee.format("%H.%n(%p)"), verifiedCallee.format("%h.%n"));
 205             }
 206         }
 207     }
 208 }