From ae0ca5f4b7f045d005d5c1a767adc18da2397d8b Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Tue, 12 May 2020 07:35:00 +0200 Subject: [PATCH] [java] ASM 8 forward compatibility fix for debugger utils (IDEA-208372) GitOrigin-RevId: 3471bf284fea3d09625af7cd1771ba479bcf6054 --- .../debugger/jdi/MethodBytecodeUtil.java | 154 +++++++++--------- 1 file changed, 73 insertions(+), 81 deletions(-) diff --git a/java/debugger/impl/src/com/intellij/debugger/jdi/MethodBytecodeUtil.java b/java/debugger/impl/src/com/intellij/debugger/jdi/MethodBytecodeUtil.java index 7efa91abf9f3..88f9a1017c4a 100644 --- a/java/debugger/impl/src/com/intellij/debugger/jdi/MethodBytecodeUtil.java +++ b/java/debugger/impl/src/com/intellij/debugger/jdi/MethodBytecodeUtil.java @@ -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 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 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 locations = withLineNumbers ? DebuggerUtilsEx.allLineLocations(method) : Collections.emptyList(); + List 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 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; - } - } } \ No newline at end of file