기본 콘텐츠로 건너뛰기

[ JAVA ] Decompile 된 코드에서 this.val$xxx 는 어떻게 해석해야 할까?

  좋은 방법도 아니고 하지 않는 것이 좋기는 하지만 제대로 된 정보를 구할 수 없는 경우에 어쩔 수 없이 외부 jar 파일에서 원하는 기능과 유사한 기능을 구현한 코드를 검토해 봐야할 수 밖에는 없는 경우가 존재한다. 이럴 때는 Decompiler를 사용하여 원본에 근사치의 코드를 얻을 수 있다. Eclipse 에 Decompiler 를 설정하는 부분은 이전 게시글에 서 설명을 했었다. 

  이 때 Decompile 된 코드를 검토하다 보면 허걱스런 코드들이 존재하는 것을 볼 수 있다. 여러 가지 상황들이 있지만 많이 만나게 되는 "this.val$xxx" 코드가 한 가지 예라고 볼 수 있다. 이런 경우는 어떻게 해석을 해야할까?

  자바 스펙 (http://docs.oracle.com/javase/specs/jls/se5.0/html/lexical.html#3.8)에 의하면 '$' 는 변수명으로 사용할 수 있는 문자이지만 다음과 같은 추가 설명이 존재한다.

The $ character should be used only in mechanically generated source code or, rarely, to access preexisting names on legacy systems.
  위의 문장을 따르면 기계적으로 생성된 소스 코드 또는 극히 일부 기존 시스템의 이미 존재하는 이름을 참조하기 위해서만 사용하여야 한다는 제한적인 의미가 된다. 왜? 이런 제한이 있어야 하는 것일까? (정확한 것이라고 할 수는 없지만 아래와 같이 이해할수 있을것 같다)
  • 내부적인 클래스가 존재하는 것과 같이 인식 되는 경우 - 익명 클래스등과 같은 내부에 Inner Class가 존재하는 경우에 외부 클래스와 내부 클래스를 식별하기 위한 방식으로 outerclass$innerclass 로 사용 된다. 이와 같은 이유로 인해서 메서드의 내부에 익명 클래스가 사용되는 경우도 역시 마찬가지로 적용된다.
  • 난독 처리가 되는 경우 - 난독처리 역시 기계적으로 처리되는 것이기 때문에 이런 부호가 사용될 수 있다. (뭐 업체의 선택이기는 하겠지만...)
  자세한 이유나 상황등에 대해서는 여기서 다루기에는 분량이 많고 이해해야 하는 부분도 많기 때문에 따로 다루지는 않는다. 단지 코드에서 이런 코드를 보았을 때 어떻게 해석을 해야할지에 대해서 간단한 샘플을 통해서 알아보도록 하자.

public void defineAnonymousInnerClass(String name) {
  new Thread(name) {  //extra constructor argument "name"
    public void run() {
      System.out.println(this.val$name); //"this.val$"  is extra
    }
  }.start();
}
  위의 코드는 가상의 Decompile 된 코드로  name 이라는 변수가 외부 메서드에서 정의되어 사용되는데 이를 내부 Thread Class 의  run 메서드에서 사용되어야 하는 경우를 나타낸 것이다. 즉, 쉽게 생각하면 run 메서드에서 외부 메서드의 name 변수 값을 사용해야 하는 코드라는 것이다. 그럼 원본 코드와는 어떻게 다른지 코드를 확인해 보도록 하자.


public void defineAnonymousInnerClass(final String name)
{
  new Thread() {  
    public void run() {
      System.out.println(name);
    }
  }.start();
}
  위의 코드는 실제 원본 코드를 나타낸 것이다. 변경된 내용은 별거 없다. 위의 코드가 컴파일 과정을 거쳐서 Bytecode 로 변환되는 것 뿐이다. 주의할 부분은 내부에서 사용될 변수는 반드시 확정된 값이라는 것을 보장할 수 있도록 "final" 로 명시된 것이어야 한다는 점이다.

   Decompile 된 코드를 원본 코드로 변경하는 것에는 아래와 같은 규칙이 존재한다.

  • 내부 클래스에서 사용된 생성자 파라미터를 제거한다. (위의 코드에서는 new Thread(name) 을 new Thread() 로 변경하는 것이다)
  • 내부에서 사용할 변수에 대해서 변경이 없다는 의미로 final 이 지정되어야 한다. (위의 코드에서는 defineAnonymousInnerCalss(String name)을  defineAnonymousInnerCalss(final String name) 으로 변경하는 것이다.
  • 변수명을 원래의 변수 명으로 교체한다. (위의 코드에서는 this.val$name을 name 으로 변경하는 것이다)

  위에서 설명하는 방식은 일부에 불과하다. 원본 코드에 따라서 Decompile 된 코드에 다양한 코드가 존재할 수도 있다. 가장 좋은 것은 원리에 대한 것을 이해할 수 있는 정보를 찾아보는 것이 좋지만 현재는 상황이 그렇지 못하기 때문에 일단 해석 방법만 정리해 놓도록 한다.

댓글