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