• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2017 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.handlers.contract;
24 
25 import static com.uber.nullaway.handlers.contract.ContractUtils.getAntecedent;
26 import static com.uber.nullaway.handlers.contract.ContractUtils.getConsequent;
27 
28 import com.google.common.base.Preconditions;
29 import com.google.errorprone.VisitorState;
30 import com.google.errorprone.util.ASTHelpers;
31 import com.sun.source.tree.MethodTree;
32 import com.sun.source.tree.ReturnTree;
33 import com.sun.source.util.TreePath;
34 import com.sun.source.util.TreePathScanner;
35 import com.sun.tools.javac.code.Symbol;
36 import com.uber.nullaway.Config;
37 import com.uber.nullaway.ErrorMessage;
38 import com.uber.nullaway.NullAway;
39 import com.uber.nullaway.Nullness;
40 import com.uber.nullaway.handlers.BaseNoOpHandler;
41 
42 /**
43  * This Handler parses the jetbrains @Contract annotation and tries to check if the contract is
44  * followed.
45  *
46  * <p>Currently, it supports the case when there is only one clause in the contract. The clause of
47  * the form in which all the elements of the antecedent are either of "_", "null" or "!null", and
48  * the consequent is "!null" is supported. The handler checks and warns under the conditions of the
49  * antecedent if the consequent is "!null" and there is a return statement with "nullable" or "null"
50  * expression.
51  */
52 public class ContractCheckHandler extends BaseNoOpHandler {
53 
54   private final Config config;
55 
ContractCheckHandler(Config config)56   public ContractCheckHandler(Config config) {
57     this.config = config;
58   }
59 
60   @Override
onMatchMethod( NullAway analysis, MethodTree tree, VisitorState state, Symbol.MethodSymbol methodSymbol)61   public void onMatchMethod(
62       NullAway analysis, MethodTree tree, VisitorState state, Symbol.MethodSymbol methodSymbol) {
63     Symbol.MethodSymbol callee = ASTHelpers.getSymbol(tree);
64     Preconditions.checkNotNull(callee);
65     // Check to see if this method has an @Contract annotation
66     String contractString = ContractUtils.getContractString(callee, config);
67     if (contractString != null) {
68       // Found a contract, lets parse it.
69       String[] clauses = contractString.split(";");
70       if (clauses.length != 1) {
71         return;
72       }
73 
74       String clause = clauses[0];
75       String[] antecedent =
76           getAntecedent(clause, tree, analysis, state, callee, tree.getParameters().size());
77       String consequent = getConsequent(clause, tree, analysis, state, callee);
78 
79       boolean supported = true;
80 
81       for (int i = 0; i < antecedent.length; ++i) {
82         String valueConstraint = antecedent[i].trim();
83         if (!(valueConstraint.equals("_")
84             || valueConstraint.equals("!null")
85             || valueConstraint.equals("null"))) {
86           supported = false;
87         }
88       }
89 
90       if (!consequent.equals("!null")) {
91         supported = false;
92       }
93 
94       if (!supported) {
95         return;
96       }
97 
98       // we scan the method tree for the return nodes and check the contract
99       new TreePathScanner<Void, Void>() {
100         @Override
101         public Void visitReturn(ReturnTree returnTree, Void unused) {
102 
103           final VisitorState returnState = state.withPath(getCurrentPath());
104           final Nullness nullness =
105               analysis
106                   .getNullnessAnalysis(returnState)
107                   .getNullnessForContractDataflow(
108                       new TreePath(returnState.getPath(), returnTree.getExpression()),
109                       returnState.context);
110 
111           if (nullness == Nullness.NULLABLE || nullness == Nullness.NULL) {
112 
113             String errorMessage;
114 
115             // used for error message
116             int nonNullAntecedentCount = 0;
117             int nonNullAntecedentPosition = -1;
118 
119             for (int i = 0; i < antecedent.length; ++i) {
120               String valueConstraint = antecedent[i].trim();
121 
122               if (valueConstraint.equals("!null")) {
123                 nonNullAntecedentCount += 1;
124                 nonNullAntecedentPosition = i;
125               }
126             }
127 
128             if (nonNullAntecedentCount == 1) {
129 
130               errorMessage =
131                   "Method "
132                       + callee.name
133                       + " has @Contract("
134                       + contractString
135                       + "), but this appears to be violated, as a @Nullable value may be returned when parameter "
136                       + tree.getParameters().get(nonNullAntecedentPosition).getName()
137                       + " is non-null.";
138             } else {
139               errorMessage =
140                   "Method "
141                       + callee.name
142                       + " has @Contract("
143                       + contractString
144                       + "), but this appears to be violated, as a @Nullable value may be returned "
145                       + "when the contract preconditions are true.";
146             }
147 
148             returnState.reportMatch(
149                 analysis
150                     .getErrorBuilder()
151                     .createErrorDescription(
152                         new ErrorMessage(
153                             ErrorMessage.MessageTypes.ANNOTATION_VALUE_INVALID, errorMessage),
154                         returnTree,
155                         analysis.buildDescription(returnTree),
156                         returnState));
157           }
158           return super.visitReturn(returnTree, unused);
159         }
160       }.scan(state.getPath(), null);
161     }
162   }
163 }
164