3

Я пытался понять, как кадры стоп-кадров работают на Java, играя с прыжками в ASM. Я создал простой способ попробовать некоторые вещи: (разобранный с Кракатау):ClassWriter COMPUTE_FRAMES в ASM

L0:  ldc 'hello' 
    L2:  astore_1 
    L3:  getstatic Field java/lang/System out Ljava/io/PrintStream; 
    L6:  new java/lang/StringBuilder 
    L9:  dup 
    L10: invokespecial Method java/lang/StringBuilder <init>()V 
    L13: ldc 'concat1' 
    L15: invokevirtual Method java/lang/StringBuilder append (Ljava/lang/String;)Ljava/lang/StringBuilder; 
    L18: aload_1 
    L19: invokevirtual Method java/lang/StringBuilder append (Ljava/lang/String;)Ljava/lang/StringBuilder; 
    L22: invokevirtual Method java/lang/StringBuilder toString()Ljava/lang/String; 
    L25: invokevirtual Method java/io/PrintStream println (Ljava/lang/String;)V 
    L28: getstatic Field java/lang/System out Ljava/io/PrintStream; 
    L31: new java/lang/StringBuilder 
    L34: dup 
    L35: invokespecial Method java/lang/StringBuilder <init>()V 
    L38: ldc 'concat2' 
    L40: invokevirtual Method java/lang/StringBuilder append (Ljava/lang/String;)Ljava/lang/StringBuilder; 
    L43: aload_1 
    L44: invokevirtual Method java/lang/StringBuilder append (Ljava/lang/String;)Ljava/lang/StringBuilder; 
    L47: invokevirtual Method java/lang/StringBuilder toString()Ljava/lang/String; 
    L50: invokevirtual Method java/io/PrintStream println (Ljava/lang/String;)V 
    L53: return 

Все это делает создать StringBuilder присоединиться некоторые строки с переменными.

Поскольку вызов invokespecial на L35 имеет точно такой же стек, что и вызов invokespecial на L10, я решил добавить последовательность непосредственно перед L35 с ASM.

Когда я разобрался (снова с Кракатау), я нашел результаты довольно странными. ASM вычислил кадра стека в L10 быть:

.stack full 
    locals Object [Ljava/lang/String; Object java/lang/String 
    stack Object java/io/PrintStream Top Top 
.end stack 

вместо

stack Object java/io/PrintStream Object java/lang/StringBuilder Object java/lang/StringBuilder 

, как я ожидал.

Кроме того, этот класс также не прошел бы проверку, как нельзя позвонить StringBuilder#<init> по телефону Top. Согласно руководству ASM, Top относится к неинициализированному значению, но он не кажется неинициализированным в коде, как из места перехода, так и из кода раньше. Я не понимаю, что случилось с прыжком.

Есть ли что-то не так с прыжком, который я вставил, что каким-то образом делает класс невозможным для вычисления кадров? Возможно, это ошибка с ClassWriter ASM?

ответ

2

Неинициализированные экземпляры являются специальными. Учтите, что, когда вы ссылаетесь на ссылку, у вас уже есть две ссылки на один и тот же экземпляр в стеке, и вы можете выполнять еще больше манипуляций с стеками или передавать ссылку на локальную переменную и оттуда копировать ее в другие переменные или нажимать на нее снова ,Тем не менее, цель ссылки должна быть инициализирована ровно один раз, прежде чем вы ее каким-либо образом используете. Чтобы проверить это, личность объекта должна отслеживаться, так что все этих ссылок на тот же объект превратится из неинициализированных к инициализируется при выполнении invokespecial <init> на нем.

Язык программирования Java не использует все возможности, но для юридического кода как
new Foo(new Foo(new Foo(), new Foo(b? new Foo(a): new Foo(b, c))), он не должен потерять след, о котором Foo экземпляр был инициализирован и который не, когда ветвление.

Поэтому каждый Неинициализированный экземпляр элемент кадра кадра привязан к инструкции new, которая ее создала. Все записи сохраняют ссылку (которую можно обрабатывать так же просто, как remembering the byte code offset of the new instruction) при передаче или копировании. Только после того, как на него вызывается invokespecial <init>, все ссылки, указывающие на ту же команду new, превращаются в обычный экземпляр класса объявления и могут быть впоследствии объединены с другими типами совместимых записей.

Это означает, что ветка, как вы пытаетесь достичь, невозможна. Два нестандартных экземпляра записей того же типа, но созданные разными командами new, несовместимы. И несовместимые типы объединяются в запись Top, которая в основном неприемлема. Это может быть даже правильный код, если вы не пытаетесь использовать эту запись в целевом филиале, поэтому ASM не делает ничего плохого при объединении их с Top без жалобы.

Обратите внимание, что это также означает, что любой цикл, который может привести к кадру стека, имеющему более одного неинициализированного экземпляра, созданного с помощью той же инструкции new, не допускается.

+0

Благодарим вас за то, что каждое неинициализированное значение связано с созданным им оператором NEW. – konsolas

0

new java/lang/StringBuilder не создает действительный StringBuilder, а скорее унифицированный объект, который пожертвован TOP в рамах карты штабеля. Это значение используется, когда команда перехода добавляется во время строительства объекта, например:

new Foo(a ? b : c); 

, который переведен на несколько Гото заявлений.

Объект сначала считается StringBuilder, когда конструктор вызывается на объект, то есть invokespecial Method java/lang/StringBuilder <init>()V. JVM не поддерживает инициализацию этого объекта в другом месте, поскольку верификатор может просматривать только тип TOP, который не отражает желаемый тип фактического оттенка, который является унифицированным StringBuilder. Вы можете утверждать, что JVM должен поддерживать это, но для этого потребуются более крупные массивы, чтобы содержать фреймы кадров стека, чтобы отражать как тип, так и состояние инициализации, которые, вероятно, не оправдывали бы эту власть, которая даже не используется языком Java.

Чтобы пояснить это, рассмотрим следующий случай:

new Foo 
dup 
.stack full 
    locals 
    stack Top Top 
.end stack 
invokespecial Bar <init>()V 

Это было бы справедливо, если JVM разрешено беспрепятственное инициализации на TOP типов, но вы явно не должно быть позволено называть Bar конструктор на Foo.

+0

Кажется, вы как-то смутили себя. Если неинициализированный объект всегда был обозначен типом 'Top', который не может использоваться в точке слияния, как ваш собственный пример« новый Foo (a? B: c) »должен работать? – Holger

+0

В этом случае верификатор применяет проверки за пределами фреймов карты стека, но проверка невозможна, если между инструкцией 'new' и вызовом конструктора есть цель перехода. Такая дополнительная проверка также выполняется при вызове метода, где, например, 'Foo foo = this; foo.protectedMethod() 'не будет работать вне пакета метода. –

+0

Когда у вас есть код типа 'new Foo (a? B: c);', там * есть * ответ на ветку между командой 'new' и вызовом конструктора. На уровне байтового кода вы даже можете иметь промежутки между циклами. – Holger