8308031: Linkers should reject unpromoted variadic parameters

Reviewed-by: mcimadamore
This commit is contained in:
Jorn Vernee 2023-06-07 12:14:55 +00:00
parent 16ebf47fe3
commit fa791119f0
11 changed files with 138 additions and 24 deletions

View file

@ -365,11 +365,41 @@ import java.util.stream.Stream;
*
* <h3 id="variadic-funcs">Variadic functions</h3>
*
* Variadic functions (e.g. a C function declared with a trailing ellipses {@code ...} at the end of the formal parameter
* list or with an empty formal parameter list) are not supported directly by the native linker. However, it is still possible
* to link a variadic function by using a <em>specialized</em> function descriptor, together with a
* {@linkplain Linker.Option#firstVariadicArg(int) a linker option} which indicates the position of the first variadic argument
* in that specialized descriptor.
* Variadic functions are C functions which can accept a variable number and type of arguments. They are declared:
* <ol>
* <li>With a trailing ellipsis ({@code ...}) at the end of the formal parameter list, such as: {@code void foo(int x, ...);}</li>
* <li>With an empty formal parameter list, called a prototype-less function, such as: {@code void foo();}</li>
* </ol>
* The arguments passed in place of the ellipsis, or the arguments passed to a prototype-less function are called
* <em>variadic arguments</em>. Variadic functions are, essentially, templates that can be <em>specialized</em> into multiple
* non-variadic functions by replacing the {@code ...} or empty formal parameter list with a list of <em>variadic parameters</em>
* of a fixed number and type.
* <p>
* It should be noted that values passed as variadic arguments undergo default argument promotion in C. For instance, the
* following argument promotions are applied:
* <ul>
* <li>{@code _Bool} -> {@code unsigned int}</li>
* <li>{@code [signed] char} -> {@code [signed] int}</li>
* <li>{@code [signed] short} -> {@code [signed] int}</li>
* <li>{@code float} -> {@code double}</li>
* </ul>
* whereby the signed-ness of the source type corresponds to the signed-ness of the promoted type. The complete process
* of default argument promotion is described in the C specification. In effect these promotions place limits on the
* specialized form of a variadic function, as the variadic parameters of the specialized form will always have a promoted
* type.
* <p>
* The native linker only supports linking the specialized form of a variadic function. A variadic function in its specialized
* form can be linked using a function descriptor describing the specialized form. Additionally, the
* {@link Linker.Option#firstVariadicArg(int)} linker option must be provided to indicate the first variadic parameter in
* the parameter list. The corresponding argument layout, and all following argument layouts in the specialized function
* descriptor, are called <em>variadic argument layouts</em>. For a prototype-less function, the index passed to
* {@link Linker.Option#firstVariadicArg(int)} should always be {@code 0}.
* <p>
* The native linker will reject an attempt to link a specialized function descriptor with any variadic argument layouts
* corresponding to a C type that would be subject to default argument promotion (as described above). Exactly which layouts
* will be rejected is platform specific, but as an example: on Linux/x64 the layouts {@link ValueLayout#JAVA_BOOLEAN},
* {@link ValueLayout#JAVA_BYTE}, {@link ValueLayout#JAVA_CHAR}, {@link ValueLayout#JAVA_SHORT}, and
* {@link ValueLayout#JAVA_FLOAT} will be rejected.
* <p>
* A well-known variadic function is the {@code printf} function, defined in the C standard library:
*

View file

@ -90,6 +90,7 @@ public abstract sealed class AbstractLinker implements Linker permits LinuxAArch
checkLayouts(function);
function = stripNames(function);
LinkerOptions optionSet = LinkerOptions.forDowncall(function, options);
validateVariadicLayouts(function, optionSet);
return DOWNCALL_CACHE.get(new LinkRequest(function, optionSet), linkRequest -> {
FunctionDescriptor fd = linkRequest.descriptor();
@ -134,6 +135,28 @@ public abstract sealed class AbstractLinker implements Linker permits LinuxAArch
/** {@return byte order used by this linker} */
protected abstract ByteOrder linkerByteOrder();
// C spec mandates that variadic arguments smaller than int are promoted to int,
// and float is promoted to double
// See: https://en.cppreference.com/w/c/language/conversion#Default_argument_promotions
// We reject the corresponding layouts here, to avoid issues where unsigned values
// are sign extended when promoted. (as we don't have a way to unambiguously represent signed-ness atm).
private void validateVariadicLayouts(FunctionDescriptor function, LinkerOptions optionSet) {
if (optionSet.isVariadicFunction()) {
List<MemoryLayout> argumentLayouts = function.argumentLayouts();
List<MemoryLayout> variadicLayouts = argumentLayouts.subList(optionSet.firstVariadicArgIndex(), argumentLayouts.size());
for (MemoryLayout variadicLayout : variadicLayouts) {
if (variadicLayout.equals(ValueLayout.JAVA_BOOLEAN)
|| variadicLayout.equals(ValueLayout.JAVA_BYTE)
|| variadicLayout.equals(ValueLayout.JAVA_CHAR)
|| variadicLayout.equals(ValueLayout.JAVA_SHORT)
|| variadicLayout.equals(ValueLayout.JAVA_FLOAT)) {
throw new IllegalArgumentException("Invalid variadic argument layout: " + variadicLayout);
}
}
}
}
private void checkLayouts(FunctionDescriptor descriptor) {
descriptor.returnLayout().ifPresent(this::checkLayout);
descriptor.argumentLayouts().forEach(this::checkLayout);

View file

@ -93,6 +93,10 @@ public class LinkerOptions {
return fva != null;
}
public int firstVariadicArgIndex() {
return getOption(FirstVariadicArg.class).index();
}
public boolean isTrivial() {
IsTrivial it = getOption(IsTrivial.class);
return it != null;

View file

@ -81,7 +81,7 @@ public final class FallbackLinker extends AbstractLinker {
@Override
protected MethodHandle arrangeDowncall(MethodType inferredMethodType, FunctionDescriptor function, LinkerOptions options) {
MemorySegment cif = makeCif(inferredMethodType, function, FFIABI.DEFAULT, Arena.ofAuto());
MemorySegment cif = makeCif(inferredMethodType, function, options, Arena.ofAuto());
int capturedStateMask = options.capturedCallState()
.mapToInt(CapturableState::mask)
@ -107,7 +107,7 @@ public final class FallbackLinker extends AbstractLinker {
@Override
protected UpcallStubFactory arrangeUpcall(MethodType targetType, FunctionDescriptor function, LinkerOptions options) {
MemorySegment cif = makeCif(targetType, function, FFIABI.DEFAULT, Arena.ofAuto());
MemorySegment cif = makeCif(targetType, function, options, Arena.ofAuto());
UpcallData invData = new UpcallData(function.returnLayout().orElse(null), function.argumentLayouts(), cif);
MethodHandle doUpcallMH = MethodHandles.insertArguments(MH_DO_UPCALL, 3, invData);
@ -123,7 +123,9 @@ public final class FallbackLinker extends AbstractLinker {
return ByteOrder.nativeOrder();
}
private static MemorySegment makeCif(MethodType methodType, FunctionDescriptor function, FFIABI abi, Arena scope) {
private static MemorySegment makeCif(MethodType methodType, FunctionDescriptor function, LinkerOptions options, Arena scope) {
FFIABI abi = FFIABI.DEFAULT;
MemorySegment argTypes = scope.allocate(function.argumentLayouts().size() * ADDRESS.byteSize());
List<MemoryLayout> argLayouts = function.argumentLayouts();
for (int i = 0; i < argLayouts.size(); i++) {
@ -134,7 +136,14 @@ public final class FallbackLinker extends AbstractLinker {
MemorySegment returnType = methodType.returnType() != void.class
? FFIType.toFFIType(function.returnLayout().orElseThrow(), abi, scope)
: LibFallback.voidType();
return LibFallback.prepCif(returnType, argLayouts.size(), argTypes, abi, scope);
if (options.isVariadicFunction()) {
int numFixedArgs = options.firstVariadicArgIndex();
int numTotalArgs = argLayouts.size();
return LibFallback.prepCifVar(returnType, numFixedArgs, numTotalArgs, argTypes, abi, scope);
} else {
return LibFallback.prepCif(returnType, argLayouts.size(), argTypes, abi, scope);
}
}
private record DowncallData(MemorySegment cif, MemoryLayout returnLayout, List<MemoryLayout> argLayouts,

View file

@ -104,6 +104,27 @@ final class LibFallback {
return cif;
}
/**
* Wrapper for {@code ffi_prep_cif_var}. The variadic version of prep_cif
*
* @param returnType a pointer to an @{code ffi_type} describing the return type
* @param numFixedArgs the number of fixed arguments
* @param numTotalArgs the number of total arguments
* @param paramTypes a pointer to an array of pointers, which each point to an {@code ffi_type} describing a
* parameter type
* @param abi the abi to be used
* @param scope the scope into which to allocate the returned {@code ffi_cif} struct
* @return a pointer to a prepared {@code ffi_cif} struct
*
* @throws IllegalStateException if the call to {@code ffi_prep_cif} returns a non-zero status code
*/
static MemorySegment prepCifVar(MemorySegment returnType, int numFixedArgs, int numTotalArgs, MemorySegment paramTypes, FFIABI abi,
Arena scope) throws IllegalStateException {
MemorySegment cif = scope.allocate(NativeConstants.SIZEOF_CIF);
checkStatus(ffi_prep_cif_var(cif.address(), abi.value(), numFixedArgs, numTotalArgs, returnType.address(), paramTypes.address()));
return cif;
}
/**
* Create an upcallStub-style closure. This method wraps the {@code ffi_closure_alloc}
* and {@code ffi_prep_closure_loc} functions.
@ -177,6 +198,7 @@ final class LibFallback {
private static native void doDowncall(long cif, long fn, long rvalue, long avalues, long capturedState, int capturedStateMask);
private static native int ffi_prep_cif(long cif, int abi, int nargs, long rtype, long atypes);
private static native int ffi_prep_cif_var(long cif, int abi, int nfixedargs, int ntotalargs, long rtype, long atypes);
private static native int ffi_get_struct_offsets(int abi, long type, long offsets);
private static native int ffi_default_abi();