2015-01-08 3 views
2

В ASM я пытаюсь определить метки для блока try-catch.Определите, где заканчивается блок catch ASM

В настоящее время у меня есть:

public void printTryCatchLabels(MethodNode method) { 

    if (method.tryCatchBlocks != null) { 
     for (int i = 0; i < method.tryCatchBlocks.size(); ++i) { 

      Label start = method.tryCatchBlocks.get(i).start.getLabel(); 
      Label end = method.tryCatchBlocks.get(i).end.getLabel(); 
      Label catch_start = method.tryCatchBlocks.get(i).handler.getLabel(); 

      System.out.println("try{  " + start.toString()); 
      System.out.println("}   " + end.toString()); 
      System.out.println("catch { " + catch_start.toString()); 
      System.out.println("}   " /*where does the catch block end?*/); 

     } 
    } 

} 

Я пытаюсь определить, где метка для конца блока поймать, но я не знаю, как. Зачем мне это нужно? Потому что я хочу «удалить» блоки try-catch из байтового кода.

Например, я пытаюсь изменить:

public void test() { 
    try { 
     System.out.println("1"); 
    } catch(Exception e) { 
     //optionally rethrow e. 
    } 
    System.out.println("2"); 
} 

к:

public void test() { 
    System.out.println("1"); 
    System.out.println("2"); 
} 

Так, чтобы удалить его, я думал, что я мог бы просто получить ярлыки и удалить все инструкции между уловом -start и catch-end, а затем удалить все ярлыки.

Любые идеи?

ответ

1

Рекомендую прочитать JVM Spec §3.12. Throwing and Handling Exceptions. Он содержит пример, который очень прост, но все еще демонстрирует проблемы с вашей идеей:

Компиляция конструкций try-catch проста. Например:

void catchOne() { 
    try { 
     tryItOut(); 
    } catch (TestExc e) { 
     handleExc(e); 
    } 
} 

скомпилирован как:

Method void catchOne() 
0 aload_0    // Beginning of try block 
1 invokevirtual #6 // Method Example.tryItOut()V 
4 return    // End of try block; normal return 
5 astore_1   // Store thrown value in local var 1 
6 aload_0    // Push this 
7 aload_1    // Push thrown value 
8 invokevirtual #5 // Invoke handler method: 
         // Example.handleExc(LTestExc;)V 
11 return    // Return after handling TestExc 
Exception table: 
From To  Target  Type 
0  4  5   Class TestExc 

Здесь catch блока заканчивается return инструкции, таким образом, не присоединяется с исходным кодом потоком. Это, однако, не является обязательным поведением. Вместо этого, скомпилированный код может иметь ветвь последней return инструкции в месте 4 return инструкции, т.е.

Method void catchOne() 
0: aload_0 
1: invokevirtual #6 // Method tryItOut:()V 
4: goto   13 
7: astore_1 
8: aload_0 
9: aload_1 
10: invokevirtual #5 // Method handleExc:(LTestExc;)V 
13: return 
Exception table: 
From To  Target  Type 
    0  4   7  Class TestExc 

(например, по меньшей мере, одна версия Eclipse, составленный на примере именно так)

Но это могло также быть наоборот, с ответом на инструкцию 4 вместо последней инструкции return.

Method void catchOne() 
0 aload_0 
1 invokevirtual #6 // Method Example.tryItOut()V 
4 return 
5 astore_1 
6 aload_0 
7 aload_1 
8 invokevirtual #5 // Method Example.handleExc(LTestExc;)V 
11 goto 4 
Exception table: 
From To  Target  Type 
0  4  5   Class TestExc 

Таким образом, у вас уже есть три возможности скомпилировать этот простой пример, который не содержит никаких условных обозначений. Условные ветви, связанные с циклами или команды if, необязательно указывают на инструкцию сразу после условного блока кода.Если за этим блоком кода будет следовать другая команда управления потоком, условная ветвь (то же самое относится к switch целям) может привести к короткому замыканию ветви.

Так что очень сложно определить, какой код принадлежит блоку catch. На уровне байтового кода он даже не должен быть смежным блоком, но может чередоваться с другим кодом.

И в это время мы даже не говорили о compiling finally и synchronized или новее try(…) with resources заявление. Все они создают обработчики исключений, которые выглядят как catch блоков на уровне байтового кода.


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

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

1

Короче говоря, вам нужно будет выполнить анализ потока выполнения. В вашем примере:

public void test() { 
    try {       // (1) try start 
     System.out.println("1"); 
    }        // (2) try end 
    catch(Exception e) {   
     //optionally rethrow e. // (3) catch start 
    }        // (4) catch end 
    System.out.println("2");  // (5) continue execution 
} 

Графически это будет выглядеть следующим образом:

---(1)-+--(2)---------------------+ 
     |       +--(5 execution path merged) 
     +--(3 branched here)--(4)--+ 

Итак, вам нужно построить график кодовых блоков, а затем удалить узлы, связанные с (3) и (4) , В настоящее время ASM не предоставляет инструменты анализа потока выполнения, хотя некоторые пользователи сообщили, что они создают такие инструменты поверх дерева дерева ASM.

+0

Но как найти (4)? – Brandon

+0

Следуя цепочке инструкций через все возможные ветви, условные прыжки и вложенные блоки try/catch. –

0

На уровне байт-кода, за исключением обработки, по сути, является goto. Код не должен быть структурирован или даже иметь четко определенный блок catch. И даже если вы имеете дело только с обычно скомпилированным Java-кодом, это все еще довольно сложно, если вы рассмотрите возможности попыток с ресурсами или сложные структуры потока управления внутри блока catch.

Если вы просто хотите удалить код, связанный с «блоком catch», я бы рекомендовал просто удалить связанную запись обработчика исключений, а затем выполнить пропуск для удаления мертвого кода. Вы, вероятно, можете найти существующий проход DCE где-нибудь (например, Саут), или вы можете написать свой собственный.

Смежные вопросы