diff --git a/Lang/src/main/java/chipmunk/vm/ChipmunkVM.java b/Lang/src/main/java/chipmunk/vm/ChipmunkVM.java index fa3794a..a487818 100644 --- a/Lang/src/main/java/chipmunk/vm/ChipmunkVM.java +++ b/Lang/src/main/java/chipmunk/vm/ChipmunkVM.java @@ -403,7 +403,7 @@ public T proxy(Class 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(); diff --git a/Lang/src/main/java/chipmunk/vm/invoke/ChipmunkLinker.java b/Lang/src/main/java/chipmunk/vm/invoke/ChipmunkLinker.java index fc54241..13f1d3f 100644 --- a/Lang/src/main/java/chipmunk/vm/invoke/ChipmunkLinker.java +++ b/Lang/src/main/java/chipmunk/vm/invoke/ChipmunkLinker.java @@ -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; @@ -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++) { @@ -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; } @@ -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); } diff --git a/Lang/src/main/java/chipmunk/vm/invoke/ProxyFilter.java b/Lang/src/main/java/chipmunk/vm/invoke/ProxyFilter.java new file mode 100644 index 0000000..55cf71a --- /dev/null +++ b/Lang/src/main/java/chipmunk/vm/invoke/ProxyFilter.java @@ -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 . + */ + +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)); + } + +} diff --git a/Lang/src/test/groovy/chipmunk/LanguageSpecification.groovy b/Lang/src/test/groovy/chipmunk/LanguageSpecification.groovy index ee7bc8a..f72311c 100644 --- a/Lang/src/test/groovy/chipmunk/LanguageSpecification.groovy +++ b/Lang/src/test/groovy/chipmunk/LanguageSpecification.groovy @@ -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()}) @@ -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 } } @@ -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) diff --git a/Lang/src/test/groovy/chipmunk/SimpleDemoProxy.java b/Lang/src/test/groovy/chipmunk/SimpleDemoProxy.java new file mode 100644 index 0000000..503e854 --- /dev/null +++ b/Lang/src/test/groovy/chipmunk/SimpleDemoProxy.java @@ -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 . + */ + +package chipmunk; + +public interface SimpleDemoProxy { + + int getA(); + float getB(); + +} diff --git a/Lang/src/test/groovy/chipmunk/SimpleDemoProxyReceiver.java b/Lang/src/test/groovy/chipmunk/SimpleDemoProxyReceiver.java new file mode 100644 index 0000000..893f2e1 --- /dev/null +++ b/Lang/src/test/groovy/chipmunk/SimpleDemoProxyReceiver.java @@ -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 . + */ + +package chipmunk; + +import java.util.function.Supplier; + +public class SimpleDemoProxyReceiver { + + public float callWithProxyArguments(SimpleDemoProxy proxy, Supplier f){ + return proxy.getA() + proxy.getB() + f.get(); + } + +} diff --git a/Lang/src/test/resources/chipmunk/ProxyArguments.chp b/Lang/src/test/resources/chipmunk/ProxyArguments.chp new file mode 100644 index 0000000..3ebe1c6 --- /dev/null +++ b/Lang/src/test/resources/chipmunk/ProxyArguments.chp @@ -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 . +module test + +class ProxyTarget { + + def getA() 1 + + def getB() 2.0 + +} + +def main(receiver){ + return receiver.callWithProxyArguments(ProxyTarget.new(), def() 4) +} \ No newline at end of file