Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

* fixed: #641 java.lang.IllegalAccessError: Attempt to access protected method from Lambda #646

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,29 +16,22 @@
*/
package org.robovm.compiler.plugin.lambda;

import static org.objectweb.asm.Opcodes.*;

import java.util.ArrayList;
import java.util.List;

import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.commons.GeneratorAdapter;
import org.robovm.compiler.CompilerException;
import org.robovm.compiler.Types;
import soot.*;
import soot.jimple.InvokeExpr;
import soot.jimple.Jimple;
import soot.jimple.Stmt;

import soot.DoubleType;
import soot.FloatType;
import soot.LongType;
import soot.PrimType;
import soot.RefType;
import soot.SootClass;
import soot.SootMethodHandle;
import soot.SootMethodRef;
import soot.SootMethodType;
import soot.Type;
import soot.VoidType;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

import static org.objectweb.asm.Opcodes.*;

public class LambdaClassGenerator {
private static int CLASS_VERSION = 51;
Expand All @@ -49,10 +42,11 @@ public LambdaClass generate(SootClass caller, String invokedName, SootMethodRef
List<Type> markerInterfaces, List<SootMethodType> bridgeMethods) {
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);

String lambdaClassName = caller.getName().replace('.', '/') + "$$Lambda$" + (counter++);
int lambdaIndex = counter++;
String lambdaClassName = caller.getName().replace('.', '/') + "$$Lambda$" + lambdaIndex;
String functionalInterface = invokedType.returnType().toString().replace('.', '/');

List<String> interfaces = new ArrayList<String>();
List<String> interfaces = new ArrayList<>();
interfaces.add(functionalInterface);
for (Type markerInterface : markerInterfaces) {
interfaces.add(markerInterface.toString().replace('.', '/'));
Expand All @@ -75,15 +69,19 @@ public LambdaClass generate(SootClass caller, String invokedName, SootMethodRef
instantiatedMethodType);
}

// create a trampoline to private member if required
SootMethodHandle methodHandle = createTrampolineForProtectedTarget(caller, implMethod,
invokedType.parameterTypes(), invokedName, lambdaIndex);

// forward the lambda method
createForwardingMethod(caller, lambdaClassName, cw, invokedName, samMethodType.getParameterTypes(),
samMethodType.getReturnType(), invokedType.parameterTypes(), samMethodType, implMethod,
samMethodType.getReturnType(), invokedType.parameterTypes(), samMethodType, methodHandle,
instantiatedMethodType, false);

// create any bridge methods necessary
for (SootMethodType bridgeMethod : bridgeMethods) {
createForwardingMethod(caller, lambdaClassName, cw, invokedName, bridgeMethod.getParameterTypes(),
bridgeMethod.getReturnType(), invokedType.parameterTypes(), samMethodType, implMethod,
bridgeMethod.getReturnType(), invokedType.parameterTypes(), samMethodType, methodHandle,
instantiatedMethodType, true);
}
cw.visitEnd();
Expand All @@ -92,6 +90,107 @@ public LambdaClass generate(SootClass caller, String invokedName, SootMethodRef
invokedType.returnType());
}

/**
* Creates a trampoline that replaces a lambda destination to inherited protected member to a wrapper
* in calling class which will perform original call. for a case when inherited member and lambda are
* located in different packages
*
* Problem sample:
* package a;
* public class A {
* protected foo(){};
* }
*
* package b;
* import a.A;
* public class B extends A{
* public void test() {
* Runnable r = ::foo;
* r.run();
* }
* }
* produces: java.lang.IllegalAccessError: Attempt to access method a.A.foo()V from class b.B$$Lambda$1
*
* @return new SootMethodHandle to trampoline method or existing if trampoline is not required
*/
private SootMethodHandle createTrampolineForProtectedTarget(SootClass caller, SootMethodHandle methodHandle,
List<Type> invokedParameters,
String name, int lambdaIndex) {
// trampoline to be created in case target method is protected and target member's class is not accessible
// from caller package
int refKind = methodHandle.getReferenceKind();
if ((refKind == SootMethodHandle.REF_invokeVirtual || refKind == SootMethodHandle.REF_invokeStatic) &&
!caller.getJavaPackageName().equals(methodHandle.getMethodRef().declaringClass().getPackageName()))
{
boolean isInstanceMethod = refKind == SootMethodHandle.REF_invokeVirtual;

// try to resolve method and if fails do nothing
SootMethod targetMethod = null;
try {
// can't invoke here targetMethod = methodHandle.getMethodRef().resolve() as it will fail
// with exception in checkStatic() as methodHandle.methodRef marked as static even for
// virtualInvoke()
// TODO: FIXME: probably a soot bug!
SootMethodRef methodRef = methodHandle.getMethodRef();
targetMethod = methodRef.declaringClass().getMethod(methodRef.getSubSignature());
} catch (Exception ignored) {
}

if (targetMethod != null && targetMethod.isProtected()) {
List<Type> trampolineParameters = new ArrayList<>(methodHandle.getMethodType().getParameterTypes());
if (isInstanceMethod) {
// it is important to replace methodType arg with type of argument otherwise it will produce
// IllegalAccessError while invoking `packageA.A.method()` from `packageB.B` class. Just call it
// as `packageB.B.method()`
trampolineParameters.set(0, invokedParameters.get(0));
}

SootMethod trampoline = new SootMethod("robovm$lambda$" + name + "$" + lambdaIndex,
trampolineParameters, methodHandle.getMethodType().getReturnType(),
Modifier.PRIVATE | Modifier.STATIC | 0x1000 /*ACC_SYNTHETIC*/);
Jimple j = Jimple.v();
Body body = j.newBody(trampoline);
PatchingChain<Unit> units = body.getUnits();
Local receiver = null;
if (isInstanceMethod) {
receiver = j.newLocal("$this", trampoline.getParameterType(0));
body.getLocals().add(receiver);
units.add(j.newIdentityStmt(receiver, j.newParameterRef(receiver.getType(), 0)));
}

LinkedList<Value> args = new LinkedList<>();
for (int i = receiver != null ? 1 : 0; i < trampoline.getParameterCount(); i++) {
Type t = trampoline.getParameterType(i);
Local p = j.newLocal("$p" + i, t);
body.getLocals().add(p);
units.add(j.newIdentityStmt(p, j.newParameterRef(t, i)));
args.add(p);
}

Local ret = null;
if (trampoline.getReturnType() != VoidType.v()) {
ret = j.newLocal("$ret", trampoline.getReturnType());
body.getLocals().add(ret);
}

InvokeExpr invokeExpr;
if (receiver != null) invokeExpr = j.newVirtualInvokeExpr(receiver, targetMethod.makeRef(), args);
else invokeExpr = j.newStaticInvokeExpr(targetMethod.makeRef(), args);

Stmt invokeStmt = ret == null ? j.newInvokeStmt(invokeExpr) : j.newAssignStmt(ret, invokeExpr);
units.add(invokeStmt);
units.add(ret != null ? j.newReturnStmt(ret) : j.newReturnVoidStmt());
trampoline.setActiveBody(body);
caller.addMethod(trampoline);

// replace lambda method handle
methodHandle = j.newMethodHandle(SootMethodHandle.REF_invokeStatic, trampoline.makeRef());
}
}

return methodHandle;
}

private void createForwardingMethod(SootClass caller, String lambdaClassName, ClassWriter cw, String name,
List<Type> parameters, Type returnType, List<Type> invokedParameters, SootMethodType samMethodType,
SootMethodHandle implMethod, SootMethodType instantiatedMethodType, boolean isBridgeMethod) {
Expand Down Expand Up @@ -140,7 +239,7 @@ private void createForwardingMethod(SootClass caller, String lambdaClassName, Cl
// parameter for the descriptor generation as it's
// not part of the method signature.
String implDescriptor = null;
List<Type> paramTypes = new ArrayList<Type>(implMethod.getMethodType().getParameterTypes());
List<Type> paramTypes = new ArrayList<>(implMethod.getMethodType().getParameterTypes());
if (isInstanceMethod)
paramTypes.remove(0);
implDescriptor = Types.getDescriptor(paramTypes, implMethod.getMethodType().getReturnType());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,6 @@ private void transformMethod(Config config, Clazz clazz, SootClass sootClass,
samMethodType.getReturnType());
for (SootMethod targetTypeMethod : targetType.getMethods()) {
boolean isBridgeMethod = targetTypeMethod.getName().equals(invokedName);
isBridgeMethod &= targetTypeMethod.getName().equals(invokedName);
isBridgeMethod &= targetTypeMethod.getParameterCount() == samMethodType.getParameterTypes().size();
isBridgeMethod &= ((targetTypeMethod.getModifiers() & BRIDGE) != 0);
isBridgeMethod &= ((targetTypeMethod.getModifiers() & SYNTHETIC) != 0);
Expand Down