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