-
1. Re: Local variables access expressions
gpothier Oct 20, 2005 11:36 AM (in response to 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 Oct 20, 2005 11:37 AM (in response to 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 Oct 20, 2005 11:38 AM (in response to 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 Oct 21, 2005 10:57 AM (in response to 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
anonym124 Oct 21, 2005 11:57 AM (in response to gpothier)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 Oct 21, 2005 7:07 PM (in response to gpothier)Ok, thanks for replying!
Anyway there's still quite some work to do, maybe I sent the patch too soon.
g