-
Enhancement
-
Resolution: Unresolved
-
P4
-
22, 23
-
None
In some cases, native compilers seem to zero/sign extend arguments whose size is < 32 bits (narrow arguments) at the caller side. In at least one case - clang with -O optimization enabled - the compiler seems to rely on such normalization at the callee side.
It is possible to construct an example where calling a shared lib compiled with clang on Linux/x64 yields an unexpected result.
//foo.c
#include <stdio.h>
#include <stdlib.h>
char* consumer(unsigned char a) {
int ex = a;
char *buf = malloc(100);
sprintf(buf, "VALUE = %d", ex);
return buf;
}
//TestSign.java
import java.lang.foreign.*;
import java.lang.invoke.*;
import java.nio.file.Path;
class TestSign {
static final SymbolLookup FOO = SymbolLookup.libraryLookup(Path.of("libfoo.so"), Arena.global());
static final MethodHandle CONSUMER = Linker.nativeLinker().downcallHandle(
FOO.find("consumer").get(),
FunctionDescriptor.of(ValueLayout.ADDRESS, ValueLayout.JAVA_BYTE)
);
public static void main(String[] args) throws Throwable {
MemorySegment buf = (MemorySegment)CONSUMER.invokeExact((byte)-42);
String str = buf.reinterpret(Long.MAX_VALUE).getString(0L);
System.out.println(str);
}
}
This works fine if the native library is compiled with gcc
$ gcc foo.c -O1 -shared -fPIC -o libfoo.so
$ java --enable-native-access=ALL-UNNAMED TestSign
VALUE = 214
(note how the printed value is positive)
But doesn't work as expected when the library is compiled with clang:
$ clang foo.c -O1 -shared -fPIC -o libfoo.so
$ java --enable-native-access=ALL-UNNAMED TestSign
VALUE = -42
(note that the value is -42, that is, the same value passed from the Java side).
This is a bit of a gray area, as discussed here:
https://mail.openjdk.org/pipermail/panama-dev/2024-July/020564.html
It is possible to construct an example where calling a shared lib compiled with clang on Linux/x64 yields an unexpected result.
//foo.c
#include <stdio.h>
#include <stdlib.h>
char* consumer(unsigned char a) {
int ex = a;
char *buf = malloc(100);
sprintf(buf, "VALUE = %d", ex);
return buf;
}
//TestSign.java
import java.lang.foreign.*;
import java.lang.invoke.*;
import java.nio.file.Path;
class TestSign {
static final SymbolLookup FOO = SymbolLookup.libraryLookup(Path.of("libfoo.so"), Arena.global());
static final MethodHandle CONSUMER = Linker.nativeLinker().downcallHandle(
FOO.find("consumer").get(),
FunctionDescriptor.of(ValueLayout.ADDRESS, ValueLayout.JAVA_BYTE)
);
public static void main(String[] args) throws Throwable {
MemorySegment buf = (MemorySegment)CONSUMER.invokeExact((byte)-42);
String str = buf.reinterpret(Long.MAX_VALUE).getString(0L);
System.out.println(str);
}
}
This works fine if the native library is compiled with gcc
$ gcc foo.c -O1 -shared -fPIC -o libfoo.so
$ java --enable-native-access=ALL-UNNAMED TestSign
VALUE = 214
(note how the printed value is positive)
But doesn't work as expected when the library is compiled with clang:
$ clang foo.c -O1 -shared -fPIC -o libfoo.so
$ java --enable-native-access=ALL-UNNAMED TestSign
VALUE = -42
(note that the value is -42, that is, the same value passed from the Java side).
This is a bit of a gray area, as discussed here:
https://mail.openjdk.org/pipermail/panama-dev/2024-July/020564.html