/* * Copyright 2021 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. */ @file:Suppress("UnstableApiUsage") package androidx.build.lint import androidx.build.lint.Stubs.Companion.RestrictTo import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.JUnit4 @RunWith(JUnit4::class) class BanUncheckedReflectionTest : AbstractLintDetectorTest( useDetector = BanUncheckedReflection(), useIssues = listOf(BanUncheckedReflection.ISSUE), stubs = arrayOf(Stubs.ChecksSdkIntAtLeast), ) { @Test fun `Detection of unchecked reflection in real-world Java sources`() { val input = arrayOf(javaSample("androidx.sample.core.app.ActivityRecreator"), RestrictTo) val expected = """ 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] performStopActivity3ParamsMethod.invoke(activityThread, ^ 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] performStopActivity2ParamsMethod.invoke(activityThread, ^ 2 errors, 0 warnings """ .trimIndent() check(*input).expect(expected) } @Test fun `Detection of unchecked reflection in real-world Kotlin sources`() { val input = arrayOf(ktSample("androidx.sample.core.app.ActivityRecreatorKt"), RestrictTo) val expected = """ 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] performStopActivity3ParamsMethod!!.invoke( ^ 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] performStopActivity2ParamsMethod!!.invoke(activityThread, token, false) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 2 errors, 0 warnings """ .trimIndent() lint().files(*input).run().expect(expected) } @Test fun `Checked reflection in real-world Java sources`() { val input = arrayOf(javaSample("androidx.sample.core.app.ActivityRecreatorChecked"), RestrictTo) val expected = """ No warnings. """ .trimIndent() check(*input).expect(expected) } @Test fun `Checked reflection in real-world Kotlin sources`() { val input = arrayOf(ktSample("androidx.sample.core.app.ActivityRecreatorKtChecked"), RestrictTo) check(*input).expectClean() } @Test fun `Checked reflection using preceding if with return`() { val input = kotlin( """ package androidx.foo import android.os.Build fun forceEnablePlatformTracing() { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) return if (Build.VERSION.SDK_INT >= 29) return val method = android.os.Trace::class.java.getMethod( "setAppTracingAllowed", Boolean::class.javaPrimitiveType ) method.invoke(null, true) } """ .trimIndent() ) check(input).expectClean() } @Test fun `Checked reflection using @DeprecatedSinceApi method`() { val input = arrayOf( kotlin( """ package androidx.foo import android.os.Build import androidx.annotation.DeprecatedSinceApi @DeprecatedSinceApi(29) fun forceEnablePlatformTracing() { val method = android.os.Trace::class.java.getMethod( "setAppTracingAllowed", Boolean::class.javaPrimitiveType ) method.invoke(null, true) } """ .trimIndent() ), Stubs.DeprecatedSinceApi ) check(*input).expectClean() } @Test fun `Checked reflection using @DeprecatedSinceApi class`() { val input = arrayOf( java( """ package androidx.foo; import android.os.Build; import androidx.annotation.DeprecatedSinceApi; public class OuterClass { public static void doCheckedReflection() { if (Build.VERSION.SDK_INT < 29) { PreApi29Impl.forceEnablePlatformTracing(); } } @DeprecatedSinceApi(29) static class PreApi29Impl { public static void forceEnablePlatformTracing() { android.os.Trace.class.getMethod( "setAppTracingAllowed", Boolean.class ).invoke(null, true); } } } """ .trimIndent() ), Stubs.DeprecatedSinceApi ) check(*input).expectClean() } @Test fun `Checked reflection using Kotlin range check`() { val input = arrayOf( kotlin( """ package androidx.foo import android.os.Build fun forceEnablePlatformTracing() { if (Build.VERSION.SDK_INT in 18..28) { val method = android.os.Trace::class.java.getMethod( "setAppTracingAllowed", Boolean::class.javaPrimitiveType ) method.invoke(null, true) } } """ .trimIndent() ) ) check(*input).expectClean() } }