[jboss-jira] [JBoss JIRA] (JASSIST-222) Using insertBeforeBody to insert try/catch block in constructor can generate invalid bytecode for certain Java compilers

Manuel Geffken (JIRA) issues at jboss.org
Mon May 5 09:06:57 EDT 2014


     [ https://issues.jboss.org/browse/JASSIST-222?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel ]

Manuel Geffken updated JASSIST-222:
-----------------------------------

    Description: 
The class S in the "boyer" benchmark of the Ashes Suite Collection (http://www.sable.mcgill.ca/ashes/) (no original source code available) has the following bytecode according to javap:
{noformat}
Classfile ashesSuiteCollection/suites/ashesHardTestSuite/benchmarks/boyer/classes/S.class
  Last modified May 2, 2014; size 154 bytes
  MD5 checksum e79a1481a39d40004bc504cc4f7c2641
class S
  minor version: 3
  major version: 45
  flags: ACC_SUPER
Constant pool:
...
{
  protected int $;
    flags: ACC_PROTECTED

  protected S(int);
    flags: ACC_PROTECTED
    Code:
      stack=3, locals=2, args_size=2
         0: aload_0       
         1: iload_1       
         2: aload_0       
         3: invokespecial #14                 // Method java/lang/Object."<init>":()V
         6: putfield      #13                 // Field $:I
         9: return        
}
{noformat}
An instrumentation of this class' constructor with a try/catch block as in
{noformat}
            CtConstructor[] ctors = target.getDeclaredConstructors();
            for (CtConstructor ctor : ctors) {
            	StringBuilder headerSB = new StringBuilder();
            	headerSB.append("{");
                headerSB.append("  try {");
                headerSB.append("    throw new java.lang.Exception();");
                headerSB.append("  } catch(java.lang.Exception e) {}");
                headerSB.append("}");
                ctor.insertBeforeBody(headerSB.toString());
            } 
{noformat}
causes an invalid class file that does not verify:
{noformat}
Classfile /home/geffken/git/X2Traverse/benchmarks/basic/ashesSuiteCollection/suites/ashesHardTestSuite/benchmarks/boyer/out_classes/S.class
  Last modified May 5, 2014; size 356 bytes
  MD5 checksum d4ef556ee82cee6c4c2960b43d7e71e1
class S
  minor version: 3
  major version: 45
  flags: ACC_SUPER
Constant pool:
...
{
  protected int $;
    flags: ACC_PROTECTED

  protected S(int);
    flags: ACC_PROTECTED
    Code:
      stack=5, locals=3, args_size=2
         0: aload_0       
         1: iload_1       
         2: aload_0       
         3: invokespecial #14                 // Method java/lang/Object."<init>":()V
         6: new           #22                 // class java/lang/Exception
         9: dup           
        10: invokespecial #23                 // Method java/lang/Exception."<init>":()V
        13: athrow        
        14: astore_2      
        15: goto          18
        18: putfield      #13                 // Field $:I
        21: return        
      Exception table:
         from    to  target type
             6    14    14   Class java/lang/Exception
}
{noformat}
The problem is that the operand stack can be empty when the putfield instruction is reached. The problem seems to be rooted in the following bytecode sequence.
{noformat}
         1: iload_1       
         2: aload_0       
         3: invokespecial #14                 // Method java/lang/Object."<init>":()V
{noformat}
In contrast the reconstructed source code (the source is not available)
{noformat}
 public class S {
   int $;
 
   public S(int i) {
     $ = i;
   }
}
{noformat}
compiled with javac 1.7.0_51 (javac -source 1.3 -target 1.1) produces
{noformat}
Classfile S.class
  Last modified May 5, 2014; size 222 bytes
  MD5 checksum 9700e6458db9d62e640018421a079761
  Compiled from "S.java"
public class S
  SourceFile: "S.java"
  minor version: 3
  major version: 45
  flags: ACC_SUPER
Constant pool:
...
{
  int $;
    flags: 

  public S(int);
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=2, args_size=2
         0: aload_0       
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: aload_0       
         5: iload_1       
         6: putfield      #2                  // Field $:I
         9: return        
}
{noformat}
This version can be instrumented as shown above without problems. Here the respective bytecode sequence is as follows.
{noformat}
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: aload_0       
         5: iload_1       
{noformat}
This sequence seems easier to instrument (after the super call) with code containing a try/catch block.


  was:
The class S in the "boyer" benchmark of the Ashes Suite Collection (http://www.sable.mcgill.ca/ashes/) (no original source code available) has the following bytecode according to javap:
{noformat}
Classfile ashesSuiteCollection/suites/ashesHardTestSuite/benchmarks/boyer/classes/S.class
  Last modified May 2, 2014; size 154 bytes
  MD5 checksum e79a1481a39d40004bc504cc4f7c2641
class S
  minor version: 3
  major version: 45
  flags: ACC_SUPER
Constant pool:
...
{
  protected int $;
    flags: ACC_PROTECTED

  protected S(int);
    flags: ACC_PROTECTED
    Code:
      stack=3, locals=2, args_size=2
         0: aload_0       
         1: iload_1       
         2: aload_0       
         3: invokespecial #14                 // Method java/lang/Object."<init>":()V
         6: putfield      #13                 // Field $:I
         9: return        
}
{noformat}
An instrumentation of this class' constructor with a try/catch block as in
{noformat}
            CtConstructor[] ctors = target.getDeclaredConstructors();
            for (CtConstructor ctor : ctors) {
            	StringBuilder headerSB = new StringBuilder();
            	headerSB.append("{");
                headerSB.append("  try {");
                headerSB.append("    throw new java.lang.Exception();");
                headerSB.append("  } catch(java.lang.Exception e) {}");
                headerSB.append("}");
                ctor.insertBeforeBody(headerSB.toString());
            } 
{noformat}
causes an invalid class file that does not verify:
{noformat}
Classfile /home/geffken/git/X2Traverse/benchmarks/basic/ashesSuiteCollection/suites/ashesHardTestSuite/benchmarks/boyer/out_classes/S.class
  Last modified May 5, 2014; size 356 bytes
  MD5 checksum d4ef556ee82cee6c4c2960b43d7e71e1
class S
  minor version: 3
  major version: 45
  flags: ACC_SUPER
Constant pool:
...
{
  protected int $;
    flags: ACC_PROTECTED

  protected S(int);
    flags: ACC_PROTECTED
    Code:
      stack=5, locals=3, args_size=2
         0: aload_0       
         1: iload_1       
         2: aload_0       
         3: invokespecial #14                 // Method java/lang/Object."<init>":()V
         6: new           #22                 // class java/lang/Exception
         9: dup           
        10: invokespecial #23                 // Method java/lang/Exception."<init>":()V
        13: athrow        
        14: astore_2      
        15: goto          18
        18: putfield      #13                 // Field $:I
        21: return        
      Exception table:
         from    to  target type
             6    14    14   Class java/lang/Exception
}
{noformat}
The problem is that the operand stack can be empty when the putfield instruction is reached. The problem seems might be rooted in the following bytecode sequence.
{noformat}
         1: iload_1       
         2: aload_0       
         3: invokespecial #14                 // Method java/lang/Object."<init>":()V
{noformat}
In contrast the reconstructed source code (the source is not available)
{noformat}
 public class S {
   int $;
 
   public S(int i) {
     $ = i;
   }
}
{noformat}
compiled with javac 1.7.0_51 (javac -source 1.3 -target 1.1) produces
{noformat}
Classfile S.class
  Last modified May 5, 2014; size 222 bytes
  MD5 checksum 9700e6458db9d62e640018421a079761
  Compiled from "S.java"
public class S
  SourceFile: "S.java"
  minor version: 3
  major version: 45
  flags: ACC_SUPER
Constant pool:
...
{
  int $;
    flags: 

  public S(int);
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=2, args_size=2
         0: aload_0       
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: aload_0       
         5: iload_1       
         6: putfield      #2                  // Field $:I
         9: return        
}
{noformat}
This version can be instrumented as shown above without problems. Here the respective bytecode sequence is as follows.
{noformat}
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: aload_0       
         5: iload_1       
{noformat}
This sequence seems easier to instrument (after the super call) with code containing a try/catch block.




> Using insertBeforeBody to insert try/catch block in constructor can generate invalid bytecode for certain Java compilers
> ------------------------------------------------------------------------------------------------------------------------
>
>                 Key: JASSIST-222
>                 URL: https://issues.jboss.org/browse/JASSIST-222
>             Project: Javassist
>          Issue Type: Bug
>    Affects Versions: 3.18.0-GA
>         Environment: Unknown java compiler generating class files with major version 45, minor version 3.
> Mint Linux, JDK 1.7.
>            Reporter: Manuel Geffken
>            Assignee: Shigeru Chiba
>            Priority: Minor
>
> The class S in the "boyer" benchmark of the Ashes Suite Collection (http://www.sable.mcgill.ca/ashes/) (no original source code available) has the following bytecode according to javap:
> {noformat}
> Classfile ashesSuiteCollection/suites/ashesHardTestSuite/benchmarks/boyer/classes/S.class
>   Last modified May 2, 2014; size 154 bytes
>   MD5 checksum e79a1481a39d40004bc504cc4f7c2641
> class S
>   minor version: 3
>   major version: 45
>   flags: ACC_SUPER
> Constant pool:
> ...
> {
>   protected int $;
>     flags: ACC_PROTECTED
>   protected S(int);
>     flags: ACC_PROTECTED
>     Code:
>       stack=3, locals=2, args_size=2
>          0: aload_0       
>          1: iload_1       
>          2: aload_0       
>          3: invokespecial #14                 // Method java/lang/Object."<init>":()V
>          6: putfield      #13                 // Field $:I
>          9: return        
> }
> {noformat}
> An instrumentation of this class' constructor with a try/catch block as in
> {noformat}
>             CtConstructor[] ctors = target.getDeclaredConstructors();
>             for (CtConstructor ctor : ctors) {
>             	StringBuilder headerSB = new StringBuilder();
>             	headerSB.append("{");
>                 headerSB.append("  try {");
>                 headerSB.append("    throw new java.lang.Exception();");
>                 headerSB.append("  } catch(java.lang.Exception e) {}");
>                 headerSB.append("}");
>                 ctor.insertBeforeBody(headerSB.toString());
>             } 
> {noformat}
> causes an invalid class file that does not verify:
> {noformat}
> Classfile /home/geffken/git/X2Traverse/benchmarks/basic/ashesSuiteCollection/suites/ashesHardTestSuite/benchmarks/boyer/out_classes/S.class
>   Last modified May 5, 2014; size 356 bytes
>   MD5 checksum d4ef556ee82cee6c4c2960b43d7e71e1
> class S
>   minor version: 3
>   major version: 45
>   flags: ACC_SUPER
> Constant pool:
> ...
> {
>   protected int $;
>     flags: ACC_PROTECTED
>   protected S(int);
>     flags: ACC_PROTECTED
>     Code:
>       stack=5, locals=3, args_size=2
>          0: aload_0       
>          1: iload_1       
>          2: aload_0       
>          3: invokespecial #14                 // Method java/lang/Object."<init>":()V
>          6: new           #22                 // class java/lang/Exception
>          9: dup           
>         10: invokespecial #23                 // Method java/lang/Exception."<init>":()V
>         13: athrow        
>         14: astore_2      
>         15: goto          18
>         18: putfield      #13                 // Field $:I
>         21: return        
>       Exception table:
>          from    to  target type
>              6    14    14   Class java/lang/Exception
> }
> {noformat}
> The problem is that the operand stack can be empty when the putfield instruction is reached. The problem seems to be rooted in the following bytecode sequence.
> {noformat}
>          1: iload_1       
>          2: aload_0       
>          3: invokespecial #14                 // Method java/lang/Object."<init>":()V
> {noformat}
> In contrast the reconstructed source code (the source is not available)
> {noformat}
>  public class S {
>    int $;
>  
>    public S(int i) {
>      $ = i;
>    }
> }
> {noformat}
> compiled with javac 1.7.0_51 (javac -source 1.3 -target 1.1) produces
> {noformat}
> Classfile S.class
>   Last modified May 5, 2014; size 222 bytes
>   MD5 checksum 9700e6458db9d62e640018421a079761
>   Compiled from "S.java"
> public class S
>   SourceFile: "S.java"
>   minor version: 3
>   major version: 45
>   flags: ACC_SUPER
> Constant pool:
> ...
> {
>   int $;
>     flags: 
>   public S(int);
>     flags: ACC_PUBLIC
>     Code:
>       stack=2, locals=2, args_size=2
>          0: aload_0       
>          1: invokespecial #1                  // Method java/lang/Object."<init>":()V
>          4: aload_0       
>          5: iload_1       
>          6: putfield      #2                  // Field $:I
>          9: return        
> }
> {noformat}
> This version can be instrumented as shown above without problems. Here the respective bytecode sequence is as follows.
> {noformat}
>          1: invokespecial #1                  // Method java/lang/Object."<init>":()V
>          4: aload_0       
>          5: iload_1       
> {noformat}
> This sequence seems easier to instrument (after the super call) with code containing a try/catch block.



--
This message was sent by Atlassian JIRA
(v6.2.3#6260)


More information about the jboss-jira mailing list