All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.uber.nullaway.handlers.OptionalEmptinessHandler Maven / Gradle / Ivy

There is a newer version: 0.12.3
Show newest version
/*
 * Copyright (c) 2018 Uber Technologies, Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package com.uber.nullaway.handlers;

import com.google.common.collect.ImmutableSet;
import com.google.errorprone.VisitorState;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.Tree;
import com.sun.source.util.TreePath;
import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.code.Types;
import com.sun.tools.javac.util.Context;
import com.uber.nullaway.Config;
import com.uber.nullaway.ErrorMessage;
import com.uber.nullaway.NullAway;
import com.uber.nullaway.Nullness;
import com.uber.nullaway.dataflow.AccessPath;
import com.uber.nullaway.dataflow.AccessPathNullnessAnalysis;
import com.uber.nullaway.dataflow.AccessPathNullnessPropagation;
import java.lang.annotation.Annotation;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import javax.annotation.Nullable;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ElementVisitor;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.Name;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeMirror;
import org.checkerframework.dataflow.cfg.node.MethodInvocationNode;
import org.checkerframework.dataflow.cfg.node.Node;

/**
 * Handler to better handle {@code isPresent()} methods in code generated for Optionals. With this
 * handler, we learn appropriate Emptiness facts about the relevant property from these calls.
 */
public class OptionalEmptinessHandler extends BaseNoOpHandler {

  @Nullable private ImmutableSet optionalTypes;
  private NullAway analysis;

  private final Config config;
  private final MethodNameUtil methodNameUtil;

  public static final VariableElement OPTIONAL_CONTENT = getOptionalContentElement();

  OptionalEmptinessHandler(Config config, MethodNameUtil methodNameUtil) {
    this.config = config;
    this.methodNameUtil = methodNameUtil;
  }

  @Override
  public boolean onOverrideMayBeNullExpr(
      NullAway analysis, ExpressionTree expr, VisitorState state, boolean exprMayBeNull) {
    if (expr.getKind() == Tree.Kind.METHOD_INVOCATION
        && optionalIsGetCall((Symbol.MethodSymbol) ASTHelpers.getSymbol(expr), state.getTypes())) {
      return true;
    }
    return exprMayBeNull;
  }

  @Override
  public void onMatchTopLevelClass(
      NullAway analysis, ClassTree tree, VisitorState state, Symbol.ClassSymbol classSymbol) {

    this.analysis = analysis;

    optionalTypes =
        config
            .getOptionalClassPaths()
            .stream()
            .map(state::getTypeFromString)
            .filter(Objects::nonNull)
            .map(state.getTypes()::erasure)
            .collect(ImmutableSet.toImmutableSet());
  }

  @Override
  public NullnessHint onDataflowVisitMethodInvocation(
      MethodInvocationNode node,
      Types types,
      Context context,
      AccessPathNullnessPropagation.SubNodeValues inputs,
      AccessPathNullnessPropagation.Updates thenUpdates,
      AccessPathNullnessPropagation.Updates elseUpdates,
      AccessPathNullnessPropagation.Updates bothUpdates) {
    Symbol.MethodSymbol symbol = ASTHelpers.getSymbol(node.getTree());

    if (optionalIsPresentCall(symbol, types)) {
      updateNonNullAPsForOptionalContent(thenUpdates, node.getTarget().getReceiver());
    } else if (config.handleTestAssertionLibraries() && methodNameUtil.isMethodIsTrue(symbol)) {
      // we check for instance of AssertThat(optionalFoo.isPresent()).isTrue()
      updateIfAssertIsPresentTrueOnOptional(node, types, bothUpdates);
    }
    return NullnessHint.UNKNOWN;
  }

  @Override
  public Optional onExpressionDereference(
      ExpressionTree expr, ExpressionTree baseExpr, VisitorState state) {

    if (ASTHelpers.getSymbol(expr) instanceof Symbol.MethodSymbol
        && optionalIsGetCall((Symbol.MethodSymbol) ASTHelpers.getSymbol(expr), state.getTypes())
        && isOptionalContentNullable(state, baseExpr, analysis.getNullnessAnalysis(state))) {
      final String message = "Invoking get() on possibly empty Optional " + baseExpr;
      return Optional.of(
          new ErrorMessage(ErrorMessage.MessageTypes.GET_ON_EMPTY_OPTIONAL, message));
    }
    return Optional.empty();
  }

  private boolean isOptionalContentNullable(
      VisitorState state, ExpressionTree baseExpr, AccessPathNullnessAnalysis analysis) {
    return analysis.getNullnessOfExpressionNamedField(
            new TreePath(state.getPath(), baseExpr), state.context, OPTIONAL_CONTENT)
        == Nullness.NULLABLE;
  }

  @Override
  public boolean includeApInfoInSavedContext(AccessPath accessPath, VisitorState state) {

    if (accessPath.getElements().size() == 1) {
      AccessPath.Root root = accessPath.getRoot();
      if (!root.isReceiver()) {
        final Element e = root.getVarElement();
        return e.getKind().equals(ElementKind.LOCAL_VARIABLE)
            && accessPath.getElements().get(0).getJavaElement().equals(OPTIONAL_CONTENT);
      }
    }
    return false;
  }

  private void updateIfAssertIsPresentTrueOnOptional(
      MethodInvocationNode node, Types types, AccessPathNullnessPropagation.Updates bothUpdates) {
    Node receiver = node.getTarget().getReceiver();
    if (receiver instanceof MethodInvocationNode) {
      MethodInvocationNode receiverMethod = (MethodInvocationNode) receiver;
      Symbol.MethodSymbol receiverSymbol = ASTHelpers.getSymbol(receiverMethod.getTree());
      if (methodNameUtil.isMethodAssertThat(receiverSymbol)) {
        // assertThat will always have at least one argument, So safe to extract from the arguments
        Node arg = receiverMethod.getArgument(0);
        if (arg instanceof MethodInvocationNode) {
          // Since assertThat(a.isPresent()) changes to
          // Truth.assertThat(Boolean.valueOf(a.isPresent()))
          // need to be unwrapped from Boolean.valueOf
          Node unwrappedArg = ((MethodInvocationNode) arg).getArgument(0);
          if (unwrappedArg instanceof MethodInvocationNode) {
            MethodInvocationNode argMethod = (MethodInvocationNode) unwrappedArg;
            Symbol.MethodSymbol argSymbol = ASTHelpers.getSymbol(argMethod.getTree());
            if (optionalIsPresentCall(argSymbol, types)) {
              updateNonNullAPsForOptionalContent(bothUpdates, argMethod.getTarget().getReceiver());
            }
          }
        }
      }
    }
  }

  private void updateNonNullAPsForOptionalContent(
      AccessPathNullnessPropagation.Updates updates, Node base) {
    AccessPath ap = AccessPath.fromBaseAndElement(base, OPTIONAL_CONTENT);
    if (ap != null && base.getTree() != null) {
      updates.set(ap, Nullness.NONNULL);
    }
  }

  private boolean optionalIsPresentCall(Symbol.MethodSymbol symbol, Types types) {
    for (Type optionalType : optionalTypes) {
      if (symbol.getSimpleName().toString().equals("isPresent")
          && symbol.getParameters().length() == 0
          && types.isSubtype(symbol.owner.type, optionalType)) return true;
    }
    return false;
  }

  private boolean optionalIsGetCall(Symbol.MethodSymbol symbol, Types types) {
    for (Type optionalType : optionalTypes) {
      if (symbol.getSimpleName().toString().equals("get")
          && symbol.getParameters().length() == 0
          && types.isSubtype(symbol.owner.type, optionalType)) return true;
    }
    return false;
  }

  private static VariableElement getOptionalContentElement() {
    return new VariableElement() {
      @Override
      public Object getConstantValue() {
        return null;
      }

      @Override
      public Name getSimpleName() {
        return null;
      }

      @Override
      public Element getEnclosingElement() {
        return null;
      }

      @Override
      public List getEnclosedElements() {
        return null;
      }

      @Override
      public List getAnnotationMirrors() {
        return null;
      }

      @Override
      public  A getAnnotation(Class aClass) {
        return null;
      }

      @Override
      public  A[] getAnnotationsByType(Class aClass) {
        return null;
      }

      @Override
      public  R accept(ElementVisitor elementVisitor, P p) {
        return null;
      }

      @Override
      public TypeMirror asType() {
        return null;
      }

      @Override
      public ElementKind getKind() {
        return null;
      }

      @Override
      public Set getModifiers() {
        return null;
      }

      @Override
      public String toString() {
        return "OPTIONAL_CONTENT";
      }
    };
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy