1 /*
2  * Copyright 2021 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 @file:Suppress("UnstableApiUsage")
18 
19 package androidx.build.lint
20 
21 import androidx.build.lint.Stubs.Companion.RestrictTo
22 import org.junit.Test
23 import org.junit.runner.RunWith
24 import org.junit.runners.JUnit4
25 
26 @RunWith(JUnit4::class)
27 class BanUncheckedReflectionTest :
28     AbstractLintDetectorTest(
29         useDetector = BanUncheckedReflection(),
30         useIssues = listOf(BanUncheckedReflection.ISSUE),
31         stubs = arrayOf(Stubs.ChecksSdkIntAtLeast),
32     ) {
33 
34     @Test
Detection of unchecked reflection in real-world Java sourcesnull35     fun `Detection of unchecked reflection in real-world Java sources`() {
36         val input = arrayOf(javaSample("androidx.sample.core.app.ActivityRecreator"), RestrictTo)
37 
38         val expected =
39             """
40 src/androidx/sample/core/app/ActivityRecreator.java:261: Error: Method.invoke requires both an upper and lower SDK bounds checks to be safe, and the upper bound must be below SdkVersionInfo.HIGHEST_KNOWN_API. [BanUncheckedReflection]
41                         performStopActivity3ParamsMethod.invoke(activityThread,
42                         ^
43 src/androidx/sample/core/app/ActivityRecreator.java:264: Error: Method.invoke requires both an upper and lower SDK bounds checks to be safe, and the upper bound must be below SdkVersionInfo.HIGHEST_KNOWN_API. [BanUncheckedReflection]
44                         performStopActivity2ParamsMethod.invoke(activityThread,
45                         ^
46 2 errors, 0 warnings
47         """
48                 .trimIndent()
49 
50         check(*input).expect(expected)
51     }
52 
53     @Test
Detection of unchecked reflection in real-world Kotlin sourcesnull54     fun `Detection of unchecked reflection in real-world Kotlin sources`() {
55         val input = arrayOf(ktSample("androidx.sample.core.app.ActivityRecreatorKt"), RestrictTo)
56 
57         val expected =
58             """
59 src/androidx/sample/core/app/ActivityRecreatorKt.kt:172: Error: Method.invoke requires both an upper and lower SDK bounds checks to be safe, and the upper bound must be below SdkVersionInfo.HIGHEST_KNOWN_API. [BanUncheckedReflection]
60                         performStopActivity3ParamsMethod!!.invoke(
61                         ^
62 src/androidx/sample/core/app/ActivityRecreatorKt.kt:179: Error: Method.invoke requires both an upper and lower SDK bounds checks to be safe, and the upper bound must be below SdkVersionInfo.HIGHEST_KNOWN_API. [BanUncheckedReflection]
63                         performStopActivity2ParamsMethod!!.invoke(activityThread, token, false)
64                         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
65 2 errors, 0 warnings
66         """
67                 .trimIndent()
68 
69         lint().files(*input).run().expect(expected)
70     }
71 
72     @Test
Checked reflection in real-world Java sourcesnull73     fun `Checked reflection in real-world Java sources`() {
74         val input =
75             arrayOf(javaSample("androidx.sample.core.app.ActivityRecreatorChecked"), RestrictTo)
76 
77         val expected =
78             """
79 No warnings.
80         """
81                 .trimIndent()
82 
83         check(*input).expect(expected)
84     }
85 
86     @Test
Checked reflection in real-world Kotlin sourcesnull87     fun `Checked reflection in real-world Kotlin sources`() {
88         val input =
89             arrayOf(ktSample("androidx.sample.core.app.ActivityRecreatorKtChecked"), RestrictTo)
90 
91         check(*input).expectClean()
92     }
93 
94     @Test
Checked reflection using preceding if with returnnull95     fun `Checked reflection using preceding if with return`() {
96         val input =
97             kotlin(
98                 """
99             package androidx.foo
100 
101             import android.os.Build
102 
103             fun forceEnablePlatformTracing() {
104                 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) return
105                 if (Build.VERSION.SDK_INT >= 29) return
106                 val method = android.os.Trace::class.java.getMethod(
107                     "setAppTracingAllowed",
108                     Boolean::class.javaPrimitiveType
109                 )
110                 method.invoke(null, true)
111             }
112         """
113                     .trimIndent()
114             )
115 
116         check(input).expectClean()
117     }
118 
119     @Test
Checked reflection using @DeprecatedSinceApi methodnull120     fun `Checked reflection using @DeprecatedSinceApi method`() {
121         val input =
122             arrayOf(
123                 kotlin(
124                     """
125             package androidx.foo
126 
127             import android.os.Build
128             import androidx.annotation.DeprecatedSinceApi
129 
130             @DeprecatedSinceApi(29)
131             fun forceEnablePlatformTracing() {
132                 val method = android.os.Trace::class.java.getMethod(
133                     "setAppTracingAllowed",
134                     Boolean::class.javaPrimitiveType
135                 )
136                 method.invoke(null, true)
137             }
138         """
139                         .trimIndent()
140                 ),
141                 Stubs.DeprecatedSinceApi
142             )
143 
144         check(*input).expectClean()
145     }
146 
147     @Test
Checked reflection using @DeprecatedSinceApi classnull148     fun `Checked reflection using @DeprecatedSinceApi class`() {
149         val input =
150             arrayOf(
151                 java(
152                     """
153             package androidx.foo;
154 
155             import android.os.Build;
156             import androidx.annotation.DeprecatedSinceApi;
157 
158             public class OuterClass {
159                 public static void doCheckedReflection() {
160                     if (Build.VERSION.SDK_INT < 29) {
161                         PreApi29Impl.forceEnablePlatformTracing();
162                     }
163                 }
164 
165                 @DeprecatedSinceApi(29)
166                 static class PreApi29Impl {
167                     public static void forceEnablePlatformTracing() {
168                         android.os.Trace.class.getMethod(
169                             "setAppTracingAllowed",
170                             Boolean.class
171                         ).invoke(null, true);
172                     }
173                 }
174             }
175         """
176                         .trimIndent()
177                 ),
178                 Stubs.DeprecatedSinceApi
179             )
180 
181         check(*input).expectClean()
182     }
183 
184     @Test
Checked reflection using Kotlin range checknull185     fun `Checked reflection using Kotlin range check`() {
186         val input =
187             arrayOf(
188                 kotlin(
189                     """
190                 package androidx.foo
191 
192                 import android.os.Build
193 
194                 fun forceEnablePlatformTracing() {
195                     if (Build.VERSION.SDK_INT in 18..28) {
196                         val method = android.os.Trace::class.java.getMethod(
197                             "setAppTracingAllowed",
198                             Boolean::class.javaPrimitiveType
199                         )
200                         method.invoke(null, true)
201                     }
202                 }
203             """
204                         .trimIndent()
205                 )
206             )
207 
208         check(*input).expectClean()
209     }
210 }
211