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

org.checkerframework.checker.optional.OptionalImplTransfer Maven / Gradle / Ivy

Go to download

The Checker Framework enhances Java's type system to make it more powerful and useful. This lets software developers detect and prevent errors in their Java programs. The Checker Framework includes compiler plug-ins ("checkers") that find bugs or verify their absence. It also permits you to write your own compiler plug-ins.

There is a newer version: 3.49.2
Show newest version
package org.checkerframework.checker.optional;

import com.sun.source.tree.AnnotationTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.LambdaExpressionTree;
import com.sun.source.tree.MemberSelectTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.VariableTree;
import com.sun.source.util.TreePath;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.function.BinaryOperator;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
import org.checkerframework.checker.nonempty.qual.NonEmpty;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.checker.optional.qual.Present;
import org.checkerframework.common.basetype.BaseTypeChecker;
import org.checkerframework.dataflow.analysis.ConditionalTransferResult;
import org.checkerframework.dataflow.analysis.TransferInput;
import org.checkerframework.dataflow.analysis.TransferResult;
import org.checkerframework.dataflow.cfg.UnderlyingAST;
import org.checkerframework.dataflow.cfg.UnderlyingAST.CFGLambda;
import org.checkerframework.dataflow.cfg.node.LocalVariableNode;
import org.checkerframework.dataflow.cfg.node.MethodInvocationNode;
import org.checkerframework.dataflow.expression.JavaExpression;
import org.checkerframework.dataflow.util.NodeUtils;
import org.checkerframework.framework.flow.CFAbstractAnalysis;
import org.checkerframework.framework.flow.CFStore;
import org.checkerframework.framework.flow.CFTransfer;
import org.checkerframework.framework.flow.CFValue;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
import org.checkerframework.javacutil.AnnotationBuilder;
import org.checkerframework.javacutil.AnnotationMirrorSet;
import org.checkerframework.javacutil.AnnotationUtils;
import org.checkerframework.javacutil.TreeUtils;

/** The transfer function for the Optional Checker. */
public class OptionalImplTransfer extends CFTransfer {

  /** The {@link OptionalImplAnnotatedTypeFactory} instance for this transfer class. */
  private final OptionalImplAnnotatedTypeFactory optionalTypeFactory;

  /** True if "-AassumePure" or "-AassumeDeterministic" was passed. */
  boolean assumeDeterministic;

  /** The @{@link Present} annotation. */
  private final AnnotationMirror PRESENT;

  /** The @{@link NonEmpty} annotation. */
  private final AnnotationMirror NON_EMPTY;

  /** The element for java.util.Optional.ifPresent(). */
  private final ExecutableElement optionalIfPresent;

  /** The element for java.util.Optional.ifPresentOrElse(), or null. */
  private final @Nullable ExecutableElement optionalIfPresentOrElse;

  /** The element for java.util.stream.Stream.max(), or null. */
  private final @Nullable ExecutableElement streamMax;

  /** The element for java.util.stream.Stream.min(), or null. */
  private final @Nullable ExecutableElement streamMin;

  /** The element for java.util.stream.Stream.reduce(BinaryOperator<T>), or null. */
  private final @Nullable ExecutableElement streamReduceNoIdentity;

  /** The element for java.util.stream.Stream.findFirst(), or null. */
  private final @Nullable ExecutableElement streamFindFirst;

  /** The element for java.util.stream.Stream.findAny(), or null. */
  private final @Nullable ExecutableElement streamFindAny;

  /** Stream methods such that if the input is @NonEmpty, the result is @Present. */
  private final List nonEmptyToPresentStreamMethods;

  /**
   * Create an OptionalImplTransfer.
   *
   * @param analysis the OptionalImpl Checker instance
   */
  public OptionalImplTransfer(CFAbstractAnalysis analysis) {
    super(analysis);
    optionalTypeFactory = (OptionalImplAnnotatedTypeFactory) analysis.getTypeFactory();
    BaseTypeChecker checker = optionalTypeFactory.getChecker();
    assumeDeterministic =
        checker.hasOption("assumePure") || checker.hasOption("assumeDeterministic");

    Elements elements = optionalTypeFactory.getElementUtils();
    PRESENT = AnnotationBuilder.fromClass(elements, Present.class);
    NON_EMPTY = AnnotationBuilder.fromClass(elements, NonEmpty.class);
    ProcessingEnvironment env = optionalTypeFactory.getProcessingEnv();
    optionalIfPresent = TreeUtils.getMethod("java.util.Optional", "ifPresent", 1, env);
    optionalIfPresentOrElse =
        TreeUtils.getMethodOrNull("java.util.Optional", "ifPresentOrElse", 2, env);
    streamMax = TreeUtils.getMethodOrNull("java.util.stream.Stream", "max", 1, env);
    streamMin = TreeUtils.getMethodOrNull("java.util.stream.Stream", "min", 1, env);
    streamReduceNoIdentity = TreeUtils.getMethodOrNull("java.util.stream.Stream", "reduce", 1, env);
    streamFindFirst = TreeUtils.getMethodOrNull("java.util.stream.Stream", "findFirst", 0, env);
    streamFindAny = TreeUtils.getMethodOrNull("java.util.stream.Stream", "findAny", 0, env);
    nonEmptyToPresentStreamMethods =
        Arrays.asList(streamMax, streamMin, streamReduceNoIdentity, streamFindFirst, streamFindAny);
  }

  @Override
  public CFStore initialStore(UnderlyingAST underlyingAST, List parameters) {

    CFStore result = super.initialStore(underlyingAST, parameters);

    if (underlyingAST.getKind() == UnderlyingAST.Kind.LAMBDA) {
      // Check whether this lambda is an argument to `Optional.ifPresent()` or
      // `Optional.ifPresentOrElse()`.  If so, then within the lambda, the receiver of the
      // `ifPresent*` method is @Present.
      CFGLambda cfgLambda = (CFGLambda) underlyingAST;
      LambdaExpressionTree lambdaTree = cfgLambda.getLambdaTree();
      List lambdaParams = lambdaTree.getParameters();
      if (lambdaParams.size() == 1) {
        TreePath lambdaPath = optionalTypeFactory.getPath(lambdaTree);
        Tree lambdaParent = lambdaPath.getParentPath().getLeaf();
        if (lambdaParent.getKind() == Tree.Kind.METHOD_INVOCATION) {
          MethodInvocationTree invok = (MethodInvocationTree) lambdaParent;
          ExecutableElement methodElt = TreeUtils.elementFromUse(invok);
          if (methodElt.equals(optionalIfPresent) || methodElt.equals(optionalIfPresentOrElse)) {
            // `underlyingAST` is an invocation of `Optional.ifPresent()` or
            // `Optional.ifPresentOrElse()`.  In the lambda, the receiver is @Present.
            ExpressionTree methodSelectTree = TreeUtils.withoutParens(invok.getMethodSelect());
            ExpressionTree receiverTree = ((MemberSelectTree) methodSelectTree).getExpression();
            JavaExpression receiverJe = JavaExpression.fromTree(receiverTree);
            result.insertValue(receiverJe, PRESENT);
          }
        }
      }
    }

    // TODO: Similar logic to the above can be applied in the Nullness Checker.
    // Some methods take a function as an argument, guaranteeing that, if the function is
    // called:
    //  * the value passed to the function is non-null
    //  * some other argument to the method is non-null
    // Examples:
    //  * Jodd's `StringUtil.ifNotNull()`
    //  * `Opt.ifPresent()`
    //  * `Opt.map()`

    return result;
  }

  @Override
  public TransferResult visitMethodInvocation(
      MethodInvocationNode n, TransferInput in) {
    TransferResult result = super.visitMethodInvocation(n, in);
    if (n.getTree() == null) {
      return result;
    }
    return refineNonEmptyToPresentStreamResult(n, result);
  }

  /**
   * Refines the result of a call to a method in {@link #nonEmptyToPresentStreamMethods}. Examples
   * are {@link java.util.stream.Stream#max(Comparator)} and {@link
   * java.util.stream.Stream#reduce(BinaryOperator)}.
   *
   * 

When one of those methods is invoked on an empty stream, the result is an empty/absent * Optional. When one of those methods is invoked on a non-empty stream, the result is a present * Optional. * * @param methodInvok the method invocation node * @param result the transfer result to side effect * @return the refined transfer result */ private TransferResult refineNonEmptyToPresentStreamResult( MethodInvocationNode methodInvok, TransferResult result) { if (NodeUtils.isMethodInvocation( methodInvok, nonEmptyToPresentStreamMethods, optionalTypeFactory.getProcessingEnv())) { if (isReceiverParameterNonEmpty(methodInvok)) { // The receiver is @Non-Empty, therefore the result is @Present. JavaExpression methodInvokJE = JavaExpression.fromNode(methodInvok); if (assumeDeterministic) { insertIntoStoresPermitNonDeterministic(result, methodInvokJE, PRESENT); } else { insertIntoStores(result, methodInvokJE, PRESENT); } CFValue value = analysis.createSingleAnnotationValue(PRESENT, methodInvok.getType()); return new ConditionalTransferResult<>( finishValue(value, result.getThenStore(), result.getElseStore()), result.getThenStore(), result.getElseStore(), result.getExceptionalStores()); } } return result; } /** * Returns true if the receiver parameter of the method being invoked is explicitly annotated * with @{@link NonEmpty}. * * @param methodInvok a method invocation node * @return true if the receiver parameter of the method being invoked is explicitly annotated * with @{@link NonEmpty} */ private boolean isReceiverParameterNonEmpty(MethodInvocationNode methodInvok) { ExpressionTree receiverTree = TreeUtils.getReceiverTree(methodInvok.getTree()); if (receiverTree instanceof MethodInvocationTree) { // TODO(https://github.com/typetools/checker-framework/issues/6848): this logic needs further // refinement to eliminate a source of false positives in the Optional Checker. // Also see the discussion in: // https://github.com/typetools/checker-framework/pull/6685#discussion_r1788632663 for // additional context. while (receiverTree instanceof MethodInvocationTree) { receiverTree = TreeUtils.getReceiverTree(receiverTree); } } Element receiverElement = TreeUtils.elementFromTree(receiverTree); if (receiverElement == null) { return false; } VariableTree receiverDeclarationInMethod = getReceiverInMethodBody(receiverElement); if (receiverDeclarationInMethod != null) { // The receiver was found in the method as a local variable or a formal parameter List receiverAnnotationTrees = receiverDeclarationInMethod.getModifiers().getAnnotations(); List receiverAnnotationMirrors = TreeUtils.annotationsFromTypeAnnotationTrees(receiverAnnotationTrees); return AnnotationUtils.containsSame(receiverAnnotationMirrors, NON_EMPTY); } TypeMirror receiverType = receiverElement.asType(); if (receiverType.getKind() == TypeKind.DECLARED) { // The receiver is a field, not a local variable AnnotationMirrorSet receiverAnnos = AnnotatedDeclaredType.getPrimaryAnnotationsFromElement( receiverElement, (DeclaredType) receiverType, optionalTypeFactory); return receiverAnnos.contains(NON_EMPTY); } return false; } /** * Returns the {@link VariableTree} for a receiver element if it is declared in a method body * (i.e., it is either a local variable, resource variable, or a formal parameter), otherwise * return null. * * @param receiverElement the receiver element * @return the {@link VariableTree} if the receiver is declared in a method body */ private @Nullable VariableTree getReceiverInMethodBody(Element receiverElement) { if (receiverElement.getKind() == ElementKind.LOCAL_VARIABLE || receiverElement.getKind() == ElementKind.RESOURCE_VARIABLE || receiverElement.getKind() == ElementKind.PARAMETER) { return (VariableTree) optionalTypeFactory.declarationFromElement(receiverElement); } return null; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy