Skip to content

Commit 0348d04

Browse files
Fix a crash where we mixed up void and Unit (#174)
We need to use a helper to move statements from one function to another, otherwise early returns break. Co-authored-by: Jesse Wilson <jwilson@squareup.com>
1 parent bd70305 commit 0348d04

File tree

4 files changed

+98
-5
lines changed

4 files changed

+98
-5
lines changed

TROUBLESHOOTING.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
Burst Troubleshooting
2+
=====================
3+
4+
NoSuchMethodError
5+
-----------------
6+
7+
```
8+
java.lang.NoSuchMethodError: 'void com.example.AbstractFooTest.intercept(app.cash.burst.TestFunction)'
9+
at com.example.StandardFooTest.intercept(StandardFooTest.kt:100)
10+
```
11+
12+
You’ll get this if the Burst Gradle Plugin wasn’t applied to a class that needs it. Update the
13+
corresponding `build.gradle.kts` to apply it:
14+
15+
```kotlin
16+
plugins {
17+
id("app.cash.burst")
18+
...
19+
}
20+
```

burst-kotlin-plugin-tests/src/test/kotlin/app/cash/burst/kotlin/TestInterceptorKotlinPluginTest.kt

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1480,4 +1480,59 @@ class TestInterceptorKotlinPluginTest {
14801480
"running BaseTest.happyPath",
14811481
)
14821482
}
1483+
1484+
/**
1485+
* Our initial implementation failed bytecode validation when a function returned 'void' when the
1486+
* interceptor required it to return 'Unit'.
1487+
*/
1488+
@Test
1489+
fun earlyReturn() {
1490+
val log = BurstTester(
1491+
packageName = "com.example",
1492+
).compileAndRun(
1493+
SourceFile.kotlin(
1494+
"Main.kt",
1495+
"""
1496+
package com.example
1497+
1498+
import app.cash.burst.InterceptTest
1499+
import app.cash.burst.TestFunction
1500+
import app.cash.burst.TestInterceptor
1501+
import kotlin.test.AfterTest
1502+
import kotlin.test.BeforeTest
1503+
import kotlin.test.Test
1504+
1505+
class SampleTest {
1506+
@InterceptTest
1507+
val interceptor = object : TestInterceptor {
1508+
override fun intercept(testFunction: TestFunction) {
1509+
log("intercepting")
1510+
testFunction()
1511+
log("intercepted")
1512+
}
1513+
}
1514+
1515+
@Test
1516+
fun happyPath() {
1517+
if (true) {
1518+
log("early return")
1519+
return
1520+
}
1521+
log("no early return")
1522+
}
1523+
}
1524+
1525+
fun main(vararg args: String) {
1526+
SampleTest().happyPath()
1527+
}
1528+
""",
1529+
),
1530+
)
1531+
1532+
assertThat(log).containsExactly(
1533+
"intercepting",
1534+
"early return",
1535+
"intercepted",
1536+
)
1537+
}
14831538
}

burst-kotlin-plugin/src/main/kotlin/app/cash/burst/kotlin/InterceptorInjector.kt

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@ import org.jetbrains.kotlin.ir.util.defaultType
5050
import org.jetbrains.kotlin.ir.util.packageFqName
5151
import org.jetbrains.kotlin.ir.util.parentClassOrNull
5252
import org.jetbrains.kotlin.ir.util.patchDeclarationParents
53-
import org.jetbrains.kotlin.ir.util.statements
5453
import org.jetbrains.kotlin.ir.util.superClass
5554
import org.jetbrains.kotlin.name.Name
5655

@@ -364,7 +363,7 @@ internal class InterceptorInjector(
364363
// If there's no function body to rewrite, we're probably looking at a superclass from another
365364
// module. The rewrite won't be emitted anywhere, but we still need to generate its symbols for
366365
// subclasses to call.
367-
val originalBody = original.body ?: return
366+
if (original.body == null) return
368367

369368
if (original.modality != Modality.FINAL && originalParent.modality != Modality.FINAL) {
370369
unexpectedOpenFunction(original)
@@ -380,9 +379,7 @@ internal class InterceptorInjector(
380379
arguments[0] = irString(packageName)
381380
arguments[1] = irString(className)
382381
arguments[2] = irString(original.name.asString())
383-
arguments[3] = localLambda(original.symbol) {
384-
+originalBody.statements
385-
}
382+
arguments[3] = moveBodyToLocalLambda(original)
386383
}
387384

388385
+irCall(

burst-kotlin-plugin/src/main/kotlin/app/cash/burst/kotlin/ir.kt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package app.cash.burst.kotlin
1717

18+
import org.jetbrains.kotlin.backend.common.ir.moveBodyTo
1819
import org.jetbrains.kotlin.backend.common.lower.DeclarationIrBuilder
1920
import org.jetbrains.kotlin.backend.common.lower.irCatch
2021
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocationWithRange
@@ -227,6 +228,26 @@ fun IrBlockBodyBuilder.localLambda(
227228
)
228229
}
229230

231+
/** Moves the body of [original] to a newly-created local lambda. */
232+
fun IrBlockBodyBuilder.moveBodyToLocalLambda(
233+
original: IrSimpleFunction,
234+
): IrFunctionExpressionImpl {
235+
return IrFunctionExpressionImpl(
236+
startOffset = startOffset,
237+
endOffset = endOffset,
238+
type = context.irBuiltIns.functionN(0).typeWith(context.irBuiltIns.unitType),
239+
function = context.irFactory.buildFun {
240+
this.name = Name.special("<anonymous>")
241+
this.returnType = context.irBuiltIns.unitType
242+
this.origin = IrDeclarationOrigin.Companion.LOCAL_FUNCTION_FOR_LAMBDA
243+
this.visibility = DescriptorVisibilities.LOCAL
244+
}.apply {
245+
body = original.moveBodyTo(this, mapOf())
246+
},
247+
origin = IrStatementOrigin.Companion.LAMBDA,
248+
)
249+
}
250+
230251
internal fun IrBlockBuilder.irAccumulateFailure(
231252
burstApis: BurstApis,
232253
failure: IrVariable,

0 commit comments

Comments
 (0)