Неправильный размер стека, рассчитанный библиотекой ASM
Я генерирую байт-коды, используя библиотеку ASM, и "Макс. Размер стека" для метода остается рассчитанным автоматически. Во время выполнения я обнаружил, что это значение (максимальный размер стека) не является правильным.
Мой исходный код:
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
....
MethodType initType = MethodType.methodType(void.class, clsList);
mv = cw.visitMethod(ACC_PUBLIC, "<init>", initType.toMethodDescriptorString(), null, null);
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/invoke/BaseTemplate", "<init>", "()V", false);
for(int i=0; i< list.size(); i++){
mv.visitVarInsn(ALOAD, 0);
mv.visitVarInsn(ALOAD, 1+i);
mv.visitFieldInsn(PUTFIELD, className, list.get(i).name(), Utils.getFieldDesc(list.get(i).type()));
}
mv.visitInsn(RETURN);
//mv.visitMaxs(2, 4); //Verify succeeds if uncomment this line.
mv.visitEnd();
....
//Verify generated code before class loading..
PrintWriter pw = new PrintWriter(System.out);
CheckClassAdapter.verify(new ClassReader(cw.toByteArray()), true, pw);
Class<?> expClass =defineClass(..);
Выше код будет генерировать байт-коды:
Classfile /C:/temp/TGWD.class
Last modified Mar 11, 2015; size 403 bytes
MD5 checksum f58b96ad4cb0bc9e62f2ae5e11e63e90
public class TGWD extends java.lang.invoke.BaseTemplate
minor version: 0
major version: 51
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Utf8 TGWD
#2 = Class #1 // TGWD
#3 = Utf8 java/lang/invoke/BaseTemplate
#4 = Class #3 // java/lang/invoke/BaseTemplate
#5 = Utf8 guard
#6 = Utf8 Ljava/lang/invoke/MethodHandle;
#7 = Utf8 trueTarget
#8 = Utf8 falseTarget
#9 = Utf8 <init>
#10 = Utf8 (Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodHandle;)V
#11 = Utf8 ()V
#12 = NameAndType #9:#11 // "<init>":()V
#13 = Methodref #4.#12 // java/lang/invoke/BaseTemplate."<init>":()V
#14 = NameAndType #5:#6 // guard:Ljava/lang/invoke/MethodHandle;
#15 = Fieldref #2.#14 // TGWD.guard:Ljava/lang/invoke/MethodHandle;
#16 = NameAndType #7:#6 // trueTarget:Ljava/lang/invoke/MethodHandle;
#17 = Fieldref #2.#16 // TGWD.trueTarget:Ljava/lang/invoke/MethodHandle;
#18 = NameAndType #8:#6 // falseTarget:Ljava/lang/invoke/MethodHandle;
#19 = Fieldref #2.#18 // TGWD.falseTarget:Ljava/lang/invoke/MethodHandle;
#20 = Utf8 eval
#21 = Utf8 Code
{
final java.lang.invoke.MethodHandle guard;
flags: ACC_FINAL
final java.lang.invoke.MethodHandle trueTarget;
flags: ACC_FINAL
final java.lang.invoke.MethodHandle falseTarget;
flags: ACC_FINAL
public TGWD(java.lang.invoke.MethodHandle, java.lang.invoke.MethodHandle, java.lang.invoke.MethodHandle);
flags: ACC_PUBLIC
Code:
stack=0, locals=4, args_size=4
0: aload_0
1: invokespecial #13 // Method java/lang/invoke/BaseTemplate."<init>":()V
4: aload_0
5: aload_1
6: putfield #15 // Field guard:Ljava/lang/invoke/MethodHandle;
9: aload_0
10: aload_2
11: putfield #17 // Field trueTarget:Ljava/lang/invoke/MethodHandle;
14: aload_0
15: aload_3
16: putfield #19 // Field falseTarget:Ljava/lang/invoke/MethodHandle;
19: return
public void eval();
flags: ACC_PUBLIC
Code:
stack=0, locals=1, args_size=1
0: return
}
Байт-код сообщает об ошибке:
org.objectweb.asm.tree.analysis.AnalyzerException: Error at instruction 0: Insufficient maximum stack size.
at org.objectweb.asm.tree.analysis.Analyzer.analyze(Unknown Source)
at org.objectweb.asm.util.CheckClassAdapter.verify(Unknown Source)
at org.objectweb.asm.util.CheckClassAdapter.verify(Unknown Source)
потому что метод построения: stack=0, locals=4, args_size=4
Правильный размер стека равен 2.
Существует другой поток ASM (из ObjectWeb), который неправильно вычисляет MaxStack, даже если установлен ClassWriter( COMPUTE_MAX + COMPUTE_STACK), что указывает на то, что максимальный размер стека может быть неверно рассчитан, если байт-код в другом месте недопустим.
Так что для меня вопросы:
- Где аннулировать байт-коды в созданном файле?
- Я все еще предпочитаю избегать вызова visitMax(). Поскольку существует ряд сгенерированных методов байт-кода, и вычисление этих значений вручную не является легкой задачей.
1 ответ
Вы не можете пропустить звонок visitMax
, Из документации ClassWriter.COMPUTE_MAXS
:
Если этот флаг установлен, то аргументы метода visitMaxs метода MethodVisitor, возвращаемого методом visitMethod, будут игнорироваться и вычисляться автоматически из сигнатуры и байт-кода каждого метода.
Другими словами, когда вы указываете флаг, вы можете передать все, что захотите, например, вызов visitMax(-1,-1)
чтобы подчеркнуть, что вы не предоставляете фактические значения, но вам все равно нужно вызвать метод, чтобы инициировать вычисление правильных значений.
Кстати, так как вы создаете файл класса версии 51
Вы должны указать COMPUTE_FRAMES
как я сомневаюсь, что вы хотите создать StackMapTable
атрибуты вручную. Обратите внимание, что COMPUTE_FRAMES
подразумевает COMPUTE_MAXS
поведение.