undefect. CWE-407 — 63 sites patched across 27 ecosystems
Authors: russell@unturf.com · brackishbert@gmail.com · foxhop.net · TimeHexOn.com Patches, unit tests, benchmarks, whitepaper, and outreach briefs. Public domain — no copyright claimed. Use freely.
This commit is contained in:
commit
0a580b313d
70422 changed files with 17213626 additions and 0 deletions
433
test/jdk/java/foreign/StdLibTest.java
Normal file
433
test/jdk/java/foreign/StdLibTest.java
Normal file
|
|
@ -0,0 +1,433 @@
|
|||
/*
|
||||
* Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @run testng/othervm/timeout=480 --enable-native-access=ALL-UNNAMED StdLibTest
|
||||
*/
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import java.lang.foreign.*;
|
||||
|
||||
import org.testng.annotations.*;
|
||||
|
||||
import static org.testng.Assert.*;
|
||||
|
||||
public class StdLibTest extends NativeTestHelper {
|
||||
|
||||
final static Linker abi = Linker.nativeLinker();
|
||||
|
||||
private StdLibHelper stdLibHelper = new StdLibHelper();
|
||||
|
||||
@Test(dataProvider = "stringPairs")
|
||||
void test_strcat(String s1, String s2) throws Throwable {
|
||||
assertEquals(stdLibHelper.strcat(s1, s2), s1 + s2);
|
||||
}
|
||||
|
||||
@Test(dataProvider = "stringPairs")
|
||||
void test_strcmp(String s1, String s2) throws Throwable {
|
||||
assertEquals(Math.signum(stdLibHelper.strcmp(s1, s2)), Math.signum(s1.compareTo(s2)));
|
||||
}
|
||||
|
||||
@Test(dataProvider = "strings")
|
||||
void test_puts(String s) throws Throwable {
|
||||
assertTrue(stdLibHelper.puts(s) >= 0);
|
||||
}
|
||||
|
||||
@Test(dataProvider = "strings")
|
||||
void test_strlen(String s) throws Throwable {
|
||||
assertEquals(stdLibHelper.strlen(s), s.length());
|
||||
}
|
||||
|
||||
@Test(dataProvider = "instants")
|
||||
void test_time(Instant instant) throws Throwable {
|
||||
StdLibHelper.Tm tm = stdLibHelper.gmtime(instant.getEpochSecond());
|
||||
LocalDateTime localTime = LocalDateTime.ofInstant(instant, ZoneOffset.UTC);
|
||||
assertEquals(tm.sec(), localTime.getSecond());
|
||||
assertEquals(tm.min(), localTime.getMinute());
|
||||
assertEquals(tm.hour(), localTime.getHour());
|
||||
//day pf year in Java has 1-offset
|
||||
assertEquals(tm.yday(), localTime.getDayOfYear() - 1);
|
||||
assertEquals(tm.mday(), localTime.getDayOfMonth());
|
||||
//days of week starts from Sunday in C, but on Monday in Java, also account for 1-offset
|
||||
assertEquals((tm.wday() + 6) % 7, localTime.getDayOfWeek().getValue() - 1);
|
||||
//month in Java has 1-offset
|
||||
assertEquals(tm.mon(), localTime.getMonth().getValue() - 1);
|
||||
assertEquals(tm.isdst(), ZoneOffset.UTC.getRules()
|
||||
.isDaylightSavings(Instant.ofEpochMilli(instant.getEpochSecond() * 1000)));
|
||||
}
|
||||
|
||||
@Test(dataProvider = "ints")
|
||||
void test_qsort(List<Integer> ints) throws Throwable {
|
||||
if (ints.size() > 0) {
|
||||
int[] input = ints.stream().mapToInt(i -> i).toArray();
|
||||
int[] sorted = stdLibHelper.qsort(input);
|
||||
Arrays.sort(input);
|
||||
assertEquals(sorted, input);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_rand() throws Throwable {
|
||||
int val = stdLibHelper.rand();
|
||||
for (int i = 0 ; i < 100 ; i++) {
|
||||
int newVal = stdLibHelper.rand();
|
||||
if (newVal != val) {
|
||||
return; //ok
|
||||
}
|
||||
val = newVal;
|
||||
}
|
||||
fail("All values are the same! " + val);
|
||||
}
|
||||
|
||||
@Test(dataProvider = "printfArgs")
|
||||
void test_printf(List<PrintfArg> args) throws Throwable {
|
||||
String javaFormatArgs = args.stream()
|
||||
.map(a -> a.javaFormat)
|
||||
.collect(Collectors.joining(","));
|
||||
String nativeFormatArgs = args.stream()
|
||||
.map(a -> a.nativeFormat)
|
||||
.collect(Collectors.joining(","));
|
||||
|
||||
String javaFormatString = "hello(" + javaFormatArgs + ")\n";
|
||||
String nativeFormatString = "hello(" + nativeFormatArgs + ")\n";
|
||||
|
||||
String expected = String.format(javaFormatString, args.stream()
|
||||
.map(a -> a.javaValue).toArray());
|
||||
|
||||
int found = stdLibHelper.printf(nativeFormatString, args);
|
||||
assertEquals(found, expected.length());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSystemLibraryBadLookupName() {
|
||||
assertTrue(LINKER.defaultLookup().find("strlen\u0000foobar").isEmpty());
|
||||
}
|
||||
|
||||
static class StdLibHelper {
|
||||
|
||||
final static MethodHandle strcat = abi.downcallHandle(abi.defaultLookup().find("strcat").get(),
|
||||
FunctionDescriptor.of(C_POINTER, C_POINTER, C_POINTER));
|
||||
|
||||
final static MethodHandle strcmp = abi.downcallHandle(abi.defaultLookup().find("strcmp").get(),
|
||||
FunctionDescriptor.of(C_INT, C_POINTER, C_POINTER));
|
||||
|
||||
final static MethodHandle puts = abi.downcallHandle(abi.defaultLookup().find("puts").get(),
|
||||
FunctionDescriptor.of(C_INT, C_POINTER));
|
||||
|
||||
final static MethodHandle strlen = abi.downcallHandle(abi.defaultLookup().find("strlen").get(),
|
||||
FunctionDescriptor.of(C_INT, C_POINTER));
|
||||
|
||||
final static MethodHandle gmtime = abi.downcallHandle(abi.defaultLookup().find("gmtime").get(),
|
||||
FunctionDescriptor.of(C_POINTER.withTargetLayout(Tm.LAYOUT), C_POINTER));
|
||||
|
||||
// void qsort( void *ptr, size_t count, size_t size, int (*comp)(const void *, const void *) );
|
||||
final static MethodHandle qsort = abi.downcallHandle(abi.defaultLookup().find("qsort").get(),
|
||||
FunctionDescriptor.ofVoid(C_POINTER, C_SIZE_T, C_SIZE_T, C_POINTER));
|
||||
|
||||
final static FunctionDescriptor qsortComparFunction = FunctionDescriptor.of(C_INT,
|
||||
C_POINTER.withTargetLayout(C_INT), C_POINTER.withTargetLayout(C_INT));
|
||||
|
||||
final static MethodHandle qsortCompar;
|
||||
|
||||
final static MethodHandle rand = abi.downcallHandle(abi.defaultLookup().find("rand").get(),
|
||||
FunctionDescriptor.of(C_INT));
|
||||
|
||||
final static MethodHandle vprintf = abi.downcallHandle(abi.defaultLookup().find("vprintf").get(),
|
||||
FunctionDescriptor.of(C_INT, C_POINTER, C_POINTER));
|
||||
|
||||
final static MemorySegment printfAddr = abi.defaultLookup().find("printf").get();
|
||||
|
||||
final static FunctionDescriptor printfBase = FunctionDescriptor.of(C_INT, C_POINTER);
|
||||
|
||||
static {
|
||||
try {
|
||||
//qsort upcall handle
|
||||
qsortCompar = MethodHandles.lookup().findStatic(StdLibTest.StdLibHelper.class, "qsortCompare",
|
||||
qsortComparFunction.toMethodType());
|
||||
} catch (ReflectiveOperationException ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
String strcat(String s1, String s2) throws Throwable {
|
||||
try (var arena = Arena.ofConfined()) {
|
||||
MemorySegment buf = arena.allocate(s1.length() + s2.length() + 1);
|
||||
buf.setString(0, s1);
|
||||
MemorySegment other = arena.allocateFrom(s2);
|
||||
return ((MemorySegment)strcat.invokeExact(buf, other)).getString(0);
|
||||
}
|
||||
}
|
||||
|
||||
int strcmp(String s1, String s2) throws Throwable {
|
||||
try (var arena = Arena.ofConfined()) {
|
||||
MemorySegment ns1 = arena.allocateFrom(s1);
|
||||
MemorySegment ns2 = arena.allocateFrom(s2);
|
||||
return (int)strcmp.invokeExact(ns1, ns2);
|
||||
}
|
||||
}
|
||||
|
||||
int puts(String msg) throws Throwable {
|
||||
try (var arena = Arena.ofConfined()) {
|
||||
MemorySegment s = arena.allocateFrom(msg);
|
||||
return (int)puts.invokeExact(s);
|
||||
}
|
||||
}
|
||||
|
||||
int strlen(String msg) throws Throwable {
|
||||
try (var arena = Arena.ofConfined()) {
|
||||
MemorySegment s = arena.allocateFrom(msg);
|
||||
return (int)strlen.invokeExact(s);
|
||||
}
|
||||
}
|
||||
|
||||
Tm gmtime(long arg) throws Throwable {
|
||||
try (var arena = Arena.ofConfined()) {
|
||||
MemorySegment time = arena.allocate(8);
|
||||
time.set(C_LONG_LONG, 0, arg);
|
||||
return new Tm((MemorySegment)gmtime.invokeExact(time));
|
||||
}
|
||||
}
|
||||
|
||||
static class Tm {
|
||||
|
||||
//Tm pointer should never be freed directly, as it points to shared memory
|
||||
private final MemorySegment base;
|
||||
|
||||
static final MemoryLayout LAYOUT = MemoryLayout.structLayout(
|
||||
C_INT.withName("sec"),
|
||||
C_INT.withName("min"),
|
||||
C_INT.withName("hour"),
|
||||
C_INT.withName("mday"),
|
||||
C_INT.withName("mon"),
|
||||
C_INT.withName("year"),
|
||||
C_INT.withName("wday"),
|
||||
C_INT.withName("yday"),
|
||||
C_BOOL.withName("isdst"),
|
||||
MemoryLayout.paddingLayout(3)
|
||||
);
|
||||
|
||||
Tm(MemorySegment addr) {
|
||||
this.base = addr;
|
||||
}
|
||||
|
||||
int sec() {
|
||||
return base.get(C_INT, 0);
|
||||
}
|
||||
int min() {
|
||||
return base.get(C_INT, 4);
|
||||
}
|
||||
int hour() {
|
||||
return base.get(C_INT, 8);
|
||||
}
|
||||
int mday() {
|
||||
return base.get(C_INT, 12);
|
||||
}
|
||||
int mon() {
|
||||
return base.get(C_INT, 16);
|
||||
}
|
||||
int year() {
|
||||
return base.get(C_INT, 20);
|
||||
}
|
||||
int wday() {
|
||||
return base.get(C_INT, 24);
|
||||
}
|
||||
int yday() {
|
||||
return base.get(C_INT, 28);
|
||||
}
|
||||
boolean isdst() {
|
||||
return base.get(C_BOOL, 32);
|
||||
}
|
||||
}
|
||||
|
||||
int[] qsort(int[] arr) throws Throwable {
|
||||
//init native array
|
||||
try (var arena = Arena.ofConfined()) {
|
||||
MemorySegment nativeArr = arena.allocateFrom(C_INT, arr);
|
||||
|
||||
//call qsort
|
||||
MemorySegment qsortUpcallStub = abi.upcallStub(qsortCompar, qsortComparFunction, arena);
|
||||
|
||||
// both of these fit in an int
|
||||
// automatically widen them to long on x64
|
||||
int count = arr.length;
|
||||
int size = (int) C_INT.byteSize();
|
||||
qsort.invoke(nativeArr, count, size, qsortUpcallStub);
|
||||
|
||||
//convert back to Java array
|
||||
return nativeArr.toArray(C_INT);
|
||||
}
|
||||
}
|
||||
|
||||
static int qsortCompare(MemorySegment addr1, MemorySegment addr2) {
|
||||
return addr1.get(C_INT, 0) -
|
||||
addr2.get(C_INT, 0);
|
||||
}
|
||||
|
||||
int rand() throws Throwable {
|
||||
return (int)rand.invokeExact();
|
||||
}
|
||||
|
||||
int printf(String format, List<PrintfArg> args) throws Throwable {
|
||||
try (var arena = Arena.ofConfined()) {
|
||||
MemorySegment formatStr = arena.allocateFrom(format);
|
||||
return (int)specializedPrintf(args).invokeExact(formatStr,
|
||||
args.stream().map(a -> a.nativeValue(arena)).toArray());
|
||||
}
|
||||
}
|
||||
|
||||
private MethodHandle specializedPrintf(List<PrintfArg> args) {
|
||||
//method type
|
||||
MethodType mt = MethodType.methodType(int.class, MemorySegment.class);
|
||||
FunctionDescriptor fd = printfBase;
|
||||
List<MemoryLayout> variadicLayouts = new ArrayList<>(args.size());
|
||||
for (PrintfArg arg : args) {
|
||||
mt = mt.appendParameterTypes(arg.carrier);
|
||||
variadicLayouts.add(arg.layout);
|
||||
}
|
||||
Linker.Option varargIndex = Linker.Option.firstVariadicArg(fd.argumentLayouts().size());
|
||||
MethodHandle mh = abi.downcallHandle(printfAddr,
|
||||
fd.appendArgumentLayouts(variadicLayouts.toArray(new MemoryLayout[args.size()])),
|
||||
varargIndex);
|
||||
return mh.asSpreader(1, Object[].class, args.size());
|
||||
}
|
||||
}
|
||||
|
||||
/*** data providers ***/
|
||||
|
||||
@DataProvider
|
||||
public static Object[][] ints() {
|
||||
return perms(0, new Integer[] { 0, 1, 2, 3, 4 }).stream()
|
||||
.map(l -> new Object[] { l })
|
||||
.toArray(Object[][]::new);
|
||||
}
|
||||
|
||||
@DataProvider
|
||||
public static Object[][] strings() {
|
||||
return perms(0, new String[] { "a", "b", "c" }).stream()
|
||||
.map(l -> new Object[] { String.join("", l) })
|
||||
.toArray(Object[][]::new);
|
||||
}
|
||||
|
||||
@DataProvider
|
||||
public static Object[][] stringPairs() {
|
||||
Object[][] strings = strings();
|
||||
Object[][] stringPairs = new Object[strings.length * strings.length][];
|
||||
int pos = 0;
|
||||
for (Object[] s1 : strings) {
|
||||
for (Object[] s2 : strings) {
|
||||
stringPairs[pos++] = new Object[] { s1[0], s2[0] };
|
||||
}
|
||||
}
|
||||
return stringPairs;
|
||||
}
|
||||
|
||||
@DataProvider
|
||||
public static Object[][] instants() {
|
||||
Instant start = ZonedDateTime.of(LocalDateTime.parse("2017-01-01T00:00:00"), ZoneOffset.UTC).toInstant();
|
||||
Instant end = ZonedDateTime.of(LocalDateTime.parse("2017-12-31T00:00:00"), ZoneOffset.UTC).toInstant();
|
||||
Object[][] instants = new Object[100][];
|
||||
for (int i = 0 ; i < instants.length ; i++) {
|
||||
Instant instant = start.plusSeconds((long)(Math.random() * (end.getEpochSecond() - start.getEpochSecond())));
|
||||
instants[i] = new Object[] { instant };
|
||||
}
|
||||
return instants;
|
||||
}
|
||||
|
||||
@DataProvider
|
||||
public static Object[][] printfArgs() {
|
||||
ArrayList<List<PrintfArg>> res = new ArrayList<>();
|
||||
List<List<PrintfArg>> perms = new ArrayList<>(perms(0, PrintfArg.values()));
|
||||
for (int i = 0 ; i < 100 ; i++) {
|
||||
Collections.shuffle(perms);
|
||||
res.addAll(perms);
|
||||
}
|
||||
return res.stream()
|
||||
.map(l -> new Object[] { l })
|
||||
.toArray(Object[][]::new);
|
||||
}
|
||||
|
||||
enum PrintfArg {
|
||||
INT(int.class, C_INT, "%d", "%d", arena -> 42, 42),
|
||||
LONG(long.class, C_LONG_LONG, "%lld", "%d", arena -> 84L, 84L),
|
||||
DOUBLE(double.class, C_DOUBLE, "%.4f", "%.4f", arena -> 1.2345d, 1.2345d),
|
||||
STRING(MemorySegment.class, C_POINTER, "%s", "%s", arena -> arena.allocateFrom("str"), "str");
|
||||
|
||||
final Class<?> carrier;
|
||||
final ValueLayout layout;
|
||||
final String nativeFormat;
|
||||
final String javaFormat;
|
||||
final Function<Arena, ?> nativeValueFactory;
|
||||
final Object javaValue;
|
||||
|
||||
<Z, L extends ValueLayout> PrintfArg(Class<?> carrier, L layout, String nativeFormat, String javaFormat,
|
||||
Function<Arena, Z> nativeValueFactory, Object javaValue) {
|
||||
this.carrier = carrier;
|
||||
this.layout = layout;
|
||||
this.nativeFormat = nativeFormat;
|
||||
this.javaFormat = javaFormat;
|
||||
this.nativeValueFactory = nativeValueFactory;
|
||||
this.javaValue = javaValue;
|
||||
}
|
||||
|
||||
public Object nativeValue(Arena arena) {
|
||||
return nativeValueFactory.apply(arena);
|
||||
}
|
||||
}
|
||||
|
||||
static <Z> Set<List<Z>> perms(int count, Z[] arr) {
|
||||
if (count == arr.length) {
|
||||
return Set.of(List.of());
|
||||
} else {
|
||||
return Arrays.stream(arr)
|
||||
.flatMap(num -> {
|
||||
Set<List<Z>> perms = perms(count + 1, arr);
|
||||
return Stream.concat(
|
||||
//take n
|
||||
perms.stream().map(l -> {
|
||||
List<Z> li = new ArrayList<>(l);
|
||||
li.add(num);
|
||||
return li;
|
||||
}),
|
||||
//drop n
|
||||
perms.stream());
|
||||
}).collect(Collectors.toCollection(LinkedHashSet::new));
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue