<p>-version 3.19 | <p>-version 3.19 | ||||
<ul> | <ul> | ||||
<li>JIRA JASSIST-206. | |||||
<li>JIRA JASSIST-158, 206. | |||||
</ul> | </ul> | ||||
</p> | </p> | ||||
sbuf.append("}"); | sbuf.append("}"); | ||||
} | } | ||||
/** | |||||
* A Mark indicates the position of a branch instruction | |||||
* or a branch target. | |||||
*/ | |||||
static class Mark implements Comparable { | static class Mark implements Comparable { | ||||
int position; | int position; | ||||
BasicBlock block; | BasicBlock block; | ||||
} | } | ||||
else { | else { | ||||
// the previous mark already has exits. | // the previous mark already has exits. | ||||
int prevPos = prev.position; | |||||
if (prevPos + prev.length < m.position) { | |||||
prev = makeBlock(prevPos + prev.length); | |||||
prev.length = m.position - prevPos; | |||||
if (prev.position + prev.length < m.position) { | |||||
// dead code is found. | |||||
prev = makeBlock(prev.position + prev.length); | |||||
blocks.add(prev); | |||||
prev.length = m.position - prev.position; | |||||
// the incoming flow from dead code is not counted | // the incoming flow from dead code is not counted | ||||
// bb.incoming++; | // bb.incoming++; | ||||
prev.exit = makeArray(bb); | prev.exit = makeArray(bb); |
// Phase 1.5 | // Phase 1.5 | ||||
/* | /* | ||||
* Javac may generate an exception handler that catches only an exception | |||||
* Javac may generate an exception handler that catches only the exception | |||||
* thrown within the handler itself. It is dead code. | * thrown within the handler itself. It is dead code. | ||||
* See javassist.JvstTest4.testJIRA195(). | |||||
*/ | */ | ||||
private void findDeadCatchers(byte[] code, TypedBlock[] blocks) throws BadBytecode { | private void findDeadCatchers(byte[] code, TypedBlock[] blocks) throws BadBytecode { | ||||
int len = blocks.length; | int len = blocks.length; | ||||
for (int i = 0; i < len; i++) { | for (int i = 0; i < len; i++) { | ||||
TypedBlock block = blocks[i]; | TypedBlock block = blocks[i]; | ||||
if (block.localsTypes == null) { // if block is dead code | |||||
if (!block.alreadySet()) { | |||||
fixDeadcode(code, block); | |||||
BasicBlock.Catch handler = block.toCatch; | BasicBlock.Catch handler = block.toCatch; | ||||
while (handler != null) | |||||
if (handler.body == block) { | |||||
BasicBlock.Catch handler2 | |||||
= new BasicBlock.Catch(block, handler.typeIndex, null); | |||||
traceException(code, handler2); | |||||
break; | |||||
if (handler != null) { | |||||
TypedBlock tb = (TypedBlock)handler.body; | |||||
if (!tb.alreadySet()) { | |||||
// tb is a handler that catches only the exceptions | |||||
// thrown from dead code. | |||||
recordStackMap(tb, handler.typeIndex); | |||||
fixDeadcode(code, tb); | |||||
tb.incoming = 1; | |||||
} | } | ||||
else | |||||
handler = handler.next; | |||||
} | |||||
} | } | ||||
} | } | ||||
} | } | ||||
private void fixDeadcode(byte[] code, TypedBlock block) throws BadBytecode { | |||||
int pos = block.position; | |||||
int len = block.length - 3; | |||||
if (len < 0) { | |||||
// if the dead-code length is shorter than 3 bytes. | |||||
if (len == -1) | |||||
code[pos] = Bytecode.NOP; | |||||
code[pos + block.length - 1] = (byte)Bytecode.ATHROW; | |||||
block.incoming = 1; | |||||
recordStackMap(block, 0); | |||||
return; | |||||
} | |||||
// if block.incomping > 0, all the incoming edges are from | |||||
// other dead code blocks. So set block.incoming to 0. | |||||
block.incoming = 0; | |||||
for (int k = 0; k < len; k++) | |||||
code[pos + k] = Bytecode.NOP; | |||||
code[pos + len] = (byte)Bytecode.GOTO; | |||||
ByteArray.write16bit(-len, code, pos + len + 1); | |||||
} | |||||
// Phase 2 | // Phase 2 | ||||
/* | /* | ||||
* This method first finds strongly connected components (SCCs) | * This method first finds strongly connected components (SCCs) | ||||
* on a graph made by TypeData by Tarjan's algorithm. | |||||
* in a TypeData graph by Tarjan's algorithm. | |||||
* SCCs are TypeData nodes sharing the same type. | * SCCs are TypeData nodes sharing the same type. | ||||
* Since SCCs are found in the topologically sorted order, | * Since SCCs are found in the topologically sorted order, | ||||
* their types are also fixed when they are found. | * their types are also fixed when they are found. | ||||
int index = 0; | int index = 0; | ||||
for (int i = 0; i < len; i++) { | for (int i = 0; i < len; i++) { | ||||
TypedBlock block = blocks[i]; | TypedBlock block = blocks[i]; | ||||
if (block.localsTypes == null) // if block is dead code | |||||
fixDeadcode(code, block); | |||||
else { | |||||
if (block.alreadySet()) { // if block is not dead code | |||||
int n = block.localsTypes.length; | int n = block.localsTypes.length; | ||||
for (int j = 0; j < n; j++) | for (int j = 0; j < n; j++) | ||||
index = block.localsTypes[j].dfs(preOrder, index, classPool); | index = block.localsTypes[j].dfs(preOrder, index, classPool); | ||||
} | } | ||||
} | } | ||||
private void fixDeadcode(byte[] code, TypedBlock block) throws BadBytecode { | |||||
int pos = block.position; | |||||
int len = block.length - 3; | |||||
if (len < 0) | |||||
throw new BadBytecode("dead code detected at " + pos | |||||
+ ". No stackmap table generated."); | |||||
for (int k = 0; k < len; k++) | |||||
code[pos + k] = Bytecode.NOP; | |||||
code[pos + len] = (byte)Bytecode.GOTO; | |||||
ByteArray.write16bit(-len, code, pos + len + 1); | |||||
} | |||||
// Phase 3 | // Phase 3 | ||||
public StackMapTable toStackMap(TypedBlock[] blocks) { | public StackMapTable toStackMap(TypedBlock[] blocks) { |
return doOpcode148_201(pos, code, op); | return doOpcode148_201(pos, code, op); | ||||
} | } | ||||
catch (ArrayIndexOutOfBoundsException e) { | catch (ArrayIndexOutOfBoundsException e) { | ||||
throw new BadBytecode("inconsistent stack height " + e.getMessage()); | |||||
throw new BadBytecode("inconsistent stack height " + e.getMessage(), e); | |||||
} | } | ||||
} | } | ||||
import javassist.*; | |||||
public class Test { | |||||
public static void main(String[] args) throws Exception { | |||||
ClassPool cp = ClassPool.getDefault(); | |||||
// ClassPool cp = new ClassPool(); | |||||
cp.insertClassPath("./target/test-classes"); | |||||
CtClass cc = cp.get("test4.JIRA207"); | |||||
// cc.getClassFile().setMajorVersion(javassist.bytecode.ClassFile.JAVA_4); | |||||
CtMethod cm = cc.getDeclaredMethod("foo"); | |||||
cm.insertBefore("throw new Exception();"); | |||||
CtMethod cm2 = cc.getDeclaredMethod("run2"); | |||||
cm2.insertBefore("throw new Exception();"); | |||||
cc.writeFile(); | |||||
} | |||||
} |
Object obj = make(cc.getName()); | Object obj = make(cc.getName()); | ||||
assertEquals(15, invoke(obj, "run")); | assertEquals(15, invoke(obj, "run")); | ||||
} | } | ||||
public void testJIRA207() throws Exception { | |||||
CtClass cc = sloader.get("test4.JIRA207"); | |||||
CtMethod cm = cc.getDeclaredMethod("foo"); | |||||
cm.insertBefore("throw new Exception();"); | |||||
CtMethod cm2 = cc.getDeclaredMethod("run2"); | |||||
cm2.insertBefore("throw new Exception();"); | |||||
cc.writeFile(); | |||||
Object obj = make(cc.getName()); | |||||
try { | |||||
invoke(obj, "run2"); | |||||
fail("run2"); | |||||
} | |||||
catch (Exception e) {} | |||||
} | |||||
} | } |
public void testJIRA175b() throws Exception { | public void testJIRA175b() throws Exception { | ||||
CtClass cc = loader.get("javassist.bytecode.StackMapTest$C6"); | CtClass cc = loader.get("javassist.bytecode.StackMapTest$C6"); | ||||
try { | |||||
cc.getDeclaredMethod("setter").instrument(new javassist.expr.ExprEditor() { | |||||
public void edit(javassist.expr.FieldAccess f) throws javassist.CannotCompileException { | |||||
if (!f.where().getMethodInfo().isMethod()) | |||||
return; | |||||
cc.getDeclaredMethod("setter").instrument(new javassist.expr.ExprEditor() { | |||||
public void edit(javassist.expr.FieldAccess f) throws javassist.CannotCompileException { | |||||
if (!f.where().getMethodInfo().isMethod()) | |||||
return; | |||||
f.replace("{ $_ = $proceed($$); return $_;}"); | |||||
} | |||||
}); | |||||
fail("deadcode detection"); | |||||
} | |||||
catch (javassist.CannotCompileException e) {} | |||||
// this will make a 1-byte dead code. | |||||
f.replace("{ $_ = $proceed($$); return $_;}"); | |||||
} | |||||
}); | |||||
cc.writeFile(); | |||||
} | } | ||||
public static class C6 { | public static class C6 { |
package test4; | |||||
public class JIRA207 { | |||||
public int run() { | |||||
int i = 3; | |||||
return foo(i); | |||||
} | |||||
public int foo(int i) { | |||||
int k = i + 3; | |||||
if (k > 0) | |||||
return k * k; | |||||
else | |||||
return k; | |||||
} | |||||
public int run2() { | |||||
int i = 0; | |||||
int p = i; | |||||
int q = p; | |||||
int r = q; | |||||
for (int k = 1; k < 3; ++k) | |||||
p += k; | |||||
for (int k = 3; k > 0; --k) | |||||
try { | |||||
foo(k); | |||||
p++; | |||||
} | |||||
finally { | |||||
p++; | |||||
} | |||||
try { | |||||
foo(p); | |||||
} | |||||
catch (RuntimeException e) { | |||||
if (p > 0) | |||||
throw e; | |||||
} | |||||
switch (p) { | |||||
case 1: | |||||
p = 100; | |||||
break; | |||||
default : | |||||
++p; | |||||
} | |||||
return p + r; | |||||
} | |||||
} |