• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2019 Uber Technologies, Inc.
3  *
4  * Permission is hereby granted, free of charge, to any person obtaining a copy
5  * of this software and associated documentation files (the "Software"), to deal
6  * in the Software without restriction, including without limitation the rights
7  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8  * copies of the Software, and to permit persons to whom the Software is
9  * furnished to do so, subject to the following conditions:
10  *
11  * The above copyright notice and this permission notice shall be included in
12  * all copies or substantial portions of the Software.
13  *
14  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20  * THE SOFTWARE.
21  */
22 
23 package com.uber.nullaway;
24 
25 import static com.uber.nullaway.ASTHelpersBackports.isStatic;
26 import static com.uber.nullaway.ErrorMessage.MessageTypes.FIELD_NO_INIT;
27 import static com.uber.nullaway.ErrorMessage.MessageTypes.GET_ON_EMPTY_OPTIONAL;
28 import static com.uber.nullaway.ErrorMessage.MessageTypes.METHOD_NO_INIT;
29 import static com.uber.nullaway.ErrorMessage.MessageTypes.NONNULL_FIELD_READ_BEFORE_INIT;
30 import static com.uber.nullaway.NullAway.CORE_CHECK_NAME;
31 import static com.uber.nullaway.NullAway.INITIALIZATION_CHECK_NAME;
32 import static com.uber.nullaway.NullAway.OPTIONAL_CHECK_NAME;
33 import static com.uber.nullaway.NullAway.getTreesInstance;
34 import static com.uber.nullaway.Nullness.hasNullableAnnotation;
35 
36 import com.google.common.base.Joiner;
37 import com.google.common.base.Preconditions;
38 import com.google.common.collect.ImmutableList;
39 import com.google.common.collect.ImmutableSet;
40 import com.google.common.collect.Iterables;
41 import com.google.common.collect.Lists;
42 import com.google.errorprone.VisitorState;
43 import com.google.errorprone.fixes.SuggestedFix;
44 import com.google.errorprone.matchers.Description;
45 import com.google.errorprone.util.ASTHelpers;
46 import com.sun.source.tree.AnnotationTree;
47 import com.sun.source.tree.ClassTree;
48 import com.sun.source.tree.MethodInvocationTree;
49 import com.sun.source.tree.MethodTree;
50 import com.sun.source.tree.ModifiersTree;
51 import com.sun.source.tree.Tree;
52 import com.sun.source.tree.VariableTree;
53 import com.sun.source.util.TreePath;
54 import com.sun.tools.javac.code.Symbol;
55 import com.sun.tools.javac.tree.JCTree.JCCompilationUnit;
56 import com.sun.tools.javac.util.DiagnosticSource;
57 import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition;
58 import com.uber.nullaway.fixserialization.SerializationService;
59 import java.util.Iterator;
60 import java.util.List;
61 import java.util.Set;
62 import java.util.stream.StreamSupport;
63 import javax.annotation.Nullable;
64 import javax.lang.model.element.Element;
65 import javax.lang.model.element.ElementKind;
66 import javax.tools.JavaFileObject;
67 
68 /** A class to construct error message to be displayed after the analysis finds error. */
69 public class ErrorBuilder {
70 
71   private final Config config;
72 
73   /** Checker name that can be used to suppress the warnings. */
74   private final String suppressionName;
75 
76   /** Additional identifiers for this check, to be checked for in @SuppressWarnings annotations. */
77   private final Set<String> allNames;
78 
ErrorBuilder(Config config, String suppressionName, Set<String> allNames)79   ErrorBuilder(Config config, String suppressionName, Set<String> allNames) {
80     this.config = config;
81     this.suppressionName = suppressionName;
82     this.allNames = allNames;
83   }
84 
85   /**
86    * create an error description for a nullability warning
87    *
88    * @param errorMessage the error message object.
89    * @param descriptionBuilder the description builder for the error.
90    * @param state the visitor state (used for e.g. suppression finding).
91    * @param nonNullTarget if non-null, this error involved a pseudo-assignment of a @Nullable
92    *     expression into a @NonNull target, and this parameter is the Symbol for that target.
93    * @return the error description
94    */
createErrorDescription( ErrorMessage errorMessage, Description.Builder descriptionBuilder, VisitorState state, @Nullable Symbol nonNullTarget)95   public Description createErrorDescription(
96       ErrorMessage errorMessage,
97       Description.Builder descriptionBuilder,
98       VisitorState state,
99       @Nullable Symbol nonNullTarget) {
100     Tree enclosingSuppressTree = suppressibleNode(state.getPath());
101     return createErrorDescription(
102         errorMessage, enclosingSuppressTree, descriptionBuilder, state, nonNullTarget);
103   }
104 
105   /**
106    * create an error description for a nullability warning
107    *
108    * @param errorMessage the error message object.
109    * @param suggestTree the location at which a fix suggestion should be made
110    * @param descriptionBuilder the description builder for the error.
111    * @param state the visitor state (used for e.g. suppression finding).
112    * @param nonNullTarget if non-null, this error involved a pseudo-assignment of a @Nullable
113    *     expression into a @NonNull target, and this parameter is the Symbol for that target.
114    * @return the error description
115    */
createErrorDescription( ErrorMessage errorMessage, @Nullable Tree suggestTree, Description.Builder descriptionBuilder, VisitorState state, @Nullable Symbol nonNullTarget)116   public Description createErrorDescription(
117       ErrorMessage errorMessage,
118       @Nullable Tree suggestTree,
119       Description.Builder descriptionBuilder,
120       VisitorState state,
121       @Nullable Symbol nonNullTarget) {
122     Description.Builder builder = descriptionBuilder.setMessage(errorMessage.message);
123     String checkName = CORE_CHECK_NAME;
124     if (errorMessage.messageType.equals(GET_ON_EMPTY_OPTIONAL)) {
125       checkName = OPTIONAL_CHECK_NAME;
126     } else if (errorMessage.messageType.equals(FIELD_NO_INIT)
127         || errorMessage.messageType.equals(METHOD_NO_INIT)
128         || errorMessage.messageType.equals(NONNULL_FIELD_READ_BEFORE_INIT)) {
129       checkName = INITIALIZATION_CHECK_NAME;
130     }
131 
132     // Mildly expensive state.getPath() traversal, occurs only once per potentially
133     // reported error.
134     if (hasPathSuppression(state.getPath(), checkName)) {
135       return Description.NO_MATCH;
136     }
137 
138     if (config.suggestSuppressions() && suggestTree != null) {
139       builder = addSuggestedSuppression(errorMessage, suggestTree, builder, state);
140     }
141 
142     if (config.serializationIsActive()) {
143       if (nonNullTarget != null) {
144         SerializationService.serializeFixSuggestion(config, state, nonNullTarget, errorMessage);
145       }
146       // For the case of initializer errors, the leaf of state.getPath() may not be the field /
147       // method on which the error is being reported (since we do a class-wide analysis to find such
148       // errors).  In such cases, the suggestTree is the appropriate field / method tree, so use
149       // that as the errorTree for serialization.
150       Tree errorTree =
151           (suggestTree != null
152                   && (errorMessage.messageType.equals(FIELD_NO_INIT)
153                       || errorMessage.messageType.equals(METHOD_NO_INIT)))
154               ? suggestTree
155               : state.getPath().getLeaf();
156       SerializationService.serializeReportingError(
157           config, state, errorTree, nonNullTarget, errorMessage);
158     }
159 
160     // #letbuildersbuild
161     return builder.build();
162   }
163 
canHaveSuppressWarningsAnnotation(Tree tree)164   private static boolean canHaveSuppressWarningsAnnotation(Tree tree) {
165     return tree instanceof MethodTree
166         || (tree instanceof ClassTree && ((ClassTree) tree).getSimpleName().length() != 0)
167         || tree instanceof VariableTree;
168   }
169 
170   /**
171    * Find out if a particular subchecker (e.g. NullAway.Optional) is being suppressed in a given
172    * path.
173    *
174    * <p>This requires a tree path traversal, which is expensive, but we only do this when we would
175    * otherwise report an error, which means this won't happen for most nodes/files.
176    *
177    * @param treePath The path with the error location as the leaf.
178    * @param subcheckerName The string to check for inside @SuppressWarnings
179    * @return Whether the subchecker is being suppressed at treePath.
180    */
hasPathSuppression(TreePath treePath, String subcheckerName)181   private boolean hasPathSuppression(TreePath treePath, String subcheckerName) {
182     return StreamSupport.stream(treePath.spliterator(), false)
183         .filter(ErrorBuilder::canHaveSuppressWarningsAnnotation)
184         .map(tree -> ASTHelpers.getSymbol(tree))
185         .filter(symbol -> symbol != null)
186         .anyMatch(
187             symbol ->
188                 symbolHasSuppressWarningsAnnotation(symbol, subcheckerName)
189                     || symbolIsExcludedClassSymbol(symbol));
190   }
191 
addSuggestedSuppression( ErrorMessage errorMessage, Tree suggestTree, Description.Builder builder, VisitorState state)192   private Description.Builder addSuggestedSuppression(
193       ErrorMessage errorMessage,
194       Tree suggestTree,
195       Description.Builder builder,
196       VisitorState state) {
197     switch (errorMessage.messageType) {
198       case DEREFERENCE_NULLABLE:
199       case RETURN_NULLABLE:
200       case PASS_NULLABLE:
201       case ASSIGN_FIELD_NULLABLE:
202       case SWITCH_EXPRESSION_NULLABLE:
203         if (config.getCastToNonNullMethod() != null && canBeCastToNonNull(suggestTree)) {
204           builder = addCastToNonNullFix(suggestTree, builder, state);
205         } else {
206           // When there is a castToNonNull method, suggestTree is set to the expression to be
207           // casted, which is not suppressible. For simplicity, we just always recompute the
208           // suppressible node here.
209           Tree suppressibleNode = suppressibleNode(state.getPath());
210           if (suppressibleNode != null) {
211             builder = addSuppressWarningsFix(suppressibleNode, builder, suppressionName);
212           }
213         }
214         break;
215       case CAST_TO_NONNULL_ARG_NONNULL:
216         builder = removeCastToNonNullFix(suggestTree, builder, state);
217         break;
218       case WRONG_OVERRIDE_RETURN:
219         builder = addSuppressWarningsFix(suggestTree, builder, suppressionName);
220         break;
221       case WRONG_OVERRIDE_PARAM:
222         builder = addSuppressWarningsFix(suggestTree, builder, suppressionName);
223         break;
224       case METHOD_NO_INIT:
225       case FIELD_NO_INIT:
226         builder = addSuppressWarningsFix(suggestTree, builder, INITIALIZATION_CHECK_NAME);
227         break;
228       case ANNOTATION_VALUE_INVALID:
229         break;
230       default:
231         builder = addSuppressWarningsFix(suggestTree, builder, suppressionName);
232     }
233     return builder;
234   }
235 
236   /**
237    * create an error description for a generalized @Nullable value to @NonNull location assignment.
238    *
239    * <p>This includes: field assignments, method arguments and method returns
240    *
241    * @param errorMessage the error message object.
242    * @param suggestTreeIfCastToNonNull the location at which a fix suggestion should be made if a
243    *     castToNonNull method is available (usually the expression to cast)
244    * @param descriptionBuilder the description builder for the error.
245    * @param state the visitor state for the location which triggered the error (i.e. for suppression
246    *     finding)
247    * @param nonNullTarget if non-null, this error involved a pseudo-assignment of a @Nullable
248    *     expression into a @NonNull target, and this parameter is the Symbol for that target.
249    * @return the error description.
250    */
createErrorDescriptionForNullAssignment( ErrorMessage errorMessage, @Nullable Tree suggestTreeIfCastToNonNull, Description.Builder descriptionBuilder, VisitorState state, @Nullable Symbol nonNullTarget)251   Description createErrorDescriptionForNullAssignment(
252       ErrorMessage errorMessage,
253       @Nullable Tree suggestTreeIfCastToNonNull,
254       Description.Builder descriptionBuilder,
255       VisitorState state,
256       @Nullable Symbol nonNullTarget) {
257     if (config.getCastToNonNullMethod() != null) {
258       return createErrorDescription(
259           errorMessage, suggestTreeIfCastToNonNull, descriptionBuilder, state, nonNullTarget);
260     } else {
261       return createErrorDescription(
262           errorMessage,
263           suppressibleNode(state.getPath()),
264           descriptionBuilder,
265           state,
266           nonNullTarget);
267     }
268   }
269 
addSuppressWarningsFix( Tree suggestTree, Description.Builder builder, String suppressionName)270   Description.Builder addSuppressWarningsFix(
271       Tree suggestTree, Description.Builder builder, String suppressionName) {
272     SuppressWarnings extantSuppressWarnings = null;
273     Symbol treeSymbol = ASTHelpers.getSymbol(suggestTree);
274     if (treeSymbol != null) {
275       extantSuppressWarnings = treeSymbol.getAnnotation(SuppressWarnings.class);
276     }
277     SuggestedFix fix;
278     if (extantSuppressWarnings == null) {
279       fix =
280           SuggestedFix.prefixWith(
281               suggestTree,
282               "@SuppressWarnings(\""
283                   + suppressionName
284                   + "\") "
285                   + config.getAutofixSuppressionComment());
286     } else {
287       // need to update the existing list of warnings
288       final List<String> suppressions = Lists.newArrayList(extantSuppressWarnings.value());
289       suppressions.add(suppressionName);
290       // find the existing annotation, so we can replace it
291       final ModifiersTree modifiers =
292           (suggestTree instanceof MethodTree)
293               ? ((MethodTree) suggestTree).getModifiers()
294               : ((VariableTree) suggestTree).getModifiers();
295       final List<? extends AnnotationTree> annotations = modifiers.getAnnotations();
296       // noinspection ConstantConditions
297       com.google.common.base.Optional<? extends AnnotationTree> suppressWarningsAnnot =
298           Iterables.tryFind(
299               annotations,
300               annot -> annot.getAnnotationType().toString().endsWith("SuppressWarnings"));
301       if (!suppressWarningsAnnot.isPresent()) {
302         throw new AssertionError("something went horribly wrong");
303       }
304       final String replacement =
305           "@SuppressWarnings({"
306               + Joiner.on(',').join(Iterables.transform(suppressions, s -> '"' + s + '"'))
307               + "}) "
308               + config.getAutofixSuppressionComment();
309       fix = SuggestedFix.replace(suppressWarningsAnnot.get(), replacement);
310     }
311     return builder.addFix(fix);
312   }
313 
314   /**
315    * Adapted from {@link com.google.errorprone.fixes.SuggestedFixes}.
316    *
317    * <p>TODO: actually use {@link
318    * com.google.errorprone.fixes.SuggestedFixes#addSuppressWarnings(VisitorState, String)} instead
319    */
320   @Nullable
suppressibleNode(@ullable TreePath path)321   private Tree suppressibleNode(@Nullable TreePath path) {
322     if (path == null) {
323       return null;
324     }
325     return StreamSupport.stream(path.spliterator(), false)
326         .filter(ErrorBuilder::canHaveSuppressWarningsAnnotation)
327         .findFirst()
328         .orElse(null);
329   }
330 
331   /**
332    * Checks if it would be appropriate to wrap {@code tree} in a {@code castToNonNull} call. There
333    * are two cases where this method returns {@code false}:
334    *
335    * <ol>
336    *   <li>{@code tree} represents the {@code null} literal
337    *   <li>{@code tree} represents a {@code @Nullable} formal parameter of the enclosing method
338    * </ol>
339    */
canBeCastToNonNull(Tree tree)340   private boolean canBeCastToNonNull(Tree tree) {
341     switch (tree.getKind()) {
342       case NULL_LITERAL:
343         // never do castToNonNull(null)
344         return false;
345       case IDENTIFIER:
346         // Don't wrap a @Nullable parameter in castToNonNull, as this misleads callers into thinking
347         // they can pass in null without causing an NPE.  A more appropriate fix would likely be to
348         // make the parameter @NonNull and add casts at call sites, but that is beyond the scope of
349         // our suggested fixes
350         Symbol symbol = ASTHelpers.getSymbol(tree);
351         return !(symbol != null
352             && symbol.getKind().equals(ElementKind.PARAMETER)
353             && hasNullableAnnotation(symbol, config));
354       default:
355         return true;
356     }
357   }
358 
addCastToNonNullFix( Tree suggestTree, Description.Builder builder, VisitorState state)359   private Description.Builder addCastToNonNullFix(
360       Tree suggestTree, Description.Builder builder, VisitorState state) {
361     final String fullMethodName = config.getCastToNonNullMethod();
362     if (fullMethodName == null) {
363       throw new IllegalStateException("cast-to-non-null method not set");
364     }
365     // Add a call to castToNonNull around suggestTree:
366     final String[] parts = fullMethodName.split("\\.");
367     final String shortMethodName = parts[parts.length - 1];
368     final String replacement = shortMethodName + "(" + state.getSourceForNode(suggestTree) + ")";
369     final SuggestedFix fix =
370         SuggestedFix.builder()
371             .replace(suggestTree, replacement)
372             .addStaticImport(fullMethodName) // ensure castToNonNull static import
373             .build();
374     return builder.addFix(fix);
375   }
376 
removeCastToNonNullFix( Tree suggestTree, Description.Builder builder, VisitorState state)377   private Description.Builder removeCastToNonNullFix(
378       Tree suggestTree, Description.Builder builder, VisitorState state) {
379     // Note: Here suggestTree refers to the argument being cast. We need to find the
380     // castToNonNull(...) invocation to be replaced by it. Fortunately, state.getPath()
381     // should be currently pointing at said call.
382     Tree currTree = state.getPath().getLeaf();
383     Preconditions.checkArgument(
384         currTree.getKind() == Tree.Kind.METHOD_INVOCATION,
385         String.format("Expected castToNonNull invocation expression, found:\n%s", currTree));
386     final MethodInvocationTree invTree = (MethodInvocationTree) currTree;
387     Preconditions.checkArgument(
388         invTree.getArguments().contains(suggestTree),
389         String.format(
390             "Method invocation tree %s does not contain the expression %s as an argument being cast",
391             invTree, suggestTree));
392     // Remove the call to castToNonNull:
393     final SuggestedFix fix =
394         SuggestedFix.builder().replace(invTree, state.getSourceForNode(suggestTree)).build();
395     return builder.addFix(fix);
396   }
397 
398   /**
399    * Reports initialization errors where a constructor fails to guarantee non-null fields are
400    * initialized along all paths at exit points.
401    *
402    * @param methodSymbol Constructor symbol.
403    * @param message Error message.
404    * @param state The VisitorState object.
405    * @param descriptionBuilder the description builder for the error.
406    * @param nonNullFields list of @Nonnull fields that are not guaranteed to be initialized along
407    *     all paths at exit points of the constructor.
408    */
reportInitializerError( Symbol.MethodSymbol methodSymbol, String message, VisitorState state, Description.Builder descriptionBuilder, ImmutableList<Symbol> nonNullFields)409   void reportInitializerError(
410       Symbol.MethodSymbol methodSymbol,
411       String message,
412       VisitorState state,
413       Description.Builder descriptionBuilder,
414       ImmutableList<Symbol> nonNullFields) {
415     // Check needed here, despite check in hasPathSuppression because initialization
416     // checking happens at the class-level (meaning state.getPath() might not include the
417     // method itself).
418     if (symbolHasSuppressWarningsAnnotation(methodSymbol, INITIALIZATION_CHECK_NAME)) {
419       return;
420     }
421     Tree methodTree = getTreesInstance(state).getTree(methodSymbol);
422     ErrorMessage errorMessage = new ErrorMessage(METHOD_NO_INIT, message);
423     state.reportMatch(
424         createErrorDescription(errorMessage, methodTree, descriptionBuilder, state, null));
425     if (config.serializationIsActive()) {
426       // For now, we serialize each fix suggestion separately and measure their effectiveness
427       // separately
428       nonNullFields.forEach(
429           symbol ->
430               SerializationService.serializeFixSuggestion(config, state, symbol, errorMessage));
431     }
432   }
433 
symbolHasSuppressWarningsAnnotation(Symbol symbol, String suppression)434   boolean symbolHasSuppressWarningsAnnotation(Symbol symbol, String suppression) {
435     SuppressWarnings annotation = symbol.getAnnotation(SuppressWarnings.class);
436     if (annotation != null) {
437       for (String s : annotation.value()) {
438         // we need to check for standard suppression here also since we may report initialization
439         // errors outside the normal ErrorProne match* methods
440         if (s.equals(suppression) || allNames.stream().anyMatch(s::equals)) {
441           return true;
442         }
443       }
444     }
445     return false;
446   }
447 
symbolIsExcludedClassSymbol(Symbol symbol)448   private boolean symbolIsExcludedClassSymbol(Symbol symbol) {
449     if (symbol instanceof Symbol.ClassSymbol) {
450       ImmutableSet<String> excludedClassAnnotations = config.getExcludedClassAnnotations();
451       return ((Symbol.ClassSymbol) symbol)
452           .getAnnotationMirrors().stream()
453               .map(anno -> anno.getAnnotationType().toString())
454               .anyMatch(excludedClassAnnotations::contains);
455     }
456     return false;
457   }
458 
getLineNumForElement(Element uninitField, VisitorState state)459   static int getLineNumForElement(Element uninitField, VisitorState state) {
460     Tree tree = getTreesInstance(state).getTree(uninitField);
461     if (tree == null) {
462       throw new RuntimeException(
463           "When getting the line number for uninitialized field, can't get the tree from the element.");
464     }
465     DiagnosticPosition position =
466         (DiagnosticPosition) tree; // Expect Tree to be JCTree and thus implement DiagnosticPosition
467     TreePath path = state.getPath();
468     JCCompilationUnit compilation = (JCCompilationUnit) path.getCompilationUnit();
469     JavaFileObject file = compilation.getSourceFile();
470     DiagnosticSource source = new DiagnosticSource(file, null);
471     return source.getLineNumber(position.getStartPosition());
472   }
473 
474   /**
475    * Generate the message for uninitialized fields, including the line number for fields.
476    *
477    * @param uninitFields the set of uninitialized fields as the type of Element.
478    * @param state the VisitorState object.
479    * @return the error message for uninitialized fields with line numbers.
480    */
errMsgForInitializer(Set<Element> uninitFields, VisitorState state)481   static String errMsgForInitializer(Set<Element> uninitFields, VisitorState state) {
482     StringBuilder message = new StringBuilder("initializer method does not guarantee @NonNull ");
483     Element uninitField;
484     if (uninitFields.size() == 1) {
485       uninitField = uninitFields.iterator().next();
486       message.append("field ");
487       message.append(uninitField.toString());
488       message.append(" (line ");
489       message.append(getLineNumForElement(uninitField, state));
490       message.append(") is initialized");
491     } else {
492       message.append("fields ");
493       Iterator<Element> it = uninitFields.iterator();
494       while (it.hasNext()) {
495         uninitField = it.next();
496         message.append(
497             uninitField.toString() + " (line " + getLineNumForElement(uninitField, state) + ")");
498         if (it.hasNext()) {
499           message.append(", ");
500         } else {
501           message.append(" are initialized");
502         }
503       }
504     }
505     message.append(
506         " along all control-flow paths (remember to check for exceptions or early returns).");
507     return message.toString();
508   }
509 
reportInitErrorOnField(Symbol symbol, VisitorState state, Description.Builder builder)510   void reportInitErrorOnField(Symbol symbol, VisitorState state, Description.Builder builder) {
511     // Check needed here, despite check in hasPathSuppression because initialization
512     // checking happens at the class-level (meaning state.getPath() might not include the
513     // field itself).
514     if (symbolHasSuppressWarningsAnnotation(symbol, INITIALIZATION_CHECK_NAME)) {
515       return;
516     }
517     Tree tree = getTreesInstance(state).getTree(symbol);
518 
519     String fieldName = symbol.toString();
520 
521     if (symbol.enclClass().getNestingKind().isNested()) {
522       String flatName = symbol.enclClass().flatName().toString();
523       int index = flatName.lastIndexOf(".") + 1;
524       fieldName = flatName.substring(index) + "." + fieldName;
525     }
526 
527     if (isStatic(symbol)) {
528       state.reportMatch(
529           createErrorDescription(
530               new ErrorMessage(
531                   FIELD_NO_INIT, "@NonNull static field " + fieldName + " not initialized"),
532               tree,
533               builder,
534               state,
535               symbol));
536     } else {
537       state.reportMatch(
538           createErrorDescription(
539               new ErrorMessage(FIELD_NO_INIT, "@NonNull field " + fieldName + " not initialized"),
540               tree,
541               builder,
542               state,
543               symbol));
544     }
545   }
546 }
547