From e4018bc52f6b5cd91e1c7c0daf5db11a5d7f4b05 Mon Sep 17 00:00:00 2001 From: Brian Alliet Date: Tue, 17 Apr 2018 10:04:05 -0400 Subject: [PATCH 01/12] Rebase all J8 changes to new HEAD This subsumes the following commits from the old pull request: commit b28f85d08eaa3494919fae7a1b5da9c63115c50e Author: Brian Alliet Date: Thu Mar 15 14:52:06 2018 -0400 Generate bridge methods if FLAG_BRIDGES is specified for altMetafactory This isn't that well tested (and could use some tests). The only compiler I've found that actually generates these is ejc. This works for the one example (involving covariant return types) I have though. commit 3579b023ad0efb100d663441d7b33e362d3f85f5 Author: Brian Alliet Date: Tue Jan 16 11:41:15 2018 -0500 Support LambdaMetaFactory.altMetaFactory (in addition to metaFactory) This adds a few extra pieces of info for the runtime generated class (extra interfaces, bridge methods, and a serializable bit) commit 003a0537a393cc0ef755e8ea0fc8270d5934bfb1 Author: Brian Alliet Date: Tue Feb 28 09:39:02 2017 -0500 Wire up LambdaMetaFactory to src/soot/asm/AsmMethodSource We rewrite invokedynamic 'calls' to LambdaMetaFactory to normal invokestatic calls to the bootstrap method of the thunk created by our LambdaMetaFactory models. We *probably* want to keep these invokedynamic instructions actually, just with some special modeled target or something... I'm not sure. Doing it this way means nobody else needs to know anything about invokedynamic now. commit 027281fbe28c7c048ebed457b2980db7d76fbe7f Author: Brian Alliet Date: Tue Feb 28 09:34:30 2017 -0500 Add 'LambdaMetaFactory' (maybe this should be renamed) which implements the 'modeling' of java.lang.invoke.LambdaMetaFactory. We model this by making a real 'thunk' class which implements the functional interface of the lambda and dispatches to the real implementation. This is sort of like what RetroLambda does. This isn't wired up to the rest of soot yet. --- src/main/java/soot/LambdaMetaFactory.java | 298 ++++++++++++++++++++ src/main/java/soot/asm/AsmMethodSource.java | 26 +- 2 files changed, 319 insertions(+), 5 deletions(-) create mode 100644 src/main/java/soot/LambdaMetaFactory.java diff --git a/src/main/java/soot/LambdaMetaFactory.java b/src/main/java/soot/LambdaMetaFactory.java new file mode 100644 index 00000000000..7e8b48953ef --- /dev/null +++ b/src/main/java/soot/LambdaMetaFactory.java @@ -0,0 +1,298 @@ +/* Soot - a J*va Optimization Framework + * Copyright (C) 1997-2014 Raja Vallee-Rai and others + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +package soot; + +import soot.jimple.*; +import soot.coffi.Util; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +public final class LambdaMetaFactory { + public static SootMethodRef makeLambdaHelper( + List bootstrapArgs, + int tag, + String name, + Type[] types) { + if(bootstrapArgs.size() < 3 || + !(bootstrapArgs.get(0) instanceof ClassConstant) || + !(bootstrapArgs.get(1) instanceof MethodHandle) || + !(bootstrapArgs.get(2) instanceof ClassConstant) || + (bootstrapArgs.size() > 3 && !(bootstrapArgs.get(3) instanceof IntConstant))) { + G.v().out.println("warning: LambdaMetaFactory: unexpected arguments for LambdaMetaFactor.metaFactory"); + return null; + } + String samMethodType = ((ClassConstant) bootstrapArgs.get(0)).getValue(); + SootMethodRef implMethod = ((MethodHandle) bootstrapArgs.get(1)).getMethodRef(); + String instantiatedMethodType = ((ClassConstant) bootstrapArgs.get(2)).getValue(); + if(!samMethodType.equals(instantiatedMethodType)) { + G.v().out.println("warning: LambdaMetaFactory: " + + samMethodType + " != " + instantiatedMethodType); + return null; + } + int flags = 0; + if(bootstrapArgs.size() > 3) + flags = ((IntConstant) bootstrapArgs.get(3)).value; + boolean serializable = (flags & 1 /* FLAGS_SERIALIZABLE */) != 0; + List markerInterfaces = new ArrayList(); + List bridges = new ArrayList(); + int va = 4; + if((flags & 2 /* FLAG_MARKERS */) != 0) { + if(va == bootstrapArgs.size() || !(bootstrapArgs.get(va) instanceof IntConstant)) { + G.v().out.println("warning: LambdaMetaFactory: unexpected arguments for LambdaMetaFactory.altMetaFactory"); + return null; + } + int count = ((IntConstant) bootstrapArgs.get(va++)).value; + for(int i=0;i= bootstrapArgs.size()) { + G.v().out.println("warning: LambdaMetaFactory: unexpected arguments for LambdaMetaFactory.altMetaFactory"); + return null; + } + Value v = bootstrapArgs.get(va++); + if(!(v instanceof ClassConstant)) { + G.v().out.println("warning: LambdaMetaFactory: unexpected arguments for LambdaMetaFactory.altMetaFactory"); + return null; + } + markerInterfaces.add((ClassConstant)v); + } + } + if((flags & 4 /* FLAG_BRIDGES */) != 0) { + if(va == bootstrapArgs.size() || !(bootstrapArgs.get(va) instanceof IntConstant)) { + G.v().out.println("warning: LambdaMetaFactory: unexpected arguments for LambdaMetaFactory.altMetaFactory"); + return null; + } + int count = ((IntConstant) bootstrapArgs.get(va++)).value; + for(int i=0;i= bootstrapArgs.size()) { + G.v().out.println("warning: LambdaMetaFactory: unexpected arguments for LambdaMetaFactory.altMetaFactory"); + return null; + } + Value v = bootstrapArgs.get(va++); + if(!(v instanceof ClassConstant)) { + G.v().out.println("warning: LambdaMetaFactory: unexpected arguments for LambdaMetaFactory.altMetaFactory"); + return null; + } + bridges.add((ClassConstant)v); + } + } + + List capTypes = Arrays.asList(types).subList(0, types.length - 1); + if(!(types[types.length-1] instanceof RefType)) { + G.v().out.println("unexpected interface type: " + types[types.length-1]); + return null; + } + SootClass iface = ((RefType) types[types.length-1]).getSootClass(); + + // Our thunk class implements the functional interface + String className = "soot.dummy." + implMethod.name() + "$" + uniqSupply(); + G.v().out.println("dummy class name " + className); + SootClass tclass = new SootClass(className); + tclass.addInterface(iface); + if(serializable) + tclass.addInterface(RefType.v("java.io.Serializable").getSootClass()); + for(int i=0;i capFields = new ArrayList(capTypes.size()); + for(int i=0;i paramTypes = Arrays.asList(samTypes).subList(0, samTypes.length - 1); + Type retType = samTypes[samTypes.length - 1]; + ms = new ThunkMethodSource(capFields, paramTypes, retType, implMethod); + } + + // Bootstrap method creates a new instance of this class + SootMethod tboot = new SootMethod( + "bootstrap$", + capTypes, + iface.getType(), + Modifier.STATIC + ); + tclass.addMethod(tboot); + tboot.setSource(ms); + + // Constructor just copies the captures + SootMethod tctor = new SootMethod( + "", + capTypes, + VoidType.v()); + tclass.addMethod(tctor); + tctor.setSource(ms); + + // Dispatch runs the 'real' method implementing the body of the lambda + SootMethod tdispatch = makeBridge(name, samMethodType, capFields, implMethod); + tclass.addMethod(tdispatch); + + for(int i=0;i capFields, + SootMethodRef implMethod) + { + Type[] samTypes = Util.v().jimpleTypesOfFieldOrMethodDescriptor(samMethodType); + List paramTypes = Arrays.asList(samTypes).subList(0, samTypes.length - 1); + Type retType = samTypes[samTypes.length - 1]; + + MethodSource ms = new ThunkMethodSource(capFields, paramTypes, retType, implMethod); + + SootMethod m = new SootMethod( + name, + paramTypes, + retType); + m.setSource(ms); + return m; + } + + private static class ThunkMethodSource implements MethodSource { + private List capFields; + private List paramTypes; + private Type retType; + private SootMethodRef implMethod; + + public ThunkMethodSource(List capFields, List paramTypes, Type retType, SootMethodRef implMethod) { + this.capFields = capFields; + this.paramTypes = paramTypes; + this.retType = retType; + this.implMethod = implMethod; + } + + public Body getBody(SootMethod m, String phaseName) { + if(!phaseName.equals("jb")) + throw new Error("unsupported body type: " + phaseName); // FIXME? + + SootClass tclass = m.getDeclaringClass(); + JimpleBody jb = Jimple.v().newBody(m); + PatchingChain us = jb.getUnits(); + + if(m.getName().equals("")) { + Local l = Jimple.v().newLocal("r", tclass.getType()); + us.add(Jimple.v().newIdentityStmt( + l, + Jimple.v().newThisRef(tclass.getType()) + )); + us.add(Jimple.v().newInvokeStmt( + Jimple.v().newSpecialInvokeExpr( + l, + Scene.v().makeConstructorRef( + Scene.v().getObjectType().getSootClass(), Collections.emptyList() + ), + Collections.emptyList()) + )); + for(SootField f : capFields) { + int i = us.size() - 2; + Local l2 = Jimple.v().newLocal("c" + i, f.getType()); + us.add(Jimple.v().newIdentityStmt( + l2, + Jimple.v().newParameterRef(f.getType(), i) + )); + us.add(Jimple.v().newAssignStmt( + Jimple.v().newInstanceFieldRef(l, f.makeRef()), + l2 + )); + } + us.add(Jimple.v().newReturnVoidStmt()); + } else if(m.getName().equals("bootstrap$")) { + Local l = Jimple.v().newLocal("r", tclass.getType()); + Value val = Jimple.v().newNewExpr(tclass.getType()); + List capVals = new ArrayList(); + us.add(Jimple.v().newAssignStmt(l, val)); + us.add(Jimple.v().newInvokeStmt( + Jimple.v().newSpecialInvokeExpr( + l, + Scene.v().makeConstructorRef(tclass, Collections.emptyList()), + Collections.emptyList()) + )); + us.add(Jimple.v().newReturnStmt(l)); + } else { + Local this_ = Jimple.v().newLocal("r", tclass.getType()); + us.add(Jimple.v().newIdentityStmt( + this_, + Jimple.v().newThisRef(tclass.getType()) + )); + + List args = new ArrayList(); + + for(SootField f : capFields) { + int i = args.size(); + Local l = Jimple.v().newLocal("c" + i, f.getType()); + us.add(Jimple.v().newAssignStmt( + l, + Jimple.v().newInstanceFieldRef(this_, f.makeRef()) + )); + args.add(l); + } + + for(Type ty : paramTypes) { + int i = args.size(); + Local l = Jimple.v().newLocal("a" + i, ty); + us.add(Jimple.v().newIdentityStmt( + l, + Jimple.v().newParameterRef(ty, i) + )); + args.add(l); + } + + Local ret = Jimple.v().newLocal("r", retType); + us.add(Jimple.v().newAssignStmt( + ret, + Jimple.v().newStaticInvokeExpr(implMethod, args) + )); + us.add(Jimple.v().newReturnStmt(ret)); + } + return jb; + } + } + + // FIXME: Move to 'G'? + private static int uniq; + private static synchronized long uniqSupply() { + return ++uniq; + } +} diff --git a/src/main/java/soot/asm/AsmMethodSource.java b/src/main/java/soot/asm/AsmMethodSource.java index bac2b42aa06..a0147af260b 100644 --- a/src/main/java/soot/asm/AsmMethodSource.java +++ b/src/main/java/soot/asm/AsmMethodSource.java @@ -83,6 +83,7 @@ import soot.DoubleType; import soot.FloatType; import soot.IntType; +import soot.LambdaMetaFactory; import soot.Local; import soot.LongType; import soot.MethodSource; @@ -1274,13 +1275,28 @@ private void convertInvokeDynamicInsn(InvokeDynamicInsnNode insn) { Collections.reverse(parameterTypes); } returnType = types[types.length - 1]; + + SootMethodRef bootstrap_model = null; + + + String bsmMethodRefStr = bsmMethodRef.toString(); + if(bsmMethodRefStr.equals("") + || bsmMethodRefStr.equals("")) + bootstrap_model = LambdaMetaFactory.makeLambdaHelper(bsmMethodArgs, insn.bsm.getTag(), insn.name, types); - // we always model invokeDynamic method refs as static method references - // of methods on the type SootClass.INVOKEDYNAMIC_DUMMY_CLASS_NAME - SootMethodRef methodRef = Scene.v().makeMethodRef(bclass, insn.name, parameterTypes, returnType, true); + InvokeExpr indy; + + if(bootstrap_model != null) { + indy = Jimple.v().newStaticInvokeExpr(bootstrap_model, methodArgs); + } else { + // we always model invokeDynamic method refs as static method references + // of methods on the type SootClass.INVOKEDYNAMIC_DUMMY_CLASS_NAME + SootMethodRef methodRef = Scene.v().makeMethodRef(bclass, insn.name, parameterTypes, returnType, true); - DynamicInvokeExpr indy = Jimple.v().newDynamicInvokeExpr(bsmMethodRef, - bsmMethodArgs, methodRef, insn.bsm.getTag(), methodArgs); + indy = Jimple.v().newDynamicInvokeExpr(bsmMethodRef, + bsmMethodArgs, methodRef, insn.bsm.getTag(), methodArgs); + } + if (boxes != null) { for (int i = 0; i < types.length - 1; i++) { boxes[i] = indy.getArgBox(i); From bb655d2eada1ef16b007117254e77313f2fd9662 Mon Sep 17 00:00:00 2001 From: Manuel Benz Date: Wed, 31 Oct 2018 15:57:31 +0100 Subject: [PATCH 02/12] Applied formatting. Converted G.out calls to logging --- src/main/java/soot/LambdaMetaFactory.java | 470 ++++++++++------------ 1 file changed, 213 insertions(+), 257 deletions(-) diff --git a/src/main/java/soot/LambdaMetaFactory.java b/src/main/java/soot/LambdaMetaFactory.java index 7e8b48953ef..c4acc6c50db 100644 --- a/src/main/java/soot/LambdaMetaFactory.java +++ b/src/main/java/soot/LambdaMetaFactory.java @@ -18,281 +18,237 @@ */ package soot; -import soot.jimple.*; -import soot.coffi.Util; - import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import soot.coffi.Util; +import soot.jimple.ClassConstant; +import soot.jimple.IntConstant; +import soot.jimple.Jimple; +import soot.jimple.JimpleBody; +import soot.jimple.MethodHandle; +import soot.jimple.MethodType; + public final class LambdaMetaFactory { - public static SootMethodRef makeLambdaHelper( - List bootstrapArgs, - int tag, - String name, - Type[] types) { - if(bootstrapArgs.size() < 3 || - !(bootstrapArgs.get(0) instanceof ClassConstant) || - !(bootstrapArgs.get(1) instanceof MethodHandle) || - !(bootstrapArgs.get(2) instanceof ClassConstant) || - (bootstrapArgs.size() > 3 && !(bootstrapArgs.get(3) instanceof IntConstant))) { - G.v().out.println("warning: LambdaMetaFactory: unexpected arguments for LambdaMetaFactor.metaFactory"); - return null; - } - String samMethodType = ((ClassConstant) bootstrapArgs.get(0)).getValue(); - SootMethodRef implMethod = ((MethodHandle) bootstrapArgs.get(1)).getMethodRef(); - String instantiatedMethodType = ((ClassConstant) bootstrapArgs.get(2)).getValue(); - if(!samMethodType.equals(instantiatedMethodType)) { - G.v().out.println("warning: LambdaMetaFactory: " + - samMethodType + " != " + instantiatedMethodType); - return null; - } - int flags = 0; - if(bootstrapArgs.size() > 3) - flags = ((IntConstant) bootstrapArgs.get(3)).value; - boolean serializable = (flags & 1 /* FLAGS_SERIALIZABLE */) != 0; - List markerInterfaces = new ArrayList(); - List bridges = new ArrayList(); - int va = 4; - if((flags & 2 /* FLAG_MARKERS */) != 0) { - if(va == bootstrapArgs.size() || !(bootstrapArgs.get(va) instanceof IntConstant)) { - G.v().out.println("warning: LambdaMetaFactory: unexpected arguments for LambdaMetaFactory.altMetaFactory"); - return null; - } - int count = ((IntConstant) bootstrapArgs.get(va++)).value; - for(int i=0;i= bootstrapArgs.size()) { - G.v().out.println("warning: LambdaMetaFactory: unexpected arguments for LambdaMetaFactory.altMetaFactory"); - return null; - } - Value v = bootstrapArgs.get(va++); - if(!(v instanceof ClassConstant)) { - G.v().out.println("warning: LambdaMetaFactory: unexpected arguments for LambdaMetaFactory.altMetaFactory"); - return null; - } - markerInterfaces.add((ClassConstant)v); - } - } - if((flags & 4 /* FLAG_BRIDGES */) != 0) { - if(va == bootstrapArgs.size() || !(bootstrapArgs.get(va) instanceof IntConstant)) { - G.v().out.println("warning: LambdaMetaFactory: unexpected arguments for LambdaMetaFactory.altMetaFactory"); - return null; - } - int count = ((IntConstant) bootstrapArgs.get(va++)).value; - for(int i=0;i= bootstrapArgs.size()) { - G.v().out.println("warning: LambdaMetaFactory: unexpected arguments for LambdaMetaFactory.altMetaFactory"); - return null; - } - Value v = bootstrapArgs.get(va++); - if(!(v instanceof ClassConstant)) { - G.v().out.println("warning: LambdaMetaFactory: unexpected arguments for LambdaMetaFactory.altMetaFactory"); - return null; - } - bridges.add((ClassConstant)v); - } - } - - List capTypes = Arrays.asList(types).subList(0, types.length - 1); - if(!(types[types.length-1] instanceof RefType)) { - G.v().out.println("unexpected interface type: " + types[types.length-1]); - return null; + // FIXME: Move to 'G'? + private static int uniq; + private static final Logger LOGGER = LoggerFactory.getLogger(LambdaMetaFactory.class); + + public static SootMethodRef makeLambdaHelper(List bootstrapArgs, int tag, String name, Type[] types) { + if (bootstrapArgs.size() < 3 || !(bootstrapArgs.get(0) instanceof ClassConstant) + || !(bootstrapArgs.get(1) instanceof MethodHandle) || !(bootstrapArgs.get(2) instanceof ClassConstant) + || (bootstrapArgs.size() > 3 && !(bootstrapArgs.get(3) instanceof IntConstant))) { + LOGGER.warn("LambdaMetaFactory: unexpected arguments for LambdaMetaFactor.metaFactory: {}", bootstrapArgs); + return null; + } + String samMethodType = ((ClassConstant) bootstrapArgs.get(0)).getValue(); + SootMethodRef implMethod = ((MethodHandle) bootstrapArgs.get(1)).getMethodRef(); + String instantiatedMethodType = ((ClassConstant) bootstrapArgs.get(2)).getValue(); + if (!samMethodType.equals(instantiatedMethodType)) { + G.v().out.println("warning: LambdaMetaFactory: " + samMethodType + " != " + instantiatedMethodType); + return null; + } + int flags = 0; + if (bootstrapArgs.size() > 3) + flags = ((IntConstant) bootstrapArgs.get(3)).value; + boolean serializable = (flags & 1 /* FLAGS_SERIALIZABLE */) != 0; + List markerInterfaces = new ArrayList(); + List bridges = new ArrayList(); + int va = 4; + if ((flags & 2 /* FLAG_MARKERS */) != 0) { + if (va == bootstrapArgs.size() || !(bootstrapArgs.get(va) instanceof IntConstant)) { + LOGGER.warn("LambdaMetaFactory: unexpected arguments for LambdaMetaFactory.altMetaFactory"); + return null; + } + int count = ((IntConstant) bootstrapArgs.get(va++)).value; + for (int i = 0; i < count; i++) { + if (va >= bootstrapArgs.size()) { + LOGGER.warn("LambdaMetaFactory: unexpected arguments for LambdaMetaFactory.altMetaFactory"); + return null; } - SootClass iface = ((RefType) types[types.length-1]).getSootClass(); - - // Our thunk class implements the functional interface - String className = "soot.dummy." + implMethod.name() + "$" + uniqSupply(); - G.v().out.println("dummy class name " + className); - SootClass tclass = new SootClass(className); - tclass.addInterface(iface); - if(serializable) - tclass.addInterface(RefType.v("java.io.Serializable").getSootClass()); - for(int i=0;i capFields = new ArrayList(capTypes.size()); - for(int i=0;i paramTypes = Arrays.asList(samTypes).subList(0, samTypes.length - 1); - Type retType = samTypes[samTypes.length - 1]; - ms = new ThunkMethodSource(capFields, paramTypes, retType, implMethod); + markerInterfaces.add((ClassConstant) v); + } + } + if ((flags & 4 /* FLAG_BRIDGES */) != 0) { + if (va == bootstrapArgs.size() || !(bootstrapArgs.get(va) instanceof IntConstant)) { + LOGGER.warn("LambdaMetaFactory: unexpected arguments for LambdaMetaFactory.altMetaFactory"); + return null; + } + int count = ((IntConstant) bootstrapArgs.get(va++)).value; + for (int i = 0; i < count; i++) { + if (va >= bootstrapArgs.size()) { + LOGGER.warn("LambdaMetaFactory: unexpected arguments for LambdaMetaFactory.altMetaFactory"); + return null; } - - // Bootstrap method creates a new instance of this class - SootMethod tboot = new SootMethod( - "bootstrap$", - capTypes, - iface.getType(), - Modifier.STATIC - ); - tclass.addMethod(tboot); - tboot.setSource(ms); - - // Constructor just copies the captures - SootMethod tctor = new SootMethod( - "", - capTypes, - VoidType.v()); - tclass.addMethod(tctor); - tctor.setSource(ms); - - // Dispatch runs the 'real' method implementing the body of the lambda - SootMethod tdispatch = makeBridge(name, samMethodType, capFields, implMethod); - tclass.addMethod(tdispatch); - - for(int i=0;i capTypes = Arrays.asList(types).subList(0, types.length - 1); + if (!(types[types.length - 1] instanceof RefType)) { + LOGGER.warn("unexpected interface type: " + types[types.length - 1]); + return null; + } + SootClass iface = ((RefType) types[types.length - 1]).getSootClass(); + + // Our thunk class implements the functional interface + String className = "soot.dummy." + implMethod.name() + "$" + uniqSupply(); + LOGGER.debug("dummy class name " + className); + SootClass tclass = new SootClass(className); + tclass.addInterface(iface); + if (serializable) + tclass.addInterface(RefType.v("java.io.Serializable").getSootClass()); + for (int i = 0; i < markerInterfaces.size(); i++) + tclass.addInterface(RefType.v(markerInterfaces.get(i).getValue()).getSootClass()); + + // It contains fields for all the captures in the lambda + List capFields = new ArrayList(capTypes.size()); + for (int i = 0; i < capTypes.size(); i++) { + SootField f = new SootField("cap" + i, capTypes.get(i), 0); + capFields.add(f); + tclass.addField(f); } - - private static SootMethod makeBridge( - String name, - String samMethodType, - List capFields, - SootMethodRef implMethod) + + MethodSource ms; { - Type[] samTypes = Util.v().jimpleTypesOfFieldOrMethodDescriptor(samMethodType); - List paramTypes = Arrays.asList(samTypes).subList(0, samTypes.length - 1); - Type retType = samTypes[samTypes.length - 1]; - - MethodSource ms = new ThunkMethodSource(capFields, paramTypes, retType, implMethod); - - SootMethod m = new SootMethod( - name, - paramTypes, - retType); - m.setSource(ms); - return m; + // FIXME? some duplication from makeBridge... probably should split this into + // two different MethodSources + Type[] samTypes = Util.v().jimpleTypesOfFieldOrMethodDescriptor(samMethodType); + List paramTypes = Arrays.asList(samTypes).subList(0, samTypes.length - 1); + Type retType = samTypes[samTypes.length - 1]; + ms = new ThunkMethodSource(capFields, paramTypes, retType, implMethod); } - - private static class ThunkMethodSource implements MethodSource { - private List capFields; - private List paramTypes; - private Type retType; - private SootMethodRef implMethod; - - public ThunkMethodSource(List capFields, List paramTypes, Type retType, SootMethodRef implMethod) { - this.capFields = capFields; - this.paramTypes = paramTypes; - this.retType = retType; - this.implMethod = implMethod; - } - public Body getBody(SootMethod m, String phaseName) { - if(!phaseName.equals("jb")) - throw new Error("unsupported body type: " + phaseName); // FIXME? + // Bootstrap method creates a new instance of this class + SootMethod tboot = new SootMethod("bootstrap$", capTypes, iface.getType(), Modifier.STATIC); + tclass.addMethod(tboot); + tboot.setSource(ms); + + // Constructor just copies the captures + SootMethod tctor = new SootMethod("", capTypes, VoidType.v()); + tclass.addMethod(tctor); + tctor.setSource(ms); - SootClass tclass = m.getDeclaringClass(); - JimpleBody jb = Jimple.v().newBody(m); - PatchingChain us = jb.getUnits(); + // Dispatch runs the 'real' method implementing the body of the lambda + SootMethod tdispatch = makeBridge(name, samMethodType, capFields, implMethod); + tclass.addMethod(tdispatch); - if(m.getName().equals("")) { - Local l = Jimple.v().newLocal("r", tclass.getType()); - us.add(Jimple.v().newIdentityStmt( - l, - Jimple.v().newThisRef(tclass.getType()) - )); - us.add(Jimple.v().newInvokeStmt( - Jimple.v().newSpecialInvokeExpr( - l, - Scene.v().makeConstructorRef( - Scene.v().getObjectType().getSootClass(), Collections.emptyList() - ), - Collections.emptyList()) - )); - for(SootField f : capFields) { - int i = us.size() - 2; - Local l2 = Jimple.v().newLocal("c" + i, f.getType()); - us.add(Jimple.v().newIdentityStmt( - l2, - Jimple.v().newParameterRef(f.getType(), i) - )); - us.add(Jimple.v().newAssignStmt( - Jimple.v().newInstanceFieldRef(l, f.makeRef()), - l2 - )); - } - us.add(Jimple.v().newReturnVoidStmt()); - } else if(m.getName().equals("bootstrap$")) { - Local l = Jimple.v().newLocal("r", tclass.getType()); - Value val = Jimple.v().newNewExpr(tclass.getType()); - List capVals = new ArrayList(); - us.add(Jimple.v().newAssignStmt(l, val)); - us.add(Jimple.v().newInvokeStmt( - Jimple.v().newSpecialInvokeExpr( - l, - Scene.v().makeConstructorRef(tclass, Collections.emptyList()), - Collections.emptyList()) - )); - us.add(Jimple.v().newReturnStmt(l)); - } else { - Local this_ = Jimple.v().newLocal("r", tclass.getType()); - us.add(Jimple.v().newIdentityStmt( - this_, - Jimple.v().newThisRef(tclass.getType()) - )); + for (int i = 0; i < bridges.size(); i++) { + SootMethod bridge = makeBridge(name, bridges.get(i).getValue(), capFields, implMethod); + tclass.addMethod(bridge); + } - List args = new ArrayList(); - - for(SootField f : capFields) { - int i = args.size(); - Local l = Jimple.v().newLocal("c" + i, f.getType()); - us.add(Jimple.v().newAssignStmt( - l, - Jimple.v().newInstanceFieldRef(this_, f.makeRef()) - )); - args.add(l); - } + Scene.v().addClass(tclass); - for(Type ty : paramTypes) { - int i = args.size(); - Local l = Jimple.v().newLocal("a" + i, ty); - us.add(Jimple.v().newIdentityStmt( - l, - Jimple.v().newParameterRef(ty, i) - )); - args.add(l); - } - - Local ret = Jimple.v().newLocal("r", retType); - us.add(Jimple.v().newAssignStmt( - ret, - Jimple.v().newStaticInvokeExpr(implMethod, args) - )); - us.add(Jimple.v().newReturnStmt(ret)); - } - return jb; - } + // FIXME remove debug stuff + System.out.println(tboot); + + java.io.PrintWriter pw = new java.io.PrintWriter(System.out); + Printer.v().printTo(tclass, pw); + pw.close(); + + return tboot.makeRef(); + } + + private static SootMethod makeBridge(String name, String samMethodType, List capFields, + SootMethodRef implMethod) { + Type[] samTypes = Util.v().jimpleTypesOfFieldOrMethodDescriptor(samMethodType); + List paramTypes = Arrays.asList(samTypes).subList(0, samTypes.length - 1); + Type retType = samTypes[samTypes.length - 1]; + + MethodSource ms = new ThunkMethodSource(capFields, paramTypes, retType, implMethod); + + SootMethod m = new SootMethod(name, paramTypes, retType); + m.setSource(ms); + return m; + } + + private static synchronized long uniqSupply() { + return ++uniq; + } + + private static class ThunkMethodSource implements MethodSource { + private List capFields; + private List paramTypes; + private Type retType; + private SootMethodRef implMethod; + + public ThunkMethodSource(List capFields, List paramTypes, Type retType, SootMethodRef implMethod) { + this.capFields = capFields; + this.paramTypes = paramTypes; + this.retType = retType; + this.implMethod = implMethod; } - - // FIXME: Move to 'G'? - private static int uniq; - private static synchronized long uniqSupply() { - return ++uniq; + + public Body getBody(SootMethod m, String phaseName) { + if (!phaseName.equals("jb")) + throw new Error("unsupported body type: " + phaseName); // FIXME? + + SootClass tclass = m.getDeclaringClass(); + JimpleBody jb = Jimple.v().newBody(m); + PatchingChain us = jb.getUnits(); + + if (m.getName().equals("")) { + Local l = Jimple.v().newLocal("r", tclass.getType()); + us.add(Jimple.v().newIdentityStmt(l, Jimple.v().newThisRef(tclass.getType()))); + us.add(Jimple.v() + .newInvokeStmt(Jimple.v().newSpecialInvokeExpr(l, + Scene.v().makeConstructorRef(Scene.v().getObjectType().getSootClass(), Collections.emptyList()), + Collections.emptyList()))); + for (SootField f : capFields) { + int i = us.size() - 2; + Local l2 = Jimple.v().newLocal("c" + i, f.getType()); + us.add(Jimple.v().newIdentityStmt(l2, Jimple.v().newParameterRef(f.getType(), i))); + us.add(Jimple.v().newAssignStmt(Jimple.v().newInstanceFieldRef(l, f.makeRef()), l2)); + } + us.add(Jimple.v().newReturnVoidStmt()); + } else if (m.getName().equals("bootstrap$")) { + Local l = Jimple.v().newLocal("r", tclass.getType()); + Value val = Jimple.v().newNewExpr(tclass.getType()); + List capVals = new ArrayList(); + us.add(Jimple.v().newAssignStmt(l, val)); + us.add(Jimple.v().newInvokeStmt(Jimple.v().newSpecialInvokeExpr(l, + Scene.v().makeConstructorRef(tclass, Collections.emptyList()), Collections.emptyList()))); + us.add(Jimple.v().newReturnStmt(l)); + } else { + Local this_ = Jimple.v().newLocal("r", tclass.getType()); + us.add(Jimple.v().newIdentityStmt(this_, Jimple.v().newThisRef(tclass.getType()))); + + List args = new ArrayList(); + + for (SootField f : capFields) { + int i = args.size(); + Local l = Jimple.v().newLocal("c" + i, f.getType()); + us.add(Jimple.v().newAssignStmt(l, Jimple.v().newInstanceFieldRef(this_, f.makeRef()))); + args.add(l); + } + + for (Type ty : paramTypes) { + int i = args.size(); + Local l = Jimple.v().newLocal("a" + i, ty); + us.add(Jimple.v().newIdentityStmt(l, Jimple.v().newParameterRef(ty, i))); + args.add(l); + } + + Local ret = Jimple.v().newLocal("r", retType); + us.add(Jimple.v().newAssignStmt(ret, Jimple.v().newStaticInvokeExpr(implMethod, args))); + us.add(Jimple.v().newReturnStmt(ret)); + } + return jb; } + } } From 14248f411e4bfcd4ff3f63f0370c5e050a8516b1 Mon Sep 17 00:00:00 2001 From: Manuel Benz Date: Wed, 31 Oct 2018 15:58:38 +0100 Subject: [PATCH 03/12] Moves method for building signatures to AbstractTestingFramework. --- .../instructions/DexByteCodeInstrutionsTest.java | 14 ++++++-------- .../framework/AbstractTestingFramework.java | 5 ++++- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/systemTest/java/soot/dexpler/instructions/DexByteCodeInstrutionsTest.java b/src/systemTest/java/soot/dexpler/instructions/DexByteCodeInstrutionsTest.java index d7eac495c28..4c27fdc2364 100644 --- a/src/systemTest/java/soot/dexpler/instructions/DexByteCodeInstrutionsTest.java +++ b/src/systemTest/java/soot/dexpler/instructions/DexByteCodeInstrutionsTest.java @@ -84,7 +84,7 @@ private String targetDexPath() { @Test public void InvokePolymorphic1() { final SootMethod testTarget = prepareTarget( - methodSig(TARGET_CLASS, "void invokePolymorphicTarget(java.lang.invoke.MethodHandle)"), TARGET_CLASS); + methodSigFromComponents(TARGET_CLASS, "void invokePolymorphicTarget(java.lang.invoke.MethodHandle)"), TARGET_CLASS); // We model invokePolymorphic as invokeVirtual final List invokes = invokesFromMethod(testTarget); @@ -92,12 +92,14 @@ public void InvokePolymorphic1() { final InvokeExpr invokePoly = invokes.get(0); Assert.assertTrue(invokePoly instanceof VirtualInvokeExpr); final SootMethodRef targetMethodRef = invokePoly.getMethodRef(); - Assert.assertEquals(methodSig(METHOD_HANDLE_CLASS, METHOD_HANDLE_INVOKE_SUBSIG), targetMethodRef.getSignature()); + Assert.assertEquals(methodSigFromComponents(METHOD_HANDLE_CLASS, METHOD_HANDLE_INVOKE_SUBSIG), + targetMethodRef.getSignature()); } @Test public void InvokeCustom1() { - final SootMethod testTarget = prepareTarget(methodSig(TARGET_CLASS, "void invokeCustomTarget()"), TARGET_CLASS); + final SootMethod testTarget + = prepareTarget(methodSigFromComponents(TARGET_CLASS, "void invokeCustomTarget()"), TARGET_CLASS); // We model invokeCustom as invokeDynamic final List invokes = invokesFromMethod(testTarget); @@ -105,7 +107,7 @@ public void InvokeCustom1() { final InvokeExpr invokeCustom = invokes.get(0); Assert.assertTrue(invokeCustom instanceof DynamicInvokeExpr); final SootMethodRef targetMethodRef = invokeCustom.getMethodRef(); - Assert.assertEquals(methodSig(SootClass.INVOKEDYNAMIC_DUMMY_CLASS_NAME, SUPPLIER_GET_SUBSIG), + Assert.assertEquals(methodSigFromComponents(SootClass.INVOKEDYNAMIC_DUMMY_CLASS_NAME, SUPPLIER_GET_SUBSIG), targetMethodRef.getSignature()); final String callToLambdaMethaFactory = "dynamicinvoke \"get\" () (methodtype: java.lang.Object __METHODTYPE__(), methodhandle: \"REF_INVOKE_STATIC\" , methodtype: java.lang.String __METHODTYPE__())"; @@ -118,8 +120,4 @@ private List invokesFromMethod(SootMethod testTarget) { .collect(Collectors.toList()); } - private String methodSig(String clazz, String subsig) { - return String.format("<%s: %s>", clazz, subsig); - } - } diff --git a/src/systemTest/java/soot/testing/framework/AbstractTestingFramework.java b/src/systemTest/java/soot/testing/framework/AbstractTestingFramework.java index 1a69bf86ee2..b9a5492d53b 100644 --- a/src/systemTest/java/soot/testing/framework/AbstractTestingFramework.java +++ b/src/systemTest/java/soot/testing/framework/AbstractTestingFramework.java @@ -41,7 +41,6 @@ import soot.Scene; import soot.SootClass; import soot.SootMethod; -import soot.SootResolver; import soot.Type; import soot.VoidType; import soot.jimple.Jimple; @@ -57,6 +56,10 @@ public abstract class AbstractTestingFramework { private static final String SYSTEMTEST_TARGET_CLASSES_DIR = "target/systemTest-target-classes"; + public static String methodSigFromComponents(String clazz, String subsig) { + return String.format("<%s: %s>", clazz, subsig); + } + /** * Sets up the Scene by analyzing all included classes and generating a call graph for the given target. This is done by * generating an entry point that calls the method with the given signature. From da82cc67785eeacf8c0e93a181c367ce227ab299 Mon Sep 17 00:00:00 2001 From: Manuel Benz Date: Wed, 31 Oct 2018 18:02:27 +0100 Subject: [PATCH 04/12] Formating of ASMMethodsource --- src/main/java/soot/asm/AsmMethodSource.java | 147 ++++++++++---------- 1 file changed, 71 insertions(+), 76 deletions(-) diff --git a/src/main/java/soot/asm/AsmMethodSource.java b/src/main/java/soot/asm/AsmMethodSource.java index 60bbc3fa5a3..54ebfa04366 100644 --- a/src/main/java/soot/asm/AsmMethodSource.java +++ b/src/main/java/soot/asm/AsmMethodSource.java @@ -194,7 +194,6 @@ import java.util.Set; import org.objectweb.asm.Handle; -import org.objectweb.asm.Opcodes; import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.FieldInsnNode; import org.objectweb.asm.tree.IincInsnNode; @@ -255,7 +254,6 @@ import soot.jimple.Constant; import soot.jimple.DefinitionStmt; import soot.jimple.DoubleConstant; -import soot.jimple.DynamicInvokeExpr; import soot.jimple.FieldRef; import soot.jimple.FloatConstant; import soot.jimple.GotoStmt; @@ -294,7 +292,18 @@ final class AsmMethodSource implements MethodSource { private static final Operand DWORD_DUMMY = new Operand(null, null); - + private static final String METAFACTORY_SIGNATURE + = ""; + private static final String ALT_METAFACTORY_SIGNATURE + = ""; + /* -const fields- */ + private final int maxLocals; + private final InsnList instructions; + private final List localVars; + private final List tryCatchBlocks; + private final Set inlineExceptionLabels = new HashSet(); + private final Map inlineExceptionHandlers = new HashMap(); + private final CastAndReturnInliner castAndReturnInliner = new CastAndReturnInliner(); /* -state fields- */ private int nextLocal; private Map locals; @@ -305,16 +314,8 @@ final class AsmMethodSource implements MethodSource { private Multimap trapHandlers; private JimpleBody body; private int lastLineNumber = -1; - /* -const fields- */ - private final int maxLocals; - private final InsnList instructions; - private final List localVars; - private final List tryCatchBlocks; - - private final Set inlineExceptionLabels = new HashSet(); - private final Map inlineExceptionHandlers = new HashMap(); - - private final CastAndReturnInliner castAndReturnInliner = new CastAndReturnInliner(); + private Table edges; + private ArrayDeque conversionWorklist; AsmMethodSource(int maxLocals, InsnList insns, List localVars, List tryCatchBlocks) { this.maxLocals = maxLocals; @@ -681,6 +682,18 @@ private void convertConstInsn(InsnNode insn) { } } + /* + * Following version is more complex, using stack frames as opposed to simply swapping + */ + /* + * StackFrame frame = getFrame(insn); Operand[] out = frame.out(); Operand dup, dup2 = null, dupd, dupd2 = null; if (out == + * null) { dupd = popImmediate(); dup = new Operand(insn, dupd.stackOrValue()); if (dword) { dupd2 = peek(); if (dupd2 == + * DWORD_DUMMY) { pop(); dupd2 = dupd; } else { dupd2 = popImmediate(); } dup2 = new Operand(insn, dupd2.stackOrValue()); + * frame.out(dup, dup2); frame.in(dupd, dupd2); } else { frame.out(dup); frame.in(dupd); } } else { dupd = pop(); dup = + * out[0]; if (dword) { dupd2 = pop(); if (dupd2 == DWORD_DUMMY) dupd2 = dupd; dup2 = out[1]; frame.mergeIn(dupd, dupd2); } + * else { frame.mergeIn(dupd); } } + */ + private void convertArrayLoadInsn(InsnNode insn) { StackFrame frame = getFrame(insn); Operand[] out = frame.out(); @@ -728,18 +741,6 @@ private void convertArrayStoreInsn(InsnNode insn) { } } - /* - * Following version is more complex, using stack frames as opposed to simply swapping - */ - /* - * StackFrame frame = getFrame(insn); Operand[] out = frame.out(); Operand dup, dup2 = null, dupd, dupd2 = null; if (out == - * null) { dupd = popImmediate(); dup = new Operand(insn, dupd.stackOrValue()); if (dword) { dupd2 = peek(); if (dupd2 == - * DWORD_DUMMY) { pop(); dupd2 = dupd; } else { dupd2 = popImmediate(); } dup2 = new Operand(insn, dupd2.stackOrValue()); - * frame.out(dup, dup2); frame.in(dupd, dupd2); } else { frame.out(dup); frame.in(dupd); } } else { dupd = pop(); dup = - * out[0]; if (dword) { dupd2 = pop(); if (dupd2 == DWORD_DUMMY) dupd2 = dupd; dup2 = out[1]; frame.mergeIn(dupd, dupd2); } - * else { frame.mergeIn(dupd); } } - */ - private void convertDupInsn(InsnNode insn) { int op = insn.getOpcode(); @@ -1406,28 +1407,25 @@ private void convertInvokeDynamicInsn(InvokeDynamicInsnNode insn) { Collections.reverse(parameterTypes); } returnType = types[types.length - 1]; - - SootMethodRef bootstrap_model = null; - - - String bsmMethodRefStr = bsmMethodRef.toString(); - if(bsmMethodRefStr.equals("") - || bsmMethodRefStr.equals("")) - bootstrap_model = LambdaMetaFactory.makeLambdaHelper(bsmMethodArgs, insn.bsm.getTag(), insn.name, types); - - InvokeExpr indy; - - if(bootstrap_model != null) { - indy = Jimple.v().newStaticInvokeExpr(bootstrap_model, methodArgs); - } else { - // we always model invokeDynamic method refs as static method references - // of methods on the type SootClass.INVOKEDYNAMIC_DUMMY_CLASS_NAME - SootMethodRef methodRef = Scene.v().makeMethodRef(bclass, insn.name, parameterTypes, returnType, true); - - indy = Jimple.v().newDynamicInvokeExpr(bsmMethodRef, - bsmMethodArgs, methodRef, insn.bsm.getTag(), methodArgs); - } - + + SootMethodRef bootstrap_model = null; + + String bsmMethodRefStr = bsmMethodRef.toString(); + if (bsmMethodRefStr.equals(METAFACTORY_SIGNATURE) || bsmMethodRefStr.equals(ALT_METAFACTORY_SIGNATURE)) + bootstrap_model = LambdaMetaFactory.makeLambdaHelper(bsmMethodArgs, insn.bsm.getTag(), insn.name, types); + + InvokeExpr indy; + + if (bootstrap_model != null) { + indy = Jimple.v().newStaticInvokeExpr(bootstrap_model, methodArgs); + } else { + // we always model invokeDynamic method refs as static method references + // of methods on the type SootClass.INVOKEDYNAMIC_DUMMY_CLASS_NAME + SootMethodRef methodRef = Scene.v().makeMethodRef(bclass, insn.name, parameterTypes, returnType, true); + + indy = Jimple.v().newDynamicInvokeExpr(bsmMethodRef, bsmMethodArgs, methodRef, insn.bsm.getTag(), methodArgs); + } + if (boxes != null) { for (int i = 0; i < types.length - 1; i++) { boxes[i] = indy.getArgBox(i); @@ -1480,18 +1478,18 @@ private SootMethodRef toSootMethodRef(Handle methodHandle) { SootClass bsmCls = Scene.v().getSootClass(bsmClsName); List bsmSigTypes = AsmUtil.toJimpleDesc(methodHandle.getDesc()); Type returnType = bsmSigTypes.remove(bsmSigTypes.size() - 1); - return Scene.v().makeMethodRef(bsmCls, methodHandle.getName(), bsmSigTypes, returnType, + return Scene.v().makeMethodRef(bsmCls, methodHandle.getName(), bsmSigTypes, returnType, methodHandle.getTag() == MethodHandle.Kind.REF_INVOKE_STATIC.getValue()); } - + private SootFieldRef toSootFieldRef(Handle methodHandle) { String bsmClsName = AsmUtil.toQualifiedName(methodHandle.getOwner()); SootClass bsmCls = Scene.v().getSootClass(bsmClsName); Type t = AsmUtil.toJimpleDesc(methodHandle.getDesc()).get(0); int kind = methodHandle.getTag(); - return Scene.v().makeFieldRef(bsmCls, methodHandle.getName(), t, - kind == MethodHandle.Kind.REF_GET_FIELD_STATIC.getValue() - || kind == MethodHandle.Kind.REF_PUT_FIELD_STATIC.getValue()); + return Scene.v().makeFieldRef(bsmCls, methodHandle.getName(), t, + kind == MethodHandle.Kind.REF_GET_FIELD_STATIC.getValue() + || kind == MethodHandle.Kind.REF_PUT_FIELD_STATIC.getValue()); } private void convertMultiANewArrayInsn(MultiANewArrayInsnNode insn) { @@ -1649,6 +1647,8 @@ private void convertVarInsn(VarInsnNode insn) { } } + /* Conversion */ + private void convertLabel(LabelNode ln) { if (!trapHandlers.containsKey(ln)) { return; @@ -1686,30 +1686,6 @@ private void convertLine(LineNumberNode ln) { lastLineNumber = ln.line; } - /* Conversion */ - - private final class Edge { - /* edge endpoint */ - final AbstractInsnNode insn; - /* previous stacks at edge */ - final LinkedList prevStacks; - /* current stack at edge */ - ArrayList stack; - - Edge(AbstractInsnNode insn, ArrayList stack) { - this.insn = insn; - this.prevStacks = new LinkedList(); - this.stack = stack; - } - - Edge(AbstractInsnNode insn) { - this(insn, new ArrayList(AsmMethodSource.this.stack)); - } - } - - private Table edges; - private ArrayDeque conversionWorklist; - private void addEdges(AbstractInsnNode cur, AbstractInsnNode tgt1, List tgts) { int lastIdx = tgts == null ? -1 : tgts.size() - 1; Operand[] stackss = (new ArrayList(stack)).toArray(new Operand[stack.size()]); @@ -2087,4 +2063,23 @@ public Body getBody(SootMethod m, String phaseName) { return jb; } + + private final class Edge { + /* edge endpoint */ + final AbstractInsnNode insn; + /* previous stacks at edge */ + final LinkedList prevStacks; + /* current stack at edge */ + ArrayList stack; + + Edge(AbstractInsnNode insn, ArrayList stack) { + this.insn = insn; + this.prevStacks = new LinkedList(); + this.stack = stack; + } + + Edge(AbstractInsnNode insn) { + this(insn, new ArrayList(AsmMethodSource.this.stack)); + } + } } From af5c9c05eaf68923aee6760846cce30e0ed61e75 Mon Sep 17 00:00:00 2001 From: Manuel Benz Date: Wed, 31 Oct 2018 18:07:18 +0100 Subject: [PATCH 05/12] Re-implemented bridging logic. The implementation just failed on calls to functional interfaces with generic types. --- src/main/java/soot/LambdaMetaFactory.java | 179 ++++++++++++++++------ 1 file changed, 128 insertions(+), 51 deletions(-) diff --git a/src/main/java/soot/LambdaMetaFactory.java b/src/main/java/soot/LambdaMetaFactory.java index c4acc6c50db..2a81ef5e700 100644 --- a/src/main/java/soot/LambdaMetaFactory.java +++ b/src/main/java/soot/LambdaMetaFactory.java @@ -18,6 +18,8 @@ */ package soot; +import com.google.common.base.Preconditions; + import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -25,43 +27,48 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import soot.coffi.Util; + +import soot.javaToJimple.LocalGenerator; import soot.jimple.ClassConstant; +import soot.jimple.IdentityStmt; import soot.jimple.IntConstant; import soot.jimple.Jimple; import soot.jimple.JimpleBody; import soot.jimple.MethodHandle; import soot.jimple.MethodType; +import soot.jimple.StaticInvokeExpr; +import soot.jimple.VirtualInvokeExpr; public final class LambdaMetaFactory { + private static final Logger LOGGER = LoggerFactory.getLogger(LambdaMetaFactory.class); // FIXME: Move to 'G'? private static int uniq; - private static final Logger LOGGER = LoggerFactory.getLogger(LambdaMetaFactory.class); public static SootMethodRef makeLambdaHelper(List bootstrapArgs, int tag, String name, Type[] types) { - if (bootstrapArgs.size() < 3 || !(bootstrapArgs.get(0) instanceof ClassConstant) - || !(bootstrapArgs.get(1) instanceof MethodHandle) || !(bootstrapArgs.get(2) instanceof ClassConstant) + if (bootstrapArgs.size() < 3 || !(bootstrapArgs.get(0) instanceof MethodType) + || !(bootstrapArgs.get(1) instanceof MethodHandle) || !(bootstrapArgs.get(2) instanceof MethodType) || (bootstrapArgs.size() > 3 && !(bootstrapArgs.get(3) instanceof IntConstant))) { LOGGER.warn("LambdaMetaFactory: unexpected arguments for LambdaMetaFactor.metaFactory: {}", bootstrapArgs); return null; } - String samMethodType = ((ClassConstant) bootstrapArgs.get(0)).getValue(); + + MethodType samMethodType = ((MethodType) bootstrapArgs.get(0)); SootMethodRef implMethod = ((MethodHandle) bootstrapArgs.get(1)).getMethodRef(); - String instantiatedMethodType = ((ClassConstant) bootstrapArgs.get(2)).getValue(); - if (!samMethodType.equals(instantiatedMethodType)) { - G.v().out.println("warning: LambdaMetaFactory: " + samMethodType + " != " + instantiatedMethodType); - return null; - } + MethodType instantiatedMethodType = ((MethodType) bootstrapArgs.get(2)); + int flags = 0; - if (bootstrapArgs.size() > 3) + if (bootstrapArgs.size() > 3) { flags = ((IntConstant) bootstrapArgs.get(3)).value; + } + boolean serializable = (flags & 1 /* FLAGS_SERIALIZABLE */) != 0; List markerInterfaces = new ArrayList(); - List bridges = new ArrayList(); + List bridges = new ArrayList(); + int va = 4; if ((flags & 2 /* FLAG_MARKERS */) != 0) { if (va == bootstrapArgs.size() || !(bootstrapArgs.get(va) instanceof IntConstant)) { - LOGGER.warn("LambdaMetaFactory: unexpected arguments for LambdaMetaFactory.altMetaFactory"); + LOGGER.warn("LambdaMetaFactory: unexpected arguments for LambdaMetaFactory.altMetaFactory"); return null; } int count = ((IntConstant) bootstrapArgs.get(va++)).value; @@ -78,6 +85,7 @@ public static SootMethodRef makeLambdaHelper(List bootstrapArgs markerInterfaces.add((ClassConstant) v); } } + if ((flags & 4 /* FLAG_BRIDGES */) != 0) { if (va == bootstrapArgs.size() || !(bootstrapArgs.get(va) instanceof IntConstant)) { LOGGER.warn("LambdaMetaFactory: unexpected arguments for LambdaMetaFactory.altMetaFactory"); @@ -90,11 +98,11 @@ public static SootMethodRef makeLambdaHelper(List bootstrapArgs return null; } Value v = bootstrapArgs.get(va++); - if (!(v instanceof ClassConstant)) { + if (!(v instanceof MethodType)) { LOGGER.warn("LambdaMetaFactory: unexpected arguments for LambdaMetaFactory.altMetaFactory"); return null; } - bridges.add((ClassConstant) v); + bridges.add((MethodType) v); } } @@ -107,7 +115,6 @@ public static SootMethodRef makeLambdaHelper(List bootstrapArgs // Our thunk class implements the functional interface String className = "soot.dummy." + implMethod.name() + "$" + uniqSupply(); - LOGGER.debug("dummy class name " + className); SootClass tclass = new SootClass(className); tclass.addInterface(iface); if (serializable) @@ -123,15 +130,14 @@ public static SootMethodRef makeLambdaHelper(List bootstrapArgs tclass.addField(f); } - MethodSource ms; - { - // FIXME? some duplication from makeBridge... probably should split this into - // two different MethodSources - Type[] samTypes = Util.v().jimpleTypesOfFieldOrMethodDescriptor(samMethodType); - List paramTypes = Arrays.asList(samTypes).subList(0, samTypes.length - 1); - Type retType = samTypes[samTypes.length - 1]; - ms = new ThunkMethodSource(capFields, paramTypes, retType, implMethod); - } + List typedParameterTypes = instantiatedMethodType.getParameterTypes(); + Type retType = instantiatedMethodType.getReturnType(); + ThunkMethodSource ms = new ThunkMethodSource(capFields, typedParameterTypes, retType, implMethod); + + // create a method with the concrete, non-erased types first + SootMethod apply = new SootMethod(name, typedParameterTypes, retType); + tclass.addMethod(apply); + apply.setSource(ms); // Bootstrap method creates a new instance of this class SootMethod tboot = new SootMethod("bootstrap$", capTypes, iface.getType(), Modifier.STATIC); @@ -143,13 +149,16 @@ public static SootMethodRef makeLambdaHelper(List bootstrapArgs tclass.addMethod(tctor); tctor.setSource(ms); - // Dispatch runs the 'real' method implementing the body of the lambda - SootMethod tdispatch = makeBridge(name, samMethodType, capFields, implMethod); - tclass.addMethod(tdispatch); + boolean isGeneric = !samMethodType.equals(instantiatedMethodType); + if (isGeneric) { + // if the functional interface is generic in it' return-/parameter-types, we have to create bridging method which can + // work with plain objects and re-routes the call to the actual thunk method + addBridge(name, tclass, samMethodType, instantiatedMethodType, apply); + } for (int i = 0; i < bridges.size(); i++) { - SootMethod bridge = makeBridge(name, bridges.get(i).getValue(), capFields, implMethod); - tclass.addMethod(bridge); + final MethodType bridgeType = bridges.get(i); + addBridge(name, tclass, bridgeType, instantiatedMethodType, apply); } Scene.v().addClass(tclass); @@ -164,17 +173,14 @@ public static SootMethodRef makeLambdaHelper(List bootstrapArgs return tboot.makeRef(); } - private static SootMethod makeBridge(String name, String samMethodType, List capFields, - SootMethodRef implMethod) { - Type[] samTypes = Util.v().jimpleTypesOfFieldOrMethodDescriptor(samMethodType); - List paramTypes = Arrays.asList(samTypes).subList(0, samTypes.length - 1); - Type retType = samTypes[samTypes.length - 1]; - - MethodSource ms = new ThunkMethodSource(capFields, paramTypes, retType, implMethod); - - SootMethod m = new SootMethod(name, paramTypes, retType); - m.setSource(ms); - return m; + private static void addBridge(String name, SootClass tclass, MethodType bridgeType, MethodType targetType, + SootMethod apply) { + final List bridgeParams = bridgeType.getParameterTypes(); + final Type bridgeReturn = bridgeType.getReturnType(); + SootMethod genericBridge = new SootMethod(name, bridgeParams, bridgeReturn); + tclass.addMethod(genericBridge); + genericBridge.setSource(new BridgeMethodSource(bridgeParams, targetType.getParameterTypes(), bridgeReturn, + targetType.getReturnType(), apply.makeRef())); } private static synchronized long uniqSupply() { @@ -196,14 +202,15 @@ public ThunkMethodSource(List capFields, List paramTypes, Type public Body getBody(SootMethod m, String phaseName) { if (!phaseName.equals("jb")) - throw new Error("unsupported body type: " + phaseName); // FIXME? + throw new Error("unsupported body type: " + phaseName); SootClass tclass = m.getDeclaringClass(); JimpleBody jb = Jimple.v().newBody(m); PatchingChain us = jb.getUnits(); + LocalGenerator lc = new LocalGenerator(jb); if (m.getName().equals("")) { - Local l = Jimple.v().newLocal("r", tclass.getType()); + Local l = lc.generateLocal(tclass.getType()); us.add(Jimple.v().newIdentityStmt(l, Jimple.v().newThisRef(tclass.getType()))); us.add(Jimple.v() .newInvokeStmt(Jimple.v().newSpecialInvokeExpr(l, @@ -211,43 +218,113 @@ public Body getBody(SootMethod m, String phaseName) { Collections.emptyList()))); for (SootField f : capFields) { int i = us.size() - 2; - Local l2 = Jimple.v().newLocal("c" + i, f.getType()); + Local l2 = lc.generateLocal(f.getType()); us.add(Jimple.v().newIdentityStmt(l2, Jimple.v().newParameterRef(f.getType(), i))); us.add(Jimple.v().newAssignStmt(Jimple.v().newInstanceFieldRef(l, f.makeRef()), l2)); } us.add(Jimple.v().newReturnVoidStmt()); } else if (m.getName().equals("bootstrap$")) { - Local l = Jimple.v().newLocal("r", tclass.getType()); + Local l = lc.generateLocal(tclass.getType()); Value val = Jimple.v().newNewExpr(tclass.getType()); - List capVals = new ArrayList(); us.add(Jimple.v().newAssignStmt(l, val)); us.add(Jimple.v().newInvokeStmt(Jimple.v().newSpecialInvokeExpr(l, Scene.v().makeConstructorRef(tclass, Collections.emptyList()), Collections.emptyList()))); us.add(Jimple.v().newReturnStmt(l)); } else { - Local this_ = Jimple.v().newLocal("r", tclass.getType()); + Local this_ = lc.generateLocal(tclass.getType()); us.add(Jimple.v().newIdentityStmt(this_, Jimple.v().newThisRef(tclass.getType()))); List args = new ArrayList(); for (SootField f : capFields) { int i = args.size(); - Local l = Jimple.v().newLocal("c" + i, f.getType()); + Local l = lc.generateLocal(f.getType()); us.add(Jimple.v().newAssignStmt(l, Jimple.v().newInstanceFieldRef(this_, f.makeRef()))); args.add(l); } for (Type ty : paramTypes) { int i = args.size(); - Local l = Jimple.v().newLocal("a" + i, ty); + Local l = lc.generateLocal(ty); us.add(Jimple.v().newIdentityStmt(l, Jimple.v().newParameterRef(ty, i))); args.add(l); } - Local ret = Jimple.v().newLocal("r", retType); - us.add(Jimple.v().newAssignStmt(ret, Jimple.v().newStaticInvokeExpr(implMethod, args))); + final StaticInvokeExpr invoke = Jimple.v().newStaticInvokeExpr(implMethod, args); + + if (retType == VoidType.v()) { + us.add(Jimple.v().newInvokeStmt(invoke)); + us.add(Jimple.v().newReturnVoidStmt()); + } else { + Local ret = lc.generateLocal(retType); + us.add(Jimple.v().newAssignStmt(ret, invoke)); + us.add(Jimple.v().newReturnStmt(ret)); + } + } + return jb; + } + } + + private static class BridgeMethodSource implements MethodSource { + private final List paramTypes; + private final List targetTypes; + private final Type retType; + private final SootMethodRef apply; + private final Type targetRetType; + + public BridgeMethodSource(List paramTypes, List targetTypes, Type retType, Type targetRetType, + SootMethodRef apply) { + Preconditions.checkArgument(paramTypes.size() == targetTypes.size()); + this.paramTypes = paramTypes; + this.targetTypes = targetTypes; + this.retType = retType; + this.targetRetType = targetRetType; + this.apply = apply; + } + + @Override + public Body getBody(SootMethod m, String phaseName) { + if (!phaseName.equals("jb")) + throw new Error("unsupported body type: " + phaseName); + + SootClass tclass = m.getDeclaringClass(); + JimpleBody jb = Jimple.v().newBody(m); + PatchingChain us = jb.getUnits(); + LocalGenerator lc = new LocalGenerator(jb); + + Local this_ = lc.generateLocal(tclass.getType()); + final IdentityStmt thisIdentity = Jimple.v().newIdentityStmt(this_, Jimple.v().newThisRef(tclass.getType())); + us.addFirst(thisIdentity); + + List args = new ArrayList(); + + for (int i = 0; i < paramTypes.size(); i++) { + final Type paramType = paramTypes.get(i); + final Type targetType = targetTypes.get(i); + + Local paramLocal = lc.generateLocal(paramType); + us.insertAfter(Jimple.v().newIdentityStmt(paramLocal, Jimple.v().newParameterRef(paramType, i)), thisIdentity); + + final Local targetLocal = lc.generateLocal(targetType); + us.add(Jimple.v().newAssignStmt(targetLocal, Jimple.v().newCastExpr(paramLocal, targetType))); + + args.add(targetLocal); + } + + final VirtualInvokeExpr invoke = Jimple.v().newVirtualInvokeExpr(this_, apply, args); + + if (retType == VoidType.v()) { + us.add(Jimple.v().newInvokeStmt(invoke)); + us.add(Jimple.v().newReturnVoidStmt()); + } else { + final Local nonCastRet = lc.generateLocal(targetRetType); + us.add(Jimple.v().newAssignStmt(nonCastRet, invoke)); + + final Local ret = lc.generateLocal(retType); + us.add(Jimple.v().newAssignStmt(ret, Jimple.v().newCastExpr(nonCastRet, retType))); us.add(Jimple.v().newReturnStmt(ret)); } + return jb; } } From 18504c0cf4e0913465557968be6d4fc4978e1593 Mon Sep 17 00:00:00 2001 From: Manuel Benz Date: Fri, 2 Nov 2018 16:12:12 +0100 Subject: [PATCH 06/12] Fixes incorrect addition of MetaFactory implementation to Scene. The factory need to implement java.lang.Object and the FastHierarchy has to be invalidated after adding the MetaFactory to the Scene, so that it is found as valid implementer of the functional interface during call graph generation --- src/main/java/soot/LambdaMetaFactory.java | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/main/java/soot/LambdaMetaFactory.java b/src/main/java/soot/LambdaMetaFactory.java index 2a81ef5e700..db0343ff5d6 100644 --- a/src/main/java/soot/LambdaMetaFactory.java +++ b/src/main/java/soot/LambdaMetaFactory.java @@ -116,7 +116,10 @@ public static SootMethodRef makeLambdaHelper(List bootstrapArgs // Our thunk class implements the functional interface String className = "soot.dummy." + implMethod.name() + "$" + uniqSupply(); SootClass tclass = new SootClass(className); + + tclass.setSuperclass(Scene.v().getObjectType().getSootClass()); tclass.addInterface(iface); + if (serializable) tclass.addInterface(RefType.v("java.io.Serializable").getSootClass()); for (int i = 0; i < markerInterfaces.size(); i++) @@ -162,13 +165,11 @@ public static SootMethodRef makeLambdaHelper(List bootstrapArgs } Scene.v().addClass(tclass); - - // FIXME remove debug stuff - System.out.println(tboot); - - java.io.PrintWriter pw = new java.io.PrintWriter(System.out); - Printer.v().printTo(tclass, pw); - pw.close(); + // The hierarchy has to be rebuild after adding the MetaFactory implementation + // soot.FastHierarchy.canStoreClass will otherwise fail due to not having an interval set for the class which eventually + // leads to the MetaFactory not being accepted as implementation of the functional interface it actually implements + // which, in turn, lead to missing edges in the call graph + Scene.v().releaseFastHierarchy(); return tboot.makeRef(); } @@ -237,9 +238,8 @@ public Body getBody(SootMethod m, String phaseName) { List args = new ArrayList(); for (SootField f : capFields) { - int i = args.size(); Local l = lc.generateLocal(f.getType()); - us.add(Jimple.v().newAssignStmt(l, Jimple.v().newInstanceFieldRef(this_, f.makeRef()))); + us.add(Jimple.v().newAssignStmt(l, Jimple.v().newInstanceFieldRef(l, f.makeRef()))); args.add(l); } @@ -250,6 +250,7 @@ public Body getBody(SootMethod m, String phaseName) { args.add(l); } + // TODO care for instance invokes final StaticInvokeExpr invoke = Jimple.v().newStaticInvokeExpr(implMethod, args); if (retType == VoidType.v()) { From 992c708430fe9cbba4ae3878bdf315a957830ae0 Mon Sep 17 00:00:00 2001 From: Manuel Benz Date: Fri, 2 Nov 2018 16:41:14 +0100 Subject: [PATCH 07/12] Adds a very basic test case of the LambdaMetaFactory with a CHA call graph. Test case is copied from the 'Java Call Graph Test Suite (JCG)' project (https://bitbucket.org/delors/jcg/src/master/) --- .../generated/singletons/soot/Singletons.java | 2 +- .../LambdaMetaFactoryCHATest.java | 72 +++++++++++++++++++ .../soot/lambdaMetaFactory/jcg/Lambda1.java | 17 +++++ .../lambdaMetaFactory/jcg/package-info.java | 8 +++ 4 files changed, 98 insertions(+), 1 deletion(-) create mode 100644 src/systemTest/java/soot/lambdaMetaFactory/LambdaMetaFactoryCHATest.java create mode 100644 src/systemTest/targets/soot/lambdaMetaFactory/jcg/Lambda1.java create mode 100644 src/systemTest/targets/soot/lambdaMetaFactory/jcg/package-info.java diff --git a/src/main/generated/singletons/soot/Singletons.java b/src/main/generated/singletons/soot/Singletons.java index 97b39b23cbe..97d246d631e 100644 --- a/src/main/generated/singletons/soot/Singletons.java +++ b/src/main/generated/singletons/soot/Singletons.java @@ -25,7 +25,7 @@ /* THIS FILE IS AUTOMATICALLY GENERATED FROM soot/singletons.xml DO NOT EDIT! */ /** A class to group together all the global variables in Soot. */ -@javax.annotation.Generated(value = "Saxonica v3.0", date = "2018-08-13T11:22:21.975+02:00", comments = "from singletons.xml") +@javax.annotation.Generated(value = "Saxonica v3.0", date = "2018-10-31T13:12:15.434+01:00", comments = "from singletons.xml") public class Singletons { public final class Global { diff --git a/src/systemTest/java/soot/lambdaMetaFactory/LambdaMetaFactoryCHATest.java b/src/systemTest/java/soot/lambdaMetaFactory/LambdaMetaFactoryCHATest.java new file mode 100644 index 00000000000..25b1bdd23a0 --- /dev/null +++ b/src/systemTest/java/soot/lambdaMetaFactory/LambdaMetaFactoryCHATest.java @@ -0,0 +1,72 @@ +package soot.lambdaMetaFactory; + +import com.google.common.collect.Lists; + +import java.util.List; + +import org.junit.Assert; +import org.junit.Test; + +import soot.PhaseOptions; +import soot.Scene; +import soot.SootMethod; +import soot.jimple.toolkits.callgraph.CallGraph; +import soot.jimple.toolkits.callgraph.Edge; +import soot.testing.framework.AbstractTestingFramework; + +/** + * @author Manuel Benz created on 31.10.18 + */ +public class LambdaMetaFactoryCHATest extends AbstractTestingFramework { + + private static final String TEST_METHOD_SUBSIG = "void main()"; + + @Test + public void lambda1() { + String testClass = "soot.lambdaMetaFactory.jcg.Lambda1"; + + final SootMethod target + = prepareTarget(methodSigFromComponents(testClass, TEST_METHOD_SUBSIG), testClass, "java.util.function.Function"); + + final CallGraph cg = Scene.v().getCallGraph(); + + final SootMethod bootstrap + = Scene.v().getMethod(""); + final SootMethod metaFactoryConstructor = Scene.v().getMethod("()>"); + final SootMethod genericApply + = Scene.v().getMethod(""); + final SootMethod concreteApply + = Scene.v().getMethod(""); + final SootMethod lambdaBody + = Scene.v().getMethod(methodSigFromComponents(testClass, "java.lang.Boolean lambda$main$0(java.lang.Integer)")); + final SootMethod doSomething = Scene.v().getMethod(""); + + final List edgesFromTarget = Lists.newArrayList(cg.edgesOutOf(target)); + + Assert.assertTrue("There should be an edge from main to the bootstrap method of the synthetic LambdaMetaFactory", + edgesFromTarget.stream().anyMatch(e -> e.tgt().equals(bootstrap) && e.isStatic())); + Assert.assertTrue("There should be an edge to the constructor of the LambdaMetaFactory in the bootstrap method", + Lists.newArrayList(cg.edgesOutOf(bootstrap)).stream() + .anyMatch(e -> e.tgt().equals(metaFactoryConstructor) && e.isSpecial())); + Assert.assertTrue( + "There should be an interface invocation on the synthetic LambdaMetaFactory's implementation of the functional interface (bridge) in the main method", + edgesFromTarget.stream().anyMatch(e -> e.getTgt().equals(genericApply) && e.isInstance())); + Assert.assertTrue( + "There should be a virtual invocation of the synthetic LambdaMetaFactory's implementation of the functional interface in the bridge method", + Lists.newArrayList(cg.edgesOutOf(genericApply)).stream() + .anyMatch(e -> e.tgt().equals(concreteApply) && e.isVirtual())); + + Assert.assertTrue( + "There should be a static call to the lambda body implementation in the generated functional interface implementation of the synthetic LambdaMetaFactory", + Lists.newArrayList(cg.edgesOutOf(concreteApply)).stream() + .anyMatch(e -> e.getTgt().equals(lambdaBody) && e.isStatic())); + + Assert.assertTrue("There should be a static call to the doSomething method in actual lambda body implementation", Lists + .newArrayList(cg.edgesOutOf(lambdaBody)).stream().anyMatch(e -> e.getTgt().equals(doSomething) && e.isStatic())); + } + + @Override + protected void setupSoot() { + PhaseOptions.v().setPhaseOption("cg.cha", "on"); + } +} \ No newline at end of file diff --git a/src/systemTest/targets/soot/lambdaMetaFactory/jcg/Lambda1.java b/src/systemTest/targets/soot/lambdaMetaFactory/jcg/Lambda1.java new file mode 100644 index 00000000000..e2038e9c286 --- /dev/null +++ b/src/systemTest/targets/soot/lambdaMetaFactory/jcg/Lambda1.java @@ -0,0 +1,17 @@ +package soot.lambdaMetaFactory.jcg; + +import java.util.function.Function; + +class Lambda1 { + private static void doSomething() { + // call in lambda + } + + public void main() { + Function isEven = (Integer a) -> { + doSomething(); + return a % 2 == 0; + }; + isEven.apply(2); + } +} \ No newline at end of file diff --git a/src/systemTest/targets/soot/lambdaMetaFactory/jcg/package-info.java b/src/systemTest/targets/soot/lambdaMetaFactory/jcg/package-info.java new file mode 100644 index 00000000000..db91df69fb5 --- /dev/null +++ b/src/systemTest/targets/soot/lambdaMetaFactory/jcg/package-info.java @@ -0,0 +1,8 @@ +/** + * The code contained in this package is copied from 'The Java Call Graph Test Suite (JCG)' project: + * https://bitbucket.org/delors/jcg/src/master/ + * + * + * @author Manuel Benz created on 31.10.18 + */ +package soot.lambdaMetaFactory.jcg; \ No newline at end of file From 1a2eb8da39dad3a8c20f9a2c928e031cda1d9bc1 Mon Sep 17 00:00:00 2001 From: Manuel Benz Date: Fri, 2 Nov 2018 16:44:31 +0100 Subject: [PATCH 08/12] Adds test configurations for RTA, VTA and SPARK. VTA and SPARK is currently failing. More tests to follow --- .../LambdaMetaFactoryRTATest.java | 74 +++++++++++++++++++ .../LambdaMetaFactorySPARKTest.java | 67 +++++++++++++++++ .../LambdaMetaFactoryVTATest.java | 73 ++++++++++++++++++ 3 files changed, 214 insertions(+) create mode 100644 src/systemTest/java/soot/lambdaMetaFactory/LambdaMetaFactoryRTATest.java create mode 100644 src/systemTest/java/soot/lambdaMetaFactory/LambdaMetaFactorySPARKTest.java create mode 100644 src/systemTest/java/soot/lambdaMetaFactory/LambdaMetaFactoryVTATest.java diff --git a/src/systemTest/java/soot/lambdaMetaFactory/LambdaMetaFactoryRTATest.java b/src/systemTest/java/soot/lambdaMetaFactory/LambdaMetaFactoryRTATest.java new file mode 100644 index 00000000000..2383599da55 --- /dev/null +++ b/src/systemTest/java/soot/lambdaMetaFactory/LambdaMetaFactoryRTATest.java @@ -0,0 +1,74 @@ +package soot.lambdaMetaFactory; + +import com.google.common.collect.Lists; + +import java.util.List; + +import org.junit.Assert; +import org.junit.Test; + +import soot.Scene; +import soot.SootMethod; +import soot.jimple.toolkits.callgraph.CallGraph; +import soot.jimple.toolkits.callgraph.Edge; +import soot.options.Options; +import soot.testing.framework.AbstractTestingFramework; + +/** + * @author Manuel Benz created on 31.10.18 + */ +public class LambdaMetaFactoryRTATest extends AbstractTestingFramework { + + private static final String TEST_METHOD_SUBSIG = "void main()"; + + @Test + public void lambda1() { + String testClass = "soot.lambdaMetaFactory.jcg.Lambda1"; + + final SootMethod target + = prepareTarget(methodSigFromComponents(testClass, TEST_METHOD_SUBSIG), testClass, "java.util.function.Function"); + + final CallGraph cg = Scene.v().getCallGraph(); + + final SootMethod bootstrap + = Scene.v().getMethod(""); + final SootMethod metaFactoryConstructor = Scene.v().getMethod("()>"); + final SootMethod genericApply + = Scene.v().getMethod(""); + final SootMethod concreteApply + = Scene.v().getMethod(""); + final SootMethod lambdaBody + = Scene.v().getMethod(methodSigFromComponents(testClass, "java.lang.Boolean lambda$main$0(java.lang.Integer)")); + final SootMethod doSomething = Scene.v().getMethod(""); + + final List edgesFromTarget = Lists.newArrayList(cg.edgesOutOf(target)); + + Assert.assertTrue("There should be an edge from main to the bootstrap method of the synthetic LambdaMetaFactory", + edgesFromTarget.stream().anyMatch(e -> e.tgt().equals(bootstrap) && e.isStatic())); + Assert.assertTrue("There should be an edge to the constructor of the LambdaMetaFactory in the bootstrap method", + Lists.newArrayList(cg.edgesOutOf(bootstrap)).stream() + .anyMatch(e -> e.tgt().equals(metaFactoryConstructor) && e.isSpecial())); + Assert.assertTrue( + "There should be an interface invocation on the synthetic LambdaMetaFactory's implementation of the functional interface (bridge) in the main method", + edgesFromTarget.stream().anyMatch(e -> e.getTgt().equals(genericApply) && e.isInstance())); + Assert.assertTrue( + "There should be a virtual invocation of the synthetic LambdaMetaFactory's implementation of the functional interface in the bridge method", + Lists.newArrayList(cg.edgesOutOf(genericApply)).stream() + .anyMatch(e -> e.tgt().equals(concreteApply) && e.isVirtual())); + + Assert.assertTrue( + "There should be a static call to the lambda body implementation in the generated functional interface implementation of the synthetic LambdaMetaFactory", + Lists.newArrayList(cg.edgesOutOf(concreteApply)).stream() + .anyMatch(e -> e.getTgt().equals(lambdaBody) && e.isStatic())); + + Assert.assertTrue("There should be a static call to the doSomething method in actual lambda body implementation", Lists + .newArrayList(cg.edgesOutOf(lambdaBody)).stream().anyMatch(e -> e.getTgt().equals(doSomething) && e.isStatic())); + } + + @Override + protected void setupSoot() { + Options.v().setPhaseOption("cg.spark", "on"); + Options.v().setPhaseOption("cg.spark", "rta:true"); + Options.v().setPhaseOption("cg.spark", "on-fly-cg:false"); + } +} \ No newline at end of file diff --git a/src/systemTest/java/soot/lambdaMetaFactory/LambdaMetaFactorySPARKTest.java b/src/systemTest/java/soot/lambdaMetaFactory/LambdaMetaFactorySPARKTest.java new file mode 100644 index 00000000000..c266fc82c4f --- /dev/null +++ b/src/systemTest/java/soot/lambdaMetaFactory/LambdaMetaFactorySPARKTest.java @@ -0,0 +1,67 @@ +package soot.lambdaMetaFactory; + +import com.google.common.collect.Lists; + +import java.util.List; + +import org.junit.Assert; +import org.junit.Test; + +import soot.Scene; +import soot.SootMethod; +import soot.jimple.toolkits.callgraph.CallGraph; +import soot.jimple.toolkits.callgraph.Edge; +import soot.options.Options; +import soot.testing.framework.AbstractTestingFramework; + +/** + * @author Manuel Benz created on 31.10.18 + */ +public class LambdaMetaFactorySPARKTest extends AbstractTestingFramework { + + private static final String TEST_METHOD_SUBSIG = "void main()"; + + @Test + public void lambda1() { + String testClass = "soot.lambdaMetaFactory.jcg.Lambda1"; + + final SootMethod target + = prepareTarget(methodSigFromComponents(testClass, TEST_METHOD_SUBSIG), testClass, "java.util.function.Function"); + + final CallGraph cg = Scene.v().getCallGraph(); + + final SootMethod bootstrap + = Scene.v().getMethod(""); + final SootMethod metaFactoryConstructor = Scene.v().getMethod("()>"); + final SootMethod genericApply + = Scene.v().getMethod(""); + final SootMethod concreteApply + = Scene.v().getMethod(""); + final SootMethod lambdaBody + = Scene.v().getMethod(methodSigFromComponents(testClass, "java.lang.Boolean lambda$main$0(java.lang.Integer)")); + final SootMethod doSomething = Scene.v().getMethod(""); + + final List edgesFromTarget = Lists.newArrayList(cg.edgesOutOf(target)); + + Assert.assertTrue("There should be an edge from main to the bootstrap method of the synthetic LambdaMetaFactory", + edgesFromTarget.stream().anyMatch(e -> e.tgt().equals(bootstrap) && e.isStatic())); + Assert.assertTrue("There should be an edge to the constructor of the LambdaMetaFactory in the bootstrap method", + Lists.newArrayList(cg.edgesOutOf(bootstrap)).stream() + .anyMatch(e -> e.tgt().equals(metaFactoryConstructor) && e.isSpecial())); + Assert.assertTrue( + "There should be an interface invocation on the synthetic LambdaMetaFactory's implementation of the functional interface (bridge) in the main method", + edgesFromTarget.stream().anyMatch(e -> e.getTgt().equals(genericApply) && e.isInstance())); + Assert.assertTrue( + "There should be a virtual invocation of the synthetic LambdaMetaFactory's implementation of the functional interface in the bridge method", + Lists.newArrayList(cg.edgesOutOf(genericApply)).stream() + .anyMatch(e -> e.tgt().equals(concreteApply) && e.isVirtual())); + + Assert.assertTrue( + "There should be a static call to the lambda body implementation in the generated functional interface implementation of the synthetic LambdaMetaFactory", + Lists.newArrayList(cg.edgesOutOf(concreteApply)).stream() + .anyMatch(e -> e.getTgt().equals(lambdaBody) && e.isStatic())); + + Assert.assertTrue("There should be a static call to the doSomething method in actual lambda body implementation", Lists + .newArrayList(cg.edgesOutOf(lambdaBody)).stream().anyMatch(e -> e.getTgt().equals(doSomething) && e.isStatic())); + } +} \ No newline at end of file diff --git a/src/systemTest/java/soot/lambdaMetaFactory/LambdaMetaFactoryVTATest.java b/src/systemTest/java/soot/lambdaMetaFactory/LambdaMetaFactoryVTATest.java new file mode 100644 index 00000000000..cca64b0f943 --- /dev/null +++ b/src/systemTest/java/soot/lambdaMetaFactory/LambdaMetaFactoryVTATest.java @@ -0,0 +1,73 @@ +package soot.lambdaMetaFactory; + +import com.google.common.collect.Lists; + +import java.util.List; + +import org.junit.Assert; +import org.junit.Test; + +import soot.Scene; +import soot.SootMethod; +import soot.jimple.toolkits.callgraph.CallGraph; +import soot.jimple.toolkits.callgraph.Edge; +import soot.options.Options; +import soot.testing.framework.AbstractTestingFramework; + +/** + * @author Manuel Benz created on 31.10.18 + */ +public class LambdaMetaFactoryVTATest extends AbstractTestingFramework { + + private static final String TEST_METHOD_SUBSIG = "void main()"; + + @Test + public void lambda1() { + String testClass = "soot.lambdaMetaFactory.jcg.Lambda1"; + + final SootMethod target + = prepareTarget(methodSigFromComponents(testClass, TEST_METHOD_SUBSIG), testClass, "java.util.function.Function"); + + final CallGraph cg = Scene.v().getCallGraph(); + + final SootMethod bootstrap + = Scene.v().getMethod(""); + final SootMethod metaFactoryConstructor = Scene.v().getMethod("()>"); + final SootMethod genericApply + = Scene.v().getMethod(""); + final SootMethod concreteApply + = Scene.v().getMethod(""); + final SootMethod lambdaBody + = Scene.v().getMethod(methodSigFromComponents(testClass, "java.lang.Boolean lambda$main$0(java.lang.Integer)")); + final SootMethod doSomething = Scene.v().getMethod(""); + + final List edgesFromTarget = Lists.newArrayList(cg.edgesOutOf(target)); + + Assert.assertTrue("There should be an edge from main to the bootstrap method of the synthetic LambdaMetaFactory", + edgesFromTarget.stream().anyMatch(e -> e.tgt().equals(bootstrap) && e.isStatic())); + Assert.assertTrue("There should be an edge to the constructor of the LambdaMetaFactory in the bootstrap method", + Lists.newArrayList(cg.edgesOutOf(bootstrap)).stream() + .anyMatch(e -> e.tgt().equals(metaFactoryConstructor) && e.isSpecial())); + Assert.assertTrue( + "There should be an interface invocation on the synthetic LambdaMetaFactory's implementation of the functional interface (bridge) in the main method", + edgesFromTarget.stream().anyMatch(e -> e.getTgt().equals(genericApply) && e.isInstance())); + Assert.assertTrue( + "There should be a virtual invocation of the synthetic LambdaMetaFactory's implementation of the functional interface in the bridge method", + Lists.newArrayList(cg.edgesOutOf(genericApply)).stream() + .anyMatch(e -> e.tgt().equals(concreteApply) && e.isVirtual())); + + Assert.assertTrue( + "There should be a static call to the lambda body implementation in the generated functional interface implementation of the synthetic LambdaMetaFactory", + Lists.newArrayList(cg.edgesOutOf(concreteApply)).stream() + .anyMatch(e -> e.getTgt().equals(lambdaBody) && e.isStatic())); + + Assert.assertTrue("There should be a static call to the doSomething method in actual lambda body implementation", Lists + .newArrayList(cg.edgesOutOf(lambdaBody)).stream().anyMatch(e -> e.getTgt().equals(doSomething) && e.isStatic())); + } + + @Override + protected void setupSoot() { + Options.v().setPhaseOption("cg.spark", "on"); + Options.v().setPhaseOption("cg.spark", "vta:true"); + } +} \ No newline at end of file From 20902f3a649b7ffd562d1105eb92ea7c2e4da9a2 Mon Sep 17 00:00:00 2001 From: Manuel Benz Date: Fri, 2 Nov 2018 17:09:32 +0100 Subject: [PATCH 09/12] Adds support for instance invokes in the interface implementing method. Fixes a parameter indexing bug. --- src/main/java/soot/LambdaMetaFactory.java | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/main/java/soot/LambdaMetaFactory.java b/src/main/java/soot/LambdaMetaFactory.java index db0343ff5d6..a6a84936419 100644 --- a/src/main/java/soot/LambdaMetaFactory.java +++ b/src/main/java/soot/LambdaMetaFactory.java @@ -32,11 +32,11 @@ import soot.jimple.ClassConstant; import soot.jimple.IdentityStmt; import soot.jimple.IntConstant; +import soot.jimple.InvokeExpr; import soot.jimple.Jimple; import soot.jimple.JimpleBody; import soot.jimple.MethodHandle; import soot.jimple.MethodType; -import soot.jimple.StaticInvokeExpr; import soot.jimple.VirtualInvokeExpr; public final class LambdaMetaFactory { @@ -243,15 +243,27 @@ public Body getBody(SootMethod m, String phaseName) { args.add(l); } - for (Type ty : paramTypes) { - int i = args.size(); + for (int i = 0; i < paramTypes.size(); i++) { + final Type ty = paramTypes.get(i); Local l = lc.generateLocal(ty); us.add(Jimple.v().newIdentityStmt(l, Jimple.v().newParameterRef(ty, i))); args.add(l); } - // TODO care for instance invokes - final StaticInvokeExpr invoke = Jimple.v().newStaticInvokeExpr(implMethod, args); + // distinguish between static and instance invocations of the lambda body + final InvokeExpr invoke; + if (implMethod.isStatic()) { + invoke = Jimple.v().newStaticInvokeExpr(implMethod, args); + } else { + // in case of instance invoke, the first argument is the actual receiver of the call + final Local base = args.remove(0); + + if (implMethod.declaringClass().isInterface()) { + invoke = Jimple.v().newInterfaceInvokeExpr(base, implMethod, args); + } else { + invoke = Jimple.v().newVirtualInvokeExpr(base, implMethod, args); + } + } if (retType == VoidType.v()) { us.add(Jimple.v().newInvokeStmt(invoke)); From ed05b769a5a3f915b94723be9105280734fff652 Mon Sep 17 00:00:00 2001 From: Manuel Benz Date: Fri, 2 Nov 2018 19:05:41 +0100 Subject: [PATCH 10/12] Finally found bug that prevented SPARK and VTA working with the synthetic LambdaMetaFactory for years! The TypeManager cached the FastHierarchy and thereby missed its invalidation after adding the synthetic LambdaMetaFactory. The TypeManager now uses a Scene managed FastHierarchy and thus can correctly find implementers of functional interfaces added after the first instance of FastHierarchy was built. --- src/main/java/soot/LambdaMetaFactory.java | 2 +- .../soot/jimple/spark/internal/TypeManager.java | 13 +++++++------ src/main/java/soot/jimple/spark/pag/PAG.java | 2 +- .../LambdaMetaFactorySPARKTest.java | 1 - 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/main/java/soot/LambdaMetaFactory.java b/src/main/java/soot/LambdaMetaFactory.java index a6a84936419..4db918f79aa 100644 --- a/src/main/java/soot/LambdaMetaFactory.java +++ b/src/main/java/soot/LambdaMetaFactory.java @@ -41,7 +41,7 @@ public final class LambdaMetaFactory { private static final Logger LOGGER = LoggerFactory.getLogger(LambdaMetaFactory.class); - // FIXME: Move to 'G'? + private static int uniq; public static SootMethodRef makeLambdaHelper(List bootstrapArgs, int tag, String name, Type[] types) { diff --git a/src/main/java/soot/jimple/spark/internal/TypeManager.java b/src/main/java/soot/jimple/spark/internal/TypeManager.java index e143706b0c3..eda0c6f1c51 100644 --- a/src/main/java/soot/jimple/spark/internal/TypeManager.java +++ b/src/main/java/soot/jimple/spark/internal/TypeManager.java @@ -29,6 +29,7 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.function.Supplier; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -234,18 +235,18 @@ final public boolean castNeverFails(Type src, Type dst) { if (dst instanceof AnySubType) { throw new RuntimeException("oops src=" + src + " dst=" + dst); } - return fh.canStoreType(src, dst); + return fh.get().canStoreType(src, dst); } - public void setFastHierarchy(FastHierarchy fh) { + public void setFastHierarchy(Supplier fh) { this.fh = fh; } public FastHierarchy getFastHierarchy() { - return fh; + return fh == null ? null : fh.get(); } - protected FastHierarchy fh = null; + protected Supplier fh = null; protected PAG pag; protected QueueReader allocNodeListener = null; @@ -295,7 +296,7 @@ final private BitVector makeClassTypeMask(SootClass clazz) { } } - Collection subclasses = fh.getSubclassesOf(clazz); + Collection subclasses = fh.get().getSubclassesOf(clazz); if (subclasses == Collections.EMPTY_LIST) { for (AllocNode an : anySubtypeAllocs) { mask.set(an.getNumber()); @@ -319,7 +320,7 @@ final private BitVector makeMaskOfInterface(SootClass interf) { BitVector ret = new BitVector(pag.getAllocNodeNumberer().size()); typeMask.put(interf.getType(), ret); - Collection implementers = fh.getAllImplementersOfInterface(interf); + Collection implementers = fh.get().getAllImplementersOfInterface(interf); for (SootClass impl : implementers) { BitVector other = (BitVector) typeMask.get(impl.getType()); diff --git a/src/main/java/soot/jimple/spark/pag/PAG.java b/src/main/java/soot/jimple/spark/pag/PAG.java index 954277febc0..685a2a38d5b 100644 --- a/src/main/java/soot/jimple/spark/pag/PAG.java +++ b/src/main/java/soot/jimple/spark/pag/PAG.java @@ -111,7 +111,7 @@ public PAG(final SparkOptions opts) { } typeManager = new TypeManager(this); if (!opts.ignore_types()) { - typeManager.setFastHierarchy(Scene.v().getOrMakeFastHierarchy()); + typeManager.setFastHierarchy(() -> Scene.v().getOrMakeFastHierarchy()); } switch (opts.set_impl()) { case SparkOptions.set_impl_hash: diff --git a/src/systemTest/java/soot/lambdaMetaFactory/LambdaMetaFactorySPARKTest.java b/src/systemTest/java/soot/lambdaMetaFactory/LambdaMetaFactorySPARKTest.java index c266fc82c4f..2387e196f7c 100644 --- a/src/systemTest/java/soot/lambdaMetaFactory/LambdaMetaFactorySPARKTest.java +++ b/src/systemTest/java/soot/lambdaMetaFactory/LambdaMetaFactorySPARKTest.java @@ -11,7 +11,6 @@ import soot.SootMethod; import soot.jimple.toolkits.callgraph.CallGraph; import soot.jimple.toolkits.callgraph.Edge; -import soot.options.Options; import soot.testing.framework.AbstractTestingFramework; /** From 681a46e338dfcc8627b85654ad8386394ff9f10d Mon Sep 17 00:00:00 2001 From: Manuel Benz Date: Fri, 2 Nov 2018 19:39:47 +0100 Subject: [PATCH 11/12] Fixes formatting and license --- src/main/java/soot/LambdaMetaFactory.java | 47 +++++++++++-------- src/main/java/soot/asm/AsmMethodSource.java | 13 +++-- .../LambdaMetaFactoryCHATest.java | 22 +++++++++ .../LambdaMetaFactoryRTATest.java | 22 +++++++++ .../LambdaMetaFactorySPARKTest.java | 22 +++++++++ .../LambdaMetaFactoryVTATest.java | 22 +++++++++ .../soot/lambdaMetaFactory/jcg/Lambda1.java | 25 ++++++++++ .../lambdaMetaFactory/jcg/package-info.java | 24 +++++++++- 8 files changed, 172 insertions(+), 25 deletions(-) diff --git a/src/main/java/soot/LambdaMetaFactory.java b/src/main/java/soot/LambdaMetaFactory.java index 4db918f79aa..023a2945f7b 100644 --- a/src/main/java/soot/LambdaMetaFactory.java +++ b/src/main/java/soot/LambdaMetaFactory.java @@ -1,22 +1,27 @@ -/* Soot - a J*va Optimization Framework - * Copyright (C) 1997-2014 Raja Vallee-Rai and others - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. +package soot; + +/*- + * #%L + * Soot - a J*va Optimization Framework + * %% + * Copyright (C) 2017 Brian Alliet Initial implementation + * Copyright (C) 2018 Manuel Benz Bug fixes and improvements + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 2.1 of the + * License, or (at your option) any later version. * - * This library is distributed in the hope that it will be useful, + * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Lesser Public License for more details. * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., 59 Temple Place - Suite 330, - * Boston, MA 02111-1307, USA. + * You should have received a copy of the GNU General Lesser Public + * License along with this program. If not, see + * . + * #L% */ -package soot; import com.google.common.base.Preconditions; @@ -120,10 +125,12 @@ public static SootMethodRef makeLambdaHelper(List bootstrapArgs tclass.setSuperclass(Scene.v().getObjectType().getSootClass()); tclass.addInterface(iface); - if (serializable) + if (serializable) { tclass.addInterface(RefType.v("java.io.Serializable").getSootClass()); - for (int i = 0; i < markerInterfaces.size(); i++) + } + for (int i = 0; i < markerInterfaces.size(); i++) { tclass.addInterface(RefType.v(markerInterfaces.get(i).getValue()).getSootClass()); + } // It contains fields for all the captures in the lambda List capFields = new ArrayList(capTypes.size()); @@ -202,8 +209,9 @@ public ThunkMethodSource(List capFields, List paramTypes, Type } public Body getBody(SootMethod m, String phaseName) { - if (!phaseName.equals("jb")) + if (!phaseName.equals("jb")) { throw new Error("unsupported body type: " + phaseName); + } SootClass tclass = m.getDeclaringClass(); JimpleBody jb = Jimple.v().newBody(m); @@ -297,8 +305,9 @@ public BridgeMethodSource(List paramTypes, List targetTypes, Type re @Override public Body getBody(SootMethod m, String phaseName) { - if (!phaseName.equals("jb")) + if (!phaseName.equals("jb")) { throw new Error("unsupported body type: " + phaseName); + } SootClass tclass = m.getDeclaringClass(); JimpleBody jb = Jimple.v().newBody(m); diff --git a/src/main/java/soot/asm/AsmMethodSource.java b/src/main/java/soot/asm/AsmMethodSource.java index 54ebfa04366..068912c355a 100644 --- a/src/main/java/soot/asm/AsmMethodSource.java +++ b/src/main/java/soot/asm/AsmMethodSource.java @@ -292,10 +292,12 @@ final class AsmMethodSource implements MethodSource { private static final Operand DWORD_DUMMY = new Operand(null, null); - private static final String METAFACTORY_SIGNATURE - = ""; - private static final String ALT_METAFACTORY_SIGNATURE - = ""; + private static final String METAFACTORY_SIGNATURE = ""; + private static final String ALT_METAFACTORY_SIGNATURE = ""; /* -const fields- */ private final int maxLocals; private final InsnList instructions; @@ -1411,8 +1413,9 @@ private void convertInvokeDynamicInsn(InvokeDynamicInsnNode insn) { SootMethodRef bootstrap_model = null; String bsmMethodRefStr = bsmMethodRef.toString(); - if (bsmMethodRefStr.equals(METAFACTORY_SIGNATURE) || bsmMethodRefStr.equals(ALT_METAFACTORY_SIGNATURE)) + if (bsmMethodRefStr.equals(METAFACTORY_SIGNATURE) || bsmMethodRefStr.equals(ALT_METAFACTORY_SIGNATURE)) { bootstrap_model = LambdaMetaFactory.makeLambdaHelper(bsmMethodArgs, insn.bsm.getTag(), insn.name, types); + } InvokeExpr indy; diff --git a/src/systemTest/java/soot/lambdaMetaFactory/LambdaMetaFactoryCHATest.java b/src/systemTest/java/soot/lambdaMetaFactory/LambdaMetaFactoryCHATest.java index 25b1bdd23a0..407b145b60c 100644 --- a/src/systemTest/java/soot/lambdaMetaFactory/LambdaMetaFactoryCHATest.java +++ b/src/systemTest/java/soot/lambdaMetaFactory/LambdaMetaFactoryCHATest.java @@ -1,5 +1,27 @@ package soot.lambdaMetaFactory; +/*- + * #%L + * Soot - a J*va Optimization Framework + * %% + * Copyright (C) 2018 Manuel Benz + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 2.1 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Lesser Public License for more details. + * + * You should have received a copy of the GNU General Lesser Public + * License along with this program. If not, see + * . + * #L% + */ + import com.google.common.collect.Lists; import java.util.List; diff --git a/src/systemTest/java/soot/lambdaMetaFactory/LambdaMetaFactoryRTATest.java b/src/systemTest/java/soot/lambdaMetaFactory/LambdaMetaFactoryRTATest.java index 2383599da55..f92778fba28 100644 --- a/src/systemTest/java/soot/lambdaMetaFactory/LambdaMetaFactoryRTATest.java +++ b/src/systemTest/java/soot/lambdaMetaFactory/LambdaMetaFactoryRTATest.java @@ -1,5 +1,27 @@ package soot.lambdaMetaFactory; +/*- + * #%L + * Soot - a J*va Optimization Framework + * %% + * Copyright (C) 2018 Manuel Benz + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 2.1 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Lesser Public License for more details. + * + * You should have received a copy of the GNU General Lesser Public + * License along with this program. If not, see + * . + * #L% + */ + import com.google.common.collect.Lists; import java.util.List; diff --git a/src/systemTest/java/soot/lambdaMetaFactory/LambdaMetaFactorySPARKTest.java b/src/systemTest/java/soot/lambdaMetaFactory/LambdaMetaFactorySPARKTest.java index 2387e196f7c..4b17763e9ac 100644 --- a/src/systemTest/java/soot/lambdaMetaFactory/LambdaMetaFactorySPARKTest.java +++ b/src/systemTest/java/soot/lambdaMetaFactory/LambdaMetaFactorySPARKTest.java @@ -1,5 +1,27 @@ package soot.lambdaMetaFactory; +/*- + * #%L + * Soot - a J*va Optimization Framework + * %% + * Copyright (C) 2018 Manuel Benz + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 2.1 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Lesser Public License for more details. + * + * You should have received a copy of the GNU General Lesser Public + * License along with this program. If not, see + * . + * #L% + */ + import com.google.common.collect.Lists; import java.util.List; diff --git a/src/systemTest/java/soot/lambdaMetaFactory/LambdaMetaFactoryVTATest.java b/src/systemTest/java/soot/lambdaMetaFactory/LambdaMetaFactoryVTATest.java index cca64b0f943..1ff5f36fc21 100644 --- a/src/systemTest/java/soot/lambdaMetaFactory/LambdaMetaFactoryVTATest.java +++ b/src/systemTest/java/soot/lambdaMetaFactory/LambdaMetaFactoryVTATest.java @@ -1,5 +1,27 @@ package soot.lambdaMetaFactory; +/*- + * #%L + * Soot - a J*va Optimization Framework + * %% + * Copyright (C) 2018 Manuel Benz + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 2.1 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Lesser Public License for more details. + * + * You should have received a copy of the GNU General Lesser Public + * License along with this program. If not, see + * . + * #L% + */ + import com.google.common.collect.Lists; import java.util.List; diff --git a/src/systemTest/targets/soot/lambdaMetaFactory/jcg/Lambda1.java b/src/systemTest/targets/soot/lambdaMetaFactory/jcg/Lambda1.java index e2038e9c286..ce2c88b6cf8 100644 --- a/src/systemTest/targets/soot/lambdaMetaFactory/jcg/Lambda1.java +++ b/src/systemTest/targets/soot/lambdaMetaFactory/jcg/Lambda1.java @@ -1,7 +1,32 @@ package soot.lambdaMetaFactory.jcg; +/*- + * #%L + * Soot - a J*va Optimization Framework + * %% + * Copyright (C) 2018 Manuel Benz + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 2.1 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Lesser Public License for more details. + * + * You should have received a copy of the GNU General Lesser Public + * License along with this program. If not, see + * . + * #L% + */ + import java.util.function.Function; +/** + * Copy from the 'Java Call Graph Test Suite (JCG)' project: https://bitbucket.org/delors/jcg/src/master/ + */ class Lambda1 { private static void doSomething() { // call in lambda diff --git a/src/systemTest/targets/soot/lambdaMetaFactory/jcg/package-info.java b/src/systemTest/targets/soot/lambdaMetaFactory/jcg/package-info.java index db91df69fb5..6ed1b18b336 100644 --- a/src/systemTest/targets/soot/lambdaMetaFactory/jcg/package-info.java +++ b/src/systemTest/targets/soot/lambdaMetaFactory/jcg/package-info.java @@ -5,4 +5,26 @@ * * @author Manuel Benz created on 31.10.18 */ -package soot.lambdaMetaFactory.jcg; \ No newline at end of file +package soot.lambdaMetaFactory.jcg; + +/*- + * #%L + * Soot - a J*va Optimization Framework + * %% + * Copyright (C) 2018 Manuel Benz + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 2.1 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Lesser Public License for more details. + * + * You should have received a copy of the GNU General Lesser Public + * License along with this program. If not, see + * . + * #L% + */ \ No newline at end of file From 7dbba98141e14a097039ad549ee82994b2b9e960 Mon Sep 17 00:00:00 2001 From: Jon Mathews Date: Fri, 30 Nov 2018 14:43:13 -0600 Subject: [PATCH 12/12] Implement parameter adaptation in LambdaMetaFactory Implemented modeling of parameter adaptation. This is required to handle conversion of primitives to their wrapper types (boxing and unboxing). Enabled writing generated classes to another format, such as .class. Generated class names follow an inner class naming pattern. Removed BridgeMethodSource; ThunkMethodSource handles all cases. The samMethodType and the bridges are signatures to be implemented by the functional object, and each one delegates to the implMethod. --- src/main/java/soot/LambdaMetaFactory.java | 637 ++++++++++++++---- src/main/java/soot/asm/AsmMethodSource.java | 4 +- .../toolkits/annotation/LineNumberAdder.java | 4 +- 3 files changed, 495 insertions(+), 150 deletions(-) diff --git a/src/main/java/soot/LambdaMetaFactory.java b/src/main/java/soot/LambdaMetaFactory.java index 023a2945f7b..4da1eb34eab 100644 --- a/src/main/java/soot/LambdaMetaFactory.java +++ b/src/main/java/soot/LambdaMetaFactory.java @@ -23,42 +23,64 @@ * #L% */ -import com.google.common.base.Preconditions; - import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; import java.util.List; +import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import soot.javaToJimple.LocalGenerator; import soot.jimple.ClassConstant; -import soot.jimple.IdentityStmt; import soot.jimple.IntConstant; import soot.jimple.InvokeExpr; +import soot.jimple.InvokeStmt; import soot.jimple.Jimple; import soot.jimple.JimpleBody; import soot.jimple.MethodHandle; import soot.jimple.MethodType; -import soot.jimple.VirtualInvokeExpr; +import soot.jimple.NewExpr; +import soot.jimple.ParameterRef; +import soot.jimple.SpecialInvokeExpr; +import soot.jimple.toolkits.scalar.LocalNameStandardizer; +import soot.util.Chain; +import soot.util.HashChain; public final class LambdaMetaFactory { private static final Logger LOGGER = LoggerFactory.getLogger(LambdaMetaFactory.class); private static int uniq; - public static SootMethodRef makeLambdaHelper(List bootstrapArgs, int tag, String name, Type[] types) { + /** + * + * @param bootstrapArgs + * @param tag + * @param name + * @param invokedType + * types of captured arguments, the last element is always the type of the FunctionalInterface + * @param enclosingClassname + * @return + */ + // FIXME: synchronized to work around concurrency errors; possibly covering up actual problems + public synchronized static SootMethodRef makeLambdaHelper(List bootstrapArgs, int tag, String name, + Type[] invokedType, SootClass enclosingClass) { if (bootstrapArgs.size() < 3 || !(bootstrapArgs.get(0) instanceof MethodType) || !(bootstrapArgs.get(1) instanceof MethodHandle) || !(bootstrapArgs.get(2) instanceof MethodType) || (bootstrapArgs.size() > 3 && !(bootstrapArgs.get(3) instanceof IntConstant))) { - LOGGER.warn("LambdaMetaFactory: unexpected arguments for LambdaMetaFactor.metaFactory: {}", bootstrapArgs); + LOGGER.warn("LambdaMetaFactory: unexpected arguments for LambdaMetaFactory.metaFactory: {}", bootstrapArgs); return null; } - + /** implemented method type */ MethodType samMethodType = ((MethodType) bootstrapArgs.get(0)); - SootMethodRef implMethod = ((MethodHandle) bootstrapArgs.get(1)).getMethodRef(); + + /** the MethodHandle providing the implementation */ + MethodHandle implMethod = ((MethodHandle) bootstrapArgs.get(1)); + + /** allows restrictions on invocation */ MethodType instantiatedMethodType = ((MethodType) bootstrapArgs.get(2)); int flags = 0; @@ -111,20 +133,40 @@ public static SootMethodRef makeLambdaHelper(List bootstrapArgs } } - List capTypes = Arrays.asList(types).subList(0, types.length - 1); - if (!(types[types.length - 1] instanceof RefType)) { - LOGGER.warn("unexpected interface type: " + types[types.length - 1]); + List capTypes = Arrays.asList(invokedType).subList(0, invokedType.length - 1); + if (!(invokedType[invokedType.length - 1] instanceof RefType)) { + LOGGER.warn("unexpected interface type: " + invokedType[invokedType.length - 1]); return null; } - SootClass iface = ((RefType) types[types.length - 1]).getSootClass(); + SootClass functionalInterfaceToImplement = ((RefType) invokedType[invokedType.length - 1]).getSootClass(); // Our thunk class implements the functional interface - String className = "soot.dummy." + implMethod.name() + "$" + uniqSupply(); - SootClass tclass = new SootClass(className); + String enclosingClassname = enclosingClass.getName(); + String enclosingClassnamePrefix = null; + if (enclosingClassname == null || enclosingClassname.equals("")) { + enclosingClassnamePrefix = "soot.dummy."; + } else { + enclosingClassnamePrefix = enclosingClassname + "$"; + } + String className; + final boolean readableClassnames = true; + if (readableClassnames) { + // class names cannot contain <> + String implMethodName = implMethod.getMethodRef().name(); + String dummyName = "".equals(implMethodName) ? "init" : implMethodName; + // XXX: $ causes confusion in inner class inference; remove for now + dummyName = dummyName.replaceAll("\\$", "_"); + className = enclosingClassnamePrefix + dummyName + "__" + uniqSupply(); + } else { + className = "soot.dummy.lambda" + uniqSupply(); + } + SootClass tclass = new SootClass(className); + tclass.setModifiers(Modifier.PUBLIC | Modifier.FINAL); tclass.setSuperclass(Scene.v().getObjectType().getSootClass()); - tclass.addInterface(iface); + tclass.addInterface(functionalInterfaceToImplement); + // additions from altMetafactory if (serializable) { tclass.addInterface(RefType.v("java.io.Serializable").getSootClass()); } @@ -140,39 +182,45 @@ public static SootMethodRef makeLambdaHelper(List bootstrapArgs tclass.addField(f); } - List typedParameterTypes = instantiatedMethodType.getParameterTypes(); - Type retType = instantiatedMethodType.getReturnType(); - ThunkMethodSource ms = new ThunkMethodSource(capFields, typedParameterTypes, retType, implMethod); + // if the implMethod is a new private static in the enclosing class, make it public access so + // it can be invoked from the thunk class + if (MethodHandle.Kind.REF_INVOKE_STATIC.getValue() == implMethod.getKind()) { + if (implMethod.getMethodRef().declaringClass().getName().equals(enclosingClassname)) { + SootMethod method + = implMethod.getMethodRef().declaringClass().getMethod(implMethod.getMethodRef().getSubSignature()); + int modifiers = method.getModifiers() & ~Modifier.PRIVATE; + modifiers = modifiers | Modifier.PUBLIC; + method.setModifiers(modifiers); + } + } - // create a method with the concrete, non-erased types first - SootMethod apply = new SootMethod(name, typedParameterTypes, retType); - tclass.addMethod(apply); - apply.setSource(ms); + MethodSource ms = new ThunkMethodSource(capFields, samMethodType, implMethod, instantiatedMethodType); // Bootstrap method creates a new instance of this class - SootMethod tboot = new SootMethod("bootstrap$", capTypes, iface.getType(), Modifier.STATIC); + SootMethod tboot = new SootMethod("bootstrap$", capTypes, functionalInterfaceToImplement.getType(), + Modifier.PUBLIC | Modifier.STATIC); tclass.addMethod(tboot); tboot.setSource(ms); // Constructor just copies the captures - SootMethod tctor = new SootMethod("", capTypes, VoidType.v()); + SootMethod tctor = new SootMethod("", capTypes, VoidType.v(), Modifier.PUBLIC); tclass.addMethod(tctor); tctor.setSource(ms); - boolean isGeneric = !samMethodType.equals(instantiatedMethodType); - if (isGeneric) { - // if the functional interface is generic in it' return-/parameter-types, we have to create bridging method which can - // work with plain objects and re-routes the call to the actual thunk method - addBridge(name, tclass, samMethodType, instantiatedMethodType, apply); - } + // Dispatch runs the 'real' method implementing the body of the lambda + addDispatch(name, tclass, samMethodType, instantiatedMethodType, capFields, implMethod); + // For each bridge MethodType, add another dispatch method which calls the 'real' method for (int i = 0; i < bridges.size(); i++) { final MethodType bridgeType = bridges.get(i); - addBridge(name, tclass, bridgeType, instantiatedMethodType, apply); + addDispatch(name, tclass, bridgeType, instantiatedMethodType, capFields, implMethod); } Scene.v().addClass(tclass); - // The hierarchy has to be rebuild after adding the MetaFactory implementation + if (enclosingClass.isApplicationClass()) + tclass.setApplicationClass(); + + // The hierarchy has to be rebuilt after adding the MetaFactory implementation // soot.FastHierarchy.canStoreClass will otherwise fail due to not having an interval set for the class which eventually // leads to the MetaFactory not being accepted as implementation of the functional interface it actually implements // which, in turn, lead to missing edges in the call graph @@ -181,14 +229,12 @@ public static SootMethodRef makeLambdaHelper(List bootstrapArgs return tboot.makeRef(); } - private static void addBridge(String name, SootClass tclass, MethodType bridgeType, MethodType targetType, - SootMethod apply) { - final List bridgeParams = bridgeType.getParameterTypes(); - final Type bridgeReturn = bridgeType.getReturnType(); - SootMethod genericBridge = new SootMethod(name, bridgeParams, bridgeReturn); - tclass.addMethod(genericBridge); - genericBridge.setSource(new BridgeMethodSource(bridgeParams, targetType.getParameterTypes(), bridgeReturn, - targetType.getReturnType(), apply.makeRef())); + private static void addDispatch(String name, SootClass tclass, MethodType implMethodType, + MethodType instantiatedMethodType, List capFields, MethodHandle implMethod) { + ThunkMethodSource ms = new ThunkMethodSource(capFields, implMethodType, implMethod, instantiatedMethodType); + SootMethod m = new SootMethod(name, implMethodType.getParameterTypes(), implMethodType.getReturnType(), Modifier.PUBLIC); + tclass.addMethod(m); + m.setSource(ms); } private static synchronized long uniqSupply() { @@ -196,16 +242,23 @@ private static synchronized long uniqSupply() { } private static class ThunkMethodSource implements MethodSource { + /** + * fields storing capture variables, in the order they appear in invokedType; to be prepended at target invocation site + */ private List capFields; - private List paramTypes; - private Type retType; - private SootMethodRef implMethod; - - public ThunkMethodSource(List capFields, List paramTypes, Type retType, SootMethodRef implMethod) { + /** MethodType of method to implemented by function object; either samMethodType or bridgeMethodType **/ + private MethodType implMethodType; + /** implMethod - the MethodHandle providing the implementation */ + private MethodHandle implMethod; + /** allows restrictions on invocation */ + private MethodType instantiatedMethodType; + + public ThunkMethodSource(List capFields, MethodType implMethodType, MethodHandle implMethod, + MethodType instantiatedMethodType) { this.capFields = capFields; - this.paramTypes = paramTypes; - this.retType = retType; + this.implMethodType = implMethodType; this.implMethod = implMethod; + this.instantiatedMethodType = instantiatedMethodType; } public Body getBody(SootMethod m, String phaseName) { @@ -215,139 +268,427 @@ public Body getBody(SootMethod m, String phaseName) { SootClass tclass = m.getDeclaringClass(); JimpleBody jb = Jimple.v().newBody(m); - PatchingChain us = jb.getUnits(); - LocalGenerator lc = new LocalGenerator(jb); if (m.getName().equals("")) { - Local l = lc.generateLocal(tclass.getType()); - us.add(Jimple.v().newIdentityStmt(l, Jimple.v().newThisRef(tclass.getType()))); - us.add(Jimple.v() - .newInvokeStmt(Jimple.v().newSpecialInvokeExpr(l, - Scene.v().makeConstructorRef(Scene.v().getObjectType().getSootClass(), Collections.emptyList()), - Collections.emptyList()))); - for (SootField f : capFields) { - int i = us.size() - 2; - Local l2 = lc.generateLocal(f.getType()); - us.add(Jimple.v().newIdentityStmt(l2, Jimple.v().newParameterRef(f.getType(), i))); - us.add(Jimple.v().newAssignStmt(Jimple.v().newInstanceFieldRef(l, f.makeRef()), l2)); - } - us.add(Jimple.v().newReturnVoidStmt()); + getInitBody(tclass, jb); } else if (m.getName().equals("bootstrap$")) { - Local l = lc.generateLocal(tclass.getType()); - Value val = Jimple.v().newNewExpr(tclass.getType()); - us.add(Jimple.v().newAssignStmt(l, val)); - us.add(Jimple.v().newInvokeStmt(Jimple.v().newSpecialInvokeExpr(l, - Scene.v().makeConstructorRef(tclass, Collections.emptyList()), Collections.emptyList()))); - us.add(Jimple.v().newReturnStmt(l)); + getBootstrapBody(tclass, jb); } else { - Local this_ = lc.generateLocal(tclass.getType()); - us.add(Jimple.v().newIdentityStmt(this_, Jimple.v().newThisRef(tclass.getType()))); - - List args = new ArrayList(); + getInvokeBody(tclass, jb); + } - for (SootField f : capFields) { - Local l = lc.generateLocal(f.getType()); - us.add(Jimple.v().newAssignStmt(l, Jimple.v().newInstanceFieldRef(l, f.makeRef()))); - args.add(l); - } + // rename locals consistent with JimpleBodyPack + LocalNameStandardizer.v().transform(jb); + return jb; + } - for (int i = 0; i < paramTypes.size(); i++) { - final Type ty = paramTypes.get(i); - Local l = lc.generateLocal(ty); - us.add(Jimple.v().newIdentityStmt(l, Jimple.v().newParameterRef(ty, i))); - args.add(l); - } + /** + * Thunk class init (constructor) + * + * @param tclass + * thunk class + * @param jb + */ + private void getInitBody(SootClass tclass, JimpleBody jb) { + PatchingChain us = jb.getUnits(); + LocalGenerator lc = new LocalGenerator(jb); - // distinguish between static and instance invocations of the lambda body - final InvokeExpr invoke; - if (implMethod.isStatic()) { - invoke = Jimple.v().newStaticInvokeExpr(implMethod, args); - } else { - // in case of instance invoke, the first argument is the actual receiver of the call - final Local base = args.remove(0); - - if (implMethod.declaringClass().isInterface()) { - invoke = Jimple.v().newInterfaceInvokeExpr(base, implMethod, args); - } else { - invoke = Jimple.v().newVirtualInvokeExpr(base, implMethod, args); - } - } + // @this + Local l = lc.generateLocal(tclass.getType()); + us.add(Jimple.v().newIdentityStmt(l, Jimple.v().newThisRef(tclass.getType()))); + + // @parameters + Chain capLocals = new HashChain<>(); + int i = 0; + for (SootField f : capFields) { + Local l2 = lc.generateLocal(f.getType()); + us.add(Jimple.v().newIdentityStmt(l2, Jimple.v().newParameterRef(f.getType(), i))); + capLocals.add(l2); + i++; + } - if (retType == VoidType.v()) { - us.add(Jimple.v().newInvokeStmt(invoke)); - us.add(Jimple.v().newReturnVoidStmt()); - } else { - Local ret = lc.generateLocal(retType); - us.add(Jimple.v().newAssignStmt(ret, invoke)); - us.add(Jimple.v().newReturnStmt(ret)); - } + // super java.lang.Object. + us.add(Jimple.v() + .newInvokeStmt(Jimple.v().newSpecialInvokeExpr(l, + Scene.v().makeConstructorRef(Scene.v().getObjectType().getSootClass(), Collections.emptyList()), + Collections.emptyList()))); + + // assign parameters to fields + Iterator localItr = capLocals.iterator(); + for (SootField f : capFields) { + Local l2 = localItr.next(); + us.add(Jimple.v().newAssignStmt(Jimple.v().newInstanceFieldRef(l, f.makeRef()), l2)); } - return jb; - } - } - private static class BridgeMethodSource implements MethodSource { - private final List paramTypes; - private final List targetTypes; - private final Type retType; - private final SootMethodRef apply; - private final Type targetRetType; - - public BridgeMethodSource(List paramTypes, List targetTypes, Type retType, Type targetRetType, - SootMethodRef apply) { - Preconditions.checkArgument(paramTypes.size() == targetTypes.size()); - this.paramTypes = paramTypes; - this.targetTypes = targetTypes; - this.retType = retType; - this.targetRetType = targetRetType; - this.apply = apply; + us.add(Jimple.v().newReturnVoidStmt()); } - @Override - public Body getBody(SootMethod m, String phaseName) { - if (!phaseName.equals("jb")) { - throw new Error("unsupported body type: " + phaseName); + private void getBootstrapBody(SootClass tclass, JimpleBody jb) { + PatchingChain us = jb.getUnits(); + LocalGenerator lc = new LocalGenerator(jb); + + List capValues = new ArrayList(); + List capTypes = new ArrayList(); + int i = 0; + for (SootField capField : capFields) { + Type type = capField.getType(); + capTypes.add(type); + Local p = lc.generateLocal(type); + ParameterRef pref = Jimple.v().newParameterRef(type, i); + us.add(Jimple.v().newIdentityStmt(p, pref)); + capValues.add(p); + i++; } + Local l = lc.generateLocal(tclass.getType()); + Value val = Jimple.v().newNewExpr(tclass.getType()); + us.add(Jimple.v().newAssignStmt(l, val)); + us.add(Jimple.v() + .newInvokeStmt(Jimple.v().newSpecialInvokeExpr(l, Scene.v().makeConstructorRef(tclass, capTypes), capValues))); + us.add(Jimple.v().newReturnStmt(l)); + } - SootClass tclass = m.getDeclaringClass(); - JimpleBody jb = Jimple.v().newBody(m); + /** + * Adds method which implements functional interface and invokes target implementation. + * + * @param tclass + * @param jb + */ + private void getInvokeBody(SootClass tclass, JimpleBody jb) { PatchingChain us = jb.getUnits(); LocalGenerator lc = new LocalGenerator(jb); + // @this Local this_ = lc.generateLocal(tclass.getType()); - final IdentityStmt thisIdentity = Jimple.v().newIdentityStmt(this_, Jimple.v().newThisRef(tclass.getType())); - us.addFirst(thisIdentity); + us.add(Jimple.v().newIdentityStmt(this_, Jimple.v().newThisRef(tclass.getType()))); + + // @parameter for direct arguments + Chain samParamLocals = new HashChain<>(); + int i = 0; + for (Type ty : implMethodType.getParameterTypes()) { + Local l = lc.generateLocal(ty); + us.add(Jimple.v().newIdentityStmt(l, Jimple.v().newParameterRef(ty, i))); + samParamLocals.add(l); + i++; + } + + // narrowing casts to match instantiatedMethodType + Iterator iptItr = instantiatedMethodType.getParameterTypes().iterator(); + Chain instParamLocals = new HashChain<>(); + for (Local l : samParamLocals) { + Type ipt = iptItr.next(); + Local l2 = narrowingReferenceConversion(l, ipt, jb, us, lc); + instParamLocals.add(l2); + } List args = new ArrayList(); - for (int i = 0; i < paramTypes.size(); i++) { - final Type paramType = paramTypes.get(i); - final Type targetType = targetTypes.get(i); + // captured arguments + for (SootField f : capFields) { + Local l = lc.generateLocal(f.getType()); + us.add(Jimple.v().newAssignStmt(l, Jimple.v().newInstanceFieldRef(this_, f.makeRef()))); + args.add(l); + } + + // direct arguments + + // The MethodHandle's first argument is the receiver, if it has one. + // If there are no captured arguments, use the first parameter as the receiver. + int kind = implMethod.getKind(); + boolean needsReceiver = false; + if (MethodHandle.Kind.REF_INVOKE_INTERFACE.getValue() == kind + || MethodHandle.Kind.REF_INVOKE_VIRTUAL.getValue() == kind + || MethodHandle.Kind.REF_INVOKE_SPECIAL.getValue() == kind) { + // NOTE: for a method reference to a constructor, the receiver is not needed because it's the new object + needsReceiver = true; + } + Iterator iplItr = instParamLocals.iterator(); + if (capFields.size() == 0 && iplItr.hasNext() && needsReceiver) { + RefType receiverType = implMethod.getMethodRef().declaringClass().getType(); + Local l = adapt(iplItr.next(), receiverType, jb, us, lc); + args.add(l); + } + + int j = args.size(); + if (needsReceiver) { + // assert: if there is a receiver, it is already filled, but the alignment to parameters is off by 1 + j = args.size() - 1; + } + while (iplItr.hasNext()) { + Local pl = iplItr.next(); + + Type to = implMethod.getMethodRef().parameterType(j); + + Local l = adapt(pl, to, jb, us, lc); + args.add(l); + j++; + } + + invokeImplMethod(jb, us, lc, args); + } + + private Local adapt(Local fromLocal, Type to, JimpleBody jb, PatchingChain us, LocalGenerator lc) { - Local paramLocal = lc.generateLocal(paramType); - us.insertAfter(Jimple.v().newIdentityStmt(paramLocal, Jimple.v().newParameterRef(paramType, i)), thisIdentity); + Type from = fromLocal.getType(); + + // Implements JLS 5.3 Method Invocation Context for adapting arguments from lambda expression to + // formal arguments of target implementation + + // an identity conversion (§5.1.1) + if (from.equals(to)) { + return fromLocal; + } - final Local targetLocal = lc.generateLocal(targetType); - us.add(Jimple.v().newAssignStmt(targetLocal, Jimple.v().newCastExpr(paramLocal, targetType))); + if (from instanceof ArrayType) { + return wideningReferenceConversion(fromLocal); + } - args.add(targetLocal); + if (from instanceof RefType && to instanceof RefType) { + return wideningReferenceConversion(fromLocal); } - final VirtualInvokeExpr invoke = Jimple.v().newVirtualInvokeExpr(this_, apply, args); + if (from instanceof PrimType) { + if (to instanceof PrimType) { + // a widening primitive conversion (§5.1.2) + return wideningPrimitiveConversion(fromLocal, to, jb, us, lc); + } else { + // a boxing conversion (§5.1.7) + // a boxing conversion followed by widening reference conversion + + // from is PrimType + // to is RefType + Local boxed = box(fromLocal, jb, us, lc); + return wideningReferenceConversion(boxed); + } + } else { + // an unboxing conversion (§5.1.8) + // an unboxing conversion followed by a widening primitive conversion + + // from is RefType + // to is PrimType + if (!(to instanceof PrimType)) { + throw new IllegalArgumentException("Expected 'to' to be a PrimType"); + } - if (retType == VoidType.v()) { - us.add(Jimple.v().newInvokeStmt(invoke)); + Local unboxed = unbox(fromLocal, jb, us, lc); + return wideningPrimitiveConversion(unboxed, to, jb, us, lc); + } + } + + /** + * P box = P.valueOf(fromLocal); + * + * @param fromLocal + * primitive + * @param jb + * @param us + * @return + */ + private Local box(Local fromLocal, JimpleBody jb, PatchingChain us, LocalGenerator lc) { + PrimType primitiveType = (PrimType) fromLocal.getType(); + RefType wrapperType = primitiveType.boxedType(); + + SootMethod valueOfMethod = Wrapper.valueOf.get(primitiveType); + + Local lBox = lc.generateLocal(wrapperType); + us.add(Jimple.v().newAssignStmt(lBox, Jimple.v().newStaticInvokeExpr(valueOfMethod.makeRef(), fromLocal))); + + return lBox; + } + + /** + * p unbox = fromLocal.pValue(); + * + * @param fromLocal + * boxed + * @param jb + * @param us + * @return + */ + private Local unbox(Local fromLocal, JimpleBody jb, PatchingChain us, LocalGenerator lc) { + RefType wrapperType = (RefType) fromLocal.getType(); + PrimType primitiveType = Wrapper.wrapperTypes.get(wrapperType); + + SootMethod primitiveValueMethod = Wrapper.primitiveValue.get(wrapperType); + + Local lUnbox = lc.generateLocal(primitiveType); + us.add(Jimple.v().newAssignStmt(lUnbox, Jimple.v().newVirtualInvokeExpr(fromLocal, primitiveValueMethod.makeRef()))); + + return lUnbox; + } + + private Local wideningReferenceConversion(Local fromLocal) { + // a widening reference conversion (JLS §5.1.5) + // TODO: confirm that 'from' is a subtype of 'to' + return fromLocal; + } + + /** + * T t = (T) fromLocal; + * + * @param fromLocal + * @param to + * @param jb + * @param us + * @return + */ + private Local narrowingReferenceConversion(Local fromLocal, Type to, JimpleBody jb, PatchingChain us, + LocalGenerator lc) { + if (fromLocal.getType().equals(to)) { + return fromLocal; + } + + if (!(fromLocal.getType() instanceof RefType || fromLocal.getType() instanceof ArrayType)) { + return fromLocal; + } + // throw new IllegalArgumentException("Expected source to have reference type"); + if (!(to instanceof RefType || to instanceof ArrayType)) { + return fromLocal; + // throw new IllegalArgumentException("Expected target to have reference type"); + } + + Local l2 = lc.generateLocal(to); + us.add(Jimple.v().newAssignStmt(l2, Jimple.v().newCastExpr(fromLocal, to))); + return l2; + } + + /** + * T t = (T) fromLocal; + * + * @param fromLocal + * @param to + * @param jb + * @param us + * @return + */ + private Local wideningPrimitiveConversion(Local fromLocal, Type to, JimpleBody jb, PatchingChain us, + LocalGenerator lc) { + if (!(fromLocal.getType() instanceof PrimType)) { + throw new IllegalArgumentException("Expected source to have primitive type"); + } + if (!(to instanceof PrimType)) { + throw new IllegalArgumentException("Expected target to have primitive type"); + } + + Local l2 = lc.generateLocal(to); + us.add(Jimple.v().newAssignStmt(l2, Jimple.v().newCastExpr(fromLocal, to))); + return l2; + } + + /** + * Invocation of target implementation method. + * + * @param jb + * @param us + * @param args + */ + private void invokeImplMethod(JimpleBody jb, PatchingChain us, LocalGenerator lc, List args) { + Value value = _invokeImplMethod(jb, us, lc, args); + + if (value instanceof InvokeExpr && soot.VoidType.v().equals(implMethod.getMethodRef().returnType())) { + // implementation method is void + us.add(Jimple.v().newInvokeStmt(value)); + us.add(Jimple.v().newReturnVoidStmt()); + } else if (soot.VoidType.v().equals(implMethodType.getReturnType())) { + // dispatch method is void + us.add(Jimple.v().newInvokeStmt(value)); us.add(Jimple.v().newReturnVoidStmt()); } else { - final Local nonCastRet = lc.generateLocal(targetRetType); - us.add(Jimple.v().newAssignStmt(nonCastRet, invoke)); + // neither is void, must pass through return value + + Local ret = lc.generateLocal(value.getType()); + us.add(Jimple.v().newAssignStmt(ret, value)); - final Local ret = lc.generateLocal(retType); - us.add(Jimple.v().newAssignStmt(ret, Jimple.v().newCastExpr(nonCastRet, retType))); - us.add(Jimple.v().newReturnStmt(ret)); + // adapt return value + Local retAdapted = adapt(ret, implMethodType.getReturnType(), jb, us, lc); + us.add(Jimple.v().newReturnStmt(retAdapted)); } + } - return jb; + private Value _invokeImplMethod(JimpleBody jb, PatchingChain us, LocalGenerator lc, List args) { + // A lambda capturing 'this' may be implemented by a private instance method. + // A method reference to an instance method may be implemented by the instance method itself. + // To use the correct invocation style, resolve the method and determine how the compiler + // implemented the lambda or method reference. + + SootMethodRef methodRef = implMethod.getMethodRef(); + MethodHandle.Kind k = MethodHandle.Kind.getKind(implMethod.getKind()); + switch (k) { + case REF_INVOKE_STATIC: + return Jimple.v().newStaticInvokeExpr(methodRef, args); + case REF_INVOKE_INTERFACE: + return Jimple.v().newInterfaceInvokeExpr(args.get(0), methodRef, rest(args)); + case REF_INVOKE_VIRTUAL: + return Jimple.v().newVirtualInvokeExpr(args.get(0), methodRef, rest(args)); + case REF_INVOKE_SPECIAL: + // e.g. private + return Jimple.v().newSpecialInvokeExpr(args.get(0), methodRef, rest(args)); + case REF_INVOKE_CONSTRUCTOR: + RefType type = methodRef.declaringClass().getType(); + NewExpr newRef = Jimple.v().newNewExpr(type); + Local newLocal = lc.generateLocal(type); + us.add(Jimple.v().newAssignStmt(newLocal, newRef)); + // NOTE: args does not include the receiver + SpecialInvokeExpr specialInvokeExpr = Jimple.v().newSpecialInvokeExpr(newLocal, methodRef, args); + InvokeStmt invokeStmt = Jimple.v().newInvokeStmt(specialInvokeExpr); + us.add(invokeStmt); + + return newLocal; + case REF_GET_FIELD: + case REF_GET_FIELD_STATIC: + case REF_PUT_FIELD: + case REF_PUT_FIELD_STATIC: + default: + } + throw new IllegalArgumentException("Unexpected MethodHandle.Kind " + implMethod.getKind()); + } + + private List rest(List args) { + int first = 1; + int last = args.size(); + if (last < first) { + return Collections.emptyList(); + } + return args.subList(first, last); } } + + private static class Wrapper { + + private static boolean isWrapper(Type t) { + return wrapperTypes.containsKey(t); + } + + private static Map wrapperTypes; + /** valueOf(primitive) method signature */ + private static Map valueOf; + /** primitiveValue() method signature */ + private static Map primitiveValue; + static { + PrimType[] tmp = { BooleanType.v(), ByteType.v(), CharType.v(), DoubleType.v(), FloatType.v(), IntType.v(), + LongType.v(), ShortType.v() }; + wrapperTypes = new HashMap<>(); + valueOf = new HashMap<>(); + primitiveValue = new HashMap<>(); + for (PrimType primType : tmp) { + RefType wrapperType = primType.boxedType(); + String cn = wrapperType.getClassName(); + + wrapperTypes.put(wrapperType, primType); + + String valueOfMethodSignature = cn + " valueOf(" + primType.toString() + ")"; + SootMethod valueOfMethod = wrapperType.getSootClass().getMethod(valueOfMethodSignature); + valueOf.put(primType, valueOfMethod); + + String primitiveValueMethodSignature = primType.toString() + " " + primType.toString() + "Value()"; + SootMethod primitiveValueMethod = wrapperType.getSootClass().getMethod(primitiveValueMethodSignature); + primitiveValue.put(wrapperType, primitiveValueMethod); + } + wrapperTypes = Collections.unmodifiableMap(wrapperTypes); + valueOf = Collections.unmodifiableMap(valueOf); + primitiveValue = Collections.unmodifiableMap(primitiveValue); + + } + + } + } diff --git a/src/main/java/soot/asm/AsmMethodSource.java b/src/main/java/soot/asm/AsmMethodSource.java index 068912c355a..0836949c468 100644 --- a/src/main/java/soot/asm/AsmMethodSource.java +++ b/src/main/java/soot/asm/AsmMethodSource.java @@ -1414,7 +1414,9 @@ private void convertInvokeDynamicInsn(InvokeDynamicInsnNode insn) { String bsmMethodRefStr = bsmMethodRef.toString(); if (bsmMethodRefStr.equals(METAFACTORY_SIGNATURE) || bsmMethodRefStr.equals(ALT_METAFACTORY_SIGNATURE)) { - bootstrap_model = LambdaMetaFactory.makeLambdaHelper(bsmMethodArgs, insn.bsm.getTag(), insn.name, types); + SootClass enclosingClass = body.getMethod().getDeclaringClass(); + bootstrap_model + = LambdaMetaFactory.makeLambdaHelper(bsmMethodArgs, insn.bsm.getTag(), insn.name, types, enclosingClass); } InvokeExpr indy; diff --git a/src/main/java/soot/jimple/toolkits/annotation/LineNumberAdder.java b/src/main/java/soot/jimple/toolkits/annotation/LineNumberAdder.java index 91455138254..e59035ca0f8 100644 --- a/src/main/java/soot/jimple/toolkits/annotation/LineNumberAdder.java +++ b/src/main/java/soot/jimple/toolkits/annotation/LineNumberAdder.java @@ -48,7 +48,9 @@ public static LineNumberAdder v() { public void internalTransform(String phaseName, Map opts) { - Iterator it = Scene.v().getApplicationClasses().iterator(); + // using a snapshot iterator because Application classes may change if LambdaMetaFactory translates invokedynamic to new + // classes; no need to visit new classes + Iterator it = Scene.v().getApplicationClasses().snapshotIterator(); while (it.hasNext()) { SootClass sc = (SootClass) it.next(); // make map of first line to each method