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"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, software 13 * distributed under the License is distributed on an "AS IS" BASIS, 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 * See the License for the specific language governing permissions and 16 * limitations under the License. 17 */ 18 19 package com.uber.nullaway.dataflow; 20 21 import com.google.common.base.Preconditions; 22 import com.google.common.collect.ImmutableList; 23 import com.google.errorprone.VisitorState; 24 import com.sun.source.tree.Tree; 25 import com.sun.source.util.TreePath; 26 import com.sun.tools.javac.util.Context; 27 import com.uber.nullaway.Config; 28 import com.uber.nullaway.Nullness; 29 import com.uber.nullaway.handlers.Handler; 30 import com.uber.nullaway.handlers.contract.ContractNullnessStoreInitializer; 31 import java.util.Collections; 32 import java.util.LinkedHashSet; 33 import java.util.Set; 34 import java.util.function.Predicate; 35 import javax.annotation.Nullable; 36 import javax.lang.model.element.Element; 37 import javax.lang.model.element.ElementKind; 38 import javax.lang.model.element.VariableElement; 39 import org.checkerframework.nullaway.dataflow.analysis.AnalysisResult; 40 import org.checkerframework.nullaway.dataflow.cfg.node.MethodAccessNode; 41 import org.checkerframework.nullaway.dataflow.cfg.node.MethodInvocationNode; 42 import org.checkerframework.nullaway.dataflow.cfg.node.Node; 43 44 /** 45 * API to our nullness dataflow analysis for access paths. 46 * 47 * <p>Based on code from Error Prone; see {@link NullnessAnalysis} 48 */ 49 public final class AccessPathNullnessAnalysis { 50 51 private static final Context.Key<AccessPathNullnessAnalysis> FIELD_NULLNESS_ANALYSIS_KEY = 52 new Context.Key<>(); 53 54 private final AccessPath.AccessPathContext apContext; 55 56 private final AccessPathNullnessPropagation nullnessPropagation; 57 58 private final DataFlow dataFlow; 59 60 @Nullable private AccessPathNullnessPropagation contractNullnessPropagation; 61 62 // Use #instance to instantiate AccessPathNullnessAnalysis( Predicate<MethodInvocationNode> methodReturnsNonNull, VisitorState state, Config config, Handler handler)63 private AccessPathNullnessAnalysis( 64 Predicate<MethodInvocationNode> methodReturnsNonNull, 65 VisitorState state, 66 Config config, 67 Handler handler) { 68 apContext = 69 AccessPath.AccessPathContext.builder() 70 .setImmutableTypes(handler.onRegisterImmutableTypes()) 71 .build(); 72 this.nullnessPropagation = 73 new AccessPathNullnessPropagation( 74 Nullness.NONNULL, 75 methodReturnsNonNull, 76 state, 77 apContext, 78 config, 79 handler, 80 new CoreNullnessStoreInitializer()); 81 this.dataFlow = new DataFlow(config.assertsEnabled()); 82 83 if (config.checkContracts()) { 84 this.contractNullnessPropagation = 85 new AccessPathNullnessPropagation( 86 Nullness.NONNULL, 87 methodReturnsNonNull, 88 state, 89 apContext, 90 config, 91 handler, 92 new ContractNullnessStoreInitializer()); 93 } 94 } 95 96 /** 97 * Get the per-Javac instance of the analysis. 98 * 99 * @param state visitor state for the compilation 100 * @param methodReturnsNonNull predicate determining whether a method is assumed to return NonNull 101 * value 102 * @param config analysis config 103 * @return instance of the analysis 104 */ instance( VisitorState state, Predicate<MethodInvocationNode> methodReturnsNonNull, Config config, Handler handler)105 public static AccessPathNullnessAnalysis instance( 106 VisitorState state, 107 Predicate<MethodInvocationNode> methodReturnsNonNull, 108 Config config, 109 Handler handler) { 110 Context context = state.context; 111 AccessPathNullnessAnalysis instance = context.get(FIELD_NULLNESS_ANALYSIS_KEY); 112 if (instance == null) { 113 instance = new AccessPathNullnessAnalysis(methodReturnsNonNull, state, config, handler); 114 context.put(FIELD_NULLNESS_ANALYSIS_KEY, instance); 115 } 116 return instance; 117 } 118 119 /** 120 * Get an expression's nullness info. 121 * 122 * @param exprPath tree path of expression 123 * @param context Javac context 124 * @return nullness info for expression, from dataflow 125 */ 126 @Nullable getNullness(TreePath exprPath, Context context)127 public Nullness getNullness(TreePath exprPath, Context context) { 128 return dataFlow.expressionDataflow(exprPath, context, nullnessPropagation); 129 } 130 131 /** 132 * Gets nullness info for expression from the dataflow analysis, for the case of checking 133 * contracts 134 * 135 * @param exprPath tree path of expression 136 * @param context Javac context 137 * @return nullness info for expression, from dataflow in case contract check 138 */ 139 @Nullable getNullnessForContractDataflow(TreePath exprPath, Context context)140 public Nullness getNullnessForContractDataflow(TreePath exprPath, Context context) { 141 return dataFlow.expressionDataflow(exprPath, context, contractNullnessPropagation); 142 } 143 144 /** 145 * Get the fields that are guaranteed to be nonnull after a method or initializer block. 146 * 147 * @param path tree path of method, or initializer block 148 * @param context Javac context 149 * @return fields guaranteed to be nonnull at exit of method (or initializer block) 150 */ getNonnullFieldsOfReceiverAtExit(TreePath path, Context context)151 public Set<Element> getNonnullFieldsOfReceiverAtExit(TreePath path, Context context) { 152 NullnessStore nullnessResult = dataFlow.finalResult(path, context, nullnessPropagation); 153 if (nullnessResult == null) { 154 // this case can occur if the method always throws an exception 155 // be conservative and say nothing is initialized 156 return Collections.emptySet(); 157 } 158 return getNonnullReceiverFields(nullnessResult); 159 } 160 getNonnullReceiverFields(NullnessStore nullnessResult)161 private Set<Element> getNonnullReceiverFields(NullnessStore nullnessResult) { 162 Set<AccessPath> nonnullAccessPaths = nullnessResult.getAccessPathsWithValue(Nullness.NONNULL); 163 Set<Element> result = new LinkedHashSet<>(); 164 for (AccessPath ap : nonnullAccessPaths) { 165 if (ap.getRoot().isReceiver()) { 166 ImmutableList<AccessPathElement> elements = ap.getElements(); 167 if (elements.size() == 1) { 168 Element elem = elements.get(0).getJavaElement(); 169 if (elem.getKind().equals(ElementKind.FIELD)) { 170 result.add(elem); 171 } 172 } 173 } 174 } 175 return result; 176 } 177 178 /** 179 * Get the instance fields that are guaranteed to be nonnull before the current expression. 180 * 181 * @param path tree path of some expression 182 * @param context Javac context 183 * @return fields of receiver guaranteed to be nonnull before expression is evaluated 184 */ getNonnullFieldsOfReceiverBefore(TreePath path, Context context)185 public Set<Element> getNonnullFieldsOfReceiverBefore(TreePath path, Context context) { 186 NullnessStore store = dataFlow.resultBeforeExpr(path, context, nullnessPropagation); 187 if (store == null) { 188 return Collections.emptySet(); 189 } 190 return getNonnullReceiverFields(store); 191 } 192 193 /** 194 * Get the static fields that are guaranteed to be nonnull before the current expression. 195 * 196 * @param path tree path of some expression 197 * @param context Javac context 198 * @return static fields guaranteed to be nonnull before expression is evaluated 199 */ getNonnullStaticFieldsBefore(TreePath path, Context context)200 public Set<Element> getNonnullStaticFieldsBefore(TreePath path, Context context) { 201 NullnessStore store = dataFlow.resultBeforeExpr(path, context, nullnessPropagation); 202 if (store == null) { 203 return Collections.emptySet(); 204 } 205 return getNonnullStaticFields(store); 206 } 207 208 /** 209 * Get nullness info for local variables before some node 210 * 211 * @param path tree path to some AST node within a method / lambda / initializer 212 * @param state visitor state 213 * @return nullness info for local variables just before the node 214 */ getNullnessInfoBeforeNewContext( TreePath path, VisitorState state, Handler handler)215 public NullnessStore getNullnessInfoBeforeNewContext( 216 TreePath path, VisitorState state, Handler handler) { 217 NullnessStore store = dataFlow.resultBefore(path, state.context, nullnessPropagation); 218 if (store == null) { 219 return NullnessStore.empty(); 220 } 221 return store.filterAccessPaths( 222 (ap) -> { 223 if (ap.getElements().size() == 0) { 224 AccessPath.Root root = ap.getRoot(); 225 if (!root.isReceiver()) { 226 Element e = root.getVarElement(); 227 return e.getKind().equals(ElementKind.PARAMETER) 228 || e.getKind().equals(ElementKind.LOCAL_VARIABLE); 229 } 230 } 231 232 return handler.includeApInfoInSavedContext(ap, state); 233 }); 234 } 235 236 /** 237 * Get the {@link Nullness} value of an access path ending in a field at some program point. 238 * 239 * @param path Tree path to the specific program point. 240 * @param context Javac context. 241 * @param baseExpr The base expression {@code expr} for the access path {@code expr . f} 242 * @param field The field {@code f} for the access path {@code expr . f} 243 * @param trimReceiver if {@code true}, {@code baseExpr} will be trimmed to extract only the 244 * receiver if the node associated to {@code baseExpr} is of type {@link MethodAccessNode}. 245 * (e.g. {@code t.f()} will be converted to {@code t}) 246 * @return The {@link Nullness} value of the access path at the program point. If the baseExpr and 247 * field cannot be represented as an {@link AccessPath}, or if the dataflow analysis has no 248 * result for the program point before {@code path}, conservatively returns {@link 249 * Nullness#NULLABLE} 250 */ 251 public Nullness getNullnessOfFieldForReceiverTree( 252 TreePath path, Context context, Tree baseExpr, VariableElement field, boolean trimReceiver) { 253 Preconditions.checkArgument(field.getKind().equals(ElementKind.FIELD)); 254 AnalysisResult<Nullness, NullnessStore> result = 255 dataFlow.resultForExpr(path, context, nullnessPropagation); 256 if (result == null) { 257 return Nullness.NULLABLE; 258 } 259 NullnessStore store = result.getStoreBefore(path.getLeaf()); 260 // used set of nodes, a tree can have multiple nodes. 261 Set<Node> baseNodes = result.getNodesForTree(baseExpr); 262 if (store == null || baseNodes == null) { 263 return Nullness.NULLABLE; 264 } 265 // look for all possible access paths might exist in store. 266 for (Node baseNode : baseNodes) { 267 // it trims the baseExpr to process only the receiver. (e.g. a.f() is trimmed to a) 268 if (trimReceiver && baseNode instanceof MethodAccessNode) { 269 baseNode = ((MethodAccessNode) baseNode).getReceiver(); 270 } 271 AccessPath accessPath = AccessPath.fromBaseAndElement(baseNode, field, apContext); 272 if (accessPath == null) { 273 continue; 274 } 275 Nullness nullness = store.getNullnessOfAccessPath(accessPath); 276 // field is non-null if at least one access path referring to it exists with non-null 277 // nullness. 278 if (!nullness.equals(Nullness.NULLABLE)) { 279 return nullness; 280 } 281 } 282 return Nullness.NULLABLE; 283 } 284 285 /** 286 * Get the static fields that are guaranteed to be nonnull after a method or initializer block. 287 * 288 * @param path tree path of static method, or initializer block 289 * @param context Javac context 290 * @return fields guaranteed to be nonnull at exit of static method (or initializer block) 291 */ 292 public Set<Element> getNonnullStaticFieldsAtExit(TreePath path, Context context) { 293 NullnessStore nullnessResult = dataFlow.finalResult(path, context, nullnessPropagation); 294 if (nullnessResult == null) { 295 // this case can occur if the method always throws an exception 296 // be conservative and say nothing is initialized 297 return Collections.emptySet(); 298 } 299 return getNonnullStaticFields(nullnessResult); 300 } 301 302 private Set<Element> getNonnullStaticFields(NullnessStore nullnessResult) { 303 Set<AccessPath> nonnullAccessPaths = nullnessResult.getAccessPathsWithValue(Nullness.NONNULL); 304 Set<Element> result = new LinkedHashSet<>(); 305 for (AccessPath ap : nonnullAccessPaths) { 306 assert !ap.getRoot().isReceiver(); 307 Element varElement = ap.getRoot().getVarElement(); 308 if (varElement.getKind().equals(ElementKind.FIELD)) { 309 result.add(varElement); 310 } 311 } 312 return result; 313 } 314 315 /** 316 * Forces a run of the access path nullness analysis on the method (or lambda) at the given 317 * TreePath. 318 * 319 * <p>Because of caching, if the analysis has run in the past for this particular path, it might 320 * not be re-run. The intended usage of this method is to force an analysis pass and thus the 321 * execution of any Handler hooks into the dataflow analysis (such as `onDataflowInitialStore` or 322 * `onDataflowVisitX`). 323 * 324 * @param methodPath tree path of the method (or lambda) to analyze. 325 * @param context Javac context 326 * @return the final NullnessStore on exit from the method. 327 */ 328 public NullnessStore forceRunOnMethod(TreePath methodPath, Context context) { 329 return dataFlow.finalResult(methodPath, context, nullnessPropagation); 330 } 331 332 /** 333 * Nullness of the variable element field of the expression is checked in the store. 334 * 335 * @param exprPath tree path of the expression 336 * @param context Javac context 337 * @param variableElement variable element for which the nullness is evaluated 338 * @return nullness info of variable element field of the expression 339 */ 340 public Nullness getNullnessOfExpressionNamedField( 341 TreePath exprPath, Context context, VariableElement variableElement) { 342 NullnessStore store = dataFlow.resultBeforeExpr(exprPath, context, nullnessPropagation); 343 344 // We use the CFG to get the Node corresponding to the expression 345 Set<Node> exprNodes = 346 dataFlow 347 .getControlFlowGraph(exprPath, context, nullnessPropagation) 348 .getNodesCorrespondingToTree(exprPath.getLeaf()); 349 350 if (exprNodes.size() != 1) { 351 // Since the expression must have a single corresponding node 352 // NULLABLE is our default assumption 353 return Nullness.NULLABLE; 354 } 355 356 AccessPath ap = 357 AccessPath.fromBaseAndElement(exprNodes.iterator().next(), variableElement, apContext); 358 359 if (store != null && ap != null) { 360 if (store 361 .getAccessPathsWithValue(Nullness.NONNULL) 362 .stream() 363 .anyMatch(accessPath -> accessPath.equals(ap))) { 364 return Nullness.NONNULL; 365 } 366 } 367 return Nullness.NULLABLE; 368 } 369 370 /** invalidate all caches */ 371 public void invalidateCaches() { 372 dataFlow.invalidateCaches(); 373 } 374 } 375