/
SuppressionInfo.java
196 lines (179 loc) · 8.23 KB
/
SuppressionInfo.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
/*
* Copyright 2018 The Error Prone Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.errorprone;
import com.google.common.collect.ImmutableSet;
import com.google.errorprone.annotations.CheckReturnValue;
import com.google.errorprone.annotations.Immutable;
import com.google.errorprone.matchers.Suppressible;
import com.google.errorprone.suppliers.Supplier;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.util.SimpleTreeVisitor;
import com.sun.tools.javac.code.Attribute;
import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.code.Symbol.ClassSymbol;
import com.sun.tools.javac.code.Symbol.MethodSymbol;
import com.sun.tools.javac.util.Name;
import com.sun.tools.javac.util.Pair;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Immutable container of "suppression signals" - annotations or other information gathered from
* source - that can be used to determine if a specific {@link Suppressible} object should be
* considered suppressed.
*
* <p>SuppressionInfo instances are obtained by starting with the {@link #EMPTY} instance, then
* getting new instances by calling {@link #withExtendedSuppressions} with symbols discovered as you
* descend a program tree.
*/
@Immutable
@CheckReturnValue
public class SuppressionInfo {
public static final SuppressionInfo EMPTY =
new SuppressionInfo(ImmutableSet.of(), ImmutableSet.of(), false);
private static final Supplier<Name> ANDROID_SUPPRESS_LINT =
VisitorState.memoize(state -> state.getName("android.annotation.SuppressLint"));
private static final Supplier<Name> VALUE = VisitorState.memoize(state -> state.getName("value"));
private final ImmutableSet<String> suppressWarningsStrings;
@SuppressWarnings("Immutable") /* Name is javac's interned version of a string. */
private final ImmutableSet<Name> customSuppressions;
private final boolean inGeneratedCode;
private SuppressionInfo(
Set<String> suppressWarningsStrings, Set<Name> customSuppressions, boolean inGeneratedCode) {
this.suppressWarningsStrings = ImmutableSet.copyOf(suppressWarningsStrings);
this.customSuppressions = ImmutableSet.copyOf(customSuppressions);
this.inGeneratedCode = inGeneratedCode;
}
private static boolean isGenerated(Symbol sym, VisitorState state) {
return !ASTHelpers.getGeneratedBy(sym, state).isEmpty();
}
/**
* Returns true if this checker should be considered suppressed given the signals present in this
* object.
*
* @param suppressible Holds information about the suppressibilty of a checker
* @param suppressedInGeneratedCode true if this checker instance should be considered suppressed
*/
public SuppressedState suppressedState(
Suppressible suppressible, boolean suppressedInGeneratedCode, VisitorState state) {
if (inGeneratedCode && suppressedInGeneratedCode) {
return SuppressedState.SUPPRESSED;
}
if (suppressible.supportsSuppressWarnings()
&& !Collections.disjoint(suppressible.allNames(), suppressWarningsStrings)) {
return SuppressedState.SUPPRESSED;
}
if (suppressible.suppressedByAnyOf(customSuppressions, state)) {
return SuppressedState.SUPPRESSED;
}
return SuppressedState.UNSUPPRESSED;
}
/**
* Generates the {@link SuppressionInfo} for a {@link CompilationUnitTree}. This differs in that
* {@code isGenerated} is determined by inspecting the annotations of the outermost class so that
* matchers on {@link CompilationUnitTree} will also be suppressed.
*/
public SuppressionInfo forCompilationUnit(CompilationUnitTree tree, VisitorState state) {
AtomicBoolean generated = new AtomicBoolean(false);
new SimpleTreeVisitor<Void, Void>() {
@Override
public Void visitClass(ClassTree node, Void unused) {
ClassSymbol symbol = ASTHelpers.getSymbol(node);
generated.compareAndSet(false, symbol != null && isGenerated(symbol, state));
return null;
}
}.visit(tree.getTypeDecls(), null);
return new SuppressionInfo(suppressWarningsStrings, customSuppressions, generated.get());
}
/**
* Returns an instance of {@code SuppressionInfo} that takes into account any suppression signals
* present on {@code sym} as well as those already stored in {@code this}.
*
* <p>Checks suppressions for any {@code @SuppressWarnings}, Android's {@code SuppressLint}, and
* custom suppression annotations described by {@code customSuppressionAnnosToLookFor}.
*
* <p>We do not modify the existing suppression sets, so they can be restored when moving up the
* tree. We also avoid copying the suppression sets if the next node to explore does not have any
* suppressed warnings or custom suppression annotations. This is the common case.
*
* @param sym The {@code Symbol} for the AST node currently being scanned
* @param state VisitorState for checking the current tree, as well as for getting the {@code
* SuppressWarnings symbol type}.
*/
public SuppressionInfo withExtendedSuppressions(
Symbol sym, VisitorState state, Set<? extends Name> customSuppressionAnnosToLookFor) {
boolean newInGeneratedCode = inGeneratedCode || isGenerated(sym, state);
boolean anyModification = newInGeneratedCode != inGeneratedCode;
/* Handle custom suppression annotations. */
Set<Name> lookingFor = new HashSet<>(customSuppressionAnnosToLookFor);
lookingFor.removeAll(customSuppressions);
Set<Name> newlyPresent = ASTHelpers.annotationsAmong(sym, lookingFor, state);
Set<Name> newCustomSuppressions;
if (!newlyPresent.isEmpty()) {
anyModification = true;
newCustomSuppressions = newlyPresent;
newCustomSuppressions.addAll(customSuppressions);
} else {
newCustomSuppressions = customSuppressions;
}
/* Handle {@code @SuppressWarnings} and {@code @SuppressLint}. */
Name suppressLint = ANDROID_SUPPRESS_LINT.get(state);
Name valueName = VALUE.get(state);
Set<String> newSuppressions = null;
// Iterate over annotations on this symbol, looking for SuppressWarnings
for (Attribute.Compound attr : sym.getAnnotationMirrors()) {
if ((attr.type.tsym == state.getSymtab().suppressWarningsType.tsym)
|| attr.type.tsym.getQualifiedName().equals(suppressLint)) {
for (Pair<MethodSymbol, Attribute> value : attr.values) {
if (value.fst.name.equals(valueName)) {
if (value.snd
instanceof Attribute.Array) { // SuppressWarnings/SuppressLint take an array
for (Attribute suppress : ((Attribute.Array) value.snd).values) {
String suppressedWarning = (String) suppress.getValue();
if (!suppressWarningsStrings.contains(suppressedWarning)) {
anyModification = true;
if (newSuppressions == null) {
newSuppressions = new HashSet<>(suppressWarningsStrings);
}
newSuppressions.add(suppressedWarning);
}
}
} else {
throw new RuntimeException(
"Expected SuppressWarnings/SuppressLint annotation to take array type");
}
}
}
}
}
// Since this is invoked every time we descend into a new node, let's save some garbage
// by returning the same instance if there were no changes.
if (!anyModification) {
return this;
}
if (newSuppressions == null) {
newSuppressions = suppressWarningsStrings;
}
return new SuppressionInfo(newSuppressions, newCustomSuppressions, newInGeneratedCode);
}
public enum SuppressedState {
UNSUPPRESSED,
SUPPRESSED
}
}