1 // Copyright 2019 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package org.chromium.tools.errorprone.plugin; 6 7 import com.google.auto.service.AutoService; 8 import com.google.errorprone.BugPattern; 9 import com.google.errorprone.VisitorState; 10 import com.google.errorprone.bugpatterns.BugChecker; 11 import com.google.errorprone.matchers.Description; 12 import com.google.errorprone.util.ASTHelpers; 13 import com.sun.source.tree.BinaryTree; 14 import com.sun.source.tree.IdentifierTree; 15 import com.sun.source.tree.LiteralTree; 16 import com.sun.source.tree.MethodInvocationTree; 17 import com.sun.source.tree.Tree; 18 import com.sun.source.util.SimpleTreeVisitor; 19 import com.sun.tools.javac.code.Symbol; 20 21 import java.util.Arrays; 22 import java.util.HashSet; 23 import java.util.List; 24 import java.util.Set; 25 26 import javax.lang.model.element.ElementKind; 27 import javax.lang.model.element.Modifier; 28 29 /** 30 * Triggers an error for {@link org.chromium.base.TraceEvent} usages with non string literals. 31 */ 32 @AutoService(BugChecker.class) 33 @BugPattern(name = "NoDynamicStringsInTraceEventCheck", 34 summary = "Only use of string literals are allowed in trace events.", 35 severity = BugPattern.SeverityLevel.ERROR, linkType = BugPattern.LinkType.CUSTOM, 36 link = "https://crbug.com/984827") 37 public class NoDynamicStringsInTraceEventCheck 38 extends BugChecker implements BugChecker.MethodInvocationTreeMatcher { 39 private static final Set<String> sTracingFunctions = new HashSet<>(Arrays.asList( 40 "begin", "end", "scoped", "startAsync", "finishAsync", "instant", "TraceEvent")); 41 42 private static final ParameterVisitor sVisitor = new ParameterVisitor(); 43 44 @Override matchMethodInvocation(MethodInvocationTree tree, VisitorState visitorState)45 public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState visitorState) { 46 Symbol.MethodSymbol method = ASTHelpers.getSymbol(tree); 47 if (!sTracingFunctions.contains(method.name.toString())) return Description.NO_MATCH; 48 49 String className = method.enclClass().fullname.toString(); 50 if (!"org.chromium.base.EarlyTraceEvent".equals(className) 51 && !"org.chromium.base.TraceEvent".equals(className)) { 52 return Description.NO_MATCH; 53 } 54 // Allow the events added by tracing. Adding SuppressWarning in these files causes all 55 // caller warnings to be ignored. 56 String filename = visitorState.getPath().getCompilationUnit().getSourceFile().getName(); 57 if (filename.endsWith("TraceEvent.java")) { 58 return Description.NO_MATCH; 59 } 60 61 List<? extends Tree> args = tree.getArguments(); 62 Tree eventName_expr = args.get(0); 63 64 ParameterVisitor.Result r = eventName_expr.accept(sVisitor, null); 65 if (r.success) return Description.NO_MATCH; 66 67 return buildDescription(tree) 68 .setMessage("Calling TraceEvent.begin() without a constant String object. " 69 + r.errorMessage) 70 .build(); 71 } 72 73 static class ParameterVisitor extends SimpleTreeVisitor<ParameterVisitor.Result, Void> { 74 static class Result { 75 public boolean success; 76 public String errorMessage; 77 Result(boolean successVal, String error)78 private Result(boolean successVal, String error) { 79 success = successVal; 80 errorMessage = error; 81 } 82 createError(String error)83 public static Result createError(String error) { 84 return new Result(false, error); 85 } 86 createOk()87 public static Result createOk() { 88 return new Result(true, null); 89 } 90 append(Result other)91 public Result append(Result other) { 92 success &= other.success; 93 if (errorMessage == null) { 94 errorMessage = other.errorMessage; 95 } else if (other.errorMessage != null) { 96 errorMessage += " " + other.errorMessage; 97 } 98 return this; 99 } 100 }; 101 102 @Override defaultAction(Tree tree, Void p)103 protected Result defaultAction(Tree tree, Void p) { 104 throw new RuntimeException("Unhandled expression tree type: " + tree.getKind()); 105 } 106 107 @Override visitBinary(BinaryTree tree, Void p)108 public Result visitBinary(BinaryTree tree, Void p) { 109 return tree.getLeftOperand() 110 .accept(this, null) 111 .append(tree.getRightOperand().accept(this, null)); 112 } 113 114 @Override visitLiteral(LiteralTree tree, Void p)115 public Result visitLiteral(LiteralTree tree, Void p) { 116 return Result.createOk(); 117 } 118 119 @Override visitIdentifier(IdentifierTree node, Void p)120 public Result visitIdentifier(IdentifierTree node, Void p) { 121 Symbol eventName = ASTHelpers.getSymbol(node); 122 if (eventName == null) { 123 return Result.createError("Identifier was not found: " + node + '.'); 124 } 125 if (eventName.getKind() == ElementKind.FIELD) { 126 if (!"java.lang.String".equals(eventName.type.toString())) { 127 return Result.createError("Field: " + eventName + " should be of type string."); 128 } 129 Set<Modifier> modifiers = eventName.getModifiers(); 130 if (!modifiers.contains(Modifier.FINAL) || !modifiers.contains(Modifier.STATIC)) { 131 return Result.createError( 132 "String literal: " + eventName + " is not static final."); 133 } 134 return Result.createOk(); 135 } else if (eventName.getKind() == ElementKind.PARAMETER) { 136 return Result.createError( 137 "Passing in event name as parameter: " + eventName + " is not supported."); 138 } 139 return Result.createError("Unhandled identifier kind: " + node.getKind() + '.'); 140 } 141 }; 142 } 143