FIR checker: differentiate UNSAFE_CALL from INAPPLICABLE_CANDIDATE

To do so, inside the root cause of inapplicable candidate errors,
we will record expected/actual type of receiver, if any.
That will help identifying inapplicable calls on nullable receiver.
This commit is contained in:
Jinseong Jeon
2021-01-26 16:03:27 -08:00
committed by Mikhail Glukhikh
parent 4b823eca21
commit e72ddbcbfe
275 changed files with 1426 additions and 1388 deletions
@@ -3,7 +3,7 @@
fun testIsNullOrBlank(x: String?) {
if (x.isNullOrBlank()) {
x.<!INAPPLICABLE_CANDIDATE!>length<!>
x.<!UNSAFE_CALL!>length<!>
}
else {
x.length
@@ -15,6 +15,6 @@ fun testIsNotNullOrBlank(x: String?) {
x.length
}
x.<!INAPPLICABLE_CANDIDATE!>length<!>
x.<!UNSAFE_CALL!>length<!>
}
@@ -3,7 +3,7 @@
fun testIsNullOrEmpty(x: String?) {
if (x.isNullOrEmpty()) {
x.<!INAPPLICABLE_CANDIDATE!>length<!>
x.<!UNSAFE_CALL!>length<!>
}
else {
x.length
@@ -15,6 +15,6 @@ fun testIsNotNullOrEmpty(x: String?) {
x.length
}
x.<!INAPPLICABLE_CANDIDATE!>length<!>
x.<!UNSAFE_CALL!>length<!>
}
@@ -17,7 +17,7 @@ fun CharSequence?.isNullOrEmpty(): Boolean {
fun smartcastOnReceiver(s: String?) {
with(s) {
if (isNullOrEmpty()) {
<!INAPPLICABLE_CANDIDATE!>length<!>
<!UNSAFE_CALL!>length<!>
}
else {
length
@@ -32,7 +32,7 @@ fun mixedReceiver(s: String?) {
}
} else {
with(s) {
<!INAPPLICABLE_CANDIDATE!>length<!>
<!UNSAFE_CALL!>length<!>
}
}
}
@@ -17,7 +17,7 @@ fun CharSequence?.isNullOrEmpty(): Boolean {
fun smartcastOnReceiver(s: String?) {
with(s) {
if (isNullOrEmpty()) {
<!INAPPLICABLE_CANDIDATE!>length<!>
<!UNSAFE_CALL!>length<!>
}
else {
length
@@ -32,7 +32,7 @@ fun mixedReceiver(s: String?) {
}
} else {
with(s) {
<!INAPPLICABLE_CANDIDATE!>length<!>
<!UNSAFE_CALL!>length<!>
}
}
}
@@ -14,7 +14,7 @@ fun nullWhenNull(x: Int?): Int? {
fun testNullWhenNull(x: Int?) {
if (nullWhenNull(x) == null) {
x.<!INAPPLICABLE_CANDIDATE!>dec<!>()
x.<!UNSAFE_CALL!>dec<!>()
}
else {
x.dec()
@@ -24,10 +24,10 @@ fun testNullWhenNull(x: Int?) {
x.dec()
}
else {
x.<!INAPPLICABLE_CANDIDATE!>dec<!>()
x.<!UNSAFE_CALL!>dec<!>()
}
x.<!INAPPLICABLE_CANDIDATE!>dec<!>()
x.<!UNSAFE_CALL!>dec<!>()
}
// NB. it is the same function as `nullWhenNull`, but annotations specifies other facet of the function behaviour
@@ -43,15 +43,15 @@ fun testNotNullWhenNotNull (x: Int?) {
x == null
}
else {
x.<!INAPPLICABLE_CANDIDATE!>dec<!>()
x.<!UNSAFE_CALL!>dec<!>()
}
if (notNullWhenNotNull(x) != null) {
x.<!INAPPLICABLE_CANDIDATE!>dec<!>()
x.<!UNSAFE_CALL!>dec<!>()
}
else {
x == null
}
x.<!INAPPLICABLE_CANDIDATE!>dec<!>()
x.<!UNSAFE_CALL!>dec<!>()
}
@@ -20,28 +20,28 @@ fun myEqualsNotNull(x: Int?): Boolean {
}
fun testBasicEquals(x: Int?) {
x.<!INAPPLICABLE_CANDIDATE!>inc<!>()
x.<!UNSAFE_CALL!>inc<!>()
if (myEqualsNull(x)) {
x.<!INAPPLICABLE_CANDIDATE!>inc<!>()
x.<!UNSAFE_CALL!>inc<!>()
}
else {
x.inc()
}
x.<!INAPPLICABLE_CANDIDATE!>inc<!>()
x.<!UNSAFE_CALL!>inc<!>()
}
fun testBasicNotEquals(x: Int?) {
x.<!INAPPLICABLE_CANDIDATE!>inc<!>()
x.<!UNSAFE_CALL!>inc<!>()
if (myEqualsNotNull(x)) {
x.inc()
}
else {
x.<!INAPPLICABLE_CANDIDATE!>inc<!>()
x.<!UNSAFE_CALL!>inc<!>()
}
x.<!INAPPLICABLE_CANDIDATE!>inc<!>()
x.<!UNSAFE_CALL!>inc<!>()
}
@@ -14,7 +14,7 @@ fun Any?.isNull(): Boolean {
fun smartcastOnReceiver(x: Int?) {
if (x.isNull()) {
x.<!INAPPLICABLE_CANDIDATE!>inc<!>()
x.<!UNSAFE_CALL!>inc<!>()
}
else {
x.dec()
@@ -26,10 +26,10 @@ class UnstableReceiver {
fun smartcastOnUnstableReceiver() {
if (x.isNull()) {
x.<!INAPPLICABLE_CANDIDATE!>inc<!>()
x.<!UNSAFE_CALL!>inc<!>()
}
else {
x.<!INAPPLICABLE_CANDIDATE!>dec<!>()
x.<!UNSAFE_CALL!>dec<!>()
}
}
}
@@ -6,6 +6,6 @@ import kotlin.contracts.*
fun f3(value: String?) {
if (!value.isNullOrEmpty() is Boolean) {
value.<!INAPPLICABLE_CANDIDATE!>length<!>
value.<!UNSAFE_CALL!>length<!>
}
}
@@ -1,6 +1,6 @@
val foo = iterator {
yield(0)
val nullable: String? = null
nullable.<!INAPPLICABLE_CANDIDATE!>length<!>
nullable.<!INAPPLICABLE_CANDIDATE!>get<!>(2)
nullable.<!UNSAFE_CALL!>length<!>
nullable.<!UNSAFE_CALL!>get<!>(2)
}
@@ -87,7 +87,7 @@ fun test() {
this@with.<!UNRESOLVED_REFERENCE!>yield<!>("")
yield2("")
this@with.<!INAPPLICABLE_CANDIDATE!>yield2<!>("")
this@with.<!UNSAFE_CALL!>yield2<!>("")
}
}
}
@@ -24,7 +24,7 @@ public class StaticOverrides {
// FILE: test.kt
fun test() {
StaticOverrides.A.<!AMBIGUITY!>foo<!> {} <!INAPPLICABLE_CANDIDATE!>checkType<!> { <!INAPPLICABLE_CANDIDATE!>_<!><Boolean>() }
StaticOverrides.A.<!AMBIGUITY!>foo<!> {} <!UNSAFE_CALL!>checkType<!> { <!INAPPLICABLE_CANDIDATE!>_<!><Boolean>() }
StaticOverrides.B.foo {} checkType { _<String>() }
StaticOverrides.C.<!AMBIGUITY!>foo<!> {} <!INAPPLICABLE_CANDIDATE!>checkType<!> { <!INAPPLICABLE_CANDIDATE!>_<!><Boolean>() }
StaticOverrides.C.<!AMBIGUITY!>foo<!> {} <!UNSAFE_CALL!>checkType<!> { <!INAPPLICABLE_CANDIDATE!>_<!><Boolean>() }
}
@@ -20,6 +20,6 @@ class Foo {
// FILE: 1.kt
fun test() {
Foo().<!AMBIGUITY!>foo<!> {} <!INAPPLICABLE_CANDIDATE!>checkType<!> { <!INAPPLICABLE_CANDIDATE!>_<!><Int>() }
Foo().<!AMBIGUITY!>bar<!> {} <!INAPPLICABLE_CANDIDATE!>checkType<!> { <!INAPPLICABLE_CANDIDATE!>_<!><String>() }
Foo().<!AMBIGUITY!>foo<!> {} <!UNSAFE_CALL!>checkType<!> { <!INAPPLICABLE_CANDIDATE!>_<!><Int>() }
Foo().<!AMBIGUITY!>bar<!> {} <!UNSAFE_CALL!>checkType<!> { <!INAPPLICABLE_CANDIDATE!>_<!><String>() }
}
@@ -21,6 +21,6 @@ class Foo {
// FILE: 1.kt
fun test() {
Foo().<!AMBIGUITY!>foo<!> {} <!INAPPLICABLE_CANDIDATE!>checkType<!> { <!INAPPLICABLE_CANDIDATE!>_<!><Int>() }
Foo().<!AMBIGUITY!>bar<!> {} <!INAPPLICABLE_CANDIDATE!>checkType<!> { <!INAPPLICABLE_CANDIDATE!>_<!><Int>() }
Foo().<!AMBIGUITY!>foo<!> {} <!UNSAFE_CALL!>checkType<!> { <!INAPPLICABLE_CANDIDATE!>_<!><Int>() }
Foo().<!AMBIGUITY!>bar<!> {} <!UNSAFE_CALL!>checkType<!> { <!INAPPLICABLE_CANDIDATE!>_<!><Int>() }
}
@@ -16,5 +16,5 @@ public class Foo {
// FILE: 1.kt
fun bar() {
Foo().<!AMBIGUITY!>test<!> {} <!INAPPLICABLE_CANDIDATE!>checkType<!> { <!INAPPLICABLE_CANDIDATE!>_<!><String>() }
Foo().<!AMBIGUITY!>test<!> {} <!UNSAFE_CALL!>checkType<!> { <!INAPPLICABLE_CANDIDATE!>_<!><String>() }
}
@@ -18,5 +18,5 @@ public class Foo {
// FILE: 1.kt
fun bar() {
Foo().<!AMBIGUITY!>test<!> {} <!INAPPLICABLE_CANDIDATE!>checkType<!> { <!INAPPLICABLE_CANDIDATE!>_<!><Int>() }
Foo().<!AMBIGUITY!>test<!> {} <!UNSAFE_CALL!>checkType<!> { <!INAPPLICABLE_CANDIDATE!>_<!><Int>() }
}
@@ -3,5 +3,5 @@
fun foo(y: String?) {
var x: String? = null
y?.let { x = it }
x.<!INAPPLICABLE_CANDIDATE!>length<!> // Smart cast is not possible
x.<!UNSAFE_CALL!>length<!> // Smart cast is not possible
}
@@ -5,7 +5,7 @@ fun bar(z: String?) = z
fun foo(y: String?) {
var x: String? = ""
if (x != null) {
bar(y?.let { x = null; it }).<!INAPPLICABLE_CANDIDATE!>length<!>
bar(y?.let { x = null; it }).<!UNSAFE_CALL!>length<!>
x.length // Smart cast is not possible
}
}
@@ -2,7 +2,7 @@ fun foo(y: String?) {
var x: String? = ""
if (x != null) {
with(y?.let { x = null; it }) {
this.<!INAPPLICABLE_CANDIDATE!>length<!>
this.<!UNSAFE_CALL!>length<!>
x.length
}
x.length
@@ -15,7 +15,7 @@ fun test2() {
catch (e: ExcB) {
10
}
s.<!INAPPLICABLE_CANDIDATE!>length<!>
s.<!UNSAFE_CALL!>length<!>
}
fun test3() {
@@ -28,7 +28,7 @@ fun test3() {
catch (e: ExcB) {
return
}
s.<!INAPPLICABLE_CANDIDATE!>length<!>
s.<!UNSAFE_CALL!>length<!>
}
fun test4() {
@@ -41,7 +41,7 @@ fun test4() {
finally {
""
}
s.<!INAPPLICABLE_CANDIDATE!>length<!>
s.<!UNSAFE_CALL!>length<!>
}
fun test5() {
@@ -54,7 +54,7 @@ fun test5() {
finally {
return
}
s.<!INAPPLICABLE_CANDIDATE!>length<!>
s.<!UNSAFE_CALL!>length<!>
}
fun test6() {
@@ -67,7 +67,7 @@ fun test6() {
catch (e: ExcB) {
return
}
s.<!INAPPLICABLE_CANDIDATE!>length<!>
s.<!UNSAFE_CALL!>length<!>
}
fun test7() {
@@ -80,7 +80,7 @@ fun test7() {
catch (e: ExcB) {
""
}
s.<!INAPPLICABLE_CANDIDATE!>length<!>
s.<!UNSAFE_CALL!>length<!>
}
fun test8() {
@@ -89,7 +89,7 @@ fun test8() {
} catch (e: ExcA) {
null
}
s.<!INAPPLICABLE_CANDIDATE!>length<!>
s.<!UNSAFE_CALL!>length<!>
}
fun test9() {
@@ -13,11 +13,11 @@ fun test0(x: Int?) {
if (x != null) {
x.inc()
y.<!INAPPLICABLE_CANDIDATE!>inc<!>()
y.<!UNSAFE_CALL!>inc<!>()
}
if (y != null) {
x.<!INAPPLICABLE_CANDIDATE!>inc<!>()
x.<!UNSAFE_CALL!>inc<!>()
y.inc()
}
}
@@ -32,11 +32,11 @@ fun test1(x: Int?) {
if (x != null) {
x.inc()
y.<!INAPPLICABLE_CANDIDATE!>inc<!>()
y.<!UNSAFE_CALL!>inc<!>()
}
if (y != null) {
x.<!INAPPLICABLE_CANDIDATE!>inc<!>()
x.<!UNSAFE_CALL!>inc<!>()
y.inc()
}
}
@@ -51,11 +51,11 @@ fun test2(x: Int?) {
if (x != null) {
x.inc()
y.<!INAPPLICABLE_CANDIDATE!>inc<!>()
y.<!UNSAFE_CALL!>inc<!>()
}
if (y != null) {
x.<!INAPPLICABLE_CANDIDATE!>inc<!>()
x.<!UNSAFE_CALL!>inc<!>()
y.inc()
}
}
@@ -69,11 +69,11 @@ fun test3(x: Int?) {
if (x != null) {
x.inc()
y.<!INAPPLICABLE_CANDIDATE!>inc<!>()
y.<!UNSAFE_CALL!>inc<!>()
}
if (y != null) {
x.<!INAPPLICABLE_CANDIDATE!>inc<!>()
x.<!UNSAFE_CALL!>inc<!>()
y.inc()
}
}
@@ -91,11 +91,11 @@ fun test5(x: Int?) {
if (x != null) {
x.inc()
y.<!INAPPLICABLE_CANDIDATE!>inc<!>()
y.<!UNSAFE_CALL!>inc<!>()
}
if (y != null) {
x.<!INAPPLICABLE_CANDIDATE!>inc<!>()
x.<!UNSAFE_CALL!>inc<!>()
y.inc()
}
}
@@ -15,8 +15,8 @@ fun test1(s: String?) {
catch (e: Exception) {
requireNotNull(s)
}
t2.<!INAPPLICABLE_CANDIDATE!>not<!>()
s.<!INAPPLICABLE_CANDIDATE!>length<!>
t2.<!UNSAFE_CALL!>not<!>()
s.<!UNSAFE_CALL!>length<!>
}
}
@@ -74,7 +74,7 @@ fun test5(s: String?) {
catch (e: ExcB) {
}
s.<!INAPPLICABLE_CANDIDATE!>length<!>
s.<!UNSAFE_CALL!>length<!>
}
fun test6(s: String?) {
@@ -16,8 +16,8 @@ fun test1(s: String?) {
catch (e: Exception) {
requireNotNull(s)
}
t2.<!INAPPLICABLE_CANDIDATE!>not<!>()
s.<!INAPPLICABLE_CANDIDATE!>length<!>
t2.<!UNSAFE_CALL!>not<!>()
s.<!UNSAFE_CALL!>length<!>
}
}
@@ -75,7 +75,7 @@ fun test5(s: String?) {
catch (e: ExcB) {
}
s.<!INAPPLICABLE_CANDIDATE!>length<!>
s.<!UNSAFE_CALL!>length<!>
}
fun test6(s: String?) {
@@ -13,15 +13,15 @@ fun test1() {
try {
x = null
} catch (e: Exception) {
x.<!INAPPLICABLE_CANDIDATE!>length<!> // smartcast shouldn't be allowed (OOME could happen after `x = null`)
x.<!UNSAFE_CALL!>length<!> // smartcast shouldn't be allowed (OOME could happen after `x = null`)
throw e
}
finally {
// smartcast shouldn't be allowed, `x = null` could've happened
x.<!INAPPLICABLE_CANDIDATE!>length<!>
x.<!UNSAFE_CALL!>length<!>
}
// smartcast shouldn't be allowed, `x = null` could've happened
x.<!INAPPLICABLE_CANDIDATE!>length<!>
x.<!UNSAFE_CALL!>length<!>
}
// With old DFA of try/catch info about unsound smartcasts after try
@@ -33,12 +33,12 @@ fun test2() {
try {
x = null
} catch (e: Exception) {
x.<!INAPPLICABLE_CANDIDATE!>length<!>
x.<!UNSAFE_CALL!>length<!>
}
finally {
x.<!INAPPLICABLE_CANDIDATE!>length<!>
x.<!UNSAFE_CALL!>length<!>
}
x.<!INAPPLICABLE_CANDIDATE!>length<!>
x.<!UNSAFE_CALL!>length<!>
}
fun test3() {
@@ -49,7 +49,7 @@ fun test3() {
} catch (e: Exception) {
t2 = null
}
t2.<!INAPPLICABLE_CANDIDATE!>not<!>() // wrong smartcast, NPE
t2.<!UNSAFE_CALL!>not<!>() // wrong smartcast, NPE
}
}
@@ -59,7 +59,7 @@ fun test4() {
try {
t2 = null
} finally { }
t2.<!INAPPLICABLE_CANDIDATE!>not<!>() // wrong smartcast, NPE
t2.<!UNSAFE_CALL!>not<!>() // wrong smartcast, NPE
}
}
@@ -80,10 +80,10 @@ fun test5() {
}
finally {
s1.length
s2.<!INAPPLICABLE_CANDIDATE!>length<!>
s2.<!UNSAFE_CALL!>length<!>
}
s1.length
s2.<!INAPPLICABLE_CANDIDATE!>length<!>
s2.<!UNSAFE_CALL!>length<!>
}
fun test6(s1: String?, s2: String?) {
@@ -97,10 +97,10 @@ fun test6(s1: String?, s2: String?) {
return
}
finally {
s.<!INAPPLICABLE_CANDIDATE!>length<!>
s.<!UNSAFE_CALL!>length<!>
requireNotNull(s2)
}
s.<!INAPPLICABLE_CANDIDATE!>length<!>
s1.<!INAPPLICABLE_CANDIDATE!>length<!>
s.<!UNSAFE_CALL!>length<!>
s1.<!UNSAFE_CALL!>length<!>
s2.length
}
@@ -14,15 +14,15 @@ fun test1() {
try {
x = null
} catch (e: Exception) {
x.<!INAPPLICABLE_CANDIDATE!>length<!> // smartcast shouldn't be allowed (OOME could happen after `x = null`)
x.<!UNSAFE_CALL!>length<!> // smartcast shouldn't be allowed (OOME could happen after `x = null`)
throw e
}
finally {
// smartcast shouldn't be allowed, `x = null` could've happened
x.<!INAPPLICABLE_CANDIDATE!>length<!>
x.<!UNSAFE_CALL!>length<!>
}
// smartcast shouldn't be allowed, `x = null` could've happened
x.<!INAPPLICABLE_CANDIDATE!>length<!>
x.<!UNSAFE_CALL!>length<!>
}
// With old DFA of try/catch info about unsound smartcasts after try
@@ -35,12 +35,12 @@ fun test2() {
x = null
} catch (e: Exception) {
// BAD
x.<!INAPPLICABLE_CANDIDATE!>length<!>
x.<!UNSAFE_CALL!>length<!>
}
finally {
x.<!INAPPLICABLE_CANDIDATE!>length<!>
x.<!UNSAFE_CALL!>length<!>
}
x.<!INAPPLICABLE_CANDIDATE!>length<!>
x.<!UNSAFE_CALL!>length<!>
}
fun test3() {
@@ -51,7 +51,7 @@ fun test3() {
} catch (e: Exception) {
t2 = null
}
t2.<!INAPPLICABLE_CANDIDATE!>not<!>() // wrong smartcast, NPE
t2.<!UNSAFE_CALL!>not<!>() // wrong smartcast, NPE
}
}
@@ -61,7 +61,7 @@ fun test4() {
try {
t2 = null
} finally { }
t2.<!INAPPLICABLE_CANDIDATE!>not<!>() // wrong smartcast, NPE
t2.<!UNSAFE_CALL!>not<!>() // wrong smartcast, NPE
}
}
@@ -82,10 +82,10 @@ fun test5() {
}
finally {
s1.length
s2.<!INAPPLICABLE_CANDIDATE!>length<!>
s2.<!UNSAFE_CALL!>length<!>
}
s1.length
s2.<!INAPPLICABLE_CANDIDATE!>length<!>
s2.<!UNSAFE_CALL!>length<!>
}
fun test6(s1: String?, s2: String?) {
@@ -99,10 +99,10 @@ fun test6(s1: String?, s2: String?) {
return
}
finally {
s.<!INAPPLICABLE_CANDIDATE!>length<!>
s.<!UNSAFE_CALL!>length<!>
requireNotNull(s2)
}
s.<!INAPPLICABLE_CANDIDATE!>length<!>
s1.<!INAPPLICABLE_CANDIDATE!>length<!>
s.<!UNSAFE_CALL!>length<!>
s1.<!UNSAFE_CALL!>length<!>
s2.length
}