mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[java] ASM 8 forward compatibility fix for debugger utils (IDEA-208372)
GitOrigin-RevId: 3471bf284fea3d09625af7cd1771ba479bcf6054
This commit is contained in:
committed by
intellij-monorepo-bot
parent
b2fd7a0ea9
commit
ae0ca5f4b7
@@ -5,7 +5,8 @@ import com.intellij.Patches;
|
||||
import com.intellij.debugger.engine.DebuggerUtils;
|
||||
import com.intellij.debugger.impl.DebuggerUtilsEx;
|
||||
import com.intellij.openapi.util.Ref;
|
||||
import com.intellij.util.ArrayUtilRt;
|
||||
import com.intellij.openapi.util.io.BufferExposingByteArrayOutputStream;
|
||||
import com.intellij.util.ArrayUtil;
|
||||
import com.intellij.util.ReflectionUtil;
|
||||
import com.intellij.util.ThrowableConsumer;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
@@ -16,9 +17,9 @@ import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.org.objectweb.asm.Type;
|
||||
import org.jetbrains.org.objectweb.asm.*;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
|
||||
public class MethodBytecodeUtil {
|
||||
@@ -61,93 +62,76 @@ public class MethodBytecodeUtil {
|
||||
|
||||
private static void visit(Method method, byte[] bytecodes, MethodVisitor methodVisitor, boolean withLineNumbers) {
|
||||
ReferenceType type = method.declaringType();
|
||||
byte[] constantPool = getConstantPool(type);
|
||||
try (ByteArrayBuilderOutputStream bos = new ByteArrayBuilderOutputStream(constantPool.length + 24);
|
||||
DataOutputStream dos = new DataOutputStream(bos)) {
|
||||
writeClassHeader(dos, type.constantPoolCount(), constantPool);
|
||||
|
||||
ClassReader reader = new ClassReader(bos.getBuffer());
|
||||
ClassWriter writer = new ClassWriter(reader, 0);
|
||||
|
||||
String superName = null;
|
||||
String[] interfaces = null;
|
||||
if (type instanceof ClassType) {
|
||||
ClassType classType = (ClassType)type;
|
||||
ClassType superClass = classType.superclass();
|
||||
superName = superClass != null ? superClass.name() : null;
|
||||
interfaces = classType.interfaces().stream().map(ReferenceType::name).toArray(String[]::new);
|
||||
}
|
||||
else if (type instanceof InterfaceType) {
|
||||
interfaces = ((InterfaceType)type).superinterfaces().stream().map(ReferenceType::name).toArray(String[]::new);
|
||||
}
|
||||
|
||||
writer.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, type.name(), type.signature(), superName, interfaces);
|
||||
Attribute bootstrapMethods = createBootstrapMethods(reader, writer);
|
||||
if (bootstrapMethods != null) {
|
||||
writer.visitAttribute(bootstrapMethods);
|
||||
}
|
||||
|
||||
MethodVisitor mv = writer.visitMethod(Opcodes.ACC_PUBLIC, method.name(), method.signature(), method.signature(), null);
|
||||
mv.visitAttribute(createCode(writer, method, bytecodes, withLineNumbers));
|
||||
|
||||
new ClassReader(writer.toByteArray()).accept(new ClassVisitor(Opcodes.API_VERSION) {
|
||||
@Override
|
||||
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
|
||||
assert name.equals(method.name());
|
||||
return methodVisitor;
|
||||
}
|
||||
}, 0);
|
||||
BufferExposingByteArrayOutputStream bytes = new BufferExposingByteArrayOutputStream();
|
||||
try (DataOutputStream dos = new DataOutputStream(bytes)) {
|
||||
writeClassHeader(dos, type.constantPoolCount(), getConstantPool(type));
|
||||
}
|
||||
catch (IOException ignored) { }
|
||||
}
|
||||
catch (IOException e) { throw new RuntimeException(e); }
|
||||
ClassReader reader = new ClassReader(bytes.getInternalBuffer(), 0, bytes.size());
|
||||
|
||||
@NotNull
|
||||
private static Attribute createAttribute(String name, ThrowableConsumer<? super DataOutputStream, ? extends IOException> generator) throws IOException {
|
||||
try (ByteArrayOutputStream bos = new ByteArrayOutputStream(); DataOutputStream dos = new DataOutputStream(bos)) {
|
||||
writeClassHeader(dos, 0, ArrayUtilRt.EMPTY_BYTE_ARRAY);
|
||||
// we generate and put attribute right after the constant pool
|
||||
int start = dos.size();
|
||||
generator.consume(dos);
|
||||
int end = dos.size();
|
||||
ClassReader cr = new ClassReader(bos.toByteArray());
|
||||
return new Attribute(name) {
|
||||
public Attribute read() {
|
||||
return read(cr, start, end - start, null, 0, null);
|
||||
}
|
||||
}.read();
|
||||
ClassWriter writer = new ClassWriter(reader, 0);
|
||||
String superName = null;
|
||||
String[] interfaces = null;
|
||||
if (type instanceof ClassType) {
|
||||
ClassType superClass = ((ClassType)type).superclass();
|
||||
superName = superClass != null ? superClass.name() : null;
|
||||
interfaces = ((ClassType)type).interfaces().stream().map(ReferenceType::name).toArray(String[]::new);
|
||||
}
|
||||
else if (type instanceof InterfaceType) {
|
||||
interfaces = ((InterfaceType)type).superinterfaces().stream().map(ReferenceType::name).toArray(String[]::new);
|
||||
}
|
||||
writer.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, type.name(), type.signature(), superName, interfaces);
|
||||
Attribute bootstrapMethods = createBootstrapMethods(reader, writer);
|
||||
if (bootstrapMethods != null) {
|
||||
writer.visitAttribute(bootstrapMethods);
|
||||
}
|
||||
MethodVisitor mv = writer.visitMethod(Opcodes.ACC_PUBLIC, method.name(), method.signature(), method.signature(), null);
|
||||
mv.visitAttribute(createCode(writer, method, bytecodes, withLineNumbers));
|
||||
|
||||
new ClassReader(writer.toByteArray()).accept(new ClassVisitor(Opcodes.API_VERSION) {
|
||||
@Override
|
||||
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
|
||||
assert name.equals(method.name());
|
||||
return methodVisitor;
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
|
||||
private static void writeClassHeader(DataOutputStream dos, int constantPoolCount, byte[] constantPool) throws IOException {
|
||||
dos.writeInt(0xCAFEBABE);
|
||||
dos.writeInt(Opcodes.V1_8);
|
||||
dos.writeShort(constantPoolCount);
|
||||
dos.writeShort(constantPoolCount + 1);
|
||||
dos.write(constantPool);
|
||||
dos.writeShort(0); // access_flags;
|
||||
dos.writeShort(0); // this_class;
|
||||
dos.writeShort(0); // super_class;
|
||||
dos.writeShort(0); // interfaces_count;
|
||||
dos.writeShort(0); // fields_count;
|
||||
dos.writeShort(0); // methods_count;
|
||||
dos.writeShort(0); // attributes_count;
|
||||
byte[] utfStr = "BootstrapMethods".getBytes(StandardCharsets.UTF_8);
|
||||
dos.writeByte(1); // name string for the fake "BootstrapMethods" attribute
|
||||
dos.writeShort(utfStr.length);
|
||||
dos.write(utfStr);
|
||||
dos.writeShort(0); // access_flags
|
||||
dos.writeShort(0); // this_class
|
||||
dos.writeShort(0); // super_class
|
||||
dos.writeShort(0); // interfaces_count
|
||||
dos.writeShort(0); // fields_count
|
||||
dos.writeShort(0); // methods_count
|
||||
dos.writeShort(1); // attributes_count
|
||||
dos.writeShort(constantPoolCount); // fake "BootstrapMethods" attribute
|
||||
dos.writeInt(0);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static Attribute createBootstrapMethods(ClassReader classReader, ClassWriter classWriter) throws IOException {
|
||||
private static @Nullable Attribute createBootstrapMethods(ClassReader classReader, ClassWriter classWriter) {
|
||||
Set<Short> bootstrapMethods = new HashSet<>();
|
||||
// scan class pool for indy calls
|
||||
for (int i = 1; i < classReader.getItemCount(); i++) {
|
||||
int index = classReader.getItem(i);
|
||||
int tag = classReader.readByte(index - 1);
|
||||
switch (tag) {
|
||||
case 5: // ClassWriter.LONG
|
||||
case 6: // ClassWriter.DOUBLE
|
||||
case 5: // Symbol.CONSTANT_LONG_TAG
|
||||
case 6: // Symbol.CONSTANT_DOUBLE_TAG
|
||||
//noinspection AssignmentToForLoopParameter
|
||||
++i;
|
||||
break;
|
||||
case 18: // ClassWriter.INDY
|
||||
case 18: // Symbol.CONSTANT_INVOKE_DYNAMIC_TAG
|
||||
bootstrapMethods.add(classReader.readShort(index));
|
||||
classReader.readShort(index + 2);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,15 +149,14 @@ public class MethodBytecodeUtil {
|
||||
return null;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static Attribute createCode(ClassWriter cw, Method method, byte[] bytecodes, boolean withLineNumbers) throws IOException {
|
||||
private static Attribute createCode(ClassWriter cw, Method method, byte[] bytecodes, boolean withLineNumbers) {
|
||||
return createAttribute("Code", dos -> {
|
||||
dos.writeShort(0); // max_stack
|
||||
dos.writeShort(0); // max_locals
|
||||
dos.writeInt(bytecodes.length); // code_length
|
||||
dos.write(bytecodes); // code
|
||||
dos.writeShort(0); // exception_table_length
|
||||
List<Location> locations = withLineNumbers ? DebuggerUtilsEx.allLineLocations(method) : Collections.emptyList();
|
||||
List<Location> locations = withLineNumbers ? DebuggerUtilsEx.allLineLocations(method) : null;
|
||||
if (!ContainerUtil.isEmpty(locations)) {
|
||||
dos.writeShort(1); // attributes_count
|
||||
dos.writeShort(cw.newUTF8("LineNumberTable"));
|
||||
@@ -190,6 +173,26 @@ public class MethodBytecodeUtil {
|
||||
});
|
||||
}
|
||||
|
||||
private static Attribute createAttribute(String name, ThrowableConsumer<DataOutputStream, IOException> generator) {
|
||||
BufferExposingByteArrayOutputStream bytes = new BufferExposingByteArrayOutputStream();
|
||||
int start, end;
|
||||
|
||||
try (DataOutputStream dos = new DataOutputStream(bytes)) {
|
||||
writeClassHeader(dos, 0, ArrayUtil.EMPTY_BYTE_ARRAY);
|
||||
start = dos.size();
|
||||
generator.consume(dos);
|
||||
end = dos.size();
|
||||
}
|
||||
catch (IOException e) { throw new RuntimeException(e); }
|
||||
|
||||
ClassReader reader = new ClassReader(bytes.getInternalBuffer(), 0, bytes.size());
|
||||
return new Attribute(name) {
|
||||
public Attribute read() {
|
||||
return read(reader, start, end - start, null, 0, null);
|
||||
}
|
||||
}.read();
|
||||
}
|
||||
|
||||
private static final Type OBJECT_TYPE = Type.getObjectType("java/lang/Object");
|
||||
|
||||
public static Type getVarInstructionType(int opcode) {
|
||||
@@ -333,15 +336,4 @@ public class MethodBytecodeUtil {
|
||||
|
||||
return locations;
|
||||
}
|
||||
|
||||
private static class ByteArrayBuilderOutputStream extends ByteArrayOutputStream {
|
||||
ByteArrayBuilderOutputStream(int size) {
|
||||
super(size);
|
||||
}
|
||||
|
||||
byte[] getBuffer() {
|
||||
assert buf.length == count : "Buffer is not fully filled";
|
||||
return buf;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user