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