• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.systemfeatures.errorprone;
18 
19 import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
20 
21 import com.android.systemfeatures.RoSystemFeaturesMetadata;
22 
23 import com.google.auto.service.AutoService;
24 import com.google.errorprone.BugPattern;
25 import com.google.errorprone.VisitorState;
26 import com.google.errorprone.bugpatterns.BugChecker;
27 import com.google.errorprone.fixes.SuggestedFix;
28 import com.google.errorprone.matchers.Description;
29 import com.google.errorprone.matchers.Matcher;
30 import com.google.errorprone.matchers.Matchers;
31 import com.google.errorprone.util.ASTHelpers;
32 import com.sun.source.tree.ExpressionTree;
33 import com.sun.source.tree.MethodInvocationTree;
34 import com.sun.tools.javac.code.Symbol;
35 
36 @AutoService(BugChecker.class)
37 @BugPattern(
38         name = "RoSystemFeaturesChecker",
39         summary = "Use RoSystemFeature instead of PackageManager.hasSystemFeature",
40         explanation =
41                 "Directly invoking `PackageManager.hasSystemFeature` is less efficient than using"
42                     + " the `RoSystemFeatures` helper class. This check flags invocations like"
43                     + " `context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FOO)`"
44                     + " and suggests replacing them with"
45                     + " `com.android.internal.pm.RoSystemFeatures.hasFeatureFoo(context)`.",
46         severity = WARNING)
47 public class RoSystemFeaturesChecker extends BugChecker
48         implements BugChecker.MethodInvocationTreeMatcher {
49 
50     private static final String PACKAGE_MANAGER_CLASS = "android.content.pm.PackageManager";
51     private static final String CONTEXT_CLASS = "android.content.Context";
52     private static final String RO_SYSTEM_FEATURE_SIMPLE_CLASS = "RoSystemFeatures";
53     private static final String RO_SYSTEM_FEATURE_CLASS =
54             "com.android.internal.pm." + RO_SYSTEM_FEATURE_SIMPLE_CLASS;
55     private static final String GET_PACKAGE_MANAGER_METHOD = "getPackageManager";
56     private static final String HAS_SYSTEM_FEATURE_METHOD = "hasSystemFeature";
57     private static final String FEATURE_PREFIX = "FEATURE_";
58 
59     private static final Matcher<ExpressionTree> HAS_SYSTEM_FEATURE_MATCHER =
60             Matchers.instanceMethod()
61                     .onDescendantOf(PACKAGE_MANAGER_CLASS)
62                     .named(HAS_SYSTEM_FEATURE_METHOD)
63                     .withParameters(String.class.getName());
64 
65     private static final Matcher<ExpressionTree> GET_PACKAGE_MANAGER_MATCHER =
66             Matchers.instanceMethod()
67                     .onDescendantOf(CONTEXT_CLASS)
68                     .named(GET_PACKAGE_MANAGER_METHOD);
69 
70     @Override
matchMethodInvocation(MethodInvocationTree tree, VisitorState state)71     public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
72         if (!HAS_SYSTEM_FEATURE_MATCHER.matches(tree, state)) {
73             return Description.NO_MATCH;
74         }
75 
76         // Check if the PackageManager was obtained from a Context instance.
77         ExpressionTree packageManager = ASTHelpers.getReceiver(tree);
78         if (!GET_PACKAGE_MANAGER_MATCHER.matches(packageManager, state)) {
79             return Description.NO_MATCH;
80         }
81 
82         // Get the feature argument and check if it's a PackageManager.FEATURE_X constant.
83         ExpressionTree feature = tree.getArguments().isEmpty() ? null : tree.getArguments().get(0);
84         Symbol featureSymbol = ASTHelpers.getSymbol(feature);
85         if (featureSymbol == null
86                 || !featureSymbol.isStatic()
87                 || !featureSymbol.getSimpleName().toString().startsWith(FEATURE_PREFIX)
88                 || ASTHelpers.enclosingClass(featureSymbol) == null
89                 || !ASTHelpers.enclosingClass(featureSymbol)
90                         .getQualifiedName()
91                         .contentEquals(PACKAGE_MANAGER_CLASS)) {
92             return Description.NO_MATCH;
93         }
94 
95         // Check if the feature argument is part of the RoSystemFeatures API surface.
96         String featureName = featureSymbol.getSimpleName().toString();
97         String methodName = RoSystemFeaturesMetadata.getMethodNameForFeatureName(featureName);
98         if (methodName == null) {
99             return Description.NO_MATCH;
100         }
101 
102         // Generate the appropriate fix.
103         String replacement =
104                 String.format(
105                         "%s.%s(%s)",
106                         RO_SYSTEM_FEATURE_SIMPLE_CLASS,
107                         methodName,
108                         state.getSourceForNode(ASTHelpers.getReceiver(packageManager)));
109         // Note that ErrorProne doesn't offer a seamless way of removing the `PackageManager` import
110         // if unused after fix application, so for now we only offer best effort import suggestions.
111         SuggestedFix fix =
112                 SuggestedFix.builder()
113                         .replace(tree, replacement)
114                         .addImport(RO_SYSTEM_FEATURE_CLASS)
115                         .removeStaticImport(PACKAGE_MANAGER_CLASS + "." + featureName)
116                         .build();
117         return describeMatch(tree, fix);
118     }
119 }
120