com.google.errorprone.bugpatterns.CheckReturnValue.java Source code

Java tutorial

Introduction

Here is the source code for com.google.errorprone.bugpatterns.CheckReturnValue.java

Source

/*
 * Copyright 2012 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.errorprone.bugpatterns;

import static com.google.errorprone.BugPattern.Category.JDK;
import static com.google.errorprone.BugPattern.SeverityLevel.ERROR;
import static com.google.errorprone.util.ASTHelpers.enclosingClass;
import static com.google.errorprone.util.ASTHelpers.enclosingPackage;
import static com.google.errorprone.util.ASTHelpers.hasAnnotation;
import static com.google.errorprone.util.ASTHelpers.hasDirectAnnotationWithSimpleName;

import com.google.common.base.Optional;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.errorprone.bugpatterns.BugChecker.ClassTreeMatcher;
import com.google.errorprone.bugpatterns.BugChecker.MethodTreeMatcher;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Matcher;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.MethodTree;
import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.code.Symbol.ClassSymbol;
import com.sun.tools.javac.code.Symbol.MethodSymbol;
import javax.lang.model.element.ElementKind;

/** @author eaftan@google.com (Eddie Aftandilian) */
@BugPattern(name = "CheckReturnValue", altNames = { "ResultOfMethodCallIgnored",
        "ReturnValueIgnored" }, summary = "Ignored return value of method that is annotated with @CheckReturnValue", category = JDK, severity = ERROR)
public class CheckReturnValue extends AbstractReturnValueIgnored implements MethodTreeMatcher, ClassTreeMatcher {

    private static final String CHECK_RETURN_VALUE = "CheckReturnValue";

    private static Optional<Boolean> shouldCheckReturnValue(Symbol sym, VisitorState state) {
        if (hasAnnotation(sym, CanIgnoreReturnValue.class, state)) {
            return Optional.of(false);
        }
        if (hasDirectAnnotationWithSimpleName(sym, CHECK_RETURN_VALUE)) {
            return Optional.of(true);
        }
        return Optional.absent();
    }

    private static Optional<Boolean> checkEnclosingClasses(MethodSymbol method, VisitorState state) {
        Symbol enclosingClass = enclosingClass(method);
        while (enclosingClass instanceof ClassSymbol) {
            Optional<Boolean> result = shouldCheckReturnValue(enclosingClass, state);
            if (result.isPresent()) {
                return result;
            }
            enclosingClass = enclosingClass.owner;
        }
        return Optional.absent();
    }

    private static Optional<Boolean> checkPackage(MethodSymbol method, VisitorState state) {
        return shouldCheckReturnValue(enclosingPackage(method), state);
    }

    private static final Matcher<MethodInvocationTree> MATCHER = new Matcher<MethodInvocationTree>() {
        @Override
        public boolean matches(MethodInvocationTree tree, VisitorState state) {
            MethodSymbol method = ASTHelpers.getSymbol(tree);
            Optional<Boolean> result = shouldCheckReturnValue(method, state);
            if (result.isPresent()) {
                return result.get();
            }

            result = checkEnclosingClasses(method, state);
            if (result.isPresent()) {
                return result.get();
            }

            result = checkPackage(method, state);
            if (result.isPresent()) {
                return result.get();
            }

            return false;
        }
    };

    /**
     * Return a matcher for method invocations in which the method being called has the
     * {@code @CheckReturnValue} annotation.
     */
    @Override
    public Matcher<MethodInvocationTree> specializedMatcher() {
        return MATCHER;
    }

    private static final String BOTH_ERROR = "@CheckReturnValue and @CanIgnoreReturnValue cannot both be applied to the same %s";

    /**
     * Validate {@link javax.annotation.CheckReturnValue} and {@link CanIgnoreReturnValue} usage on
     * methods.
     *
     * <p>The annotations should not both be appled to the same method.
     *
     * <p>The annotations should not be applied to void-returning methods. Doing so makes no sense,
     * because there is no return value to check.
     */
    @Override
    public Description matchMethod(MethodTree tree, VisitorState state) {
        MethodSymbol method = ASTHelpers.getSymbol(tree);

        boolean checkReturn = hasDirectAnnotationWithSimpleName(method, CHECK_RETURN_VALUE);
        boolean canIgnore = hasAnnotation(method, CanIgnoreReturnValue.class, state);

        if (checkReturn && canIgnore) {
            return buildDescription(tree).setMessage(String.format(BOTH_ERROR, "method")).build();
        }

        String annotationToValidate;
        if (checkReturn) {
            annotationToValidate = javax.annotation.CheckReturnValue.class.getSimpleName();
        } else if (canIgnore) {
            annotationToValidate = CanIgnoreReturnValue.class.getSimpleName();
        } else {
            return Description.NO_MATCH;
        }
        if (method.getKind() != ElementKind.METHOD) {
            // skip contructors (which javac thinks are void-returning)
            return Description.NO_MATCH;
        }
        if (!ASTHelpers.isVoidType(method.getReturnType(), state)) {
            return Description.NO_MATCH;
        }
        String message = String.format("@%s may not be applied to void-returning methods", annotationToValidate);
        return buildDescription(tree).setMessage(message).build();
    }

    /**
     * Validate that at most one of {@link javax.annotation.CheckReturnValue} and {@link
     * CanIgnoreReturnValue} are applied to a class (or interface or enum).
     */
    @Override
    public Description matchClass(ClassTree tree, VisitorState state) {
        if (hasDirectAnnotationWithSimpleName(ASTHelpers.getSymbol(tree), CHECK_RETURN_VALUE)
                && hasAnnotation(tree, CanIgnoreReturnValue.class, state)) {
            return buildDescription(tree).setMessage(String.format(BOTH_ERROR, "class")).build();
        }
        return Description.NO_MATCH;
    }
}