6 Replies Latest reply on Oct 21, 2005 7:07 PM by gpothier

    Local variables access expressions

    gpothier

      Hi,
      I'm currently implementing an expression that reifies access to local variables, very similar to the expression that reifies accesses to fields.

      In the current version I can properly reify and replace write accesses; read accesses are also properly reified, but there are some problems with $-style arguments for replacing.
      Handling of the WIDE bytecode prefix is prepared, but I need more support from the CodeIterator to indicate that the current bytecode has been prefixed by a WIDE or not.

      I'm attaching the patch to the latest CVS HEAD, as well as a simple test case.

        • 1. Re: Local variables access expressions
          gpothier

          I didn't find how to attach a file so here is the patch:

          Index: src/main/javassist/bytecode/LocalVariableAttribute.java
          ===================================================================
          RCS file: /cvsroot/jboss/javassist/src/main/javassist/bytecode/LocalVariableAttribute.java,v
          retrieving revision 1.7
          diff -u -r1.7 LocalVariableAttribute.java
          --- src/main/javassist/bytecode/LocalVariableAttribute.java 2 Feb 2005 16:14:34 -0000 1.7
          +++ src/main/javassist/bytecode/LocalVariableAttribute.java 20 Oct 2005 15:33:52 -0000
          @@ -141,6 +141,26 @@
          }

          /**
          + * Returns the index of the variable info in the table that describes
          + * the variable that is stored at the specified index in the frame's
          + * local variables array, for the specified program counter position
          + * @param slotIndex Position in the local variables array of the stack frame
          + * where the variable's value is stored.
          + * @param pc Program counter position
          + * @return Index of the variable information in this table, or -1 if not found.
          + */
          + public int varIndex(int slotIndex, int pc) {
          + for (int i=0; i<tableLength(); i++)
          + {
          + int start = startPc(i);
          + int len = codeLength(i);
          + int index = index(i);
          + if (slotIndex == index && start <= pc && pc < start + len) return i;
          + }
          + return -1;
          + }
          +
          + /**
          * Returns the value of local_variable_table.name_index.
          * This represents the name of the local variable.
          *
          Index: src/main/javassist/expr/Expr.java
          ===================================================================
          RCS file: /cvsroot/jboss/javassist/src/main/javassist/expr/Expr.java,v
          retrieving revision 1.12
          diff -u -r1.12 Expr.java
          --- src/main/javassist/expr/Expr.java 18 Jan 2005 07:08:43 -0000 1.12
          +++ src/main/javassist/expr/Expr.java 20 Oct 2005 15:33:53 -0000
          @@ -219,7 +219,7 @@
          return hasIt;
          }

          - /*
          + /**
          * If isStaticCall is true, null is assigned to $0. So $0 must be declared
          * by calling Javac.recordParams().
          *
          @@ -235,6 +235,17 @@
          bytecode.addAstore(regno);
          }

          + /**
          + * This version of storeStack should be used when editing expressions
          + * where "staticness" is meaningless, eg. accessing local variables.
          + *
          + * After executing this method, the current stack depth might be less than
          + * 0.
          + */
          + static final void storeStackLocal(CtClass[] params, int regno, Bytecode bytecode) {
          + storeStack0(0, params.length, params, regno, bytecode);
          + }
          +
          private static void storeStack0(int i, int n, CtClass[] params, int regno,
          Bytecode bytecode) {
          if (i >= n)
          Index: src/main/javassist/expr/ExprEditor.java
          ===================================================================
          RCS file: /cvsroot/jboss/javassist/src/main/javassist/expr/ExprEditor.java,v
          retrieving revision 1.7
          diff -u -r1.7 ExprEditor.java
          --- src/main/javassist/expr/ExprEditor.java 15 Jul 2005 10:08:22 -0000 1.7
          +++ src/main/javassist/expr/ExprEditor.java 20 Oct 2005 15:33:53 -0000
          @@ -108,7 +108,13 @@
          int pos = iterator.next();
          int c = iterator.byteAt(pos);

          - if (c < Opcode.GETSTATIC) // c < 178
          + if (LocalVariableAccess.isStore(c) || LocalVariableAccess.isLoad(c))
          + {
          + // TODO: implement support for WIDE opecodes.
          + expr = new LocalVariableAccess(pos, iterator, clazz, minfo, c, false);
          + edit((LocalVariableAccess)expr);
          + }
          + else if (c < Opcode.GETSTATIC) // c < 178
          /* skip */;
          else if (c < Opcode.NEWARRAY) { // c < 188
          if (c == Opcode.INVOKESTATIC
          @@ -241,6 +247,13 @@
          public void edit(FieldAccess f) throws CannotCompileException {}

          /**
          + * Edits a local variable access expression (overridable).
          + * Local variable access means both read and write.
          + * The default implementation performs nothing.
          + */
          + public void edit(LocalVariableAccess f) throws CannotCompileException {}
          +
          + /**
          * Edits an instanceof expression (overridable).
          * The default implementation performs nothing.
          */
          Index: src/main/javassist/expr/LocalVariableAccess.java
          ===================================================================
          RCS file: src/main/javassist/expr/LocalVariableAccess.java
          diff -N src/main/javassist/expr/LocalVariableAccess.java
          --- /dev/null 1 Jan 1970 00:00:00 -0000
          +++ src/main/javassist/expr/LocalVariableAccess.java 1 Jan 1970 00:00:00 -0000
          @@ -0,0 +1,368 @@
          +/*
          + * Javassist, a Java-bytecode translator toolkit.
          + * Copyright (C) 1999-2005 Shigeru Chiba. All Rights Reserved.
          + *
          + * The contents of this file are subject to the Mozilla Public License Version
          + * 1.1 (the "License"); you may not use this file except in compliance with
          + * the License. Alternatively, the contents of this file may be used under
          + * the terms of the GNU Lesser General Public License Version 2.1 or later.
          + *
          + * Software distributed under the License is distributed on an "AS IS" basis,
          + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
          + * for the specific language governing rights and limitations under the
          + * License.
          + */
          +
          +package javassist.expr;
          +
          +import javassist.*;
          +import javassist.bytecode.*;
          +import javassist.compiler.*;
          +import javassist.compiler.ast.ASTList;
          +import javassist.expr.Expr;
          +
          +/**
          + * Expression that represents access to a local variable
          + * (including behavior parameters).
          + * TODO: Move to Javassist (and rename) when ready.
          + */
          +public class LocalVariableAccess extends Expr {
          + int opcode;
          +
          + /**
          + * If this flag is true, our opcode was preceded by a WIDE instruction.
          + */
          + boolean wide;
          +
          + /**
          + * Indicates if the given opcode is of the xLOAD family
          + */
          + public static boolean isLoad (int op)
          + {
          + return op >= Opcode.ILOAD && op <= Opcode.ALOAD_3;
          + }
          +
          + /**
          + * Indicates if the given opcode is of the xSTORE family
          + */
          + public static boolean isStore (int op)
          + {
          + return op >= Opcode.ISTORE && op <= Opcode.ASTORE_3;
          + }
          +
          + protected LocalVariableAccess(int pos, CodeIterator i, CtClass declaring,
          + MethodInfo m, int op, boolean wide) {
          + super(pos, i, declaring, m);
          + opcode = op;
          + this.wide = wide;
          + }
          +
          + /**
          + * Returns true if the field is read.
          + */
          + public boolean isReader() {
          + return isLoad(opcode);
          + }
          +
          + /**
          + * Returns true if the field is written in.
          + */
          + public boolean isWriter() {
          + return isStore(opcode);
          + }
          +
          + /**
          + * Returns the index of the access in the local variables array.
          + */
          + private int getIndex()
          + {
          + switch (opcode)
          + {
          + case Opcode.ALOAD_0:
          + case Opcode.ASTORE_0:
          + case Opcode.ILOAD_0:
          + case Opcode.ISTORE_0:
          + case Opcode.FLOAD_0:
          + case Opcode.FSTORE_0:
          + case Opcode.LLOAD_0:
          + case Opcode.LSTORE_0:
          + case Opcode.DLOAD_0:
          + case Opcode.DSTORE_0: return 0;
          +
          + case Opcode.ALOAD_1:
          + case Opcode.ASTORE_1:
          + case Opcode.ILOAD_1:
          + case Opcode.ISTORE_1:
          + case Opcode.FLOAD_1:
          + case Opcode.FSTORE_1:
          + case Opcode.LLOAD_1:
          + case Opcode.LSTORE_1:
          + case Opcode.DLOAD_1:
          + case Opcode.DSTORE_1: return 1;
          +
          + case Opcode.ALOAD_2:
          + case Opcode.ASTORE_2:
          + case Opcode.ILOAD_2:
          + case Opcode.ISTORE_2:
          + case Opcode.FLOAD_2:
          + case Opcode.FSTORE_2:
          + case Opcode.LLOAD_2:
          + case Opcode.LSTORE_2:
          + case Opcode.DLOAD_2:
          + case Opcode.DSTORE_2: return 2;
          +
          + case Opcode.ALOAD_3:
          + case Opcode.ASTORE_3:
          + case Opcode.ILOAD_3:
          + case Opcode.ISTORE_3:
          + case Opcode.FLOAD_3:
          + case Opcode.FSTORE_3:
          + case Opcode.LLOAD_3:
          + case Opcode.LSTORE_3:
          + case Opcode.DLOAD_3:
          + case Opcode.DSTORE_3: return 3;
          +
          + case Opcode.ALOAD:
          + case Opcode.ASTORE:
          + case Opcode.ILOAD:
          + case Opcode.ISTORE:
          + case Opcode.FLOAD:
          + case Opcode.FSTORE:
          + case Opcode.LLOAD:
          + case Opcode.LSTORE:
          + case Opcode.DLOAD:
          + case Opcode.DSTORE:
          + if (wide) return iterator.u16bitAt(currentPos + 1);
          + else return iterator.byteAt(currentPos + 1);
          +
          + default:
          + throw new RuntimeException("Internal error: bad opcode: "+opcode);
          + }
          + }
          +
          + /**
          + * returns the number of bytes used to store the index.
          + */
          + private int getIndexSize()
          + {
          + if ((opcode >= Opcode.ILOAD_0 && opcode <= Opcode.ALOAD_3)
          + ||
          + (opcode >= Opcode.ISTORE_0 && opcode <= Opcode.ASTORE_3)) return 0;
          +
          + else return wide ? 2 : 1;
          +
          + }
          +
          + /**
          + * Returns the name of the accessed local variable.
          + * The name is accurate only if debug info was included in the class file.
          + */
          + public String getVariableName()
          + {
          + CodeAttribute ca = iterator.get();
          + int index = getIndex();
          + LocalVariableAttribute va = (LocalVariableAttribute)ca.getAttribute(LocalVariableAttribute.tag);
          + if (va != null)
          + {
          + int tableIndex = va.varIndex(index, currentPos+1+getIndexSize());
          + if (tableIndex >= 0) return va.variableName(tableIndex);
          + }
          + return "$"+index;
          + }
          +
          + /**
          + * Returns the name of the local variable's type, or null if not found
          + */
          + public String getVariableTypeName()
          + {
          + CodeAttribute ca = iterator.get();
          + LocalVariableAttribute va = (LocalVariableAttribute)ca.getAttribute(LocalVariableAttribute.tag);
          + if (va != null)
          + {
          + int index = getIndex();
          + int tableIndex = va.varIndex(index, currentPos+1+getIndexSize());
          + if (tableIndex >= 0) return va.descriptor(tableIndex);
          + }
          + return null;
          + }
          +
          +
          + /**
          + * Replaces the method call with the bytecode derived from
          + * the given source text.
          + *
          + * $0 is available even if the called method is static.
          + * If the field access is writing, $_ is available but the value
          + * of $_ is ignored.
          + *
          + * @param statement a Java statement.
          + */
          + public void replace(String statement) throws CannotCompileException {
          + int pos = currentPos;
          +
          + Javac jc = new Javac(thisClass);
          + CodeAttribute ca = iterator.get();
          + try {
          + CtClass[] params;
          + CtClass retType;
          + String varTypeName = getVariableTypeName();
          + if (varTypeName == null) return; //This happens with unused variables at the end of methods.
          + CtClass varType = Descriptor.toCtClass(varTypeName, thisClass.getClassPool());
          + int index = getIndex();
          +
          + boolean read = isReader();
          + if (read) {
          + params = new CtClass[0];
          + retType = varType;
          + }
          + else {
          + params = new CtClass[1];
          + params[0] = varType;
          + retType = CtClass.voidType;
          + }
          +
          + int paramVar = ca.getMaxLocals();
          + jc.recordParams(null, params, false, paramVar, withinStatic());
          +
          + /* Is $_ included in the source code?
          + */
          + boolean included = checkResultValue(retType, statement);
          + if (read)
          + included = true;
          +
          + int retVar = jc.recordReturnType(retType, included);
          + if (read)
          + jc.recordProceed(new ProceedForRead(retType, index, paramVar));
          + else {
          + // because $type is not the return type...
          + jc.recordType(varType);
          + jc.recordProceed(new ProceedForWrite(params[0], index, paramVar));
          + }
          +
          + Bytecode bytecode = jc.getBytecode();
          + storeStackLocal(params, paramVar, bytecode);
          + jc.recordLocalVariables(ca, pos);
          +
          + if (included)
          + if (retType == CtClass.voidType) {
          + bytecode.addOpcode(ACONST_NULL);
          + bytecode.addAstore(retVar);
          + }
          + else {
          + bytecode.addConstZero(retType);
          + bytecode.addStore(retVar, retType); // initialize $_
          + }
          +
          + jc.compileStmnt(statement);
          + if (read)
          + bytecode.addLoad(retVar, retType);
          +
          + // Compute the size of replaced bytecode.
          + int width = 1 + getIndexSize();
          + if (wide) width++;
          + replace0(pos, bytecode, width);
          + }
          + catch (CompileError e) { throw new CannotCompileException(e); }
          + catch (NotFoundException e) { throw new CannotCompileException(e); }
          + catch (BadBytecode e) {
          + throw new CannotCompileException("broken method");
          + }
          + }
          +
          + /* <field type> $proceed()
          + */
          + class ProceedForRead implements ProceedHandler {
          + CtClass varType;
          + int targetVar;
          + int index;
          +
          + ProceedForRead(CtClass type, int i, int var) {
          + varType = type;
          + targetVar = var;
          + index = i;
          + }
          +
          + public void doit(JvstCodeGen gen, Bytecode bytecode, ASTList args)
          + throws CompileError
          + {
          + if (args != null && !gen.isParamListName(args))
          + throw new CompileError(Javac.proceedName
          + + "() cannot take a parameter for field reading");
          +
          + int stack = 0;
          +
          + if (varType instanceof CtPrimitiveType)
          + stack += ((CtPrimitiveType)varType).getDataSize();
          + else
          + ++stack;
          +
          + if (wide) bytecode.add(Opcode.WIDE);
          + bytecode.add(opcode);
          + int width = getIndexSize();
          + if (width == 1) bytecode.add(index);
          + else if (width == 2) bytecode.addIndex(index);
          + else if (width != 0) throw new RuntimeException("Internal error, width = "+width);
          +
          + bytecode.growStack(stack);
          + gen.setType(varType);
          + }
          +
          + public void setReturnType(JvstTypeChecker c, ASTList args)
          + throws CompileError
          + {
          + c.setType(varType);
          + }
          + }
          +
          + /* void $proceed(<field type>)
          + * the return type is not the field type but void.
          + */
          + class ProceedForWrite implements ProceedHandler {
          + CtClass varType;
          + int targetVar;
          + int index;
          +
          + ProceedForWrite(CtClass type, int i, int var) {
          + varType = type;
          + targetVar = var;
          + index = i;
          + }
          +
          + public void doit(JvstCodeGen gen, Bytecode bytecode, ASTList args)
          + throws CompileError
          + {
          + if (gen.getMethodArgsLength(args) != 1)
          + throw new CompileError(Javac.proceedName
          + + "() cannot take more than one parameter "
          + + "for field writing");
          +
          + int stack = 0;
          +
          + gen.atMethodArgs(args, new int[1], new int[1], new String[1]);
          + gen.doNumCast(varType);
          + if (varType instanceof CtPrimitiveType)
          + stack -= ((CtPrimitiveType)varType).getDataSize();
          + else
          + --stack;
          +
          + if (wide) bytecode.add(Opcode.WIDE);
          + bytecode.add(opcode);
          + int width = getIndexSize();
          + if (width == 1) bytecode.add(index);
          + else if (width == 2) bytecode.addIndex(index);
          + else if (width != 0) throw new RuntimeException("Internal error, width = "+width);
          +
          + bytecode.growStack(stack);
          + gen.setType(CtClass.voidType);
          + gen.addNullIfVoid();
          + }
          +
          + public void setReturnType(JvstTypeChecker c, ASTList args)
          + throws CompileError
          + {
          + c.atMethodArgs(args, new int[1], new int[1], new String[1]);
          + c.setType(CtClass.voidType);
          + c.addNullIfVoid();
          + }
          + }
          +}

          • 2. Re: Local variables access expressions
            gpothier

            Class to instrument for the test case
            TestClass.java

            /*
            * Created on Oct 20, 2005
            */
            package reflex.test.operation.localvaraccess;

            public class TestClass
            {
            public void foo()
            {
            int i = 0;
            byte b = 1;
            long l = 123456789456l;
            char c = 'a';
            float f = 456.4f;
            double d = 5.2;
            String s = "ooo";

            for (int j = 0; j < 10; j++)
            {
            i++;
            b += 3;
            l -= 10;
            c += 1;
            f *= 1.1f;
            d /= 4.2;
            s += ""+j;
            }

            if (i > 0)
            {
            int i1 = 10;
            long lx = i1 + i;
            }
            else
            {
            int i1 = 12;
            int ly = i1 + i;
            }

            System.out.println("foo completed.");
            }

            public void foo2()
            {
            int i = 0;

            System.out.println("foo2: "+i);
            }
            }

            • 3. Re: Local variables access expressions
              gpothier

              Actual test case
              Edit.java

              /*
              * Created on Oct 20, 2005
              */
              package reflex.test.operation.localvaraccess;

              import java.io.DataOutputStream;
              import java.io.FileOutputStream;
              import java.lang.reflect.Method;

              import javassist.CannotCompileException;
              import javassist.ClassPool;
              import javassist.CtClass;
              import javassist.CtMethod;
              import javassist.expr.ExprEditor;
              import javassist.expr.LocalVariableAccess;
              import junit.framework.TestCase;

              /**
              * Test basic Javassist infrastructure of local variable access.
              * @author gpothier
              */
              public class Edit extends TestCase
              {
              public void testEdit()
              {
              try
              {
              CtClass theCtClass = ClassPool.getDefault().get("reflex.test.operation.localvaraccess.TestClass");
              CtMethod theCtMethod = theCtClass.getDeclaredMethod("foo");
              theCtMethod.instrument(new ExprEditor()
              {
              @Override
              public void edit(LocalVariableAccess aF) throws CannotCompileException
              {
              if (aF.isWriter())
              {
              String theName = aF.getVariableName();
              System.out.println("Editing (w) for "+theName);
              aF.replace(String.format("{ " +
              "java.lang.System.out.println(\"%s: \"+$1); " +
              "$proceed($$);}",
              theName));
              }
              else
              {
              String theName = aF.getVariableName();
              System.out.println("Editing (r) for "+theName);
              aF.replace(String.format("{ " +
              "java.lang.System.out.println(\"%s:: \"); " +
              "$_ = $proceed($$);}",
              // "$_ = 0;",
              // ("d".equals(theName) ? "$_ = $proceed($$);" : "$_ = 0.0;") + "}",
              theName));

              }
              }
              });

              // FileOutputStream theStream = new FileOutputStream("/home/gpothier/tmp/edit/C.class");
              // theCtClass.toBytecode(new DataOutputStream(theStream));

              Class theClass = theCtClass.toClass();
              Method theMethod = theClass.getDeclaredMethod("foo");

              Object theInstance = theClass.newInstance();
              theMethod.invoke(theInstance);
              }
              catch (Exception e)
              {
              e.printStackTrace();
              fail(e.getMessage());
              }
              }
              }

              • 4. Re: Local variables access expressions
                gpothier

                I have been doing more work with local variables access, and there is still more to do, so please tell me if you are interested, so that I can provide new code.
                I plan to do the following:
                - support for $0 parameter in LocalVariableAccess.replace
                - Reification of local variables (CtLocalVariable)

                Regards,
                Guillaume Pothier

                • 5. Re: Local variables access expressions

                  Guillaume,

                  probably no one now have sufficient time to examine your job..

                  I invite you to wait..
                  Pao

                  • 6. Re: Local variables access expressions
                    gpothier

                    Ok, thanks for replying!
                    Anyway there's still quite some work to do, maybe I sent the patch too soon.
                    g