mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-08-31 17:45:48 +07:00
GitOrigin-RevId: 7ea6cbdf25d1121c5444550643526570e4114148
726 lines
19 KiB
Kotlin
726 lines
19 KiB
Kotlin
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
|
package com.intellij.byteCodeViewer
|
|
|
|
import com.intellij.testFramework.PlatformTestUtil
|
|
import com.intellij.testFramework.fixtures.BasePlatformTestCase
|
|
|
|
internal class BytecodeLineMappingTest : BasePlatformTestCase() {
|
|
private fun getSelectionFromSource(text: String): Pair<Int, Int> {
|
|
myFixture.configureByText(getTestName(false) + ".java", text)
|
|
val sourceDocument = myFixture.editor.document
|
|
|
|
val sourceStartOffset = myFixture.editor.selectionModel.selectionStart
|
|
val sourceEndOffset = myFixture.editor.selectionModel.selectionEnd
|
|
|
|
val startLine = sourceDocument.getLineNumber(sourceStartOffset)
|
|
val endLine = sourceDocument.getLineNumber(sourceEndOffset)
|
|
return startLine to endLine
|
|
}
|
|
|
|
private fun doTest(
|
|
sourceWithSelection: String,
|
|
fixture: Fixture,
|
|
showDebugInfo: Boolean,
|
|
expectedBytecodeSelection: String,
|
|
) {
|
|
val (startLine, endLine) = getSelectionFromSource(sourceWithSelection)
|
|
assertEquals(fixture.source, myFixture.editor.document.text) // ensure consistency
|
|
assertEquals(fixture.bytecode, removeDebugInfo(fixture.bytecodeWithDebugInfo)) // ensure consistency
|
|
|
|
val linesRange = mapLines(
|
|
bytecodeWithDebugInfo = fixture.bytecodeWithDebugInfo,
|
|
sourceStartLine = startLine,
|
|
sourceEndLine = endLine,
|
|
showDebugInfo = showDebugInfo,
|
|
)
|
|
val bytecodeSelectionStartLine = linesRange.first
|
|
var bytecodeSelectionEndLine = linesRange.last
|
|
bytecodeSelectionEndLine++ // because most substring() functions use exclusive indexing for "end"
|
|
bytecodeSelectionEndLine++ // because string operations are 0-indexed but the editor is 1-indexed
|
|
|
|
val bytecode = if (showDebugInfo) fixture.bytecodeWithDebugInfo else fixture.bytecode
|
|
assertEquals(expectedBytecodeSelection,
|
|
bytecode.lines().subList(fromIndex = bytecodeSelectionStartLine, toIndex = bytecodeSelectionEndLine).joinToString("\n"))
|
|
}
|
|
|
|
fun `test removeDebugInfo`() {
|
|
val actualStrippedBytecode = removeDebugInfo(simple1.bytecodeWithDebugInfo)
|
|
val expectedStrippedBytecode = """
|
|
|// class version 67.0 (67)
|
|
|// access flags 0x21
|
|
|public class simple1/Main {
|
|
|
|
|
| // compiled from: Main.java
|
|
|
|
|
| // access flags 0x1
|
|
| public <init>()V
|
|
| L0
|
|
| ALOAD 0
|
|
| INVOKESPECIAL java/lang/Object.<init> ()V
|
|
| RETURN
|
|
| L1
|
|
| MAXSTACK = 1
|
|
| MAXLOCALS = 1
|
|
|
|
|
| // access flags 0x9
|
|
| public static main([Ljava/lang/String;)V
|
|
| L0
|
|
| GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
|
|
| LDC "hello world"
|
|
| INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
|
|
| L1
|
|
| RETURN
|
|
| L2
|
|
| MAXSTACK = 2
|
|
| MAXLOCALS = 1
|
|
|}
|
|
|
|
|
""".trimMargin("|")
|
|
|
|
assertEquals(expectedStrippedBytecode, actualStrippedBytecode)
|
|
assertEquals(expectedStrippedBytecode, simple1.bytecode)
|
|
}
|
|
|
|
fun `test simple 1 method body start`() {
|
|
|
|
val source = """
|
|
package simple1;
|
|
|
|
public class Main {
|
|
public static void main(String[] args) {<caret>
|
|
System.out.println("hello world");
|
|
}
|
|
}
|
|
|
|
""".trimIndent()
|
|
|
|
doTest(
|
|
sourceWithSelection = source,
|
|
fixture = simple1,
|
|
showDebugInfo = false,
|
|
expectedBytecodeSelection = """
|
|
| L0
|
|
""".trimMargin("|"),
|
|
)
|
|
}
|
|
|
|
fun `test simple 1 method body start (show debug info)`() {
|
|
|
|
val source = """
|
|
package simple1;
|
|
|
|
public class Main {
|
|
public static void main(String[] args) {<caret>
|
|
System.out.println("hello world");
|
|
}
|
|
}
|
|
|
|
""".trimIndent()
|
|
|
|
doTest(
|
|
sourceWithSelection = source,
|
|
fixture = simple1,
|
|
showDebugInfo = true,
|
|
expectedBytecodeSelection = """
|
|
| L0
|
|
""".trimMargin("|"),
|
|
)
|
|
}
|
|
|
|
fun `test simple 1 method body - single line selected`() {
|
|
val source = """
|
|
package simple1;
|
|
|
|
public class Main {
|
|
public static void main(String[] args) {
|
|
<selection>System.out.println("hello world");</selection>
|
|
}
|
|
}
|
|
|
|
""".trimIndent()
|
|
|
|
doTest(
|
|
sourceWithSelection = source,
|
|
fixture = simple1,
|
|
showDebugInfo = false,
|
|
expectedBytecodeSelection = """
|
|
| L0
|
|
| GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
|
|
| LDC "hello world"
|
|
| INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
|
|
| L1
|
|
""".trimMargin("|"),
|
|
)
|
|
}
|
|
|
|
fun `test simple 1 method body - single line selected (show debug info)`() {
|
|
val source = """
|
|
package simple1;
|
|
|
|
public class Main {
|
|
public static void main(String[] args) {
|
|
<selection>System.out.println("hello world");</selection>
|
|
}
|
|
}
|
|
|
|
""".trimIndent()
|
|
|
|
doTest(
|
|
sourceWithSelection = source, fixture = simple1, showDebugInfo = true,
|
|
expectedBytecodeSelection = """
|
|
| L0
|
|
| LINENUMBER 5 L0
|
|
| GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
|
|
| LDC "hello world"
|
|
| INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
|
|
| L1
|
|
""".trimMargin("|"),
|
|
)
|
|
}
|
|
|
|
fun `test simple 1 method body - from brace to brace`() {
|
|
val source = """
|
|
package simple1;
|
|
|
|
public class Main {
|
|
public static void main(String[] args) {<selection>
|
|
System.out.println("hello world");
|
|
</selection>}
|
|
}
|
|
|
|
""".trimIndent()
|
|
|
|
doTest(
|
|
sourceWithSelection = source, fixture = simple1, showDebugInfo = false,
|
|
expectedBytecodeSelection = """
|
|
| L0
|
|
| GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
|
|
| LDC "hello world"
|
|
| INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
|
|
| L1
|
|
| RETURN
|
|
| L2
|
|
""".trimMargin("|"),
|
|
)
|
|
}
|
|
|
|
fun `test simple 1 method body - from brace to brace (show debug info)`() {
|
|
val source = """
|
|
package simple1;
|
|
|
|
public class Main {
|
|
public static void main(String[] args) {<selection>
|
|
System.out.println("hello world");
|
|
</selection>}
|
|
}
|
|
|
|
""".trimIndent()
|
|
|
|
doTest(
|
|
sourceWithSelection = source, fixture = simple1, showDebugInfo = true,
|
|
expectedBytecodeSelection = """
|
|
| L0
|
|
| LINENUMBER 5 L0
|
|
| GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
|
|
| LDC "hello world"
|
|
| INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
|
|
| L1
|
|
| LINENUMBER 6 L1
|
|
| RETURN
|
|
| L2
|
|
""".trimMargin("|"),
|
|
)
|
|
}
|
|
|
|
fun `test simple 1 method body end`() {
|
|
val source = """
|
|
package simple1;
|
|
|
|
public class Main {
|
|
public static void main(String[] args) {
|
|
System.out.println("hello world");
|
|
}<caret>
|
|
}
|
|
|
|
""".trimIndent()
|
|
|
|
doTest(
|
|
sourceWithSelection = source, fixture = simple1, showDebugInfo = false,
|
|
expectedBytecodeSelection = """
|
|
| L1
|
|
| RETURN
|
|
| L2
|
|
""".trimMargin("|"),
|
|
)
|
|
}
|
|
|
|
|
|
fun `test simple 1 method body end (show debug info)`() {
|
|
val source = """
|
|
package simple1;
|
|
|
|
public class Main {
|
|
public static void main(String[] args) {
|
|
System.out.println("hello world");
|
|
}<caret>
|
|
}
|
|
|
|
""".trimIndent()
|
|
|
|
doTest(
|
|
sourceWithSelection = source, fixture = simple1, showDebugInfo = true,
|
|
expectedBytecodeSelection = """
|
|
| L1
|
|
| LINENUMBER 6 L1
|
|
| RETURN
|
|
| L2
|
|
""".trimMargin("|"),
|
|
)
|
|
}
|
|
|
|
fun `test simple 1 class`() {
|
|
val source = """
|
|
package simple1;
|
|
|
|
public class Main {<caret>
|
|
public static void main(String[] args) {
|
|
System.out.println("hello world");
|
|
}
|
|
}
|
|
|
|
""".trimIndent()
|
|
|
|
doTest(
|
|
sourceWithSelection = source, fixture = simple1, showDebugInfo = false,
|
|
expectedBytecodeSelection = """
|
|
| L0
|
|
| ALOAD 0
|
|
| INVOKESPECIAL java/lang/Object.<init> ()V
|
|
| RETURN
|
|
| L1
|
|
""".trimMargin("|"),
|
|
)
|
|
}
|
|
|
|
fun `test simple 1 class (show debug info)`() {
|
|
val source = """
|
|
package simple1;
|
|
|
|
public class Main {<caret>
|
|
public static void main(String[] args) {
|
|
System.out.println("hello world");
|
|
}
|
|
}
|
|
|
|
""".trimIndent()
|
|
|
|
doTest(
|
|
sourceWithSelection = source, fixture = simple1, showDebugInfo = true,
|
|
expectedBytecodeSelection = """
|
|
| L0
|
|
| LINENUMBER 3 L0
|
|
| ALOAD 0
|
|
| INVOKESPECIAL java/lang/Object.<init> ()V
|
|
| RETURN
|
|
| L1
|
|
""".trimMargin("|"),
|
|
)
|
|
}
|
|
|
|
fun `test simple 2 - single field selected`() {
|
|
val source = """
|
|
package simple2;
|
|
|
|
public class Main {
|
|
<selection>public static String name = "Charlie";</selection>
|
|
public static Object obj = new Object();
|
|
|
|
public static void main(String[] args) {
|
|
System.out.println("hello world");
|
|
int argCount = args.length;
|
|
System.out.println("there are " + argCount + " args");
|
|
}
|
|
}
|
|
|
|
""".trimIndent()
|
|
|
|
doTest(
|
|
sourceWithSelection = source, fixture = simple2, showDebugInfo = false,
|
|
expectedBytecodeSelection = """
|
|
| L0
|
|
| LDC "Charlie"
|
|
| PUTSTATIC simple2/Main.name : Ljava/lang/String;
|
|
| L1
|
|
""".trimMargin("|"),
|
|
)
|
|
}
|
|
|
|
fun `test simple 2 - single field selected (show debug info)`() {
|
|
val source = """
|
|
package simple2;
|
|
|
|
public class Main {
|
|
<selection>public static String name = "Charlie";</selection>
|
|
public static Object obj = new Object();
|
|
|
|
public static void main(String[] args) {
|
|
System.out.println("hello world");
|
|
int argCount = args.length;
|
|
System.out.println("there are " + argCount + " args");
|
|
}
|
|
}
|
|
|
|
""".trimIndent()
|
|
|
|
doTest(
|
|
sourceWithSelection = source, fixture = simple2, showDebugInfo = true,
|
|
expectedBytecodeSelection = """
|
|
| L0
|
|
| LINENUMBER 4 L0
|
|
| LDC "Charlie"
|
|
| PUTSTATIC simple2/Main.name : Ljava/lang/String;
|
|
| L1
|
|
""".trimMargin("|"),
|
|
)
|
|
}
|
|
|
|
fun `test simple 2 - two fields selected`() {
|
|
val source = """
|
|
package simple2;
|
|
|
|
public class Main {
|
|
<selection>public static String name = "Charlie";
|
|
public static Object obj = new Object();</selection>
|
|
|
|
public static void main(String[] args) {
|
|
System.out.println("hello world");
|
|
int argCount = args.length;
|
|
System.out.println("there are " + argCount + " args");
|
|
}
|
|
}
|
|
|
|
""".trimIndent()
|
|
|
|
doTest(
|
|
sourceWithSelection = source, fixture = simple2, showDebugInfo = false,
|
|
expectedBytecodeSelection = """
|
|
| L0
|
|
| LDC "Charlie"
|
|
| PUTSTATIC simple2/Main.name : Ljava/lang/String;
|
|
| L1
|
|
| NEW java/lang/Object
|
|
| DUP
|
|
| INVOKESPECIAL java/lang/Object.<init> ()V
|
|
| PUTSTATIC simple2/Main.obj : Ljava/lang/Object;
|
|
| RETURN
|
|
| MAXSTACK = 2
|
|
""".trimMargin("|"),
|
|
)
|
|
}
|
|
|
|
fun `test simple 2 - two fields selected (show debug info)`() {
|
|
val source = """
|
|
package simple2;
|
|
|
|
public class Main {
|
|
<selection>public static String name = "Charlie";
|
|
public static Object obj = new Object();</selection>
|
|
|
|
public static void main(String[] args) {
|
|
System.out.println("hello world");
|
|
int argCount = args.length;
|
|
System.out.println("there are " + argCount + " args");
|
|
}
|
|
}
|
|
|
|
""".trimIndent()
|
|
|
|
doTest(
|
|
sourceWithSelection = source, fixture = simple2, showDebugInfo = true,
|
|
expectedBytecodeSelection = """
|
|
| L0
|
|
| LINENUMBER 4 L0
|
|
| LDC "Charlie"
|
|
| PUTSTATIC simple2/Main.name : Ljava/lang/String;
|
|
| L1
|
|
| LINENUMBER 5 L1
|
|
| NEW java/lang/Object
|
|
| DUP
|
|
| INVOKESPECIAL java/lang/Object.<init> ()V
|
|
| PUTSTATIC simple2/Main.obj : Ljava/lang/Object;
|
|
| RETURN
|
|
""".trimMargin("|"),
|
|
)
|
|
}
|
|
|
|
fun `test simple 2 method body - two lines selected`() {
|
|
val source = """
|
|
package simple2;
|
|
|
|
public class Main {
|
|
public static String name = "Charlie";
|
|
public static Object obj = new Object();
|
|
|
|
public static void main(String[] args) {
|
|
<selection>System.out.println("hello world");
|
|
int argCount = args.length;</selection>
|
|
System.out.println("there are " + argCount + " args");
|
|
}
|
|
}
|
|
|
|
""".trimIndent()
|
|
|
|
doTest(
|
|
sourceWithSelection = source, fixture = simple2, showDebugInfo = false,
|
|
expectedBytecodeSelection = """
|
|
| L0
|
|
| GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
|
|
| LDC "hello world"
|
|
| INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
|
|
| L1
|
|
| ALOAD 0
|
|
| ARRAYLENGTH
|
|
| ISTORE 1
|
|
| L2
|
|
""".trimMargin("|"),
|
|
)
|
|
}
|
|
|
|
fun `test simple 2 method body - two lines selected (show debug info)`() {
|
|
val source = """
|
|
package simple2;
|
|
|
|
public class Main {
|
|
public static String name = "Charlie";
|
|
public static Object obj = new Object();
|
|
|
|
public static void main(String[] args) {
|
|
<selection>System.out.println("hello world");
|
|
int argCount = args.length;</selection>
|
|
System.out.println("there are " + argCount + " args");
|
|
}
|
|
}
|
|
|
|
""".trimIndent()
|
|
|
|
doTest(
|
|
sourceWithSelection = source, fixture = simple2, showDebugInfo = true,
|
|
expectedBytecodeSelection = """
|
|
| L0
|
|
| LINENUMBER 8 L0
|
|
| GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
|
|
| LDC "hello world"
|
|
| INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
|
|
| L1
|
|
| LINENUMBER 9 L1
|
|
| ALOAD 0
|
|
| ARRAYLENGTH
|
|
| ISTORE 1
|
|
| L2
|
|
""".trimMargin("|"),
|
|
)
|
|
}
|
|
|
|
fun `test simple 3 - conditional jumps (1)`() {
|
|
val source = """
|
|
package simple3;
|
|
|
|
public class Main {
|
|
String method1(boolean value) {
|
|
if (value == true) {
|
|
return "baz";
|
|
}
|
|
return "baz";
|
|
}
|
|
|
|
String method2(boolean value) {
|
|
<selection>if (value == Boolean.TRUE) {
|
|
return "bar";
|
|
}</selection>
|
|
return "baz";
|
|
}
|
|
|
|
String method3(boolean value) {
|
|
if (value == Boolean.FALSE) {
|
|
return "bar";
|
|
}
|
|
return "baz";
|
|
}
|
|
|
|
String method(boolean value) {
|
|
if (Boolean.TRUE.equals(returnsBool(value))) {
|
|
return "foo";
|
|
}
|
|
return "baz";
|
|
}
|
|
|
|
public Boolean returnsBool(boolean value) {
|
|
return Math.random() > 0.5;
|
|
}
|
|
}
|
|
|
|
""".trimIndent()
|
|
|
|
doTest(
|
|
sourceWithSelection = source, fixture = simple3, showDebugInfo = false,
|
|
expectedBytecodeSelection = """
|
|
| L0
|
|
| ILOAD 1
|
|
| GETSTATIC java/lang/Boolean.TRUE : Ljava/lang/Boolean;
|
|
| INVOKEVIRTUAL java/lang/Boolean.booleanValue ()Z
|
|
| IF_ICMPNE L1
|
|
| L2
|
|
| LDC "bar"
|
|
| ARETURN
|
|
| L1
|
|
""".trimMargin("|"),
|
|
)
|
|
}
|
|
|
|
fun `test simple 3 - conditional jumps (1) (show debug info)`() {
|
|
val source = """
|
|
package simple3;
|
|
|
|
public class Main {
|
|
String method1(boolean value) {
|
|
if (value == true) {
|
|
return "baz";
|
|
}
|
|
return "baz";
|
|
}
|
|
|
|
String method2(boolean value) {
|
|
<selection>if (value == Boolean.TRUE) {
|
|
return "bar";
|
|
}</selection>
|
|
return "baz";
|
|
}
|
|
|
|
String method3(boolean value) {
|
|
if (value == Boolean.FALSE) {
|
|
return "bar";
|
|
}
|
|
return "baz";
|
|
}
|
|
|
|
String method(boolean value) {
|
|
if (Boolean.TRUE.equals(returnsBool(value))) {
|
|
return "foo";
|
|
}
|
|
return "baz";
|
|
}
|
|
|
|
public Boolean returnsBool(boolean value) {
|
|
return Math.random() > 0.5;
|
|
}
|
|
}
|
|
|
|
""".trimIndent()
|
|
|
|
doTest(
|
|
sourceWithSelection = source, fixture = simple3, showDebugInfo = true,
|
|
expectedBytecodeSelection = """
|
|
| L0
|
|
| LINENUMBER 12 L0
|
|
| ILOAD 1
|
|
| GETSTATIC java/lang/Boolean.TRUE : Ljava/lang/Boolean;
|
|
| INVOKEVIRTUAL java/lang/Boolean.booleanValue ()Z
|
|
| IF_ICMPNE L1
|
|
| L2
|
|
| LINENUMBER 13 L2
|
|
| LDC "bar"
|
|
| ARETURN
|
|
| L1
|
|
""".trimMargin("|"),
|
|
)
|
|
}
|
|
|
|
fun `test simple 3 - conditional jumps (2)`() {
|
|
val source = """
|
|
package simple3;
|
|
|
|
public class Main {
|
|
String method1(boolean value) {
|
|
if (value == true) {
|
|
return "baz";
|
|
}
|
|
return "baz";
|
|
}
|
|
|
|
String method2(boolean value) {
|
|
if (value == Boolean.TRUE) {
|
|
return "bar";
|
|
}
|
|
return "baz";
|
|
}
|
|
|
|
String method3(boolean value) {
|
|
if (value == Boolean.FALSE) {
|
|
return "bar";
|
|
}
|
|
return "baz";
|
|
}
|
|
|
|
String method(boolean value) {
|
|
<selection>if (Boolean.TRUE.equals(returnsBool(value))) {
|
|
return "foo";
|
|
}</selection>
|
|
return "baz";
|
|
}
|
|
|
|
public Boolean returnsBool(boolean value) {
|
|
return Math.random() > 0.5;
|
|
}
|
|
}
|
|
|
|
""".trimIndent()
|
|
|
|
doTest(
|
|
sourceWithSelection = source, fixture = simple3, showDebugInfo = false,
|
|
expectedBytecodeSelection = """
|
|
| L0
|
|
| GETSTATIC java/lang/Boolean.TRUE : Ljava/lang/Boolean;
|
|
| ALOAD 0
|
|
| ILOAD 1
|
|
| INVOKEVIRTUAL simple3/Main.returnsBool (Z)Ljava/lang/Boolean;
|
|
| INVOKEVIRTUAL java/lang/Boolean.equals (Ljava/lang/Object;)Z
|
|
| IFEQ L1
|
|
| L2
|
|
| LDC "foo"
|
|
| ARETURN
|
|
| L1
|
|
""".trimMargin("|"),
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Bytecode fixtures, as compiled with javac 23.
|
|
*
|
|
* Bytecode without debug info were created with [removeDebugInfo].
|
|
*/
|
|
companion object Fixtures {
|
|
data class Fixture(
|
|
val source: String,
|
|
val bytecode: String,
|
|
val bytecodeWithDebugInfo: String,
|
|
)
|
|
|
|
private fun createFixture(name: String): Fixture {
|
|
return Fixture(
|
|
source = PlatformTestUtil.loadFileText("$testDataPath/$name/Main.java"),
|
|
bytecode = PlatformTestUtil.loadFileText("$testDataPath/$name/bytecode.txt"),
|
|
bytecodeWithDebugInfo = PlatformTestUtil.loadFileText("$testDataPath/$name/bytecodeWithDebugInfo.txt"),
|
|
)
|
|
}
|
|
|
|
private val testDataPath: String
|
|
get() = PlatformTestUtil.getCommunityPath() + "/plugins/ByteCodeViewer/testData/lineMapping"
|
|
|
|
private val simple1: Fixture get() = createFixture("simple1")
|
|
|
|
private val simple2: Fixture get() = createFixture("simple2")
|
|
|
|
private val simple3: Fixture get() = createFixture("simple3")
|
|
}
|
|
}
|