[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
Tue May 6 05:11:56 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 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 fact that the original bytecode sequence assumes that the two entries of the operand stack reach the {{putfield}} instruction. However, this assumption is invalid in the instrumented code.

In contrast the reconstructed source code (the original source is not available)
{noformat}
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 operand of the putfield instruction are pushed on the stack _after_ the {{putfield}} instruction.
{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 to be rooted in the fact that the original bytecode sequence assumes that the two entries of the operand stack reach the {{putfield}} instruction. However, this assumption is invalid in the instrumented code.

In contrast the reconstructed source code (the original source is not available)
{noformat}
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 operand of the putfield instruction are pushed on the stack _after_ the {{putfield}} instruction.
{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
>         Attachments: S.class, S.java, S_instr_error.class, S_instr_success.class
>
>
> 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 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 fact that the original bytecode sequence assumes that the two entries of the operand stack reach the {{putfield}} instruction. However, this assumption is invalid in the instrumented code.
> In contrast the reconstructed source code (the original source is not available)
> {noformat}
> 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 operand of the putfield instruction are pushed on the stack _after_ the {{putfield}} instruction.
> {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