Прекрасная печать сигнатуры метода Java и обратный анализ

Я делаю редактор байт-кода Java, который переводит байт-код в какой-то промежуточный код, который может быть изменен пользователем, а затем анализирует этот код обратно в байт-код. На данный момент объявление метода выглядит ужасно и трудночитаемо. Но, хорошо, это работает.

method [public,static] testMethod (Ljava/lang/Object;)Ljava/lang/Object; throws [java/lang/Exception] <T:Ljava/lang/Object;>(TT;)TT; 

Как вы можете видеть, есть флаги доступа, имя метода, описание метода, сигнатура дженериков и исключение, которые я получаю от

org.objectweb.asm.ClassVisitor#visitMethod

Есть ли удобный способ сделать это похожим на исходный код, а затем извлечь из этой красивой строки всю эту информацию? Метод выше должен быть таким:

public static <T> T testMethod(T) throws Exception

Благодарю вас!


person BeforeBigBang    schedule 08.10.2015    source источник
comment
Ну так напиши парсер. Я думаю, что в ANTLR есть примеры грамматик, с которых можно начать.   -  person Axel    schedule 08.10.2015
comment
Что ж, первое, что приходит на ум, — использовать регулярное выражение с правильными группами захвата. Это должно дать вам правильную информацию в каждой группе. Но знаете, как говорят... у программиста есть проблема, и он решает ее решить с помощью регулярных выражений. Теперь у него две проблемы! В качестве альтернативы вы можете просто закодировать метод парсера, чтобы вернуть вам объект, содержащий нужные вам поля. Получение красивого вывода может быть таким же простым (?), как использование toString для его форматирования по вашему желанию... И да, мне также нравится предложение ANTLR от @Axel.   -  person triadiktyo    schedule 08.10.2015


Ответы (1)


Если вы реализуете метод

public MethodVisitor visitMethod(
  int access, String name, String desc, String signature, String[] exceptions)

у вас уже есть большая часть необходимой информации, которую вы можете собрать в удобочитаемую декларацию. Самым большим препятствием является подпись, но, к счастью, библиотека ASM уже предоставляет инструменты, помогающие в этом.

В следующем коде используются SignatureReader и TraceSignatureVisitor для их форматирования. К сожалению, он требует некоторой постобработки, так как он не разделяет параметры типа и параметры метода и опускает Object возвращаемых типов для необобщенных методов. Кроме того, он генерирует список исключений, только если есть общие исключения, поэтому в противном случае нам придется делать это вручную.

static String decode(int access, String name, String desc, String signature, String[] exceptions) {
  if(signature==null) signature=desc;
  StringBuilder sb=new StringBuilder();
  appendModifiers(sb, access);
  TraceSignatureVisitor v = new TraceSignatureVisitor(0);
  new SignatureReader(signature).accept(v);

  String declaration = v.getDeclaration(), rType = v.getReturnType();
  if(declaration.charAt(0)=='<')
    sb.append(declaration, 0, declaration.indexOf("(")).append(' ');
  else if(rType.isEmpty() || rType.charAt(0)=='[')
    sb.append("java.lang.Object");
  sb.append(rType).append(' ').append(name)
    .append(declaration, declaration.indexOf('('), declaration.length());
  if((access&Opcodes.ACC_VARARGS)!=0 && declaration.endsWith("[])"))
    sb.replace(sb.length()-3, sb.length(), "...)");
  String genericExceptions = v.getExceptions();
  if(genericExceptions!=null && !v.getDeclaration().isEmpty())
    sb.append(" throws ").append(genericExceptions);
  else if(exceptions!=null && exceptions.length>0) {
    sb.append(" throws ");
    int pos=sb.length();
    for(String e: exceptions) sb.append(e).append(", ");
    int e=sb.length()-2;
    sb.setLength(e);
    for(; pos<e; pos++) if(sb.charAt(pos)=='/') sb.setCharAt(pos, '.');
  }
  return sb.toString();
}

private static void appendModifiers(StringBuilder buf, int access) {
  for(int bit; access!=0; access-=bit) {
    bit=access & -access;
    switch(bit) {
      case Opcodes.ACC_PUBLIC:    buf.append("public "); break;
      case Opcodes.ACC_PRIVATE:   buf.append("private "); break;
      case Opcodes.ACC_PROTECTED: buf.append("protected "); break;
      case Opcodes.ACC_STATIC:    buf.append("static "); break;
      case Opcodes.ACC_FINAL:     buf.append("final "); break;
      case Opcodes.ACC_ABSTRACT:  buf.append("abstract "); break;
      case Opcodes.ACC_NATIVE:    buf.append("native "); break;
      case Opcodes.ACC_STRICT:    buf.append("strictfp "); break;
      case Opcodes.ACC_SYNCHRONIZED: buf.append("synchronized "); break;
    }
  }
}

Он также декодирует модификаторы вручную, как вы можете видеть в конце, но это не такая уж большая проблема.

Если вы протестируете его с помощью:

String[] exceptions={"java/lang/Exception"};
System.out.println(decode(9, "testMethod", "(Ljava/lang/Object;)Ljava/lang/Object;",
    "<T:Ljava/lang/Object;>(TT;)TT;", exceptions));

он будет печатать:

public static <T> T testMethod(T) throws java.lang.Exception

Когда дело доходит до разбора такого объявления, все становится сложнее. Поскольку вы собираетесь реализовать редактор, а не ассемблер, вы можете подумать об альтернативе предоставления отдельных компонентов редактора для различных функций, например. флажки и поля со списком для модификаторов, текстовое поле для имени и редактор списка для типов параметров.

На этом этапе я бы рекомендовал изучить Type< /а> класс. Это позволяет извлекать типы параметров и возвращает тип из подписи, и после применения изменений вы можете восстановить подпись.

Точно так же SignatureReader и SignatureWriter может помочь справиться с общей подписью в структурным способом, а не преобразовывать их в текстовую форму и повторно анализировать эту форму обратно.

person Holger    schedule 09.10.2015