Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import java.lang.foreign.MemoryLayout;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.SegmentAllocator;
import java.lang.foreign.SequenceLayout;
import java.lang.foreign.ValueLayout;
import static java.lang.foreign.ValueLayout.ADDRESS;
import static java.lang.foreign.ValueLayout.JAVA_BOOLEAN;
Expand Down Expand Up @@ -85,6 +86,7 @@ public static boolean isSupported() {

@Override
protected MethodHandle arrangeDowncall(MethodType inferredMethodType, FunctionDescriptor function, LinkerOptions options) {
assertNotEmpty(function);
MemorySegment cif = makeCif(inferredMethodType, function, options, Arena.ofAuto());

int capturedStateMask = options.capturedCallState()
Expand All @@ -111,6 +113,7 @@ protected MethodHandle arrangeDowncall(MethodType inferredMethodType, FunctionDe

@Override
protected UpcallStubFactory arrangeUpcall(MethodType targetType, FunctionDescriptor function, LinkerOptions options) {
assertNotEmpty(function);
MemorySegment cif = makeCif(targetType, function, options, Arena.ofAuto());

UpcallData invData = new UpcallData(function.returnLayout().orElse(null), function.argumentLayouts(), cif);
Expand Down Expand Up @@ -325,4 +328,35 @@ class Holder {

return Holder.CANONICAL_LAYOUTS;
}

private static void assertNotEmpty(FunctionDescriptor fd) {
fd.returnLayout().ifPresent(FallbackLinker::assertNotEmpty);
fd.argumentLayouts().forEach(FallbackLinker::assertNotEmpty);
}

// Recursively tests for emptiness
private static void assertNotEmpty(MemoryLayout layout) {
switch (layout) {
case GroupLayout gl -> {
if (gl.memberLayouts().isEmpty()) {
throw empty(gl);
} else {
gl.memberLayouts().forEach(FallbackLinker::assertNotEmpty);
}
}
case SequenceLayout sl -> {
if (sl.elementCount() == 0) {
throw empty(sl);
} else {
assertNotEmpty(sl.elementLayout());
}
}
default -> { /* do nothing */ }
}
}

private static IllegalArgumentException empty(MemoryLayout layout) {
return new IllegalArgumentException("The layout " + layout + " is empty");
}

}
11 changes: 9 additions & 2 deletions test/jdk/java/foreign/TestLinker.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,13 @@

/*
* @test
* @modules java.base/jdk.internal.foreign
* @modules java.base/jdk.internal.foreign java.base/jdk.internal.foreign.abi.fallback
* @run testng TestLinker
* @run testng/othervm TestLinker
*/

import jdk.internal.foreign.CABI;
import jdk.internal.foreign.abi.fallback.FallbackLinker;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

Expand Down Expand Up @@ -250,7 +251,13 @@ public void sequenceOfZeroElements() {
var padding5a1 = MemoryLayout.paddingLayout(5);
var struct8a8 = MemoryLayout.structLayout(sequence0a8, sequence3a1, padding5a1);
var fd = FunctionDescriptor.of(struct8a8, struct8a8, struct8a8);
linker.downcallHandle(fd);
if (linker.getClass().equals(FallbackLinker.class)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alternatively, we could move this test case to a separate file (perhaps with other test cases that test empty aggregates), and then exclude it for the fallback linker using @requires jdk.foreign.linker != "FALLBACK".

I don't mind this approach though.

// The fallback linker does not support empty layouts (FFI_BAD_TYPEDEF)
var iae = expectThrows(IllegalArgumentException.class, () -> linker.downcallHandle(fd));
assertTrue(iae.getMessage().contains("is empty"));
} else {
linker.downcallHandle(fd);
}
}

@DataProvider
Expand Down