Есть ли декомпилятор Java (будь то автономный или плагин Eclipse), который способен декомпилировать код, сплетенный AspectJ?

Я просмотрел различные связанные вопросы в Интернете (например http://www.java-decompiler.com/) и SO в частности. До сих пор я мог найти только два декомпилятора Java - JD-GUI и DJ Java Decompiler, которые утверждают, что являются актуальными.

Все остальные либо недоступны для скачивания, либо прекращены.

Итак, я взял файл .class, содержащий код AspectJ weaven, и декомпилировал его, используя оба доступных декомпилятора. Соблюдайте результаты:

  • JD-GUI: enter image description here
  • DJ Decompiler: enter image description here

Как вы можете видеть, оба инструмента не могут декомпилировать Java-код с помощью AspectJ.

Теперь я не слишком придирчив, я просто привык к .NET Reflector и ищет одинаковое качество в декомпиляторе Java, будь то автономный или плагин Eclipse, бесплатный или коммерческий.

Пожалуйста, помогите мне найти тот, который действительно работает (и удобен в использовании). Спасибо.

ИЗМЕНИТЬ

Хорошо, общее настроение ответов на мой вопрос - это что-то вроде этого: "Ну, что вам нужно? Хотя AspectJ создает допустимый байт-код JVM, этот байт-код не может быть переведен на допустимую Java". Все, что я могу сказать, повторить слова Sting - Я не подписался на эту точку зрения

Позвольте мне представить вам еще один контекст, и я надеюсь, вы согласитесь со мной в том, что инструменты могут и должны улучшаться.

Декомпилированный Java-класс был weaven, используя следующий аспект:

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) { ... }
}

Теперь класс weaven является следующим:

@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, с которыми эти декомпиляторы не справляются? Все, что я хочу, это декомпилятор Java, который находится на одном уровне с .NET Reflector, который не имеет абсолютно никакой проблемы с декомпиляцией инструментального кода С#, за исключением действительно особых случаев, и нас там нет.

РЕДАКТИРОВАТЬ 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);
  }
}

Выход, созданный диджеем:

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 создает поврежденный вывод. Так получилось, что код более-менее правильный, , но весь хвост метода выводится в одной строке! При просмотре внутри графического интерфейса он выглядит так, как метод усечен. Только после копирования кода вставку в 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 (мягко говоря), поэтому я не могу сказать, является ли байт-код JVM autoInstrumented "плохим". Можете ли вы?

Резюме

  • DJ не хорош
  • JD-GUI почти там, но все же создает плохую Java, а удобство использования - ужасно - нужно скопировать, вставить и переформатировать в Eclipse, чтобы понять, что происходит.

Bottomline

Нет любви.

Ответ 1

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

Ответ 2

Я бы сказал, что и компиляторы, и декомпиляторы работают по шаблонам. Оптимизация JIT для шаблонов, созданных компиляторами и декомпиляторами, распознает шаблоны байт-кода, чтобы создать "хороший" исходный код на декомпилированном выходе. AspectJ пытается вписаться в эти шаблоны и генерировать код, который выглядит так, как если бы вы написали его вручную (как в ручной кодировке, что пытается достичь этот аспект). Если есть ситуация, когда java, созданный декомпилятором, не очень хорош, я бы сказал, поднимая ошибку AspectJ, чтобы ткач мог быть изменен.

В тех случаях, когда вещи, скорее всего, ужасно ломаются, с try..catch..finally блокируются. Существуют определенные шаблоны, которые легко нарушаются при модификации байт-кода. Тем не менее, стоит обратить внимание на проблему AspectJ.

Чтобы узнать, как должен выглядеть байт-код, нужно скомпилировать с aspectj, попытаться декомпилировать, исправить ошибки в этом декомпилированном выпуске, скомпилировать этот декомпилированный материал с помощью javac, а затем сравнить его с тем, что изначально создавалось ajc.