• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2018 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 package com.uber.nullaway.handlers;
23 
24 import static com.uber.nullaway.ASTHelpersBackports.getEnclosedElements;
25 
26 import com.google.errorprone.VisitorState;
27 import com.google.errorprone.suppliers.Supplier;
28 import com.google.errorprone.suppliers.Suppliers;
29 import com.sun.source.tree.ClassTree;
30 import com.sun.tools.javac.code.Symbol;
31 import com.sun.tools.javac.code.Type;
32 import com.sun.tools.javac.code.Types;
33 import com.uber.nullaway.NullAway;
34 import com.uber.nullaway.Nullness;
35 import com.uber.nullaway.annotations.Initializer;
36 import com.uber.nullaway.dataflow.AccessPath;
37 import com.uber.nullaway.dataflow.AccessPathNullnessPropagation;
38 import java.util.Objects;
39 import java.util.Optional;
40 import javax.annotation.Nullable;
41 import javax.lang.model.element.Element;
42 import javax.lang.model.element.ElementKind;
43 import org.checkerframework.nullaway.dataflow.cfg.node.MethodInvocationNode;
44 import org.checkerframework.nullaway.dataflow.cfg.node.Node;
45 
46 /**
47  * Handler to better handle {@code isSetXXXX()} methods in code generated by Apache Thrift. With
48  * this handler, we learn appropriate nullability facts about the relevant property from these
49  * calls.
50  */
51 public class ApacheThriftIsSetHandler extends BaseNoOpHandler {
52 
53   private static final String TBASE_NAME = "org.apache.thrift.TBase";
54 
55   private static final Supplier<Type> TBASE_TYPE_SUPPLIER = Suppliers.typeFromString(TBASE_NAME);
56 
57   private Optional<Type> tbaseType;
58 
59   /**
60    * This method is annotated {@code @Initializer} since it will be invoked when the first class is
61    * processed, before any other handler methods
62    */
63   @Initializer
64   @Override
onMatchTopLevelClass( NullAway analysis, ClassTree tree, VisitorState state, Symbol.ClassSymbol classSymbol)65   public void onMatchTopLevelClass(
66       NullAway analysis, ClassTree tree, VisitorState state, Symbol.ClassSymbol classSymbol) {
67     if (tbaseType == null) {
68       tbaseType =
69           Optional.ofNullable(TBASE_TYPE_SUPPLIER.get(state)).map(state.getTypes()::erasure);
70     }
71   }
72 
73   @Override
onDataflowVisitMethodInvocation( MethodInvocationNode node, Symbol.MethodSymbol symbol, VisitorState state, AccessPath.AccessPathContext apContext, AccessPathNullnessPropagation.SubNodeValues inputs, AccessPathNullnessPropagation.Updates thenUpdates, AccessPathNullnessPropagation.Updates elseUpdates, AccessPathNullnessPropagation.Updates bothUpdates)74   public NullnessHint onDataflowVisitMethodInvocation(
75       MethodInvocationNode node,
76       Symbol.MethodSymbol symbol,
77       VisitorState state,
78       AccessPath.AccessPathContext apContext,
79       AccessPathNullnessPropagation.SubNodeValues inputs,
80       AccessPathNullnessPropagation.Updates thenUpdates,
81       AccessPathNullnessPropagation.Updates elseUpdates,
82       AccessPathNullnessPropagation.Updates bothUpdates) {
83     if (thriftIsSetCall(symbol, state.getTypes())) {
84       String methodName = symbol.getSimpleName().toString();
85       // remove "isSet"
86       String capPropName = methodName.substring(5);
87       if (capPropName.length() > 0) {
88         // build access paths for the getter and the field access, and
89         // make them nonnull in the thenUpdates
90         FieldAndGetterElements fieldAndGetter = getFieldAndGetterForProperty(symbol, capPropName);
91         Node base = node.getTarget().getReceiver();
92         updateNonNullAPsForElement(thenUpdates, fieldAndGetter.fieldElem, base, apContext);
93         updateNonNullAPsForElement(thenUpdates, fieldAndGetter.getterElem, base, apContext);
94       }
95     }
96     return NullnessHint.UNKNOWN;
97   }
98 
updateNonNullAPsForElement( AccessPathNullnessPropagation.Updates updates, @Nullable Element elem, Node base, AccessPath.AccessPathContext apContext)99   private void updateNonNullAPsForElement(
100       AccessPathNullnessPropagation.Updates updates,
101       @Nullable Element elem,
102       Node base,
103       AccessPath.AccessPathContext apContext) {
104     if (elem != null) {
105       AccessPath ap = AccessPath.fromBaseAndElement(base, elem, apContext);
106       if (ap != null) {
107         updates.set(ap, Nullness.NONNULL);
108       }
109     }
110   }
111 
112   private static final class FieldAndGetterElements {
113 
114     @Nullable final Element fieldElem;
115 
116     @Nullable final Element getterElem;
117 
FieldAndGetterElements(@ullable Element fieldElem, @Nullable Element getterElem)118     public FieldAndGetterElements(@Nullable Element fieldElem, @Nullable Element getterElem) {
119       this.fieldElem = fieldElem;
120       this.getterElem = getterElem;
121     }
122 
123     @Override
equals(Object o)124     public boolean equals(Object o) {
125       if (this == o) {
126         return true;
127       }
128       if (o == null || getClass() != o.getClass()) {
129         return false;
130       }
131       FieldAndGetterElements that = (FieldAndGetterElements) o;
132       return Objects.equals(fieldElem, that.fieldElem)
133           && Objects.equals(getterElem, that.getterElem);
134     }
135 
136     @Override
hashCode()137     public int hashCode() {
138       return Objects.hash(fieldElem, getterElem);
139     }
140   }
141 
142   /**
143    * Returns the field (if it exists and is visible) and the getter for a property. If the field is
144    * not available, returns {@code null}.
145    */
getFieldAndGetterForProperty( Symbol.MethodSymbol symbol, String capPropName)146   private FieldAndGetterElements getFieldAndGetterForProperty(
147       Symbol.MethodSymbol symbol, String capPropName) {
148     Element field = null;
149     Element getter = null;
150     String fieldName = decapitalize(capPropName);
151     String getterName = "get" + capPropName;
152     for (Symbol elem : getEnclosedElements(symbol.owner)) {
153       if (elem.getKind().isField() && elem.getSimpleName().toString().equals(fieldName)) {
154         if (field != null) {
155           throw new RuntimeException("already found field " + fieldName);
156         }
157         field = elem;
158       } else if (elem.getKind().equals(ElementKind.METHOD)
159           && elem.getSimpleName().toString().equals(getterName)) {
160         if (getter != null) {
161           throw new RuntimeException("already found getter " + getterName);
162         }
163         getter = elem;
164       }
165     }
166     if (field != null && field.asType().getKind().isPrimitive()) {
167       // ignore primitive properties
168       return new FieldAndGetterElements(null, null);
169     }
170     return new FieldAndGetterElements(field, getter);
171   }
172 
decapitalize(String str)173   private static String decapitalize(String str) {
174     // assumes str is non-null and non-empty
175     char c[] = str.toCharArray();
176     c[0] = Character.toLowerCase(c[0]);
177     return new String(c);
178   }
179 
thriftIsSetCall(Symbol.MethodSymbol symbol, Types types)180   private boolean thriftIsSetCall(Symbol.MethodSymbol symbol, Types types) {
181     // noinspection ConstantConditions
182     return tbaseType.isPresent()
183         && symbol.getSimpleName().toString().startsWith("isSet")
184         // weeds out the isSet() method in TBase itself
185         && symbol.getParameters().length() == 0
186         && types.isSubtype(symbol.owner.type, tbaseType.get());
187   }
188 }
189