From 8675d71f84790aa59eb722d79e204fd4ae90f69c Mon Sep 17 00:00:00 2001 From: Demyan Kimitsa Date: Wed, 28 Feb 2024 16:56:58 +0200 Subject: [PATCH] * transforms unknown dynamic invoke into NoSuchMethodError (#764) currently only StringConcat and Lambda bootstraps are recognized and cause DynamicInvoke to be transformed/dessugared. in other cases DynamicInvoke instruction will stay in place and will cause compilation type exception: > Java.lang.ClassCastException: soot.jimple.internal.JDynamicInvokeExpr cannot be cast to soot.jimple.InstanceInvokeExpr Issue was risen in gitter channel in scope Scala/desirialize labda being inserted in all classes. All these classes were failed to compile. Even if this functionality is not used (https://github.com/scala/scala/pull/4501). As a workaround changed how InvokeDynamic is being handled: - introduced single InbokeDynamicCompilerPlugin; - LabdaPlugin and StringconcatRewriter plugins are made as delegates of InbokeDynamicCompilerPlugin; - all not recognized InvokeDynamic are now translated into NoSuchMethodError exceptions Co-authored-by: Tomski --- .../org/robovm/compiler/config/Config.java | 18 +- .../desugar/StringConcatRewriterPlugin.java | 117 ------- .../InvokeDynamicCompilerPlugin.java | 187 +++++++++++ .../lambda/LambdaClass.java | 2 +- .../lambda/LambdaClassGenerator.java | 2 +- .../invokedynamic/lambda/LambdaPlugin.java | 253 +++++++++++++++ .../stringconcat}/StringConcatRewriter.java | 2 +- .../StringConcatRewriterPlugin.java | 64 ++++ .../compiler/plugin/lambda/LambdaPlugin.java | 306 ------------------ 9 files changed, 510 insertions(+), 441 deletions(-) delete mode 100755 compiler/compiler/src/main/java/org/robovm/compiler/plugin/desugar/StringConcatRewriterPlugin.java create mode 100644 compiler/compiler/src/main/java/org/robovm/compiler/plugin/invokedynamic/InvokeDynamicCompilerPlugin.java rename compiler/compiler/src/main/java/org/robovm/compiler/plugin/{ => invokedynamic}/lambda/LambdaClass.java (97%) rename compiler/compiler/src/main/java/org/robovm/compiler/plugin/{ => invokedynamic}/lambda/LambdaClassGenerator.java (99%) create mode 100644 compiler/compiler/src/main/java/org/robovm/compiler/plugin/invokedynamic/lambda/LambdaPlugin.java rename compiler/compiler/src/main/java/org/robovm/compiler/plugin/{desugar => invokedynamic/stringconcat}/StringConcatRewriter.java (99%) create mode 100644 compiler/compiler/src/main/java/org/robovm/compiler/plugin/invokedynamic/stringconcat/StringConcatRewriterPlugin.java delete mode 100755 compiler/compiler/src/main/java/org/robovm/compiler/plugin/lambda/LambdaPlugin.java diff --git a/compiler/compiler/src/main/java/org/robovm/compiler/config/Config.java b/compiler/compiler/src/main/java/org/robovm/compiler/config/Config.java index b9f77ead2..c467458b1 100755 --- a/compiler/compiler/src/main/java/org/robovm/compiler/config/Config.java +++ b/compiler/compiler/src/main/java/org/robovm/compiler/config/Config.java @@ -24,17 +24,7 @@ import org.robovm.compiler.clazz.Clazz; import org.robovm.compiler.clazz.Clazzes; import org.robovm.compiler.clazz.Path; -import org.robovm.compiler.config.ConfigXmlEntries.AppExtensionsList; -import org.robovm.compiler.config.ConfigXmlEntries.ClasspathentryList; -import org.robovm.compiler.config.ConfigXmlEntries.ForceLinkClassesList; -import org.robovm.compiler.config.ConfigXmlEntries.ForceLinkMethodsList; -import org.robovm.compiler.config.ConfigXmlEntries.FrameworksList; -import org.robovm.compiler.config.ConfigXmlEntries.LibsList; -import org.robovm.compiler.config.ConfigXmlEntries.PathsList; -import org.robovm.compiler.config.ConfigXmlEntries.PluginArgumentsList; -import org.robovm.compiler.config.ConfigXmlEntries.ResourcesList; -import org.robovm.compiler.config.ConfigXmlEntries.RootsList; -import org.robovm.compiler.config.ConfigXmlEntries.SymbolsList; +import org.robovm.compiler.config.ConfigXmlEntries.*; import org.robovm.compiler.config.StripArchivesConfig.StripArchivesBuilder; import org.robovm.compiler.config.tools.Tools; import org.robovm.compiler.llvm.DataLayout; @@ -43,8 +33,7 @@ import org.robovm.compiler.plugin.annotation.AnnotationImplPlugin; import org.robovm.compiler.plugin.debug.DebugInformationPlugin; import org.robovm.compiler.plugin.debug.DebuggerLaunchPlugin; -import org.robovm.compiler.plugin.desugar.StringConcatRewriterPlugin; -import org.robovm.compiler.plugin.lambda.LambdaPlugin; +import org.robovm.compiler.plugin.invokedynamic.InvokeDynamicCompilerPlugin; import org.robovm.compiler.plugin.objc.*; import org.robovm.compiler.target.ConsoleTarget; import org.robovm.compiler.target.Target; @@ -245,8 +234,7 @@ protected Config(UUID uuid) { new ObjCMemberPlugin(), new ObjCBlockPlugin(), new AnnotationImplPlugin(), - new StringConcatRewriterPlugin(), - new LambdaPlugin(), + new InvokeDynamicCompilerPlugin(), new DebugInformationPlugin(), new DebuggerLaunchPlugin(), new BuildGarbageCollectorPlugin() diff --git a/compiler/compiler/src/main/java/org/robovm/compiler/plugin/desugar/StringConcatRewriterPlugin.java b/compiler/compiler/src/main/java/org/robovm/compiler/plugin/desugar/StringConcatRewriterPlugin.java deleted file mode 100755 index c864ae118..000000000 --- a/compiler/compiler/src/main/java/org/robovm/compiler/plugin/desugar/StringConcatRewriterPlugin.java +++ /dev/null @@ -1,117 +0,0 @@ -package org.robovm.compiler.plugin.desugar; - -import org.robovm.compiler.ModuleBuilder; -import org.robovm.compiler.clazz.Clazz; -import org.robovm.compiler.config.Config; -import org.robovm.compiler.plugin.AbstractCompilerPlugin; -import org.robovm.compiler.plugin.PluginArgument; -import org.robovm.compiler.plugin.PluginArguments; -import soot.*; -import soot.jimple.DefinitionStmt; -import soot.jimple.DynamicInvokeExpr; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.LinkedList; -import java.util.List; - -/** - * This plugin adds support for Java 9+ String concatenation by replacing dynamicInvoke instructions - * to {@code java.lang.invoke.StringConcatFactory} with StringBuilder appends. - * - * plugin argument to control: 'desugar:enableJava9StringConcat=false' - * - * @author CoderBaron - */ -public class StringConcatRewriterPlugin extends AbstractCompilerPlugin { - private StringConcatRewriter rewriter; - private static final String ARG_KEY_ENABLE_PLUGIN = "enableJava9StringConcat"; - private Boolean enabled; - - private void init() { - if (rewriter == null) { - rewriter = new StringConcatRewriter(); - } - } - - private static boolean isMakeConcatBootstrapMethod(SootMethodRef methodRef) { - return methodRef.declaringClass().getName().equals("java.lang.invoke.StringConcatFactory") - && methodRef.name().equals("makeConcat"); - } - - private static boolean isMakeConcatWithConstantsBootstrapMethod(SootMethodRef methodRef) { - return methodRef.declaringClass().getName().equals("java.lang.invoke.StringConcatFactory") - && methodRef.name().equals("makeConcatWithConstants"); - } - - @Override - public PluginArguments getArguments() { - // list of arguments as these passed by idea, check idea/compilation/RoboVMCompileTask - List args = new ArrayList<>(); - args.add(new PluginArgument(ARG_KEY_ENABLE_PLUGIN, "false", "Flag: disables String concatenation " + - "by replacing dynamicInvoke instructions")); - return new PluginArguments("desugar", args); - } - - private boolean isEnabled(Config config) { - if (enabled == null) { - enabled = argumentValue(parseArguments(config), ARG_KEY_ENABLE_PLUGIN, true); - } - return enabled; - } - - @Override - public void beforeConfig(Config.Builder builder, Config config) throws IOException { - super.beforeConfig(builder, config); - // config to be built, reset enabled flag - enabled = null; - } - - @Override - public void beforeClass(Config config, Clazz clazz, ModuleBuilder moduleBuilder) throws IOException { - if (isEnabled(config)) { - init(); - - SootClass sootClass = clazz.getSootClass(); - - for (SootMethod method : sootClass.getMethods()) { - transformStringConcats(method); - } - } - } - - private void transformStringConcats(SootMethod method) { - if (!method.isConcrete()) { - return; - } - - Body body = method.retrieveActiveBody(); - PatchingChain units = body.getUnits(); - for (Unit unit = units.getFirst(); unit != null; unit = body.getUnits().getSuccOf(unit)) { - if (unit instanceof DefinitionStmt) { - if (((DefinitionStmt) unit).getRightOp() instanceof DynamicInvokeExpr) { - DynamicInvokeExpr expr = (DynamicInvokeExpr) ((DefinitionStmt) unit).getRightOp(); - - Value outValue = ((DefinitionStmt) unit).getLeftOp(); - SootMethodRef bootstrapMethodRef = expr.getBootstrapMethodRef(); - List args = expr.getArgs(); - List bootstrapArgs = expr.getBootstrapArgs(); - - LinkedList newUnits = null; - if (isMakeConcatBootstrapMethod(bootstrapMethodRef)) { - newUnits = rewriter.rewriteMakeConcat(body, outValue, args); - } else if (isMakeConcatWithConstantsBootstrapMethod(bootstrapMethodRef)) { - newUnits = rewriter.rewriteMakeConcatWithConstants(body, outValue, args, bootstrapArgs); - } - - if (newUnits != null) { - // Replace string concat instruction. - units.insertAfter(newUnits, unit); - units.remove(unit); - unit = newUnits.getLast(); - } - } - } - } - } -} diff --git a/compiler/compiler/src/main/java/org/robovm/compiler/plugin/invokedynamic/InvokeDynamicCompilerPlugin.java b/compiler/compiler/src/main/java/org/robovm/compiler/plugin/invokedynamic/InvokeDynamicCompilerPlugin.java new file mode 100644 index 000000000..24be3eb43 --- /dev/null +++ b/compiler/compiler/src/main/java/org/robovm/compiler/plugin/invokedynamic/InvokeDynamicCompilerPlugin.java @@ -0,0 +1,187 @@ +package org.robovm.compiler.plugin.invokedynamic; + +import org.robovm.compiler.CompilerException; +import org.robovm.compiler.ModuleBuilder; +import org.robovm.compiler.clazz.Clazz; +import org.robovm.compiler.config.Config; +import org.robovm.compiler.plugin.AbstractCompilerPlugin; +import org.robovm.compiler.plugin.invokedynamic.lambda.LambdaPlugin; +import org.robovm.compiler.plugin.invokedynamic.stringconcat.StringConcatRewriterPlugin; +import soot.*; +import soot.jimple.*; + +import java.io.IOException; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +public class InvokeDynamicCompilerPlugin extends AbstractCompilerPlugin { + + /** + * Delegate for specific bootstrap method handler + * (specific implementation to be done there) + */ + public interface Delegate { + default void beforeClass(Config config, Clazz clazz, ModuleBuilder moduleBuilder) throws IOException { + } + + default void beforeMethod(Config config, Clazz clazz, SootMethod method, ModuleBuilder moduleBuilder) throws IOException { + } + + default void afterClass(Config config, Clazz clazz, ModuleBuilder moduleBuilder) throws IOException { + } + + LinkedList transformDynamicInvoke( + Config config, Clazz clazz, SootClass sootClass, SootMethod method, DefinitionStmt defStmt, + DynamicInvokeExpr invokeExpr, ModuleBuilder moduleBuilder) throws IOException; + } + + private final List supportedDynamicInvokes; + + public InvokeDynamicCompilerPlugin() { + supportedDynamicInvokes = List.of( + new LambdaPlugin(), + new StringConcatRewriterPlugin(), + new UnrecognizedBootstrapDelegate() // has to be declared last ! + ); + } + + @Override + public void beforeClass(Config config, Clazz clazz, ModuleBuilder moduleBuilder) throws IOException { + SootClass sootClass = clazz.getSootClass(); + + // deliver beforeClass notification to allow delegates to initializes + for (Delegate delegate : supportedDynamicInvokes) + delegate.beforeClass(config, clazz, moduleBuilder); + + for (SootMethod method : sootClass.getMethods()) { + // deliver beforeMethod notification to allow delegates to reset counters whatever + for (Delegate delegate : supportedDynamicInvokes) + delegate.beforeMethod(config, clazz, method, moduleBuilder); + + transformMethod(config, clazz, sootClass, method, moduleBuilder); + } + + // deliver afterClass notification to allow delegates to reset class related activities + for (Delegate delegate : supportedDynamicInvokes) + delegate.afterClass(config, clazz, moduleBuilder); + } + + private void transformMethod(Config config, Clazz clazz, SootClass sootClass, + SootMethod method, ModuleBuilder moduleBuilder) throws IOException { + if (!method.isConcrete()) + return; + + Body body = method.retrieveActiveBody(); + PatchingChain units = body.getUnits(); + for (Unit unit = units.getFirst(); unit != null; unit = body.getUnits().getSuccOf(unit)) { + if (unit instanceof DefinitionStmt) { + DefinitionStmt defStmt = (DefinitionStmt) unit; + if (defStmt.getRightOp() instanceof DynamicInvokeExpr) { + DynamicInvokeExpr invokeExpr = (DynamicInvokeExpr) ((DefinitionStmt) unit).getRightOp(); + LinkedList newUnits = null; + try { + for (Delegate delegate : supportedDynamicInvokes) { + newUnits = delegate.transformDynamicInvoke(config, clazz, sootClass, method, defStmt, + invokeExpr, moduleBuilder); + if (newUnits != null) + break; + } + + // should not happen as there is fallback + assert newUnits != null; + + units.insertAfter(newUnits, unit); + units.remove(unit); + unit = newUnits.getLast(); + + } catch (Throwable e) { + // TODO: Change the jimple of the method to throw a + // LambdaConversionException at runtime. + throw new CompilerException(e); + } + } + } + } + } + + /** + * Fallback that will insert NoSuchMethodError exception for any attempt to invoke + */ + private static class UnrecognizedBootstrapDelegate implements Delegate { + private int tmpCounter = 0; + + private boolean initialized = false; + private SootClass java_lang_NoSuchMethodError; + private SootMethodRef java_lang_NoSuchMethodError_init; + + /** + * performs lazy initializations. + * can't perform it in constructor as config and plugins are being created before soot is + * initialized and it will perform G.reset() which causes VoidType.v() to be not equals as received + * from different globals. It seems to be a wide project bug in Soot's equal implementations. + * Check VoidType.equals for example + */ + private void initializeIfRequired() { + if (initialized) + return; + + SootResolver r = SootResolver.v(); + SootClass java_lang_String = r.makeClassRef("java.lang.String"); + + java_lang_NoSuchMethodError = r.makeClassRef("java.lang.NoSuchMethodError"); + java_lang_NoSuchMethodError_init = + Scene.v().makeMethodRef( + java_lang_NoSuchMethodError, + "", + Collections.singletonList(java_lang_String.getType()), + VoidType.v(), false); + initialized = true; + } + + @Override + public void beforeMethod(Config config, Clazz clazz, SootMethod method, ModuleBuilder moduleBuilder) { + tmpCounter = 0; + } + + @Override + public LinkedList transformDynamicInvoke( + Config config, Clazz clazz, SootClass sootClass, SootMethod method, + DefinitionStmt defStmt, DynamicInvokeExpr invokeExpr, + ModuleBuilder moduleBuilder) + { + initializeIfRequired(); + String msg = "Unsupported InvokeDynamic to " + invokeExpr.getBootstrapMethodRef().declaringClass().getName() + + '.' + invokeExpr.getBootstrapMethodRef().name(); + Jimple jimple = Jimple.v(); + Body body = method.retrieveActiveBody(); + LinkedList newUnits = new LinkedList<>(); + + // initialize left size of expression just to keep a variable in scope + Type localType = defStmt.getLeftOp().getType(); + Value dummyConstant; + if (localType instanceof RefLikeType) + dummyConstant = NullConstant.v(); + else if (localType instanceof IntegerType) + dummyConstant = IntConstant.v(0); + else if (localType instanceof LongType) + dummyConstant = LongConstant.v(0); + else if (localType instanceof FloatType) + dummyConstant = FloatConstant.v(0); + else if (localType instanceof DoubleType) + dummyConstant = DoubleConstant.v(0); + else throw new IllegalStateException("Unexpected local type " + localType); + newUnits.add(jimple.newAssignStmt(defStmt.getLeftOp(), dummyConstant)); + + // insert exception + Local exc = jimple.newLocal("$tmp_invdyn_exc" + (tmpCounter++), java_lang_NoSuchMethodError.getType()); + body.getLocals().add(exc); + newUnits.add(jimple.newAssignStmt(exc, jimple.newNewExpr(java_lang_NoSuchMethodError.getType()))); + newUnits.add(jimple.newInvokeStmt(jimple.newSpecialInvokeExpr(exc, java_lang_NoSuchMethodError_init, + StringConstant.v(msg)))); + newUnits.add(jimple.newThrowStmt(exc)); + + return newUnits; + } + } +} diff --git a/compiler/compiler/src/main/java/org/robovm/compiler/plugin/lambda/LambdaClass.java b/compiler/compiler/src/main/java/org/robovm/compiler/plugin/invokedynamic/lambda/LambdaClass.java similarity index 97% rename from compiler/compiler/src/main/java/org/robovm/compiler/plugin/lambda/LambdaClass.java rename to compiler/compiler/src/main/java/org/robovm/compiler/plugin/invokedynamic/lambda/LambdaClass.java index 436095b8c..1044de7d5 100755 --- a/compiler/compiler/src/main/java/org/robovm/compiler/plugin/lambda/LambdaClass.java +++ b/compiler/compiler/src/main/java/org/robovm/compiler/plugin/invokedynamic/lambda/LambdaClass.java @@ -14,7 +14,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.robovm.compiler.plugin.lambda; +package org.robovm.compiler.plugin.invokedynamic.lambda; import java.util.List; diff --git a/compiler/compiler/src/main/java/org/robovm/compiler/plugin/lambda/LambdaClassGenerator.java b/compiler/compiler/src/main/java/org/robovm/compiler/plugin/invokedynamic/lambda/LambdaClassGenerator.java similarity index 99% rename from compiler/compiler/src/main/java/org/robovm/compiler/plugin/lambda/LambdaClassGenerator.java rename to compiler/compiler/src/main/java/org/robovm/compiler/plugin/invokedynamic/lambda/LambdaClassGenerator.java index add8f4dd9..4e4312ebd 100755 --- a/compiler/compiler/src/main/java/org/robovm/compiler/plugin/lambda/LambdaClassGenerator.java +++ b/compiler/compiler/src/main/java/org/robovm/compiler/plugin/invokedynamic/lambda/LambdaClassGenerator.java @@ -14,7 +14,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.robovm.compiler.plugin.lambda; +package org.robovm.compiler.plugin.invokedynamic.lambda; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.MethodVisitor; diff --git a/compiler/compiler/src/main/java/org/robovm/compiler/plugin/invokedynamic/lambda/LambdaPlugin.java b/compiler/compiler/src/main/java/org/robovm/compiler/plugin/invokedynamic/lambda/LambdaPlugin.java new file mode 100644 index 000000000..4ede383b6 --- /dev/null +++ b/compiler/compiler/src/main/java/org/robovm/compiler/plugin/invokedynamic/lambda/LambdaPlugin.java @@ -0,0 +1,253 @@ +/* + * Copyright (C) 2014 RoboVM AB + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * 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 Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.robovm.compiler.plugin.invokedynamic.lambda; + +import org.apache.commons.io.FileUtils; +import org.robovm.compiler.ModuleBuilder; +import org.robovm.compiler.Types; +import org.robovm.compiler.clazz.Clazz; +import org.robovm.compiler.config.Config; +import org.robovm.compiler.plugin.invokedynamic.InvokeDynamicCompilerPlugin; +import soot.*; +import soot.jimple.*; +import soot.tagkit.LineNumberTag; +import soot.util.Switch; + +import java.io.File; +import java.io.IOException; +import java.util.*; + +public class LambdaPlugin implements InvokeDynamicCompilerPlugin.Delegate { + static final int BRIDGE = 0x00000040; + static final int SYNTHETIC = 0x00001000; + + private static int FLAG_MARKERS = 2; + private static int FLAG_BRIDGES = 4; + private int tmpCounter = 0; + + final Map generators = new HashMap(); + + private static boolean isLambdaBootstrapMethod(SootMethodRef methodRef) { + return methodRef.declaringClass().getName().equals("java.lang.invoke.LambdaMetafactory") + && (methodRef.name().equals("metafactory") || methodRef.name().equals("altMetafactory")); + } + + @Override + public void afterClass(Config config, Clazz clazz, ModuleBuilder moduleBuilder) throws IOException { + synchronized (generators) { + generators.remove(clazz.getSootClass()); + } + } + + @Override + public void beforeMethod(Config config, Clazz clazz, SootMethod method, ModuleBuilder moduleBuilder) throws IOException { + tmpCounter = 0; + } + + @Override + public LinkedList transformDynamicInvoke(Config config, Clazz clazz, SootClass sootClass, + SootMethod method, DefinitionStmt defStmt, DynamicInvokeExpr invokeExpr, ModuleBuilder moduleBuilder) throws IOException { + + LinkedList newUnits = null; + if (isLambdaBootstrapMethod(invokeExpr.getBootstrapMethodRef())) { + Body body = method.retrieveActiveBody(); + PatchingChain units = body.getUnits(); + LambdaClassGenerator generator = null; + synchronized (generators) { + generator = generators.get(sootClass); + if (generator == null) { + generator = new LambdaClassGenerator(); + generators.put(sootClass, generator); + } + } + + List bsmArgs = invokeExpr.getBootstrapArgs(); + SootClass caller = sootClass; + String invokedName = invokeExpr.getMethodRef().name(); + SootMethodRef invokedType = invokeExpr.getMethodRef(); + SootMethodType samMethodType = (SootMethodType) bsmArgs.get(0); + SootMethodHandle implMethod = (SootMethodHandle) bsmArgs.get(1); + SootMethodType instantiatedMethodType = (SootMethodType) bsmArgs.get(2); + + LambdaClass callSite; + List markerInterfaces = new ArrayList<>(); + List bridgeMethods = new ArrayList<>(); + if (invokeExpr.getBootstrapMethodRef().name().equals("altMetafactory")) { + int flags = ((IntConstant) bsmArgs.get(3)).value; + int bsmArgsIdx = 4; + if ((flags & FLAG_MARKERS) > 0) { + int count = ((IntConstant) bsmArgs.get(bsmArgsIdx++)).value; + for (int i = 0; i < count; i++) { + Object value = bsmArgs.get(bsmArgsIdx++); + if (value instanceof Type) { + markerInterfaces.add((Type) value); + } else if (value instanceof ClassConstant) { + String className = ((ClassConstant) value).getValue().replace('/', '.'); + markerInterfaces.add(SootResolver.v() + .resolveClass(className, SootClass.HIERARCHY).getType()); + } + } + } + if ((flags & FLAG_BRIDGES) > 0) { + int count = ((IntConstant) bsmArgs.get(bsmArgsIdx++)).value; + for (int i = 0; i < count; i++) { + bridgeMethods.add((SootMethodType) bsmArgs.get(bsmArgsIdx++)); + } + } + } + + // search for additional bridge methods in the + // interface we implement. Javac + // may not emit them in the invoke dynamic call + // see issue #1087 + if (bridgeMethods.size() == 0) { + SootClass targetType = SootResolver.v().resolveClass( + invokedType.returnType().toString().replace('/', '.'), SootClass.SIGNATURES); + String samDescriptor = Types.getDescriptor(samMethodType.getParameterTypes(), + samMethodType.getReturnType()); + for (SootMethod targetTypeMethod : targetType.getMethods()) { + boolean isBridgeMethod = targetTypeMethod.getName().equals(invokedName); + isBridgeMethod &= targetTypeMethod.getParameterCount() == samMethodType.getParameterTypes().size(); + isBridgeMethod &= ((targetTypeMethod.getModifiers() & BRIDGE) != 0); + isBridgeMethod &= ((targetTypeMethod.getModifiers() & SYNTHETIC) != 0); + if(isBridgeMethod) { + String targetTypeMethodDesc = Types.getDescriptor(targetTypeMethod); + if (!targetTypeMethodDesc.equals(samDescriptor)) { + bridgeMethods.add(new BridgeMethodType(targetTypeMethod.getReturnType(), + targetTypeMethod.getParameterTypes())); + } + } + } + } + + // generate the lambda class + callSite = generator.generate(caller, invokedName, invokedType, samMethodType, implMethod, + instantiatedMethodType, markerInterfaces, bridgeMethods); + File f = clazz.getPath().getGeneratedClassFile(callSite.getLambdaClassName()); + FileUtils.writeByteArrayToFile(f, callSite.getClassData()); + // The lambda class is created after the caller is + // compiled. + // This prevents the triggering of a recompile of + // the caller. + f.setLastModified(clazz.lastModified()); + + SootClass lambdaClass = SootResolver.v() + .makeClassRef(callSite.getLambdaClassName().replace('/', '.')); + + Local l = (Local) defStmt.getLeftOp(); + Type samType = callSite.getTargetMethodReturnType(); + newUnits = new LinkedList<>(); + if (callSite.getTargetMethodName().equals("")) { + // Constant lambda. Create an instance once and + // reuse for + // every call. + String fieldName = lambdaClass.getName() + .substring(lambdaClass.getName().lastIndexOf('.') + 1); + SootField field = new SootField(fieldName, lambdaClass.getType(), + Modifier.STATIC | Modifier.PRIVATE | Modifier.TRANSIENT + | 0x1000 /* SYNTHETIC */); + method.getDeclaringClass().addField(field); + // l = LambdaClass.lambdaField + newUnits.add( + Jimple.v().newAssignStmt(l, Jimple.v().newStaticFieldRef(field.makeRef()))); + // if l != null goto succOfInvokedynamic + newUnits.add(Jimple.v().newIfStmt(Jimple.v().newNeExpr(l, NullConstant.v()), + units.getSuccOf(defStmt))); + // $tmpX = new LambdaClass() + Local tmp = Jimple.v().newLocal("$tmp_lambda" + (tmpCounter++), lambdaClass.getType()); + body.getLocals().add(tmp); + newUnits.add( + Jimple.v().newAssignStmt(tmp, Jimple.v().newNewExpr(lambdaClass.getType()))); + newUnits.add(Jimple.v().newInvokeStmt(Jimple.v().newSpecialInvokeExpr(tmp, + Scene.v().makeConstructorRef(lambdaClass, Collections. emptyList())))); + // LambdaClass.lambdaField = $tmpX + newUnits.add( + Jimple.v().newAssignStmt(Jimple.v().newStaticFieldRef(field.makeRef()), tmp)); + // l = $tmpX + newUnits.add(Jimple.v().newAssignStmt(l, tmp)); + } else { + // Static factory method returns the lambda to + // use. + newUnits.add(Jimple.v().newAssignStmt(l, + Jimple.v().newStaticInvokeExpr( + Scene.v().makeMethodRef(lambdaClass, + callSite.getTargetMethodName(), + callSite.getTargetMethodParameters(), + samType, true), + invokeExpr.getArgs()))); + } + + // dkimitsa: attach LineNumberTag to all new units that are inserted instead of DynamicInvokeExpr + // as it would break variable resolution in debugger information plugin + for (Object o : defStmt.getTags()) { + if (o instanceof LineNumberTag) { + // attach same line number to all units in new units + LineNumberTag ln = (LineNumberTag) o; + for (Unit u : newUnits) + u.addTag(new LineNumberTag(ln.getLineNumber())); + break; + } + } + } + + return newUnits; + } + + static class BridgeMethodType extends Constant implements SootMethodType { + private static final long serialVersionUID = 1L; + + private final Type returnType; + private final List parameterTypes; + + public BridgeMethodType(Type returnType, List parameterTypes) { + this.returnType = returnType; + this.parameterTypes = parameterTypes; + } + + public Type getReturnType() { + return returnType; + } + + public List getParameterTypes() { + return Collections.unmodifiableList(parameterTypes); + } + + @Override + public Type getType() { + return RefType.v("java.lang.invoke.MethodType"); + } + + @Override + public void apply(Switch sw) { + ((ConstantSwitch) sw).defaultCase(this); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + + sb.append('('); + for (Iterator it = parameterTypes.iterator(); it.hasNext();) { + sb.append(it.next()); + if (it.hasNext()) { + sb.append(','); + } + } + return sb.append(')').append(returnType).toString(); + } + } +} diff --git a/compiler/compiler/src/main/java/org/robovm/compiler/plugin/desugar/StringConcatRewriter.java b/compiler/compiler/src/main/java/org/robovm/compiler/plugin/invokedynamic/stringconcat/StringConcatRewriter.java similarity index 99% rename from compiler/compiler/src/main/java/org/robovm/compiler/plugin/desugar/StringConcatRewriter.java rename to compiler/compiler/src/main/java/org/robovm/compiler/plugin/invokedynamic/stringconcat/StringConcatRewriter.java index 89049aa0a..5d334d401 100755 --- a/compiler/compiler/src/main/java/org/robovm/compiler/plugin/desugar/StringConcatRewriter.java +++ b/compiler/compiler/src/main/java/org/robovm/compiler/plugin/invokedynamic/stringconcat/StringConcatRewriter.java @@ -1,4 +1,4 @@ -package org.robovm.compiler.plugin.desugar; +package org.robovm.compiler.plugin.invokedynamic.stringconcat; import soot.*; import soot.jimple.Jimple; diff --git a/compiler/compiler/src/main/java/org/robovm/compiler/plugin/invokedynamic/stringconcat/StringConcatRewriterPlugin.java b/compiler/compiler/src/main/java/org/robovm/compiler/plugin/invokedynamic/stringconcat/StringConcatRewriterPlugin.java new file mode 100644 index 000000000..a8b3df6db --- /dev/null +++ b/compiler/compiler/src/main/java/org/robovm/compiler/plugin/invokedynamic/stringconcat/StringConcatRewriterPlugin.java @@ -0,0 +1,64 @@ +package org.robovm.compiler.plugin.invokedynamic.stringconcat; + +import org.robovm.compiler.ModuleBuilder; +import org.robovm.compiler.clazz.Clazz; +import org.robovm.compiler.config.Config; +import org.robovm.compiler.plugin.invokedynamic.InvokeDynamicCompilerPlugin; +import soot.*; +import soot.jimple.DefinitionStmt; +import soot.jimple.DynamicInvokeExpr; + +import java.io.IOException; +import java.util.LinkedList; +import java.util.List; + +/** + * This plugin adds support for Java 9+ String concatenation by replacing dynamicInvoke instructions + * to {@code java.lang.invoke.StringConcatFactory} with StringBuilder appends. + * + * plugin argument to control: 'desugar:enableJava9StringConcat=false' + * + * @author CoderBaron + */ +public class StringConcatRewriterPlugin implements InvokeDynamicCompilerPlugin.Delegate { + private StringConcatRewriter rewriter; + + private void init() { + if (rewriter == null) { + rewriter = new StringConcatRewriter(); + } + } + + private static boolean isMakeConcatBootstrapMethod(SootMethodRef methodRef) { + return methodRef.declaringClass().getName().equals("java.lang.invoke.StringConcatFactory") + && methodRef.name().equals("makeConcat"); + } + + private static boolean isMakeConcatWithConstantsBootstrapMethod(SootMethodRef methodRef) { + return methodRef.declaringClass().getName().equals("java.lang.invoke.StringConcatFactory") + && methodRef.name().equals("makeConcatWithConstants"); + } + + @Override + public LinkedList transformDynamicInvoke( + Config config, Clazz clazz, SootClass sootClass, SootMethod method, + DefinitionStmt defStmt, DynamicInvokeExpr invokeExpr, ModuleBuilder moduleBuilder) throws IOException + { + init(); + + LinkedList newUnits = null; + Body body = method.retrieveActiveBody(); + Value outValue = defStmt.getLeftOp(); + SootMethodRef bootstrapMethodRef = invokeExpr.getBootstrapMethodRef(); + List args = invokeExpr.getArgs(); + List bootstrapArgs = invokeExpr.getBootstrapArgs(); + + if (isMakeConcatBootstrapMethod(bootstrapMethodRef)) { + newUnits = rewriter.rewriteMakeConcat(body, outValue, args); + } else if (isMakeConcatWithConstantsBootstrapMethod(bootstrapMethodRef)) { + newUnits = rewriter.rewriteMakeConcatWithConstants(body, outValue, args, bootstrapArgs); + } + + return newUnits; + } +} diff --git a/compiler/compiler/src/main/java/org/robovm/compiler/plugin/lambda/LambdaPlugin.java b/compiler/compiler/src/main/java/org/robovm/compiler/plugin/lambda/LambdaPlugin.java deleted file mode 100755 index e8ecc3f10..000000000 --- a/compiler/compiler/src/main/java/org/robovm/compiler/plugin/lambda/LambdaPlugin.java +++ /dev/null @@ -1,306 +0,0 @@ -/* - * Copyright (C) 2014 RoboVM AB - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * 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 Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.robovm.compiler.plugin.lambda; - -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; - -import org.apache.commons.io.FileUtils; -import org.robovm.compiler.CompilerException; -import org.robovm.compiler.ModuleBuilder; -import org.robovm.compiler.Types; -import org.robovm.compiler.clazz.Clazz; -import org.robovm.compiler.config.Config; -import org.robovm.compiler.plugin.AbstractCompilerPlugin; - -import soot.Body; -import soot.Local; -import soot.Modifier; -import soot.PatchingChain; -import soot.RefType; -import soot.Scene; -import soot.SootClass; -import soot.SootField; -import soot.SootMethod; -import soot.SootMethodHandle; -import soot.SootMethodRef; -import soot.SootMethodType; -import soot.SootResolver; -import soot.Type; -import soot.Unit; -import soot.Value; -import soot.jimple.ClassConstant; -import soot.jimple.Constant; -import soot.jimple.ConstantSwitch; -import soot.jimple.DefinitionStmt; -import soot.jimple.DynamicInvokeExpr; -import soot.jimple.IntConstant; -import soot.jimple.Jimple; -import soot.jimple.NullConstant; -import soot.tagkit.LineNumberTag; -import soot.util.Switch; - -public class LambdaPlugin extends AbstractCompilerPlugin { - static final int BRIDGE = 0x00000040; - static final int SYNTHETIC = 0x00001000; - - private static int FLAG_MARKERS = 2; - private static int FLAG_BRIDGES = 4; - - final Map generators = new HashMap(); - - private static boolean isLambdaBootstrapMethod(SootMethodRef methodRef) { - return methodRef.declaringClass().getName().equals("java.lang.invoke.LambdaMetafactory") - && (methodRef.name().equals("metafactory") || methodRef.name().equals("altMetafactory")); - } - - @Override - public void afterClass(Config config, Clazz clazz, ModuleBuilder moduleBuilder) throws IOException { - synchronized (generators) { - generators.remove(clazz.getSootClass()); - } - } - - @Override - public void beforeClass(Config config, Clazz clazz, ModuleBuilder moduleBuilder) throws IOException { - SootClass sootClass = clazz.getSootClass(); - - for (SootMethod method : sootClass.getMethods()) { - transformMethod(config, clazz, sootClass, method, moduleBuilder); - } - } - - private void transformMethod(Config config, Clazz clazz, SootClass sootClass, - SootMethod method, ModuleBuilder moduleBuilder) throws IOException { - - if (!method.isConcrete()) { - return; - } - - int tmpCounter = 0; - Body body = method.retrieveActiveBody(); - PatchingChain units = body.getUnits(); - for (Unit unit = units.getFirst(); unit != null; unit = body.getUnits().getSuccOf(unit)) { - if (unit instanceof DefinitionStmt) { - if (((DefinitionStmt) unit).getRightOp() instanceof DynamicInvokeExpr) { - DynamicInvokeExpr expr = (DynamicInvokeExpr) ((DefinitionStmt) unit).getRightOp(); - - if (isLambdaBootstrapMethod(expr.getBootstrapMethodRef())) { - LambdaClassGenerator generator = null; - synchronized (generators) { - generator = generators.get(sootClass); - if (generator == null) { - generator = new LambdaClassGenerator(); - generators.put(sootClass, generator); - } - } - - List bsmArgs = expr.getBootstrapArgs(); - SootClass caller = sootClass; - String invokedName = expr.getMethodRef().name(); - SootMethodRef invokedType = expr.getMethodRef(); - SootMethodType samMethodType = (SootMethodType) bsmArgs.get(0); - SootMethodHandle implMethod = (SootMethodHandle) bsmArgs.get(1); - SootMethodType instantiatedMethodType = (SootMethodType) bsmArgs.get(2); - - try { - LambdaClass callSite = null; - List markerInterfaces = new ArrayList<>(); - List bridgeMethods = new ArrayList<>(); - if (expr.getBootstrapMethodRef().name().equals("altMetafactory")) { - int flags = ((IntConstant) bsmArgs.get(3)).value; - int bsmArgsIdx = 4; - if ((flags & FLAG_MARKERS) > 0) { - int count = ((IntConstant) bsmArgs.get(bsmArgsIdx++)).value; - for (int i = 0; i < count; i++) { - Object value = bsmArgs.get(bsmArgsIdx++); - if (value instanceof Type) { - markerInterfaces.add((Type) value); - } else if (value instanceof ClassConstant) { - String className = ((ClassConstant) value).getValue().replace('/', '.'); - markerInterfaces.add(SootResolver.v() - .resolveClass(className, SootClass.HIERARCHY).getType()); - } - } - } - if ((flags & FLAG_BRIDGES) > 0) { - int count = ((IntConstant) bsmArgs.get(bsmArgsIdx++)).value; - for (int i = 0; i < count; i++) { - bridgeMethods.add((SootMethodType) bsmArgs.get(bsmArgsIdx++)); - } - } - } - - // search for additional bridge methods in the - // interface we implement. Javac - // may not emit them in the invoke dynamic call - // see issue #1087 - if (bridgeMethods.size() == 0) { - SootClass targetType = SootResolver.v().resolveClass( - invokedType.returnType().toString().replace('/', '.'), SootClass.SIGNATURES); - String samDescriptor = Types.getDescriptor(samMethodType.getParameterTypes(), - samMethodType.getReturnType()); - for (SootMethod targetTypeMethod : targetType.getMethods()) { - boolean isBridgeMethod = targetTypeMethod.getName().equals(invokedName); - isBridgeMethod &= targetTypeMethod.getParameterCount() == samMethodType.getParameterTypes().size(); - isBridgeMethod &= ((targetTypeMethod.getModifiers() & BRIDGE) != 0); - isBridgeMethod &= ((targetTypeMethod.getModifiers() & SYNTHETIC) != 0); - if(isBridgeMethod) { - String targetTypeMethodDesc = Types.getDescriptor(targetTypeMethod); - if (!targetTypeMethodDesc.equals(samDescriptor)) { - bridgeMethods.add(new BridgeMethodType(targetTypeMethod.getReturnType(), - targetTypeMethod.getParameterTypes())); - } - } - } - } - - // generate the lambda class - callSite = generator.generate(caller, invokedName, invokedType, samMethodType, implMethod, - instantiatedMethodType, markerInterfaces, bridgeMethods); - File f = clazz.getPath().getGeneratedClassFile(callSite.getLambdaClassName()); - FileUtils.writeByteArrayToFile(f, callSite.getClassData()); - // The lambda class is created after the caller is - // compiled. - // This prevents the triggering of a recompile of - // the caller. - f.setLastModified(clazz.lastModified()); - - SootClass lambdaClass = SootResolver.v() - .makeClassRef(callSite.getLambdaClassName().replace('/', '.')); - - Local l = (Local) ((DefinitionStmt) unit).getLeftOp(); - Type samType = callSite.getTargetMethodReturnType(); - LinkedList newUnits = new LinkedList<>(); - if (callSite.getTargetMethodName().equals("")) { - // Constant lambda. Create an instance once and - // reuse for - // every call. - String fieldName = lambdaClass.getName() - .substring(lambdaClass.getName().lastIndexOf('.') + 1); - SootField field = new SootField(fieldName, lambdaClass.getType(), - Modifier.STATIC | Modifier.PRIVATE | Modifier.TRANSIENT - | 0x1000 /* SYNTHETIC */); - method.getDeclaringClass().addField(field); - // l = LambdaClass.lambdaField - newUnits.add( - Jimple.v().newAssignStmt(l, Jimple.v().newStaticFieldRef(field.makeRef()))); - // if l != null goto succOfInvokedynamic - newUnits.add(Jimple.v().newIfStmt(Jimple.v().newNeExpr(l, NullConstant.v()), - units.getSuccOf(unit))); - // $tmpX = new LambdaClass() - Local tmp = Jimple.v().newLocal("$tmp" + (tmpCounter++), lambdaClass.getType()); - body.getLocals().add(tmp); - newUnits.add( - Jimple.v().newAssignStmt(tmp, Jimple.v().newNewExpr(lambdaClass.getType()))); - newUnits.add(Jimple.v().newInvokeStmt(Jimple.v().newSpecialInvokeExpr(tmp, - Scene.v().makeConstructorRef(lambdaClass, Collections. emptyList())))); - // LambdaClass.lambdaField = $tmpX - newUnits.add( - Jimple.v().newAssignStmt(Jimple.v().newStaticFieldRef(field.makeRef()), tmp)); - // l = $tmpX - newUnits.add(Jimple.v().newAssignStmt(l, tmp)); - } else { - // Static factory method returns the lambda to - // use. - newUnits.add(Jimple.v().newAssignStmt(l, - Jimple.v().newStaticInvokeExpr( - Scene.v().makeMethodRef(lambdaClass, - callSite.getTargetMethodName(), - callSite.getTargetMethodParameters(), - samType, true), - expr.getArgs()))); - } - - // dkimitsa: attach LineNumberTag to all new units that are inserted instead of DynamicInvokeExpr - // as it would break variable resolution in debugger information plugin - for (Object o : unit.getTags()) { - if (o instanceof LineNumberTag) { - // attach same line number to all units in new units - LineNumberTag ln = (LineNumberTag) o; - for (Unit u : newUnits) - u.addTag(new LineNumberTag(ln.getLineNumber())); - break; - } - } - - units.insertAfter(newUnits, unit); - units.remove(unit); - unit = newUnits.getLast(); - - } catch (Throwable e) { - // TODO: Change the jimple of the method to throw a - // LambdaConversionException at runtime. - throw new CompilerException(e); - } - } - } - } - } - } - - static class BridgeMethodType extends Constant implements SootMethodType { - private static final long serialVersionUID = 1L; - - private final Type returnType; - private final List parameterTypes; - - public BridgeMethodType(Type returnType, List parameterTypes) { - this.returnType = returnType; - this.parameterTypes = parameterTypes; - } - - public Type getReturnType() { - return returnType; - } - - public List getParameterTypes() { - return Collections.unmodifiableList(parameterTypes); - } - - @Override - public Type getType() { - return RefType.v("java.lang.invoke.MethodType"); - } - - @Override - public void apply(Switch sw) { - ((ConstantSwitch) sw).defaultCase(this); - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - - sb.append('('); - for (Iterator it = parameterTypes.iterator(); it.hasNext();) { - sb.append(it.next()); - if (it.hasNext()) { - sb.append(','); - } - } - return sb.append(')').append(returnType).toString(); - } - } -}