@@ -283,7 +283,8 @@ see javassist.Dump. | |||
<p>-version 3.19 | |||
<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> | |||
</p> | |||
@@ -924,11 +924,14 @@ public class Bytecode extends ByteVector implements Cloneable, Opcode { | |||
* @see Descriptor#ofConstructor(CtClass[]) | |||
*/ | |||
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 name the method name | |||
@@ -938,11 +941,12 @@ public class Bytecode extends ByteVector implements Cloneable, Opcode { | |||
* @see Descriptor#ofConstructor(CtClass[]) | |||
*/ | |||
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> | |||
* structure. | |||
@@ -953,8 +957,31 @@ public class Bytecode extends ByteVector implements Cloneable, Opcode { | |||
* @see Descriptor#ofConstructor(CtClass[]) | |||
*/ | |||
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); | |||
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); | |||
} | |||
@@ -489,30 +489,44 @@ public class MemberCodeGen extends CodeGen { | |||
} | |||
else if (op == '.') { | |||
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 | |||
badMethod(); |
@@ -21,6 +21,7 @@ import java.lang.ref.WeakReference; | |||
import java.util.WeakHashMap; | |||
import java.util.List; | |||
import java.util.Iterator; | |||
import javassist.*; | |||
import javassist.bytecode.*; | |||
import javassist.compiler.ast.*; | |||
@@ -523,6 +524,19 @@ public class MemberResolver implements TokenId { | |||
+ 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) { | |||
return classname.replace('.', '/'); | |||
} |
@@ -1001,6 +1001,7 @@ public final class Parser implements TokenId { | |||
* | postfix.expr "." Identifier | |||
* | postfix.expr ( "[" "]" )* "." CLASS | |||
* | postfix.expr "#" Identifier | |||
* | postfix.expr "." SUPER | |||
* | |||
* "#" is not an operator of regular Java. It separates | |||
* a class name and a member name in an expression for static member | |||
@@ -1058,9 +1059,10 @@ public final class Parser implements TokenId { | |||
case '.' : | |||
lex.get(); | |||
t = lex.get(); | |||
if (t == CLASS) { | |||
if (t == CLASS) | |||
expr = parseDotClass(expr, 0); | |||
} | |||
else if (t == SUPER) | |||
expr = Expr.make('.', new Symbol(toClassName(expr)), new Keyword(t)); | |||
else if (t == Identifier) { | |||
str = lex.getString(); | |||
expr = Expr.make('.', expr, new Member(str)); |
@@ -656,28 +656,34 @@ public class TypeChecker extends Visitor implements Opcode, TokenId { | |||
false); | |||
else if (op == '.') { | |||
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 | |||
badMethod(); | |||
@@ -694,6 +700,26 @@ public class TypeChecker extends Visitor implements Opcode, TokenId { | |||
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 | |||
* and the MethodInfo of that method. Never null. |
@@ -2,27 +2,10 @@ import javassist.*; | |||
public class Test { | |||
public static void main(String[] args) throws Exception { | |||
if (args.length > 1) { | |||
new Test().bar(3); | |||
return; | |||
} | |||
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); } | |||
} |
@@ -1108,6 +1108,7 @@ public class JvstTest extends JvstTestRoot { | |||
suite.addTestSuite(JvstTest2.class); | |||
suite.addTestSuite(JvstTest3.class); | |||
suite.addTestSuite(JvstTest4.class); | |||
suite.addTestSuite(JvstTest5.class); | |||
suite.addTestSuite(LoaderTestByRandall.class); | |||
suite.addTestSuite(javassist.bytecode.BytecodeTest.class); | |||
suite.addTestSuite(javassist.bytecode.StackMapTest.class); |
@@ -5,7 +5,6 @@ import java.io.File; | |||
import java.io.FileInputStream; | |||
import java.io.FileOutputStream; | |||
import java.util.HashSet; | |||
import java.util.List; | |||
import javassist.bytecode.*; | |||
import javassist.bytecode.annotation.Annotation; | |||
@@ -662,8 +661,9 @@ public class JvstTest4 extends JvstTestRoot { | |||
long t2 = endTime2 - endTime; | |||
long t3 = endTime3 - endTime2; | |||
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 { |
@@ -0,0 +1,34 @@ | |||
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")); | |||
} | |||
} |
@@ -0,0 +1,19 @@ | |||
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); | |||
} | |||
} |