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