Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 559130df authored by Jeff Sharkey's avatar Jeff Sharkey Committed by Android (Google) Code Review
Browse files

Merge "Add custom Error Prone check for SDK comparisons." into rvc-dev

parents 0a19f879 4d1d7b56
Loading
Loading
Loading
Loading

errorprone/Android.bp

0 → 100644
+25 −0
Original line number Diff line number Diff line

java_plugin {
    name: "error_prone_android_framework",

    static_libs: [
        "error_prone_android_framework_lib",
    ],
}

java_library_host {
    name: "error_prone_android_framework_lib",

    srcs: ["java/**/*.java"],

    static_libs: [
        "//external/error_prone:error_prone_core",
        "//external/dagger2:dagger2-auto-service",
    ],

    plugins: [
        "//external/dagger2:dagger2-auto-service",
    ],

    javacflags: ["-verbose"],
}
+81 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 The Android Open Source Project
 *
 * 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.android;

import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
import static com.google.errorprone.matchers.Matchers.allOf;
import static com.google.errorprone.matchers.Matchers.anyOf;
import static com.google.errorprone.matchers.Matchers.anything;
import static com.google.errorprone.matchers.Matchers.kindIs;

import com.google.auto.service.AutoService;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.BinaryTreeMatcher;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.FieldMatchers;
import com.google.errorprone.matchers.Matcher;
import com.sun.source.tree.BinaryTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.Tree.Kind;

@AutoService(BugChecker.class)
@BugPattern(
    name = "AndroidFrameworkTargetSdk",
    summary = "Verifies that all target SDK comparisons are sane",
    severity = WARNING)
public final class TargetSdkChecker extends BugChecker implements BinaryTreeMatcher {
    private static final Matcher<ExpressionTree> VERSION_CODE = FieldMatchers
            .anyFieldInClass("android.os.Build.VERSION_CODES");

    private static final Matcher<BinaryTree> INVALID_OLD_BEHAVIOR = anyOf(
            allOf(kindIs(Kind.LESS_THAN_EQUAL), binaryTreeExact(anything(), VERSION_CODE)),
            allOf(kindIs(Kind.GREATER_THAN_EQUAL), binaryTreeExact(VERSION_CODE, anything())));

    private static final Matcher<BinaryTree> INVALID_NEW_BEHAVIOR = anyOf(
            allOf(kindIs(Kind.GREATER_THAN), binaryTreeExact(anything(), VERSION_CODE)),
            allOf(kindIs(Kind.LESS_THAN), binaryTreeExact(VERSION_CODE, anything())));

    @Override
    public Description matchBinary(BinaryTree tree, VisitorState state) {
        if (INVALID_OLD_BEHAVIOR.matches(tree, state)) {
            return buildDescription(tree)
                    .setMessage("Legacy behaviors must be written in style "
                            + "'targetSdk < Build.VERSION_CODES.Z'")
                    .build();
        }
        if (INVALID_NEW_BEHAVIOR.matches(tree, state)) {
            return buildDescription(tree)
                    .setMessage("Modern behaviors must be written in style "
                            + "'targetSdk >= Build.VERSION_CODES.Z'")
                    .build();
        }
        return Description.NO_MATCH;
    }

    private static Matcher<BinaryTree> binaryTreeExact(Matcher<ExpressionTree> left,
            Matcher<ExpressionTree> right) {
        return new Matcher<BinaryTree>() {
            @Override
            public boolean matches(BinaryTree tree, VisitorState state) {
                return left.matches(tree.getLeftOperand(), state)
                        && right.matches(tree.getRightOperand(), state);
            }
        };
    }
}
+99 −0
Original line number Diff line number Diff line
/*
 * Copyright 2018 The Error Prone Authors.
 *
 * 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.matchers;

import com.google.errorprone.VisitorState;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.ImportTree;
import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.code.Symbol.ClassSymbol;
import javax.annotation.Nullable;

// TODO(glorioso): this likely wants to be a fluent interface like MethodMatchers.
// Ex: [staticField()|instanceField()]
//         .[onClass(String)|onAnyClass|onClassMatching]
//         .[named(String)|withAnyName|withNameMatching]
/** Static utility methods for creating {@link Matcher}s for detecting references to fields. */
public final class FieldMatchers {
  private FieldMatchers() {}

  public static Matcher<ExpressionTree> anyFieldInClass(String className) {
    return new FieldReferenceMatcher() {
      @Override
      boolean classIsAppropriate(ClassSymbol classSymbol) {
        return classSymbol.getQualifiedName().contentEquals(className);
      }

      @Override
      boolean fieldSymbolIsAppropriate(Symbol symbol) {
        return true;
      }
    };
  }

  public static Matcher<ExpressionTree> staticField(String className, String fieldName) {
    return new FieldReferenceMatcher() {
      @Override
      boolean classIsAppropriate(ClassSymbol classSymbol) {
        return classSymbol.getQualifiedName().contentEquals(className);
      }

      @Override
      boolean fieldSymbolIsAppropriate(Symbol symbol) {
        return symbol.isStatic() && symbol.getSimpleName().contentEquals(fieldName);
      }
    };
  }

  public static Matcher<ExpressionTree> instanceField(String className, String fieldName) {
    return new FieldReferenceMatcher() {
      @Override
      boolean classIsAppropriate(ClassSymbol classSymbol) {
        return classSymbol.getQualifiedName().contentEquals(className);
      }

      @Override
      boolean fieldSymbolIsAppropriate(Symbol symbol) {
        return !symbol.isStatic() && symbol.getSimpleName().contentEquals(fieldName);
      }
    };
  }

  private abstract static class FieldReferenceMatcher implements Matcher<ExpressionTree> {
    @Override
    public boolean matches(ExpressionTree expressionTree, VisitorState state) {
      return isSymbolFieldInAppropriateClass(ASTHelpers.getSymbol(expressionTree))
          // Don't match if this is part of a static import tree, since they will get the finding
          // on any usage of the field in their source.
          && ASTHelpers.findEnclosingNode(state.getPath(), ImportTree.class) == null;
    }

    private boolean isSymbolFieldInAppropriateClass(@Nullable Symbol symbol) {
      if (symbol == null) {
        return false;
      }
      return symbol.getKind().isField()
          && fieldSymbolIsAppropriate(symbol)
          && classIsAppropriate(symbol.owner.enclClass());
    }

    abstract boolean fieldSymbolIsAppropriate(Symbol symbol);

    abstract boolean classIsAppropriate(ClassSymbol classSymbol);
  }
}
+7 −1
Original line number Diff line number Diff line
java_defaults {
    name: "services_defaults",
    plugins: [
        "error_prone_android_framework",
    ],
}

filegroup {
    name: "services-main-sources",
    srcs: ["java/**/*.java"],
@@ -83,7 +90,6 @@ java_library {

    // Uncomment to enable output of certain warnings (deprecated, unchecked)
    //javacflags: ["-Xlint"],

}

// native library
+1 −0
Original line number Diff line number Diff line
@@ -7,6 +7,7 @@ filegroup {

java_library_static {
    name: "services.accessibility",
    defaults: ["services_defaults"],
    srcs: [":services.accessibility-sources"],
    libs: ["services.core"],
}
Loading