Skip to content

Commit

Permalink
Merge pull request #292 from headius/new_varargs
Browse files Browse the repository at this point in the history
Adapt variadic logic for new support in jffi
  • Loading branch information
headius authored Jan 6, 2022
2 parents 773ee60 + bde8cb8 commit 942b98e
Show file tree
Hide file tree
Showing 6 changed files with 101 additions and 24 deletions.
4 changes: 2 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -61,13 +61,13 @@
<dependency>
<groupId>com.github.jnr</groupId>
<artifactId>jffi</artifactId>
<version>1.3.8</version>
<version>1.3.9-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.github.jnr</groupId>
<artifactId>jffi</artifactId>
<version>1.3.8</version>
<version>1.3.9-SNAPSHOT</version>
<scope>runtime</scope>
<classifier>native</classifier>
</dependency>
Expand Down
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
37 changes: 28 additions & 9 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,15 +131,24 @@ 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) {
marshallers[i] = getMarshaller(parameterTypes[i]);
}

return new DefaultInvoker(runtime, library, function, functionInvoker, marshallers);
invoker = new DefaultInvoker(runtime, library, function, functionInvoker, marshallers);
}

//
Expand Down Expand Up @@ -355,10 +365,17 @@ public final Object invoke(Object self, Object[] parameters) {
}
}

//Add one extra vararg of NULL to meet the common convention of ending
//varargs with a NULL. Functions that get a length from the fixed arguments
//will ignore the extra, and funtions that expect the extra NULL will get it.
//This matches what JNA does.
// Add one extra vararg of NULL to meet the common convention of ending
// varargs with a NULL. Functions that get a length from the fixed arguments
// will ignore the extra, and funtions that expect the extra NULL will get it.
// This matches what JNA does.
//
// Note: After https://github.com/jnr/jffi/pull/121 we now use the variadic ffi_prep_cif_var function for
// setup of the call when invoking a variadic function, which does not need the trailing null. However, for
// 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 All @@ -368,8 +385,10 @@ public final Object invoke(Object self, Object[] parameters) {
variableArgs[variableArgsCount] = null;
variableArgsCount++;

int fixedParamCount = fixedParameterTypes.length - 1;
int totalArgsCount = variableArgsCount + fixedParamCount;
Function function = new Function(functionAddress,
getCallContext(resultType, argTypes, variableArgsCount + fixedParameterTypes.length - 1, callingConvention, requiresErrno));
getCallContext(resultType, fixedParamCount, argTypes, totalArgsCount, callingConvention, requiresErrno));
HeapInvocationBuffer buffer = new HeapInvocationBuffer(function.getCallContext());

InvocationSession session = new InvocationSession();
Expand All @@ -379,7 +398,7 @@ public final Object invoke(Object self, Object[] parameters) {
}

for (int i = 0; i < variableArgsCount; ++i) {
getMarshaller(argTypes[i + fixedParameterTypes.length - 1]).marshal(session, buffer, variableArgs[i]);
getMarshaller(argTypes[i + fixedParamCount]).marshal(session, buffer, variableArgs[i]);
}

return functionInvoker.invoke(runtime, function, buffer);
Expand Down
16 changes: 12 additions & 4 deletions src/main/java/jnr/ffi/provider/jffi/InvokerUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -189,18 +189,26 @@ static ParameterType[] getParameterTypes(jnr.ffi.Runtime runtime, SignatureTypeM
}

static CallContext getCallContext(SigType resultType, SigType[] parameterTypes, jnr.ffi.CallingConvention convention, boolean requiresErrno) {
return getCallContext(resultType, parameterTypes, parameterTypes.length, convention, requiresErrno);
return getCallContext(resultType, parameterTypes.length, parameterTypes, parameterTypes.length, convention, requiresErrno);
}

static CallContext getCallContext(SigType resultType, SigType[] parameterTypes, int paramTypesLength, jnr.ffi.CallingConvention convention, boolean requiresErrno) {
static CallContext getCallContext(SigType resultType, int fixedParamCount, SigType[] parameterTypes, jnr.ffi.CallingConvention convention, boolean requiresErrno) {
return getCallContext(resultType, fixedParamCount, parameterTypes, parameterTypes.length, convention, requiresErrno);
}

static CallContext getCallContext(SigType resultType, int fixedParamCount, SigType[] parameterTypes, int paramTypesLength, jnr.ffi.CallingConvention convention, boolean requiresErrno) {
com.kenai.jffi.Type[] nativeParamTypes = new com.kenai.jffi.Type[paramTypesLength];

for (int i = 0; i < nativeParamTypes.length; ++i) {
nativeParamTypes[i] = jffiType(parameterTypes[i].getNativeType());
}

return CallContextCache.getInstance().getCallContext(jffiType(resultType.getNativeType()),
nativeParamTypes, jffiConvention(convention), requiresErrno);
return CallContextCache.getInstance().getCallContext(
jffiType(resultType.getNativeType()),
fixedParamCount,
nativeParamTypes,
jffiConvention(convention),
requiresErrno);
}


Expand Down
27 changes: 27 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,10 @@

public class VarargsTest {
public static interface C {
@Variadic(fixedCount = 3)
public int snprintf(Pointer buffer, @size_t long bufferSize, String format, @size_t int value);
@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 +44,28 @@ public static void tearDownClass() throws Exception {
assertEquals("12345", result);
}

@Test public void testSizeTNoType() {
Pointer ptr = Runtime.getRuntime(c).getMemoryManager().allocate(5000);
int size = c.snprintf(ptr, 5000, "%zu", 12345L);
assertEquals(5, size);
String result = ptr.getString(0, size, Charset.defaultCharset());
assertEquals("12345", result);
}

@Test public void testSizeTNoVarargs() {
Pointer ptr = Runtime.getRuntime(c).getMemoryManager().allocate(5000);
int size = c.snprintf(ptr, 5000, "%zu", 12345L);
assertEquals(5, size);
String result = ptr.getString(0, size, Charset.defaultCharset());
assertEquals("12345", result);

// int form with size_t annotation
size = c.snprintf(ptr, 5000, "%zu", 12345);
assertEquals(5, size);
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 942b98e

Please sign in to comment.