blob: da62c1ee761a92b807d98ab70f541c086ce292d7 [file] [log] [blame]
/*
* Copyright 2023 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 androidx.build.lint
import androidx.build.lint.Stubs.Companion.DoNotInline
import androidx.build.lint.Stubs.Companion.RequiresApi
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
@RunWith(JUnit4::class)
class ImplicitCastVerificationFailureDetectorTest :
AbstractLintDetectorTest(
useDetector = ClassVerificationFailureDetector(),
useIssues = listOf(ClassVerificationFailureDetector.IMPLICIT_CAST_ISSUE),
stubs =
arrayOf(
// AndroidManifest with minSdkVersion=14
manifest().minSdk(14),
RequiresApi,
DoNotInline,
),
) {
@Test
fun `Unsafe implicit cast for method argument`() {
val input =
arrayOf(
java(
"""
package java.androidx;
import android.app.Notification;
import androidx.annotation.DoNotInline;
import androidx.annotation.RequiresApi;
public class UnsafeImplicitCastAsMethodArgumentJava {
@RequiresApi(24)
public void setBuilder(Notification.MessagingStyle style,
Notification.Builder builder) {
Api16Impl.setBuilder(style, builder);
}
@RequiresApi(16)
static class Api16Impl {
private Api16Impl() {}
@DoNotInline
static void setBuilder(Notification.Style style,
Notification.Builder builder) {
style.setBuilder(builder);
}
}
}
"""
.trimIndent()
),
kotlin(
"""
package java.androidx
import android.app.Notification
import androidx.annotation.DoNotInline
import androidx.annotation.RequiresApi
class UnsafeImplicitCastAsMethodArgumentKotlin {
@RequiresApi(24)
fun setBuilder(style: Notification.MessagingStyle,
builder: Notification.Builder) {
Api16Impl.setBuilder(style, builder);
}
@RequiresApi(16)
private object Api16Impl {
@JvmStatic
@DoNotInline
fun setBuilder(style: Notification.Style, builder: Notification.Builder) {
style.setBuilder(builder)
}
}
}
"""
.trimIndent()
),
)
val expected =
"""
src/java/androidx/UnsafeImplicitCastAsMethodArgumentJava.java:11: Error: This expression has type android.app.Notification.MessagingStyle (introduced in API level 24) but it used as type android.app.Notification.Style (introduced in API level 16). Run-time class verification will not be able to validate this implicit cast on devices between these API levels. [ImplicitCastClassVerificationFailure]
Api16Impl.setBuilder(style, builder);
~~~~~
src/java/androidx/UnsafeImplicitCastAsMethodArgumentKotlin.kt:11: Error: This expression has type android.app.Notification.MessagingStyle (introduced in API level 24) but it used as type android.app.Notification.Style (introduced in API level 16). Run-time class verification will not be able to validate this implicit cast on devices between these API levels. [ImplicitCastClassVerificationFailure]
Api16Impl.setBuilder(style, builder);
~~~~~
2 errors, 0 warnings
"""
val expectedFixDiffs =
"""
Fix for src/java/androidx/UnsafeImplicitCastAsMethodArgumentJava.java line 11: Extract to static inner class:
@@ -11 +11
- Api16Impl.setBuilder(style, builder);
+ Api16Impl.setBuilder(Api24Impl.castToStyle(style), builder);
@@ -23 +23
+ @RequiresApi(24)
+ static class Api24Impl {
+ private Api24Impl() {
+ // This class is not instantiable.
+ }
+
+ @DoNotInline
+ static Notification.Style castToStyle(Notification.MessagingStyle messagingStyle) {
+ return messagingStyle;
+ }
+
@@ -24 +35
+ }
"""
check(*input).expect(expected).expectFixDiffs(expectedFixDiffs)
}
@Test
fun `Unsafe implicit cast within catch block`() {
val input =
arrayOf(
java(
"""
package java.androidx;
import android.app.Presentation;
import android.util.Log;
import android.view.WindowManager;
import androidx.annotation.DoNotInline;
import androidx.annotation.RequiresApi;
public class UnsafeImplicitCastInCatchBlockJava {
@RequiresApi(17)
public void tryShowPresentation(Presentation presentation) {
try {
Api17Impl.show(presentation);
} catch (WindowManager.InvalidDisplayException e) {
Log.w("Error", "Couldn't show presentation!", e);
}
}
@RequiresApi(17)
static class Api17Impl {
private Api16Impl() {}
@DoNotInline
static void show(Presentation presentation) {
presentation.show();
}
}
}
"""
.trimIndent()
),
kotlin(
"""
package java.androidx
import android.app.Presentation
import android.util.Log
import android.view.WindowManager
import androidx.annotation.DoNotInline
import androidx.annotation.RequiresApi
class UnsafeImplicitCastInCatchBlockKotlin {
@RequiresApi(17)
fun tryShowPresentation(presentation: Presentation) {
try {
Api17Impl.show(presentation)
} catch (e: WindowManager.InvalidDisplayException) {
Log.w("Error", "Couldn't show presentation!", e)
}
}
@RequiresApi(17)
private object Api17Impl {
@JvmStatic
@DoNotInline
fun show(presentation: Presentation) {
presentation.show()
}
}
}
"""
.trimIndent()
),
)
val expected =
"""
src/java/androidx/UnsafeImplicitCastInCatchBlockJava.java:15: Error: This expression has type android.view.WindowManager.InvalidDisplayException (introduced in API level 17) but it used as type java.lang.Throwable (introduced in API level 1). Run-time class verification will not be able to validate this implicit cast on devices between these API levels. [ImplicitCastClassVerificationFailure]
Log.w("Error", "Couldn't show presentation!", e);
~
src/java/androidx/UnsafeImplicitCastInCatchBlockKotlin.kt:15: Error: This expression has type android.view.WindowManager.InvalidDisplayException (introduced in API level 17) but it used as type java.lang.Throwable (introduced in API level 1). Run-time class verification will not be able to validate this implicit cast on devices between these API levels. [ImplicitCastClassVerificationFailure]
Log.w("Error", "Couldn't show presentation!", e)
~
2 errors, 0 warnings
"""
val expectedFixDiffs =
"""
Fix for src/java/androidx/UnsafeImplicitCastInCatchBlockJava.java line 15: Extract to static inner class:
@@ -15 +15
- Log.w("Error", "Couldn't show presentation!", e);
+ Log.w("Error", "Couldn't show presentation!", Api17Impl.castToThrowable(e));
@@ -26 +26
- }
+ @DoNotInline
+ static java.lang.Throwable castToThrowable(WindowManager.InvalidDisplayException invalidDisplayException) {
+ return invalidDisplayException;
@@ -28 +30
+ }
+ }
"""
check(*input).expect(expected).expectFixDiffs(expectedFixDiffs)
}
@Test
fun `Unsafe implicit cast in assignment statement`() {
val input =
arrayOf(
java(
"""
package java.androidx;
import android.app.Notification;
import androidx.annotation.RequiresApi;
public class UnsafeImplicitCastInAssignmentJava {
Notification.Style style;
@RequiresApi(24)
public void setNotificationStyle(Notification.MessagingStyle messagingStyle) {
style = messagingStyle;
}
}
"""
.trimIndent()
),
kotlin(
"""
package java.androidx
import android.app.Notification
import androidx.annotation.RequiresApi
class UnsafeImplicitCastInAssignmentKotlin {
lateinit var style: Notification.Style
@RequiresApi(24)
fun setNotificationStyle(messagingStyle: Notification.MessagingStyle) {
style = messagingStyle
}
}
"""
.trimIndent()
),
)
val expected =
"""
src/java/androidx/UnsafeImplicitCastInAssignmentJava.java:11: Error: This expression has type android.app.Notification.MessagingStyle (introduced in API level 24) but it used as type android.app.Notification.Style (introduced in API level 16). Run-time class verification will not be able to validate this implicit cast on devices between these API levels. [ImplicitCastClassVerificationFailure]
style = messagingStyle;
~~~~~~~~~~~~~~
src/java/androidx/UnsafeImplicitCastInAssignmentKotlin.kt:11: Error: This expression has type android.app.Notification.MessagingStyle (introduced in API level 24) but it used as type android.app.Notification.Style (introduced in API level 16). Run-time class verification will not be able to validate this implicit cast on devices between these API levels. [ImplicitCastClassVerificationFailure]
style = messagingStyle
~~~~~~~~~~~~~~
2 errors, 0 warnings
"""
val expectedFixDiffs =
"""
Fix for src/java/androidx/UnsafeImplicitCastInAssignmentJava.java line 11: Extract to static inner class:
@@ -11 +11
- style = messagingStyle;
+ style = Api24Impl.castToStyle(messagingStyle);
@@ -13 +13
+ @RequiresApi(24)
+ static class Api24Impl {
+ private Api24Impl() {
+ // This class is not instantiable.
+ }
+
+ @DoNotInline
+ static Notification.Style castToStyle(Notification.MessagingStyle messagingStyle) {
+ return messagingStyle;
+ }
+
@@ -14 +25
+ }
"""
check(*input).expect(expected).expectFixDiffs(expectedFixDiffs)
}
@Test
fun `Unsafe implicit cast on return`() {
val input =
arrayOf(
java(
"""
package java.androidx;
import android.app.Notification;
import androidx.annotation.RequiresApi;
public class ImplicitCastOnReturnJava {
@RequiresApi(24)
public Notification.Style convertStyle(Notification.MessagingStyle style) {
return style;
}
}
"""
.trimIndent()
),
kotlin(
"""
package java.androidx
import android.app.Notification
import androidx.annotation.RequiresApi
class ImplicitCastOnReturnKotlin {
@RequiresApi(24)
fun convertStyle(style: Notification.MessagingStyle): Notification.Style {
return style
}
}
"""
.trimIndent()
),
)
val expected =
"""
src/java/androidx/ImplicitCastOnReturnJava.java:9: Error: This expression has type android.app.Notification.MessagingStyle (introduced in API level 24) but it used as type android.app.Notification.Style (introduced in API level 16). Run-time class verification will not be able to validate this implicit cast on devices between these API levels. [ImplicitCastClassVerificationFailure]
return style;
~~~~~
src/java/androidx/ImplicitCastOnReturnKotlin.kt:9: Error: This expression has type android.app.Notification.MessagingStyle (introduced in API level 24) but it used as type android.app.Notification.Style (introduced in API level 16). Run-time class verification will not be able to validate this implicit cast on devices between these API levels. [ImplicitCastClassVerificationFailure]
return style
~~~~~
2 errors, 0 warnings
"""
val expectedFixDiffs =
"""
Fix for src/java/androidx/ImplicitCastOnReturnJava.java line 9: Extract to static inner class:
@@ -9 +9
- return style;
+ return Api24Impl.castToStyle(style);
@@ -11 +11
+ @RequiresApi(24)
+ static class Api24Impl {
+ private Api24Impl() {
+ // This class is not instantiable.
+ }
+
+ @DoNotInline
+ static Notification.Style castToStyle(Notification.MessagingStyle messagingStyle) {
+ return messagingStyle;
+ }
+
@@ -12 +23
+ }
"""
check(*input).expect(expected).expectFixDiffs(expectedFixDiffs)
}
@Test
fun `Unsafe implicit cast of method call result`() {
val input =
arrayOf(
java(
"""
package java.androidx;
import android.graphics.drawable.AdaptiveIconDrawable;
import android.graphics.drawable.Drawable;
import androidx.annotation.DoNotInline;
import androidx.annotation.RequiresApi;
public class ImplicitCastOfMethodCallResultJava {
@RequiresApi(26)
public Drawable createAdaptiveIconDrawable() {
return Api26Impl.createAdaptiveIconDrawable(null, null);
}
@RequiresApi(26)
static class Api26Impl {
private Api26Impl() {}
@DoNotInline
static AdaptiveIconDrawable createAdaptiveIconDrawable(
Drawable backgroundDrawable, Drawable foregroundDrawable) {
return new AdaptiveIconDrawable(backgroundDrawable, foregroundDrawable);
}
}
}
"""
.trimIndent()
),
kotlin(
"""
package java.androidx
import android.graphics.drawable.AdaptiveIconDrawable
import android.graphics.drawable.Drawable
import androidx.annotation.DoNotInline
import androidx.annotation.RequiresApi
class ImplicitCastOfMethodCallResultKotlin {
@RequiresApi(26)
fun createAdaptiveIconDrawable(): Drawable =
Api26Impl.createAdaptiveIconDrawable(null, null)
@RequiresApi(26)
object Api26Impl {
@JvmStatic
@DoNotInline
fun createAdaptiveIconDrawable(backgroundDrawable: Drawable,
foregroundDrawable: Drawable): AdaptiveIconDrawable {
return AdaptiveIconDrawable(backgroundDrawable, foregroundDrawable)
}
}
}
"""
.trimIndent()
),
)
val expected =
"""
src/java/androidx/ImplicitCastOfMethodCallResultJava.java:11: Error: This expression has type android.graphics.drawable.AdaptiveIconDrawable (introduced in API level 26) but it used as type android.graphics.drawable.Drawable (introduced in API level 1). Run-time class verification will not be able to validate this implicit cast on devices between these API levels. [ImplicitCastClassVerificationFailure]
return Api26Impl.createAdaptiveIconDrawable(null, null);
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/java/androidx/ImplicitCastOfMethodCallResultKotlin.kt:11: Error: This expression has type android.graphics.drawable.AdaptiveIconDrawable (introduced in API level 26) but it used as type android.graphics.drawable.Drawable (introduced in API level 1). Run-time class verification will not be able to validate this implicit cast on devices between these API levels. [ImplicitCastClassVerificationFailure]
Api26Impl.createAdaptiveIconDrawable(null, null)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2 errors, 0 warnings
"""
val expectedFixDiffs =
"""
Fix for src/java/androidx/ImplicitCastOfMethodCallResultJava.java line 11: Extract to static inner class:
@@ -11 +11
- return Api26Impl.createAdaptiveIconDrawable(null, null);
+ return Api26Impl.castToDrawable(Api26Impl.createAdaptiveIconDrawable(null, null));
@@ -22 +22
- }
+ @DoNotInline
+ static Drawable castToDrawable(AdaptiveIconDrawable adaptiveIconDrawable) {
+ return adaptiveIconDrawable;
@@ -24 +26
+ }
+ }
"""
check(*input).expect(expected).expectFixDiffs(expectedFixDiffs)
}
@Test
fun `Safe implicit cast to object`() {
val input =
arrayOf(
java(
"""
package java.androidx;
import android.app.Notification;
import androidx.annotation.RequiresApi;
public class SafeImplicitCastToObjectJava {
Object style;
@RequiresApi(24)
public void setNotificationStyle(Notification.MessagingStyle messagingStyle) {
style = messagingStyle;
}
}
"""
.trimIndent()
),
kotlin(
"""
package java.androidx
import android.app.Notification
import androidx.annotation.RequiresApi
class SafeImplicitCastToObjectKotlin {
lateinit var style: Any
@RequiresApi(24)
fun setNotificationStyle(messagingStyle: Notification.MessagingStyle) {
style = messagingStyle
}
}
"""
.trimIndent()
),
)
check(*input).expectClean()
}
@Test
fun `Safe explicit cast`() {
val input =
arrayOf(
java(
"""
package java.androidx;
import android.app.Notification;
import androidx.annotation.RequiresApi;
public class SafeExplicitCastJava {
Notification.Style style;
@RequiresApi(24)
public void setNotificationStyle(Notification.MessagingStyle messagingStyle) {
style = (Notification.Style) messagingStyle;
}
}
"""
.trimIndent()
),
kotlin(
"""
package java.androidx
import android.app.Notification
import androidx.annotation.RequiresApi
class SafeExplicitCastKotlin {
lateinit var style: Notification.Style
@RequiresApi(24)
fun setNotificationStyle(messagingStyle: Notification.MessagingStyle) {
style = messagingStyle as Notification.Style
}
}
"""
.trimIndent()
),
)
check(*input).expectClean()
}
@Test
fun `Safe implicit cast between classes from the same API level`() {
val input =
arrayOf(
java(
"""
package java.androidx;
import android.media.tv.BroadcastInfoResponse;
import android.media.tv.PesResponse;
import androidx.annotation.RequiresApi;
public class SafeImplicitCastSameApiLevelJava {
BroadcastInfoResponse response;
@RequiresApi(33)
public void setResponse(PesResponse pesResponse) {
response = pesResponse;
}
}
"""
.trimIndent()
),
kotlin(
"""
package java.androidx
import android.media.tv.BroadcastInfoResponse
import android.media.tv.PesResponse
import androidx.annotation.RequiresApi
class SafeImplicitCastSameApiLevelKotlin {
lateinit var response: BroadcastInfoResponse
@RequiresApi(33)
fun setResponse(pesResponse: PesResponse) {
response = pesResponse
}
}
"""
.trimIndent()
),
)
check(*input).expectClean()
}
@Test
fun `Safe implicit cast within @RequiresApi class`() {
val input =
arrayOf(
java(
"""
package java.androidx;
import android.app.Notification;
import androidx.annotation.DoNotInline;
import androidx.annotation.RequiresApi;
@RequiresApi(24)
static class SafeImplicitCastWithRequiresApiJava {
private SafeImplicitCastWithRequiresApi() {}
@DoNotInline
static void extend(Notification.Builder builder,
Notification.CarExtender extender) {
builder.extend(extender);
}
}
"""
.trimIndent()
),
kotlin(
"""
package java.androidx
import android.app.Notification
import androidx.annotation.DoNotInline;
import androidx.annotation.RequiresApi;
@RequiresApi(24)
object SafeImplicitCastWithRequiresApiKotlin {
@JvmStatic
@DoNotInline
fun extend(builder: Notification.Builder, extender: Notification.CarExtender) {
builder.extend(extender)
}
}
"""
.trimIndent()
),
)
check(*input).expectClean()
}
@Test
fun `Safe implicit cast from null value`() {
val input =
arrayOf(
java(
"""
package java.androidx;
import android.app.Notification;
import androidx.annotation.RequiresApi;
public class SafeCastFromNullJava {
@RequiresApi(24)
public Notification.MessagingStyle getStyle() {
return null;
}
}
"""
.trimIndent()
),
kotlin(
"""
package java.androidx
import android.app.Notification
import androidx.annotation.RequiresApi
class SafeCastFromNullKotlin {
@RequiresApi(24)
fun getStyle(): Notification.MessagingStyle? {
return null
}
}
"""
.trimIndent()
),
)
check(*input).expectClean()
}
@Test
fun `Safe implicit cast from type introduced earlier than the minSdk`() {
val input =
arrayOf(
java(
"""
package java.androidx;
import android.app.FragmentBreadCrumbs;
import android.view.ViewGroup;
public class SafeCastFromPreMinSdkClassJava {
ViewGroup viewGroup;
public void setViewGroup(FragmentBreadCrumbs breadCrumbs) {
// FragmentBreadCrumbs was introduced in API level 11
viewGroup = breadCrumbs;
}
}
"""
.trimIndent()
),
kotlin(
"""
package java.androidx
import android.app.FragmentBreadCrumbs
import android.view.ViewGroup
class SafeCastFromPreMinSdkClassKotlin {
lateinit var viewGroup: ViewGroup
fun setViewGroup(breadCrumbs: FragmentBreadCrumbs) {
// FragmentBreadCrumbs was introduced in API level 11
viewGroup = breadCrumbs
}
}
"""
.trimIndent()
),
)
check(*input).expectClean()
}
@Test
fun `Unsafe implicit cast to varargs method`() {
val input =
arrayOf(
java(
"""
package java.androidx;
import android.icu.number.FormattedNumber;
import android.widget.BaseAdapter;
import androidx.annotation.DoNotInline;
import androidx.annotation.RequiresApi;
public class UnsafeCastToVarargs() {
@RequiresApi(30)
public void callVarArgsMethod(BaseAdapter adapter, FormattedNumber vararg1, FormattedNumber vararg2, FormattedNumber vararg3) {
Api27Impl.setAutofillOptions(adapter, vararg1, vararg2, vararg3);
}
@RequiresApi(27)
static class Api27Impl {
private Api27Impl() {}
@DoNotInline
static void setAutofillOptions(BaseAdapter baseAdapter, CharSequence... options) {
baseAdapter.setAutofillOptions(baseAdapter, options);
}
}
}
"""
.trimIndent()
)
)
val expected =
"""
src/java/androidx/UnsafeCastToVarargs.java:11: Error: This expression has type android.icu.number.FormattedNumber (introduced in API level 30) but it used as type java.lang.CharSequence (introduced in API level 1). Run-time class verification will not be able to validate this implicit cast on devices between these API levels. [ImplicitCastClassVerificationFailure]
Api27Impl.setAutofillOptions(adapter, vararg1, vararg2, vararg3);
~~~~~~~
src/java/androidx/UnsafeCastToVarargs.java:11: Error: This expression has type android.icu.number.FormattedNumber (introduced in API level 30) but it used as type java.lang.CharSequence (introduced in API level 1). Run-time class verification will not be able to validate this implicit cast on devices between these API levels. [ImplicitCastClassVerificationFailure]
Api27Impl.setAutofillOptions(adapter, vararg1, vararg2, vararg3);
~~~~~~~
src/java/androidx/UnsafeCastToVarargs.java:11: Error: This expression has type android.icu.number.FormattedNumber (introduced in API level 30) but it used as type java.lang.CharSequence (introduced in API level 1). Run-time class verification will not be able to validate this implicit cast on devices between these API levels. [ImplicitCastClassVerificationFailure]
Api27Impl.setAutofillOptions(adapter, vararg1, vararg2, vararg3);
~~~~~~~
3 errors, 0 warnings
"""
val expectedFixDiffs =
"""
Fix for src/java/androidx/UnsafeCastToVarargs.java line 11: Extract to static inner class:
@@ -11 +11
- Api27Impl.setAutofillOptions(adapter, vararg1, vararg2, vararg3);
+ Api27Impl.setAutofillOptions(adapter, Api30Impl.castToCharSequence(vararg1), vararg2, vararg3);
@@ -22 +22
+ @RequiresApi(30)
+ static class Api30Impl {
+ private Api30Impl() {
+ // This class is not instantiable.
+ }
+
+ @DoNotInline
+ static java.lang.CharSequence castToCharSequence(FormattedNumber formattedNumber) {
+ return formattedNumber;
+ }
+
@@ -23 +34
+ }
Fix for src/java/androidx/UnsafeCastToVarargs.java line 11: Extract to static inner class:
@@ -11 +11
- Api27Impl.setAutofillOptions(adapter, vararg1, vararg2, vararg3);
+ Api27Impl.setAutofillOptions(adapter, vararg1, Api30Impl.castToCharSequence(vararg2), vararg3);
@@ -22 +22
+ @RequiresApi(30)
+ static class Api30Impl {
+ private Api30Impl() {
+ // This class is not instantiable.
+ }
+
+ @DoNotInline
+ static java.lang.CharSequence castToCharSequence(FormattedNumber formattedNumber) {
+ return formattedNumber;
+ }
+
@@ -23 +34
+ }
Fix for src/java/androidx/UnsafeCastToVarargs.java line 11: Extract to static inner class:
@@ -11 +11
- Api27Impl.setAutofillOptions(adapter, vararg1, vararg2, vararg3);
+ Api27Impl.setAutofillOptions(adapter, vararg1, vararg2, Api30Impl.castToCharSequence(vararg3));
@@ -22 +22
+ @RequiresApi(30)
+ static class Api30Impl {
+ private Api30Impl() {
+ // This class is not instantiable.
+ }
+
+ @DoNotInline
+ static java.lang.CharSequence castToCharSequence(FormattedNumber formattedNumber) {
+ return formattedNumber;
+ }
+
@@ -23 +34
+ }
"""
check(*input).expect(expected).expectFixDiffs(expectedFixDiffs)
}
}