• 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   /**
55    * Produce an empty store.
56    *
57    * @return an empty store
58    */
empty()59   public static NullnessStore empty() {
60     return EMPTY;
61   }
62 
63   /**
64    * Get the nullness for a local variable.
65    *
66    * @param node node representing local variable
67    * @param defaultValue default value if we have no fact
68    * @return fact associated with local
69    */
valueOfLocalVariable(LocalVariableNode node, Nullness defaultValue)70   public Nullness valueOfLocalVariable(LocalVariableNode node, Nullness defaultValue) {
71     return contents.getOrDefault(AccessPath.fromLocal(node), 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     return contents.getOrDefault(path, defaultValue);
88   }
89 
90   /**
91    * Get the nullness of a method call.
92    *
93    * @param node node representing method invocation
94    * @param defaultValue default value if we have no fact
95    * @return fact associated with method invocation
96    */
valueOfMethodCall( MethodInvocationNode node, VisitorState state, Nullness defaultValue, AccessPath.AccessPathContext apContext)97   public Nullness valueOfMethodCall(
98       MethodInvocationNode node,
99       VisitorState state,
100       Nullness defaultValue,
101       AccessPath.AccessPathContext apContext) {
102     AccessPath accessPath = AccessPath.fromMethodCall(node, state, apContext);
103     if (accessPath == null) {
104       return defaultValue;
105     }
106     return contents.getOrDefault(accessPath, defaultValue);
107   }
108 
109   /**
110    * Get all access paths in this store with a particular nullness value.
111    *
112    * @param value a nullness value
113    * @return all access paths in this store that have the given nullness value
114    */
getAccessPathsWithValue(Nullness value)115   public Set<AccessPath> getAccessPathsWithValue(Nullness value) {
116     Set<AccessPath> result = new LinkedHashSet<>();
117     for (Map.Entry<AccessPath, Nullness> entry : contents.entrySet()) {
118       if (value.equals(entry.getValue())) {
119         result.add(entry.getKey());
120       }
121     }
122     return result;
123   }
124 
125   /**
126    * If this store maps an access path {@code p} whose map-get argument is an {@link
127    * IteratorContentsKey} whose variable is {@code iteratorVar}, returns {@code p}. Otherwise,
128    * returns {@code null}.
129    */
130   @Nullable
getMapGetIteratorContentsAccessPath(LocalVariableNode iteratorVar)131   public AccessPath getMapGetIteratorContentsAccessPath(LocalVariableNode iteratorVar) {
132     for (AccessPath accessPath : contents.keySet()) {
133       MapKey mapGetArg = accessPath.getMapGetArg();
134       if (mapGetArg instanceof IteratorContentsKey) {
135         IteratorContentsKey iteratorContentsKey = (IteratorContentsKey) mapGetArg;
136         if (iteratorContentsKey.getIteratorVarElement().equals(iteratorVar.getElement())) {
137           return accessPath;
138         }
139       }
140     }
141     return null;
142   }
143 
144   /**
145    * Gets the {@link Nullness} value of an access path.
146    *
147    * @param accessPath The access path.
148    * @return The {@link Nullness} value of the access path.
149    */
getNullnessOfAccessPath(AccessPath accessPath)150   public Nullness getNullnessOfAccessPath(AccessPath accessPath) {
151     if (contents == null) {
152       return Nullness.NULLABLE;
153     }
154     return contents.getOrDefault(accessPath, Nullness.NULLABLE);
155   }
156 
toBuilder()157   public Builder toBuilder() {
158     return new Builder(this);
159   }
160 
161   @Override
copy()162   public NullnessStore copy() {
163     return this;
164   }
165 
166   @Override
leastUpperBound(NullnessStore other)167   public NullnessStore leastUpperBound(NullnessStore other) {
168     NullnessStore.Builder result = NullnessStore.empty().toBuilder();
169     for (AccessPath ap : intersection(contents.keySet(), other.contents.keySet())) {
170       Nullness apContents = contents.get(ap);
171       if (apContents == null) {
172         throw new RuntimeException("null contents for " + ap);
173       }
174       Nullness otherAPContents = other.contents.get(ap);
175       if (otherAPContents == null) {
176         throw new RuntimeException("null other contents for " + ap);
177       }
178       result.contents.put(ap, apContents.leastUpperBound(otherAPContents));
179     }
180     return result.build();
181   }
182 
183   @Override
widenedUpperBound(NullnessStore vNullnessStore)184   public NullnessStore widenedUpperBound(NullnessStore vNullnessStore) {
185     return leastUpperBound(vNullnessStore);
186   }
187 
188   @Override
equals(@ullable Object o)189   public boolean equals(@Nullable Object o) {
190     if (!(o instanceof NullnessStore)) {
191       return false;
192     }
193     NullnessStore other = (NullnessStore) o;
194     return contents.equals(other.contents);
195   }
196 
197   @Override
hashCode()198   public int hashCode() {
199     return contents.hashCode();
200   }
201 
202   @Override
toString()203   public String toString() {
204     return contents.toString();
205   }
206 
207   @Override
canAlias(JavaExpression a, JavaExpression b)208   public boolean canAlias(JavaExpression a, JavaExpression b) {
209     return true;
210   }
211 
212   @Override
visualize(CFGVisualizer<?, NullnessStore, ?> viz)213   public String visualize(CFGVisualizer<?, NullnessStore, ?> viz) {
214     throw new UnsupportedOperationException();
215   }
216 
217   /**
218    * Takes the Access Paths rooted at specific locals in this NullnessStore and translates them to
219    * paths rooted at different/renamed local variables.
220    *
221    * <p>This method is used to patch-around the paths inter-procedurally when handling certain
222    * libraries. For example, by {@code handlers.RxNullabilityPropagator} to translate access paths
223    * relative to the argument of a filter(...) method, to paths relative to the argument of a
224    * map(...) method in a filter(...).map(...) chain pattern.
225    *
226    * @param localVarTranslations A map from local variable nodes to local variable nodes, indicating
227    *     the desired re-rooting / re-naming.
228    * @return A store containing only those access paths in {@code this} which are relative to
229    *     variables in the domain of {@code localVarTranslations}, with each access path re-rooted to
230    *     be relative to the corresponding local variable in the co-domain of the map.
231    */
uprootAccessPaths( Map<LocalVariableNode, LocalVariableNode> localVarTranslations)232   public NullnessStore uprootAccessPaths(
233       Map<LocalVariableNode, LocalVariableNode> localVarTranslations) {
234     NullnessStore.Builder nullnessBuilder = NullnessStore.empty().toBuilder();
235     for (AccessPath ap : contents.keySet()) {
236       Element element = ap.getRoot();
237       if (element == null) {
238         // Access path is rooted at the receiver, so we don't need to uproot it
239         continue;
240       }
241       for (LocalVariableNode fromVar : localVarTranslations.keySet()) {
242         if (element.equals(fromVar.getElement())) {
243           LocalVariableNode toVar = localVarTranslations.get(fromVar);
244           AccessPath newAP = AccessPath.switchRoot(ap, toVar.getElement());
245           nullnessBuilder.setInformation(newAP, contents.get(ap));
246         }
247       }
248     }
249     return nullnessBuilder.build();
250   }
251 
252   /**
253    * Get access paths matching a predicate.
254    *
255    * @param pred predicate over {@link AccessPath}s
256    * @return NullnessStore containing only AccessPaths that pass the predicate
257    */
filterAccessPaths(Predicate<AccessPath> pred)258   public NullnessStore filterAccessPaths(Predicate<AccessPath> pred) {
259     return new NullnessStore(
260         contents.entrySet().stream()
261             .filter(e -> pred.test(e.getKey()))
262             .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)));
263   }
264 
265   /** class for building up instances of the store. */
266   public static final class Builder {
267     private final Map<AccessPath, Nullness> contents;
268 
Builder(NullnessStore prototype)269     Builder(NullnessStore prototype) {
270 
271       contents = new HashMap<>(prototype.contents);
272     }
273 
274     /**
275      * Sets the value for the given variable. {@code element} must come from a call to {@link
276      * LocalVariableNode#getElement()} or {@link
277      * org.checkerframework.nullaway.javacutil.TreeUtils#elementFromDeclaration} ({@link
278      * org.checkerframework.nullaway.dataflow.cfg.node.VariableDeclarationNode#getTree()}).
279      *
280      * @param ap relevant access path
281      * @param value fact for access path
282      * @return the new builder
283      */
setInformation(AccessPath ap, Nullness value)284     public NullnessStore.Builder setInformation(AccessPath ap, Nullness value) {
285       contents.put(checkNotNull(ap), checkNotNull(value));
286       return this;
287     }
288 
289     /**
290      * Construct the immutable NullnessStore instance.
291      *
292      * @return a store constructed from everything added to the builder
293      */
build()294     public NullnessStore build() {
295       return new NullnessStore(contents);
296     }
297   }
298 }
299