Skip to content

Commit

Permalink
Support automatic proxying of Chipmunk objects to Java interfaces whe…
Browse files Browse the repository at this point in the history
…n calling Java methods.
  • Loading branch information
danielperano committed Apr 17, 2024
1 parent 8afbad3 commit c38aac5
Show file tree
Hide file tree
Showing 7 changed files with 181 additions and 5 deletions.
2 changes: 1 addition & 1 deletion Lang/src/main/java/chipmunk/vm/ChipmunkVM.java
Original file line number Diff line number Diff line change
Expand Up @@ -403,7 +403,7 @@ public <T> T proxy(Class<T> interfaceType, Object target) throws NoSuchMethodExc
throw new IllegalArgumentException("MethodBinding target may only be cast to a functional interface");
}

var proxyName = interfaceType.getName() + "$Proxy$" + target.getClass().getName().replace('.', '$');
var proxyName = "chipmunk.proxy." + interfaceType.getName() + "$Proxy$" + target.getClass().getName().replace('.', '$');

var script = ChipmunkScript.getCurrentScript();
var classloader = script.getModuleLoader().getClassLoader();
Expand Down
15 changes: 15 additions & 0 deletions Lang/src/main/java/chipmunk/vm/invoke/ChipmunkLinker.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import chipmunk.runtime.MethodBinding;
import chipmunk.runtime.TraitField;
import chipmunk.vm.ChipmunkScript;
import chipmunk.vm.ChipmunkVM;
import chipmunk.vm.invoke.security.LinkingPolicy;
import jdk.dynalink.NamedOperation;
import jdk.dynalink.StandardOperation;
Expand Down Expand Up @@ -245,6 +246,7 @@ public MethodHandle getMethod(Object receiver, Class<?> expectedReturnType, Stri
if (retType.equals(void.class) || isCallTypeCompatible(expectedReturnType, retType)) {

boolean paramsMatch = true;
long interfaceParamMask = 0;
boolean isStatic = Modifier.isStatic(m.getModifiers());
for (int i = 0; i < candidatePTypes.length; i++) {

Expand All @@ -255,6 +257,10 @@ public MethodHandle getMethod(Object receiver, Class<?> expectedReturnType, Stri

//isCallTypeCompatible(candidatePType, callPType != null ? callPType : Object.class);
if (!isCallTypeCompatible(candidatePType, callPType != null ? callPType : Object.class)) {
if(candidatePType.isInterface()){
interfaceParamMask |= (1L << i);
continue;
}
paramsMatch = false;
break;
}
Expand All @@ -270,6 +276,15 @@ public MethodHandle getMethod(Object receiver, Class<?> expectedReturnType, Stri
var handle = isStatic
? MethodHandles.dropArguments(lookup.unreflect(m), 0, Object.class)
: lookup.unreflect(m);
while(interfaceParamMask != 0){
for(int i = candidatePTypes.length - 1; i >= 0; i--){
var paramIndex = isStatic ? i : i + 1;
if((interfaceParamMask & 1) != 0){
handle = MethodHandles.filterArguments(handle, paramIndex, ProxyFilter.filterFor(lookup, candidatePTypes[i]).asType(MethodType.methodType(candidatePTypes[i], Object.class)));
}
interfaceParamMask >>>= 1; // Do unsigned right shift so that a 1 in the leading bit isn't propagated
}
}
if(m.isVarArgs()){
handle = handle.asVarargsCollector(Object[].class);
}
Expand Down
58 changes: 58 additions & 0 deletions Lang/src/main/java/chipmunk/vm/invoke/ProxyFilter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* Copyright (C) 2024 MyWorld, LLC
* All rights reserved.
*
* This file is part of Chipmunk.
*
* Chipmunk 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 3 of the License, or
* (at your option) any later version.
*
* Chipmunk 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 Chipmunk. If not, see <https://www.gnu.org/licenses/>.
*/

package chipmunk.vm.invoke;

import chipmunk.vm.ChipmunkScript;
import chipmunk.vm.ChipmunkVM;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.InvocationTargetException;

public class ProxyFilter {

protected final ChipmunkVM vm;
protected final Class<?> target;

public ProxyFilter(ChipmunkVM vm, Class<?> target){
this.vm = vm;
this.target = target;
}

public Object filter(Object param) throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {
if(param == null){
return param;
}

return vm.proxy(target, param);
}

public static MethodHandle filterFor(MethodHandles.Lookup lookup, Class<?> target) throws NoSuchMethodException, IllegalAccessException{
return filterFor(lookup, ChipmunkScript.getCurrentScript().getVM(), target);
}

public static MethodHandle filterFor(MethodHandles.Lookup lookup, ChipmunkVM vm, Class<?> target) throws NoSuchMethodException, IllegalAccessException {
return lookup.findVirtual(ProxyFilter.class, "filter", MethodType.methodType(Object.class, Object.class))
.bindTo(new ProxyFilter(vm, target));
}

}
22 changes: 18 additions & 4 deletions Lang/src/test/groovy/chipmunk/LanguageSpecification.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ class LanguageSpecification extends Specification {
ChipmunkCompiler compiler = new ChipmunkCompiler()

def compileAndRun(String scriptName, boolean disassembleOnException = false){
return compileAndRunWithArgs(scriptName, null, disassembleOnException)
}

def compileAndRunWithArgs(String scriptName, List args = null, boolean disassembleOnException = false){
ModuleLoader loader = new ModuleLoader()
loader.registerNativeFactory(JvmImportModule.IMPORT_MODULE_NAME, { new JvmImportModule()})

Expand All @@ -60,21 +64,23 @@ class LanguageSpecification extends Specification {
script.setModuleLoader(loader)
ChipmunkScript.setCurrentScript(script)

def argArray = args != null ? args.toArray() : null

if(!disassembleOnException){
return script.run()
return argArray == null ? script.run() : script.run(argArray)
}else{
try{
return script.run()
return argArray == null ? script.run() : script.run(argArray)
}catch(Throwable e){

for(def binaryModule : modules){
println(ChipmunkDisassembler.disassemble(binaryModule))
}

def sw = new StringWriter()
e.printStackTrace(new PrintWriter(sw))
println(sw.toString())

throw e
}
}
Expand Down Expand Up @@ -415,6 +421,14 @@ class LanguageSpecification extends Specification {
fResult == 32.0f
}

def "Run ProxyArguments.chp"(){
when:
def result = compileAndRunWithArgs("ProxyArguments.chp", [new SimpleDemoProxyReceiver()], true)

then:
result == 7.0f
}

def "Run MultilineExpressions.chp"(){
when:
def result = compileAndRun("MultilineExpressions.chp", true)
Expand Down
28 changes: 28 additions & 0 deletions Lang/src/test/groovy/chipmunk/SimpleDemoProxy.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright (C) 2024 MyWorld, LLC
* All rights reserved.
*
* This file is part of Chipmunk.
*
* Chipmunk 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 3 of the License, or
* (at your option) any later version.
*
* Chipmunk 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 Chipmunk. If not, see <https://www.gnu.org/licenses/>.
*/

package chipmunk;

public interface SimpleDemoProxy {

int getA();
float getB();

}
31 changes: 31 additions & 0 deletions Lang/src/test/groovy/chipmunk/SimpleDemoProxyReceiver.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Copyright (C) 2024 MyWorld, LLC
* All rights reserved.
*
* This file is part of Chipmunk.
*
* Chipmunk 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 3 of the License, or
* (at your option) any later version.
*
* Chipmunk 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 Chipmunk. If not, see <https://www.gnu.org/licenses/>.
*/

package chipmunk;

import java.util.function.Supplier;

public class SimpleDemoProxyReceiver {

public float callWithProxyArguments(SimpleDemoProxy proxy, Supplier<Integer> f){
return proxy.getA() + proxy.getB() + f.get();
}

}
30 changes: 30 additions & 0 deletions Lang/src/test/resources/chipmunk/ProxyArguments.chp
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Copyright (C) 2024 MyWorld, LLC
# All rights reserved.
#
# This file is part of Chipmunk.
#
# Chipmunk 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 3 of the License, or
# (at your option) any later version.
#
# Chipmunk 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 Chipmunk. If not, see <https://www.gnu.org/licenses/>.
module test

class ProxyTarget {

def getA() 1

def getB() 2.0

}

def main(receiver){
return receiver.callWithProxyArguments(ProxyTarget.new(), def() 4)
}

0 comments on commit c38aac5

Please sign in to comment.