• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package com.uber.nullaway.handlers.contract;
2 
3 import com.google.common.base.Function;
4 import com.google.errorprone.VisitorState;
5 import com.sun.source.tree.Tree;
6 import com.sun.tools.javac.code.Symbol;
7 import com.uber.nullaway.Config;
8 import com.uber.nullaway.ErrorMessage;
9 import com.uber.nullaway.NullAway;
10 import com.uber.nullaway.NullabilityUtil;
11 import java.util.Set;
12 import java.util.stream.Collectors;
13 import javax.annotation.Nullable;
14 import javax.lang.model.element.AnnotationMirror;
15 import org.checkerframework.nullaway.javacutil.AnnotationUtils;
16 
17 /** An utility class for {@link ContractHandler} and {@link ContractCheckHandler}. */
18 public class ContractUtils {
19 
20   private static final String[] EMPTY_STRING_ARRAY = new String[0];
21 
22   /**
23    * Returns a set of field names excluding their receivers (e.g. "this.a" will be "a")
24    *
25    * @param fieldNames A set of raw class field names.
26    * @return A set of trimmed field names.
27    */
trimReceivers(Set<String> fieldNames)28   public static Set<String> trimReceivers(Set<String> fieldNames) {
29     return fieldNames.stream()
30         .map((Function<String, String>) input -> input.substring(input.lastIndexOf(".") + 1))
31         .collect(Collectors.toSet());
32   }
33 
34   /**
35    * Parses the contract clause and returns the consequent in the contract.
36    *
37    * @param clause The contract clause.
38    * @param tree The AST Node for contract.
39    * @param analysis A reference to the running NullAway analysis.
40    * @param state The current visitor state.
41    * @param callee Symbol for callee.
42    * @return consequent in the contract.
43    */
getConsequent( String clause, Tree tree, NullAway analysis, VisitorState state, Symbol callee)44   static String getConsequent(
45       String clause, Tree tree, NullAway analysis, VisitorState state, Symbol callee) {
46 
47     String[] parts = clause.split("->");
48     if (parts.length != 2) {
49       String message =
50           "Invalid @Contract annotation detected for method "
51               + callee
52               + ". It contains the following uparseable clause: "
53               + clause
54               + "(see https://www.jetbrains.com/help/idea/contract-annotations.html).";
55       state.reportMatch(
56           analysis
57               .getErrorBuilder()
58               .createErrorDescription(
59                   new ErrorMessage(ErrorMessage.MessageTypes.ANNOTATION_VALUE_INVALID, message),
60                   tree,
61                   analysis.buildDescription(tree),
62                   state,
63                   null));
64     }
65     return parts[1].trim();
66   }
67 
68   /**
69    * Parses the contract clause and returns the antecedents in the contract.
70    *
71    * @param clause The contract clause.
72    * @param tree The AST Node for contract.
73    * @param analysis A reference to the running NullAway analysis.
74    * @param state The current visitor state.
75    * @param callee Symbol for callee.
76    * @param numOfArguments Number of arguments in the method associated with the contract.
77    * @return antecedents in the contract.
78    */
getAntecedent( String clause, Tree tree, NullAway analysis, VisitorState state, Symbol callee, int numOfArguments)79   static String[] getAntecedent(
80       String clause,
81       Tree tree,
82       NullAway analysis,
83       VisitorState state,
84       Symbol callee,
85       int numOfArguments) {
86 
87     String[] parts = clause.split("->");
88 
89     String[] antecedent = parts[0].trim().isEmpty() ? new String[0] : parts[0].split(",");
90 
91     if (antecedent.length != numOfArguments) {
92       String message =
93           "Invalid @Contract annotation detected for method "
94               + callee
95               + ". It contains the following uparseable clause: "
96               + clause
97               + " (incorrect number of arguments in the clause's antecedent ["
98               + antecedent.length
99               + "], should be the same as the number of "
100               + "arguments in for the method ["
101               + numOfArguments
102               + "]).";
103       state.reportMatch(
104           analysis
105               .getErrorBuilder()
106               .createErrorDescription(
107                   new ErrorMessage(ErrorMessage.MessageTypes.ANNOTATION_VALUE_INVALID, message),
108                   tree,
109                   analysis.buildDescription(tree),
110                   state,
111                   null));
112     }
113     return antecedent;
114   }
115 
116   /**
117    * Returns the value of a Contract annotation if present on the method.
118    *
119    * @param methodSymbol the method to check for a Contract annotation
120    * @param config the NullAway config
121    * @return the value of a Contract annotation if present, or {@code null} if not present.
122    */
123   @Nullable
getContractString(Symbol.MethodSymbol methodSymbol, Config config)124   static String getContractString(Symbol.MethodSymbol methodSymbol, Config config) {
125     for (AnnotationMirror annotation : methodSymbol.getAnnotationMirrors()) {
126       String name = AnnotationUtils.annotationName(annotation);
127       if (config.isContractAnnotation(name)) {
128         return NullabilityUtil.getAnnotationValue(methodSymbol, name);
129       }
130     }
131     return null;
132   }
133 
getContractClauses(Symbol.MethodSymbol callee, Config config)134   static String[] getContractClauses(Symbol.MethodSymbol callee, Config config) {
135     // Check to see if this method has an @Contract annotation
136     String contractString = getContractString(callee, config);
137     if (contractString != null) {
138       String trimmedContractString = contractString.trim();
139       if (!trimmedContractString.isEmpty()) {
140         return trimmedContractString.split(";");
141       }
142     }
143     return EMPTY_STRING_ARRAY;
144   }
145 }
146