From 505374e5d3998a2051ebbeb820cf6aad72a3879d Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Wed, 5 Jan 2022 10:05:01 -0600 Subject: [PATCH] Add Variadic annotation to specify fixed args When calling a variadic function with a non-varargs Java signature users must provide the Variadic annotation to indicate how many fixed arguments the target function takes. This is required to support platforms where variadic args are passed differently than fixed args, such as on Apple Silicon. --- .../java/jnr/ffi/annotations/Variadic.java | 20 ++++++++++++++++++ .../ffi/provider/jffi/AsmLibraryLoader.java | 21 +++++++++++-------- .../provider/jffi/DefaultInvokerFactory.java | 16 +++++++++++--- src/test/java/jnr/ffi/VarargsTest.java | 11 ++++++++++ 4 files changed, 56 insertions(+), 12 deletions(-) create mode 100644 src/main/java/jnr/ffi/annotations/Variadic.java diff --git a/src/main/java/jnr/ffi/annotations/Variadic.java b/src/main/java/jnr/ffi/annotations/Variadic.java new file mode 100644 index 00000000..f4ad5de0 --- /dev/null +++ b/src/main/java/jnr/ffi/annotations/Variadic.java @@ -0,0 +1,20 @@ +package jnr.ffi.annotations; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Specifies that a non-varargs function binding will call a variadic C function with the specified number of fixed + * arguments. + * + * Platforms that pass variadic arguments differently than fixed arguments will need this annotation if the Java binding + * does not itself use varargs. Without Java varargs or this annotation, there's no way for jnr-ffi to know where fixed + * args end and variadic arguments begin, causing them all to be passed the way fixed arguments get passed on the given + * platform. + * + * See https://github.com/jnr/jnr-ffi/pull/292 + */ +@Retention(RetentionPolicy.RUNTIME) +public @interface Variadic { + int fixedCount(); +} diff --git a/src/main/java/jnr/ffi/provider/jffi/AsmLibraryLoader.java b/src/main/java/jnr/ffi/provider/jffi/AsmLibraryLoader.java index cbb9c682..61cc90f2 100644 --- a/src/main/java/jnr/ffi/provider/jffi/AsmLibraryLoader.java +++ b/src/main/java/jnr/ffi/provider/jffi/AsmLibraryLoader.java @@ -21,6 +21,7 @@ import com.kenai.jffi.Function; +import jnr.ffi.annotations.Variadic; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.ClassWriter; @@ -126,9 +127,11 @@ private T generateInterfaceImpl(final NativeLibrary library, Class interf InterfaceScanner scanner = new InterfaceScanner(interfaceClass, typeMapper, libraryCallingConvention); for (NativeFunction function : scanner.functions()) { - if (function.getMethod().isVarArgs()) { - ObjectField field = builder.getObjectField(invokerFactory.createInvoker(function.getMethod()), Invoker.class); - generateVarargsInvocation(builder, function.getMethod(), field); + Method method = function.getMethod(); + + if (method.isVarArgs() || method.isAnnotationPresent(Variadic.class)) { + ObjectField field = builder.getObjectField(invokerFactory.createInvoker(method), Invoker.class); + generateVarargsInvocation(builder, method, field); continue; } @@ -137,13 +140,13 @@ private T generateInterfaceImpl(final NativeLibrary library, Class interf try { long functionAddress = library.findSymbolAddress(functionName); - FromNativeContext resultContext = new MethodResultContext(runtime, function.getMethod()); - SignatureType signatureType = DefaultSignatureType.create(function.getMethod().getReturnType(), resultContext); - ResultType resultType = getResultType(runtime, function.getMethod().getReturnType(), + FromNativeContext resultContext = new MethodResultContext(runtime, method); + SignatureType signatureType = DefaultSignatureType.create(method.getReturnType(), resultContext); + ResultType resultType = getResultType(runtime, method.getReturnType(), resultContext.getAnnotations(), typeMapper.getFromNativeType(signatureType, resultContext), resultContext); - ParameterType[] parameterTypes = getParameterTypes(runtime, typeMapper, function.getMethod()); + ParameterType[] parameterTypes = getParameterTypes(runtime, typeMapper, method); boolean saveError = jnr.ffi.LibraryLoader.saveError(libraryOptions, function.hasSaveError(), function.hasIgnoreError()); @@ -152,7 +155,7 @@ private T generateInterfaceImpl(final NativeLibrary library, Class interf for (MethodGenerator g : generators) { if (g.isSupported(resultType, parameterTypes, function.convention())) { - g.generate(builder, function.getMethod().getName(), jffiFunction, resultType, parameterTypes, !saveError); + g.generate(builder, method.getName(), jffiFunction, resultType, parameterTypes, !saveError); break; } } @@ -161,7 +164,7 @@ private T generateInterfaceImpl(final NativeLibrary library, Class interf String errorFieldName = "error_" + uniqueId.incrementAndGet(); cv.visitField(ACC_PRIVATE | ACC_FINAL | ACC_STATIC, errorFieldName, ci(String.class), null, ex.getMessage()); generateFunctionNotFound(cv, builder.getClassNamePath(), errorFieldName, functionName, - function.getMethod().getReturnType(), function.getMethod().getParameterTypes()); + method.getReturnType(), method.getParameterTypes()); } } diff --git a/src/main/java/jnr/ffi/provider/jffi/DefaultInvokerFactory.java b/src/main/java/jnr/ffi/provider/jffi/DefaultInvokerFactory.java index 5ad69176..1a75e597 100644 --- a/src/main/java/jnr/ffi/provider/jffi/DefaultInvokerFactory.java +++ b/src/main/java/jnr/ffi/provider/jffi/DefaultInvokerFactory.java @@ -32,6 +32,7 @@ import jnr.ffi.annotations.Meta; import jnr.ffi.annotations.StdCall; import jnr.ffi.annotations.Synchronized; +import jnr.ffi.annotations.Variadic; import jnr.ffi.mapper.DataConverter; import jnr.ffi.mapper.DefaultSignatureType; import jnr.ffi.mapper.FromNativeContext; @@ -130,8 +131,17 @@ public Invoker createInvoker(Method method) { if (method.isVarArgs()) { invoker = new VariadicInvoker(runtime, functionInvoker, typeMapper, parameterTypes, functionAddress, resultType, saveError, callingConvention); } else { - Function function = new Function(functionAddress, - getCallContext(resultType, parameterTypes, callingConvention, saveError)); + Function function; + // check if method is all-fixed but calling a variadic function + Variadic variadic = method.getAnnotation(Variadic.class); + + if (variadic != null) { + function = new Function(functionAddress, + getCallContext(resultType, variadic.fixedCount(), parameterTypes, callingConvention, saveError)); + } else { + function = new Function(functionAddress, + getCallContext(resultType, parameterTypes, callingConvention, saveError)); + } Marshaller[] marshallers = new Marshaller[parameterTypes.length]; for (int i = 0; i < marshallers.length; ++i) { @@ -365,7 +375,7 @@ public final Object invoke(Object self, Object[] parameters) { // platforms where we have not rebuilt the jffi stub we still set up this trailing NULL to be compatible // with ffi_prep_cif and the common va_arg layout. Once all platforms have been rebuilt to use // ffi_prep_cif_var, this NULL and the +1 on variableArgs allocation above can be removed. - + argTypes[fixedParameterTypes.length + variableArgsCount - 1] = new ParameterType( Pointer.class, Types.getType(runtime, Pointer.class, Collections.emptyList()).getNativeType(), diff --git a/src/test/java/jnr/ffi/VarargsTest.java b/src/test/java/jnr/ffi/VarargsTest.java index 7a31b154..54642012 100644 --- a/src/test/java/jnr/ffi/VarargsTest.java +++ b/src/test/java/jnr/ffi/VarargsTest.java @@ -2,6 +2,7 @@ import jnr.ffi.annotations.Encoding; import jnr.ffi.annotations.Meta; +import jnr.ffi.annotations.Variadic; import jnr.ffi.provider.FFIProvider; import jnr.ffi.types.size_t; import org.junit.jupiter.api.AfterAll; @@ -15,6 +16,8 @@ public class VarargsTest { public static interface C { + @Variadic(fixedCount = 3) + public int snprintf(Pointer buffer, @size_t long bufferSize, String format, long value); public int snprintf(Pointer buffer, @size_t long bufferSize, String format, Object... varargs); } @@ -39,6 +42,14 @@ public static void tearDownClass() throws Exception { assertEquals("12345", result); } + @Test public void testSizeTNoVarargs() { + Pointer ptr = Runtime.getRuntime(c).getMemoryManager().allocate(5000); + int size = c.snprintf(ptr, 5000, "%zu", 12345); + assertEquals(5, size); + String result = ptr.getString(0, size, Charset.defaultCharset()); + assertEquals("12345", result); + } + @Test public void testMetaAscii() throws UnsupportedEncodingException { Pointer ptr = Runtime.getRuntime(c).getMemoryManager().allocate(5000); int size = c.snprintf(ptr, 5000, "%s", AsciiEncoding.class, "\u7684");