<p>-version 3.19 | <p>-version 3.19 | ||||
<ul> | <ul> | ||||
<li>JIRA JASSIST-158, 205, 206, 207, 208, 209, 211, 212, 216, 220, 223, 224, 227, 230, 234, 235, 236. | |||||
<li>JIRA JASSIST-158, 205, 206, 207, 208, 209, 211, 212, 216, 220, 223, 224, | |||||
227, 230, 234, 235, 236, 237. | |||||
</ul> | </ul> | ||||
</p> | </p> | ||||
* @see Descriptor#ofConstructor(CtClass[]) | * @see Descriptor#ofConstructor(CtClass[]) | ||||
*/ | */ | ||||
public void addInvokespecial(CtClass clazz, String name, String desc) { | public void addInvokespecial(CtClass clazz, String name, String desc) { | ||||
addInvokespecial(constPool.addClassInfo(clazz), name, desc); | |||||
boolean isInterface = clazz == null ? false : clazz.isInterface(); | |||||
addInvokespecial(isInterface, | |||||
constPool.addClassInfo(clazz), name, desc); | |||||
} | } | ||||
/** | /** | ||||
* Appends INVOKESPECIAL. | |||||
* Appends INVOKESPECIAL. The invoked method must not be a default | |||||
* method declared in an interface. | |||||
* | * | ||||
* @param clazz the fully-qualified class name. | * @param clazz the fully-qualified class name. | ||||
* @param name the method name | * @param name the method name | ||||
* @see Descriptor#ofConstructor(CtClass[]) | * @see Descriptor#ofConstructor(CtClass[]) | ||||
*/ | */ | ||||
public void addInvokespecial(String clazz, String name, String desc) { | public void addInvokespecial(String clazz, String name, String desc) { | ||||
addInvokespecial(constPool.addClassInfo(clazz), name, desc); | |||||
addInvokespecial(false, constPool.addClassInfo(clazz), name, desc); | |||||
} | } | ||||
/** | /** | ||||
* Appends INVOKESPECIAL. | |||||
* Appends INVOKESPECIAL. The invoked method must not be a default | |||||
* method declared in an interface. | |||||
* | * | ||||
* @param clazz the index of <code>CONSTANT_Class_info</code> | * @param clazz the index of <code>CONSTANT_Class_info</code> | ||||
* structure. | * structure. | ||||
* @see Descriptor#ofConstructor(CtClass[]) | * @see Descriptor#ofConstructor(CtClass[]) | ||||
*/ | */ | ||||
public void addInvokespecial(int clazz, String name, String desc) { | public void addInvokespecial(int clazz, String name, String desc) { | ||||
addInvokespecial(false, clazz, name, desc); | |||||
} | |||||
/** | |||||
* Appends INVOKESPECIAL. | |||||
* | |||||
* @param isInterface true if the invoked method is a default method | |||||
* declared in an interface. | |||||
* @param clazz the index of <code>CONSTANT_Class_info</code> | |||||
* structure. | |||||
* @param name the method name | |||||
* @param desc the descriptor of the method signature. | |||||
* | |||||
* @see Descriptor#ofMethod(CtClass,CtClass[]) | |||||
* @see Descriptor#ofConstructor(CtClass[]) | |||||
*/ | |||||
public void addInvokespecial(boolean isInterface, int clazz, String name, String desc) { | |||||
add(INVOKESPECIAL); | add(INVOKESPECIAL); | ||||
addIndex(constPool.addMethodrefInfo(clazz, name, desc)); | |||||
int index; | |||||
if (isInterface) | |||||
index = constPool.addInterfaceMethodrefInfo(clazz, name, desc); | |||||
else | |||||
index = constPool.addMethodrefInfo(clazz, name, desc); | |||||
addIndex(index); | |||||
growStack(Descriptor.dataSize(desc) - 1); | growStack(Descriptor.dataSize(desc) - 1); | ||||
} | } | ||||
} | } | ||||
else if (op == '.') { | else if (op == '.') { | ||||
ASTree target = e.oprand1(); | ASTree target = e.oprand1(); | ||||
if (target instanceof Keyword) | |||||
if (((Keyword)target).get() == SUPER) | |||||
isSpecial = true; | |||||
try { | |||||
target.accept(this); | |||||
String classFollowedByDotSuper = TypeChecker.isDotSuper(target); | |||||
if (classFollowedByDotSuper != null) { | |||||
isSpecial = true; | |||||
targetClass = MemberResolver.getSuperInterface(thisClass, | |||||
classFollowedByDotSuper); | |||||
if (inStaticMethod || (cached != null && cached.isStatic())) | |||||
isStatic = true; // should be static | |||||
else { | |||||
aload0pos = bytecode.currentPc(); | |||||
bytecode.addAload(0); // this | |||||
} | |||||
} | } | ||||
catch (NoFieldException nfe) { | |||||
if (nfe.getExpr() != target) | |||||
throw nfe; | |||||
// it should be a static method. | |||||
exprType = CLASS; | |||||
arrayDim = 0; | |||||
className = nfe.getField(); // JVM-internal | |||||
isStatic = true; | |||||
else { | |||||
if (target instanceof Keyword) | |||||
if (((Keyword)target).get() == SUPER) | |||||
isSpecial = true; | |||||
try { | |||||
target.accept(this); | |||||
} | |||||
catch (NoFieldException nfe) { | |||||
if (nfe.getExpr() != target) | |||||
throw nfe; | |||||
// it should be a static method. | |||||
exprType = CLASS; | |||||
arrayDim = 0; | |||||
className = nfe.getField(); // JVM-internal | |||||
isStatic = true; | |||||
} | |||||
if (arrayDim > 0) | |||||
targetClass = resolver.lookupClass(javaLangObject, true); | |||||
else if (exprType == CLASS /* && arrayDim == 0 */) | |||||
targetClass = resolver.lookupClassByJvmName(className); | |||||
else | |||||
badMethod(); | |||||
} | } | ||||
if (arrayDim > 0) | |||||
targetClass = resolver.lookupClass(javaLangObject, true); | |||||
else if (exprType == CLASS /* && arrayDim == 0 */) | |||||
targetClass = resolver.lookupClassByJvmName(className); | |||||
else | |||||
badMethod(); | |||||
} | } | ||||
else | else | ||||
badMethod(); | badMethod(); |
import java.util.WeakHashMap; | import java.util.WeakHashMap; | ||||
import java.util.List; | import java.util.List; | ||||
import java.util.Iterator; | import java.util.Iterator; | ||||
import javassist.*; | import javassist.*; | ||||
import javassist.bytecode.*; | import javassist.bytecode.*; | ||||
import javassist.compiler.ast.*; | import javassist.compiler.ast.*; | ||||
+ c.getName()); | + c.getName()); | ||||
} | } | ||||
public static CtClass getSuperInterface(CtClass c, String interfaceName) | |||||
throws CompileError | |||||
{ | |||||
try { | |||||
CtClass[] intfs = c.getInterfaces(); | |||||
for (int i = 0; i < intfs.length; i++) | |||||
if (intfs[i].getName().equals(interfaceName)) | |||||
return intfs[i]; | |||||
} catch (NotFoundException e) {} | |||||
throw new CompileError("cannot find the super inetrface " + interfaceName | |||||
+ " of " + c.getName()); | |||||
} | |||||
public static String javaToJvmName(String classname) { | public static String javaToJvmName(String classname) { | ||||
return classname.replace('.', '/'); | return classname.replace('.', '/'); | ||||
} | } |
* | postfix.expr "." Identifier | * | postfix.expr "." Identifier | ||||
* | postfix.expr ( "[" "]" )* "." CLASS | * | postfix.expr ( "[" "]" )* "." CLASS | ||||
* | postfix.expr "#" Identifier | * | postfix.expr "#" Identifier | ||||
* | postfix.expr "." SUPER | |||||
* | * | ||||
* "#" is not an operator of regular Java. It separates | * "#" is not an operator of regular Java. It separates | ||||
* a class name and a member name in an expression for static member | * a class name and a member name in an expression for static member | ||||
case '.' : | case '.' : | ||||
lex.get(); | lex.get(); | ||||
t = lex.get(); | t = lex.get(); | ||||
if (t == CLASS) { | |||||
if (t == CLASS) | |||||
expr = parseDotClass(expr, 0); | expr = parseDotClass(expr, 0); | ||||
} | |||||
else if (t == SUPER) | |||||
expr = Expr.make('.', new Symbol(toClassName(expr)), new Keyword(t)); | |||||
else if (t == Identifier) { | else if (t == Identifier) { | ||||
str = lex.getString(); | str = lex.getString(); | ||||
expr = Expr.make('.', expr, new Member(str)); | expr = Expr.make('.', expr, new Member(str)); |
false); | false); | ||||
else if (op == '.') { | else if (op == '.') { | ||||
ASTree target = e.oprand1(); | ASTree target = e.oprand1(); | ||||
try { | |||||
target.accept(this); | |||||
} | |||||
catch (NoFieldException nfe) { | |||||
if (nfe.getExpr() != target) | |||||
throw nfe; | |||||
// it should be a static method. | |||||
exprType = CLASS; | |||||
arrayDim = 0; | |||||
className = nfe.getField(); // JVM-internal | |||||
e.setOperator(MEMBER); | |||||
e.setOprand1(new Symbol(MemberResolver.jvmToJavaName( | |||||
className))); | |||||
String classFollowedByDotSuper = isDotSuper(target); | |||||
if (classFollowedByDotSuper != null) | |||||
targetClass = MemberResolver.getSuperInterface(thisClass, | |||||
classFollowedByDotSuper); | |||||
else { | |||||
try { | |||||
target.accept(this); | |||||
} | |||||
catch (NoFieldException nfe) { | |||||
if (nfe.getExpr() != target) | |||||
throw nfe; | |||||
// it should be a static method. | |||||
exprType = CLASS; | |||||
arrayDim = 0; | |||||
className = nfe.getField(); // JVM-internal | |||||
e.setOperator(MEMBER); | |||||
e.setOprand1(new Symbol(MemberResolver.jvmToJavaName( | |||||
className))); | |||||
} | |||||
if (arrayDim > 0) | |||||
targetClass = resolver.lookupClass(javaLangObject, true); | |||||
else if (exprType == CLASS /* && arrayDim == 0 */) | |||||
targetClass = resolver.lookupClassByJvmName(className); | |||||
else | |||||
badMethod(); | |||||
} | } | ||||
if (arrayDim > 0) | |||||
targetClass = resolver.lookupClass(javaLangObject, true); | |||||
else if (exprType == CLASS /* && arrayDim == 0 */) | |||||
targetClass = resolver.lookupClassByJvmName(className); | |||||
else | |||||
badMethod(); | |||||
} | } | ||||
else | else | ||||
badMethod(); | badMethod(); | ||||
throw new CompileError("bad method"); | throw new CompileError("bad method"); | ||||
} | } | ||||
/** | |||||
* Returns non-null if target is something like Foo.super | |||||
* for accessing the default method in an interface. | |||||
* Otherwise, null. | |||||
* | |||||
* @return the class name followed by {@code .super} or null. | |||||
*/ | |||||
static String isDotSuper(ASTree target) { | |||||
if (target instanceof Expr) { | |||||
Expr e = (Expr)target; | |||||
if (e.getOperator() == '.') { | |||||
ASTree right = e.oprand2(); | |||||
if (right instanceof Keyword && ((Keyword)right).get() == SUPER) | |||||
return ((Symbol)e.oprand1()).get(); | |||||
} | |||||
} | |||||
return null; | |||||
} | |||||
/** | /** | ||||
* @return a pair of the class declaring the invoked method | * @return a pair of the class declaring the invoked method | ||||
* and the MethodInfo of that method. Never null. | * and the MethodInfo of that method. Never null. |
public class Test { | public class Test { | ||||
public static void main(String[] args) throws Exception { | public static void main(String[] args) throws Exception { | ||||
if (args.length > 1) { | |||||
new Test().bar(3); | |||||
return; | |||||
} | |||||
ClassPool cp = ClassPool.getDefault(); | ClassPool cp = ClassPool.getDefault(); | ||||
CtClass inner3 = cp.get("test2.Anon$Anon2.1"); | |||||
CtBehavior ct = inner3.getEnclosingBehavior(); | |||||
/* CtClass str = cp.get("java.lang.String"); | |||||
CtClass cc = cp.get("Test"); | |||||
cc.getClassFile().setMajorVersion(javassist.bytecode.ClassFile.JAVA_4); | |||||
CtMethod m = cc.getDeclaredMethod("bar"); | |||||
m.addLocalVariable("aVar", str); | |||||
m.insertAfter(" dismiss( aVar );" , true); | |||||
cc.getClassFile().setMajorVersion(javassist.bytecode.ClassFile.JAVA_7); | |||||
m.insertBefore("aVar = initVar();"); | |||||
cc.writeFile();*/ | |||||
CtClass cc = cp.get("test5.DefaultMethod"); | |||||
CtMethod m = CtNewMethod.make("public int run(){ return test5.DefaultMethodIntf.super.foo(); }", cc); | |||||
cc.addMethod(m); | |||||
cc.writeFile(); | |||||
} | } | ||||
public void bar(int i) { foo(i); } | |||||
public void foo(int i) { System.out.println(i); } | |||||
public String initVar() { return "init"; } | |||||
public void dismiss(String s) { System.out.println(s); } | |||||
} | } |
suite.addTestSuite(JvstTest2.class); | suite.addTestSuite(JvstTest2.class); | ||||
suite.addTestSuite(JvstTest3.class); | suite.addTestSuite(JvstTest3.class); | ||||
suite.addTestSuite(JvstTest4.class); | suite.addTestSuite(JvstTest4.class); | ||||
suite.addTestSuite(JvstTest5.class); | |||||
suite.addTestSuite(LoaderTestByRandall.class); | suite.addTestSuite(LoaderTestByRandall.class); | ||||
suite.addTestSuite(javassist.bytecode.BytecodeTest.class); | suite.addTestSuite(javassist.bytecode.BytecodeTest.class); | ||||
suite.addTestSuite(javassist.bytecode.StackMapTest.class); | suite.addTestSuite(javassist.bytecode.StackMapTest.class); |
import java.io.FileInputStream; | import java.io.FileInputStream; | ||||
import java.io.FileOutputStream; | import java.io.FileOutputStream; | ||||
import java.util.HashSet; | import java.util.HashSet; | ||||
import java.util.List; | |||||
import javassist.bytecode.*; | import javassist.bytecode.*; | ||||
import javassist.bytecode.annotation.Annotation; | import javassist.bytecode.annotation.Annotation; | ||||
long t2 = endTime2 - endTime; | long t2 = endTime2 - endTime; | ||||
long t3 = endTime3 - endTime2; | long t3 = endTime3 - endTime2; | ||||
System.out.println("JIRA150: " + t1 + ", " + t2 + ", " + t3); | System.out.println("JIRA150: " + t1 + ", " + t2 + ", " + t3); | ||||
assertTrue(t2 < t1 * 5); | |||||
assertTrue(t3 < t1 * 3); | |||||
assertTrue("performance test (the next try may succeed): " + t1 + "/ 5 < " + t2, | |||||
t2 < t1 * 5); | |||||
assertTrue("", t3 < t1 * 3); | |||||
} | } | ||||
public void testJIRA150b() throws Exception { | public void testJIRA150b() throws Exception { |
package javassist; | |||||
public class JvstTest5 extends JvstTestRoot { | |||||
public JvstTest5(String name) { | |||||
super(name); | |||||
} | |||||
public void testDollarClassInStaticMethod() throws Exception { | |||||
CtClass cc = sloader.makeClass("test5.DollarClass"); | |||||
CtMethod m = CtNewMethod.make("public static int run(){ return $class.getName().length(); }", cc); | |||||
cc.addMethod(m); | |||||
m = CtNewMethod.make("public int run2(){ return $class.getName().length(); }", cc); | |||||
cc.addMethod(m); | |||||
cc.writeFile(); | |||||
Object obj = make(cc.getName()); | |||||
assertEquals(cc.getName().length(), invoke(obj, "run")); | |||||
assertEquals(cc.getName().length(), invoke(obj, "run2")); | |||||
} | |||||
public void testSuperDefaultMethodCall() throws Exception { | |||||
CtClass cc = sloader.get("test5.DefaultMethod"); | |||||
CtMethod m = CtNewMethod.make("public int run(){ return test5.DefaultMethodIntf.super.foo(); }", cc); | |||||
cc.addMethod(m); | |||||
m = CtNewMethod.make("public int run2(){ return test5.DefaultMethodIntf.baz(); }", cc); | |||||
cc.addMethod(m); | |||||
m = CtNewMethod.make("public int run3(){ return test5.DefaultMethodIntf.super.baz(); }", cc); | |||||
cc.addMethod(m); | |||||
cc.writeFile(); | |||||
Object obj = make(cc.getName()); | |||||
assertEquals(1, invoke(obj, "run")); | |||||
assertEquals(10, invoke(obj, "run2")); | |||||
assertEquals(10, invoke(obj, "run3")); | |||||
} | |||||
} |
package test5; | |||||
interface DefaultMethodSupIntf { | |||||
default int foo() { return 0; } | |||||
} | |||||
interface DefaultMethodIntf extends DefaultMethodSupIntf { | |||||
default int foo() { return 1; } | |||||
static int baz() { return 10; } | |||||
} | |||||
public class DefaultMethod implements DefaultMethodIntf { | |||||
public int bar() { return DefaultMethodIntf.super.foo(); } | |||||
public static void main(String[] args) { | |||||
int i = new DefaultMethod().bar() + new DefaultMethod().foo() + DefaultMethodIntf.baz(); | |||||
System.out.println(i); | |||||
} | |||||
} |