是否有一个Java反编译器(无论是独立的还是Eclipse插件)能够反编译由AspectJ编织的代码?

时间:2011-12-08 12:34:51

标签: java decompiling

我已经在互联网上扫描了各种相关问题(例如http://www.java-decompiler.com/)和SO。到目前为止,我只能找到两个声称是最新的Java反编译器 - JD-GUIDJ Java Decompiler

所有其他内容无法下载或停止使用。

因此,我使用包含AspectJ weaven代码的.class文件,并使用两个可用的反编译器对其进行反编译。观察结果:

  1. JD-GUI:

    enter image description here

  2. DJ Java Decompiler:

    enter image description here

  3. 正如您所看到的,这两个工具都无法使用AspectJ反编译Java代码。

    现在我不是太挑剔,我只是习惯了.NET Reflector并在Java反编译器中寻找相同的质量,无论是独立的还是Eclipse插件,免费或商业。

    我正在寻找一种实际可行的,并且使用方便。

    修改

    对我的问题的回答的一般趋势是这样的 - “嗯,你想要什么?虽然,AspectJ创建有效的JVM字节码,但这个字节码无法转换成有效的Java”。我只能说我不赞成这种观点。

    让我向您介绍更多背景信息,我希望您同意这些工具可以而且应该做得更好。

    反编译的Java类使用以下方面:

    public abstract aspect LoggingAspect {
      declare parents: (@LogMe *) implements ILoggable;
    
      public Logger ILoggable.getLogger() {
        LoggerHolderAspect holder = LoggerHolderAspect.aspectOf(this.getClass());
        return holder.getLogger();
      }
    
      abstract pointcut loggedMethods();
    
      before(ILoggable o): loggedMethods() && this(o) {
        logBefore(o.getLogger(), thisJoinPoint);
      }
    
      after(ILoggable o) returning (Object result): loggedMethods() && this(o) {
        logAfterReturning(o.getLogger(), thisJoinPoint, result);
      }
    
      after(ILoggable o) throwing (Exception e): loggedMethods() && this(o) {
        logAfterThrowing(o.getLogger(), thisJoinPoint, e);
      }
    
      protected void logBefore(Logger l, JoinPoint jp) { ... }
      protected void logAfterReturning(Logger l, JoinPoint jp, Object result) { ... }
      protected void logAfterThrowing(Logger l, JoinPoint jp, Exception e) { ... }
    }
    

    现在,正在上课的是:

    @Path("user")
    public class UserHandler {
      ...    
    
      @GET
      @Path("{id}")
      @Produces({ "application/json", "application/xml" })
      public User getUser(@PathParam("id") int id) { ... }
    
      @DELETE
      @Path("{id}")
      public void deleteUser(@PathParam("id") int id) { ... }
    
      @PUT
      @Path("{id}")
      public void putUser(@PathParam("id") int id, User entity) { ... }
    
      @POST
      @Produces({ "application/json", "application/xml" })
      public Response postUser(User entity) { ... }
    }
    

    现在,JD-GUI无法正确反编译每个检测方法。生成的输出看起来像是一个糟糕的SVN合并。这是好奇的完整文件的链接 - http://pastebin.com/raw.php?i=WEmMNCPS

    DJ Java Decompiler产生的输出略胜一筹。看起来像DJ有非空方法的问题。确实,观察它如何反编译void方法:

    @DELETE
    @Path(value="{id}")
    public void deleteUser(@PathParam(value="id") int id)
    {
        int i = id;
        org.aspectj.lang.JoinPoint joinpoint = Factory.makeJP(ajc$tjp_1, this, this, Conversions.intObject(i));
        try
        {
            ResourceHandlerLoggingAspect.aspectOf().ajc$before$com_shunra_poc_logging_LoggingAspect$1$51e061ae(this, joinpoint);
            if(!securityContext.isUserInRole(Enroler.ADMINISTRATOR.getName()))
            {
                throw new WebApplicationException(javax.ws.rs.core.Response.Status.UNAUTHORIZED);
            } else
            {
                m_userService.delete(id);
                Object obj = null;
                ResourceHandlerLoggingAspect.aspectOf().ajc$afterReturning$com_shunra_poc_logging_LoggingAspect$2$51e061ae(this, obj, joinpoint);
                return;
            }
        }
        catch(Exception exception)
        {
            ResourceHandlerLoggingAspect.aspectOf().ajc$afterThrowing$com_shunra_poc_logging_LoggingAspect$3$51e061ae(this, exception, joinpoint);
            throw exception;
        }
    }
    

    这很好,但如果方法返回某些内容,则DJ会失败,例如:

    @POST
    @Produces(value={"application/json", "application/xml"})
    public Response postUser(User entity)
    {
        org.aspectj.lang.JoinPoint joinpoint;
        User user = entity;
        joinpoint = Factory.makeJP(ajc$tjp_3, this, this, user);
        ResourceHandlerLoggingAspect.aspectOf().ajc$before$com_shunra_poc_logging_LoggingAspect$1$51e061ae(this, joinpoint);
        entity.Id = 0;
        m_userService.post(entity);
        Response response;
        Response response1 = response = Response.created(postedUserLocation(entity.Id)).entity(new EntityPostResult(entity.Id)).build();
        ResourceHandlerLoggingAspect.aspectOf().ajc$afterReturning$com_shunra_poc_logging_LoggingAspect$2$51e061ae(this, response1, joinpoint);
        return response;
        Exception exception;
        exception;
        ResourceHandlerLoggingAspect.aspectOf().ajc$afterThrowing$com_shunra_poc_logging_LoggingAspect$3$51e061ae(this, exception, joinpoint);
        throw exception;
    }
    

    同样,这是好奇的完整输出 - http://pastebin.com/raw.php?i=Qnwjm16y

    AspectJ究竟做了什么,这些反编译器无法应对?我想要的只是一个与.NET Reflector相同的Java反编译器,除了真正的特殊情况之外,它对反编译的C#代码完全没有问题,我们不在那里。

    编辑2

    根据Andy Clement的建议,我创建了一个小应用程序,仅用于测试AspectJ如何检测代码,将其与手动检测进行比较,并了解JD-GUI和DJ如何对其进行反编译。以下是我的发现:

    Java源代码是:

    public class Program {
      private static boolean doThrow;
    
      public static void main(String[] args) {
        run();
        doThrow = true;
        run();
      }
    
      private static void run() {
        System.out.println("===============================================");
        System.out.println("doThrow = " + doThrow);
        System.out.println("===============================================");
        System.out.println("autoInstrumented:");
        try {
          System.out.println(autoInstrumented());
        } catch (Exception e) {
          System.out.println(e.getMessage());
        }
        System.out.println("===============================================");
        System.out.println("manuallyInstrumented:");
        try {
          System.out.println(manuallyInstrumented());
        } catch (Exception e) {
          System.out.println(e.getMessage());
        }
        System.out.println("===============================================");
      }
    
      public static void before() {
        System.out.println("before(f)");
      }
    
      public static void afterReturning(int x) {
        System.out.println("afterReturning(f) = " + x);
      }
    
      public static void afterThrowing(Exception e) {
        System.out.println("afterThrowing(f) = " + e.getMessage());
      }
    
      public static int f() throws Exception {
        if (doThrow) {
          throw new Exception("*** EXCEPTION !!! ***");
        }
        return 10;
      }
    
      public static int autoInstrumented() throws Exception {
        return f();
      }
    
      public static int manuallyInstrumented() throws Exception {
        before();
        try {
          int result = f();
          afterReturning(result);
          return result;
        } catch (Exception e) {
          afterThrowing(e);
          throw e;
        }
      }
    }
    

    方面代码是:

    public aspect Weave {
      pointcut autoInstrumented() : execution(int Program.autoInstrumented());
    
      before() : autoInstrumented() {
        Program.before();
      }
    
      after() returning (int result) : autoInstrumented() {
        Program.afterReturning(result);
      }
    
      after() throwing (Exception e) : autoInstrumented() {
        Program.afterThrowing(e);
      }
    }
    

    DJ产生的输出是:

    public static int autoInstrumented()
        throws Exception
    {
        Weave.aspectOf().ajc$before$Weave$1$be1609d6();
        int i;
        int j = i = f();
        Weave.aspectOf().ajc$afterReturning$Weave$2$be1609d6(j);
        return i;
        Exception exception;
        exception;
        Weave.aspectOf().ajc$afterThrowing$Weave$3$be1609d6(exception);
        throw exception;
    }
    

    抛开AspectJ创建的方法的名称,生成的Java代码既错误又无效。这是错误的,因为没有try-catch语句。它无效,因为exception;不是有效的Java语句。

    接下来是JD-GUI:

    public static int autoInstrumented() throws Exception {
      try {
        Weave.aspectOf().ajc$before$Weave$1$be1609d6();
        int i;
        int j = i = f(); Weave.aspectOf().ajc$afterReturning$Weave$2$be1609d6(j); return i; } catch (Exception localException) { Weave.aspectOf().ajc$afterThrowing$Weave$3$be1609d6(localException); } throw localException;
    }
    

    我必须接受有关JD-GUI产生损坏的输出的话。碰巧代码或多或少是正确的,但是所有的方法尾都在一行输出!在GUI内部查看时,看起来该方法被截断。只有在复制代码之后,在Eclipse中粘贴并重新格式化才能看到它几乎没问题:

    public static int autoInstrumented() throws Exception {
      try {
        Weave.aspectOf().ajc$before$Weave$1$be1609d6();
        int i;
        int j = i = f();
        Weave.aspectOf().ajc$afterReturning$Weave$2$be1609d6(j);
        return i;
      } catch (Exception localException) {
        Weave.aspectOf().ajc$afterThrowing$Weave$3$be1609d6(localException);
      }
      throw localException;
    }
    

    差不多,因为throw localException;语句怎么会发现自己在catch块之外?

    现在,对于实际的JVM字节代码。我使用了ByteCode Outline Eclipse扩展,结果如下:

    手动检测的方法manuallyInstrumented

    public static manuallyInstrumented()I throws java/lang/Exception 
    ATTRIBUTE org.aspectj.weaver.MethodDeclarationLineNumber : unknown
      TRYCATCHBLOCK L0 L1 L2 java/lang/Exception
     L3
      INVOKESTATIC Program.before()V
     L0
      INVOKESTATIC Program.f()I
      ISTORE 0
     L4
      ILOAD 0
      INVOKESTATIC Program.afterReturning(I)V
     L5
      ILOAD 0
     L1
      IRETURN
     L2
     FRAME SAME1 java/lang/Exception
      ASTORE 0
     L6
      ALOAD 0
      INVOKESTATIC Program.afterThrowing(Ljava/lang/Exception;)V
     L7
      ALOAD 0
      ATHROW
     L8
      LOCALVARIABLE result I L4 L2 0
      LOCALVARIABLE e Ljava/lang/Exception; L6 L8 0
      MAXSTACK = 1
      MAXLOCALS = 1
    

    自动检测方法autoInstrumented

    public static autoInstrumented()I throws java/lang/Exception 
    ATTRIBUTE org.aspectj.weaver.MethodDeclarationLineNumber : unknown
      TRYCATCHBLOCK L0 L1 L1 java/lang/Exception
     L0
      INVOKESTATIC Weave.aspectOf()LWeave;
      INVOKEVIRTUAL Weave.ajc$before$Weave$1$be1609d6()V
      INVOKESTATIC Program.f()I
      DUP
      ISTORE 0
      DUP
      ISTORE 1
      INVOKESTATIC Weave.aspectOf()LWeave;
      ILOAD 1
      INVOKEVIRTUAL Weave.ajc$afterReturning$Weave$2$be1609d6(I)V
      ILOAD 0
      IRETURN
     L1
      ASTORE 2
      INVOKESTATIC Weave.aspectOf()LWeave;
      ALOAD 2
      INVOKEVIRTUAL Weave.ajc$afterThrowing$Weave$3$be1609d6(Ljava/lang/Exception;)V
      ALOAD 2
      ATHROW
      MAXSTACK = 3
      MAXLOCALS = 3
    

    我不是JVM大师(温和地说),所以我无法判断autoInstrumented的JVM字节代码是否“坏”。你能吗?

    摘要

    • DJ不好
    • JD-GUI几乎就在那里,但仍然会产生糟糕的Java并且可用性很糟糕 - 需要在Eclipse中复制,粘贴和重新格式化以了解正在发生的事情。

    底线

    没有爱。

2 个答案:

答案 0 :(得分:4)

Java字节码可以表达字面上无法直接转换为Java的内容,因此,由直接生成字节码的工具创建的类文件不一定 反编译。人类可以编写类似的 Java代码,但这是一个AI问题,与反编译完全不同。

答案 1 :(得分:3)

我想说编译器和反编译器都是按模式工作的。 JIT针对编译器和反编译器生成的模式进行优化,识别字节码模式,以便在反编译输出中创建“漂亮”的源代码。 AspectJ尝试适应这些模式并生成看起来像手工编写的代码(如手工编码方面试图实现的那样)。如果出现反编译器生成的java不漂亮的情况,我会说提出一个AspectJ错误,以便编织器可以调整。

哪些事情最有可能发生可怕的破坏是try..catch..finally块。那里有特定的模式很容易受到字节码修改的干扰。但是,值得提出一个AspectJ问题进行调查。

要查看字节码应该是什么样的,需要编译aspectj,尝试反编译,修复反编译输出中的错误,用javac编译这个反编译的东西,然后比较它与ajc最初制作的一样。