3

저는 스택 맵 프레임이 Java에서 어떻게 작동 하는지를 ASM의 점프와 함께 재생하여 이해하려고 노력해 왔습니다. 그것은이하는 모든 변수를 몇 가지 문자열을 결합하기 위하여 StringBuilder을 만드는 것입니다ClassWriter ASM의 COMPUTE_FRAMES

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 

: (크라 카타로 분해) : 나는 몇 가지를 시도하는 간단한 방법을 만들었습니다.

L35의 invokespecial 호출은 L10의 invokespecial 호출과 정확히 같은 스택을 가지고 있기 때문에 L35 바로 앞에 ASN을 사용하여 ICONST_1; IFEQ L10 시퀀스를 추가하기로 결정했습니다.

내가 (다시 Krakatau로) 분해했을 때, 나는 결과가 이상하다는 것을 발견했다. 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 

의.

또한이 클래스는 TopStringBuilder#<init>을 호출 할 수 없으므로 확인을 통과하지 못합니다. ASM 설명서에 따르면 Top은 초기화되지 않은 값을 나타내지 만 점프 위치와 코드 이전의 코드에서는 초기화되지 않은 것처럼 보입니다. 나는 점프에 무엇이 잘못되었는지 이해하지 못한다.

내가 삽입 한 점프에 문제가있어 어떻게 든 프레임을 계산할 수없는 클래스를 만들었습니까? 아마도 ASM의 ClassWriter에 버그가 있습니까?

답변

2

초기화되지 않은 인스턴스는 특별합니다. 예를 들어, dup 참조를 사용하면 스택에있는 동일한 인스턴스에 대한 두 개의 참조가 이미 있고 더 많은 스택 조작을 수행하거나 참조를 로컬 변수로 전송 한 다음 그 곳에서 다른 변수로 복사하거나 다시 밀어 넣을 수 있습니다 .그럼에도 불구하고 참조의 대상은 어떤 식 으로든 사용하기 전에 정확히 한 번 초기화되어야합니다. 당신이 상 invokespecial <init>을 수행 할 때 동일한 개체에 대한 모든이 참조를 초기화 초기화되지 않은에에서 켜 것이다 있도록이를 확인하려면 개체의 정체성, 추적해야합니다.

자바 프로그래밍 언어는 모든 가능성을 사용하지만,
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 항목에 병합됩니다. 분기 대상에서 해당 항목을 사용하지 않으려는 경우에도 올바른 코드 일 수 있으므로 Top에 병합 할 때 ASM이 잘못된 작업을하지 않습니다.

이것은 동일한 new 명령으로 생성 된 둘 이상의 초기화되지 않은 인스턴스가있는 스택 프레임으로 이어질 수있는 모든 종류의 루프가 허용되지 않는다는 것을 의미합니다.

+0

감사합니다. 초기화되지 않은 각 값이 생성 된 NEW 문에 바인딩되어 있다는 사실이 여기에 핵심적인 것처럼 보입니다. – konsolas

0

new java/lang/StringBuilder은 유효한 StringBuilder을 만들지 않고 스택 맵 프레임에 TOP으로 기증 된 단위화된 개체를 만듭니다. 이 값은 객체의 생성 중에 점프 명령이 추가 될 때 사용됩니다. 예를 들면 다음과 같습니다.

new Foo(a ? b : c); 

이것은 여러 goto 문으로 변환됩니다.

개체는 생성자가 개체 (예 : invokespecial Method java/lang/StringBuilder <init>()V)에 대해 호출 될 때 먼저 StringBuilder으로 간주됩니다. JVM은 검증 자 (verifier)가 단위 형식 StringBuilder 인 원하는 유형의 실제 음영을 반영하지 않는 TOP 유형만을 볼 수 있기 때문에이 객체를 다른 위치에서 초기화하는 것을 지원하지 않습니다. JVM이 이것을 지원해야한다고 주장 할 수는 있지만, 스택과 프레임을 포함하는 큰 배열은 Java 언어에서 사용되지 않는이 힘을 정당화하지 못하는 유형과 초기화 상태를 모두 반영해야합니다.

이 분명, 다음과 같은 경우를 생각해 만들려면 : JVM이 TOP 유형을 선택하지 않은 초기화를 허용하지만 당신은 분명히 FooBar 생성자를 호출 할 수 없습니다하는 경우는
new Foo 
dup 
.stack full 
    locals 
    stack Top Top 
.end stack 
invokespecial Bar <init>()V 

이 유효 할 것이다.

+0

어떻게 든 혼란스러워합니다. 초기화되지 않은 객체가 병합 점에서 사용할 수없는'Top' 유형으로 항상 표시 되었다면, 어떻게 당신의 예제가 되었습니까?'new Foo (a? b : c)'가 작동할까요? – Holger

+0

이 경우, 검증자는 스택 맵 프레임 이상으로 검사를 적용하지만 'new' 명령과 생성자 호출 사이에 점프 대상이있는 경우 검증을 수행 할 수 없습니다. 이러한 추가적인 검증은 또한 메소드 호출에 대해서도 수행된다. 'Foo foo = this; foo.protectedMethod()'는 메소드 패키지 밖에서 작동하지 않습니다. –

+0

'new Foo (a? b : c);'와 같은 코드를 사용하면'new' 명령어와 생성자 호출 사이에 분기 타겟이 * 존재합니다. 바이트 코드 수준에서는 중간에 루프가있을 수도 있습니다. – Holger