Skip to content

Commit

Permalink
Add Variadic annotation to specify fixed args
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
headius committed Jan 5, 2022
1 parent b5c0934 commit 505374e
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 12 deletions.
20 changes: 20 additions & 0 deletions src/main/java/jnr/ffi/annotations/Variadic.java
Original file line number Diff line number Diff line change
@@ -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();
}
21 changes: 12 additions & 9 deletions src/main/java/jnr/ffi/provider/jffi/AsmLibraryLoader.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -126,9 +127,11 @@ private <T> T generateInterfaceImpl(final NativeLibrary library, Class<T> 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;
}

Expand All @@ -137,13 +140,13 @@ private <T> T generateInterfaceImpl(final NativeLibrary library, Class<T> 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());

Expand All @@ -152,7 +155,7 @@ private <T> T generateInterfaceImpl(final NativeLibrary library, Class<T> 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;
}
}
Expand All @@ -161,7 +164,7 @@ private <T> T generateInterfaceImpl(final NativeLibrary library, Class<T> 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());
}
}

Expand Down
16 changes: 13 additions & 3 deletions src/main/java/jnr/ffi/provider/jffi/DefaultInvokerFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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.<Annotation>emptyList()).getNativeType(),
Expand Down
11 changes: 11 additions & 0 deletions src/test/java/jnr/ffi/VarargsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);
}

Expand All @@ -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");
Expand Down

0 comments on commit 505374e

Please sign in to comment.