[ByteCodeViewer] Improve and simplify testing

Make all tests have two variants: with and without debug info.

Also, move bytecode fixtures out of BytecodeLineMappingTest because it adds too much noise there.
Move it to the newly created testData directory instead.

GitOrigin-RevId: 1b44b118d470ace068774d9714eafc2704119e86
This commit is contained in:
Bartek Pacia
2025-06-24 11:14:53 +02:00
committed by intellij-monorepo-bot
parent ea6d36e854
commit ca5906a4f7
11 changed files with 882 additions and 529 deletions

View File

@@ -2,22 +2,25 @@
package com.intellij.byteCodeViewer
import org.jetbrains.org.objectweb.asm.ClassReader
import org.jetbrains.org.objectweb.asm.util.Textifier
import java.util.*
/**
* This method removes the following debugging information from `bytecode`:
* This method removes the following debug information from `bytecode`:
* - `LINENUMBER`
* - `LOCALVARIABLE`
*
* ### Why is this needed?
*
* Ideally, we would use `ClassReader#SKIP_DEBUG` flag, but it has problematic behavior when it comes to labels.
* Ideally, to get bytecode without debug information, we would use [ClassReader] from the ASM library, and parse bytecode with the [ClassReader.SKIP_DEBUG] flag.
* Unfortunately, this flag doesn't achieve what we want because it removes labels from the bytecode.
*
* When parsing bytecode with ASM's `ClassReader`, we want to set `ClassReader#SKIP_DEBUG`, because it's actually not part of bytecode.
* Unfortunately, `ClassReader#SKIP_DEBUG` also removes labels from the bytecode in most cases.
* This is bad because labels are often targets of conditional jumps.
* Also, we do want to display labels.
* They're useful.
*
* @param bytecodeWithDebugInfo - bytecode returned by ASM [ClassReader] and [Textifier] with [ClassReader.SKIP_FRAMES] flag.
* @see BytecodeToolWindowPanel.deserializeBytecode
*/
internal fun removeDebugInfo(bytecodeWithDebugInfo: String): String = bytecodeWithDebugInfo.lines()
.filter { line -> !isDebugLine(line.trim()) }
@@ -27,14 +30,14 @@ internal fun removeDebugInfo(bytecodeWithDebugInfo: String): String = bytecodeWi
/**
* Maps the line numbers from the provided bytecode to the source code line numbers within a specified range.
*
* @param bytecodeWithDebugInfo The Java bytecode in ASM format, with debugging information included (see [ClassReader.SKIP_DEBUG])
* @param bytecodeWithDebugInfo The Java bytecode in ASM format, with debug information included (see [ClassReader.SKIP_DEBUG])
* @param sourceStartLine The starting line number in the source code to map from.
* @param sourceEndLine The ending line number in the source code to map to.
* @return A pair where the first element is the start line number in the bytecode, and the second element is the end line number in the bytecode. Returns (0, 0) if no valid mapping
* is found.
*/
internal fun mapLines(bytecodeWithDebugInfo: String, sourceStartLine: Int, sourceEndLine: Int, showDebugInfo: Boolean = true): IntRange {
var sourceStartLine = sourceStartLine // + 1 // editor selection is 0-indexed
var sourceStartLine = sourceStartLine // editor selection is 0-indexed
var currentBytecodeLine = 0
var bytecodeStartLine = -1
var bytecodeEndLine = -1

View File

@@ -0,0 +1,7 @@
package simple1;
public class Main {
public static void main(String[] args) {
System.out.println("hello world");
}
}

View File

@@ -0,0 +1,28 @@
// 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
}

View File

@@ -0,0 +1,33 @@
// class version 67.0 (67)
// access flags 0x21
public class simple1/Main {
// compiled from: Main.java
// access flags 0x1
public <init>()V
L0
LINENUMBER 3 L0
ALOAD 0
INVOKESPECIAL java/lang/Object.<init> ()V
RETURN
L1
LOCALVARIABLE this Lsimple1/Main; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
// access flags 0x9
public static main([Ljava/lang/String;)V
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
LOCALVARIABLE args [Ljava/lang/String; L0 L2 0
MAXSTACK = 2
MAXLOCALS = 1
}

View File

@@ -0,0 +1,12 @@
package simple2;
public class Main {
public static String name = "Charlie";
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");
}
}

View File

@@ -0,0 +1,64 @@
// class version 67.0 (67)
// access flags 0x21
public class simple2/Main {
// compiled from: Main.java
// access flags 0x19
public final static INNERCLASS java/lang/invoke/MethodHandles$Lookup java/lang/invoke/MethodHandles Lookup
// access flags 0x9
public static Ljava/lang/String; name
// access flags 0x9
public static Ljava/lang/Object; obj
// 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
ALOAD 0
ARRAYLENGTH
ISTORE 1
L2
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
ILOAD 1
INVOKEDYNAMIC makeConcatWithConstants(I)Ljava/lang/String; [
// handle kind 0x6 : INVOKESTATIC
java/lang/invoke/StringConcatFactory.makeConcatWithConstants(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
// arguments:
"there are \u0001 args"
]
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
L3
RETURN
L4
MAXSTACK = 2
MAXLOCALS = 2
// access flags 0x8
static <clinit>()V
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
MAXLOCALS = 0
}

View File

@@ -0,0 +1,74 @@
// class version 67.0 (67)
// access flags 0x21
public class simple2/Main {
// compiled from: Main.java
// access flags 0x19
public final static INNERCLASS java/lang/invoke/MethodHandles$Lookup java/lang/invoke/MethodHandles Lookup
// access flags 0x9
public static Ljava/lang/String; name
// access flags 0x9
public static Ljava/lang/Object; obj
// access flags 0x1
public <init>()V
L0
LINENUMBER 3 L0
ALOAD 0
INVOKESPECIAL java/lang/Object.<init> ()V
RETURN
L1
LOCALVARIABLE this Lsimple2/Main; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
// access flags 0x9
public static main([Ljava/lang/String;)V
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
LINENUMBER 10 L2
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
ILOAD 1
INVOKEDYNAMIC makeConcatWithConstants(I)Ljava/lang/String; [
// handle kind 0x6 : INVOKESTATIC
java/lang/invoke/StringConcatFactory.makeConcatWithConstants(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
// arguments:
"there are \u0001 args"
]
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
L3
LINENUMBER 11 L3
RETURN
L4
LOCALVARIABLE args [Ljava/lang/String; L0 L4 0
LOCALVARIABLE argCount I L2 L4 1
MAXSTACK = 2
MAXLOCALS = 2
// access flags 0x8
static <clinit>()V
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
MAXSTACK = 2
MAXLOCALS = 0
}

View File

@@ -0,0 +1,35 @@
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) {
if (Boolean.TRUE.equals(returnsBool(value))) {
return "foo";
}
return "baz";
}
public Boolean returnsBool(boolean value) {
return Math.random() > 0.5;
}
}

View File

@@ -0,0 +1,103 @@
// class version 67.0 (67)
// access flags 0x21
public class simple3/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 0x0
method1(Z)Ljava/lang/String;
L0
ILOAD 1
ICONST_1
IF_ICMPNE L1
L2
LDC "baz"
ARETURN
L1
LDC "baz"
ARETURN
L3
MAXSTACK = 2
MAXLOCALS = 2
// access flags 0x0
method2(Z)Ljava/lang/String;
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
LDC "baz"
ARETURN
L3
MAXSTACK = 2
MAXLOCALS = 2
// access flags 0x0
method3(Z)Ljava/lang/String;
L0
ILOAD 1
GETSTATIC java/lang/Boolean.FALSE : Ljava/lang/Boolean;
INVOKEVIRTUAL java/lang/Boolean.booleanValue ()Z
IF_ICMPNE L1
L2
LDC "bar"
ARETURN
L1
LDC "baz"
ARETURN
L3
MAXSTACK = 2
MAXLOCALS = 2
// access flags 0x0
method(Z)Ljava/lang/String;
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
LDC "baz"
ARETURN
L3
MAXSTACK = 3
MAXLOCALS = 2
// access flags 0x1
public returnsBool(Z)Ljava/lang/Boolean;
L0
INVOKESTATIC java/lang/Math.random ()D
LDC 0.5
DCMPL
IFLE L1
ICONST_1
GOTO L2
L1
ICONST_0
L2
INVOKESTATIC java/lang/Boolean.valueOf (Z)Ljava/lang/Boolean;
ARETURN
L3
MAXSTACK = 4
MAXLOCALS = 2
}

View File

@@ -0,0 +1,128 @@
// class version 67.0 (67)
// access flags 0x21
public class simple3/Main {
// compiled from: Main.java
// access flags 0x1
public <init>()V
L0
LINENUMBER 3 L0
ALOAD 0
INVOKESPECIAL java/lang/Object.<init> ()V
RETURN
L1
LOCALVARIABLE this Lsimple3/Main; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
// access flags 0x0
method1(Z)Ljava/lang/String;
L0
LINENUMBER 5 L0
ILOAD 1
ICONST_1
IF_ICMPNE L1
L2
LINENUMBER 6 L2
LDC "baz"
ARETURN
L1
LINENUMBER 8 L1
LDC "baz"
ARETURN
L3
LOCALVARIABLE this Lsimple3/Main; L0 L3 0
LOCALVARIABLE value Z L0 L3 1
MAXSTACK = 2
MAXLOCALS = 2
// access flags 0x0
method2(Z)Ljava/lang/String;
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
LINENUMBER 15 L1
LDC "baz"
ARETURN
L3
LOCALVARIABLE this Lsimple3/Main; L0 L3 0
LOCALVARIABLE value Z L0 L3 1
MAXSTACK = 2
MAXLOCALS = 2
// access flags 0x0
method3(Z)Ljava/lang/String;
L0
LINENUMBER 19 L0
ILOAD 1
GETSTATIC java/lang/Boolean.FALSE : Ljava/lang/Boolean;
INVOKEVIRTUAL java/lang/Boolean.booleanValue ()Z
IF_ICMPNE L1
L2
LINENUMBER 20 L2
LDC "bar"
ARETURN
L1
LINENUMBER 22 L1
LDC "baz"
ARETURN
L3
LOCALVARIABLE this Lsimple3/Main; L0 L3 0
LOCALVARIABLE value Z L0 L3 1
MAXSTACK = 2
MAXLOCALS = 2
// access flags 0x0
method(Z)Ljava/lang/String;
L0
LINENUMBER 26 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
LINENUMBER 27 L2
LDC "foo"
ARETURN
L1
LINENUMBER 29 L1
LDC "baz"
ARETURN
L3
LOCALVARIABLE this Lsimple3/Main; L0 L3 0
LOCALVARIABLE value Z L0 L3 1
MAXSTACK = 3
MAXLOCALS = 2
// access flags 0x1
public returnsBool(Z)Ljava/lang/Boolean;
L0
LINENUMBER 33 L0
INVOKESTATIC java/lang/Math.random ()D
LDC 0.5
DCMPL
IFLE L1
ICONST_1
GOTO L2
L1
ICONST_0
L2
INVOKESTATIC java/lang/Boolean.valueOf (Z)Ljava/lang/Boolean;
ARETURN
L3
LOCALVARIABLE this Lsimple3/Main; L0 L3 0
LOCALVARIABLE value Z L0 L3 1
MAXSTACK = 4
MAXLOCALS = 2
}