Skip to content

Commit

Permalink
Merge pull request #2 from LachlanMcKee/add-java-code-decompilation
Browse files Browse the repository at this point in the history
Add java code decompilation to unit tests
  • Loading branch information
bnorm authored Jan 2, 2021
2 parents 7fffbd6 + a4b83a0 commit b5cc7ee
Show file tree
Hide file tree
Showing 5 changed files with 328 additions and 48 deletions.
1 change: 1 addition & 0 deletions debuglog-plugin/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ dependencies {
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.7.0")
testImplementation("org.jetbrains.kotlin:kotlin-compiler-embeddable")
testImplementation("com.github.tschuchortdev:kotlin-compile-testing:1.2.6")
testImplementation("org.bitbucket.mstrobel:procyon-compilertools:0.5.36")
}

buildConfig {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,10 +99,15 @@ class DebugLogTransformer(
if (expression.returnTargetSymbol != function.symbol) return super.visitReturn(expression)

return DeclarationIrBuilder(pluginContext, function.symbol).irBlock {
val result = irTemporary(expression.value)
+irDebugExit(function, startTime, irGet(result))
+expression.apply {
value = irGet(result)
if (expression.value.type == typeUnit) {
+irDebugExit(function, startTime)
+expression
} else {
val result = irTemporary(expression.value)
+irDebugExit(function, startTime, irGet(result))
+expression.apply {
value = irGet(result)
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/*
* Copyright (C) 2020 Brian Norman
*
* 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 com.bnorm.debug.log

import com.tschuchort.compiletesting.KotlinCompilation
import com.tschuchort.compiletesting.SourceFile
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test

class IrPluginEarlyReturnTest {

private val earlyReturn = SourceFile.kotlin(
"EarlyReturn.kt", """
import com.bnorm.debug.log.DebugLog
@DebugLog
fun earlyReturn(input: String): String {
if (input == "EARLY_RETURN_1") {
return "Early return - 1"
}
if (input == "EARLY_RETURN_2") {
return "Early return - 2"
}
return "Normal return"
}
"""
)

private val earlyExit = SourceFile.kotlin(
"EarlyExit.kt", """
import com.bnorm.debug.log.DebugLog
@DebugLog
fun earlyExit(input: String) {
if (input == "EARLY_RETURN_1") {
return
}
if (input == "EARLY_RETURN_2") {
return
}
}
"""
)

@Test
fun `IR plugin enabled - with early return`() {
val result = compile(sourceFile = earlyReturn, DebugLogComponentRegistrar(true))
assertEquals(KotlinCompilation.ExitCode.OK, result.exitCode)

assertFunction(result.javaCode("EarlyReturnKt"), "public static final String earlyReturn",
"""
public static final String earlyReturn(@NotNull final String input) {
Intrinsics.checkNotNullParameter(input, "input");
System.out.println((Object)("⇢ earlyReturn(input=" + input + ')'));
final TimeMark markNow = TimeSource.Monotonic.INSTANCE.markNow();
try {
if (Intrinsics.areEqual(input, "EARLY_RETURN_1")) {
System.out.println((Object)("⇠ earlyReturn [" + (Object)Duration.toString-impl(markNow.elapsedNow-UwyO8pc()) + "] = " + "Early return - 1"));
return "Early return - 1";
}
if (Intrinsics.areEqual(input, "EARLY_RETURN_2")) {
System.out.println((Object)("⇠ earlyReturn [" + (Object)Duration.toString-impl(markNow.elapsedNow-UwyO8pc()) + "] = " + "Early return - 2"));
return "Early return - 2";
}
System.out.println((Object)("⇠ earlyReturn [" + (Object)Duration.toString-impl(markNow.elapsedNow-UwyO8pc()) + "] = " + "Normal return"));
return "Normal return";
}
catch (Throwable t) {
System.out.println((Object)("⇠ earlyReturn [" + (Object)Duration.toString-impl(markNow.elapsedNow-UwyO8pc()) + "] = " + t));
throw t;
}
}
""".trimIndent())
}

@Test
fun `IR plugin enabled - with early exit`() {
val result = compile(sourceFile = earlyExit, DebugLogComponentRegistrar(true))
assertEquals(KotlinCompilation.ExitCode.OK, result.exitCode)

assertFunction(result.javaCode("EarlyExitKt"), "public static final void earlyExit",
"""
public static final void earlyExit(@NotNull final String input) {
Intrinsics.checkNotNullParameter(input, "input");
System.out.println((Object)("⇢ earlyExit(input=" + input + ')'));
final TimeMark markNow = TimeSource.Monotonic.INSTANCE.markNow();
try {
if (Intrinsics.areEqual(input, "EARLY_RETURN_1")) {
System.out.println((Object)("⇠ earlyExit [" + (Object)Duration.toString-impl(markNow.elapsedNow-UwyO8pc()) + ']'));
return;
}
if (Intrinsics.areEqual(input, "EARLY_RETURN_2")) {
System.out.println((Object)("⇠ earlyExit [" + (Object)Duration.toString-impl(markNow.elapsedNow-UwyO8pc()) + ']'));
return;
}
System.out.println((Object)("⇠ earlyExit [" + (Object)Duration.toString-impl(markNow.elapsedNow-UwyO8pc()) + ']'));
}
catch (Throwable t) {
System.out.println((Object)("⇠ earlyExit [" + (Object)Duration.toString-impl(markNow.elapsedNow-UwyO8pc()) + "] = " + t));
throw t;
}
}
""".trimIndent())
}
}
101 changes: 57 additions & 44 deletions debuglog-plugin/src/test/kotlin/com/bnorm/debug/log/IrPluginTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,9 @@ package com.bnorm.debug.log

import com.tschuchort.compiletesting.KotlinCompilation
import com.tschuchort.compiletesting.SourceFile
import org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
import java.io.ByteArrayOutputStream
import java.io.PrintStream
import java.lang.reflect.InvocationTargetException

class IrPluginTest {

Expand Down Expand Up @@ -56,6 +52,44 @@ fun doSomething() {
val result = compile(sourceFile = main, DebugLogComponentRegistrar(true))
assertEquals(KotlinCompilation.ExitCode.OK, result.exitCode)

val javaCode = result.javaCode("MainKt")

assertFunction(javaCode, "public static final String greet",
"""
public static final String greet(@NotNull final String greeting, @NotNull final String name) {
Intrinsics.checkNotNullParameter(greeting, "greeting");
Intrinsics.checkNotNullParameter(name, "name");
System.out.println((Object)("⇢ greet(greeting=" + greeting + ", name=" + name + ')'));
final TimeMark markNow = TimeSource.Monotonic.INSTANCE.markNow();
try {
Thread.sleep(15L);
final String string = greeting + ", " + name + '!';
System.out.println((Object)("⇠ greet [" + (Object)Duration.toString-impl(markNow.elapsedNow-UwyO8pc()) + "] = " + string));
return string;
}
catch (Throwable t) {
System.out.println((Object)("⇠ greet [" + (Object)Duration.toString-impl(markNow.elapsedNow-UwyO8pc()) + "] = " + t));
throw t;
}
}
""".trimIndent())

assertFunction(javaCode, "public static final void doSomething",
"""
public static final void doSomething() {
System.out.println((Object)"⇢ doSomething()");
final TimeMark markNow = TimeSource.Monotonic.INSTANCE.markNow();
try {
Thread.sleep(15L);
System.out.println((Object)("⇠ doSomething [" + (Object)Duration.toString-impl(markNow.elapsedNow-UwyO8pc()) + ']'));
}
catch (Throwable t) {
System.out.println((Object)("⇠ doSomething [" + (Object)Duration.toString-impl(markNow.elapsedNow-UwyO8pc()) + "] = " + t));
throw t;
}
}
""".trimIndent())

val out = invokeMain(result, "MainKt").trim().split("""\r?\n+""".toRegex())
assert(out.size == 8)
assert(out[0] == "⇢ greet(greeting=Hello, name=World)")
Expand All @@ -73,49 +107,28 @@ fun doSomething() {
val result = compile(sourceFile = main, DebugLogComponentRegistrar(false))
assertEquals(KotlinCompilation.ExitCode.OK, result.exitCode)

val javaCode = result.javaCode("MainKt")

assertFunction(javaCode, "public static final String greet",
"""
public static final String greet(@NotNull final String greeting, @NotNull final String name) {
Intrinsics.checkNotNullParameter(greeting, "greeting");
Intrinsics.checkNotNullParameter(name, "name");
Thread.sleep(15L);
return greeting + ", " + name + '!';
}
""".trimIndent())

assertFunction(javaCode, "public static final void doSomething",
"""
public static final void doSomething() {
Thread.sleep(15L);
}
""".trimIndent())

val out = invokeMain(result, "MainKt").trim().split("""\r?\n+""".toRegex())
assertTrue(out.size == 2)
assertTrue(out[0] == "Hello, World!")
assertTrue(out[1] == "Hello, Kotlin IR!")
}
}

fun compile(
sourceFiles: List<SourceFile>,
plugin: ComponentRegistrar,
): KotlinCompilation.Result {
return KotlinCompilation().apply {
sources = sourceFiles
useIR = true
compilerPlugins = listOf(plugin)
inheritClassPath = true
verbose = false
}.compile()
}

fun compile(
sourceFile: SourceFile,
plugin: ComponentRegistrar,
): KotlinCompilation.Result {
return compile(listOf(sourceFile), plugin)
}

fun invokeMain(result: KotlinCompilation.Result, className: String): String {
val oldOut = System.out
try {
val buffer = ByteArrayOutputStream()
System.setOut(PrintStream(buffer, false, "UTF-8"))

try {
val kClazz = result.classLoader.loadClass(className)
val main = kClazz.declaredMethods.single { it.name == "main" && it.parameterCount == 0 }
main.invoke(null)
} catch (e: InvocationTargetException) {
throw e.targetException
}

return buffer.toString("UTF-8")
} finally {
System.setOut(oldOut)
}
}
Loading

0 comments on commit b5cc7ee

Please sign in to comment.