• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2014 Google Inc. All Rights Reserved.
3  *
4  * Modifications copyright (C) 2017 Uber Technologies, Inc.
5  *
6  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
7  * in compliance with the License. You may obtain a copy of the License at
8  *
9  * http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software distributed under the License
12  * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
13  * or implied. See the License for the specific language governing permissions and limitations under
14  * the License.
15  */
16 
17 package com.uber.nullaway.dataflow;
18 
19 import static com.google.common.base.Preconditions.checkNotNull;
20 import static com.google.common.collect.Sets.intersection;
21 
22 import com.google.common.collect.ImmutableMap;
23 import com.google.errorprone.VisitorState;
24 import com.uber.nullaway.Nullness;
25 import com.uber.nullaway.dataflow.AccessPath.IteratorContentsKey;
26 import java.util.HashMap;
27 import java.util.LinkedHashSet;
28 import java.util.Map;
29 import java.util.Set;
30 import java.util.function.Predicate;
31 import java.util.stream.Collectors;
32 import javax.annotation.Nullable;
33 import javax.lang.model.element.Element;
34 import org.checkerframework.nullaway.dataflow.analysis.Store;
35 import org.checkerframework.nullaway.dataflow.cfg.node.FieldAccessNode;
36 import org.checkerframework.nullaway.dataflow.cfg.node.LocalVariableNode;
37 import org.checkerframework.nullaway.dataflow.cfg.node.MethodInvocationNode;
38 import org.checkerframework.nullaway.dataflow.cfg.visualize.CFGVisualizer;
39 import org.checkerframework.nullaway.dataflow.expression.JavaExpression;
40 
41 /**
42  * Highly based on {@link com.google.errorprone.dataflow.LocalStore}, but for {@link AccessPath}s.
43  */
44 public class NullnessStore implements Store<NullnessStore> {
45 
46   private static final NullnessStore EMPTY = new NullnessStore(ImmutableMap.of());
47 
48   private final ImmutableMap<AccessPath, Nullness> contents;
49 
NullnessStore(Map<AccessPath, Nullness> contents)50   private NullnessStore(Map<AccessPath, Nullness> contents) {
51     this.contents = ImmutableMap.copyOf(contents);
52   }
53   /**
54    * Produce an empty store.
55    *
56    * @return an empty store
57    */
empty()58   public static NullnessStore empty() {
59     return EMPTY;
60   }
61 
62   /**
63    * Get the nullness for a local variable.
64    *
65    * @param node node representing local variable
66    * @param defaultValue default value if we have no fact
67    * @return fact associated with local
68    */
valueOfLocalVariable(LocalVariableNode node, Nullness defaultValue)69   public Nullness valueOfLocalVariable(LocalVariableNode node, Nullness defaultValue) {
70     Nullness result = contents.get(AccessPath.fromLocal(node));
71     return result != null ? result : defaultValue;
72   }
73 
74   /**
75    * Get the nullness of a field.
76    *
77    * @param node node representing field access
78    * @param defaultValue default value if we have no fact
79    * @return fact associated with field access
80    */
valueOfField( FieldAccessNode node, Nullness defaultValue, AccessPath.AccessPathContext apContext)81   public Nullness valueOfField(
82       FieldAccessNode node, Nullness defaultValue, AccessPath.AccessPathContext apContext) {
83     AccessPath path = AccessPath.fromFieldAccess(node, apContext);
84     if (path == null) {
85       return defaultValue;
86     }
87     Nullness result = contents.get(path);
88     return result != null ? result : defaultValue;
89   }
90 
91   /**
92    * Get the nullness of a method call.
93    *
94    * @param node node representing method invocation
95    * @param defaultValue default value if we have no fact
96    * @return fact associated with method invocation
97    */
valueOfMethodCall( MethodInvocationNode node, VisitorState state, Nullness defaultValue, AccessPath.AccessPathContext apContext)98   public Nullness valueOfMethodCall(
99       MethodInvocationNode node,
100       VisitorState state,
101       Nullness defaultValue,
102       AccessPath.AccessPathContext apContext) {
103     AccessPath accessPath = AccessPath.fromMethodCall(node, state, apContext);
104     if (accessPath == null) {
105       return defaultValue;
106     }
107     Nullness result = contents.get(accessPath);
108     return result != null ? result : defaultValue;
109   }
110 
111   /**
112    * Get all access paths in this store with a particular nullness value.
113    *
114    * @param value a nullness value
115    * @return all access paths in this store that have the given nullness value
116    */
getAccessPathsWithValue(Nullness value)117   public Set<AccessPath> getAccessPathsWithValue(Nullness value) {
118     Set<AccessPath> result = new LinkedHashSet<>();
119     for (Map.Entry<AccessPath, Nullness> entry : contents.entrySet()) {
120       if (value.equals(entry.getValue())) {
121         result.add(entry.getKey());
122       }
123     }
124     return result;
125   }
126 
127   /**
128    * If this store maps an access path {@code p} whose map-get argument is an {@link
129    * IteratorContentsKey} whose variable is {@code iteratorVar}, returns {@code p}. Otherwise,
130    * returns {@code null}.
131    */
132   @Nullable
getMapGetIteratorContentsAccessPath(LocalVariableNode iteratorVar)133   public AccessPath getMapGetIteratorContentsAccessPath(LocalVariableNode iteratorVar) {
134     for (AccessPath accessPath : contents.keySet()) {
135       MapKey mapGetArg = accessPath.getMapGetArg();
136       if (mapGetArg instanceof IteratorContentsKey) {
137         IteratorContentsKey iteratorContentsKey = (IteratorContentsKey) mapGetArg;
138         if (iteratorContentsKey.getIteratorVarElement().equals(iteratorVar.getElement())) {
139           return accessPath;
140         }
141       }
142     }
143     return null;
144   }
145   /**
146    * Gets the {@link Nullness} value of an access path.
147    *
148    * @param accessPath The access path.
149    * @return The {@link Nullness} value of the access path.
150    */
getNullnessOfAccessPath(AccessPath accessPath)151   public Nullness getNullnessOfAccessPath(AccessPath accessPath) {
152     if (contents == null) {
153       return Nullness.NULLABLE;
154     }
155     Nullness nullness = contents.get(accessPath);
156     return (nullness == null) ? Nullness.NULLABLE : nullness;
157   }
158 
toBuilder()159   public Builder toBuilder() {
160     return new Builder(this);
161   }
162 
163   @Override
copy()164   public NullnessStore copy() {
165     return this;
166   }
167 
168   @Override
leastUpperBound(NullnessStore other)169   public NullnessStore leastUpperBound(NullnessStore other) {
170     NullnessStore.Builder result = NullnessStore.empty().toBuilder();
171     for (AccessPath ap : intersection(contents.keySet(), other.contents.keySet())) {
172       Nullness apContents = contents.get(ap);
173       if (apContents == null) {
174         throw new RuntimeException("null contents for " + ap);
175       }
176       Nullness otherAPContents = other.contents.get(ap);
177       if (otherAPContents == null) {
178         throw new RuntimeException("null other contents for " + ap);
179       }
180       result.contents.put(ap, apContents.leastUpperBound(otherAPContents));
181     }
182     return result.build();
183   }
184 
185   @Override
widenedUpperBound(NullnessStore vNullnessStore)186   public NullnessStore widenedUpperBound(NullnessStore vNullnessStore) {
187     return leastUpperBound(vNullnessStore);
188   }
189 
190   @Override
equals(Object o)191   public boolean equals(Object o) {
192     if (!(o instanceof NullnessStore)) {
193       return false;
194     }
195     NullnessStore other = (NullnessStore) o;
196     return contents.equals(other.contents);
197   }
198 
199   @Override
hashCode()200   public int hashCode() {
201     return contents.hashCode();
202   }
203 
204   @Override
toString()205   public String toString() {
206     return contents.toString();
207   }
208 
209   @Override
canAlias(JavaExpression a, JavaExpression b)210   public boolean canAlias(JavaExpression a, JavaExpression b) {
211     return true;
212   }
213 
214   @Override
visualize(CFGVisualizer<?, NullnessStore, ?> viz)215   public String visualize(CFGVisualizer<?, NullnessStore, ?> viz) {
216     throw new UnsupportedOperationException();
217   }
218 
219   /**
220    * Takes the Access Paths rooted at specific locals in this NullnessStore and translates them to
221    * paths rooted at different/renamed local variables.
222    *
223    * <p>This method is used to patch-around the paths inter-procedurally when handling certain
224    * libraries. For example, by {@code handlers.RxNullabilityPropagator} to translate access paths
225    * relative to the argument of a filter(...) method, to paths relative to the argument of a
226    * map(...) method in a filter(...).map(...) chain pattern.
227    *
228    * @param localVarTranslations A map from local variable nodes to local variable nodes, indicating
229    *     the desired re-rooting / re-naming.
230    * @return A store containing only those access paths in {@code this} which are relative to
231    *     variables in the domain of {@code localVarTranslations}, with each access path re-rooted to
232    *     be relative to the corresponding local variable in the co-domain of the map.
233    */
uprootAccessPaths( Map<LocalVariableNode, LocalVariableNode> localVarTranslations)234   public NullnessStore uprootAccessPaths(
235       Map<LocalVariableNode, LocalVariableNode> localVarTranslations) {
236     NullnessStore.Builder nullnessBuilder = NullnessStore.empty().toBuilder();
237     for (AccessPath ap : contents.keySet()) {
238       if (ap.getRoot().isReceiver()) {
239         continue;
240       }
241       Element varElement = ap.getRoot().getVarElement();
242       for (LocalVariableNode fromVar : localVarTranslations.keySet()) {
243         if (varElement.equals(fromVar.getElement())) {
244           LocalVariableNode toVar = localVarTranslations.get(fromVar);
245           AccessPath newAP =
246               new AccessPath(new AccessPath.Root(toVar.getElement()), ap.getElements());
247           nullnessBuilder.setInformation(newAP, contents.get(ap));
248         }
249       }
250     }
251     return nullnessBuilder.build();
252   }
253 
254   /**
255    * Get access paths matching a predicate.
256    *
257    * @param pred predicate over {@link AccessPath}s
258    * @return NullnessStore containing only AccessPaths that pass the predicate
259    */
filterAccessPaths(Predicate<AccessPath> pred)260   public NullnessStore filterAccessPaths(Predicate<AccessPath> pred) {
261     return new NullnessStore(
262         contents
263             .entrySet()
264             .stream()
265             .filter(e -> pred.test(e.getKey()))
266             .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)));
267   }
268 
269   /** class for building up instances of the store. */
270   public static final class Builder {
271     private final Map<AccessPath, Nullness> contents;
272 
Builder(NullnessStore prototype)273     Builder(NullnessStore prototype) {
274 
275       contents = new HashMap<>(prototype.contents);
276     }
277 
278     /**
279      * Sets the value for the given variable. {@code element} must come from a call to {@link
280      * LocalVariableNode#getElement()} or {@link
281      * org.checkerframework.nullaway.javacutil.TreeUtils#elementFromDeclaration} ({@link
282      * org.checkerframework.nullaway.dataflow.cfg.node.VariableDeclarationNode#getTree()}).
283      *
284      * @param ap relevant access path
285      * @param value fact for access path
286      * @return the new builder
287      */
setInformation(AccessPath ap, Nullness value)288     public NullnessStore.Builder setInformation(AccessPath ap, Nullness value) {
289       contents.put(checkNotNull(ap), checkNotNull(value));
290       return this;
291     }
292 
293     /**
294      * Construct the immutable NullnessStore instance.
295      *
296      * @return a store constructed from everything added to the builder
297      */
build()298     public NullnessStore build() {
299       return new NullnessStore(contents);
300     }
301   }
302 }
303