Browse Source

fixed JASSIST-160 by rewriting the whole javassist.bytecode.stackmap package.

git-svn-id: http://anonsvn.jboss.org/repos/javassist/trunk@655 30ef5769-5b8d-40dd-aea6-55b5d6557bb3
tags/rel_3_17_1_ga
chiba 11 years ago
parent
commit
b667870a98

+ 3
- 2
.classpath View File

@@ -1,8 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry excluding="javassist/util/HotSwapper.java" kind="src" path="src/main"/>
<classpathentry kind="src" path="src/test"/>
<classpathentry excluding=".svn" kind="src" path="src/main"/>
<classpathentry excluding=".svn" kind="src" path="src/test"/>
<classpathentry kind="lib" path="lib/junit.jar"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry kind="lib" path="/System/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Classes/classes.jar"/>
<classpathentry kind="output" path="eclipse-output/classes"/>
</classpath>

BIN
javassist.jar View File


+ 8
- 0
src/main/javassist/CodeConverter.java View File

@@ -532,6 +532,14 @@ public class CodeConverter {

if (stack > 0)
codeAttr.setMaxStack(codeAttr.getMaxStack() + stack);

try {
minfo.rebuildStackMapIf6(clazz.getClassPool(),
clazz.getClassFile2());
}
catch (BadBytecode b) {
throw new CannotCompileException(b.getMessage(), b);
}
}

/**

+ 7
- 2
src/main/javassist/CtNewMethod.java View File

@@ -246,7 +246,9 @@ public class CtNewMethod {
}

minfo.setCodeAttribute(code.toCodeAttribute());
return new CtMethod(minfo, field.getDeclaringClass());
CtClass cc = field.getDeclaringClass();
// a stack map is not needed.
return new CtMethod(minfo, cc);
}

/**
@@ -290,7 +292,9 @@ public class CtNewMethod {
}

minfo.setCodeAttribute(code.toCodeAttribute());
return new CtMethod(minfo, field.getDeclaringClass());
CtClass cc = field.getDeclaringClass();
// a stack map is not needed.
return new CtMethod(minfo, cc);
}

/**
@@ -359,6 +363,7 @@ public class CtNewMethod {
code.setMaxLocals(++s);
code.setMaxStack(s < 2 ? 2 : s); // for a 2-word return value
minfo.setCodeAttribute(code.toCodeAttribute());
// a stack map is not needed.
return new CtMethod(minfo, declaring);
}


+ 1
- 0
src/main/javassist/CtNewWrappedConstructor.java View File

@@ -39,6 +39,7 @@ class CtNewWrappedConstructor extends CtNewWrappedMethod {
howToCallSuper, body,
parameterTypes, constParam);
cons.getMethodInfo2().setCodeAttribute(code.toCodeAttribute());
// a stack map table is not needed.
return cons;
}
catch (NotFoundException e) {

+ 3
- 1
src/main/javassist/CtNewWrappedMethod.java View File

@@ -44,7 +44,9 @@ class CtNewWrappedMethod {

Bytecode code = makeBody(declaring, declaring.getClassFile2(), body,
parameterTypes, returnType, constParam);
mt.getMethodInfo2().setCodeAttribute(code.toCodeAttribute());
MethodInfo minfo = mt.getMethodInfo2();
minfo.setCodeAttribute(code.toCodeAttribute());
// a stack map has been already created.
return mt;
}


+ 1
- 1
src/main/javassist/bytecode/ConstPool.java View File

@@ -174,7 +174,7 @@ public final class ConstPool {
* @return a fully-qualified class or interface name specified
* by <code>name_index</code>. If the type is an array
* type, this method returns an encoded name like
* <code>[java.lang.Object;</code> (note that the separators
* <code>[Ljava.lang.Object;</code> (note that the separators
* are not slashes but dots).
* @see javassist.ClassPool#getCtClass(String)
*/

+ 6
- 4
src/main/javassist/bytecode/StackMapTable.java View File

@@ -67,7 +67,7 @@ public class StackMapTable extends AttributeInfo {
{
try {
return new StackMapTable(newCp,
new Copier(this.constPool, info, newCp).doit());
new Copier(this.constPool, info, newCp, classnames).doit());
}
catch (BadBytecode e) {
throw new RuntimeCopyException("bad bytecode. fatal?");
@@ -400,16 +400,18 @@ public class StackMapTable extends AttributeInfo {

static class Copier extends SimpleCopy {
private ConstPool srcPool, destPool;
private Map classnames;

public Copier(ConstPool src, byte[] data, ConstPool dest) {
public Copier(ConstPool src, byte[] data, ConstPool dest, Map names) {
super(data);
srcPool = src;
destPool = dest;
classnames = names;
}

protected int copyData(int tag, int data) {
if (tag == OBJECT)
return srcPool.copy(data, destPool, null);
return srcPool.copy(data, destPool, classnames);
else
return data;
}
@@ -418,7 +420,7 @@ public class StackMapTable extends AttributeInfo {
int[] newData = new int[data.length];
for (int i = 0; i < data.length; i++)
if (tags[i] == OBJECT)
newData[i] = srcPool.copy(data[i], destPool, null);
newData[i] = srcPool.copy(data[i], destPool, classnames);
else
newData[i] = data[i];


+ 1
- 1
src/main/javassist/bytecode/stackmap/BasicBlock.java View File

@@ -274,7 +274,7 @@ public class BasicBlock {
else if (op == Opcode.JSR_W)
makeJsr(marks, index, index + ci.s32bitAt(index + 1), 5);
else if (op == Opcode.WIDE && ci.byteAt(index + 1) == Opcode.RET)
makeMark(marks, index, null, 1, true);
makeMark(marks, index, null, 4, true);
}

if (et != null) {

+ 0
- 366
src/main/javassist/bytecode/stackmap/Liveness.java View File

@@ -1,366 +0,0 @@
/*
* Javassist, a Java-bytecode translator toolkit.
* Copyright (C) 1999- 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,
* or the Apache License Version 2.0.
*
* 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.bytecode.stackmap;

import javassist.bytecode.*;

public class Liveness {
protected static final byte UNKNOWN = 0;
protected static final byte READ = 1;
protected static final byte UPDATED = 2;
protected byte[] localsUsage;

/**
* If true, all the arguments become alive within the whole method body.
*
* To correctly compute a stack map table, all the arguments must
* be alive (localsUsage[?] must be READ) at least in the first block.
*/
public static boolean useArgs = true;

public void compute(CodeIterator ci, TypedBlock[] blocks, int maxLocals,
TypeData[] args)
throws BadBytecode
{
computeUsage(ci, blocks, maxLocals);
if (useArgs)
useAllArgs(blocks, args);

computeLiveness1(blocks[0]);
while (hasChanged(blocks))
computeLiveness2(blocks[0]);
}

private void useAllArgs(TypedBlock[] blocks, TypeData[] args) {
for (int k = 0; k < blocks.length; k++) {
byte[] usage = blocks[k].localsUsage;
for (int i = 0; i < args.length; i++)
if (args[i] != TypeTag.TOP)
usage[i] = READ;
}
}

static final int NOT_YET = 0;
static final int CHANGED_LAST = 1;
static final int DONE = 2;
static final int CHANGED_NOW = 3;

private void computeLiveness1(TypedBlock tb) {
if (tb.updating) {
// a loop was detected.
computeLiveness1u(tb);
return;
}

if (tb.inputs != null)
return;

tb.updating = true;
byte[] usage = tb.localsUsage;
int n = usage.length;
boolean[] in = new boolean[n];
for (int i = 0; i < n; i++)
in[i] = usage[i] == READ;

BasicBlock.Catch handlers = tb.toCatch;
while (handlers != null) {
TypedBlock h = (TypedBlock)handlers.body;
computeLiveness1(h);
for (int k = 0; k < n; k++)
if (h.inputs[k])
in[k] = true;

handlers = handlers.next;
}

if (tb.exit != null) {
for (int i = 0; i < tb.exit.length; i++) {
TypedBlock e = (TypedBlock)tb.exit[i];
computeLiveness1(e);
for (int k = 0; k < n; k++)
if (!in[k])
in[k] = usage[k] == UNKNOWN && e.inputs[k];
}
}

tb.updating = false;
if (tb.inputs == null) {
tb.inputs = in;
tb.status = DONE;
}
else {
for (int i = 0; i < n; i++)
if (in[i] && !tb.inputs[i]) {
tb.inputs[i] = true;
tb.status = CHANGED_NOW;
}
}
}

private void computeLiveness1u(TypedBlock tb) {
if (tb.inputs == null) {
byte[] usage = tb.localsUsage;
int n = usage.length;
boolean[] in = new boolean[n];
for (int i = 0; i < n; i++)
in[i] = usage[i] == READ;

tb.inputs = in;
tb.status = DONE;
}
}

private void computeLiveness2(TypedBlock tb) {
if (tb.updating || tb.status >= DONE)
return;

tb.updating = true;
if (tb.exit == null)
tb.status = DONE;
else {
boolean changed = false;
for (int i = 0; i < tb.exit.length; i++) {
TypedBlock e = (TypedBlock)tb.exit[i];
computeLiveness2(e);
if (e.status != DONE)
changed = true;
}

if (changed) {
changed = false;
byte[] usage = tb.localsUsage;
int n = usage.length;
for (int i = 0; i < tb.exit.length; i++) {
TypedBlock e = (TypedBlock)tb.exit[i];
if (e.status != DONE)
for (int k = 0; k < n; k++)
if (!tb.inputs[k]) {
if (usage[k] == UNKNOWN && e.inputs[k]) {
tb.inputs[k] = true;
changed = true;
}
}
}

tb.status = changed ? CHANGED_NOW : DONE;
}
else
tb.status = DONE;
}

if (computeLiveness2except(tb))
tb.status = CHANGED_NOW;

tb.updating = false;
}

private boolean computeLiveness2except(TypedBlock tb) {
BasicBlock.Catch handlers = tb.toCatch;
boolean changed = false;
while (handlers != null) {
TypedBlock h = (TypedBlock)handlers.body;
computeLiveness2(h);
if (h.status != DONE) {
boolean[] in = tb.inputs;
int n = in.length;
for (int k = 0; k < n; k++)
if (!in[k] && h.inputs[k]) {
in[k] = true;
changed = true;
}
}

handlers = handlers.next;
}

return changed;
}

private boolean hasChanged(TypedBlock[] blocks) {
int n = blocks.length;
boolean changed = false;
for (int i = 0; i < n; i++) {
TypedBlock tb = blocks[i];
if (tb.status == CHANGED_NOW) {
tb.status = CHANGED_LAST;
changed = true;
}
else
tb.status = NOT_YET;
}

return changed;
}

private void computeUsage(CodeIterator ci, TypedBlock[] blocks, int maxLocals)
throws BadBytecode
{
int n = blocks.length;
for (int i = 0; i < n; i++) {
TypedBlock tb = blocks[i];
localsUsage = tb.localsUsage = new byte[maxLocals];
int pos = tb.position;
analyze(ci, pos, pos + tb.length);
localsUsage = null;
}
}

protected final void readLocal(int reg) {
if (localsUsage[reg] == UNKNOWN)
localsUsage[reg] = READ;
}

protected final void writeLocal(int reg) {
if (localsUsage[reg] == UNKNOWN)
localsUsage[reg] = UPDATED;
}

protected void analyze(CodeIterator ci, int begin, int end)
throws BadBytecode
{
ci.begin();
ci.move(begin);
while (ci.hasNext()) {
int index = ci.next();
if (index >= end)
break;

int op = ci.byteAt(index);
if (op < 96)
if (op < 54)
doOpcode0_53(ci, index, op);
else
doOpcode54_95(ci, index, op);
else
if (op == Opcode.IINC) {
// this does not call writeLocal().
readLocal(ci.byteAt(index + 1));
}
else if (op == Opcode.WIDE)
doWIDE(ci, index);
}
}

private void doOpcode0_53(CodeIterator ci, int pos, int op) {
switch (op) {
case Opcode.ILOAD :
case Opcode.LLOAD :
case Opcode.FLOAD :
case Opcode.DLOAD :
case Opcode.ALOAD :
readLocal(ci.byteAt(pos + 1));
break;
case Opcode.ILOAD_0 :
case Opcode.ILOAD_1 :
case Opcode.ILOAD_2 :
case Opcode.ILOAD_3 :
readLocal(op - Opcode.ILOAD_0);
break;
case Opcode.LLOAD_0 :
case Opcode.LLOAD_1 :
case Opcode.LLOAD_2 :
case Opcode.LLOAD_3 :
readLocal(op - Opcode.LLOAD_0);
break;
case Opcode.FLOAD_0 :
case Opcode.FLOAD_1 :
case Opcode.FLOAD_2 :
case Opcode.FLOAD_3 :
readLocal(op - Opcode.FLOAD_0);
break;
case Opcode.DLOAD_0 :
case Opcode.DLOAD_1 :
case Opcode.DLOAD_2 :
case Opcode.DLOAD_3 :
readLocal(op - Opcode.DLOAD_0);
break;
case Opcode.ALOAD_0 :
case Opcode.ALOAD_1 :
case Opcode.ALOAD_2 :
case Opcode.ALOAD_3 :
readLocal(op - Opcode.ALOAD_0);
break;
}
}

private void doOpcode54_95(CodeIterator ci, int pos, int op) {
switch (op) {
case Opcode.ISTORE :
case Opcode.LSTORE :
case Opcode.FSTORE :
case Opcode.DSTORE :
case Opcode.ASTORE :
writeLocal(ci.byteAt(pos + 1));
break;
case Opcode.ISTORE_0 :
case Opcode.ISTORE_1 :
case Opcode.ISTORE_2 :
case Opcode.ISTORE_3 :
writeLocal(op - Opcode.ISTORE_0);
break;
case Opcode.LSTORE_0 :
case Opcode.LSTORE_1 :
case Opcode.LSTORE_2 :
case Opcode.LSTORE_3 :
writeLocal(op - Opcode.LSTORE_0);
break;
case Opcode.FSTORE_0 :
case Opcode.FSTORE_1 :
case Opcode.FSTORE_2 :
case Opcode.FSTORE_3 :
writeLocal(op - Opcode.FSTORE_0);
break;
case Opcode.DSTORE_0 :
case Opcode.DSTORE_1 :
case Opcode.DSTORE_2 :
case Opcode.DSTORE_3 :
writeLocal(op - Opcode.DSTORE_0);
break;
case Opcode.ASTORE_0 :
case Opcode.ASTORE_1 :
case Opcode.ASTORE_2 :
case Opcode.ASTORE_3 :
writeLocal(op - Opcode.ASTORE_0);
break;
}
}

private void doWIDE(CodeIterator ci, int pos) throws BadBytecode {
int op = ci.byteAt(pos + 1);
int var = ci.u16bitAt(pos + 2);
switch (op) {
case Opcode.ILOAD :
case Opcode.LLOAD :
case Opcode.FLOAD :
case Opcode.DLOAD :
case Opcode.ALOAD :
readLocal(var);
break;
case Opcode.ISTORE :
case Opcode.LSTORE :
case Opcode.FSTORE :
case Opcode.DSTORE :
case Opcode.ASTORE :
writeLocal(var);
break;
case Opcode.IINC :
readLocal(var);
// this does not call writeLocal().
break;
}
}
}

+ 98
- 129
src/main/javassist/bytecode/stackmap/MapMaker.java View File

@@ -16,7 +16,9 @@

package javassist.bytecode.stackmap;

import java.util.ArrayList;
import javassist.ClassPool;
import javassist.NotFoundException;
import javassist.bytecode.*;

/**
@@ -125,9 +127,7 @@ public class MapMaker extends Tracer {
TypedBlock.getRetType(minfo.getDescriptor()));
}

protected MapMaker(MapMaker old, boolean copyStack) {
super(old, copyStack);
}
protected MapMaker(MapMaker old) { super(old); }

/**
* Runs an analyzer (Phase 1 and 2).
@@ -135,34 +135,11 @@ public class MapMaker extends Tracer {
void make(TypedBlock[] blocks, byte[] code)
throws BadBytecode
{
TypedBlock first = blocks[0];
fixParamTypes(first);
TypeData[] srcTypes = first.localsTypes;
copyFrom(srcTypes.length, srcTypes, this.localsTypes);
make(code, first);

int n = blocks.length;
for (int i = 0; i < n; i++)
evalExpected(blocks[i]);
}

/*
* If a parameter type is String but it is used only as Object
* within the method body, this MapMaker class will report its type
* is Object. To avoid this, fixParamTypes calls TypeData.setType()
* on each parameter type.
*/
private void fixParamTypes(TypedBlock first) throws BadBytecode {
TypeData[] types = first.localsTypes;
int n = types.length;
for (int i = 0; i < n; i++) {
TypeData t = types[i];
if (t instanceof TypeData.ClassName) {
/* Skip the following statement if t.isNullType() is true
* although a parameter type is never null type.
*/
TypeData.setType(t, t.getName(), classPool);
}
make(code, blocks[0]);
try {
fixTypes(blocks);
} catch (NotFoundException e) {
throw new BadBytecode("failed to resolve types", e);
}
}

@@ -171,17 +148,19 @@ public class MapMaker extends Tracer {
private void make(byte[] code, TypedBlock tb)
throws BadBytecode
{
BasicBlock.Catch handlers = tb.toCatch;
while (handlers != null) {
traceException(code, handlers);
handlers = handlers.next;
}
copyTypeData(tb.stackTop, tb.stackTypes, stackTypes);
stackTop = tb.stackTop;
copyTypeData(tb.localsTypes.length, tb.localsTypes, localsTypes);
traceException(code, tb.toCatch);

int pos = tb.position;
int end = pos + tb.length;
while (pos < end)
pos += doOpcode(pos, code);

traceException(code, tb.toCatch);

if (tb.exit != null) {
for (int i = 0; i < tb.exit.length; i++) {
TypedBlock e = (TypedBlock)tb.exit[i];
@@ -189,7 +168,7 @@ public class MapMaker extends Tracer {
mergeMap(e, true);
else {
recordStackMap(e);
MapMaker maker = new MapMaker(this, true);
MapMaker maker = new MapMaker(this);
maker.make(code, e);
}
}
@@ -199,71 +178,66 @@ public class MapMaker extends Tracer {
private void traceException(byte[] code, TypedBlock.Catch handler)
throws BadBytecode
{
TypedBlock tb = (TypedBlock)handler.body;
if (tb.alreadySet())
mergeMap(tb, false);
else {
recordStackMap(tb, handler.typeIndex);
MapMaker maker = new MapMaker(this, false);

/* the following code is equivalent to maker.copyFrom(this)
* except stackTypes are not copied.
*/
maker.stackTypes[0] = tb.stackTypes[0].getSelf();
maker.stackTop = 1;
maker.make(code, tb);
while (handler != null) {
TypedBlock tb = (TypedBlock)handler.body;
if (tb.alreadySet())
mergeMap(tb, false);
else {
recordStackMap(tb, handler.typeIndex);
MapMaker maker = new MapMaker(this);
maker.make(code, tb);
}

handler = handler.next;
}
}

private void mergeMap(TypedBlock dest, boolean mergeStack) {
boolean[] inputs = dest.inputs;
int n = inputs.length;
private void mergeMap(TypedBlock dest, boolean mergeStack) throws BadBytecode {
int n = localsTypes.length;
for (int i = 0; i < n; i++)
if (inputs[i])
merge(localsTypes[i], dest.localsTypes[i]);
dest.localsTypes[i] = merge(localsTypes[i], dest.localsTypes[i]);

if (mergeStack) {
n = stackTop;
for (int i = 0; i < n; i++)
merge(stackTypes[i], dest.stackTypes[i]);
dest.stackTypes[i] = merge(stackTypes[i], dest.stackTypes[i]);
}
}

private void merge(TypeData td, TypeData target) {
boolean tdIsObj = false;
boolean targetIsObj = false;
// td or target is null if it is TOP.
if (td != TOP && td.isObjectType())
tdIsObj = true;
if (target != TOP && target.isObjectType())
targetIsObj = true;
if (tdIsObj && targetIsObj)
target.merge(td);
private TypeData merge(TypeData src, TypeData target) throws BadBytecode {
if (src == target)
return target;
else if (target instanceof TypeData.ClassName
|| target instanceof TypeData.BasicType) // a parameter
return target;
else if (target instanceof TypeData.AbsTypeVar) {
((TypeData.AbsTypeVar)target).merge(src);
return target;
}
else
throw new RuntimeException("fatal: this should never happen");
}

private void recordStackMap(TypedBlock target)
throws BadBytecode
{
TypeData[] tStackTypes = new TypeData[stackTypes.length];
TypeData[] tStackTypes = TypeData.make(stackTypes.length);
int st = stackTop;
copyFrom(st, stackTypes, tStackTypes);
recordTypeData(st, stackTypes, tStackTypes);
recordStackMap0(target, st, tStackTypes);
}

private void recordStackMap(TypedBlock target, int exceptionType)
throws BadBytecode
{
TypeData[] tStackTypes = TypeData.make(stackTypes.length);
String type;
if (exceptionType == 0)
if (exceptionType == 0) // for finally clauses
type = "java.lang.Throwable";
else
type = cpool.getClassInfo(exceptionType);

TypeData[] tStackTypes = new TypeData[stackTypes.length];
tStackTypes[0] = new TypeData.ClassName(type);

recordStackMap0(target, 1, tStackTypes);
}

@@ -271,34 +245,50 @@ public class MapMaker extends Tracer {
throws BadBytecode
{
int n = localsTypes.length;
TypeData[] tLocalsTypes = new TypeData[n];
int k = copyFrom(n, localsTypes, tLocalsTypes);

boolean[] inputs = target.inputs;
for (int i = 0; i < n; i++)
if (!inputs[i])
tLocalsTypes[i] = TOP;

TypeData[] tLocalsTypes = TypeData.make(n);
int k = recordTypeData(n, localsTypes, tLocalsTypes);
target.setStackMap(st, tStackTypes, k, tLocalsTypes);
}

// Phase 2
protected static int recordTypeData(int n, TypeData[] srcTypes, TypeData[] destTypes) {
int k = -1;
for (int i = 0; i < n; i++) {
TypeData t = srcTypes[i];
destTypes[i] = t.join();
if (t != TOP)
k = i + 1; // t might be long or double.
}

void evalExpected(TypedBlock target) throws BadBytecode {
ClassPool cp = classPool;
evalExpected(cp, target.stackTop, target.stackTypes);
TypeData[] types = target.localsTypes;
if (types != null) // unless this block is dead code
evalExpected(cp, types.length, types);
return k + 1;
}

private static void evalExpected(ClassPool cp, int n, TypeData[] types)
throws BadBytecode
{
for (int i = 0; i < n; i++) {
TypeData td = types[i];
if (td != null)
td.evalExpectedType(cp);
protected static void copyTypeData(int n, TypeData[] srcTypes, TypeData[] destTypes) {
for (int i = 0; i < n; i++)
destTypes[i] = srcTypes[i];
}

// Phase 2

/*
* This method first finds strongly connected components (SCCs)
* on a graph made by TypeData by Tarjan's algorithm.
* SCCs are TypeData nodes sharing the same type.
* Since SCCs are found in the topologically sorted order,
* their types are also fixed when they are found.
*/
private void fixTypes(TypedBlock[] blocks) throws NotFoundException {
ArrayList preOrder = new ArrayList();
int len = blocks.length;
int index = 0;
for (int i = 0; i < len; i++) {
TypedBlock block = blocks[i];
int n = block.localsTypes.length;
for (int j = 0; j < n; j++)
index = block.localsTypes[j].dfs(preOrder, index, classPool);

n = block.stackTop;
for (int j = 0; j < n; j++)
index = block.stackTypes[j].dfs(preOrder, index, classPool);
}
}

@@ -370,19 +360,14 @@ public class MapMaker extends Tracer {
}
else if (stackTop == 1 && diffL == 0) {
TypeData td = bb.stackTypes[0];
if (td == TOP)
writer.sameLocals(offsetDelta, StackMapTable.TOP, 0);
else
writer.sameLocals(offsetDelta, td.getTypeTag(),
td.getTypeData(cpool));
writer.sameLocals(offsetDelta, td.getTypeTag(), td.getTypeData(cpool));
return;
}
else if (stackTop == 2 && diffL == 0) {
TypeData td = bb.stackTypes[0];
if (td != TOP && td.is2WordType()) {
if (td.is2WordType()) {
// bb.stackTypes[1] must be TOP.
writer.sameLocals(offsetDelta, td.getTypeTag(),
td.getTypeData(cpool));
writer.sameLocals(offsetDelta, td.getTypeTag(), td.getTypeData(cpool));
return;
}
}
@@ -401,16 +386,10 @@ public class MapMaker extends Tracer {
int j = 0;
for (int i = 0; i < num; i++) {
TypeData td = types[offset + i];
if (td == TOP) {
tags[j] = StackMapTable.TOP;
data[j] = 0;
}
else {
tags[j] = td.getTypeTag();
data[j] = td.getTypeData(cp);
if (td.is2WordType())
i++;
}
tags[j] = td.getTypeTag();
data[j] = td.getTypeData(cp);
if (td.is2WordType())
i++;

j++;
}
@@ -439,14 +418,8 @@ public class MapMaker extends Tracer {

private static boolean stackMapEq(TypeData[] oldTd, TypeData[] newTd, int len) {
for (int i = 0; i < len; i++) {
TypeData td = oldTd[i];
if (td == TOP) { // the next element to LONG/DOUBLE is TOP.
if (newTd[i] != TOP)
return false;
}
else
if (!oldTd[i].equals(newTd[i]))
return false;
if (!oldTd[i].eq(newTd[i]))
return false;
}

return true;
@@ -457,7 +430,7 @@ public class MapMaker extends Tracer {
while (offset < len) {
TypeData td = types[offset++];
num++;
if (td != TOP && td.is2WordType())
if (td.is2WordType())
offset++;
}

@@ -515,13 +488,9 @@ public class MapMaker extends Tracer {
writer.write16bit(num - numDWord);
for (int i = 0; i < num; i++) {
TypeData td = types[i];
if (td == TOP)
writer.writeVerifyTypeInfo(StackMap.TOP, 0);
else {
writer.writeVerifyTypeInfo(td.getTypeTag(), td.getTypeData(cp));
if (td.is2WordType())
i++;
}
writer.writeVerifyTypeInfo(td.getTypeTag(), td.getTypeData(cp));
if (td.is2WordType())
i++;
}
}
}

+ 40
- 63
src/main/javassist/bytecode/stackmap/Tracer.java View File

@@ -31,7 +31,7 @@ import javassist.ClassPool;
public abstract class Tracer implements TypeTag {
protected ClassPool classPool;
protected ConstPool cpool;
protected String returnType;
protected String returnType; // used as the type of ARETURN

protected int stackTop;
protected TypeData[] stackTypes;
@@ -43,39 +43,17 @@ public abstract class Tracer implements TypeTag {
cpool = cp;
returnType = retType;
stackTop = 0;
stackTypes = new TypeData[maxStack];
localsTypes = new TypeData[maxLocals];
stackTypes = TypeData.make(maxStack);
localsTypes = TypeData.make(maxLocals);
}

public Tracer(Tracer t, boolean copyStack) {
public Tracer(Tracer t) {
classPool = t.classPool;
cpool = t.cpool;
returnType = t.returnType;

stackTop = t.stackTop;
int size = t.stackTypes.length;
stackTypes = new TypeData[size];
if (copyStack)
copyFrom(t.stackTop, t.stackTypes, stackTypes);

int size2 = t.localsTypes.length;
localsTypes = new TypeData[size2];
copyFrom(size2, t.localsTypes, localsTypes);
}

protected static int copyFrom(int n, TypeData[] srcTypes, TypeData[] destTypes) {
int k = -1;
for (int i = 0; i < n; i++) {
TypeData t = srcTypes[i];
destTypes[i] = t == TOP ? TOP : t.getSelf();
if (t != TOP)
if (t.is2WordType())
k = i + 1;
else
k = i;
}

return k + 1;
stackTypes = TypeData.make(t.stackTypes.length);
localsTypes = TypeData.make(t.localsTypes.length);
}

/**
@@ -255,11 +233,7 @@ public abstract class Tracer implements TypeTag {
case Opcode.AALOAD : {
int s = --stackTop - 1;
TypeData data = stackTypes[s];
if (data == null || !data.isObjectType())
throw new BadBytecode("bad AALOAD");
else
stackTypes[s] = new TypeData.ArrayElement(data);

stackTypes[s] = TypeData.ArrayElement.make(data);
break; }
case Opcode.BALOAD :
case Opcode.CALOAD :
@@ -309,14 +283,12 @@ public abstract class Tracer implements TypeTag {
return 2;
}

private int doALOAD(int localVar) { // int localVar, TypeData type) {
private int doALOAD(int localVar) {
stackTypes[stackTop++] = localsTypes[localVar];
return 2;
}

private int doOpcode54_95(int pos, byte[] code, int op) throws BadBytecode {
TypeData[] localsTypes = this.localsTypes;
TypeData[] stackTypes = this.stackTypes;
switch (op) {
case Opcode.ISTORE :
return doXSTORE(pos, code, INTEGER);
@@ -376,9 +348,9 @@ public abstract class Tracer implements TypeTag {
stackTop -= (op == Opcode.LASTORE || op == Opcode.DASTORE) ? 4 : 3;
break;
case Opcode.AASTORE :
TypeData.setType(stackTypes[stackTop - 1],
TypeData.ArrayElement.getElementType(stackTypes[stackTop - 3].getName()),
classPool);
TypeData.ArrayElement.aastore(stackTypes[stackTop - 3],
stackTypes[stackTop - 1],
classPool);
stackTop -= 3;
break;
case Opcode.BASTORE :
@@ -450,7 +422,7 @@ public abstract class Tracer implements TypeTag {
private int doASTORE(int index) {
stackTop--;
// implicit upcast might be done.
localsTypes[index] = stackTypes[stackTop].copy();
localsTypes[index] = stackTypes[stackTop];
return 2;
}

@@ -475,16 +447,16 @@ public abstract class Tracer implements TypeTag {
// this does not call writeLocal().
return 3;
case Opcode.I2L :
stackTypes[stackTop] = LONG;
stackTypes[stackTop - 1] = TOP;
stackTypes[stackTop - 1] = LONG;
stackTypes[stackTop] = TOP;
stackTop++;
break;
case Opcode.I2F :
stackTypes[stackTop - 1] = FLOAT;
break;
case Opcode.I2D :
stackTypes[stackTop] = DOUBLE;
stackTypes[stackTop - 1] = TOP;
stackTypes[stackTop - 1] = DOUBLE;
stackTypes[stackTop] = TOP;
stackTop++;
break;
case Opcode.L2I :
@@ -494,24 +466,26 @@ public abstract class Tracer implements TypeTag {
stackTypes[--stackTop - 1] = FLOAT;
break;
case Opcode.L2D :
stackTypes[stackTop - 1] = DOUBLE;
stackTypes[stackTop - 2] = DOUBLE;
break;
case Opcode.F2I :
stackTypes[stackTop - 1] = INTEGER;
break;
case Opcode.F2L :
stackTypes[stackTop - 1] = TOP;
stackTypes[stackTop++] = LONG;
stackTypes[stackTop - 1] = LONG;
stackTypes[stackTop] = TOP;
stackTop++;
break;
case Opcode.F2D :
stackTypes[stackTop - 1] = TOP;
stackTypes[stackTop++] = DOUBLE;
stackTypes[stackTop - 1] = DOUBLE;
stackTypes[stackTop] = TOP;
stackTop++;
break;
case Opcode.D2I :
stackTypes[--stackTop - 1] = INTEGER;
break;
case Opcode.D2L :
stackTypes[stackTop - 1] = LONG;
stackTypes[stackTop - 2] = LONG;
break;
case Opcode.D2F :
stackTypes[--stackTop - 1] = FLOAT;
@@ -602,7 +576,7 @@ public abstract class Tracer implements TypeTag {
visitReturn(pos, code);
break;
case Opcode.ARETURN :
TypeData.setType(stackTypes[--stackTop], returnType, classPool);
stackTypes[--stackTop].setType(returnType, classPool);
visitReturn(pos, code);
break;
case Opcode.RETURN :
@@ -644,17 +618,21 @@ public abstract class Tracer implements TypeTag {
= new TypeData.ClassName(type);
return 3; }
case Opcode.ARRAYLENGTH :
TypeData.setType(stackTypes[stackTop - 1], "[Ljava.lang.Object;", classPool);
stackTypes[stackTop - 1].setType("[Ljava.lang.Object;", classPool);
stackTypes[stackTop - 1] = INTEGER;
break;
case Opcode.ATHROW :
TypeData.setType(stackTypes[--stackTop], "java.lang.Throwable", classPool);
stackTypes[--stackTop].setType("java.lang.Throwable", classPool);
visitThrow(pos, code);
break;
case Opcode.CHECKCAST : {
// TypeData.setType(stackTypes[stackTop - 1], "java.lang.Object", classPool);
int i = ByteArray.readU16bit(code, pos + 1);
stackTypes[stackTop - 1] = new TypeData.ClassName(cpool.getClassInfo(i));
String type = cpool.getClassInfo(i);
if (type.charAt(0) == '[')
type = type.replace('.', '/'); // getClassInfo() may return "[java.lang.Object;".

stackTypes[stackTop - 1] = new TypeData.ClassName(type);
return 3; }
case Opcode.INSTANCEOF :
// TypeData.setType(stackTypes[stackTop - 1], "java.lang.Object", classPool);
@@ -748,9 +726,9 @@ public abstract class Tracer implements TypeTag {
stackTop -= Descriptor.dataSize(desc);
char c = desc.charAt(0);
if (c == 'L')
TypeData.setType(stackTypes[stackTop], getFieldClassName(desc, 0), classPool);
stackTypes[stackTop].setType(getFieldClassName(desc, 0), classPool);
else if (c == '[')
TypeData.setType(stackTypes[stackTop], desc, classPool);
stackTypes[stackTop].setType(desc, classPool);

setFieldTarget(notStatic, index);
return 3;
@@ -767,7 +745,7 @@ public abstract class Tracer implements TypeTag {
private void setFieldTarget(boolean notStatic, int index) throws BadBytecode {
if (notStatic) {
String className = cpool.getFieldrefClassName(index);
TypeData.setType(stackTypes[--stackTop], className, classPool);
stackTypes[--stackTop].setType(className, classPool);
}
}

@@ -823,7 +801,7 @@ public abstract class Tracer implements TypeTag {
checkParamTypes(desc, 1);
if (notStatic) {
String className = cpool.getMethodrefClassName(i);
TypeData.setType(stackTypes[--stackTop], className, classPool);
stackTypes[--stackTop].setType(className, classPool);
}

pushMemberType(desc);
@@ -835,7 +813,7 @@ public abstract class Tracer implements TypeTag {
String desc = cpool.getInterfaceMethodrefType(i);
checkParamTypes(desc, 1);
String className = cpool.getInterfaceMethodrefClassName(i);
TypeData.setType(stackTypes[--stackTop], className, classPool);
stackTypes[--stackTop].setType(className, classPool);
pushMemberType(desc);
return 5;
}
@@ -912,10 +890,9 @@ public abstract class Tracer implements TypeTag {
stackTop--;

if (array)
TypeData.setType(stackTypes[stackTop],
desc.substring(i, k), classPool);
stackTypes[stackTop].setType(desc.substring(i, k), classPool);
else if (c == 'L')
TypeData.setType(stackTypes[stackTop],
desc.substring(i + 1, k - 1).replace('/', '.'), classPool);
stackTypes[stackTop].setType(desc.substring(i + 1, k - 1).replace('/', '.'),
classPool);
}
}

+ 519
- 327
src/main/javassist/bytecode/stackmap/TypeData.java
File diff suppressed because it is too large
View File


+ 2
- 1
src/main/javassist/bytecode/stackmap/TypeTag.java View File

@@ -19,7 +19,8 @@ package javassist.bytecode.stackmap;
import javassist.bytecode.StackMapTable;

public interface TypeTag {
TypeData TOP = null;
String TOP_TYPE = "*top*";
TypeData TOP = new TypeData.BasicType(TOP_TYPE, StackMapTable.TOP);
TypeData INTEGER = new TypeData.BasicType("int", StackMapTable.INTEGER);
TypeData FLOAT = new TypeData.BasicType("float", StackMapTable.FLOAT);
TypeData DOUBLE = new TypeData.BasicType("double", StackMapTable.DOUBLE);

+ 8
- 24
src/main/javassist/bytecode/stackmap/TypedBlock.java View File

@@ -20,16 +20,10 @@ import javassist.bytecode.*;

public class TypedBlock extends BasicBlock {
public int stackTop, numLocals;
public TypeData[] stackTypes, localsTypes;

// set by a Liveness object.
// inputs[i] is true if the i-th variable is used within this block.
public boolean[] inputs;

// working area for Liveness class.
public boolean updating;
public int status;
public byte[] localsUsage;
// localsTypes is set to non-null when this block is first visited by a MapMaker.
// see alreadySet().
public TypeData[] localsTypes;
public TypeData[] stackTypes;

/**
* Divides the method body into basic blocks.
@@ -52,16 +46,12 @@ public class TypedBlock extends BasicBlock {
blocks[0].initFirstBlock(ca.getMaxStack(), ca.getMaxLocals(),
pool.getClassName(), minfo.getDescriptor(),
isStatic, minfo.isConstructor());
new Liveness().compute(ca.iterator(), blocks, ca.getMaxLocals(),
blocks[0].localsTypes);
return blocks;
}

protected TypedBlock(int pos) {
super(pos);
localsTypes = null;
inputs = null;
updating = false;
}

protected void toString2(StringBuffer sbuf) {
@@ -70,11 +60,6 @@ public class TypedBlock extends BasicBlock {
printTypes(sbuf, stackTop, stackTypes);
sbuf.append("}, locals={");
printTypes(sbuf, numLocals, localsTypes);
sbuf.append("}, inputs={");
if (inputs != null)
for (int i = 0; i < inputs.length; i++)
sbuf.append(inputs[i] ? "1, " : "0, ");

sbuf.append('}');
}

@@ -111,10 +96,9 @@ public class TypedBlock extends BasicBlock {
public void resetNumLocals() {
if (localsTypes != null) {
int nl = localsTypes.length;
while (nl > 0 && localsTypes[nl - 1] == TypeTag.TOP) {
while (nl > 0 && localsTypes[nl - 1].isBasicType() == TypeTag.TOP) {
if (nl > 1) {
TypeData td = localsTypes[nl - 2];
if (td == TypeTag.LONG || td == TypeTag.DOUBLE)
if (localsTypes[nl - 2].is2WordType())
break;
}

@@ -153,8 +137,8 @@ public class TypedBlock extends BasicBlock {
throw new BadBytecode("no method descriptor: " + methodDesc);

stackTop = 0;
stackTypes = new TypeData[maxStack];
TypeData[] locals = new TypeData[maxLocals];
stackTypes = TypeData.make(maxStack);
TypeData[] locals = TypeData.make(maxLocals);
if (isConstructor)
locals[0] = new TypeData.UninitThis(className);
else if (!isStatic)

+ 1
- 1
src/test/javassist/JvstTest.java View File

@@ -1110,7 +1110,7 @@ public class JvstTest extends JvstTestRoot {
suite.addTestSuite(JvstTest4.class);
suite.addTestSuite(LoaderTestByRandall.class);
suite.addTestSuite(javassist.bytecode.BytecodeTest.class);
// suite.addTestSuite(javassist.bytecode.StackMapTest.class);
suite.addTestSuite(javassist.bytecode.StackMapTest.class);
suite.addTestSuite(javassist.compiler.CompTest.class);
suite.addTestSuite(javassist.SetterTest.class);
suite.addTestSuite(javassist.bytecode.InsertGap0.class);

+ 2
- 1
src/test/javassist/JvstTest4.java View File

@@ -683,9 +683,10 @@ public class JvstTest4 extends JvstTestRoot {
}
}
System.gc();
System.gc();
int size = javassist.compiler.MemberResolver.getInvalidMapSize();
System.out.println("JIRA150b " + size);
assertTrue(size < N - 10);
assertTrue("JIRA150b size: " + size, size < N - 10);
}

public void testJIRA152() throws Exception {

+ 315
- 0
src/test/javassist/bytecode/StackMapTest.java View File

@@ -0,0 +1,315 @@
package javassist.bytecode;

import java.util.Iterator;
import java.util.List;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.lang.reflect.Method;

import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.CtNewMethod;
import javassist.JvstTest;
import javassist.Loader;
import javassist.bytecode.stackmap.MapMaker;
import javassist.bytecode.stackmap.TypeData;
import junit.framework.TestCase;

public class StackMapTest extends TestCase {
public static final String PATH = JvstTest.PATH;
private ClassPool loader, dloader;
private Loader cloader;

public StackMapTest(String name) { super(name); }

protected void setUp() throws Exception {
loader = ClassPool.getDefault();
dloader = new ClassPool(null);
dloader.appendSystemPath();
dloader.insertClassPath(".");
cloader = new Loader(dloader);
}

protected Object make(String name) throws Exception {
return cloader.loadClass(name).newInstance();
}

protected int invoke(Object target, String method) throws Exception {
Method m = target.getClass().getMethod(method, new Class[0]);
Object res = m.invoke(target, new Object[0]);
return ((Integer)res).intValue();
}

protected static void rebuildStackMaps(CtClass cc) throws BadBytecode, IOException {
ClassPool cp = cc.getClassPool();
Iterator it = cc.getClassFile().getMethods().iterator();
while (it.hasNext()) {
MethodInfo minfo = (MethodInfo)it.next();
rebuildStackMap(cc, cp, minfo);
}
}

protected static void rebuildStackMap(CtClass cc, ClassPool cp, MethodInfo minfo) throws BadBytecode, IOException {
CodeAttribute ca = minfo.getCodeAttribute();
if (ca != null) {
StackMapTable smt = (StackMapTable)ca.getAttribute(StackMapTable.tag);
if (smt != null) {
String data = readSmt(smt);
StackMapTable smt2 = MapMaker.make(cp, minfo);
String data2 = readSmt(smt2);
try {
assertEquals(cc.getName() + ":" + minfo.getName() + ":" + minfo.getDescriptor(),
data, data2);
}
catch (junit.framework.ComparisonFailure e) {
System.out.println("*** " + cc.getName() + ":" + minfo.getName() + ":" + minfo.getDescriptor());
smt.println(System.out);
System.out.println("---");
smt2.println(System.out);
}
}
}
}

protected static void rebuildStackMaps2(CtClass cc) throws BadBytecode {
ClassPool cp = cc.getClassPool();
Iterator it = cc.getClassFile().getMethods().iterator();
while (it.hasNext()) {
MethodInfo minfo = (MethodInfo)it.next();
minfo.rebuildStackMap(cp);
}
}

protected static String readSmt(StackMapTable smt) throws BadBytecode, IOException {
if (smt == null)
return "";

ByteArrayOutputStream out = new ByteArrayOutputStream();
PrintStream pout = new PrintStream(out);
smt.println(pout);
pout.close();
return out.toString();
}

public static void main(String[] args) throws Exception {
if (args.length == 2 && args[0].equals("-c")) {
CtClass cc = ClassPool.getDefault().get(args[0].replace('/', '.'));
rebuildStackMaps(cc);
}
else {
for (int i = 0; i < args.length; i++) {
CtClass cc = ClassPool.getDefault().get(getClassName(args[i]));
System.out.println(cc.getName());
rebuildStackMaps2(cc);
cc.writeFile("rebuild");
}
}
}

public static String getClassName(String fileName) {
Matcher m = Pattern.compile("(.*)\\.class").matcher(fileName);
if (m.matches())
return m.group(1).replace('/', '.');
else
return fileName;
}

public void testCommonSuper() throws Exception {
CtClass type = loader.get("javassist.CtClassType[]");
CtClass base = loader.get("javassist.CtClass[]");
CtClass array = loader.get("javassist.CtArray[]");
CtClass array2 = loader.get("javassist.CtArray[][]");
CtClass str = loader.get("java.lang.String[]");
CtClass strObj = loader.get("java.lang.String");
CtClass obj = loader.get("java.lang.Object[]");
CtClass objObj = loader.get("java.lang.Object");

assertEquals(base, TypeData.commonSuperClassEx(type, base));
assertEquals(base, TypeData.commonSuperClassEx(base, type));
assertEquals(base, TypeData.commonSuperClassEx(array, type));
assertEquals(obj, TypeData.commonSuperClassEx(base, str));
assertEquals(objObj, TypeData.commonSuperClassEx(strObj, str));
assertEquals(obj, TypeData.commonSuperClassEx(array, array2));
assertEquals(obj, TypeData.commonSuperClassEx(base, array2));
}

public void testRebuild() throws Exception {
CtClass cc = loader.get("javassist.bytecode.StackMapTest$T1");
rebuildStackMaps2(cc);
//Class c = cc.toClass();
//Object t1 = c.newInstance();
cc.writeFile();
Object t1 = make(cc.getName());
assertEquals(3, invoke(t1, "test"));
}

public static interface Intf {
int foo();
}

public static class C1 implements Intf {
public int foo() { return 0; }
}
public static class C2 implements Intf {
public int foo() { return 3; }
}

public static class T1 {
public int test() {
return foo(-1);
}

public int foo(int i) {
Intf obj;
if (i > 0)
obj = new C1();
else
obj = new C2();

return obj.foo();
}
}

public void testRebuild2() throws Exception {
CtClass cc = loader.get("javassist.bytecode.StackMapTest$C3");
rebuildStackMaps2(cc);
cc.writeFile();
Object t1 = make(cc.getName());
assertEquals(7, invoke(t1, "test"));
}

public static class C3 {
int value;
public C3(int i) {
value = i;
}
public C3(boolean b) {
this(b ? 7 : 10);
}
public C3() { this(true); }
public int test() { return value; }
}

public void testRebuild3() throws Exception {
CtClass cc = loader.get("javassist.bytecode.StackMapTest$T3");
rebuildStackMaps2(cc);
cc.writeFile();
Object t1 = make(cc.getName());
assertEquals(1100, invoke(t1, "test"));
}

public static interface Intf2 {
int bar();
}

public static class D1 extends C1 implements Intf2 {
public int bar() { return 10; }
}

public static class D2 extends C1 implements Intf2 {
public int bar() { return 100; }
}

public static class T3 {
public int bar(Intf2 i) { return 1000; }

public int test() {
return foo(-1);
}

public int foo(int i) {
Intf2 obj;
if (i > 0)
obj = new D1();
else
obj = new D2();

System.out.println(obj.toString());
return obj.bar() + bar(obj);
}
}

public void testRebuildArray() throws Exception {
CtClass cc = loader.get("javassist.bytecode.StackMapTest$T4");
rebuildStackMaps2(cc);
cc.writeFile();
Object t1 = make(cc.getName());
assertEquals(30, invoke(t1, "test"));
}

public static class T4 {
public int test() {
return foo(3) + foo2(3) + foo3(3) + foo4(3);
}

public int foo(int i) {
C1[] a;
if (i > 0)
a = new D1[1];
else
a = new D2[1];

if (i > 0)
a[0] = new D1();
else
a[0] = new D2();

return a[0].foo();
}

public int foo2(int i) {
Intf2[] a;
if (i > 0)
a = new D1[1];
else
a = new D2[1];

if (i > 0)
a[0] = new D1();
else
a[0] = new D2();

return a[0].bar();
}

public int foo3(int i) {
Intf2[] a = null;
if (i > 0)
a = new D1[1];

a[0] = new D1();
return a[0].bar();
}

public int foo4(int i) {
Intf2[] a = new Intf2[1];
if (i > 0)
a[0] = new D1();
return a[0].bar();
}
}

public void tstCtClassType() throws Exception {
ClassPool cp = ClassPool.getDefault();
CtClass cc = cp.get("javassist.CtClassType");
MethodInfo minfo = getMethodInfo(cc.getClassFile(), "getFields", "(Ljava/util/ArrayList;Ljavassist/CtClass;)V");
rebuildStackMap(cc, cp, minfo);
}

MethodInfo getMethodInfo(ClassFile cf, String name, String desc) {
List list = cf.getMethods();
Iterator it = list.iterator();
while (it.hasNext()) {
MethodInfo mi = (MethodInfo)it.next();
if (mi.getName().equals(name) && mi.getDescriptor().equals(desc))
return mi;
}

return null;
}
}

+ 4
- 0
src/test/javassist/tools/reflect/LoaderTest.java View File

@@ -53,6 +53,10 @@ public class LoaderTest extends TestCase {
"javassist.tools.reflect.ClassMetaobject");

ClassPool cp = ClassPool.getDefault();

CtClass cc2 = cp.get("javassist.tools.reflect.SuperClass");
cc2.debugWriteFile("reflected/");

CtClass cc = cp.get("javassist.tools.reflect.SubClass");

CtMethod[] ms = cc.getMethods();

Loading…
Cancel
Save