forked from MobiVM/robovm
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* transforms unknown dynamic invoke into NoSuchMethodError (MobiVM#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 (scala/scala#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 <[email protected]> (cherry picked from commit 8675d71)
- Loading branch information
Showing
9 changed files
with
510 additions
and
441 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
117 changes: 0 additions & 117 deletions
117
...compiler/src/main/java/org/robovm/compiler/plugin/desugar/StringConcatRewriterPlugin.java
This file was deleted.
Oops, something went wrong.
187 changes: 187 additions & 0 deletions
187
...r/src/main/java/org/robovm/compiler/plugin/invokedynamic/InvokeDynamicCompilerPlugin.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<Unit> transformDynamicInvoke( | ||
Config config, Clazz clazz, SootClass sootClass, SootMethod method, DefinitionStmt defStmt, | ||
DynamicInvokeExpr invokeExpr, ModuleBuilder moduleBuilder) throws IOException; | ||
} | ||
|
||
private final List<Delegate> 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<Unit> 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<Unit> 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, | ||
"<init>", | ||
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<Unit> 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<Unit> 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; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.