From d8aa5c0f9ab51466ef00f27304cdac28d02dcf4f Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Thu, 25 Dec 2025 13:42:57 +0200 Subject: [PATCH 01/40] Include file stream natives in translator output --- .../tools/translator/ByteCodeTranslator.java | 4 + .../src/java_io_FileStreams.m | 276 ++++++++++++++++++ vm/JavaAPI/src/java/io/File.java | 37 +++ vm/JavaAPI/src/java/io/FileFilter.java | 32 ++ vm/JavaAPI/src/java/io/FileInputStream.java | 119 ++++++++ vm/JavaAPI/src/java/io/FileOutputStream.java | 98 +++++++ vm/JavaAPI/src/java/io/FileWriter.java | 43 +++ vm/JavaAPI/src/java/io/FilenameFilter.java | 32 ++ .../FileStreamsIntegrationTest.java | 148 ++++++++++ 9 files changed, 789 insertions(+) create mode 100644 vm/ByteCodeTranslator/src/java_io_FileStreams.m create mode 100644 vm/JavaAPI/src/java/io/FileFilter.java create mode 100644 vm/JavaAPI/src/java/io/FileInputStream.java create mode 100644 vm/JavaAPI/src/java/io/FileOutputStream.java create mode 100644 vm/JavaAPI/src/java/io/FileWriter.java create mode 100644 vm/JavaAPI/src/java/io/FilenameFilter.java create mode 100644 vm/tests/src/test/java/com/codename1/tools/translator/FileStreamsIntegrationTest.java diff --git a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/ByteCodeTranslator.java b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/ByteCodeTranslator.java index 5790a9d5da..393b43ab5c 100644 --- a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/ByteCodeTranslator.java +++ b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/ByteCodeTranslator.java @@ -246,6 +246,8 @@ private static void handleCleanOutput(ByteCodeTranslator b, File[] sources, File copy(ByteCodeTranslator.class.getResourceAsStream("/nativeMethods.m"), new FileOutputStream(nativeMethods)); File javaIoFileM = new File(srcRoot, "java_io_File.m"); copy(ByteCodeTranslator.class.getResourceAsStream("/java_io_File.m"), new FileOutputStream(javaIoFileM)); + File javaIoFileStreamsM = new File(srcRoot, "java_io_FileStreams.m"); + copy(ByteCodeTranslator.class.getResourceAsStream("/java_io_FileStreams.m"), new FileOutputStream(javaIoFileStreamsM)); Parser.writeOutput(srcRoot); @@ -303,6 +305,8 @@ private static void handleIosOutput(ByteCodeTranslator b, File[] sources, File d copy(ByteCodeTranslator.class.getResourceAsStream("/nativeMethods.m"), new FileOutputStream(nativeMethods)); File javaIoFileM = new File(srcRoot, "java_io_File.m"); copy(ByteCodeTranslator.class.getResourceAsStream("/java_io_File.m"), new FileOutputStream(javaIoFileM)); + File javaIoFileStreamsM = new File(srcRoot, "java_io_FileStreams.m"); + copy(ByteCodeTranslator.class.getResourceAsStream("/java_io_FileStreams.m"), new FileOutputStream(javaIoFileStreamsM)); if (System.getProperty("USE_RPMALLOC", "false").equals("true")) { File malloc = new File(srcRoot, "malloc.c"); diff --git a/vm/ByteCodeTranslator/src/java_io_FileStreams.m b/vm/ByteCodeTranslator/src/java_io_FileStreams.m new file mode 100644 index 0000000000..50194ae649 --- /dev/null +++ b/vm/ByteCodeTranslator/src/java_io_FileStreams.m @@ -0,0 +1,276 @@ +#include "cn1_globals.h" +#include "java_io_FileInputStream.h" +#include "java_io_FileOutputStream.h" +#include "java_lang_String.h" + +#if defined(__APPLE__) && defined(__OBJC__) +#import +#include +#include +#include + +static NSFileHandle* toHandle(JAVA_LONG handle) { + return (NSFileHandle*)(uintptr_t)handle; +} + +static JAVA_LONG retainHandle(NSFileHandle* handle) { + if (handle == nil) { + return 0; + } + [handle retain]; + return (JAVA_LONG)(uintptr_t)handle; +} + +JAVA_LONG java_io_FileInputStream_openImpl___java_lang_String_R_long(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT __cn1ThisObject, JAVA_OBJECT path) { + if (path == JAVA_NULL) { + return 0; + } + NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; + NSString* p = toNSString(CN1_THREAD_STATE_PASS_ARG path); + NSFileHandle* handle = [NSFileHandle fileHandleForReadingAtPath:p]; + JAVA_LONG result = retainHandle(handle); + [pool release]; + return result; +} + +JAVA_INT java_io_FileInputStream_readImpl___long_byte_1ARRAY_int_int_R_int(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT __cn1ThisObject, JAVA_LONG handle, JAVA_OBJECT buffer, JAVA_INT off, JAVA_INT len) { + NSFileHandle* h = toHandle(handle); + if (h == nil) { + return -1; + } + NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; + NSData* data = [h readDataOfLength:len]; + NSUInteger count = [data length]; + if (count > 0 && buffer != JAVA_NULL) { + memcpy(((JAVA_ARRAY_BYTE*)(*(JAVA_ARRAY)buffer).data) + off, [data bytes], count); + } + [pool release]; + return count == 0 ? -1 : (JAVA_INT)count; +} + +JAVA_LONG java_io_FileInputStream_skipImpl___long_long_R_long(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT __cn1ThisObject, JAVA_LONG handle, JAVA_LONG n) { + NSFileHandle* h = toHandle(handle); + if (h == nil) { + return 0; + } + unsigned long long current = [h offsetInFile]; + unsigned long long target = current + (unsigned long long)n; + [h seekToFileOffset:target]; + unsigned long long updated = [h offsetInFile]; + if (updated < current) { + return 0; + } + return (JAVA_LONG)(updated - current); +} + +JAVA_INT java_io_FileInputStream_availableImpl___long_R_int(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT __cn1ThisObject, JAVA_LONG handle) { + NSFileHandle* h = toHandle(handle); + if (h == nil) { + return 0; + } + NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; + unsigned long long current = [h offsetInFile]; + unsigned long long end = [h seekToEndOfFile]; + [h seekToFileOffset:current]; + [pool release]; + if (end <= current) { + return 0; + } + unsigned long long diff = end - current; + if (diff > (unsigned long long)INT_MAX) { + return INT_MAX; + } + return (JAVA_INT)diff; +} + +JAVA_VOID java_io_FileInputStream_closeImpl___long(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT __cn1ThisObject, JAVA_LONG handle) { + NSFileHandle* h = toHandle(handle); + if (h == nil) { + return; + } + NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; + [h closeFile]; + [h release]; + [pool release]; +} + +JAVA_LONG java_io_FileOutputStream_openImpl___java_lang_String_boolean_R_long(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT __cn1ThisObject, JAVA_OBJECT path, JAVA_BOOLEAN append) { + if (path == JAVA_NULL) { + return 0; + } + NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; + NSString* p = toNSString(CN1_THREAD_STATE_PASS_ARG path); + NSFileManager* manager = [NSFileManager defaultManager]; + if (![manager fileExistsAtPath:p]) { + [manager createFileAtPath:p contents:nil attributes:nil]; + } else if (!append) { + [manager removeItemAtPath:p error:NULL]; + [manager createFileAtPath:p contents:nil attributes:nil]; + } + NSFileHandle* handle = [NSFileHandle fileHandleForWritingAtPath:p]; + if (append && handle != nil) { + [handle seekToEndOfFile]; + } + JAVA_LONG result = retainHandle(handle); + [pool release]; + return result; +} + +JAVA_VOID java_io_FileOutputStream_writeImpl___long_byte_1ARRAY_int_int(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT __cn1ThisObject, JAVA_LONG handle, JAVA_OBJECT buffer, JAVA_INT off, JAVA_INT len) { + NSFileHandle* h = toHandle(handle); + if (h == nil || buffer == JAVA_NULL) { + return; + } + NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; + NSData* data = [NSData dataWithBytes:((JAVA_ARRAY_BYTE*)(*(JAVA_ARRAY)buffer).data) + off length:len]; + [h writeData:data]; + [pool release]; +} + +JAVA_VOID java_io_FileOutputStream_flushImpl___long(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT __cn1ThisObject, JAVA_LONG handle) { + NSFileHandle* h = toHandle(handle); + if (h == nil) { + return; + } + NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; + [h synchronizeFile]; + [pool release]; +} + +JAVA_VOID java_io_FileOutputStream_closeImpl___long(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT __cn1ThisObject, JAVA_LONG handle) { + NSFileHandle* h = toHandle(handle); + if (h == nil) { + return; + } + NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; + [h closeFile]; + [h release]; + [pool release]; +} + +#else + +#include +#include +#include +#include +#include +#include "xmlvm.h" + +static int toFd(JAVA_LONG handle) { + return (int)handle; +} + +JAVA_LONG java_io_FileInputStream_openImpl___java_lang_String_R_long(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT __cn1ThisObject, JAVA_OBJECT path) { + if (path == JAVA_NULL) { + return 0; + } + const char* p = stringToUTF8(threadStateData, path); + int fd = open(p, O_RDONLY); + if (fd < 0) { + return 0; + } + return (JAVA_LONG)fd; +} + +JAVA_INT java_io_FileInputStream_readImpl___long_byte_1ARRAY_int_int_R_int(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT __cn1ThisObject, JAVA_LONG handle, JAVA_OBJECT buffer, JAVA_INT off, JAVA_INT len) { + int fd = toFd(handle); + if (fd < 0 || buffer == JAVA_NULL) { + return -1; + } + ssize_t count = read(fd, ((JAVA_ARRAY_BYTE*)(*(JAVA_ARRAY)buffer).data) + off, len); + if (count <= 0) { + return -1; + } + return (JAVA_INT)count; +} + +JAVA_LONG java_io_FileInputStream_skipImpl___long_long_R_long(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT __cn1ThisObject, JAVA_LONG handle, JAVA_LONG n) { + int fd = toFd(handle); + if (fd < 0 || n <= 0) { + return 0; + } + off_t current = lseek(fd, 0, SEEK_CUR); + if (current == (off_t)-1) { + return 0; + } + off_t target = lseek(fd, (off_t)n, SEEK_CUR); + if (target == (off_t)-1 || target < current) { + return 0; + } + return (JAVA_LONG)(target - current); +} + +JAVA_INT java_io_FileInputStream_availableImpl___long_R_int(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT __cn1ThisObject, JAVA_LONG handle) { + int fd = toFd(handle); + if (fd < 0) { + return 0; + } + off_t current = lseek(fd, 0, SEEK_CUR); + if (current == (off_t)-1) { + return 0; + } + off_t end = lseek(fd, 0, SEEK_END); + if (end == (off_t)-1) { + return 0; + } + lseek(fd, current, SEEK_SET); + if (end <= current) { + return 0; + } + off_t diff = end - current; + if (diff > (off_t)INT_MAX) { + return INT_MAX; + } + return (JAVA_INT)diff; +} + +JAVA_VOID java_io_FileInputStream_closeImpl___long(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT __cn1ThisObject, JAVA_LONG handle) { + int fd = toFd(handle); + if (fd >= 0) { + close(fd); + } +} + +JAVA_LONG java_io_FileOutputStream_openImpl___java_lang_String_boolean_R_long(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT __cn1ThisObject, JAVA_OBJECT path, JAVA_BOOLEAN append) { + if (path == JAVA_NULL) { + return 0; + } + const char* p = stringToUTF8(threadStateData, path); + int flags = O_WRONLY | O_CREAT; + if (append) { + flags |= O_APPEND; + } else { + flags |= O_TRUNC; + } + int fd = open(p, flags, 0666); + if (fd < 0) { + return 0; + } + return (JAVA_LONG)fd; +} + +JAVA_VOID java_io_FileOutputStream_writeImpl___long_byte_1ARRAY_int_int(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT __cn1ThisObject, JAVA_LONG handle, JAVA_OBJECT buffer, JAVA_INT off, JAVA_INT len) { + int fd = toFd(handle); + if (fd < 0 || buffer == JAVA_NULL || len <= 0) { + return; + } + ssize_t written = write(fd, ((JAVA_ARRAY_BYTE*)(*(JAVA_ARRAY)buffer).data) + off, len); + (void)written; +} + +JAVA_VOID java_io_FileOutputStream_flushImpl___long(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT __cn1ThisObject, JAVA_LONG handle) { + int fd = toFd(handle); + if (fd >= 0) { + fsync(fd); + } +} + +JAVA_VOID java_io_FileOutputStream_closeImpl___long(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT __cn1ThisObject, JAVA_LONG handle) { + int fd = toFd(handle); + if (fd >= 0) { + close(fd); + } +} + +#endif diff --git a/vm/JavaAPI/src/java/io/File.java b/vm/JavaAPI/src/java/io/File.java index fa29553647..c7250bbec9 100644 --- a/vm/JavaAPI/src/java/io/File.java +++ b/vm/JavaAPI/src/java/io/File.java @@ -173,6 +173,43 @@ public File[] listFiles() { } return fs; } + + public File[] listFiles(FileFilter filter) { + String[] names = list(); + if (names == null) return null; + File[] results = new File[names.length]; + int count = 0; + for (int i = 0; i < names.length; i++) { + File f = new File(this, names[i]); + if (filter == null || filter.accept(f)) { + results[count++] = f; + } + } + if (count == results.length) { + return results; + } + File[] trimmed = new File[count]; + System.arraycopy(results, 0, trimmed, 0, count); + return trimmed; + } + + public File[] listFiles(FilenameFilter filter) { + String[] names = list(); + if (names == null) return null; + File[] results = new File[names.length]; + int count = 0; + for (int i = 0; i < names.length; i++) { + if (filter == null || filter.accept(this, names[i])) { + results[count++] = new File(this, names[i]); + } + } + if (count == results.length) { + return results; + } + File[] trimmed = new File[count]; + System.arraycopy(results, 0, trimmed, 0, count); + return trimmed; + } public boolean mkdir() { return mkdirImpl(path); diff --git a/vm/JavaAPI/src/java/io/FileFilter.java b/vm/JavaAPI/src/java/io/FileFilter.java new file mode 100644 index 0000000000..a1f98434a8 --- /dev/null +++ b/vm/JavaAPI/src/java/io/FileFilter.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2024, Codename One 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. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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 Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ + +package java.io; + +/** + * Instances of this interface are used to filter {@link File} objects when + * enumerating directory contents. + */ +public interface FileFilter { + boolean accept(File pathname); +} diff --git a/vm/JavaAPI/src/java/io/FileInputStream.java b/vm/JavaAPI/src/java/io/FileInputStream.java new file mode 100644 index 0000000000..f10c2ba4ba --- /dev/null +++ b/vm/JavaAPI/src/java/io/FileInputStream.java @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2024, Codename One 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. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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 Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ + +package java.io; + +public class FileInputStream extends InputStream { + private long handle; + private boolean closed; + + public FileInputStream(String name) throws IOException { + this(name == null ? null : new File(name)); + } + + public FileInputStream(File file) throws IOException { + if (file == null) { + throw new NullPointerException(); + } + this.handle = openImpl(file.getPath()); + if (this.handle == 0) { + throw new IOException("Unable to open file for reading: " + file.getPath()); + } + } + + @Override + public int read() throws IOException { + byte[] buffer = new byte[1]; + int count = read(buffer, 0, 1); + if (count <= 0) { + return -1; + } + return buffer[0] & 0xff; + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + ensureOpen(); + if (b == null) { + throw new NullPointerException(); + } + if (off < 0 || len < 0 || off + len > b.length) { + throw new IndexOutOfBoundsException(); + } + if (len == 0) { + return 0; + } + return readImpl(handle, b, off, len); + } + + @Override + public long skip(long n) throws IOException { + ensureOpen(); + if (n <= 0) { + return 0; + } + return skipImpl(handle, n); + } + + @Override + public int available() throws IOException { + ensureOpen(); + return availableImpl(handle); + } + + @Override + public void close() throws IOException { + if (!closed) { + closed = true; + closeImpl(handle); + handle = 0; + } + } + + @Override + public synchronized void mark(int readlimit) { + // mark/reset not supported + } + + @Override + public void reset() throws IOException { + throw new IOException("mark/reset not supported"); + } + + @Override + public boolean markSupported() { + return false; + } + + private void ensureOpen() throws IOException { + if (closed) { + throw new IOException("Stream closed"); + } + } + + private static native long openImpl(String path) throws IOException; + private static native int readImpl(long handle, byte[] b, int off, int len) throws IOException; + private static native long skipImpl(long handle, long n) throws IOException; + private static native int availableImpl(long handle) throws IOException; + private static native void closeImpl(long handle) throws IOException; +} diff --git a/vm/JavaAPI/src/java/io/FileOutputStream.java b/vm/JavaAPI/src/java/io/FileOutputStream.java new file mode 100644 index 0000000000..9b37a43c16 --- /dev/null +++ b/vm/JavaAPI/src/java/io/FileOutputStream.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2024, Codename One 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. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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 Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ + +package java.io; + +public class FileOutputStream extends OutputStream { + private long handle; + private boolean closed; + + public FileOutputStream(String name) throws IOException { + this(name, false); + } + + public FileOutputStream(String name, boolean append) throws IOException { + this(name == null ? null : new File(name), append); + } + + public FileOutputStream(File file) throws IOException { + this(file, false); + } + + public FileOutputStream(File file, boolean append) throws IOException { + if (file == null) { + throw new NullPointerException(); + } + this.handle = openImpl(file.getPath(), append); + if (this.handle == 0) { + throw new IOException("Unable to open file for writing: " + file.getPath()); + } + } + + @Override + public void write(int b) throws IOException { + byte[] single = new byte[] {(byte)b}; + write(single, 0, 1); + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + ensureOpen(); + if (b == null) { + throw new NullPointerException(); + } + if (off < 0 || len < 0 || off + len > b.length) { + throw new IndexOutOfBoundsException(); + } + if (len == 0) { + return; + } + writeImpl(handle, b, off, len); + } + + @Override + public void flush() throws IOException { + ensureOpen(); + flushImpl(handle); + } + + @Override + public void close() throws IOException { + if (!closed) { + closed = true; + closeImpl(handle); + handle = 0; + } + } + + private void ensureOpen() throws IOException { + if (closed) { + throw new IOException("Stream closed"); + } + } + + private static native long openImpl(String path, boolean append) throws IOException; + private static native void writeImpl(long handle, byte[] b, int off, int len) throws IOException; + private static native void flushImpl(long handle) throws IOException; + private static native void closeImpl(long handle) throws IOException; +} diff --git a/vm/JavaAPI/src/java/io/FileWriter.java b/vm/JavaAPI/src/java/io/FileWriter.java new file mode 100644 index 0000000000..cdc9a52f01 --- /dev/null +++ b/vm/JavaAPI/src/java/io/FileWriter.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2024, Codename One 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. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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 Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ + +package java.io; + +public class FileWriter extends OutputStreamWriter { + + public FileWriter(String fileName) throws IOException { + this(new File(fileName), false); + } + + public FileWriter(String fileName, boolean append) throws IOException { + this(new File(fileName), append); + } + + public FileWriter(File file) throws IOException { + this(file, false); + } + + public FileWriter(File file, boolean append) throws IOException { + super(new FileOutputStream(file, append)); + } +} diff --git a/vm/JavaAPI/src/java/io/FilenameFilter.java b/vm/JavaAPI/src/java/io/FilenameFilter.java new file mode 100644 index 0000000000..11a2b3fe30 --- /dev/null +++ b/vm/JavaAPI/src/java/io/FilenameFilter.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2024, Codename One 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. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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 Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ + +package java.io; + +/** + * Implementations filter filename strings when enumerating directory + * contents. + */ +public interface FilenameFilter { + boolean accept(File dir, String name); +} diff --git a/vm/tests/src/test/java/com/codename1/tools/translator/FileStreamsIntegrationTest.java b/vm/tests/src/test/java/com/codename1/tools/translator/FileStreamsIntegrationTest.java new file mode 100644 index 0000000000..51e74008e0 --- /dev/null +++ b/vm/tests/src/test/java/com/codename1/tools/translator/FileStreamsIntegrationTest.java @@ -0,0 +1,148 @@ +package com.codename1.tools.translator; + +import org.junit.jupiter.params.ParameterizedTest; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class FileStreamsIntegrationTest { + + @ParameterizedTest + @org.junit.jupiter.params.provider.MethodSource("com.codename1.tools.translator.BytecodeInstructionIntegrationTest#provideCompilerConfigs") + public void testFileStreams(CompilerHelper.CompilerConfig config) throws Exception { + Parser.cleanup(); + + Path sourceDir = Files.createTempDirectory("file-stream-sources"); + Path classesDir = Files.createTempDirectory("file-stream-classes"); + Path javaFile = sourceDir.resolve("FileStreamApp.java"); + + Files.write(javaFile, fileStreamAppSource().getBytes(StandardCharsets.UTF_8)); + + Path javaApiSrc = Paths.get("../JavaAPI/src"); + if (!Files.exists(javaApiSrc)) { + javaApiSrc = Paths.get("vm/JavaAPI/src"); + } + + List compileArgs = new ArrayList<>(); + double jdkVer = 1.8; + try { jdkVer = Double.parseDouble(config.jdkVersion); } catch (NumberFormatException ignored) {} + + if (jdkVer >= 9) { + if (Double.parseDouble(config.targetVersion) < 9) { + return; + } + compileArgs.add("-source"); + compileArgs.add(config.targetVersion); + compileArgs.add("-target"); + compileArgs.add(config.targetVersion); + compileArgs.add("--patch-module"); + compileArgs.add("java.base=" + javaApiSrc.toString()); + compileArgs.add("-Xlint:-module"); + } else { + compileArgs.add("-source"); + compileArgs.add(config.targetVersion); + compileArgs.add("-target"); + compileArgs.add(config.targetVersion); + compileArgs.add("-Xlint:-options"); + } + + compileArgs.add("-d"); + compileArgs.add(classesDir.toString()); + compileArgs.add(javaFile.toString()); + + Files.walk(javaApiSrc) + .filter(p -> p.toString().endsWith(".java")) + .forEach(p -> compileArgs.add(p.toString())); + + int compileResult = CompilerHelper.compile(config.jdkHome, compileArgs); + assertEquals(0, compileResult, "FileStreamApp.java compilation failed with " + config); + + Path outputDir = Files.createTempDirectory("file-stream-output"); + CleanTargetIntegrationTest.runTranslator(classesDir, outputDir, "FileStreamApp"); + + Path distDir = outputDir.resolve("dist"); + Path cmakeLists = distDir.resolve("CMakeLists.txt"); + assertTrue(Files.exists(cmakeLists), "Translator should emit a CMake project"); + + Path srcRoot = distDir.resolve("FileStreamApp-src"); + CleanTargetIntegrationTest.patchCn1Globals(srcRoot); + + assertTrue(Files.exists(srcRoot.resolve("java_io_FileStreams.m")), "java_io_FileStreams.m should exist"); + + replaceLibraryWithExecutableTarget(cmakeLists); + + Path buildDir = distDir.resolve("build"); + Files.createDirectories(buildDir); + + CleanTargetIntegrationTest.runCommand(Arrays.asList( + "cmake", + "-S", distDir.toString(), + "-B", buildDir.toString(), + "-DCMAKE_C_COMPILER=clang", + "-DCMAKE_OBJC_COMPILER=clang" + ), distDir); + + CleanTargetIntegrationTest.runCommand(Arrays.asList("cmake", "--build", buildDir.toString()), distDir); + + Path executable = buildDir.resolve("FileStreamApp"); + CleanTargetIntegrationTest.runCommand(Arrays.asList(executable.toString()), buildDir); + } + + private String fileStreamAppSource() { + return "import java.io.*;\n" + + "public class FileStreamApp {\n" + + " public static void main(String[] args) {\n" + + " try {\n" + + " File file = new File(\"stream_data.bin\");\n" + + " if (file.exists()) { file.delete(); }\n" + + " DataOutputStream dos = new DataOutputStream(new FileOutputStream(file));\n" + + " dos.writeInt(0x12345678);\n" + + " dos.writeUTF(\"hello\");\n" + + " dos.flush();\n" + + " dos.close();\n" + + " DataInputStream dis = new DataInputStream(new FileInputStream(file));\n" + + " int iv = dis.readInt();\n" + + " String sv = dis.readUTF();\n" + + " dis.close();\n" + + " if (iv != 0x12345678 || !\"hello\".equals(sv)) System.exit(1);\n" + + " FileWriter writer = new FileWriter(file, true);\n" + + " writer.write('!');\n" + + " writer.close();\n" + + " FileInputStream check = new FileInputStream(file);\n" + + " if (check.available() <= 0) System.exit(1);\n" + + " byte[] all = new byte[32];\n" + + " int count = check.read(all);\n" + + " check.close();\n" + + " if (count <= 0 || all[count-1] != '!') System.exit(1);\n" + + " File parent = file.getParentFile();\n" + + " if (parent != null) {\n" + + " File[] filtered = parent.listFiles(new FilenameFilter() {\n" + + " public boolean accept(File dir, String name) { return \"stream_data.bin\".equals(name); }\n" + + " });\n" + + " if (filtered == null || filtered.length == 0) System.exit(1);\n" + + " }\n" + + " } catch (Exception e) {\n" + + " System.exit(1);\n" + + " }\n" + + " }\n" + + "}\n"; + } + + private void replaceLibraryWithExecutableTarget(Path cmakeLists) throws IOException { + String content = new String(Files.readAllBytes(cmakeLists), StandardCharsets.UTF_8); + String replacement = content.replace( + "add_library(${PROJECT_NAME} ${TRANSLATOR_SOURCES} ${TRANSLATOR_HEADERS})", + "add_executable(${PROJECT_NAME} ${TRANSLATOR_SOURCES} ${TRANSLATOR_HEADERS})\ntarget_link_libraries(${PROJECT_NAME} m)" + ); + Files.write(cmakeLists, replacement.getBytes(StandardCharsets.UTF_8)); + } +} From 9661e4db280c35014a0d9f3ad71a7311c837aefa Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Thu, 25 Dec 2025 14:14:51 +0200 Subject: [PATCH 02/40] Use UTFDataFormatException for malformed DataInputStream UTF --- vm/JavaAPI/src/java/io/DataInputStream.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/vm/JavaAPI/src/java/io/DataInputStream.java b/vm/JavaAPI/src/java/io/DataInputStream.java index d6fd0973ac..398e472e91 100644 --- a/vm/JavaAPI/src/java/io/DataInputStream.java +++ b/vm/JavaAPI/src/java/io/DataInputStream.java @@ -200,25 +200,25 @@ private static String decode(byte[] in, char[] out, int offset, int utfSize) thr s++; } else if (((a = out[s]) & 0xe0) == 0xc0) { if (count >= utfSize) { - throw new RuntimeException("bad second byte at " + count); + throw new UTFDataFormatException("bad second byte"); } int b = in[offset + count++]; if ((b & 0xC0) != 0x80) { - throw new RuntimeException("bad second byte at " + (count - 1)); + throw new UTFDataFormatException("bad second byte"); } out[s++] = (char) (((a & 0x1F) << 6) | (b & 0x3F)); } else if ((a & 0xf0) == 0xe0) { if (count + 1 >= utfSize) { - throw new RuntimeException("bad third byte at " + (count + 1)); + throw new UTFDataFormatException("bad third byte"); } int b = in[offset + count++]; int c = in[offset + count++]; if (((b & 0xC0) != 0x80) || ((c & 0xC0) != 0x80)) { - throw new RuntimeException("bad second or third byte at " + (count - 2)); + throw new UTFDataFormatException("bad second or third byte"); } out[s++] = (char) (((a & 0x0F) << 12) | ((b & 0x3F) << 6) | (c & 0x3F)); } else { - throw new RuntimeException("bad byte at " + (count - 1)); + throw new UTFDataFormatException("bad byte"); } } return new String(out, 0, s); From 0ee75dc207d0e391114927065debfbe3eb27dfce Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Thu, 25 Dec 2025 18:51:31 +0200 Subject: [PATCH 03/40] Simplify DataInputStream UTF exceptions --- vm/JavaAPI/src/java/io/DataInputStream.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/vm/JavaAPI/src/java/io/DataInputStream.java b/vm/JavaAPI/src/java/io/DataInputStream.java index 398e472e91..0b2d300927 100644 --- a/vm/JavaAPI/src/java/io/DataInputStream.java +++ b/vm/JavaAPI/src/java/io/DataInputStream.java @@ -200,25 +200,25 @@ private static String decode(byte[] in, char[] out, int offset, int utfSize) thr s++; } else if (((a = out[s]) & 0xe0) == 0xc0) { if (count >= utfSize) { - throw new UTFDataFormatException("bad second byte"); + throw new UTFDataFormatException(); } int b = in[offset + count++]; if ((b & 0xC0) != 0x80) { - throw new UTFDataFormatException("bad second byte"); + throw new UTFDataFormatException(); } out[s++] = (char) (((a & 0x1F) << 6) | (b & 0x3F)); } else if ((a & 0xf0) == 0xe0) { if (count + 1 >= utfSize) { - throw new UTFDataFormatException("bad third byte"); + throw new UTFDataFormatException(); } int b = in[offset + count++]; int c = in[offset + count++]; if (((b & 0xC0) != 0x80) || ((c & 0xC0) != 0x80)) { - throw new UTFDataFormatException("bad second or third byte"); + throw new UTFDataFormatException(); } out[s++] = (char) (((a & 0x0F) << 12) | ((b & 0x3F) << 6) | (c & 0x3F)); } else { - throw new UTFDataFormatException("bad byte"); + throw new UTFDataFormatException(); } } return new String(out, 0, s); From 61c6112bae9a203a93008855db553b11ea807cac Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Thu, 25 Dec 2025 19:57:35 +0200 Subject: [PATCH 04/40] Fix native runtime linking for file streams --- vm/ByteCodeTranslator/src/cn1_globals.h | 2 + vm/ByteCodeTranslator/src/cn1_globals.m | 5 + .../tools/translator/ByteCodeTranslator.java | 2 + .../src/java_io_FileStreams.m | 28 +++--- vm/ByteCodeTranslator/src/nativeMethods.m | 4 +- .../FileStreamsIntegrationTest.java | 3 +- .../translator/HeavyLoadBenchmarkTest.java | 96 ++++++++++++++++++- 7 files changed, 120 insertions(+), 20 deletions(-) diff --git a/vm/ByteCodeTranslator/src/cn1_globals.h b/vm/ByteCodeTranslator/src/cn1_globals.h index eaca5f3cba..7daf65fd59 100644 --- a/vm/ByteCodeTranslator/src/cn1_globals.h +++ b/vm/ByteCodeTranslator/src/cn1_globals.h @@ -3,6 +3,8 @@ #include #include + +#define CN1_GLOBALS_IMPLEMENTED 1 #include "cn1_class_method_index.h" #include #include diff --git a/vm/ByteCodeTranslator/src/cn1_globals.m b/vm/ByteCodeTranslator/src/cn1_globals.m index 2c1010599f..b41f111107 100644 --- a/vm/ByteCodeTranslator/src/cn1_globals.m +++ b/vm/ByteCodeTranslator/src/cn1_globals.m @@ -1,5 +1,6 @@ #include "cn1_globals.h" #include +#include #include "java_lang_Class.h" #include "java_lang_Object.h" #include "java_lang_Boolean.h" @@ -14,6 +15,10 @@ #include "java_lang_Float.h" #include "java_lang_Runnable.h" #include "java_lang_System.h" + +JAVA_BOOLEAN lowMemoryMode = JAVA_FALSE; +int mallocWhileSuspended = 0; +BOOL isAppSuspended = NO; #include "java_lang_ArrayIndexOutOfBoundsException.h" #if defined(__APPLE__) && defined(__OBJC__) #import diff --git a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/ByteCodeTranslator.java b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/ByteCodeTranslator.java index 393b43ab5c..45467c8b83 100644 --- a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/ByteCodeTranslator.java +++ b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/ByteCodeTranslator.java @@ -244,6 +244,8 @@ private static void handleCleanOutput(ByteCodeTranslator b, File[] sources, File copy(ByteCodeTranslator.class.getResourceAsStream("/xmlvm.h"), new FileOutputStream(xmlvm)); File nativeMethods = new File(srcRoot, "nativeMethods.m"); copy(ByteCodeTranslator.class.getResourceAsStream("/nativeMethods.m"), new FileOutputStream(nativeMethods)); + File cn1GlobalsM = new File(srcRoot, "cn1_globals.m"); + copy(ByteCodeTranslator.class.getResourceAsStream("/cn1_globals.m"), new FileOutputStream(cn1GlobalsM)); File javaIoFileM = new File(srcRoot, "java_io_File.m"); copy(ByteCodeTranslator.class.getResourceAsStream("/java_io_File.m"), new FileOutputStream(javaIoFileM)); File javaIoFileStreamsM = new File(srcRoot, "java_io_FileStreams.m"); diff --git a/vm/ByteCodeTranslator/src/java_io_FileStreams.m b/vm/ByteCodeTranslator/src/java_io_FileStreams.m index 50194ae649..e80744c12a 100644 --- a/vm/ByteCodeTranslator/src/java_io_FileStreams.m +++ b/vm/ByteCodeTranslator/src/java_io_FileStreams.m @@ -21,7 +21,7 @@ static JAVA_LONG retainHandle(NSFileHandle* handle) { return (JAVA_LONG)(uintptr_t)handle; } -JAVA_LONG java_io_FileInputStream_openImpl___java_lang_String_R_long(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT __cn1ThisObject, JAVA_OBJECT path) { +JAVA_LONG java_io_FileInputStream_openImpl___java_lang_String_R_long(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT path) { if (path == JAVA_NULL) { return 0; } @@ -33,7 +33,7 @@ JAVA_LONG java_io_FileInputStream_openImpl___java_lang_String_R_long(CODENAME_ON return result; } -JAVA_INT java_io_FileInputStream_readImpl___long_byte_1ARRAY_int_int_R_int(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT __cn1ThisObject, JAVA_LONG handle, JAVA_OBJECT buffer, JAVA_INT off, JAVA_INT len) { +JAVA_INT java_io_FileInputStream_readImpl___long_byte_1ARRAY_int_int_R_int(CODENAME_ONE_THREAD_STATE, JAVA_LONG handle, JAVA_OBJECT buffer, JAVA_INT off, JAVA_INT len) { NSFileHandle* h = toHandle(handle); if (h == nil) { return -1; @@ -48,7 +48,7 @@ JAVA_INT java_io_FileInputStream_readImpl___long_byte_1ARRAY_int_int_R_int(CODEN return count == 0 ? -1 : (JAVA_INT)count; } -JAVA_LONG java_io_FileInputStream_skipImpl___long_long_R_long(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT __cn1ThisObject, JAVA_LONG handle, JAVA_LONG n) { +JAVA_LONG java_io_FileInputStream_skipImpl___long_long_R_long(CODENAME_ONE_THREAD_STATE, JAVA_LONG handle, JAVA_LONG n) { NSFileHandle* h = toHandle(handle); if (h == nil) { return 0; @@ -63,7 +63,7 @@ JAVA_LONG java_io_FileInputStream_skipImpl___long_long_R_long(CODENAME_ONE_THREA return (JAVA_LONG)(updated - current); } -JAVA_INT java_io_FileInputStream_availableImpl___long_R_int(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT __cn1ThisObject, JAVA_LONG handle) { +JAVA_INT java_io_FileInputStream_availableImpl___long_R_int(CODENAME_ONE_THREAD_STATE, JAVA_LONG handle) { NSFileHandle* h = toHandle(handle); if (h == nil) { return 0; @@ -83,7 +83,7 @@ JAVA_INT java_io_FileInputStream_availableImpl___long_R_int(CODENAME_ONE_THREAD_ return (JAVA_INT)diff; } -JAVA_VOID java_io_FileInputStream_closeImpl___long(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT __cn1ThisObject, JAVA_LONG handle) { +JAVA_VOID java_io_FileInputStream_closeImpl___long(CODENAME_ONE_THREAD_STATE, JAVA_LONG handle) { NSFileHandle* h = toHandle(handle); if (h == nil) { return; @@ -161,7 +161,7 @@ static int toFd(JAVA_LONG handle) { return (int)handle; } -JAVA_LONG java_io_FileInputStream_openImpl___java_lang_String_R_long(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT __cn1ThisObject, JAVA_OBJECT path) { +JAVA_LONG java_io_FileInputStream_openImpl___java_lang_String_R_long(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT path) { if (path == JAVA_NULL) { return 0; } @@ -173,7 +173,7 @@ JAVA_LONG java_io_FileInputStream_openImpl___java_lang_String_R_long(CODENAME_ON return (JAVA_LONG)fd; } -JAVA_INT java_io_FileInputStream_readImpl___long_byte_1ARRAY_int_int_R_int(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT __cn1ThisObject, JAVA_LONG handle, JAVA_OBJECT buffer, JAVA_INT off, JAVA_INT len) { +JAVA_INT java_io_FileInputStream_readImpl___long_byte_1ARRAY_int_int_R_int(CODENAME_ONE_THREAD_STATE, JAVA_LONG handle, JAVA_OBJECT buffer, JAVA_INT off, JAVA_INT len) { int fd = toFd(handle); if (fd < 0 || buffer == JAVA_NULL) { return -1; @@ -185,7 +185,7 @@ JAVA_INT java_io_FileInputStream_readImpl___long_byte_1ARRAY_int_int_R_int(CODEN return (JAVA_INT)count; } -JAVA_LONG java_io_FileInputStream_skipImpl___long_long_R_long(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT __cn1ThisObject, JAVA_LONG handle, JAVA_LONG n) { +JAVA_LONG java_io_FileInputStream_skipImpl___long_long_R_long(CODENAME_ONE_THREAD_STATE, JAVA_LONG handle, JAVA_LONG n) { int fd = toFd(handle); if (fd < 0 || n <= 0) { return 0; @@ -201,7 +201,7 @@ JAVA_LONG java_io_FileInputStream_skipImpl___long_long_R_long(CODENAME_ONE_THREA return (JAVA_LONG)(target - current); } -JAVA_INT java_io_FileInputStream_availableImpl___long_R_int(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT __cn1ThisObject, JAVA_LONG handle) { +JAVA_INT java_io_FileInputStream_availableImpl___long_R_int(CODENAME_ONE_THREAD_STATE, JAVA_LONG handle) { int fd = toFd(handle); if (fd < 0) { return 0; @@ -225,14 +225,14 @@ JAVA_INT java_io_FileInputStream_availableImpl___long_R_int(CODENAME_ONE_THREAD_ return (JAVA_INT)diff; } -JAVA_VOID java_io_FileInputStream_closeImpl___long(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT __cn1ThisObject, JAVA_LONG handle) { +JAVA_VOID java_io_FileInputStream_closeImpl___long(CODENAME_ONE_THREAD_STATE, JAVA_LONG handle) { int fd = toFd(handle); if (fd >= 0) { close(fd); } } -JAVA_LONG java_io_FileOutputStream_openImpl___java_lang_String_boolean_R_long(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT __cn1ThisObject, JAVA_OBJECT path, JAVA_BOOLEAN append) { +JAVA_LONG java_io_FileOutputStream_openImpl___java_lang_String_boolean_R_long(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT path, JAVA_BOOLEAN append) { if (path == JAVA_NULL) { return 0; } @@ -250,7 +250,7 @@ JAVA_LONG java_io_FileOutputStream_openImpl___java_lang_String_boolean_R_long(CO return (JAVA_LONG)fd; } -JAVA_VOID java_io_FileOutputStream_writeImpl___long_byte_1ARRAY_int_int(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT __cn1ThisObject, JAVA_LONG handle, JAVA_OBJECT buffer, JAVA_INT off, JAVA_INT len) { +JAVA_VOID java_io_FileOutputStream_writeImpl___long_byte_1ARRAY_int_int(CODENAME_ONE_THREAD_STATE, JAVA_LONG handle, JAVA_OBJECT buffer, JAVA_INT off, JAVA_INT len) { int fd = toFd(handle); if (fd < 0 || buffer == JAVA_NULL || len <= 0) { return; @@ -259,14 +259,14 @@ JAVA_VOID java_io_FileOutputStream_writeImpl___long_byte_1ARRAY_int_int(CODENAME (void)written; } -JAVA_VOID java_io_FileOutputStream_flushImpl___long(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT __cn1ThisObject, JAVA_LONG handle) { +JAVA_VOID java_io_FileOutputStream_flushImpl___long(CODENAME_ONE_THREAD_STATE, JAVA_LONG handle) { int fd = toFd(handle); if (fd >= 0) { fsync(fd); } } -JAVA_VOID java_io_FileOutputStream_closeImpl___long(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT __cn1ThisObject, JAVA_LONG handle) { +JAVA_VOID java_io_FileOutputStream_closeImpl___long(CODENAME_ONE_THREAD_STATE, JAVA_LONG handle) { int fd = toFd(handle); if (fd >= 0) { close(fd); diff --git a/vm/ByteCodeTranslator/src/nativeMethods.m b/vm/ByteCodeTranslator/src/nativeMethods.m index 6a6eb6abbc..695e7fb24e 100644 --- a/vm/ByteCodeTranslator/src/nativeMethods.m +++ b/vm/ByteCodeTranslator/src/nativeMethods.m @@ -1856,6 +1856,7 @@ JAVA_OBJECT java_lang_String_format___java_lang_String_java_lang_Object_1ARRAY_R // Additional Stubs for Linking +#if !defined(CN1_GLOBALS_IMPLEMENTED) struct clazz class_array1__JAVA_BOOLEAN = {0}; struct clazz class_array1__JAVA_CHAR = {0}; struct clazz class_array1__JAVA_BYTE = {0}; @@ -1998,4 +1999,5 @@ void initConstantPool() { pthread_key_t recursionKey; int currentGcMarkValue = 0; -#endif +#endif /* !CN1_GLOBALS_IMPLEMENTED */ +#endif /* __APPLE__ */ diff --git a/vm/tests/src/test/java/com/codename1/tools/translator/FileStreamsIntegrationTest.java b/vm/tests/src/test/java/com/codename1/tools/translator/FileStreamsIntegrationTest.java index 51e74008e0..16e04789db 100644 --- a/vm/tests/src/test/java/com/codename1/tools/translator/FileStreamsIntegrationTest.java +++ b/vm/tests/src/test/java/com/codename1/tools/translator/FileStreamsIntegrationTest.java @@ -131,6 +131,7 @@ private String fileStreamAppSource() { " if (filtered == null || filtered.length == 0) System.exit(1);\n" + " }\n" + " } catch (Exception e) {\n" + + " e.printStackTrace();\n" + " System.exit(1);\n" + " }\n" + " }\n" + @@ -141,7 +142,7 @@ private void replaceLibraryWithExecutableTarget(Path cmakeLists) throws IOExcept String content = new String(Files.readAllBytes(cmakeLists), StandardCharsets.UTF_8); String replacement = content.replace( "add_library(${PROJECT_NAME} ${TRANSLATOR_SOURCES} ${TRANSLATOR_HEADERS})", - "add_executable(${PROJECT_NAME} ${TRANSLATOR_SOURCES} ${TRANSLATOR_HEADERS})\ntarget_link_libraries(${PROJECT_NAME} m)" + "add_executable(${PROJECT_NAME} ${TRANSLATOR_SOURCES} ${TRANSLATOR_HEADERS})\ntarget_link_libraries(${PROJECT_NAME} m objc)" ); Files.write(cmakeLists, replacement.getBytes(StandardCharsets.UTF_8)); } diff --git a/vm/tests/src/test/java/com/codename1/tools/translator/HeavyLoadBenchmarkTest.java b/vm/tests/src/test/java/com/codename1/tools/translator/HeavyLoadBenchmarkTest.java index 3c9ecc2c7c..2196acfefa 100644 --- a/vm/tests/src/test/java/com/codename1/tools/translator/HeavyLoadBenchmarkTest.java +++ b/vm/tests/src/test/java/com/codename1/tools/translator/HeavyLoadBenchmarkTest.java @@ -11,6 +11,7 @@ import javax.tools.ToolProvider; import java.io.File; import java.io.IOException; +import java.io.UncheckedIOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.URL; @@ -23,6 +24,8 @@ import java.util.stream.Collectors; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; +import java.util.jar.JarEntry; +import java.util.jar.JarOutputStream; @Tag("benchmark") public class HeavyLoadBenchmarkTest { @@ -33,10 +36,7 @@ public class HeavyLoadBenchmarkTest { @Test public void benchmarkJavaAPITranslation() throws Exception { // Locate JavaAPI.jar - Path javaApiJar = Paths.get("..", "JavaAPI", "dist", "JavaAPI.jar").normalize().toAbsolutePath(); - if (!Files.exists(javaApiJar)) { - javaApiJar = Paths.get("vm", "JavaAPI", "dist", "JavaAPI.jar").normalize().toAbsolutePath(); - } + Path javaApiJar = resolveJavaApiJar(); // Locate CodenameOne Core jar Path coreJar = findDependencyJar("codenameone-core"); @@ -299,6 +299,94 @@ private Path findPath(String... parts) { return null; } + private Path resolveJavaApiJar() throws IOException { + List candidates = new ArrayList<>(); + candidates.add(Paths.get("..", "JavaAPI", "dist", "JavaAPI.jar").normalize().toAbsolutePath()); + candidates.add(Paths.get("vm", "JavaAPI", "dist", "JavaAPI.jar").normalize().toAbsolutePath()); + candidates.add(firstJarInTarget(Paths.get("..", "JavaAPI", "target"))); + candidates.add(firstJarInTarget(Paths.get("vm", "JavaAPI", "target"))); + + for (Path candidate : candidates) { + if (candidate != null && Files.exists(candidate)) { + return candidate; + } + } + Path srcRoot = findPath("JavaAPI", "src"); + if (srcRoot == null) { + srcRoot = findPath("vm", "JavaAPI", "src"); + } + Path built = buildJavaApiJar(srcRoot); + if (built != null) { + return built; + } + return candidates.get(0); + } + + private Path firstJarInTarget(Path dir) throws IOException { + if (!Files.isDirectory(dir)) { + return null; + } + try (java.util.stream.Stream stream = Files.list(dir)) { + return stream.filter(p -> p.toString().endsWith(".jar")).findFirst().orElse(null); + } + } + + private Path buildJavaApiJar(Path srcRoot) throws IOException { + if (srcRoot == null) { + return null; + } + JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); + if (compiler == null) { + return null; + } + + List sources; + try (java.util.stream.Stream stream = Files.walk(srcRoot)) { + sources = stream.filter(p -> p.toString().endsWith(".java")) + .map(Path::toString) + .collect(Collectors.toList()); + } + if (sources.isEmpty()) { + return null; + } + + Path classesDir = Files.createTempDirectory("javaapi-classes"); + List args = new ArrayList<>(); + args.add("--release"); + args.add("11"); + args.add("--patch-module"); + args.add("java.base=" + srcRoot.toString()); + args.add("-Xlint:-options"); + args.add("-Xlint:-module"); + args.add("-d"); + args.add(classesDir.toString()); + args.addAll(sources); + + int result = compiler.run(null, null, null, args.toArray(new String[0])); + if (result != 0) { + return null; + } + + Path jar = Files.createTempFile("JavaAPI-", ".jar"); + try (JarOutputStream jos = new JarOutputStream(Files.newOutputStream(jar))) { + try (java.util.stream.Stream stream = Files.walk(classesDir)) { + stream.filter(Files::isRegularFile).forEach(p -> { + JarEntry entry = new JarEntry(classesDir.relativize(p).toString().replace(File.separatorChar, '/')); + try { + jos.putNextEntry(entry); + Files.copy(p, jos); + jos.closeEntry(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + }); + } + } catch (UncheckedIOException e) { + throw e.getCause(); + } + return jar.toAbsolutePath(); + } + private Path findDependencyJar(String namePart) { Path depsDir = Paths.get("target", "benchmark-dependencies"); if (!Files.exists(depsDir)) return null; From 01e564909aae044c4558a2c5d62b3e92bcb0fc54 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Fri, 26 Dec 2025 06:59:52 +0200 Subject: [PATCH 05/40] Avoid Objective-C dependency for clean target outputs --- .../codename1/tools/translator/ByteCodeTranslator.java | 8 ++++---- .../tools/translator/CleanTargetIntegrationTest.java | 3 +-- .../tools/translator/FileStreamsIntegrationTest.java | 7 +++---- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/ByteCodeTranslator.java b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/ByteCodeTranslator.java index 45467c8b83..9f5115b68a 100644 --- a/vm/ByteCodeTranslator/src/com/codename1/tools/translator/ByteCodeTranslator.java +++ b/vm/ByteCodeTranslator/src/com/codename1/tools/translator/ByteCodeTranslator.java @@ -242,13 +242,13 @@ private static void handleCleanOutput(ByteCodeTranslator b, File[] sources, File } File xmlvm = new File(srcRoot, "xmlvm.h"); copy(ByteCodeTranslator.class.getResourceAsStream("/xmlvm.h"), new FileOutputStream(xmlvm)); - File nativeMethods = new File(srcRoot, "nativeMethods.m"); + File nativeMethods = new File(srcRoot, "nativeMethods.c"); copy(ByteCodeTranslator.class.getResourceAsStream("/nativeMethods.m"), new FileOutputStream(nativeMethods)); - File cn1GlobalsM = new File(srcRoot, "cn1_globals.m"); + File cn1GlobalsM = new File(srcRoot, "cn1_globals.c"); copy(ByteCodeTranslator.class.getResourceAsStream("/cn1_globals.m"), new FileOutputStream(cn1GlobalsM)); - File javaIoFileM = new File(srcRoot, "java_io_File.m"); + File javaIoFileM = new File(srcRoot, "java_io_File.c"); copy(ByteCodeTranslator.class.getResourceAsStream("/java_io_File.m"), new FileOutputStream(javaIoFileM)); - File javaIoFileStreamsM = new File(srcRoot, "java_io_FileStreams.m"); + File javaIoFileStreamsM = new File(srcRoot, "java_io_FileStreams.c"); copy(ByteCodeTranslator.class.getResourceAsStream("/java_io_FileStreams.m"), new FileOutputStream(javaIoFileStreamsM)); Parser.writeOutput(srcRoot); diff --git a/vm/tests/src/test/java/com/codename1/tools/translator/CleanTargetIntegrationTest.java b/vm/tests/src/test/java/com/codename1/tools/translator/CleanTargetIntegrationTest.java index 78f148ea92..bfd2552b79 100644 --- a/vm/tests/src/test/java/com/codename1/tools/translator/CleanTargetIntegrationTest.java +++ b/vm/tests/src/test/java/com/codename1/tools/translator/CleanTargetIntegrationTest.java @@ -108,8 +108,7 @@ void generatesRunnableHelloWorldUsingCleanTarget(CompilerHelper.CompilerConfig c "cmake", "-S", distDir.toString(), "-B", buildDir.toString(), - "-DCMAKE_C_COMPILER=clang", - "-DCMAKE_OBJC_COMPILER=clang" + "-DCMAKE_C_COMPILER=clang" ), distDir); runCommand(Arrays.asList("cmake", "--build", buildDir.toString()), distDir); diff --git a/vm/tests/src/test/java/com/codename1/tools/translator/FileStreamsIntegrationTest.java b/vm/tests/src/test/java/com/codename1/tools/translator/FileStreamsIntegrationTest.java index 16e04789db..97b1d729a1 100644 --- a/vm/tests/src/test/java/com/codename1/tools/translator/FileStreamsIntegrationTest.java +++ b/vm/tests/src/test/java/com/codename1/tools/translator/FileStreamsIntegrationTest.java @@ -76,7 +76,7 @@ public void testFileStreams(CompilerHelper.CompilerConfig config) throws Excepti Path srcRoot = distDir.resolve("FileStreamApp-src"); CleanTargetIntegrationTest.patchCn1Globals(srcRoot); - assertTrue(Files.exists(srcRoot.resolve("java_io_FileStreams.m")), "java_io_FileStreams.m should exist"); + assertTrue(Files.exists(srcRoot.resolve("java_io_FileStreams.c")), "java_io_FileStreams.c should exist"); replaceLibraryWithExecutableTarget(cmakeLists); @@ -87,8 +87,7 @@ public void testFileStreams(CompilerHelper.CompilerConfig config) throws Excepti "cmake", "-S", distDir.toString(), "-B", buildDir.toString(), - "-DCMAKE_C_COMPILER=clang", - "-DCMAKE_OBJC_COMPILER=clang" + "-DCMAKE_C_COMPILER=clang" ), distDir); CleanTargetIntegrationTest.runCommand(Arrays.asList("cmake", "--build", buildDir.toString()), distDir); @@ -142,7 +141,7 @@ private void replaceLibraryWithExecutableTarget(Path cmakeLists) throws IOExcept String content = new String(Files.readAllBytes(cmakeLists), StandardCharsets.UTF_8); String replacement = content.replace( "add_library(${PROJECT_NAME} ${TRANSLATOR_SOURCES} ${TRANSLATOR_HEADERS})", - "add_executable(${PROJECT_NAME} ${TRANSLATOR_SOURCES} ${TRANSLATOR_HEADERS})\ntarget_link_libraries(${PROJECT_NAME} m objc)" + "add_executable(${PROJECT_NAME} ${TRANSLATOR_SOURCES} ${TRANSLATOR_HEADERS})\ntarget_link_libraries(${PROJECT_NAME} m)" ); Files.write(cmakeLists, replacement.getBytes(StandardCharsets.UTF_8)); } From 414a3c9eacc523ffec87d2985eaa5799658fe4c5 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Fri, 26 Dec 2025 13:03:04 +0200 Subject: [PATCH 06/40] Add minimal wrapper classes to StampedLock integration mock API --- .../StampedLockIntegrationTest.java | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/vm/tests/src/test/java/com/codename1/tools/translator/StampedLockIntegrationTest.java b/vm/tests/src/test/java/com/codename1/tools/translator/StampedLockIntegrationTest.java index 396743994d..bd73f99383 100644 --- a/vm/tests/src/test/java/com/codename1/tools/translator/StampedLockIntegrationTest.java +++ b/vm/tests/src/test/java/com/codename1/tools/translator/StampedLockIntegrationTest.java @@ -158,6 +158,56 @@ private void writeMockJavaClasses(Path sourceDir) throws Exception { " public String toString() { return \"\"; }\n" + "}\n").getBytes(StandardCharsets.UTF_8)); + // Primitive wrappers + Files.write(lang.resolve("Boolean.java"), ("package java.lang;\n" + + "public final class Boolean {\n" + + " private boolean value;\n" + + " public Boolean(boolean value) { this.value = value; }\n" + + " public boolean booleanValue() { return value; }\n" + + "}\n").getBytes(StandardCharsets.UTF_8)); + Files.write(lang.resolve("Byte.java"), ("package java.lang;\n" + + "public final class Byte {\n" + + " private byte value;\n" + + " public Byte(byte value) { this.value = value; }\n" + + " public byte byteValue() { return value; }\n" + + "}\n").getBytes(StandardCharsets.UTF_8)); + Files.write(lang.resolve("Short.java"), ("package java.lang;\n" + + "public final class Short {\n" + + " private short value;\n" + + " public Short(short value) { this.value = value; }\n" + + " public short shortValue() { return value; }\n" + + "}\n").getBytes(StandardCharsets.UTF_8)); + Files.write(lang.resolve("Character.java"), ("package java.lang;\n" + + "public final class Character {\n" + + " private char value;\n" + + " public Character(char value) { this.value = value; }\n" + + " public char charValue() { return value; }\n" + + "}\n").getBytes(StandardCharsets.UTF_8)); + Files.write(lang.resolve("Integer.java"), ("package java.lang;\n" + + "public final class Integer {\n" + + " private int value;\n" + + " public Integer(int value) { this.value = value; }\n" + + " public int intValue() { return value; }\n" + + "}\n").getBytes(StandardCharsets.UTF_8)); + Files.write(lang.resolve("Long.java"), ("package java.lang;\n" + + "public final class Long {\n" + + " private long value;\n" + + " public Long(long value) { this.value = value; }\n" + + " public long longValue() { return value; }\n" + + "}\n").getBytes(StandardCharsets.UTF_8)); + Files.write(lang.resolve("Float.java"), ("package java.lang;\n" + + "public final class Float {\n" + + " private float value;\n" + + " public Float(float value) { this.value = value; }\n" + + " public float floatValue() { return value; }\n" + + "}\n").getBytes(StandardCharsets.UTF_8)); + Files.write(lang.resolve("Double.java"), ("package java.lang;\n" + + "public final class Double {\n" + + " private double value;\n" + + " public Double(double value) { this.value = value; }\n" + + " public double doubleValue() { return value; }\n" + + "}\n").getBytes(StandardCharsets.UTF_8)); + // java.lang.Class Files.write(lang.resolve("Class.java"), ("package java.lang;\n" + "public final class Class {}\n").getBytes(StandardCharsets.UTF_8)); @@ -193,6 +243,7 @@ private void writeMockJavaClasses(Path sourceDir) throws Exception { Files.write(lang.resolve("Throwable.java"), "package java.lang; public class Throwable { public Throwable() {} public Throwable(String s) {} }".getBytes(StandardCharsets.UTF_8)); Files.write(lang.resolve("Exception.java"), "package java.lang; public class Exception extends Throwable { public Exception() {} public Exception(String s) {} }".getBytes(StandardCharsets.UTF_8)); Files.write(lang.resolve("RuntimeException.java"), "package java.lang; public class RuntimeException extends Exception { public RuntimeException() {} public RuntimeException(String s) {} }".getBytes(StandardCharsets.UTF_8)); + Files.write(lang.resolve("ArrayIndexOutOfBoundsException.java"), "package java.lang; public class ArrayIndexOutOfBoundsException extends RuntimeException { public ArrayIndexOutOfBoundsException() {} }".getBytes(StandardCharsets.UTF_8)); Files.write(lang.resolve("InterruptedException.java"), "package java.lang; public class InterruptedException extends Exception { public InterruptedException() {} }".getBytes(StandardCharsets.UTF_8)); Files.write(lang.resolve("NullPointerException.java"), "package java.lang; public class NullPointerException extends RuntimeException { public NullPointerException() {} }".getBytes(StandardCharsets.UTF_8)); Files.write(lang.resolve("IllegalMonitorStateException.java"), "package java.lang; public class IllegalMonitorStateException extends RuntimeException { public IllegalMonitorStateException() {} }".getBytes(StandardCharsets.UTF_8)); From b7c74b8893f54957ccaa053624be4466a7e94bce Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Fri, 26 Dec 2025 13:24:37 +0200 Subject: [PATCH 07/40] Fix clean target runtime stubs for StampedLock --- vm/ByteCodeTranslator/src/cn1_globals.m | 18 ++++++++++-------- .../translator/CleanTargetIntegrationTest.java | 2 ++ .../translator/StampedLockIntegrationTest.java | 6 +++++- 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/vm/ByteCodeTranslator/src/cn1_globals.m b/vm/ByteCodeTranslator/src/cn1_globals.m index b41f111107..75e12962af 100644 --- a/vm/ByteCodeTranslator/src/cn1_globals.m +++ b/vm/ByteCodeTranslator/src/cn1_globals.m @@ -23,9 +23,11 @@ #if defined(__APPLE__) && defined(__OBJC__) #import #import +#define CN1_LOG(fmt, ...) NSLog(@fmt, ##__VA_ARGS__) #else #include -#define NSLog(...) printf(__VA_ARGS__); printf("\n") +#include +#define CN1_LOG(fmt, ...) printf(fmt "\n", ##__VA_ARGS__) #endif // The amount of memory allocated between GC cycle checks (generally 30 seconds) @@ -591,8 +593,8 @@ void codenameOneGCMark() { { long later = time(0)-now; if(later>10000) { - NSLog(@"GC trapped for %d seconds waiting for thread %d in slot %d (%d)", - (int)(later/1000),(int)t->threadId,iter,t->threadKilled); + CN1_LOG("GC trapped for %d seconds waiting for thread %d in slot %d (%d)", + (int)(later/1000),(int)t->threadId,iter,t->threadKilled); } } } @@ -627,7 +629,7 @@ void codenameOneGCMark() { } if (CN1_EDT_THREAD_ID == t->threadId && agressiveAllocator) { long freeMemory = get_free_memory(); - NSLog(@"[GC] Blocking EDT as aggressive allocator, free memory=%lld", freeMemory); + CN1_LOG("[GC] Blocking EDT as aggressive allocator, free memory=%lld", freeMemory); } @@ -717,7 +719,7 @@ void printObjectsPostSweep(CODENAME_ONE_THREAD_STATE) { } } int actualTotalMemory = 0; - NSLog(@"\n\n**** There are %i - %i = %i nulls available entries out of %i objects in heap which take up %i, sweep saved %i ****", nullSpaces, nullSpacesPreSweep, nullSpaces - nullSpacesPreSweep, t, totalAllocatedHeap, preSweepRam - totalAllocatedHeap); + CN1_LOG("\n\n**** There are %i - %i = %i nulls available entries out of %i objects in heap which take up %i, sweep saved %i ****", nullSpaces, nullSpacesPreSweep, nullSpaces - nullSpacesPreSweep, t, totalAllocatedHeap, preSweepRam - totalAllocatedHeap); for(int iter = 0 ; iter < cn1_array_3_id_java_util_Vector ; iter++) { if(classTypeCount[iter] > 0) { if(classTypeCountPreSweep[iter] - classTypeCount[iter] > 0) { @@ -736,7 +738,7 @@ void printObjectsPostSweep(CODENAME_ONE_THREAD_STATE) { } } //NSLog(@"Actual ram = %i vs total mallocs = %i", actualTotalMemory, totalAllocatedHeap); - NSLog(@"**** GC cycle complete ****"); + CN1_LOG("**** GC cycle complete ****"); free(arrayOfNames); #if defined(__APPLE__) && defined(__OBJC__) @@ -774,7 +776,7 @@ void printObjectTypesInHeap(CODENAME_ONE_THREAD_STATE) { } } int actualTotalMemory = 0; - NSLog(@"There are %i null available entries out of %i objects in heap which take up %i", nullSpaces, t, totalAllocatedHeap); + CN1_LOG("There are %i null available entries out of %i objects in heap which take up %i", nullSpaces, t, totalAllocatedHeap); for(int iter = 0 ; iter < cn1_array_3_id_java_util_Vector ; iter++) { if(classTypeCount[iter] > 0) { float f = ((float)classTypeCount[iter]) / ((float)t) * 100.0f; @@ -792,7 +794,7 @@ void printObjectTypesInHeap(CODENAME_ONE_THREAD_STATE) { actualTotalMemory += sizeInHeapForType[iter]; } } - NSLog(@"Actual ram = %i vs total mallocs = %i", actualTotalMemory, totalAllocatedHeap); + CN1_LOG("Actual ram = %i vs total mallocs = %i", actualTotalMemory, totalAllocatedHeap); free(arrayOfNames); #if defined(__APPLE__) && defined(__OBJC__) diff --git a/vm/tests/src/test/java/com/codename1/tools/translator/CleanTargetIntegrationTest.java b/vm/tests/src/test/java/com/codename1/tools/translator/CleanTargetIntegrationTest.java index bfd2552b79..c2c53b703d 100644 --- a/vm/tests/src/test/java/com/codename1/tools/translator/CleanTargetIntegrationTest.java +++ b/vm/tests/src/test/java/com/codename1/tools/translator/CleanTargetIntegrationTest.java @@ -300,6 +300,8 @@ static void writeRuntimeStubs(Path srcRoot) throws IOException { "struct clazz class_array1__JAVA_BYTE = {0};\n" + "struct clazz class_array1__JAVA_SHORT = {0};\n" + "struct clazz class_array1__JAVA_LONG = {0};\n" + + "struct clazz class_array1__java_lang_String = {0};\n" + + "struct clazz class_array2__java_lang_String = {0};\n" + "\n" + "static JAVA_OBJECT allocArrayInternal(CODENAME_ONE_THREAD_STATE, int length, struct clazz* type, int primitiveSize, int dim) {\n" + " struct JavaArrayPrototype* arr = (struct JavaArrayPrototype*)calloc(1, sizeof(struct JavaArrayPrototype));\n" + diff --git a/vm/tests/src/test/java/com/codename1/tools/translator/StampedLockIntegrationTest.java b/vm/tests/src/test/java/com/codename1/tools/translator/StampedLockIntegrationTest.java index bd73f99383..5b5fbd399b 100644 --- a/vm/tests/src/test/java/com/codename1/tools/translator/StampedLockIntegrationTest.java +++ b/vm/tests/src/test/java/com/codename1/tools/translator/StampedLockIntegrationTest.java @@ -144,6 +144,7 @@ private void writeMockJavaClasses(Path sourceDir) throws Exception { " private int count;\n" + " public String(char[] v) { value = v; count=v.length; }\n" + " public static String valueOf(Object obj) { return obj == null ? \"null\" : obj.toString(); }\n" + + " public byte[] getBytes(String charset) { return new byte[0]; }\n" + "}\n").getBytes(StandardCharsets.UTF_8)); // java.lang.StringBuilder @@ -236,7 +237,10 @@ private void writeMockJavaClasses(Path sourceDir) throws Exception { // java.lang.System Files.write(lang.resolve("System.java"), ("package java.lang;\n" + "public final class System {\n" + - " public static native long currentTimeMillis();\n" + + " public static long currentTimeMillis() { return 0L; }\n" + + " public static void gc() {}\n" + + " public static void startGCThread() {}\n" + + " public static Thread gcThreadInstance;\n" + "}\n").getBytes(StandardCharsets.UTF_8)); // Exceptions From 7a458f07aeb20c4fe26f05fd158bcc4e0c1d4fe0 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Fri, 26 Dec 2025 16:17:20 +0200 Subject: [PATCH 08/40] Add string array stubs for clean runtime builds --- vm/ByteCodeTranslator/src/cn1_globals.m | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/vm/ByteCodeTranslator/src/cn1_globals.m b/vm/ByteCodeTranslator/src/cn1_globals.m index 75e12962af..dbc15dac8d 100644 --- a/vm/ByteCodeTranslator/src/cn1_globals.m +++ b/vm/ByteCodeTranslator/src/cn1_globals.m @@ -268,6 +268,11 @@ static void init_gc_thresholds() { DEBUG_GC_INIT 0, 999999, 0, 0, 0, 0, 0, 0, 0, 0, cn1_array_1_id_JAVA_DOUBLE, "double[]", JAVA_TRUE, 1, &class__java_lang_Double, JAVA_TRUE, &class__java_lang_Object, EMPTY_INTERFACES, 0, 0, 0 }; +// Minimal stubs for String array classes so clean-target builds that only +// reference primitive wrappers still have constant pool backing storage. +struct clazz class_array1__java_lang_String = {0}; +struct clazz class_array2__java_lang_String = {0}; + struct clazz class_array2__JAVA_DOUBLE = { DEBUG_GC_INIT 0, 999999, 0, 0, 0, 0, 0, 0, &gcMarkArrayObject, 0, cn1_array_2_id_JAVA_DOUBLE, "double[]", JAVA_TRUE, 2, &class__java_lang_Double, JAVA_TRUE, &class__java_lang_Object, EMPTY_INTERFACES, 0, 0, 0 }; @@ -629,7 +634,7 @@ void codenameOneGCMark() { } if (CN1_EDT_THREAD_ID == t->threadId && agressiveAllocator) { long freeMemory = get_free_memory(); - CN1_LOG("[GC] Blocking EDT as aggressive allocator, free memory=%lld", freeMemory); + CN1_LOG("[GC] Blocking EDT as aggressive allocator, free memory=%ld", freeMemory); } From 53157cbc58fa7ff65cb678e4cc9b60d324ce756d Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Fri, 26 Dec 2025 21:08:38 +0200 Subject: [PATCH 09/40] Add File stub for StampedLock integration --- .../StampedLockIntegrationTest.java | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/vm/tests/src/test/java/com/codename1/tools/translator/StampedLockIntegrationTest.java b/vm/tests/src/test/java/com/codename1/tools/translator/StampedLockIntegrationTest.java index 5b5fbd399b..12108f75b2 100644 --- a/vm/tests/src/test/java/com/codename1/tools/translator/StampedLockIntegrationTest.java +++ b/vm/tests/src/test/java/com/codename1/tools/translator/StampedLockIntegrationTest.java @@ -257,6 +257,60 @@ private void writeMockJavaClasses(Path sourceDir) throws Exception { // java.io.Serializable Files.write(io.resolve("Serializable.java"), "package java.io; public interface Serializable {}".getBytes(StandardCharsets.UTF_8)); + // Minimal java.io.File to satisfy translator native headers + Files.write(io.resolve("File.java"), ("package java.io;\n" + + "public class File {\n" + + " public static final String separator = \"/\";\n" + + " public static final char separatorChar = '/';\n" + + " public static final String pathSeparator = \":";\n" + + " public static final char pathSeparatorChar = ':';\n" + + " private String path;\n" + + " public File(String pathname) { this.path = pathname == null ? \"\" : pathname; }\n" + + " public File(File parent, String child) { this(parent == null ? child : parent.getPath() + separator + child); }\n" + + " public File(String parent, String child) { this(parent == null ? child : parent + separator + child); }\n" + + " public String getPath() { return path; }\n" + + " public boolean exists() { return existsImpl(path); }\n" + + " public boolean isDirectory() { return isDirectoryImpl(path); }\n" + + " public boolean isFile() { return isFileImpl(path); }\n" + + " public boolean isHidden() { return isHiddenImpl(path); }\n" + + " public long lastModified() { return lastModifiedImpl(path); }\n" + + " public long length() { return lengthImpl(path); }\n" + + " public boolean createNewFile() { return createNewFileImpl(path); }\n" + + " public boolean delete() { return deleteImpl(path); }\n" + + " public String[] list() { return listImpl(path); }\n" + + " public boolean mkdir() { return mkdirImpl(path); }\n" + + " public boolean renameTo(File dest) { return renameToImpl(path, dest == null ? null : dest.getPath()); }\n" + + " public boolean setReadOnly() { return setReadOnlyImpl(path); }\n" + + " public boolean setWritable(boolean writable) { return setWritableImpl(path, writable); }\n" + + " public boolean setReadable(boolean readable) { return setReadableImpl(path, readable); }\n" + + " public boolean setExecutable(boolean executable) { return setExecutableImpl(path, executable); }\n" + + " public long getTotalSpace() { return getTotalSpaceImpl(path); }\n" + + " public long getFreeSpace() { return getFreeSpaceImpl(path); }\n" + + " public long getUsableSpace() { return getUsableSpaceImpl(path); }\n" + + " public String getAbsolutePath() { return getAbsolutePathImpl(path); }\n" + + " public String getCanonicalPath() { return getCanonicalPathImpl(path); }\n" + + " private native String getAbsolutePathImpl(String path);\n" + + " private native String getCanonicalPathImpl(String path);\n" + + " private native boolean existsImpl(String path);\n" + + " private native boolean isDirectoryImpl(String path);\n" + + " private native boolean isFileImpl(String path);\n" + + " private native boolean isHiddenImpl(String path);\n" + + " private native long lastModifiedImpl(String path);\n" + + " private native long lengthImpl(String path);\n" + + " private native boolean createNewFileImpl(String path);\n" + + " private native boolean deleteImpl(String path);\n" + + " private native String[] listImpl(String path);\n" + + " private native boolean mkdirImpl(String path);\n" + + " private native boolean renameToImpl(String path, String dest);\n" + + " private native boolean setReadOnlyImpl(String path);\n" + + " private native boolean setWritableImpl(String path, boolean writable);\n" + + " private native boolean setReadableImpl(String path, boolean readable);\n" + + " private native boolean setExecutableImpl(String path, boolean executable);\n" + + " private native long getTotalSpaceImpl(String path);\n" + + " private native long getFreeSpaceImpl(String path);\n" + + " private native long getUsableSpaceImpl(String path);\n" + + "}\n").getBytes(StandardCharsets.UTF_8)); + // java.util.concurrent.TimeUnit Files.write(concurrent.resolve("TimeUnit.java"), ("package java.util.concurrent;\n" + "public class TimeUnit {\n" + From bbbb18a3b987e2b9b0f199c353acd296aa20959e Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Sat, 27 Dec 2025 07:27:50 +0200 Subject: [PATCH 10/40] Fix File pathSeparator literal in StampedLock test --- .../codename1/tools/translator/StampedLockIntegrationTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vm/tests/src/test/java/com/codename1/tools/translator/StampedLockIntegrationTest.java b/vm/tests/src/test/java/com/codename1/tools/translator/StampedLockIntegrationTest.java index 12108f75b2..9dcf7490f6 100644 --- a/vm/tests/src/test/java/com/codename1/tools/translator/StampedLockIntegrationTest.java +++ b/vm/tests/src/test/java/com/codename1/tools/translator/StampedLockIntegrationTest.java @@ -262,7 +262,7 @@ private void writeMockJavaClasses(Path sourceDir) throws Exception { "public class File {\n" + " public static final String separator = \"/\";\n" + " public static final char separatorChar = '/';\n" + - " public static final String pathSeparator = \":";\n" + + " public static final String pathSeparator = \":\";\n" + " public static final char pathSeparatorChar = ':';\n" + " private String path;\n" + " public File(String pathname) { this.path = pathname == null ? \"\" : pathname; }\n" + From c4d2578dc7503ba0f53010ed1073823006dd96da Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Sat, 27 Dec 2025 08:53:26 +0200 Subject: [PATCH 11/40] Add native stubs for translator clean builds --- .../BytecodeInstructionIntegrationTest.java | 20 +- .../CleanTargetIntegrationTest.java | 56 ++++- .../tools/translator/CompilerHelper.java | 10 +- .../translator/FileClassIntegrationTest.java | 10 +- .../FileStreamsIntegrationTest.java | 9 +- .../translator/LambdaIntegrationTest.java | 10 +- .../tools/translator/LockIntegrationTest.java | 10 +- .../ReadWriteLockIntegrationTest.java | 10 +- .../StampedLockIntegrationTest.java | 235 ++++++++++++++++-- 9 files changed, 301 insertions(+), 69 deletions(-) diff --git a/vm/tests/src/test/java/com/codename1/tools/translator/BytecodeInstructionIntegrationTest.java b/vm/tests/src/test/java/com/codename1/tools/translator/BytecodeInstructionIntegrationTest.java index 4a55df8d05..608562fc7e 100644 --- a/vm/tests/src/test/java/com/codename1/tools/translator/BytecodeInstructionIntegrationTest.java +++ b/vm/tests/src/test/java/com/codename1/tools/translator/BytecodeInstructionIntegrationTest.java @@ -142,13 +142,13 @@ void translatesOptimizedBytecodeToLLVMExecutable(CompilerHelper.CompilerConfig c Path buildDir = distDir.resolve("build"); Files.createDirectories(buildDir); - CleanTargetIntegrationTest.runCommand(Arrays.asList( + List cmakeCommand = new ArrayList<>(Arrays.asList( "cmake", "-S", distDir.toString(), - "-B", buildDir.toString(), - "-DCMAKE_C_COMPILER=clang", - "-DCMAKE_OBJC_COMPILER=clang" - ), distDir); + "-B", buildDir.toString() + )); + cmakeCommand.addAll(CleanTargetIntegrationTest.cmakeCompilerArgs()); + CleanTargetIntegrationTest.runCommand(cmakeCommand, distDir); CleanTargetIntegrationTest.runCommand(Arrays.asList("cmake", "--build", buildDir.toString()), distDir); @@ -293,13 +293,13 @@ void translatesInvokeAndLdcBytecodeToLLVMExecutable(CompilerHelper.CompilerConfi Path buildDir = distDir.resolve("build"); Files.createDirectories(buildDir); - CleanTargetIntegrationTest.runCommand(Arrays.asList( + List cmakeCommand = new ArrayList<>(Arrays.asList( "cmake", "-S", distDir.toString(), - "-B", buildDir.toString(), - "-DCMAKE_C_COMPILER=clang", - "-DCMAKE_OBJC_COMPILER=clang" - ), distDir); + "-B", buildDir.toString() + )); + cmakeCommand.addAll(CleanTargetIntegrationTest.cmakeCompilerArgs()); + CleanTargetIntegrationTest.runCommand(cmakeCommand, distDir); CleanTargetIntegrationTest.runCommand(Arrays.asList("cmake", "--build", buildDir.toString()), distDir); diff --git a/vm/tests/src/test/java/com/codename1/tools/translator/CleanTargetIntegrationTest.java b/vm/tests/src/test/java/com/codename1/tools/translator/CleanTargetIntegrationTest.java index c2c53b703d..1abc31f084 100644 --- a/vm/tests/src/test/java/com/codename1/tools/translator/CleanTargetIntegrationTest.java +++ b/vm/tests/src/test/java/com/codename1/tools/translator/CleanTargetIntegrationTest.java @@ -7,6 +7,7 @@ import javax.tools.JavaCompiler; import javax.tools.ToolProvider; import java.io.BufferedReader; +import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.lang.reflect.InvocationTargetException; @@ -17,6 +18,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; @@ -104,12 +106,13 @@ void generatesRunnableHelloWorldUsingCleanTarget(CompilerHelper.CompilerConfig c Path buildDir = distDir.resolve("build"); Files.createDirectories(buildDir); - runCommand(Arrays.asList( + List cmakeCommand = new ArrayList<>(Arrays.asList( "cmake", "-S", distDir.toString(), - "-B", buildDir.toString(), - "-DCMAKE_C_COMPILER=clang" - ), distDir); + "-B", buildDir.toString() + )); + cmakeCommand.addAll(cmakeCompilerArgs()); + runCommand(cmakeCommand, distDir); runCommand(Arrays.asList("cmake", "--build", buildDir.toString()), distDir); @@ -180,13 +183,56 @@ static void replaceLibraryWithExecutableTarget(Path cmakeLists, String sourceDir String globWithObjc = String.format("file(GLOB TRANSLATOR_SOURCES \"%s/*.c\" \"%s/*.m\")", sourceDirName, sourceDirName); String globCOnly = String.format("file(GLOB TRANSLATOR_SOURCES \"%s/*.c\")", sourceDirName); content = content.replace(globWithObjc, globCOnly); + content = content.replace(" LANGUAGES C OBJC", " LANGUAGES C"); + content = content.replace("enable_language(OBJC OPTIONAL)\n", ""); String replacement = content.replace( "add_library(${PROJECT_NAME} ${TRANSLATOR_SOURCES} ${TRANSLATOR_HEADERS})", - "add_executable(${PROJECT_NAME} ${TRANSLATOR_SOURCES} ${TRANSLATOR_HEADERS})\ntarget_link_libraries(${PROJECT_NAME} m)" + "add_executable(${PROJECT_NAME} ${TRANSLATOR_SOURCES} ${TRANSLATOR_HEADERS})\ntarget_link_libraries(${PROJECT_NAME} m pthread)" ); Files.write(cmakeLists, replacement.getBytes(StandardCharsets.UTF_8)); } + static List cmakeCompilerArgs() { + String cCompiler = findCompiler("clang", "gcc", "cc"); + if (cCompiler == null) { + cCompiler = "cc"; + } + String objcCompiler = findCompiler("clang"); + List args = new ArrayList<>(); + args.add("-DCMAKE_C_COMPILER=" + cCompiler); + if (objcCompiler != null) { + args.add("-DCMAKE_OBJC_COMPILER=" + objcCompiler); + } + return args; + } + + private static String findCompiler(String... candidates) { + String path = System.getenv("PATH"); + List searchPaths = new ArrayList<>(); + if (path != null && !path.isEmpty()) { + searchPaths.addAll(Arrays.asList(path.split(File.pathSeparator))); + } + + for (String candidate : candidates) { + if (candidate == null || candidate.isEmpty()) { + continue; + } + Path candidatePath = Paths.get(candidate); + if (!candidatePath.isAbsolute()) { + for (String dir : searchPaths) { + Path resolved = Paths.get(dir, candidate); + if (Files.isExecutable(resolved)) { + return resolved.toString(); + } + } + } else if (Files.isExecutable(candidatePath)) { + return candidatePath.toString(); + } + } + + return null; + } + static String runCommand(List command, Path workingDir) throws Exception { ProcessBuilder builder = new ProcessBuilder(command); builder.directory(workingDir.toFile()); diff --git a/vm/tests/src/test/java/com/codename1/tools/translator/CompilerHelper.java b/vm/tests/src/test/java/com/codename1/tools/translator/CompilerHelper.java index 39864a0872..0f2a9340db 100644 --- a/vm/tests/src/test/java/com/codename1/tools/translator/CompilerHelper.java +++ b/vm/tests/src/test/java/com/codename1/tools/translator/CompilerHelper.java @@ -532,13 +532,13 @@ public static boolean compileAndRun(String code, String expectedOutput) throws E java.nio.file.Path buildDir = distDir.resolve("build"); java.nio.file.Files.createDirectories(buildDir); - CleanTargetIntegrationTest.runCommand(Arrays.asList( + List cmakeCommand = new ArrayList<>(Arrays.asList( "cmake", "-S", distDir.toString(), - "-B", buildDir.toString(), - "-DCMAKE_C_COMPILER=clang", - "-DCMAKE_OBJC_COMPILER=clang" - ), distDir); + "-B", buildDir.toString() + )); + cmakeCommand.addAll(CleanTargetIntegrationTest.cmakeCompilerArgs()); + CleanTargetIntegrationTest.runCommand(cmakeCommand, distDir); CleanTargetIntegrationTest.runCommand(Arrays.asList("cmake", "--build", buildDir.toString()), distDir); diff --git a/vm/tests/src/test/java/com/codename1/tools/translator/FileClassIntegrationTest.java b/vm/tests/src/test/java/com/codename1/tools/translator/FileClassIntegrationTest.java index d358ae4244..68cd74adde 100644 --- a/vm/tests/src/test/java/com/codename1/tools/translator/FileClassIntegrationTest.java +++ b/vm/tests/src/test/java/com/codename1/tools/translator/FileClassIntegrationTest.java @@ -90,13 +90,13 @@ public void testFileClassMethods(CompilerHelper.CompilerConfig config) throws Ex Path buildDir = distDir.resolve("build"); Files.createDirectories(buildDir); - CleanTargetIntegrationTest.runCommand(Arrays.asList( + List cmakeCommand = new ArrayList<>(Arrays.asList( "cmake", "-S", distDir.toString(), - "-B", buildDir.toString(), - "-DCMAKE_C_COMPILER=clang", - "-DCMAKE_OBJC_COMPILER=clang" - ), distDir); + "-B", buildDir.toString() + )); + cmakeCommand.addAll(CleanTargetIntegrationTest.cmakeCompilerArgs()); + CleanTargetIntegrationTest.runCommand(cmakeCommand, distDir); CleanTargetIntegrationTest.runCommand(Arrays.asList("cmake", "--build", buildDir.toString()), distDir); diff --git a/vm/tests/src/test/java/com/codename1/tools/translator/FileStreamsIntegrationTest.java b/vm/tests/src/test/java/com/codename1/tools/translator/FileStreamsIntegrationTest.java index 97b1d729a1..e6d3b3299d 100644 --- a/vm/tests/src/test/java/com/codename1/tools/translator/FileStreamsIntegrationTest.java +++ b/vm/tests/src/test/java/com/codename1/tools/translator/FileStreamsIntegrationTest.java @@ -83,12 +83,13 @@ public void testFileStreams(CompilerHelper.CompilerConfig config) throws Excepti Path buildDir = distDir.resolve("build"); Files.createDirectories(buildDir); - CleanTargetIntegrationTest.runCommand(Arrays.asList( + List cmakeCommand = new ArrayList<>(Arrays.asList( "cmake", "-S", distDir.toString(), - "-B", buildDir.toString(), - "-DCMAKE_C_COMPILER=clang" - ), distDir); + "-B", buildDir.toString() + )); + cmakeCommand.addAll(CleanTargetIntegrationTest.cmakeCompilerArgs()); + CleanTargetIntegrationTest.runCommand(cmakeCommand, distDir); CleanTargetIntegrationTest.runCommand(Arrays.asList("cmake", "--build", buildDir.toString()), distDir); diff --git a/vm/tests/src/test/java/com/codename1/tools/translator/LambdaIntegrationTest.java b/vm/tests/src/test/java/com/codename1/tools/translator/LambdaIntegrationTest.java index f9f14e931f..114c15cc5e 100644 --- a/vm/tests/src/test/java/com/codename1/tools/translator/LambdaIntegrationTest.java +++ b/vm/tests/src/test/java/com/codename1/tools/translator/LambdaIntegrationTest.java @@ -128,13 +128,13 @@ void translatesLambdaBytecodeToLLVMExecutable(String targetVersion) throws Excep Path buildDir = distDir.resolve("build"); Files.createDirectories(buildDir); - CleanTargetIntegrationTest.runCommand(Arrays.asList( + List cmakeCommand = new ArrayList<>(Arrays.asList( "cmake", "-S", distDir.toString(), - "-B", buildDir.toString(), - "-DCMAKE_C_COMPILER=clang", - "-DCMAKE_OBJC_COMPILER=clang" - ), distDir); + "-B", buildDir.toString() + )); + cmakeCommand.addAll(CleanTargetIntegrationTest.cmakeCompilerArgs()); + CleanTargetIntegrationTest.runCommand(cmakeCommand, distDir); CleanTargetIntegrationTest.runCommand(Arrays.asList("cmake", "--build", buildDir.toString()), distDir); diff --git a/vm/tests/src/test/java/com/codename1/tools/translator/LockIntegrationTest.java b/vm/tests/src/test/java/com/codename1/tools/translator/LockIntegrationTest.java index 45c50c6fd4..6235fe48c7 100644 --- a/vm/tests/src/test/java/com/codename1/tools/translator/LockIntegrationTest.java +++ b/vm/tests/src/test/java/com/codename1/tools/translator/LockIntegrationTest.java @@ -95,13 +95,13 @@ void verifiesLockAndReentrantLockBehavior(CompilerHelper.CompilerConfig config) Path buildDir = distDir.resolve("build"); Files.createDirectories(buildDir); - CleanTargetIntegrationTest.runCommand(Arrays.asList( + List cmakeCommand = new ArrayList<>(Arrays.asList( "cmake", "-S", distDir.toString(), - "-B", buildDir.toString(), - "-DCMAKE_C_COMPILER=clang", - "-DCMAKE_OBJC_COMPILER=clang" - ), distDir); + "-B", buildDir.toString() + )); + cmakeCommand.addAll(CleanTargetIntegrationTest.cmakeCompilerArgs()); + CleanTargetIntegrationTest.runCommand(cmakeCommand, distDir); CleanTargetIntegrationTest.runCommand(Arrays.asList("cmake", "--build", buildDir.toString()), distDir); diff --git a/vm/tests/src/test/java/com/codename1/tools/translator/ReadWriteLockIntegrationTest.java b/vm/tests/src/test/java/com/codename1/tools/translator/ReadWriteLockIntegrationTest.java index 054e5e1cfd..90cb28d38f 100644 --- a/vm/tests/src/test/java/com/codename1/tools/translator/ReadWriteLockIntegrationTest.java +++ b/vm/tests/src/test/java/com/codename1/tools/translator/ReadWriteLockIntegrationTest.java @@ -99,13 +99,13 @@ void verifiesReadWriteLockBehavior(CompilerHelper.CompilerConfig config) throws Path buildDir = distDir.resolve("build"); Files.createDirectories(buildDir); - CleanTargetIntegrationTest.runCommand(Arrays.asList( + List cmakeCommand = new ArrayList<>(Arrays.asList( "cmake", "-S", distDir.toString(), - "-B", buildDir.toString(), - "-DCMAKE_C_COMPILER=clang", - "-DCMAKE_OBJC_COMPILER=clang" - ), distDir); + "-B", buildDir.toString() + )); + cmakeCommand.addAll(CleanTargetIntegrationTest.cmakeCompilerArgs()); + CleanTargetIntegrationTest.runCommand(cmakeCommand, distDir); CleanTargetIntegrationTest.runCommand(Arrays.asList("cmake", "--build", buildDir.toString()), distDir); diff --git a/vm/tests/src/test/java/com/codename1/tools/translator/StampedLockIntegrationTest.java b/vm/tests/src/test/java/com/codename1/tools/translator/StampedLockIntegrationTest.java index 9dcf7490f6..f3f2695ad9 100644 --- a/vm/tests/src/test/java/com/codename1/tools/translator/StampedLockIntegrationTest.java +++ b/vm/tests/src/test/java/com/codename1/tools/translator/StampedLockIntegrationTest.java @@ -90,19 +90,20 @@ void verifiesStampedLockBehavior(CompilerHelper.CompilerConfig config) throws Ex Path srcRoot = distDir.resolve("StampedLockTestApp-src"); CleanTargetIntegrationTest.patchCn1Globals(srcRoot); writeRuntimeStubs(srcRoot); + patchHashMapNativeSupport(srcRoot); - replaceLibraryWithExecutableTarget(cmakeLists, srcRoot.getFileName().toString()); + CleanTargetIntegrationTest.replaceLibraryWithExecutableTarget(cmakeLists, srcRoot.getFileName().toString()); Path buildDir = distDir.resolve("build"); Files.createDirectories(buildDir); - CleanTargetIntegrationTest.runCommand(Arrays.asList( + List cmakeCommand = new ArrayList<>(Arrays.asList( "cmake", "-S", distDir.toString(), - "-B", buildDir.toString(), - "-DCMAKE_C_COMPILER=clang", - "-DCMAKE_OBJC_COMPILER=clang" - ), distDir); + "-B", buildDir.toString() + )); + cmakeCommand.addAll(CleanTargetIntegrationTest.cmakeCompilerArgs()); + CleanTargetIntegrationTest.runCommand(cmakeCommand, distDir); CleanTargetIntegrationTest.runCommand(Arrays.asList("cmake", "--build", buildDir.toString()), distDir); @@ -122,6 +123,20 @@ private void writeMockJavaClasses(Path sourceDir) throws Exception { Files.createDirectories(concurrent); Files.createDirectories(io); + // java.util.HashMap (needed for generated native stubs) + Files.write(util.resolve("HashMap.java"), ("package java.util;\n" + + "public class HashMap {\n" + + " public HashMap() {}\n" + + " public V put(K key, V value) { return value; }\n" + + " public V get(Object key) { return null; }\n" + + " public int size() { return 0; }\n" + + " public static class Entry {\n" + + " public K key;\n" + + " public V value;\n" + + " public Entry(K key, V value) { this.key = key; this.value = value; }\n" + + " }\n" + + "}\n").getBytes(StandardCharsets.UTF_8)); + // java.lang.Object Files.write(lang.resolve("Object.java"), ("package java.lang;\n" + "public class Object {\n" + @@ -143,20 +158,32 @@ private void writeMockJavaClasses(Path sourceDir) throws Exception { " private int offset;\n" + " private int count;\n" + " public String(char[] v) { value = v; count=v.length; }\n" + + " public String(char[] v, int off, int len) { value = v; offset = off; count = len; }\n" + " public static String valueOf(Object obj) { return obj == null ? \"null\" : obj.toString(); }\n" + + " public byte[] getBytes() { return new byte[0]; }\n" + " public byte[] getBytes(String charset) { return new byte[0]; }\n" + + " public void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin) {\n" + + " for (int i = srcBegin; i < srcEnd; i++) { dst[dstBegin + i - srcBegin] = value[offset + i]; }\n" + + " }\n" + + " public int length() { return count; }\n" + "}\n").getBytes(StandardCharsets.UTF_8)); // java.lang.StringBuilder Files.write(lang.resolve("StringBuilder.java"), ("package java.lang;\n" + "public class StringBuilder {\n" + - " public StringBuilder() {}\n" + - " public StringBuilder(String str) {}\n" + - " public StringBuilder(int cap) {}\n" + - " public StringBuilder append(String s) { return this; }\n" + - " public StringBuilder append(Object o) { return this; }\n" + - " public StringBuilder append(int i) { return this; }\n" + - " public String toString() { return \"\"; }\n" + + " char[] value;\n" + + " int count;\n" + + " public StringBuilder() { this(16); }\n" + + " public StringBuilder(String str) { this(16); append(str); }\n" + + " public StringBuilder(int cap) { value = new char[cap]; }\n" + + " private void ensureCapacity(int cap) { if (cap > value.length) { char[] n = new char[cap]; System.arraycopy(value, 0, n, 0, count); value = n; } }\n" + + " public StringBuilder append(String s) { if (s == null) return append(\"null\"); int len = s.length(); ensureCapacity(count + len); s.getChars(0, len, value, count); count += len; return this; }\n" + + " public StringBuilder append(Object o) { return append(String.valueOf(o)); }\n" + + " public StringBuilder append(int i) { return append(String.valueOf(i)); }\n" + + " public StringBuilder append(char c) { ensureCapacity(count + 1); value[count++] = c; return this; }\n" + + " public void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin) { System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin); }\n" + + " public int length() { return count; }\n" + + " public String toString() { return new String(value, 0, count); }\n" + "}\n").getBytes(StandardCharsets.UTF_8)); // Primitive wrappers @@ -238,6 +265,7 @@ private void writeMockJavaClasses(Path sourceDir) throws Exception { Files.write(lang.resolve("System.java"), ("package java.lang;\n" + "public final class System {\n" + " public static long currentTimeMillis() { return 0L; }\n" + + " public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length) {}\n" + " public static void gc() {}\n" + " public static void startGCThread() {}\n" + " public static Thread gcThreadInstance;\n" + @@ -311,6 +339,46 @@ private void writeMockJavaClasses(Path sourceDir) throws Exception { " private native long getUsableSpaceImpl(String path);\n" + "}\n").getBytes(StandardCharsets.UTF_8)); + Files.write(io.resolve("FileInputStream.java"), ("package java.io;\n" + + "public class FileInputStream {\n" + + " public FileInputStream(String name) { open(name); }\n" + + " public FileInputStream(File file) { open(file); }\n" + + " public native int read();\n" + + " public native int read(byte[] b, int off, int len);\n" + + " public native long skip(long n);\n" + + " public native int available();\n" + + " public native void close();\n" + + " private native void open(String name);\n" + + " private native void open(File file);\n" + + "}\n").getBytes(StandardCharsets.UTF_8)); + + Files.write(io.resolve("FileOutputStream.java"), ("package java.io;\n" + + "public class FileOutputStream {\n" + + " public FileOutputStream(String name) { open(name, false); }\n" + + " public FileOutputStream(String name, boolean append) { open(name, append); }\n" + + " public FileOutputStream(File file) { open(file.getPath(), false); }\n" + + " public FileOutputStream(File file, boolean append) { open(file.getPath(), append); }\n" + + " public native void write(int b);\n" + + " public native void write(byte[] b, int off, int len);\n" + + " public native void flush();\n" + + " public native void close();\n" + + " private native void open(String name, boolean append);\n" + + "}\n").getBytes(StandardCharsets.UTF_8)); + + Files.write(io.resolve("FileWriter.java"), ("package java.io;\n" + + "public class FileWriter {\n" + + " private final FileOutputStream out;\n" + + " public FileWriter(String name) { this.out = new FileOutputStream(name); }\n" + + " public FileWriter(File file) { this.out = new FileOutputStream(file); }\n" + + " public void write(String s) throws java.io.IOException {\n" + + " if (s == null) return;\n" + + " byte[] data = s.getBytes();\n" + + " out.write(data, 0, data.length);\n" + + " }\n" + + " public void flush() { out.flush(); }\n" + + " public void close() { out.close(); }\n" + + "}\n").getBytes(StandardCharsets.UTF_8)); + // java.util.concurrent.TimeUnit Files.write(concurrent.resolve("TimeUnit.java"), ("package java.util.concurrent;\n" + "public class TimeUnit {\n" + @@ -322,14 +390,28 @@ private void writeMockJavaClasses(Path sourceDir) throws Exception { private String stampedLockTestAppSource() { return "import java.util.concurrent.locks.*;\n" + "import java.util.concurrent.TimeUnit;\n" + + "import java.io.*;\n" + "public class StampedLockTestApp {\n" + " private static native void report(String msg);\n" + " \n" + " public static void main(String[] args) {\n" + + " touchFileStreams();\n" + + " java.util.HashMap map = new java.util.HashMap();\n" + + " map.put(\"k\", \"v\");\n" + + " map.get(\"k\");\n" + " testBasic();\n" + " testOptimisticRead();\n" + " testWriteLockExclusion();\n" + " }\n" + + " private static void touchFileStreams() {\n" + + " try {\n" + + " File f = new File(\"fs-touch.tmp\");\n" + + " new FileInputStream(f).close();\n" + + " new FileOutputStream(f).close();\n" + + " new FileWriter(f).close();\n" + + " } catch (Exception ignore) {\n" + + " }\n" + + " }\n" + " \n" + " private static void testBasic() {\n" + " StampedLock sl = new StampedLock();\n" + @@ -429,18 +511,6 @@ private String nativeReportSource() { "}\n"; } - static void replaceLibraryWithExecutableTarget(Path cmakeLists, String sourceDirName) throws java.io.IOException { - String content = new String(Files.readAllBytes(cmakeLists), StandardCharsets.UTF_8); - String globWithObjc = String.format("file(GLOB TRANSLATOR_SOURCES \"%s/*.c\" \"%s/*.m\")", sourceDirName, sourceDirName); - String globCOnly = String.format("file(GLOB TRANSLATOR_SOURCES \"%s/*.c\")", sourceDirName); - content = content.replace(globWithObjc, globCOnly); - String replacement = content.replace( - "add_library(${PROJECT_NAME} ${TRANSLATOR_SOURCES} ${TRANSLATOR_HEADERS})", - "add_executable(${PROJECT_NAME} ${TRANSLATOR_SOURCES} ${TRANSLATOR_HEADERS})\ntarget_link_libraries(${PROJECT_NAME} m pthread)" - ); - Files.write(cmakeLists, replacement.getBytes(StandardCharsets.UTF_8)); - } - private void writeRuntimeStubs(Path srcRoot) throws java.io.IOException { Path stubs = srcRoot.resolve("runtime_stubs.c"); String content = "#include \"cn1_globals.h\"\n" + @@ -651,4 +721,119 @@ private void writeRuntimeStubs(Path srcRoot) throws java.io.IOException { Files.write(stubs, content.getBytes(StandardCharsets.UTF_8)); } + + private void patchHashMapNativeSupport(Path srcRoot) throws java.io.IOException { + Path hashMapHeader = srcRoot.resolve("java_util_HashMap.h"); + if (Files.exists(hashMapHeader)) { + String content = new String(Files.readAllBytes(hashMapHeader), StandardCharsets.UTF_8); + if (!content.contains("java_util_HashMap_elementData")) { + String structDef = "struct obj__java_util_HashMap {\n" + + " DEBUG_GC_VARIABLES\n" + + " struct clazz *__codenameOneParentClsReference;\n" + + " int __codenameOneReferenceCount;\n" + + " void* __codenameOneThreadData;\n" + + " int __codenameOneGcMark;\n" + + " void* __ownerThread;\n" + + " int __heapPosition;\n" + + " JAVA_OBJECT java_util_HashMap_elementData;\n" + + "};"; + content = content.replace("struct obj__java_util_HashMap {\n" + + " DEBUG_GC_VARIABLES\n" + + " struct clazz *__codenameOneParentClsReference;\n" + + " int __codenameOneReferenceCount;\n" + + " void* __codenameOneThreadData;\n" + + " int __codenameOneGcMark;\n" + + " void* __ownerThread;\n" + + " int __heapPosition;\n" + + "};", structDef); + Files.write(hashMapHeader, content.getBytes(StandardCharsets.UTF_8)); + } + } + + Path entryHeader = srcRoot.resolve("java_util_HashMap_Entry.h"); + if (!Files.exists(entryHeader)) { + String entryContent = "#ifndef __JAVA_UTIL_HASHMAP_ENTRY__\n" + + "#define __JAVA_UTIL_HASHMAP_ENTRY__\n\n" + + "#include \"cn1_globals.h\"\n" + + "#include \"java_lang_Object.h\"\n\n" + + "extern struct clazz class__java_util_HashMap_Entry;\n\n" + + "struct obj__java_util_HashMap_Entry {\n" + + " DEBUG_GC_VARIABLES\n" + + " struct clazz *__codenameOneParentClsReference;\n" + + " int __codenameOneReferenceCount;\n" + + " void* __codenameOneThreadData;\n" + + " int __codenameOneGcMark;\n" + + " void* __ownerThread;\n" + + " int __heapPosition;\n" + + " JAVA_OBJECT java_util_MapEntry_key;\n" + + " JAVA_OBJECT java_util_MapEntry_value;\n" + + " JAVA_INT java_util_HashMap_Entry_origKeyHash;\n" + + " JAVA_OBJECT java_util_HashMap_Entry_next;\n" + + "};\n\n" + + "#endif\n"; + Files.write(entryHeader, entryContent.getBytes(StandardCharsets.UTF_8)); + } + + Path dateHeader = srcRoot.resolve("java_util_Date.h"); + if (!Files.exists(dateHeader)) { + String dateContent = "#ifndef __JAVA_UTIL_DATE__\n" + + "#define __JAVA_UTIL_DATE__\n\n" + + "#include \"cn1_globals.h\"\n" + + "#include \"java_lang_Object.h\"\n\n" + + "extern struct clazz class__java_util_Date;\n\n" + + "struct obj__java_util_Date {\n" + + " DEBUG_GC_VARIABLES\n" + + " struct clazz *__codenameOneParentClsReference;\n" + + " int __codenameOneReferenceCount;\n" + + " void* __codenameOneThreadData;\n" + + " int __codenameOneGcMark;\n" + + " void* __ownerThread;\n" + + " int __heapPosition;\n" + + " JAVA_LONG java_util_Date_date;\n" + + "};\n\n" + + "#endif\n"; + Files.write(dateHeader, dateContent.getBytes(StandardCharsets.UTF_8)); + } + + Path dateFormatHeader = srcRoot.resolve("java_text_DateFormat.h"); + if (!Files.exists(dateFormatHeader)) { + String dateFormatContent = "#ifndef __JAVA_TEXT_DATEFORMAT__\n" + + "#define __JAVA_TEXT_DATEFORMAT__\n\n" + + "#include \"cn1_globals.h\"\n" + + "#include \"java_lang_Object.h\"\n\n" + + "extern struct clazz class__java_text_DateFormat;\n\n" + + "struct obj__java_text_DateFormat {\n" + + " DEBUG_GC_VARIABLES\n" + + " struct clazz *__codenameOneParentClsReference;\n" + + " int __codenameOneReferenceCount;\n" + + " void* __codenameOneThreadData;\n" + + " int __codenameOneGcMark;\n" + + " void* __ownerThread;\n" + + " int __heapPosition;\n" + + " JAVA_INT java_text_DateFormat_dateStyle;\n" + + "};\n\n" + + "#endif\n"; + Files.write(dateFormatHeader, dateFormatContent.getBytes(StandardCharsets.UTF_8)); + } + + Path stringToRealHeader = srcRoot.resolve("java_lang_StringToReal.h"); + if (!Files.exists(stringToRealHeader)) { + String strContent = "#ifndef __JAVA_LANG_STRINGTOREAL__\n" + + "#define __JAVA_LANG_STRINGTOREAL__\n\n" + + "#include \"cn1_globals.h\"\n" + + "#include \"java_lang_Object.h\"\n\n" + + "extern struct clazz class__java_lang_StringToReal;\n\n" + + "struct obj__java_lang_StringToReal {\n" + + " DEBUG_GC_VARIABLES\n" + + " struct clazz *__codenameOneParentClsReference;\n" + + " int __codenameOneReferenceCount;\n" + + " void* __codenameOneThreadData;\n" + + " int __codenameOneGcMark;\n" + + " void* __ownerThread;\n" + + " int __heapPosition;\n" + + "};\n\n" + + "#endif\n"; + Files.write(stringToRealHeader, strContent.getBytes(StandardCharsets.UTF_8)); + } + } } From fe25f5554f9ae681db237ae33a3118148e86689b Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Sat, 27 Dec 2025 10:30:23 +0200 Subject: [PATCH 12/40] Add primitive wrapper mocks for read/write lock integration --- .../ReadWriteLockIntegrationTest.java | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/vm/tests/src/test/java/com/codename1/tools/translator/ReadWriteLockIntegrationTest.java b/vm/tests/src/test/java/com/codename1/tools/translator/ReadWriteLockIntegrationTest.java index 90cb28d38f..b1ac755fd5 100644 --- a/vm/tests/src/test/java/com/codename1/tools/translator/ReadWriteLockIntegrationTest.java +++ b/vm/tests/src/test/java/com/codename1/tools/translator/ReadWriteLockIntegrationTest.java @@ -149,7 +149,40 @@ private void writeMockJavaClasses(Path sourceDir) throws Exception { " private int offset;\n" + " private int count;\n" + " public String(char[] v) { value = v; count=v.length; }\n" + + " public String(char[] v, int off, int len) { value = v; offset = off; count = len; }\n" + " public static String valueOf(Object obj) { return obj == null ? \"null\" : obj.toString(); }\n" + + " public byte[] getBytes() { return new byte[0]; }\n" + + " public byte[] getBytes(String charset) { return new byte[0]; }\n" + + " public void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin) {\n" + + " for (int i = srcBegin; i < srcEnd; i++) { dst[dstBegin + i - srcBegin] = value[offset + i]; }\n" + + " }\n" + + " public int length() { return count; }\n" + + "}\n").getBytes(StandardCharsets.UTF_8)); + + // Primitive wrappers + Files.write(lang.resolve("Boolean.java"), ("package java.lang;\n" + + "public final class Boolean {\n" + + " private final boolean value;\n" + + " public Boolean(boolean value) { this.value = value; }\n" + + " public boolean booleanValue() { return value; }\n" + + "}\n").getBytes(StandardCharsets.UTF_8)); + Files.write(lang.resolve("Byte.java"), ("package java.lang;\n" + + "public final class Byte {\n" + + " private final byte value;\n" + + " public Byte(byte value) { this.value = value; }\n" + + " public byte byteValue() { return value; }\n" + + "}\n").getBytes(StandardCharsets.UTF_8)); + Files.write(lang.resolve("Short.java"), ("package java.lang;\n" + + "public final class Short {\n" + + " private final short value;\n" + + " public Short(short value) { this.value = value; }\n" + + " public short shortValue() { return value; }\n" + + "}\n").getBytes(StandardCharsets.UTF_8)); + Files.write(lang.resolve("Character.java"), ("package java.lang;\n" + + "public final class Character {\n" + + " private final char value;\n" + + " public Character(char value) { this.value = value; }\n" + + " public char charValue() { return value; }\n" + "}\n").getBytes(StandardCharsets.UTF_8)); // java.lang.StringBuilder @@ -216,6 +249,29 @@ private void writeMockJavaClasses(Path sourceDir) throws Exception { " public String toString() { return \"\"+value; }\n" + "}\n").getBytes(StandardCharsets.UTF_8)); + // Additional primitive wrappers + Files.write(lang.resolve("Long.java"), ("package java.lang;\n" + + "public final class Long extends Number {\n" + + " private final long value;\n" + + " public Long(long value) { this.value = value; }\n" + + " public long longValue() { return value; }\n" + + " public int intValue() { return (int)value; }\n" + + "}\n").getBytes(StandardCharsets.UTF_8)); + Files.write(lang.resolve("Float.java"), ("package java.lang;\n" + + "public final class Float extends Number {\n" + + " private final float value;\n" + + " public Float(float value) { this.value = value; }\n" + + " public float floatValue() { return value; }\n" + + " public int intValue() { return (int)value; }\n" + + "}\n").getBytes(StandardCharsets.UTF_8)); + Files.write(lang.resolve("Double.java"), ("package java.lang;\n" + + "public final class Double extends Number {\n" + + " private final double value;\n" + + " public Double(double value) { this.value = value; }\n" + + " public double doubleValue() { return value; }\n" + + " public int intValue() { return (int)value; }\n" + + "}\n").getBytes(StandardCharsets.UTF_8)); + // java.lang.Number Files.write(lang.resolve("Number.java"), ("package java.lang;\n" + "public abstract class Number implements java.io.Serializable {\n" + From 1d4ad0d13b56e7c1c7d067d8215280383dc2ca84 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Sat, 27 Dec 2025 16:04:50 +0200 Subject: [PATCH 13/40] Add ArrayIndexOutOfBoundsException mock for RW lock test --- .../codename1/tools/translator/ReadWriteLockIntegrationTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/vm/tests/src/test/java/com/codename1/tools/translator/ReadWriteLockIntegrationTest.java b/vm/tests/src/test/java/com/codename1/tools/translator/ReadWriteLockIntegrationTest.java index b1ac755fd5..9c4442e7f6 100644 --- a/vm/tests/src/test/java/com/codename1/tools/translator/ReadWriteLockIntegrationTest.java +++ b/vm/tests/src/test/java/com/codename1/tools/translator/ReadWriteLockIntegrationTest.java @@ -286,6 +286,7 @@ private void writeMockJavaClasses(Path sourceDir) throws Exception { Files.write(lang.resolve("NullPointerException.java"), "package java.lang; public class NullPointerException extends RuntimeException { public NullPointerException() {} }".getBytes(StandardCharsets.UTF_8)); Files.write(lang.resolve("IllegalMonitorStateException.java"), "package java.lang; public class IllegalMonitorStateException extends RuntimeException { public IllegalMonitorStateException() {} }".getBytes(StandardCharsets.UTF_8)); Files.write(lang.resolve("IllegalArgumentException.java"), "package java.lang; public class IllegalArgumentException extends RuntimeException { public IllegalArgumentException(String s) {} }".getBytes(StandardCharsets.UTF_8)); + Files.write(lang.resolve("ArrayIndexOutOfBoundsException.java"), "package java.lang; public class ArrayIndexOutOfBoundsException extends RuntimeException { public ArrayIndexOutOfBoundsException() {} }".getBytes(StandardCharsets.UTF_8)); Files.write(lang.resolve("UnsupportedOperationException.java"), "package java.lang; public class UnsupportedOperationException extends RuntimeException { public UnsupportedOperationException() {} }".getBytes(StandardCharsets.UTF_8)); // java.io.Serializable From 9fe2e63ac39198602851de8a0aa50f7ac5379e41 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Sat, 27 Dec 2025 19:41:06 +0200 Subject: [PATCH 14/40] Add GC stubs to ReadWriteLock System mock --- .../tools/translator/ReadWriteLockIntegrationTest.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/vm/tests/src/test/java/com/codename1/tools/translator/ReadWriteLockIntegrationTest.java b/vm/tests/src/test/java/com/codename1/tools/translator/ReadWriteLockIntegrationTest.java index 9c4442e7f6..6e8c9362f5 100644 --- a/vm/tests/src/test/java/com/codename1/tools/translator/ReadWriteLockIntegrationTest.java +++ b/vm/tests/src/test/java/com/codename1/tools/translator/ReadWriteLockIntegrationTest.java @@ -237,6 +237,9 @@ private void writeMockJavaClasses(Path sourceDir) throws Exception { Files.write(lang.resolve("System.java"), ("package java.lang;\n" + "public final class System {\n" + " public static native long currentTimeMillis();\n" + + " public static void gc() {}\n" + + " public static void startGCThread() {}\n" + + " public static Thread gcThreadInstance;\n" + "}\n").getBytes(StandardCharsets.UTF_8)); // java.lang.Integer From d1f71e54f19bc0f6cd214428df7ec4829630679a Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Sat, 27 Dec 2025 20:01:23 +0200 Subject: [PATCH 15/40] Add File mock for ReadWriteLock integration test --- .../ReadWriteLockIntegrationTest.java | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/vm/tests/src/test/java/com/codename1/tools/translator/ReadWriteLockIntegrationTest.java b/vm/tests/src/test/java/com/codename1/tools/translator/ReadWriteLockIntegrationTest.java index 6e8c9362f5..872c0953d0 100644 --- a/vm/tests/src/test/java/com/codename1/tools/translator/ReadWriteLockIntegrationTest.java +++ b/vm/tests/src/test/java/com/codename1/tools/translator/ReadWriteLockIntegrationTest.java @@ -295,6 +295,60 @@ private void writeMockJavaClasses(Path sourceDir) throws Exception { // java.io.Serializable Files.write(io.resolve("Serializable.java"), "package java.io; public interface Serializable {}".getBytes(StandardCharsets.UTF_8)); + // Minimal java.io.File to satisfy translator native headers + Files.write(io.resolve("File.java"), ("package java.io;\n" + + "public class File {\n" + + " public static final String separator = \"/\";\n" + + " public static final char separatorChar = '/';\n" + + " public static final String pathSeparator = \":\";\n" + + " public static final char pathSeparatorChar = ':';\n" + + " private String path;\n" + + " public File(String pathname) { this.path = pathname == null ? \"\" : pathname; }\n" + + " public File(File parent, String child) { this(parent == null ? child : parent.getPath() + separator + child); }\n" + + " public File(String parent, String child) { this(parent == null ? child : parent + separator + child); }\n" + + " public String getPath() { return path; }\n" + + " public boolean exists() { return existsImpl(path); }\n" + + " public boolean isDirectory() { return isDirectoryImpl(path); }\n" + + " public boolean isFile() { return isFileImpl(path); }\n" + + " public boolean isHidden() { return isHiddenImpl(path); }\n" + + " public long lastModified() { return lastModifiedImpl(path); }\n" + + " public long length() { return lengthImpl(path); }\n" + + " public boolean createNewFile() { return createNewFileImpl(path); }\n" + + " public boolean delete() { return deleteImpl(path); }\n" + + " public String[] list() { return listImpl(path); }\n" + + " public boolean mkdir() { return mkdirImpl(path); }\n" + + " public boolean renameTo(File dest) { return renameToImpl(path, dest == null ? null : dest.getPath()); }\n" + + " public boolean setReadOnly() { return setReadOnlyImpl(path); }\n" + + " public boolean setWritable(boolean writable) { return setWritableImpl(path, writable); }\n" + + " public boolean setReadable(boolean readable) { return setReadableImpl(path, readable); }\n" + + " public boolean setExecutable(boolean executable) { return setExecutableImpl(path, executable); }\n" + + " public long getTotalSpace() { return getTotalSpaceImpl(path); }\n" + + " public long getFreeSpace() { return getFreeSpaceImpl(path); }\n" + + " public long getUsableSpace() { return getUsableSpaceImpl(path); }\n" + + " public String getAbsolutePath() { return getAbsolutePathImpl(path); }\n" + + " public String getCanonicalPath() { return getCanonicalPathImpl(path); }\n" + + " private native String getAbsolutePathImpl(String path);\n" + + " private native String getCanonicalPathImpl(String path);\n" + + " private native boolean existsImpl(String path);\n" + + " private native boolean isDirectoryImpl(String path);\n" + + " private native boolean isFileImpl(String path);\n" + + " private native boolean isHiddenImpl(String path);\n" + + " private native long lastModifiedImpl(String path);\n" + + " private native long lengthImpl(String path);\n" + + " private native boolean createNewFileImpl(String path);\n" + + " private native boolean deleteImpl(String path);\n" + + " private native String[] listImpl(String path);\n" + + " private native boolean mkdirImpl(String path);\n" + + " private native boolean renameToImpl(String source, String dest);\n" + + " private native boolean setReadOnlyImpl(String path);\n" + + " private native boolean setWritableImpl(String path, boolean writable);\n" + + " private native boolean setReadableImpl(String path, boolean readable);\n" + + " private native boolean setExecutableImpl(String path, boolean executable);\n" + + " private native long getTotalSpaceImpl(String path);\n" + + " private native long getFreeSpaceImpl(String path);\n" + + " private native long getUsableSpaceImpl(String path);\n" + + "}\n").getBytes(StandardCharsets.UTF_8)); + // java.util.Collection Files.write(util.resolve("Collection.java"), "package java.util; public interface Collection {}".getBytes(StandardCharsets.UTF_8)); From d82026d21ef50f4d09459347bd4e2abb80a47ba5 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Sat, 27 Dec 2025 20:27:28 +0200 Subject: [PATCH 16/40] Patch java_io_File header for clean builds --- .../translator/CleanTargetIntegrationTest.java | 14 ++++++++++++++ .../translator/ReadWriteLockIntegrationTest.java | 1 + .../translator/StampedLockIntegrationTest.java | 1 + 3 files changed, 16 insertions(+) diff --git a/vm/tests/src/test/java/com/codename1/tools/translator/CleanTargetIntegrationTest.java b/vm/tests/src/test/java/com/codename1/tools/translator/CleanTargetIntegrationTest.java index 1abc31f084..8ee7fada46 100644 --- a/vm/tests/src/test/java/com/codename1/tools/translator/CleanTargetIntegrationTest.java +++ b/vm/tests/src/test/java/com/codename1/tools/translator/CleanTargetIntegrationTest.java @@ -260,6 +260,20 @@ static void patchCn1Globals(Path srcRoot) throws IOException { } } + static void patchFileHeader(Path srcRoot) throws IOException { + Path fileHeader = srcRoot.resolve("java_io_File.h"); + if (!Files.exists(fileHeader)) { + return; + } + String content = new String(Files.readAllBytes(fileHeader), StandardCharsets.UTF_8); + String updated = content + .replace("get_static_java_io_File_separator();", "get_static_java_io_File_separator(CODENAME_ONE_THREAD_STATE);") + .replace("get_static_java_io_File_separatorChar();", "get_static_java_io_File_separatorChar(CODENAME_ONE_THREAD_STATE);"); + if (!updated.equals(content)) { + Files.write(fileHeader, updated.getBytes(StandardCharsets.UTF_8)); + } + } + static void writeRuntimeStubs(Path srcRoot) throws IOException { Path objectHeader = srcRoot.resolve("java_lang_Object.h"); if (!Files.exists(objectHeader)) { diff --git a/vm/tests/src/test/java/com/codename1/tools/translator/ReadWriteLockIntegrationTest.java b/vm/tests/src/test/java/com/codename1/tools/translator/ReadWriteLockIntegrationTest.java index 872c0953d0..8923d4d37e 100644 --- a/vm/tests/src/test/java/com/codename1/tools/translator/ReadWriteLockIntegrationTest.java +++ b/vm/tests/src/test/java/com/codename1/tools/translator/ReadWriteLockIntegrationTest.java @@ -92,6 +92,7 @@ void verifiesReadWriteLockBehavior(CompilerHelper.CompilerConfig config) throws Path srcRoot = distDir.resolve("ReadWriteLockTestApp-src"); CleanTargetIntegrationTest.patchCn1Globals(srcRoot); + CleanTargetIntegrationTest.patchFileHeader(srcRoot); writeRuntimeStubs(srcRoot); replaceLibraryWithExecutableTarget(cmakeLists, srcRoot.getFileName().toString()); diff --git a/vm/tests/src/test/java/com/codename1/tools/translator/StampedLockIntegrationTest.java b/vm/tests/src/test/java/com/codename1/tools/translator/StampedLockIntegrationTest.java index f3f2695ad9..4dca309763 100644 --- a/vm/tests/src/test/java/com/codename1/tools/translator/StampedLockIntegrationTest.java +++ b/vm/tests/src/test/java/com/codename1/tools/translator/StampedLockIntegrationTest.java @@ -89,6 +89,7 @@ void verifiesStampedLockBehavior(CompilerHelper.CompilerConfig config) throws Ex Path srcRoot = distDir.resolve("StampedLockTestApp-src"); CleanTargetIntegrationTest.patchCn1Globals(srcRoot); + CleanTargetIntegrationTest.patchFileHeader(srcRoot); writeRuntimeStubs(srcRoot); patchHashMapNativeSupport(srcRoot); From 33d956db4b49e28c18588ae09835583e68a8a953 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Sat, 27 Dec 2025 20:38:53 +0200 Subject: [PATCH 17/40] Add file stream stubs to clean translator tests --- .../CleanTargetIntegrationTest.java | 4 +- .../ReadWriteLockIntegrationTest.java | 87 ++++++++++++++++++ .../StampedLockIntegrationTest.java | 90 ++++++++++++++----- 3 files changed, 160 insertions(+), 21 deletions(-) diff --git a/vm/tests/src/test/java/com/codename1/tools/translator/CleanTargetIntegrationTest.java b/vm/tests/src/test/java/com/codename1/tools/translator/CleanTargetIntegrationTest.java index 8ee7fada46..133cabd324 100644 --- a/vm/tests/src/test/java/com/codename1/tools/translator/CleanTargetIntegrationTest.java +++ b/vm/tests/src/test/java/com/codename1/tools/translator/CleanTargetIntegrationTest.java @@ -268,7 +268,9 @@ static void patchFileHeader(Path srcRoot) throws IOException { String content = new String(Files.readAllBytes(fileHeader), StandardCharsets.UTF_8); String updated = content .replace("get_static_java_io_File_separator();", "get_static_java_io_File_separator(CODENAME_ONE_THREAD_STATE);") - .replace("get_static_java_io_File_separatorChar();", "get_static_java_io_File_separatorChar(CODENAME_ONE_THREAD_STATE);"); + .replace("get_static_java_io_File_separatorChar();", "get_static_java_io_File_separatorChar(CODENAME_ONE_THREAD_STATE);") + .replace("get_static_java_io_File_pathSeparator();", "get_static_java_io_File_pathSeparator(CODENAME_ONE_THREAD_STATE);") + .replace("get_static_java_io_File_pathSeparatorChar();", "get_static_java_io_File_pathSeparatorChar(CODENAME_ONE_THREAD_STATE);"); if (!updated.equals(content)) { Files.write(fileHeader, updated.getBytes(StandardCharsets.UTF_8)); } diff --git a/vm/tests/src/test/java/com/codename1/tools/translator/ReadWriteLockIntegrationTest.java b/vm/tests/src/test/java/com/codename1/tools/translator/ReadWriteLockIntegrationTest.java index 8923d4d37e..184da83df3 100644 --- a/vm/tests/src/test/java/com/codename1/tools/translator/ReadWriteLockIntegrationTest.java +++ b/vm/tests/src/test/java/com/codename1/tools/translator/ReadWriteLockIntegrationTest.java @@ -143,6 +143,9 @@ private void writeMockJavaClasses(Path sourceDir) throws Exception { " public final native Class getClass();\n" + "}\n").getBytes(StandardCharsets.UTF_8)); + // java.lang.AutoCloseable + Files.write(lang.resolve("AutoCloseable.java"), "package java.lang; public interface AutoCloseable { void close() throws java.io.IOException; }".getBytes(StandardCharsets.UTF_8)); + // java.lang.String Files.write(lang.resolve("String.java"), ("package java.lang;\n" + "public class String {\n" + @@ -296,6 +299,90 @@ private void writeMockJavaClasses(Path sourceDir) throws Exception { // java.io.Serializable Files.write(io.resolve("Serializable.java"), "package java.io; public interface Serializable {}".getBytes(StandardCharsets.UTF_8)); + // java.io.IOException + Files.write(io.resolve("IOException.java"), "package java.io; public class IOException extends Exception { public IOException() {} public IOException(String s) { super(s); } }".getBytes(StandardCharsets.UTF_8)); + + // java.io.InputStream + Files.write(io.resolve("InputStream.java"), ("package java.io;\n" + + "public class InputStream implements java.lang.AutoCloseable {\n" + + " public InputStream() {}\n" + + " public int available() throws IOException { return 0; }\n" + + " public int read() throws IOException { return -1; }\n" + + " public int read(byte[] b, int off, int len) throws IOException { return read(); }\n" + + " public long skip(long n) throws IOException { return 0; }\n" + + " public void close() throws IOException {}\n" + + " public synchronized void mark(int readlimit) {}\n" + + " public void reset() throws IOException { throw new IOException(); }\n" + + " public boolean markSupported() { return false; }\n" + + "}\n").getBytes(StandardCharsets.UTF_8)); + + // java.io.OutputStream + Files.write(io.resolve("OutputStream.java"), ("package java.io;\n" + + "public class OutputStream implements java.lang.AutoCloseable {\n" + + " public OutputStream() {}\n" + + " public void write(int b) throws IOException {}\n" + + " public void write(byte[] b, int off, int len) throws IOException {\n" + + " for (int i = 0; i < len; i++) { write(b[off + i]); }\n" + + " }\n" + + " public void flush() throws IOException {}\n" + + " public void close() throws IOException {}\n" + + "}\n").getBytes(StandardCharsets.UTF_8)); + + // java.io.File streams + Files.write(io.resolve("FileInputStream.java"), ("package java.io;\n" + + "public class FileInputStream extends InputStream {\n" + + " private long handle;\n" + + " private boolean closed;\n" + + " public FileInputStream(String name) throws IOException { this(name == null ? null : new File(name)); }\n" + + " public FileInputStream(File file) throws IOException {\n" + + " if (file == null) throw new NullPointerException();\n" + + " this.handle = openImpl(file.getPath());\n" + + " if (this.handle == 0) throw new IOException();\n" + + " }\n" + + " public int read() throws IOException { byte[] b = new byte[1]; int c = read(b,0,1); return c <= 0 ? -1 : b[0] & 0xff; }\n" + + " public int read(byte[] b, int off, int len) throws IOException { if (closed) throw new IOException(); return readImpl(handle, b, off, len); }\n" + + " public long skip(long n) throws IOException { if (closed) throw new IOException(); return skipImpl(handle, n); }\n" + + " public int available() throws IOException { if (closed) throw new IOException(); return availableImpl(handle); }\n" + + " public void close() throws IOException { if (!closed) { closed = true; closeImpl(handle); handle = 0; } }\n" + + " private static native long openImpl(String path) throws IOException;\n" + + " private static native int readImpl(long handle, byte[] b, int off, int len) throws IOException;\n" + + " private static native long skipImpl(long handle, long n) throws IOException;\n" + + " private static native int availableImpl(long handle) throws IOException;\n" + + " private static native void closeImpl(long handle) throws IOException;\n" + + "}\n").getBytes(StandardCharsets.UTF_8)); + + Files.write(io.resolve("FileOutputStream.java"), ("package java.io;\n" + + "public class FileOutputStream extends OutputStream {\n" + + " private long handle;\n" + + " private boolean closed;\n" + + " public FileOutputStream(String name) throws IOException { this(name, false); }\n" + + " public FileOutputStream(String name, boolean append) throws IOException { this(name == null ? null : new File(name), append); }\n" + + " public FileOutputStream(File file) throws IOException { this(file, false); }\n" + + " public FileOutputStream(File file, boolean append) throws IOException {\n" + + " if (file == null) throw new NullPointerException();\n" + + " this.handle = openImpl(file.getPath(), append);\n" + + " if (this.handle == 0) throw new IOException();\n" + + " }\n" + + " public void write(int b) throws IOException { byte[] tmp = new byte[]{(byte)b}; write(tmp,0,1); }\n" + + " public void write(byte[] b, int off, int len) throws IOException { if (closed) throw new IOException(); writeImpl(handle, b, off, len); }\n" + + " public void flush() throws IOException { if (closed) throw new IOException(); flushImpl(handle); }\n" + + " public void close() throws IOException { if (!closed) { closed = true; flushImpl(handle); closeImpl(handle); handle = 0; } }\n" + + " private static native long openImpl(String path, boolean append) throws IOException;\n" + + " private static native void writeImpl(long handle, byte[] b, int off, int len) throws IOException;\n" + + " private static native void flushImpl(long handle) throws IOException;\n" + + " private static native void closeImpl(long handle) throws IOException;\n" + + "}\n").getBytes(StandardCharsets.UTF_8)); + + Files.write(io.resolve("FileWriter.java"), ("package java.io;\n" + + "public class FileWriter {\n" + + " private final FileOutputStream out;\n" + + " public FileWriter(String name) throws IOException { this.out = new FileOutputStream(name); }\n" + + " public FileWriter(File file) throws IOException { this.out = new FileOutputStream(file); }\n" + + " public void write(String s) throws IOException { if (s == null) return; byte[] data = s.getBytes(); out.write(data, 0, data.length); }\n" + + " public void flush() throws IOException { out.flush(); }\n" + + " public void close() throws IOException { out.close(); }\n" + + "}\n").getBytes(StandardCharsets.UTF_8)); + // Minimal java.io.File to satisfy translator native headers Files.write(io.resolve("File.java"), ("package java.io;\n" + "public class File {\n" + diff --git a/vm/tests/src/test/java/com/codename1/tools/translator/StampedLockIntegrationTest.java b/vm/tests/src/test/java/com/codename1/tools/translator/StampedLockIntegrationTest.java index 4dca309763..f962d9d9f1 100644 --- a/vm/tests/src/test/java/com/codename1/tools/translator/StampedLockIntegrationTest.java +++ b/vm/tests/src/test/java/com/codename1/tools/translator/StampedLockIntegrationTest.java @@ -152,6 +152,9 @@ private void writeMockJavaClasses(Path sourceDir) throws Exception { " public final native Class getClass();\n" + "}\n").getBytes(StandardCharsets.UTF_8)); + // java.lang.AutoCloseable + Files.write(lang.resolve("AutoCloseable.java"), "package java.lang; public interface AutoCloseable { void close() throws java.io.IOException; }".getBytes(StandardCharsets.UTF_8)); + // java.lang.String Files.write(lang.resolve("String.java"), ("package java.lang;\n" + "public class String {\n" + @@ -286,6 +289,35 @@ private void writeMockJavaClasses(Path sourceDir) throws Exception { // java.io.Serializable Files.write(io.resolve("Serializable.java"), "package java.io; public interface Serializable {}".getBytes(StandardCharsets.UTF_8)); + // java.io.IOException + Files.write(io.resolve("IOException.java"), "package java.io; public class IOException extends Exception { public IOException() {} public IOException(String s) { super(s); } }".getBytes(StandardCharsets.UTF_8)); + + // java.io.InputStream + Files.write(io.resolve("InputStream.java"), ("package java.io;\n" + + "public class InputStream implements java.lang.AutoCloseable {\n" + + " public InputStream() {}\n" + + " public int available() throws IOException { return 0; }\n" + + " public int read() throws IOException { return -1; }\n" + + " public int read(byte[] b, int off, int len) throws IOException { return read(); }\n" + + " public long skip(long n) throws IOException { return 0; }\n" + + " public void close() throws IOException {}\n" + + " public synchronized void mark(int readlimit) {}\n" + + " public void reset() throws IOException { throw new IOException(); }\n" + + " public boolean markSupported() { return false; }\n" + + "}\n").getBytes(StandardCharsets.UTF_8)); + + // java.io.OutputStream + Files.write(io.resolve("OutputStream.java"), ("package java.io;\n" + + "public class OutputStream implements java.lang.AutoCloseable {\n" + + " public OutputStream() {}\n" + + " public void write(int b) throws IOException {}\n" + + " public void write(byte[] b, int off, int len) throws IOException {\n" + + " for (int i = 0; i < len; i++) { write(b[off + i]); }\n" + + " }\n" + + " public void flush() throws IOException {}\n" + + " public void close() throws IOException {}\n" + + "}\n").getBytes(StandardCharsets.UTF_8)); + // Minimal java.io.File to satisfy translator native headers Files.write(io.resolve("File.java"), ("package java.io;\n" + "public class File {\n" + @@ -341,29 +373,47 @@ private void writeMockJavaClasses(Path sourceDir) throws Exception { "}\n").getBytes(StandardCharsets.UTF_8)); Files.write(io.resolve("FileInputStream.java"), ("package java.io;\n" + - "public class FileInputStream {\n" + - " public FileInputStream(String name) { open(name); }\n" + - " public FileInputStream(File file) { open(file); }\n" + - " public native int read();\n" + - " public native int read(byte[] b, int off, int len);\n" + - " public native long skip(long n);\n" + - " public native int available();\n" + - " public native void close();\n" + - " private native void open(String name);\n" + - " private native void open(File file);\n" + + "public class FileInputStream extends InputStream {\n" + + " private long handle;\n" + + " private boolean closed;\n" + + " public FileInputStream(String name) throws IOException { this(name == null ? null : new File(name)); }\n" + + " public FileInputStream(File file) throws IOException {\n" + + " if (file == null) throw new NullPointerException();\n" + + " this.handle = openImpl(file.getPath());\n" + + " if (this.handle == 0) throw new IOException();\n" + + " }\n" + + " public int read() throws IOException { byte[] b = new byte[1]; int c = read(b,0,1); return c <= 0 ? -1 : b[0] & 0xff; }\n" + + " public int read(byte[] b, int off, int len) throws IOException { if (closed) throw new IOException(); return readImpl(handle, b, off, len); }\n" + + " public long skip(long n) throws IOException { if (closed) throw new IOException(); return skipImpl(handle, n); }\n" + + " public int available() throws IOException { if (closed) throw new IOException(); return availableImpl(handle); }\n" + + " public void close() throws IOException { if (!closed) { closed = true; closeImpl(handle); handle = 0; } }\n" + + " private static native long openImpl(String path) throws IOException;\n" + + " private static native int readImpl(long handle, byte[] b, int off, int len) throws IOException;\n" + + " private static native long skipImpl(long handle, long n) throws IOException;\n" + + " private static native int availableImpl(long handle) throws IOException;\n" + + " private static native void closeImpl(long handle) throws IOException;\n" + "}\n").getBytes(StandardCharsets.UTF_8)); Files.write(io.resolve("FileOutputStream.java"), ("package java.io;\n" + - "public class FileOutputStream {\n" + - " public FileOutputStream(String name) { open(name, false); }\n" + - " public FileOutputStream(String name, boolean append) { open(name, append); }\n" + - " public FileOutputStream(File file) { open(file.getPath(), false); }\n" + - " public FileOutputStream(File file, boolean append) { open(file.getPath(), append); }\n" + - " public native void write(int b);\n" + - " public native void write(byte[] b, int off, int len);\n" + - " public native void flush();\n" + - " public native void close();\n" + - " private native void open(String name, boolean append);\n" + + "public class FileOutputStream extends OutputStream {\n" + + " private long handle;\n" + + " private boolean closed;\n" + + " public FileOutputStream(String name) throws IOException { this(name, false); }\n" + + " public FileOutputStream(String name, boolean append) throws IOException { this(name == null ? null : new File(name), append); }\n" + + " public FileOutputStream(File file) throws IOException { this(file, false); }\n" + + " public FileOutputStream(File file, boolean append) throws IOException {\n" + + " if (file == null) throw new NullPointerException();\n" + + " this.handle = openImpl(file.getPath(), append);\n" + + " if (this.handle == 0) throw new IOException();\n" + + " }\n" + + " public void write(int b) throws IOException { byte[] tmp = new byte[]{(byte)b}; write(tmp,0,1); }\n" + + " public void write(byte[] b, int off, int len) throws IOException { if (closed) throw new IOException(); writeImpl(handle, b, off, len); }\n" + + " public void flush() throws IOException { if (closed) throw new IOException(); flushImpl(handle); }\n" + + " public void close() throws IOException { if (!closed) { closed = true; flushImpl(handle); closeImpl(handle); handle = 0; } }\n" + + " private static native long openImpl(String path, boolean append) throws IOException;\n" + + " private static native void writeImpl(long handle, byte[] b, int off, int len) throws IOException;\n" + + " private static native void flushImpl(long handle) throws IOException;\n" + + " private static native void closeImpl(long handle) throws IOException;\n" + "}\n").getBytes(StandardCharsets.UTF_8)); Files.write(io.resolve("FileWriter.java"), ("package java.io;\n" + From 6580285f1bee4309d1e50505db9d131efacc0412 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Sat, 27 Dec 2025 21:01:45 +0200 Subject: [PATCH 18/40] Add String valueOf overloads to clean translator stubs --- .../tools/translator/ReadWriteLockIntegrationTest.java | 6 ++++++ .../tools/translator/StampedLockIntegrationTest.java | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/vm/tests/src/test/java/com/codename1/tools/translator/ReadWriteLockIntegrationTest.java b/vm/tests/src/test/java/com/codename1/tools/translator/ReadWriteLockIntegrationTest.java index 184da83df3..c4291f38eb 100644 --- a/vm/tests/src/test/java/com/codename1/tools/translator/ReadWriteLockIntegrationTest.java +++ b/vm/tests/src/test/java/com/codename1/tools/translator/ReadWriteLockIntegrationTest.java @@ -155,6 +155,12 @@ private void writeMockJavaClasses(Path sourceDir) throws Exception { " public String(char[] v) { value = v; count=v.length; }\n" + " public String(char[] v, int off, int len) { value = v; offset = off; count = len; }\n" + " public static String valueOf(Object obj) { return obj == null ? \"null\" : obj.toString(); }\n" + + " public static String valueOf(int i) { return new String(new char[0]); }\n" + + " public static String valueOf(long i) { return new String(new char[0]); }\n" + + " public static String valueOf(boolean b) { return new String(new char[0]); }\n" + + " public static String valueOf(char c) { return new String(new char[]{c}); }\n" + + " public static String valueOf(float f) { return new String(new char[0]); }\n" + + " public static String valueOf(double d) { return new String(new char[0]); }\n" + " public byte[] getBytes() { return new byte[0]; }\n" + " public byte[] getBytes(String charset) { return new byte[0]; }\n" + " public void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin) {\n" + diff --git a/vm/tests/src/test/java/com/codename1/tools/translator/StampedLockIntegrationTest.java b/vm/tests/src/test/java/com/codename1/tools/translator/StampedLockIntegrationTest.java index f962d9d9f1..9e0f13e1ee 100644 --- a/vm/tests/src/test/java/com/codename1/tools/translator/StampedLockIntegrationTest.java +++ b/vm/tests/src/test/java/com/codename1/tools/translator/StampedLockIntegrationTest.java @@ -164,6 +164,12 @@ private void writeMockJavaClasses(Path sourceDir) throws Exception { " public String(char[] v) { value = v; count=v.length; }\n" + " public String(char[] v, int off, int len) { value = v; offset = off; count = len; }\n" + " public static String valueOf(Object obj) { return obj == null ? \"null\" : obj.toString(); }\n" + + " public static String valueOf(int i) { return new String(new char[0]); }\n" + + " public static String valueOf(long i) { return new String(new char[0]); }\n" + + " public static String valueOf(boolean b) { return new String(new char[0]); }\n" + + " public static String valueOf(char c) { return new String(new char[]{c}); }\n" + + " public static String valueOf(float f) { return new String(new char[0]); }\n" + + " public static String valueOf(double d) { return new String(new char[0]); }\n" + " public byte[] getBytes() { return new byte[0]; }\n" + " public byte[] getBytes(String charset) { return new byte[0]; }\n" + " public void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin) {\n" + From e1c1611313e6b029d46aaa65d3a1fddf1bad2594 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Sun, 28 Dec 2025 03:47:48 +0200 Subject: [PATCH 19/40] Patch HashMap stubs in ReadWriteLock integration --- .../ReadWriteLockIntegrationTest.java | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/vm/tests/src/test/java/com/codename1/tools/translator/ReadWriteLockIntegrationTest.java b/vm/tests/src/test/java/com/codename1/tools/translator/ReadWriteLockIntegrationTest.java index c4291f38eb..b7cae4d2ce 100644 --- a/vm/tests/src/test/java/com/codename1/tools/translator/ReadWriteLockIntegrationTest.java +++ b/vm/tests/src/test/java/com/codename1/tools/translator/ReadWriteLockIntegrationTest.java @@ -93,6 +93,7 @@ void verifiesReadWriteLockBehavior(CompilerHelper.CompilerConfig config) throws Path srcRoot = distDir.resolve("ReadWriteLockTestApp-src"); CleanTargetIntegrationTest.patchCn1Globals(srcRoot); CleanTargetIntegrationTest.patchFileHeader(srcRoot); + patchHashMapNativeSupport(srcRoot); writeRuntimeStubs(srcRoot); replaceLibraryWithExecutableTarget(cmakeLists, srcRoot.getFileName().toString()); @@ -836,4 +837,57 @@ private void writeRuntimeStubs(Path srcRoot) throws java.io.IOException { Files.write(stubs, content.getBytes(StandardCharsets.UTF_8)); } + + private void patchHashMapNativeSupport(Path srcRoot) throws java.io.IOException { + Path hashMapHeader = srcRoot.resolve("java_util_HashMap.h"); + if (Files.exists(hashMapHeader)) { + String content = new String(Files.readAllBytes(hashMapHeader), StandardCharsets.UTF_8); + if (!content.contains("java_util_HashMap_elementData")) { + String structDef = "struct obj__java_util_HashMap {\n" + + " DEBUG_GC_VARIABLES\n" + + " struct clazz *__codenameOneParentClsReference;\n" + + " int __codenameOneReferenceCount;\n" + + " void* __codenameOneThreadData;\n" + + " int __codenameOneGcMark;\n" + + " void* __ownerThread;\n" + + " int __heapPosition;\n" + + " JAVA_OBJECT java_util_HashMap_elementData;\n" + + "};"; + content = content.replace("struct obj__java_util_HashMap {\n" + + " DEBUG_GC_VARIABLES\n" + + " struct clazz *__codenameOneParentClsReference;\n" + + " int __codenameOneReferenceCount;\n" + + " void* __codenameOneThreadData;\n" + + " int __codenameOneGcMark;\n" + + " void* __ownerThread;\n" + + " int __heapPosition;\n" + + "};", structDef); + Files.write(hashMapHeader, content.getBytes(StandardCharsets.UTF_8)); + } + } + + Path entryHeader = srcRoot.resolve("java_util_HashMap_Entry.h"); + if (!Files.exists(entryHeader)) { + String entryContent = "#ifndef __JAVA_UTIL_HASHMAP_ENTRY__\n" + + "#define __JAVA_UTIL_HASHMAP_ENTRY__\n\n" + + "#include \"cn1_globals.h\"\n" + + "#include \"java_lang_Object.h\"\n\n" + + "extern struct clazz class__java_util_HashMap_Entry;\n\n" + + "struct obj__java_util_HashMap_Entry {\n" + + " DEBUG_GC_VARIABLES\n" + + " struct clazz *__codenameOneParentClsReference;\n" + + " int __codenameOneReferenceCount;\n" + + " void* __codenameOneThreadData;\n" + + " int __codenameOneGcMark;\n" + + " void* __ownerThread;\n" + + " int __heapPosition;\n" + + " JAVA_OBJECT java_util_MapEntry_key;\n" + + " JAVA_OBJECT java_util_MapEntry_value;\n" + + " JAVA_INT java_util_HashMap_Entry_origKeyHash;\n" + + " JAVA_OBJECT java_util_HashMap_Entry_next;\n" + + "};\n\n" + + "#endif\n"; + Files.write(entryHeader, entryContent.getBytes(StandardCharsets.UTF_8)); + } + } } From 812511fca65a481927339d43b79dcefcafe1db02 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Sun, 28 Dec 2025 08:02:22 +0200 Subject: [PATCH 20/40] Add HashMap Entry stubs for clean translator builds --- .../ReadWriteLockIntegrationTest.java | 57 ++++++++++++------- .../StampedLockIntegrationTest.java | 39 +++++++++++-- 2 files changed, 71 insertions(+), 25 deletions(-) diff --git a/vm/tests/src/test/java/com/codename1/tools/translator/ReadWriteLockIntegrationTest.java b/vm/tests/src/test/java/com/codename1/tools/translator/ReadWriteLockIntegrationTest.java index b7cae4d2ce..89202f2dcd 100644 --- a/vm/tests/src/test/java/com/codename1/tools/translator/ReadWriteLockIntegrationTest.java +++ b/vm/tests/src/test/java/com/codename1/tools/translator/ReadWriteLockIntegrationTest.java @@ -467,38 +467,55 @@ private void writeMockJavaClasses(Path sourceDir) throws Exception { // java.util.HashMap Files.write(util.resolve("HashMap.java"), ("package java.util;\n" + "public class HashMap implements Map {\n" + - " private Object[] keys = new Object[16];\n" + - " private Object[] values = new Object[16];\n" + + " public static class Entry {\n" + + " public K key;\n" + + " public V value;\n" + + " public int hash;\n" + + " public int origKeyHash;\n" + + " public Entry next;\n" + + " public Entry(K key, V value, int hash, Entry next) {\n" + + " this.key = key;\n" + + " this.value = value;\n" + + " this.hash = hash;\n" + + " this.origKeyHash = hash;\n" + + " this.next = next;\n" + + " }\n" + + " }\n" + + " private Entry[] elementData = new Entry[16];\n" + " private int size = 0;\n" + " public V get(Object key) {\n" + - " for(int i=0; i= keys.length) return null;\n" + // overflow ignored for mock - " keys[size] = key;\n" + - " values[size] = value;\n" + + " elementData[idx] = new Entry(key, value, h, elementData[idx]);\n" + " size++;\n" + " return null;\n" + " }\n" + " public V remove(Object key) {\n" + - " for(int i=0; i {\n" + - " public HashMap() {}\n" + - " public V put(K key, V value) { return value; }\n" + - " public V get(Object key) { return null; }\n" + - " public int size() { return 0; }\n" + " public static class Entry {\n" + " public K key;\n" + " public V value;\n" + - " public Entry(K key, V value) { this.key = key; this.value = value; }\n" + + " public int hash;\n" + + " public int origKeyHash;\n" + + " public Entry next;\n" + + " public Entry(K key, V value, int hash, Entry next) {\n" + + " this.key = key;\n" + + " this.value = value;\n" + + " this.hash = hash;\n" + + " this.origKeyHash = hash;\n" + + " this.next = next;\n" + + " }\n" + + " }\n" + + " private Entry[] elementData = new Entry[16];\n" + + " private int size = 0;\n" + + " public HashMap() {}\n" + + " public V put(K key, V value) {\n" + + " int h = key == null ? 0 : key.hashCode();\n" + + " int idx = (h & 0x7fffffff) % elementData.length;\n" + + " Entry e = elementData[idx];\n" + + " while (e != null) {\n" + + " if (e.key == key) { V old = (V)e.value; e.value = value; return old; }\n" + + " e = e.next;\n" + + " }\n" + + " elementData[idx] = new Entry(key, value, h, elementData[idx]);\n" + + " size++;\n" + + " return null;\n" + + " }\n" + + " public V get(Object key) {\n" + + " int h = key == null ? 0 : key.hashCode();\n" + + " int idx = (h & 0x7fffffff) % elementData.length;\n" + + " for (Entry e = elementData[idx]; e != null; e = e.next) {\n" + + " if (e.key == key) return (V)e.value;\n" + + " }\n" + + " return null;\n" + " }\n" + + " public int size() { return size; }\n" + "}\n").getBytes(StandardCharsets.UTF_8)); // java.lang.Object From 0e417c999748829a60cb03fec6025cf785f71e5e Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Sun, 28 Dec 2025 08:53:52 +0200 Subject: [PATCH 21/40] Handle CMakeLists without Objective-C --- .../tools/translator/CleanTargetIntegrationTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vm/tests/src/test/java/com/codename1/tools/translator/CleanTargetIntegrationTest.java b/vm/tests/src/test/java/com/codename1/tools/translator/CleanTargetIntegrationTest.java index 133cabd324..aa5d945579 100644 --- a/vm/tests/src/test/java/com/codename1/tools/translator/CleanTargetIntegrationTest.java +++ b/vm/tests/src/test/java/com/codename1/tools/translator/CleanTargetIntegrationTest.java @@ -183,8 +183,8 @@ static void replaceLibraryWithExecutableTarget(Path cmakeLists, String sourceDir String globWithObjc = String.format("file(GLOB TRANSLATOR_SOURCES \"%s/*.c\" \"%s/*.m\")", sourceDirName, sourceDirName); String globCOnly = String.format("file(GLOB TRANSLATOR_SOURCES \"%s/*.c\")", sourceDirName); content = content.replace(globWithObjc, globCOnly); - content = content.replace(" LANGUAGES C OBJC", " LANGUAGES C"); - content = content.replace("enable_language(OBJC OPTIONAL)\n", ""); + content = content.replaceAll("LANGUAGES\\s+C\\s+OBJC", "LANGUAGES C"); + content = content.replaceAll("(?m)^enable_language\\(OBJC OPTIONAL\\)\\s*$\\n?", ""); String replacement = content.replace( "add_library(${PROJECT_NAME} ${TRANSLATOR_SOURCES} ${TRANSLATOR_HEADERS})", "add_executable(${PROJECT_NAME} ${TRANSLATOR_SOURCES} ${TRANSLATOR_HEADERS})\ntarget_link_libraries(${PROJECT_NAME} m pthread)" From 11f10f02bad77e8fe0f0c27730318fd851adc35a Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Sun, 28 Dec 2025 09:30:56 +0200 Subject: [PATCH 22/40] Add clean target stubs for read/write lock integration --- .../translator/FileClassIntegrationTest.java | 2 + .../FileStreamsIntegrationTest.java | 2 + .../tools/translator/LockIntegrationTest.java | 2 + .../ReadWriteLockIntegrationTest.java | 92 +++++++++++++++++-- 4 files changed, 88 insertions(+), 10 deletions(-) diff --git a/vm/tests/src/test/java/com/codename1/tools/translator/FileClassIntegrationTest.java b/vm/tests/src/test/java/com/codename1/tools/translator/FileClassIntegrationTest.java index 68cd74adde..f1c0ae4899 100644 --- a/vm/tests/src/test/java/com/codename1/tools/translator/FileClassIntegrationTest.java +++ b/vm/tests/src/test/java/com/codename1/tools/translator/FileClassIntegrationTest.java @@ -133,6 +133,8 @@ private String fileTestAppSource() { private void replaceLibraryWithExecutableTarget(Path cmakeLists, String sourceDirName) throws IOException { String content = new String(Files.readAllBytes(cmakeLists), StandardCharsets.UTF_8); + content = content.replaceAll("LANGUAGES\\s+C\\s+OBJC", "LANGUAGES C"); + content = content.replaceAll("(?m)^enable_language\\(OBJC OPTIONAL\\)\\s*$\\n?", ""); String replacement = content.replace( "add_library(${PROJECT_NAME} ${TRANSLATOR_SOURCES} ${TRANSLATOR_HEADERS})", "add_executable(${PROJECT_NAME} ${TRANSLATOR_SOURCES} ${TRANSLATOR_HEADERS})\ntarget_link_libraries(${PROJECT_NAME} m)" diff --git a/vm/tests/src/test/java/com/codename1/tools/translator/FileStreamsIntegrationTest.java b/vm/tests/src/test/java/com/codename1/tools/translator/FileStreamsIntegrationTest.java index e6d3b3299d..79a8f648a6 100644 --- a/vm/tests/src/test/java/com/codename1/tools/translator/FileStreamsIntegrationTest.java +++ b/vm/tests/src/test/java/com/codename1/tools/translator/FileStreamsIntegrationTest.java @@ -140,6 +140,8 @@ private String fileStreamAppSource() { private void replaceLibraryWithExecutableTarget(Path cmakeLists) throws IOException { String content = new String(Files.readAllBytes(cmakeLists), StandardCharsets.UTF_8); + content = content.replaceAll("LANGUAGES\\s+C\\s+OBJC", "LANGUAGES C"); + content = content.replaceAll("(?m)^enable_language\\(OBJC OPTIONAL\\)\\s*$\\n?", ""); String replacement = content.replace( "add_library(${PROJECT_NAME} ${TRANSLATOR_SOURCES} ${TRANSLATOR_HEADERS})", "add_executable(${PROJECT_NAME} ${TRANSLATOR_SOURCES} ${TRANSLATOR_HEADERS})\ntarget_link_libraries(${PROJECT_NAME} m)" diff --git a/vm/tests/src/test/java/com/codename1/tools/translator/LockIntegrationTest.java b/vm/tests/src/test/java/com/codename1/tools/translator/LockIntegrationTest.java index 6235fe48c7..bf997ba4ad 100644 --- a/vm/tests/src/test/java/com/codename1/tools/translator/LockIntegrationTest.java +++ b/vm/tests/src/test/java/com/codename1/tools/translator/LockIntegrationTest.java @@ -330,6 +330,8 @@ static void replaceLibraryWithExecutableTarget(Path cmakeLists, String sourceDir String globWithObjc = String.format("file(GLOB TRANSLATOR_SOURCES \"%s/*.c\" \"%s/*.m\")", sourceDirName, sourceDirName); String globCOnly = String.format("file(GLOB TRANSLATOR_SOURCES \"%s/*.c\")", sourceDirName); content = content.replace(globWithObjc, globCOnly); + content = content.replaceAll("LANGUAGES\\s+C\\s+OBJC", "LANGUAGES C"); + content = content.replaceAll("(?m)^enable_language\\(OBJC OPTIONAL\\)\\s*$\\n?", ""); String replacement = content.replace( "add_library(${PROJECT_NAME} ${TRANSLATOR_SOURCES} ${TRANSLATOR_HEADERS})", "add_executable(${PROJECT_NAME} ${TRANSLATOR_SOURCES} ${TRANSLATOR_HEADERS})\ntarget_link_libraries(${PROJECT_NAME} m pthread)" diff --git a/vm/tests/src/test/java/com/codename1/tools/translator/ReadWriteLockIntegrationTest.java b/vm/tests/src/test/java/com/codename1/tools/translator/ReadWriteLockIntegrationTest.java index 89202f2dcd..2dfd77f7ab 100644 --- a/vm/tests/src/test/java/com/codename1/tools/translator/ReadWriteLockIntegrationTest.java +++ b/vm/tests/src/test/java/com/codename1/tools/translator/ReadWriteLockIntegrationTest.java @@ -199,9 +199,19 @@ private void writeMockJavaClasses(Path sourceDir) throws Exception { // java.lang.StringBuilder Files.write(lang.resolve("StringBuilder.java"), ("package java.lang;\n" + "public class StringBuilder {\n" + + " char[] value = new char[16];\n" + + " int count = 0;\n" + " public StringBuilder() {}\n" + " public StringBuilder(String str) {}\n" + " public StringBuilder(int cap) {}\n" + + " void appendNull() {\n" + + " append(\"null\");\n" + + " }\n" + + " void enlargeBuffer(int newCap) {\n" + + " if (newCap > value.length) {\n" + + " value = new char[newCap];\n" + + " }\n" + + " }\n" + " public StringBuilder append(String s) { return this; }\n" + " public StringBuilder append(Object o) { return this; }\n" + " public StringBuilder append(int i) { return this; }\n" + @@ -632,6 +642,8 @@ static void replaceLibraryWithExecutableTarget(Path cmakeLists, String sourceDir String globWithObjc = String.format("file(GLOB TRANSLATOR_SOURCES \"%s/*.c\" \"%s/*.m\")", sourceDirName, sourceDirName); String globCOnly = String.format("file(GLOB TRANSLATOR_SOURCES \"%s/*.c\")", sourceDirName); content = content.replace(globWithObjc, globCOnly); + content = content.replaceAll("LANGUAGES\\s+C\\s+OBJC", "LANGUAGES C"); + content = content.replaceAll("(?m)^enable_language\\(OBJC OPTIONAL\\)\\s*$\\n?", ""); String replacement = content.replace( "add_library(${PROJECT_NAME} ${TRANSLATOR_SOURCES} ${TRANSLATOR_HEADERS})", "add_executable(${PROJECT_NAME} ${TRANSLATOR_SOURCES} ${TRANSLATOR_HEADERS})\ntarget_link_libraries(${PROJECT_NAME} m pthread)" @@ -884,13 +896,77 @@ private void patchHashMapNativeSupport(Path srcRoot) throws java.io.IOException } Path entryHeader = srcRoot.resolve("java_util_HashMap_Entry.h"); - if (!Files.exists(entryHeader)) { - String entryContent = "#ifndef __JAVA_UTIL_HASHMAP_ENTRY__\n" + - "#define __JAVA_UTIL_HASHMAP_ENTRY__\n\n" + + String entryContent = "#ifndef __JAVA_UTIL_HASHMAP_ENTRY__\n" + + "#define __JAVA_UTIL_HASHMAP_ENTRY__\n\n" + + "#include \"cn1_globals.h\"\n" + + "#include \"java_lang_Object.h\"\n\n" + + "extern struct clazz class__java_util_HashMap_Entry;\n\n" + + "struct obj__java_util_HashMap_Entry {\n" + + " DEBUG_GC_VARIABLES\n" + + " struct clazz *__codenameOneParentClsReference;\n" + + " int __codenameOneReferenceCount;\n" + + " void* __codenameOneThreadData;\n" + + " int __codenameOneGcMark;\n" + + " void* __ownerThread;\n" + + " int __heapPosition;\n" + + " JAVA_OBJECT java_util_MapEntry_key;\n" + + " JAVA_OBJECT java_util_MapEntry_value;\n" + + " JAVA_INT java_util_HashMap_Entry_origKeyHash;\n" + + " JAVA_OBJECT java_util_HashMap_Entry_next;\n" + + "};\n\n" + + "#endif\n"; + Files.write(entryHeader, entryContent.getBytes(StandardCharsets.UTF_8)); + + Path dateHeader = srcRoot.resolve("java_util_Date.h"); + if (!Files.exists(dateHeader)) { + String dateContent = "#ifndef __JAVA_UTIL_DATE__\n" + + "#define __JAVA_UTIL_DATE__\n\n" + + "#include \"cn1_globals.h\"\n" + + "#include \"java_lang_Object.h\"\n\n" + + "extern struct clazz class__java_util_Date;\n\n" + + "struct obj__java_util_Date {\n" + + " DEBUG_GC_VARIABLES\n" + + " struct clazz *__codenameOneParentClsReference;\n" + + " int __codenameOneReferenceCount;\n" + + " void* __codenameOneThreadData;\n" + + " int __codenameOneGcMark;\n" + + " void* __ownerThread;\n" + + " int __heapPosition;\n" + + " JAVA_LONG java_util_Date_date;\n" + + "};\n\n" + + "#endif\n"; + Files.write(dateHeader, dateContent.getBytes(StandardCharsets.UTF_8)); + } + + Path dateFormatHeader = srcRoot.resolve("java_text_DateFormat.h"); + if (!Files.exists(dateFormatHeader)) { + String dateFormatContent = "#ifndef __JAVA_TEXT_DATEFORMAT__\n" + + "#define __JAVA_TEXT_DATEFORMAT__\n\n" + + "#include \"cn1_globals.h\"\n" + + "#include \"java_lang_Object.h\"\n\n" + + "extern struct clazz class__java_text_DateFormat;\n\n" + + "struct obj__java_text_DateFormat {\n" + + " DEBUG_GC_VARIABLES\n" + + " struct clazz *__codenameOneParentClsReference;\n" + + " int __codenameOneReferenceCount;\n" + + " void* __codenameOneThreadData;\n" + + " int __codenameOneGcMark;\n" + + " void* __ownerThread;\n" + + " int __heapPosition;\n" + + " JAVA_INT java_text_DateFormat_dateStyle;\n" + + "};\n\n" + + "#endif\n"; + Files.write(dateFormatHeader, dateFormatContent.getBytes(StandardCharsets.UTF_8)); + } + + Path stringToRealHeader = srcRoot.resolve("java_lang_StringToReal.h"); + if (!Files.exists(stringToRealHeader)) { + String strContent = "#ifndef __JAVA_LANG_STRINGTOREAL__\n" + + "#define __JAVA_LANG_STRINGTOREAL__\n\n" + "#include \"cn1_globals.h\"\n" + "#include \"java_lang_Object.h\"\n\n" + - "extern struct clazz class__java_util_HashMap_Entry;\n\n" + - "struct obj__java_util_HashMap_Entry {\n" + + "extern struct clazz class__java_lang_StringToReal;\n\n" + + "struct obj__java_lang_StringToReal {\n" + " DEBUG_GC_VARIABLES\n" + " struct clazz *__codenameOneParentClsReference;\n" + " int __codenameOneReferenceCount;\n" + @@ -898,13 +974,9 @@ private void patchHashMapNativeSupport(Path srcRoot) throws java.io.IOException " int __codenameOneGcMark;\n" + " void* __ownerThread;\n" + " int __heapPosition;\n" + - " JAVA_OBJECT java_util_MapEntry_key;\n" + - " JAVA_OBJECT java_util_MapEntry_value;\n" + - " JAVA_INT java_util_HashMap_Entry_origKeyHash;\n" + - " JAVA_OBJECT java_util_HashMap_Entry_next;\n" + "};\n\n" + "#endif\n"; - Files.write(entryHeader, entryContent.getBytes(StandardCharsets.UTF_8)); + Files.write(stringToRealHeader, strContent.getBytes(StandardCharsets.UTF_8)); } } } From 03ff25a3beaf407d632f1b3c138bc4ee333e0a4d Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Sun, 28 Dec 2025 12:23:06 +0200 Subject: [PATCH 23/40] Simplify clean-target HashMap mocks --- .../ReadWriteLockIntegrationTest.java | 92 +++++----------- .../StampedLockIntegrationTest.java | 104 ++++++------------ 2 files changed, 64 insertions(+), 132 deletions(-) diff --git a/vm/tests/src/test/java/com/codename1/tools/translator/ReadWriteLockIntegrationTest.java b/vm/tests/src/test/java/com/codename1/tools/translator/ReadWriteLockIntegrationTest.java index 2dfd77f7ab..ea72093d73 100644 --- a/vm/tests/src/test/java/com/codename1/tools/translator/ReadWriteLockIntegrationTest.java +++ b/vm/tests/src/test/java/com/codename1/tools/translator/ReadWriteLockIntegrationTest.java @@ -491,36 +491,28 @@ private void writeMockJavaClasses(Path sourceDir) throws Exception { " this.next = next;\n" + " }\n" + " }\n" + - " private Entry[] elementData = new Entry[16];\n" + + " private Entry head;\n" + " private int size = 0;\n" + " public V get(Object key) {\n" + - " int h = key == null ? 0 : key.hashCode();\n" + - " int idx = (h & 0x7fffffff) % elementData.length;\n" + - " for (Entry e = elementData[idx]; e != null; e = e.next) {\n" + + " for (Entry e = head; e != null; e = e.next) {\n" + " if (e.key == key) return (V)e.value;\n" + " }\n" + " return null;\n" + " }\n" + " public V put(K key, V value) {\n" + - " int h = key == null ? 0 : key.hashCode();\n" + - " int idx = (h & 0x7fffffff) % elementData.length;\n" + - " Entry e = elementData[idx];\n" + - " while (e != null) {\n" + + " for (Entry e = head; e != null; e = e.next) {\n" + " if (e.key == key) { V old = (V)e.value; e.value = value; return old; }\n" + - " e = e.next;\n" + " }\n" + - " elementData[idx] = new Entry(key, value, h, elementData[idx]);\n" + + " head = new Entry(key, value, key == null ? 0 : key.hashCode(), head);\n" + " size++;\n" + " return null;\n" + " }\n" + " public V remove(Object key) {\n" + - " int h = key == null ? 0 : key.hashCode();\n" + - " int idx = (h & 0x7fffffff) % elementData.length;\n" + " Entry prev = null;\n" + - " Entry e = elementData[idx];\n" + + " Entry e = head;\n" + " while (e != null) {\n" + " if (e.key == key) {\n" + - " if (prev == null) elementData[idx] = e.next; else prev.next = e.next;\n" + + " if (prev == null) head = e.next; else prev.next = e.next;\n" + " size--;\n" + " return (V)e.value;\n" + " }\n" + @@ -529,9 +521,8 @@ private void writeMockJavaClasses(Path sourceDir) throws Exception { " }\n" + " return null;\n" + " }\n" + + " public int size() { return size; }\n" + "}\n").getBytes(StandardCharsets.UTF_8)); - - // java.util.concurrent.TimeUnit Files.write(concurrent.resolve("TimeUnit.java"), ("package java.util.concurrent;\n" + "public class TimeUnit {\n" + @@ -868,54 +859,29 @@ private void writeRuntimeStubs(Path srcRoot) throws java.io.IOException { } private void patchHashMapNativeSupport(Path srcRoot) throws java.io.IOException { - Path hashMapHeader = srcRoot.resolve("java_util_HashMap.h"); - if (Files.exists(hashMapHeader)) { - String content = new String(Files.readAllBytes(hashMapHeader), StandardCharsets.UTF_8); - if (!content.contains("java_util_HashMap_elementData")) { - String structDef = "struct obj__java_util_HashMap {\n" + - " DEBUG_GC_VARIABLES\n" + - " struct clazz *__codenameOneParentClsReference;\n" + - " int __codenameOneReferenceCount;\n" + - " void* __codenameOneThreadData;\n" + - " int __codenameOneGcMark;\n" + - " void* __ownerThread;\n" + - " int __heapPosition;\n" + - " JAVA_OBJECT java_util_HashMap_elementData;\n" + - "};"; - content = content.replace("struct obj__java_util_HashMap {\n" + - " DEBUG_GC_VARIABLES\n" + - " struct clazz *__codenameOneParentClsReference;\n" + - " int __codenameOneReferenceCount;\n" + - " void* __codenameOneThreadData;\n" + - " int __codenameOneGcMark;\n" + - " void* __ownerThread;\n" + - " int __heapPosition;\n" + - "};", structDef); - Files.write(hashMapHeader, content.getBytes(StandardCharsets.UTF_8)); - } - } - Path entryHeader = srcRoot.resolve("java_util_HashMap_Entry.h"); - String entryContent = "#ifndef __JAVA_UTIL_HASHMAP_ENTRY__\n" + - "#define __JAVA_UTIL_HASHMAP_ENTRY__\n\n" + - "#include \"cn1_globals.h\"\n" + - "#include \"java_lang_Object.h\"\n\n" + - "extern struct clazz class__java_util_HashMap_Entry;\n\n" + - "struct obj__java_util_HashMap_Entry {\n" + - " DEBUG_GC_VARIABLES\n" + - " struct clazz *__codenameOneParentClsReference;\n" + - " int __codenameOneReferenceCount;\n" + - " void* __codenameOneThreadData;\n" + - " int __codenameOneGcMark;\n" + - " void* __ownerThread;\n" + - " int __heapPosition;\n" + - " JAVA_OBJECT java_util_MapEntry_key;\n" + - " JAVA_OBJECT java_util_MapEntry_value;\n" + - " JAVA_INT java_util_HashMap_Entry_origKeyHash;\n" + - " JAVA_OBJECT java_util_HashMap_Entry_next;\n" + - "};\n\n" + - "#endif\n"; - Files.write(entryHeader, entryContent.getBytes(StandardCharsets.UTF_8)); + if (!Files.exists(entryHeader)) { + String entryContent = "#ifndef __JAVA_UTIL_HASHMAP_ENTRY__\n" + + "#define __JAVA_UTIL_HASHMAP_ENTRY__\n\n" + + "#include \"cn1_globals.h\"\n" + + "#include \"java_lang_Object.h\"\n\n" + + "extern struct clazz class__java_util_HashMap_Entry;\n\n" + + "struct obj__java_util_HashMap_Entry {\n" + + " DEBUG_GC_VARIABLES\n" + + " struct clazz *__codenameOneParentClsReference;\n" + + " int __codenameOneReferenceCount;\n" + + " void* __codenameOneThreadData;\n" + + " int __codenameOneGcMark;\n" + + " void* __ownerThread;\n" + + " int __heapPosition;\n" + + " JAVA_OBJECT java_util_MapEntry_key;\n" + + " JAVA_OBJECT java_util_MapEntry_value;\n" + + " JAVA_INT java_util_HashMap_Entry_origKeyHash;\n" + + " JAVA_OBJECT java_util_HashMap_Entry_next;\n" + + "};\n\n" + + "#endif\n"; + Files.write(entryHeader, entryContent.getBytes(StandardCharsets.UTF_8)); + } Path dateHeader = srcRoot.resolve("java_util_Date.h"); if (!Files.exists(dateHeader)) { diff --git a/vm/tests/src/test/java/com/codename1/tools/translator/StampedLockIntegrationTest.java b/vm/tests/src/test/java/com/codename1/tools/translator/StampedLockIntegrationTest.java index f7ccc4f7e9..6e6be72b26 100644 --- a/vm/tests/src/test/java/com/codename1/tools/translator/StampedLockIntegrationTest.java +++ b/vm/tests/src/test/java/com/codename1/tools/translator/StampedLockIntegrationTest.java @@ -125,48 +125,41 @@ private void writeMockJavaClasses(Path sourceDir) throws Exception { Files.createDirectories(io); // java.util.HashMap (needed for generated native stubs) - Files.write(util.resolve("HashMap.java"), ("package java.util;\n" + - "public class HashMap {\n" + - " public static class Entry {\n" + - " public K key;\n" + - " public V value;\n" + - " public int hash;\n" + - " public int origKeyHash;\n" + - " public Entry next;\n" + - " public Entry(K key, V value, int hash, Entry next) {\n" + - " this.key = key;\n" + - " this.value = value;\n" + - " this.hash = hash;\n" + - " this.origKeyHash = hash;\n" + - " this.next = next;\n" + - " }\n" + - " }\n" + - " private Entry[] elementData = new Entry[16];\n" + - " private int size = 0;\n" + - " public HashMap() {}\n" + - " public V put(K key, V value) {\n" + - " int h = key == null ? 0 : key.hashCode();\n" + - " int idx = (h & 0x7fffffff) % elementData.length;\n" + - " Entry e = elementData[idx];\n" + - " while (e != null) {\n" + - " if (e.key == key) { V old = (V)e.value; e.value = value; return old; }\n" + - " e = e.next;\n" + - " }\n" + - " elementData[idx] = new Entry(key, value, h, elementData[idx]);\n" + - " size++;\n" + - " return null;\n" + - " }\n" + - " public V get(Object key) {\n" + - " int h = key == null ? 0 : key.hashCode();\n" + - " int idx = (h & 0x7fffffff) % elementData.length;\n" + - " for (Entry e = elementData[idx]; e != null; e = e.next) {\n" + - " if (e.key == key) return (V)e.value;\n" + - " }\n" + - " return null;\n" + - " }\n" + - " public int size() { return size; }\n" + - "}\n").getBytes(StandardCharsets.UTF_8)); - + Files.write(util.resolve(\"HashMap.java\"), (\"package java.util;\\n\" + + \"public class HashMap {\\n\" + + \" public static class Entry {\\n\" + + \" public K key;\\n\" + + \" public V value;\\n\" + + \" public int hash;\\n\" + + \" public int origKeyHash;\\n\" + + \" public Entry next;\\n\" + + \" public Entry(K key, V value, int hash, Entry next) {\\n\" + + \" this.key = key;\\n\" + + \" this.value = value;\\n\" + + \" this.hash = hash;\\n\" + + \" this.origKeyHash = hash;\\n\" + + \" this.next = next;\\n\" + + \" }\\n\" + + \" }\\n\" + + \" private Entry head;\\n\" + + \" private int size = 0;\\n\" + + \" public HashMap() {}\\n\" + + \" public V put(K key, V value) {\\n\" + + \" for (Entry e = head; e != null; e = e.next) {\\n\" + + \" if (e.key == key) { V old = (V)e.value; e.value = value; return old; }\\n\" + + \" }\\n\" + + \" head = new Entry(key, value, key == null ? 0 : key.hashCode(), head);\\n\" + + \" size++;\\n\" + + \" return null;\\n\" + + \" }\\n\" + + \" public V get(Object key) {\\n\" + + \" for (Entry e = head; e != null; e = e.next) {\\n\" + + \" if (e.key == key) return (V)e.value;\\n\" + + \" }\\n\" + + \" return null;\\n\" + + \" }\\n\" + + \" public int size() { return size; }\\n\" + + \"}\\n\").getBytes(StandardCharsets.UTF_8)); // java.lang.Object Files.write(lang.resolve("Object.java"), ("package java.lang;\n" + "public class Object {\n" + @@ -809,33 +802,6 @@ private void writeRuntimeStubs(Path srcRoot) throws java.io.IOException { } private void patchHashMapNativeSupport(Path srcRoot) throws java.io.IOException { - Path hashMapHeader = srcRoot.resolve("java_util_HashMap.h"); - if (Files.exists(hashMapHeader)) { - String content = new String(Files.readAllBytes(hashMapHeader), StandardCharsets.UTF_8); - if (!content.contains("java_util_HashMap_elementData")) { - String structDef = "struct obj__java_util_HashMap {\n" + - " DEBUG_GC_VARIABLES\n" + - " struct clazz *__codenameOneParentClsReference;\n" + - " int __codenameOneReferenceCount;\n" + - " void* __codenameOneThreadData;\n" + - " int __codenameOneGcMark;\n" + - " void* __ownerThread;\n" + - " int __heapPosition;\n" + - " JAVA_OBJECT java_util_HashMap_elementData;\n" + - "};"; - content = content.replace("struct obj__java_util_HashMap {\n" + - " DEBUG_GC_VARIABLES\n" + - " struct clazz *__codenameOneParentClsReference;\n" + - " int __codenameOneReferenceCount;\n" + - " void* __codenameOneThreadData;\n" + - " int __codenameOneGcMark;\n" + - " void* __ownerThread;\n" + - " int __heapPosition;\n" + - "};", structDef); - Files.write(hashMapHeader, content.getBytes(StandardCharsets.UTF_8)); - } - } - Path entryHeader = srcRoot.resolve("java_util_HashMap_Entry.h"); if (!Files.exists(entryHeader)) { String entryContent = "#ifndef __JAVA_UTIL_HASHMAP_ENTRY__\n" + From c038e84fb3042c86907b8c22f16c8afd35f5bb00 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Sun, 28 Dec 2025 12:37:20 +0200 Subject: [PATCH 24/40] Fix mock HashMap source in StampedLock integration test --- .../StampedLockIntegrationTest.java | 92 ++++++++++++------- 1 file changed, 57 insertions(+), 35 deletions(-) diff --git a/vm/tests/src/test/java/com/codename1/tools/translator/StampedLockIntegrationTest.java b/vm/tests/src/test/java/com/codename1/tools/translator/StampedLockIntegrationTest.java index 6e6be72b26..55bebe22ea 100644 --- a/vm/tests/src/test/java/com/codename1/tools/translator/StampedLockIntegrationTest.java +++ b/vm/tests/src/test/java/com/codename1/tools/translator/StampedLockIntegrationTest.java @@ -124,42 +124,64 @@ private void writeMockJavaClasses(Path sourceDir) throws Exception { Files.createDirectories(concurrent); Files.createDirectories(io); + // java.util.Map + Files.write(util.resolve("Map.java"), ("package java.util;\n" + + "public interface Map {\n" + + " V get(Object key);\n" + + " V put(K key, V value);\n" + + " V remove(Object key);\n" + + "}\n").getBytes(StandardCharsets.UTF_8)); + // java.util.HashMap (needed for generated native stubs) - Files.write(util.resolve(\"HashMap.java\"), (\"package java.util;\\n\" + - \"public class HashMap {\\n\" + - \" public static class Entry {\\n\" + - \" public K key;\\n\" + - \" public V value;\\n\" + - \" public int hash;\\n\" + - \" public int origKeyHash;\\n\" + - \" public Entry next;\\n\" + - \" public Entry(K key, V value, int hash, Entry next) {\\n\" + - \" this.key = key;\\n\" + - \" this.value = value;\\n\" + - \" this.hash = hash;\\n\" + - \" this.origKeyHash = hash;\\n\" + - \" this.next = next;\\n\" + - \" }\\n\" + - \" }\\n\" + - \" private Entry head;\\n\" + - \" private int size = 0;\\n\" + - \" public HashMap() {}\\n\" + - \" public V put(K key, V value) {\\n\" + - \" for (Entry e = head; e != null; e = e.next) {\\n\" + - \" if (e.key == key) { V old = (V)e.value; e.value = value; return old; }\\n\" + - \" }\\n\" + - \" head = new Entry(key, value, key == null ? 0 : key.hashCode(), head);\\n\" + - \" size++;\\n\" + - \" return null;\\n\" + - \" }\\n\" + - \" public V get(Object key) {\\n\" + - \" for (Entry e = head; e != null; e = e.next) {\\n\" + - \" if (e.key == key) return (V)e.value;\\n\" + - \" }\\n\" + - \" return null;\\n\" + - \" }\\n\" + - \" public int size() { return size; }\\n\" + - \"}\\n\").getBytes(StandardCharsets.UTF_8)); + Files.write(util.resolve("HashMap.java"), ("package java.util;\n" + + "public class HashMap implements Map {\n" + + " public static class Entry {\n" + + " public K key;\n" + + " public V value;\n" + + " public int hash;\n" + + " public int origKeyHash;\n" + + " public Entry next;\n" + + " public Entry(K key, V value, int hash, Entry next) {\n" + + " this.key = key;\n" + + " this.value = value;\n" + + " this.hash = hash;\n" + + " this.origKeyHash = hash;\n" + + " this.next = next;\n" + + " }\n" + + " }\n" + + " private Entry head;\n" + + " private int size = 0;\n" + + " public HashMap() {}\n" + + " public V get(Object key) {\n" + + " for (Entry e = head; e != null; e = e.next) {\n" + + " if (e.key == key) return (V)e.value;\n" + + " }\n" + + " return null;\n" + + " }\n" + + " public V put(K key, V value) {\n" + + " for (Entry e = head; e != null; e = e.next) {\n" + + " if (e.key == key) { V old = (V)e.value; e.value = value; return old; }\n" + + " }\n" + + " head = new Entry(key, value, key == null ? 0 : key.hashCode(), head);\n" + + " size++;\n" + + " return null;\n" + + " }\n" + + " public V remove(Object key) {\n" + + " Entry prev = null;\n" + + " Entry e = head;\n" + + " while (e != null) {\n" + + " if (e.key == key) {\n" + + " if (prev == null) head = e.next; else prev.next = e.next;\n" + + " size--;\n" + + " return (V)e.value;\n" + + " }\n" + + " prev = e;\n" + + " e = e.next;\n" + + " }\n" + + " return null;\n" + + " }\n" + + " public int size() { return size; }\n" + + "}\n").getBytes(StandardCharsets.UTF_8)); // java.lang.Object Files.write(lang.resolve("Object.java"), ("package java.lang;\n" + "public class Object {\n" + From 20496ea28a19ec42432703315f7409a0d8cfabdc Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Sun, 28 Dec 2025 12:59:38 +0200 Subject: [PATCH 25/40] Tighten clean target runtime stubs for integration tests --- .../ReadWriteLockIntegrationTest.java | 297 +++++------------- .../StampedLockIntegrationTest.java | 293 +++++------------ 2 files changed, 173 insertions(+), 417 deletions(-) diff --git a/vm/tests/src/test/java/com/codename1/tools/translator/ReadWriteLockIntegrationTest.java b/vm/tests/src/test/java/com/codename1/tools/translator/ReadWriteLockIntegrationTest.java index ea72093d73..7106370316 100644 --- a/vm/tests/src/test/java/com/codename1/tools/translator/ReadWriteLockIntegrationTest.java +++ b/vm/tests/src/test/java/com/codename1/tools/translator/ReadWriteLockIntegrationTest.java @@ -10,6 +10,8 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -94,6 +96,7 @@ void verifiesReadWriteLockBehavior(CompilerHelper.CompilerConfig config) throws CleanTargetIntegrationTest.patchCn1Globals(srcRoot); CleanTargetIntegrationTest.patchFileHeader(srcRoot); patchHashMapNativeSupport(srcRoot); + patchHashMapNativeMethods(srcRoot); writeRuntimeStubs(srcRoot); replaceLibraryWithExecutableTarget(cmakeLists, srcRoot.getFileName().toString()); @@ -643,221 +646,28 @@ static void replaceLibraryWithExecutableTarget(Path cmakeLists, String sourceDir } private void writeRuntimeStubs(Path srcRoot) throws java.io.IOException { - // Reuse the stubs from LockIntegrationTest Path stubs = srcRoot.resolve("runtime_stubs.c"); - // ... (Same content as LockIntegrationTest.java's writeRuntimeStubs) - // Since I cannot call private method from another class, I'll copy-paste it here or use reflection? - // Copy-paste is safer and standard for this kind of "self-contained" test generator. - - String content = "#include \"cn1_globals.h\"\n" + - "#include \"java_lang_Object.h\"\n" + - "#include \n" + - "#include \n" + - "#include \n" + - "#include \n" + - "#include \n" + - "#include \n" + - "#include \n" + - "\n" + - "static pthread_mutexattr_t mtx_attr;\n" + - "void __attribute__((constructor)) init_debug() {\n" + - " setbuf(stdout, NULL);\n" + - " setbuf(stderr, NULL);\n" + - " pthread_mutexattr_init(&mtx_attr);\n" + - " pthread_mutexattr_settype(&mtx_attr, PTHREAD_MUTEX_RECURSIVE);\n" + - "}\n" + - "\n" + - "static pthread_key_t thread_state_key;\n" + - "static pthread_key_t current_thread_key;\n" + - "static pthread_once_t key_once = PTHREAD_ONCE_INIT;\n" + - "\n" + - "static void make_key() {\n" + - " pthread_key_create(&thread_state_key, free);\n" + - " pthread_key_create(¤t_thread_key, NULL);\n" + - "}\n" + - "\n" + - "struct ThreadLocalData* getThreadLocalData() {\n" + - " pthread_once(&key_once, make_key);\n" + - " struct ThreadLocalData* data = pthread_getspecific(thread_state_key);\n" + - " if (!data) {\n" + - " data = calloc(1, sizeof(struct ThreadLocalData));\n" + - " data->blocks = calloc(100, sizeof(struct TryBlock));\n" + - " data->threadObjectStack = calloc(100, sizeof(struct elementStruct));\n" + - " data->pendingHeapAllocations = calloc(100, sizeof(void*));\n" + - " pthread_setspecific(thread_state_key, data);\n" + - " }\n" + - " return data;\n" + - "}\n" + - "\n" + - "// Monitor implementation\n" + - "#define MAX_MONITORS 1024\n" + - "typedef struct {\n" + - " JAVA_OBJECT obj;\n" + - " pthread_mutex_t mutex;\n" + - " pthread_cond_t cond;\n" + - "} Monitor;\n" + - "static Monitor monitors[MAX_MONITORS];\n" + - "static pthread_mutex_t global_monitor_lock = PTHREAD_MUTEX_INITIALIZER;\n" + - "\n" + - "static Monitor* getMonitor(JAVA_OBJECT obj) {\n" + - " pthread_mutex_lock(&global_monitor_lock);\n" + - " for(int i=0; imutex);\n" + - "}\n" + - "\n" + - "void monitorExit(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT obj) {\n" + - " if (!obj) return;\n" + - " Monitor* m = getMonitor(obj);\n" + - " pthread_mutex_unlock(&m->mutex);\n" + - "}\n" + - "\n" + - "void java_lang_Object_wait___long_int(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT obj, JAVA_LONG timeout, JAVA_INT nanos) {\n" + - " Monitor* m = getMonitor(obj);\n" + - " if (timeout > 0 || nanos > 0) {\n" + - " struct timespec ts;\n" + - " struct timeval now;\n" + - " gettimeofday(&now, NULL);\n" + - " ts.tv_sec = now.tv_sec + timeout / 1000;\n" + - " ts.tv_nsec = now.tv_usec * 1000 + (timeout % 1000) * 1000000 + nanos;\n" + - " if (ts.tv_nsec >= 1000000000) {\n" + - " ts.tv_sec++;\n" + - " ts.tv_nsec -= 1000000000;\n" + - " }\n" + - " pthread_cond_timedwait(&m->cond, &m->mutex, &ts);\n" + - " } else {\n" + - " pthread_cond_wait(&m->cond, &m->mutex);\n" + - " }\n" + - "}\n" + - "\n" + - "void java_lang_Object_notify__(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT obj) {\n" + - " Monitor* m = getMonitor(obj);\n" + - " pthread_cond_signal(&m->cond);\n" + - "}\n" + - "\n" + - "void java_lang_Object_notifyAll__(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT obj) {\n" + - " Monitor* m = getMonitor(obj);\n" + - " pthread_cond_broadcast(&m->cond);\n" + - "}\n" + - "\n" + - "JAVA_OBJECT* constantPoolObjects = NULL;\n" + - "void initConstantPool() {\n" + - " if (constantPoolObjects == NULL) {\n" + - " constantPoolObjects = calloc(1024, sizeof(JAVA_OBJECT));\n" + - " }\n" + - "}\n" + - "JAVA_OBJECT codenameOneGcMalloc(CODENAME_ONE_THREAD_STATE, int size, struct clazz* parent) {\n" + - " JAVA_OBJECT obj = (JAVA_OBJECT)calloc(1, size);\n" + - " if (obj != JAVA_NULL) {\n" + - " obj->__codenameOneParentClsReference = parent;\n" + - " }\n" + - " return obj;\n" + - "}\n" + - "void codenameOneGcFree(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT obj) { free(obj); }\n" + - "void arrayFinalizerFunction(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT array) { free(array); }\n" + - "void gcMarkArrayObject(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT obj, JAVA_BOOLEAN force) {}\n" + - "void gcMarkObject(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT obj, JAVA_BOOLEAN force) {}\n" + - "void** initVtableForInterface() { static void* table[1]; return (void**)table; }\n" + - "struct clazz class_array1__JAVA_INT = {0};\n" + - "struct clazz class_array2__JAVA_INT = {0};\n" + - "struct clazz class_array1__JAVA_BOOLEAN = {0};\n" + - "struct clazz class_array1__JAVA_CHAR = {0};\n" + - "struct clazz class_array1__JAVA_FLOAT = {0};\n" + - "struct clazz class_array1__JAVA_DOUBLE = {0};\n" + - "struct clazz class_array1__JAVA_BYTE = {0};\n" + - "struct clazz class_array1__JAVA_SHORT = {0};\n" + - "struct clazz class_array1__JAVA_LONG = {0};\n" + - "void initMethodStack(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT __cn1ThisObject, int stackSize, int localsStackSize, int classNameId, int methodNameId) {}\n" + - "void releaseForReturn(CODENAME_ONE_THREAD_STATE, int cn1LocalsBeginInThread) {}\n" + - "void releaseForReturnInException(CODENAME_ONE_THREAD_STATE, int cn1LocalsBeginInThread, int methodBlockOffset) {}\n" + - "void monitorEnterBlock(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT obj) {}\n" + - "void monitorExitBlock(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT obj) {}\n" + - "struct elementStruct* pop(struct elementStruct** sp) { (*sp)--; return *sp; }\n" + - "void popMany(CODENAME_ONE_THREAD_STATE, int count, struct elementStruct** sp) { while(count--) (*sp)--; }\n" + - "void throwException(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT obj) { exit(1); }\n" + - "int instanceofFunction(int sourceClass, int destId) { return 1; }\n" + - "extern struct clazz class__java_lang_Class;\n" + - "extern struct clazz class__java_lang_String;\n" + - "int currentGcMarkValue = 1;\n" + - "\n" + - "// Allocator Implementation\n" + - "JAVA_OBJECT allocArray(CODENAME_ONE_THREAD_STATE, int length, struct clazz* type, int primitiveSize, int dim) {\n" + - " struct JavaArrayPrototype* arr = (struct JavaArrayPrototype*)calloc(1, sizeof(struct JavaArrayPrototype));\n" + - " arr->__codenameOneParentClsReference = type;\n" + - " arr->length = length;\n" + - " arr->dimensions = dim;\n" + - " arr->primitiveSize = primitiveSize;\n" + - " int size = primitiveSize ? primitiveSize : sizeof(JAVA_OBJECT);\n" + - " arr->data = calloc(length, size);\n" + - " return (JAVA_OBJECT)arr;\n" + - "}\n" + - "\n" + - "// Threading\n" + - "extern void java_lang_Thread_run__(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT me);\n" + - "void* java_thread_entry(void* arg) {\n" + - " JAVA_OBJECT threadObj = (JAVA_OBJECT)arg;\n" + - " struct ThreadLocalData* data = getThreadLocalData();\n" + - " pthread_setspecific(current_thread_key, threadObj);\n" + - " java_lang_Thread_run__(data, threadObj);\n" + - " return NULL;\n" + - "}\n" + - "\n" + - "void java_lang_Thread_start0__(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT me) {\n" + - " pthread_t pt;\n" + - " pthread_create(&pt, NULL, java_thread_entry, me);\n" + - "}\n" + - "\n" + - "extern JAVA_OBJECT __NEW_java_lang_Thread(CODENAME_ONE_THREAD_STATE);\n" + - "// We don't call INIT on main thread lazily created\n" + - "\n" + - "JAVA_OBJECT java_lang_Thread_currentThread___R_java_lang_Thread(CODENAME_ONE_THREAD_STATE) {\n" + - " JAVA_OBJECT t = pthread_getspecific(current_thread_key);\n" + - " if (!t) {\n" + - " t = __NEW_java_lang_Thread(threadStateData);\n" + - " pthread_setspecific(current_thread_key, t);\n" + - " }\n" + - " return t;\n" + - "}\n" + - "\n" + - "void java_lang_Thread_sleep0___long(CODENAME_ONE_THREAD_STATE, JAVA_LONG millis) {\n" + - " usleep(millis * 1000);\n" + - "}\n" + - "\n" + - "JAVA_LONG java_lang_System_currentTimeMillis___R_long(CODENAME_ONE_THREAD_STATE) {\n" + - " struct timeval tv;\n" + - " gettimeofday(&tv, NULL);\n" + - " return (long long)tv.tv_sec * 1000 + tv.tv_usec / 1000;\n" + - "}\n" + - "\n" + - "// HashCode\n" + - "JAVA_INT java_lang_Object_hashCode___R_int(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT me) { return (JAVA_INT)(JAVA_LONG)me; }\n" + - "// getClass\n" + - "JAVA_OBJECT java_lang_Object_getClass___R_java_lang_Class(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT me) { return NULL; }\n"; - + String content = "#include \"cn1_globals.h\"\n" + + "#include \"cn1_class_method_index.h\"\n" + + "#include \"java_lang_Object.h\"\n" + + "#include \"java_lang_String.h\"\n" + + "#include \"java_lang_StringToReal.h\"\n" + + "#include \"java_lang_ArrayIndexOutOfBoundsException.h\"\n" + + "#include \"java_lang_Thread.h\"\n\n" + + "int *classInstanceOf[] = { 0 };\n" + + "struct clazz* classesList[] = { 0 };\n" + + "int classListSize = 0;\n" + + "JAVA_OBJECT* constantPoolObjects = NULL;\n\n" + + "JAVA_OBJECT java_lang_String_toCharNoCopy___R_char_1ARRAY(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT str) { return JAVA_NULL; }\n" + + "JAVA_OBJECT java_lang_StringToReal_invalidReal___java_lang_String_boolean_R_java_lang_NumberFormatException(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT s, JAVA_BOOLEAN strict) { return JAVA_NULL; }\n" + + "JAVA_VOID java_lang_Thread_runImpl___long(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT t, JAVA_LONG id) { }\n" + + "JAVA_VOID java_lang_ArrayIndexOutOfBoundsException___INIT_____int(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT obj, JAVA_INT idx) { }\n" + + "JAVA_OBJECT get_field_java_lang_Throwable_stack(JAVA_OBJECT t) { return JAVA_NULL; }\n" + + "void set_field_java_lang_Throwable_stack(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT val, JAVA_OBJECT t) { }\n"; Files.write(stubs, content.getBytes(StandardCharsets.UTF_8)); } + private void patchHashMapNativeSupport(Path srcRoot) throws java.io.IOException { Path entryHeader = srcRoot.resolve("java_util_HashMap_Entry.h"); if (!Files.exists(entryHeader)) { @@ -945,4 +755,69 @@ private void patchHashMapNativeSupport(Path srcRoot) throws java.io.IOException Files.write(stringToRealHeader, strContent.getBytes(StandardCharsets.UTF_8)); } } + + private void patchHashMapNativeMethods(Path srcRoot) throws java.io.IOException { + Path nativeMethods = srcRoot.resolve("nativeMethods.c"); + if (!Files.exists(nativeMethods)) { + return; + } + + String content = Files.readString(nativeMethods); + + content = content.replaceFirst("#include \\\"java_lang_System.h\\\"\\n\\n", + "#include \"java_lang_System.h\"\n\nJAVA_OBJECT java_lang_Class_getName___R_java_lang_String(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT cls);\nJAVA_OBJECT java_lang_Throwable_getStack___R_java_lang_String(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT t);\n\n"); + + Pattern areEqualKeys = Pattern.compile( + "JAVA_BOOLEAN java_util_HashMap_areEqualKeys___java_lang_Object_java_lang_Object_R_boolean\\(.*?\n}\n", + Pattern.DOTALL); + Matcher areEqualMatcher = areEqualKeys.matcher(content); + if (areEqualMatcher.find()) { + String replacement = "JAVA_BOOLEAN java_util_HashMap_areEqualKeys___java_lang_Object_java_lang_Object_R_boolean(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT __cn1Arg1, JAVA_OBJECT __cn1Arg2) {\n" + + " if (__cn1Arg1 == __cn1Arg2) {\n" + + " return JAVA_TRUE;\n" + + " }\n" + + " return java_lang_Object_equals___java_lang_Object_R_boolean(threadStateData, __cn1Arg1, __cn1Arg2);\n" + + "}\n"; + content = areEqualMatcher.replaceFirst(Matcher.quoteReplacement(replacement)); + } + + Pattern stringHash = Pattern.compile( + "JAVA_INT java_lang_String_hashCode___R_int\\(.*?\n}\n", + Pattern.DOTALL); + Matcher hashMatcher = stringHash.matcher(content); + if (hashMatcher.find()) { + String replacement = "JAVA_INT java_lang_String_hashCode___R_int(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT __cn1ThisObject) {\n" + + " struct obj__java_lang_String* t = (struct obj__java_lang_String*)__cn1ThisObject;\n" + + " JAVA_INT hash = 0;\n" + + " if (t->java_lang_String_count == 0) {\n" + + " return 0;\n" + + " }\n" + + " JAVA_INT end = t->java_lang_String_count + t->java_lang_String_offset;\n" + + " JAVA_ARRAY_CHAR* chars = (JAVA_ARRAY_CHAR*)((JAVA_ARRAY)t->java_lang_String_value)->data;\n" + + " for (JAVA_INT i = t->java_lang_String_offset; i < end; ++i) {\n" + + " hash = 31 * hash + chars[i];\n" + + " }\n" + + " return hash;\n" + + "}\n"; + content = hashMatcher.replaceFirst(Matcher.quoteReplacement(replacement)); + } + + Pattern findEntry = Pattern.compile( + "JAVA_OBJECT java_util_HashMap_findNonNullKeyEntry___java_lang_Object_int_int_R_java_util_HashMap_Entry\\(.*?\n}\n", + Pattern.DOTALL); + Matcher matcher = findEntry.matcher(content); + if (matcher.find()) { + String replacement = "JAVA_OBJECT java_util_HashMap_findNonNullKeyEntry___java_lang_Object_int_int_R_java_util_HashMap_Entry(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT __cn1ThisObject, JAVA_OBJECT key, JAVA_INT index, JAVA_INT keyHash) {\n" + + " struct obj__java_util_HashMap* t = (struct obj__java_util_HashMap*)__cn1ThisObject;\n" + + " struct obj__java_util_HashMap_Entry* m = (struct obj__java_util_HashMap_Entry*)t->java_util_HashMap_head;\n" + + " while (m != 0 && (m->java_util_HashMap_Entry_origKeyHash != keyHash || !java_util_HashMap_areEqualKeys___java_lang_Object_java_lang_Object_R_boolean(threadStateData, key, m->java_util_HashMap_Entry_key))) {\n" + + " m = (struct obj__java_util_HashMap_Entry*)m->java_util_HashMap_Entry_next;\n" + + " }\n" + + " return (JAVA_OBJECT)m;\n" + + "}\n"; + content = matcher.replaceFirst(Matcher.quoteReplacement(replacement)); + } + + Files.write(nativeMethods, content.getBytes(StandardCharsets.UTF_8)); + } } diff --git a/vm/tests/src/test/java/com/codename1/tools/translator/StampedLockIntegrationTest.java b/vm/tests/src/test/java/com/codename1/tools/translator/StampedLockIntegrationTest.java index 55bebe22ea..b6e6ab5b46 100644 --- a/vm/tests/src/test/java/com/codename1/tools/translator/StampedLockIntegrationTest.java +++ b/vm/tests/src/test/java/com/codename1/tools/translator/StampedLockIntegrationTest.java @@ -10,6 +10,8 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -92,6 +94,7 @@ void verifiesStampedLockBehavior(CompilerHelper.CompilerConfig config) throws Ex CleanTargetIntegrationTest.patchFileHeader(srcRoot); writeRuntimeStubs(srcRoot); patchHashMapNativeSupport(srcRoot); + patchHashMapNativeMethods(srcRoot); CleanTargetIntegrationTest.replaceLibraryWithExecutableTarget(cmakeLists, srcRoot.getFileName().toString()); @@ -612,217 +615,30 @@ private String nativeReportSource() { "}\n"; } + private void writeRuntimeStubs(Path srcRoot) throws java.io.IOException { Path stubs = srcRoot.resolve("runtime_stubs.c"); - String content = "#include \"cn1_globals.h\"\n" + - "#include \"java_lang_Object.h\"\n" + - "#include \n" + - "#include \n" + - "#include \n" + - "#include \n" + - "#include \n" + - "#include \n" + - "#include \n" + - "\n" + - "static pthread_mutexattr_t mtx_attr;\n" + - "void __attribute__((constructor)) init_debug() {\n" + - " setbuf(stdout, NULL);\n" + - " setbuf(stderr, NULL);\n" + - " pthread_mutexattr_init(&mtx_attr);\n" + - " pthread_mutexattr_settype(&mtx_attr, PTHREAD_MUTEX_RECURSIVE);\n" + - "}\n" + - "\n" + - "static pthread_key_t thread_state_key;\n" + - "static pthread_key_t current_thread_key;\n" + - "static pthread_once_t key_once = PTHREAD_ONCE_INIT;\n" + - "\n" + - "static void make_key() {\n" + - " pthread_key_create(&thread_state_key, free);\n" + - " pthread_key_create(¤t_thread_key, NULL);\n" + - "}\n" + - "\n" + - "struct ThreadLocalData* getThreadLocalData() {\n" + - " pthread_once(&key_once, make_key);\n" + - " struct ThreadLocalData* data = pthread_getspecific(thread_state_key);\n" + - " if (!data) {\n" + - " data = calloc(1, sizeof(struct ThreadLocalData));\n" + - " data->blocks = calloc(100, sizeof(struct TryBlock));\n" + - " data->threadObjectStack = calloc(100, sizeof(struct elementStruct));\n" + - " data->pendingHeapAllocations = calloc(100, sizeof(void*));\n" + - " pthread_setspecific(thread_state_key, data);\n" + - " }\n" + - " return data;\n" + - "}\n" + - "\n" + - "// Monitor implementation\n" + - "#define MAX_MONITORS 1024\n" + - "typedef struct {\n" + - " JAVA_OBJECT obj;\n" + - " pthread_mutex_t mutex;\n" + - " pthread_cond_t cond;\n" + - "} Monitor;\n" + - "static Monitor monitors[MAX_MONITORS];\n" + - "static pthread_mutex_t global_monitor_lock = PTHREAD_MUTEX_INITIALIZER;\n" + - "\n" + - "static Monitor* getMonitor(JAVA_OBJECT obj) {\n" + - " pthread_mutex_lock(&global_monitor_lock);\n" + - " for(int i=0; imutex);\n" + - "}\n" + - "\n" + - "void monitorExit(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT obj) {\n" + - " if (!obj) return;\n" + - " Monitor* m = getMonitor(obj);\n" + - " pthread_mutex_unlock(&m->mutex);\n" + - "}\n" + - "\n" + - "void java_lang_Object_wait___long_int(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT obj, JAVA_LONG timeout, JAVA_INT nanos) {\n" + - " Monitor* m = getMonitor(obj);\n" + - " if (timeout > 0 || nanos > 0) {\n" + - " struct timespec ts;\n" + - " struct timeval now;\n" + - " gettimeofday(&now, NULL);\n" + - " ts.tv_sec = now.tv_sec + timeout / 1000;\n" + - " ts.tv_nsec = now.tv_usec * 1000 + (timeout % 1000) * 1000000 + nanos;\n" + - " if (ts.tv_nsec >= 1000000000) {\n" + - " ts.tv_sec++;\n" + - " ts.tv_nsec -= 1000000000;\n" + - " }\n" + - " pthread_cond_timedwait(&m->cond, &m->mutex, &ts);\n" + - " } else {\n" + - " pthread_cond_wait(&m->cond, &m->mutex);\n" + - " }\n" + - "}\n" + - "\n" + - "void java_lang_Object_notify__(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT obj) {\n" + - " Monitor* m = getMonitor(obj);\n" + - " pthread_cond_signal(&m->cond);\n" + - "}\n" + - "\n" + - "void java_lang_Object_notifyAll__(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT obj) {\n" + - " Monitor* m = getMonitor(obj);\n" + - " pthread_cond_broadcast(&m->cond);\n" + - "}\n" + - "\n" + - "JAVA_OBJECT* constantPoolObjects = NULL;\n" + - "void initConstantPool() {\n" + - " if (constantPoolObjects == NULL) {\n" + - " constantPoolObjects = calloc(1024, sizeof(JAVA_OBJECT));\n" + - " }\n" + - "}\n" + - "JAVA_OBJECT codenameOneGcMalloc(CODENAME_ONE_THREAD_STATE, int size, struct clazz* parent) {\n" + - " JAVA_OBJECT obj = (JAVA_OBJECT)calloc(1, size);\n" + - " if (obj != JAVA_NULL) {\n" + - " obj->__codenameOneParentClsReference = parent;\n" + - " }\n" + - " return obj;\n" + - "}\n" + - "void codenameOneGcFree(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT obj) { free(obj); }\n" + - "void arrayFinalizerFunction(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT array) { free(array); }\n" + - "void gcMarkArrayObject(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT obj, JAVA_BOOLEAN force) {}\n" + - "void gcMarkObject(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT obj, JAVA_BOOLEAN force) {}\n" + - "void** initVtableForInterface() { static void* table[1]; return (void**)table; }\n" + - "struct clazz class_array1__JAVA_INT = {0};\n" + - "struct clazz class_array2__JAVA_INT = {0};\n" + - "struct clazz class_array1__JAVA_BOOLEAN = {0};\n" + - "struct clazz class_array1__JAVA_CHAR = {0};\n" + - "struct clazz class_array1__JAVA_FLOAT = {0};\n" + - "struct clazz class_array1__JAVA_DOUBLE = {0};\n" + - "struct clazz class_array1__JAVA_BYTE = {0};\n" + - "struct clazz class_array1__JAVA_SHORT = {0};\n" + - "struct clazz class_array1__JAVA_LONG = {0};\n" + - "void initMethodStack(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT __cn1ThisObject, int stackSize, int localsStackSize, int classNameId, int methodNameId) {}\n" + - "void releaseForReturn(CODENAME_ONE_THREAD_STATE, int cn1LocalsBeginInThread) {}\n" + - "void releaseForReturnInException(CODENAME_ONE_THREAD_STATE, int cn1LocalsBeginInThread, int methodBlockOffset) {}\n" + - "void monitorEnterBlock(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT obj) {}\n" + - "void monitorExitBlock(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT obj) {}\n" + - "struct elementStruct* pop(struct elementStruct** sp) { (*sp)--; return *sp; }\n" + - "void popMany(CODENAME_ONE_THREAD_STATE, int count, struct elementStruct** sp) { while(count--) (*sp)--; }\n" + - "void throwException(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT obj) { exit(1); }\n" + - "int instanceofFunction(int sourceClass, int destId) { return 1; }\n" + - "extern struct clazz class__java_lang_Class;\n" + - "extern struct clazz class__java_lang_String;\n" + - "int currentGcMarkValue = 1;\n" + - "\n" + - "// Allocator Implementation\n" + - "JAVA_OBJECT allocArray(CODENAME_ONE_THREAD_STATE, int length, struct clazz* type, int primitiveSize, int dim) {\n" + - " struct JavaArrayPrototype* arr = (struct JavaArrayPrototype*)calloc(1, sizeof(struct JavaArrayPrototype));\n" + - " arr->__codenameOneParentClsReference = type;\n" + - " arr->length = length;\n" + - " arr->dimensions = dim;\n" + - " arr->primitiveSize = primitiveSize;\n" + - " int size = primitiveSize ? primitiveSize : sizeof(JAVA_OBJECT);\n" + - " arr->data = calloc(length, size);\n" + - " return (JAVA_OBJECT)arr;\n" + - "}\n" + - "\n" + - "// Threading\n" + - "extern void java_lang_Thread_run__(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT me);\n" + - "void* java_thread_entry(void* arg) {\n" + - " JAVA_OBJECT threadObj = (JAVA_OBJECT)arg;\n" + - " struct ThreadLocalData* data = getThreadLocalData();\n" + - " pthread_setspecific(current_thread_key, threadObj);\n" + - " java_lang_Thread_run__(data, threadObj);\n" + - " return NULL;\n" + - "}\n" + - "\n" + - "void java_lang_Thread_start0__(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT me) {\n" + - " pthread_t pt;\n" + - " pthread_create(&pt, NULL, java_thread_entry, me);\n" + - "}\n" + - "\n" + - "extern JAVA_OBJECT __NEW_java_lang_Thread(CODENAME_ONE_THREAD_STATE);\n" + - "// We don't call INIT on main thread lazily created\n" + - "\n" + - "JAVA_OBJECT java_lang_Thread_currentThread___R_java_lang_Thread(CODENAME_ONE_THREAD_STATE) {\n" + - " JAVA_OBJECT t = pthread_getspecific(current_thread_key);\n" + - " if (!t) {\n" + - " t = __NEW_java_lang_Thread(threadStateData);\n" + - " pthread_setspecific(current_thread_key, t);\n" + - " }\n" + - " return t;\n" + - "}\n" + - "\n" + - "void java_lang_Thread_sleep0___long(CODENAME_ONE_THREAD_STATE, JAVA_LONG millis) {\n" + - " usleep(millis * 1000);\n" + - "}\n" + - "\n" + - "JAVA_LONG java_lang_System_currentTimeMillis___R_long(CODENAME_ONE_THREAD_STATE) {\n" + - " struct timeval tv;\n" + - " gettimeofday(&tv, NULL);\n" + - " return (long long)tv.tv_sec * 1000 + tv.tv_usec / 1000;\n" + - "}\n" + - "\n" + - "// HashCode\n" + - "JAVA_INT java_lang_Object_hashCode___R_int(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT me) { return (JAVA_INT)(JAVA_LONG)me; }\n" + - "// getClass\n" + - "JAVA_OBJECT java_lang_Object_getClass___R_java_lang_Class(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT me) { return NULL; }\n"; - + String content = "#include \"cn1_globals.h\"\n" + + "#include \"cn1_class_method_index.h\"\n" + + "#include \"java_lang_Object.h\"\n" + + "#include \"java_lang_String.h\"\n" + + "#include \"java_lang_StringToReal.h\"\n" + + "#include \"java_lang_ArrayIndexOutOfBoundsException.h\"\n" + + "#include \"java_lang_Thread.h\"\n\n" + + "int *classInstanceOf[] = { 0 };\n" + + "struct clazz* classesList[] = { 0 };\n" + + "int classListSize = 0;\n" + + "JAVA_OBJECT* constantPoolObjects = NULL;\n\n" + + "JAVA_OBJECT java_lang_String_toCharNoCopy___R_char_1ARRAY(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT str) { return JAVA_NULL; }\n" + + "JAVA_OBJECT java_lang_StringToReal_invalidReal___java_lang_String_boolean_R_java_lang_NumberFormatException(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT s, JAVA_BOOLEAN strict) { return JAVA_NULL; }\n" + + "JAVA_VOID java_lang_Thread_runImpl___long(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT t, JAVA_LONG id) { }\n" + + "JAVA_VOID java_lang_ArrayIndexOutOfBoundsException___INIT_____int(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT obj, JAVA_INT idx) { }\n" + + "JAVA_OBJECT get_field_java_lang_Throwable_stack(JAVA_OBJECT t) { return JAVA_NULL; }\n" + + "void set_field_java_lang_Throwable_stack(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT val, JAVA_OBJECT t) { }\n"; Files.write(stubs, content.getBytes(StandardCharsets.UTF_8)); } + private void patchHashMapNativeSupport(Path srcRoot) throws java.io.IOException { Path entryHeader = srcRoot.resolve("java_util_HashMap_Entry.h"); if (!Files.exists(entryHeader)) { @@ -910,4 +726,69 @@ private void patchHashMapNativeSupport(Path srcRoot) throws java.io.IOException Files.write(stringToRealHeader, strContent.getBytes(StandardCharsets.UTF_8)); } } + + private void patchHashMapNativeMethods(Path srcRoot) throws java.io.IOException { + Path nativeMethods = srcRoot.resolve("nativeMethods.c"); + if (!Files.exists(nativeMethods)) { + return; + } + + String content = Files.readString(nativeMethods); + + content = content.replaceFirst("#include \\\"java_lang_System.h\\\"\\n\\n", + "#include \"java_lang_System.h\"\n\nJAVA_OBJECT java_lang_Class_getName___R_java_lang_String(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT cls);\nJAVA_OBJECT java_lang_Throwable_getStack___R_java_lang_String(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT t);\n\n"); + + Pattern areEqualKeys = Pattern.compile( + "JAVA_BOOLEAN java_util_HashMap_areEqualKeys___java_lang_Object_java_lang_Object_R_boolean\\(.*?\n}\n", + Pattern.DOTALL); + Matcher areEqualMatcher = areEqualKeys.matcher(content); + if (areEqualMatcher.find()) { + String replacement = "JAVA_BOOLEAN java_util_HashMap_areEqualKeys___java_lang_Object_java_lang_Object_R_boolean(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT __cn1Arg1, JAVA_OBJECT __cn1Arg2) {\n" + + " if (__cn1Arg1 == __cn1Arg2) {\n" + + " return JAVA_TRUE;\n" + + " }\n" + + " return java_lang_Object_equals___java_lang_Object_R_boolean(threadStateData, __cn1Arg1, __cn1Arg2);\n" + + "}\n"; + content = areEqualMatcher.replaceFirst(Matcher.quoteReplacement(replacement)); + } + + Pattern stringHash = Pattern.compile( + "JAVA_INT java_lang_String_hashCode___R_int\\(.*?\n}\n", + Pattern.DOTALL); + Matcher hashMatcher = stringHash.matcher(content); + if (hashMatcher.find()) { + String replacement = "JAVA_INT java_lang_String_hashCode___R_int(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT __cn1ThisObject) {\n" + + " struct obj__java_lang_String* t = (struct obj__java_lang_String*)__cn1ThisObject;\n" + + " JAVA_INT hash = 0;\n" + + " if (t->java_lang_String_count == 0) {\n" + + " return 0;\n" + + " }\n" + + " JAVA_INT end = t->java_lang_String_count + t->java_lang_String_offset;\n" + + " JAVA_ARRAY_CHAR* chars = (JAVA_ARRAY_CHAR*)((JAVA_ARRAY)t->java_lang_String_value)->data;\n" + + " for (JAVA_INT i = t->java_lang_String_offset; i < end; ++i) {\n" + + " hash = 31 * hash + chars[i];\n" + + " }\n" + + " return hash;\n" + + "}\n"; + content = hashMatcher.replaceFirst(Matcher.quoteReplacement(replacement)); + } + + Pattern findEntry = Pattern.compile( + "JAVA_OBJECT java_util_HashMap_findNonNullKeyEntry___java_lang_Object_int_int_R_java_util_HashMap_Entry\\(.*?\n}\n", + Pattern.DOTALL); + Matcher matcher = findEntry.matcher(content); + if (matcher.find()) { + String replacement = "JAVA_OBJECT java_util_HashMap_findNonNullKeyEntry___java_lang_Object_int_int_R_java_util_HashMap_Entry(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT __cn1ThisObject, JAVA_OBJECT key, JAVA_INT index, JAVA_INT keyHash) {\n" + + " struct obj__java_util_HashMap* t = (struct obj__java_util_HashMap*)__cn1ThisObject;\n" + + " struct obj__java_util_HashMap_Entry* m = (struct obj__java_util_HashMap_Entry*)t->java_util_HashMap_head;\n" + + " while (m != 0 && (m->java_util_HashMap_Entry_origKeyHash != keyHash || !java_util_HashMap_areEqualKeys___java_lang_Object_java_lang_Object_R_boolean(threadStateData, key, m->java_util_HashMap_Entry_key))) {\n" + + " m = (struct obj__java_util_HashMap_Entry*)m->java_util_HashMap_Entry_next;\n" + + " }\n" + + " return (JAVA_OBJECT)m;\n" + + "}\n"; + content = matcher.replaceFirst(Matcher.quoteReplacement(replacement)); + } + + Files.write(nativeMethods, content.getBytes(StandardCharsets.UTF_8)); + } } From f829c6982e086c60c6d4e06dcc3530e5dd9ce454 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Sun, 28 Dec 2025 13:16:17 +0200 Subject: [PATCH 26/40] Use Java 8 compatible file reads in integration tests --- .../tools/translator/ReadWriteLockIntegrationTest.java | 2 +- .../codename1/tools/translator/StampedLockIntegrationTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/vm/tests/src/test/java/com/codename1/tools/translator/ReadWriteLockIntegrationTest.java b/vm/tests/src/test/java/com/codename1/tools/translator/ReadWriteLockIntegrationTest.java index 7106370316..ebf2bd388e 100644 --- a/vm/tests/src/test/java/com/codename1/tools/translator/ReadWriteLockIntegrationTest.java +++ b/vm/tests/src/test/java/com/codename1/tools/translator/ReadWriteLockIntegrationTest.java @@ -762,7 +762,7 @@ private void patchHashMapNativeMethods(Path srcRoot) throws java.io.IOException return; } - String content = Files.readString(nativeMethods); + String content = new String(Files.readAllBytes(nativeMethods), StandardCharsets.UTF_8); content = content.replaceFirst("#include \\\"java_lang_System.h\\\"\\n\\n", "#include \"java_lang_System.h\"\n\nJAVA_OBJECT java_lang_Class_getName___R_java_lang_String(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT cls);\nJAVA_OBJECT java_lang_Throwable_getStack___R_java_lang_String(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT t);\n\n"); diff --git a/vm/tests/src/test/java/com/codename1/tools/translator/StampedLockIntegrationTest.java b/vm/tests/src/test/java/com/codename1/tools/translator/StampedLockIntegrationTest.java index b6e6ab5b46..0081a4898f 100644 --- a/vm/tests/src/test/java/com/codename1/tools/translator/StampedLockIntegrationTest.java +++ b/vm/tests/src/test/java/com/codename1/tools/translator/StampedLockIntegrationTest.java @@ -733,7 +733,7 @@ private void patchHashMapNativeMethods(Path srcRoot) throws java.io.IOException return; } - String content = Files.readString(nativeMethods); + String content = new String(Files.readAllBytes(nativeMethods), StandardCharsets.UTF_8); content = content.replaceFirst("#include \\\"java_lang_System.h\\\"\\n\\n", "#include \"java_lang_System.h\"\n\nJAVA_OBJECT java_lang_Class_getName___R_java_lang_String(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT cls);\nJAVA_OBJECT java_lang_Throwable_getStack___R_java_lang_String(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT t);\n\n"); From f9978beae136038798909c1c22c7a03707939f21 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Sun, 28 Dec 2025 14:35:57 +0200 Subject: [PATCH 27/40] Harden clean target integration stubs for native builds --- .../CleanTargetIntegrationTest.java | 17 +++++++ .../ReadWriteLockIntegrationTest.java | 41 ++++++++++++++-- .../StampedLockIntegrationTest.java | 49 +++++++++++++++---- 3 files changed, 93 insertions(+), 14 deletions(-) diff --git a/vm/tests/src/test/java/com/codename1/tools/translator/CleanTargetIntegrationTest.java b/vm/tests/src/test/java/com/codename1/tools/translator/CleanTargetIntegrationTest.java index aa5d945579..449b605ed8 100644 --- a/vm/tests/src/test/java/com/codename1/tools/translator/CleanTargetIntegrationTest.java +++ b/vm/tests/src/test/java/com/codename1/tools/translator/CleanTargetIntegrationTest.java @@ -21,6 +21,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.stream.Stream; import java.util.stream.Collectors; import static org.junit.jupiter.api.Assertions.*; @@ -192,6 +193,22 @@ static void replaceLibraryWithExecutableTarget(Path cmakeLists, String sourceDir Files.write(cmakeLists, replacement.getBytes(StandardCharsets.UTF_8)); } + static void copyObjcSourcesAsC(Path srcRoot) throws IOException { + try (Stream files = Files.list(srcRoot)) { + files.filter(p -> p.getFileName().toString().endsWith(".m")) + .forEach(p -> { + Path asC = p.resolveSibling(p.getFileName().toString().replaceAll("\\.m$", ".c")); + if (!Files.exists(asC)) { + try { + Files.copy(p, asC); + } catch (IOException ignore) { + // Best-effort copy for clean targets; failures are tolerated during tests. + } + } + }); + } + } + static List cmakeCompilerArgs() { String cCompiler = findCompiler("clang", "gcc", "cc"); if (cCompiler == null) { diff --git a/vm/tests/src/test/java/com/codename1/tools/translator/ReadWriteLockIntegrationTest.java b/vm/tests/src/test/java/com/codename1/tools/translator/ReadWriteLockIntegrationTest.java index ebf2bd388e..54beee04ec 100644 --- a/vm/tests/src/test/java/com/codename1/tools/translator/ReadWriteLockIntegrationTest.java +++ b/vm/tests/src/test/java/com/codename1/tools/translator/ReadWriteLockIntegrationTest.java @@ -93,6 +93,7 @@ void verifiesReadWriteLockBehavior(CompilerHelper.CompilerConfig config) throws assertTrue(Files.exists(cmakeLists)); Path srcRoot = distDir.resolve("ReadWriteLockTestApp-src"); + CleanTargetIntegrationTest.copyObjcSourcesAsC(srcRoot); CleanTargetIntegrationTest.patchCn1Globals(srcRoot); CleanTargetIntegrationTest.patchFileHeader(srcRoot); patchHashMapNativeSupport(srcRoot); @@ -654,16 +655,18 @@ private void writeRuntimeStubs(Path srcRoot) throws java.io.IOException { + "#include \"java_lang_StringToReal.h\"\n" + "#include \"java_lang_ArrayIndexOutOfBoundsException.h\"\n" + "#include \"java_lang_Thread.h\"\n\n" - + "int *classInstanceOf[] = { 0 };\n" - + "struct clazz* classesList[] = { 0 };\n" - + "int classListSize = 0;\n" - + "JAVA_OBJECT* constantPoolObjects = NULL;\n\n" + + "extern int *classInstanceOf[];\n" + + "extern struct clazz* classesList[];\n" + + "extern int classListSize;\n\n" + "JAVA_OBJECT java_lang_String_toCharNoCopy___R_char_1ARRAY(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT str) { return JAVA_NULL; }\n" + "JAVA_OBJECT java_lang_StringToReal_invalidReal___java_lang_String_boolean_R_java_lang_NumberFormatException(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT s, JAVA_BOOLEAN strict) { return JAVA_NULL; }\n" + "JAVA_VOID java_lang_Thread_runImpl___long(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT t, JAVA_LONG id) { }\n" + "JAVA_VOID java_lang_ArrayIndexOutOfBoundsException___INIT_____int(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT obj, JAVA_INT idx) { }\n" + "JAVA_OBJECT get_field_java_lang_Throwable_stack(JAVA_OBJECT t) { return JAVA_NULL; }\n" - + "void set_field_java_lang_Throwable_stack(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT val, JAVA_OBJECT t) { }\n"; + + "void set_field_java_lang_Throwable_stack(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT val, JAVA_OBJECT t) { }\n" + + "JAVA_VOID java_lang_Thread_start0__(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT t) { }\n" + + "JAVA_VOID java_lang_Thread_sleep0___long(CODENAME_ONE_THREAD_STATE, JAVA_LONG millis) { }\n" + + "JAVA_OBJECT java_lang_Object_getClass___R_java_lang_Class(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT obj) { return JAVA_NULL; }\n"; Files.write(stubs, content.getBytes(StandardCharsets.UTF_8)); } @@ -767,6 +770,34 @@ private void patchHashMapNativeMethods(Path srcRoot) throws java.io.IOException content = content.replaceFirst("#include \\\"java_lang_System.h\\\"\\n\\n", "#include \"java_lang_System.h\"\n\nJAVA_OBJECT java_lang_Class_getName___R_java_lang_String(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT cls);\nJAVA_OBJECT java_lang_Throwable_getStack___R_java_lang_String(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT t);\n\n"); + String[][] duplicateRuntimeSymbols = new String[][] { + {"JAVA_BOOLEAN java_lang_String_equals___java_lang_Object_R_boolean", "JAVA_BOOLEAN java_lang_String_equals___java_lang_Object_R_boolean\\(.*?\n}\\n"}, + {"JAVA_OBJECT java_lang_Object_toString___R_java_lang_String", "JAVA_OBJECT java_lang_Object_toString___R_java_lang_String\\(.*?\n}\\n"}, + {"JAVA_OBJECT java_lang_String_toString___R_java_lang_String", "JAVA_OBJECT java_lang_String_toString___R_java_lang_String\\(.*?\n}\\n"}, + {"JAVA_VOID java_lang_String_getChars___int_int_char_1ARRAY_int", "JAVA_VOID java_lang_String_getChars___int_int_char_1ARRAY_int\\(.*?\n}\\n"}, + {"JAVA_OBJECT java_lang_StringBuilder_append___java_lang_String_R_java_lang_StringBuilder", "JAVA_OBJECT java_lang_StringBuilder_append___java_lang_String_R_java_lang_StringBuilder\\(.*?\n}\\n"}, + {"JAVA_OBJECT java_lang_StringBuilder_append___java_lang_Object_R_java_lang_StringBuilder", "JAVA_OBJECT java_lang_StringBuilder_append___java_lang_Object_R_java_lang_StringBuilder\\(.*?\n}\\n"}, + {"JAVA_VOID java_lang_System_arraycopy___java_lang_Object_int_java_lang_Object_int_int", "JAVA_VOID java_lang_System_arraycopy___java_lang_Object_int_java_lang_Object_int_int\\(.*?\n}\\n"}, + {"JAVA_LONG java_lang_System_currentTimeMillis___R_long", "JAVA_LONG java_lang_System_currentTimeMillis___R_long\\(.*?\n}\\n"}, + {"JAVA_VOID java_lang_StringBuilder_getChars___int_int_char_1ARRAY_int", "JAVA_VOID java_lang_StringBuilder_getChars___int_int_char_1ARRAY_int\\(.*?\n}\\n"}, + {"JAVA_OBJECT java_lang_StringBuilder_append___char_R_java_lang_StringBuilder", "JAVA_OBJECT java_lang_StringBuilder_append___char_R_java_lang_StringBuilder\\(.*?\n}\\n"}, + {"JAVA_VOID java_lang_Thread_sleep___long", "JAVA_VOID java_lang_Thread_sleep___long\\(.*?\n}\\n"}, + {"JAVA_VOID java_lang_Thread_start__", "JAVA_VOID java_lang_Thread_start__\\(.*?\n}\\n"}, + {"JAVA_BOOLEAN java_lang_Thread_isInterrupted___boolean_R_boolean", "JAVA_BOOLEAN java_lang_Thread_isInterrupted___boolean_R_boolean\\(.*?\n}\\n"} + }; + + for (String[] pattern : duplicateRuntimeSymbols) { + Pattern p = Pattern.compile(pattern[1], Pattern.DOTALL); + Matcher m = p.matcher(content); + if (m.find()) { + content = m.replaceFirst("\n"); + } + } + + content = content.replace("JAVA_OBJECT* constantPoolObjects = 0;", "extern JAVA_OBJECT* constantPoolObjects;"); + content = content.replace("void initConstantPool() {\n // Allocate dummy pool to prevent segfaults, though contents will be null\n constantPoolObjects = calloc(65536, sizeof(void*));\n}\n", + "void initConstantPool() {}\n"); + Pattern areEqualKeys = Pattern.compile( "JAVA_BOOLEAN java_util_HashMap_areEqualKeys___java_lang_Object_java_lang_Object_R_boolean\\(.*?\n}\n", Pattern.DOTALL); diff --git a/vm/tests/src/test/java/com/codename1/tools/translator/StampedLockIntegrationTest.java b/vm/tests/src/test/java/com/codename1/tools/translator/StampedLockIntegrationTest.java index 0081a4898f..dfc0169a16 100644 --- a/vm/tests/src/test/java/com/codename1/tools/translator/StampedLockIntegrationTest.java +++ b/vm/tests/src/test/java/com/codename1/tools/translator/StampedLockIntegrationTest.java @@ -90,6 +90,7 @@ void verifiesStampedLockBehavior(CompilerHelper.CompilerConfig config) throws Ex assertTrue(Files.exists(cmakeLists)); Path srcRoot = distDir.resolve("StampedLockTestApp-src"); + CleanTargetIntegrationTest.copyObjcSourcesAsC(srcRoot); CleanTargetIntegrationTest.patchCn1Globals(srcRoot); CleanTargetIntegrationTest.patchFileHeader(srcRoot); writeRuntimeStubs(srcRoot); @@ -472,15 +473,15 @@ private void writeMockJavaClasses(Path sourceDir) throws Exception { Files.write(io.resolve("FileWriter.java"), ("package java.io;\n" + "public class FileWriter {\n" + " private final FileOutputStream out;\n" + - " public FileWriter(String name) { this.out = new FileOutputStream(name); }\n" + - " public FileWriter(File file) { this.out = new FileOutputStream(file); }\n" + + " public FileWriter(String name) throws java.io.IOException { this.out = new FileOutputStream(name); }\n" + + " public FileWriter(File file) throws java.io.IOException { this.out = new FileOutputStream(file); }\n" + " public void write(String s) throws java.io.IOException {\n" + " if (s == null) return;\n" + " byte[] data = s.getBytes();\n" + " out.write(data, 0, data.length);\n" + " }\n" + - " public void flush() { out.flush(); }\n" + - " public void close() { out.close(); }\n" + + " public void flush() throws java.io.IOException { out.flush(); }\n" + + " public void close() throws java.io.IOException { out.close(); }\n" + "}\n").getBytes(StandardCharsets.UTF_8)); // java.util.concurrent.TimeUnit @@ -625,16 +626,18 @@ private void writeRuntimeStubs(Path srcRoot) throws java.io.IOException { + "#include \"java_lang_StringToReal.h\"\n" + "#include \"java_lang_ArrayIndexOutOfBoundsException.h\"\n" + "#include \"java_lang_Thread.h\"\n\n" - + "int *classInstanceOf[] = { 0 };\n" - + "struct clazz* classesList[] = { 0 };\n" - + "int classListSize = 0;\n" - + "JAVA_OBJECT* constantPoolObjects = NULL;\n\n" + + "extern int *classInstanceOf[];\n" + + "extern struct clazz* classesList[];\n" + + "extern int classListSize;\n\n" + "JAVA_OBJECT java_lang_String_toCharNoCopy___R_char_1ARRAY(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT str) { return JAVA_NULL; }\n" + "JAVA_OBJECT java_lang_StringToReal_invalidReal___java_lang_String_boolean_R_java_lang_NumberFormatException(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT s, JAVA_BOOLEAN strict) { return JAVA_NULL; }\n" + "JAVA_VOID java_lang_Thread_runImpl___long(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT t, JAVA_LONG id) { }\n" + "JAVA_VOID java_lang_ArrayIndexOutOfBoundsException___INIT_____int(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT obj, JAVA_INT idx) { }\n" + "JAVA_OBJECT get_field_java_lang_Throwable_stack(JAVA_OBJECT t) { return JAVA_NULL; }\n" - + "void set_field_java_lang_Throwable_stack(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT val, JAVA_OBJECT t) { }\n"; + + "void set_field_java_lang_Throwable_stack(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT val, JAVA_OBJECT t) { }\n" + + "JAVA_VOID java_lang_Thread_start0__(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT t) { }\n" + + "JAVA_VOID java_lang_Thread_sleep0___long(CODENAME_ONE_THREAD_STATE, JAVA_LONG millis) { }\n" + + "JAVA_OBJECT java_lang_Object_getClass___R_java_lang_Class(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT obj) { return JAVA_NULL; }\n"; Files.write(stubs, content.getBytes(StandardCharsets.UTF_8)); } @@ -738,6 +741,34 @@ private void patchHashMapNativeMethods(Path srcRoot) throws java.io.IOException content = content.replaceFirst("#include \\\"java_lang_System.h\\\"\\n\\n", "#include \"java_lang_System.h\"\n\nJAVA_OBJECT java_lang_Class_getName___R_java_lang_String(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT cls);\nJAVA_OBJECT java_lang_Throwable_getStack___R_java_lang_String(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT t);\n\n"); + String[][] duplicateRuntimeSymbols = new String[][] { + {"JAVA_BOOLEAN java_lang_String_equals___java_lang_Object_R_boolean", "JAVA_BOOLEAN java_lang_String_equals___java_lang_Object_R_boolean\\(.*?\n}\\n"}, + {"JAVA_OBJECT java_lang_Object_toString___R_java_lang_String", "JAVA_OBJECT java_lang_Object_toString___R_java_lang_String\\(.*?\n}\\n"}, + {"JAVA_OBJECT java_lang_String_toString___R_java_lang_String", "JAVA_OBJECT java_lang_String_toString___R_java_lang_String\\(.*?\n}\\n"}, + {"JAVA_VOID java_lang_String_getChars___int_int_char_1ARRAY_int", "JAVA_VOID java_lang_String_getChars___int_int_char_1ARRAY_int\\(.*?\n}\\n"}, + {"JAVA_OBJECT java_lang_StringBuilder_append___java_lang_String_R_java_lang_StringBuilder", "JAVA_OBJECT java_lang_StringBuilder_append___java_lang_String_R_java_lang_StringBuilder\\(.*?\n}\\n"}, + {"JAVA_OBJECT java_lang_StringBuilder_append___java_lang_Object_R_java_lang_StringBuilder", "JAVA_OBJECT java_lang_StringBuilder_append___java_lang_Object_R_java_lang_StringBuilder\\(.*?\n}\\n"}, + {"JAVA_VOID java_lang_System_arraycopy___java_lang_Object_int_java_lang_Object_int_int", "JAVA_VOID java_lang_System_arraycopy___java_lang_Object_int_java_lang_Object_int_int\\(.*?\n}\\n"}, + {"JAVA_LONG java_lang_System_currentTimeMillis___R_long", "JAVA_LONG java_lang_System_currentTimeMillis___R_long\\(.*?\n}\\n"}, + {"JAVA_VOID java_lang_StringBuilder_getChars___int_int_char_1ARRAY_int", "JAVA_VOID java_lang_StringBuilder_getChars___int_int_char_1ARRAY_int\\(.*?\n}\\n"}, + {"JAVA_OBJECT java_lang_StringBuilder_append___char_R_java_lang_StringBuilder", "JAVA_OBJECT java_lang_StringBuilder_append___char_R_java_lang_StringBuilder\\(.*?\n}\\n"}, + {"JAVA_VOID java_lang_Thread_sleep___long", "JAVA_VOID java_lang_Thread_sleep___long\\(.*?\n}\\n"}, + {"JAVA_VOID java_lang_Thread_start__", "JAVA_VOID java_lang_Thread_start__\\(.*?\n}\\n"}, + {"JAVA_BOOLEAN java_lang_Thread_isInterrupted___boolean_R_boolean", "JAVA_BOOLEAN java_lang_Thread_isInterrupted___boolean_R_boolean\\(.*?\n}\\n"} + }; + + for (String[] pattern : duplicateRuntimeSymbols) { + Pattern p = Pattern.compile(pattern[1], Pattern.DOTALL); + Matcher m = p.matcher(content); + if (m.find()) { + content = m.replaceFirst("\n"); + } + } + + content = content.replace("JAVA_OBJECT* constantPoolObjects = 0;", "extern JAVA_OBJECT* constantPoolObjects;"); + content = content.replace("void initConstantPool() {\n // Allocate dummy pool to prevent segfaults, though contents will be null\n constantPoolObjects = calloc(65536, sizeof(void*));\n}\n", + "void initConstantPool() {}\n"); + Pattern areEqualKeys = Pattern.compile( "JAVA_BOOLEAN java_util_HashMap_areEqualKeys___java_lang_Object_java_lang_Object_R_boolean\\(.*?\n}\n", Pattern.DOTALL); From 5338c7a9783899eb3fa4e95656e3d7974c9d712c Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Fri, 2 Jan 2026 12:45:37 +0200 Subject: [PATCH 28/40] Fix clean target GC getter prototypes --- .../CleanTargetIntegrationTest.java | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/vm/tests/src/test/java/com/codename1/tools/translator/CleanTargetIntegrationTest.java b/vm/tests/src/test/java/com/codename1/tools/translator/CleanTargetIntegrationTest.java index 449b605ed8..48578de64e 100644 --- a/vm/tests/src/test/java/com/codename1/tools/translator/CleanTargetIntegrationTest.java +++ b/vm/tests/src/test/java/com/codename1/tools/translator/CleanTargetIntegrationTest.java @@ -275,6 +275,17 @@ static void patchCn1Globals(Path srcRoot) throws IOException { content = content.replace("#include \n", "#include \n#include \n#include \n#include \n"); Files.write(cn1Globals, content.getBytes(StandardCharsets.UTF_8)); } + + Path cn1GlobalsC = srcRoot.resolve("cn1_globals.c"); + if (Files.exists(cn1GlobalsC)) { + String cContent = new String(Files.readAllBytes(cn1GlobalsC), StandardCharsets.UTF_8); + String cUpdated = cContent.replace( + "get_static_java_lang_System_gcThreadInstance()", + "get_static_java_lang_System_gcThreadInstance(threadStateData)"); + if (!cUpdated.equals(cContent)) { + Files.write(cn1GlobalsC, cUpdated.getBytes(StandardCharsets.UTF_8)); + } + } } static void patchFileHeader(Path srcRoot) throws IOException { @@ -291,6 +302,50 @@ static void patchFileHeader(Path srcRoot) throws IOException { if (!updated.equals(content)) { Files.write(fileHeader, updated.getBytes(StandardCharsets.UTF_8)); } + + Path systemHeader = srcRoot.resolve("java_lang_System.h"); + if (Files.exists(systemHeader)) { + String systemContent = new String(Files.readAllBytes(systemHeader), StandardCharsets.UTF_8); + String systemUpdated = systemContent.replace( + "get_static_java_lang_System_gcThreadInstance();", + "get_static_java_lang_System_gcThreadInstance(CODENAME_ONE_THREAD_STATE);"); + if (!systemUpdated.equals(systemContent)) { + Files.write(systemHeader, systemUpdated.getBytes(StandardCharsets.UTF_8)); + } + } + + Path rwLockHeader = srcRoot.resolve("java_util_concurrent_locks_ReentrantReadWriteLock.h"); + if (Files.exists(rwLockHeader)) { + String rwContent = new String(Files.readAllBytes(rwLockHeader), StandardCharsets.UTF_8); + String rwUpdated = rwContent.replace( + "get_static_java_util_concurrent_locks_ReentrantReadWriteLock_serialVersionUID();", + "get_static_java_util_concurrent_locks_ReentrantReadWriteLock_serialVersionUID(CODENAME_ONE_THREAD_STATE);"); + if (!rwUpdated.equals(rwContent)) { + Files.write(rwLockHeader, rwUpdated.getBytes(StandardCharsets.UTF_8)); + } + } + + Path rwReadLockHeader = srcRoot.resolve("java_util_concurrent_locks_ReentrantReadWriteLock_ReadLock.h"); + if (Files.exists(rwReadLockHeader)) { + String rwReadContent = new String(Files.readAllBytes(rwReadLockHeader), StandardCharsets.UTF_8); + String rwReadUpdated = rwReadContent.replace( + "get_static_java_util_concurrent_locks_ReentrantReadWriteLock_ReadLock_serialVersionUID();", + "get_static_java_util_concurrent_locks_ReentrantReadWriteLock_ReadLock_serialVersionUID(CODENAME_ONE_THREAD_STATE);"); + if (!rwReadUpdated.equals(rwReadContent)) { + Files.write(rwReadLockHeader, rwReadUpdated.getBytes(StandardCharsets.UTF_8)); + } + } + + Path rwWriteLockHeader = srcRoot.resolve("java_util_concurrent_locks_ReentrantReadWriteLock_WriteLock.h"); + if (Files.exists(rwWriteLockHeader)) { + String rwWriteContent = new String(Files.readAllBytes(rwWriteLockHeader), StandardCharsets.UTF_8); + String rwWriteUpdated = rwWriteContent.replace( + "get_static_java_util_concurrent_locks_ReentrantReadWriteLock_WriteLock_serialVersionUID();", + "get_static_java_util_concurrent_locks_ReentrantReadWriteLock_WriteLock_serialVersionUID(CODENAME_ONE_THREAD_STATE);"); + if (!rwWriteUpdated.equals(rwWriteContent)) { + Files.write(rwWriteLockHeader, rwWriteUpdated.getBytes(StandardCharsets.UTF_8)); + } + } } static void writeRuntimeStubs(Path srcRoot) throws IOException { From f40dae1d5e56928c34a2d800446f2757f3014990 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Sat, 3 Jan 2026 13:35:23 +0200 Subject: [PATCH 29/40] Add clean-target prototypes for runtime stubs --- .../tools/translator/ReadWriteLockIntegrationTest.java | 7 ++++++- .../tools/translator/StampedLockIntegrationTest.java | 7 ++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/vm/tests/src/test/java/com/codename1/tools/translator/ReadWriteLockIntegrationTest.java b/vm/tests/src/test/java/com/codename1/tools/translator/ReadWriteLockIntegrationTest.java index 54beee04ec..fa6b9c4fa5 100644 --- a/vm/tests/src/test/java/com/codename1/tools/translator/ReadWriteLockIntegrationTest.java +++ b/vm/tests/src/test/java/com/codename1/tools/translator/ReadWriteLockIntegrationTest.java @@ -768,7 +768,12 @@ private void patchHashMapNativeMethods(Path srcRoot) throws java.io.IOException String content = new String(Files.readAllBytes(nativeMethods), StandardCharsets.UTF_8); content = content.replaceFirst("#include \\\"java_lang_System.h\\\"\\n\\n", - "#include \"java_lang_System.h\"\n\nJAVA_OBJECT java_lang_Class_getName___R_java_lang_String(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT cls);\nJAVA_OBJECT java_lang_Throwable_getStack___R_java_lang_String(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT t);\n\n"); + "#include \"java_lang_System.h\"\n\n" + + "JAVA_OBJECT java_lang_Class_getName___R_java_lang_String(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT cls);\n" + + "JAVA_OBJECT java_lang_Throwable_getStack___R_java_lang_String(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT t);\n" + + "JAVA_VOID java_lang_Thread_runImpl___long(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT t, JAVA_LONG id);\n" + + "JAVA_OBJECT java_lang_String_toCharNoCopy___R_char_1ARRAY(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT str);\n" + + "JAVA_OBJECT java_lang_StringToReal_invalidReal___java_lang_String_boolean_R_java_lang_NumberFormatException(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT s, JAVA_BOOLEAN strict);\n\n"); String[][] duplicateRuntimeSymbols = new String[][] { {"JAVA_BOOLEAN java_lang_String_equals___java_lang_Object_R_boolean", "JAVA_BOOLEAN java_lang_String_equals___java_lang_Object_R_boolean\\(.*?\n}\\n"}, diff --git a/vm/tests/src/test/java/com/codename1/tools/translator/StampedLockIntegrationTest.java b/vm/tests/src/test/java/com/codename1/tools/translator/StampedLockIntegrationTest.java index dfc0169a16..25ac94a183 100644 --- a/vm/tests/src/test/java/com/codename1/tools/translator/StampedLockIntegrationTest.java +++ b/vm/tests/src/test/java/com/codename1/tools/translator/StampedLockIntegrationTest.java @@ -739,7 +739,12 @@ private void patchHashMapNativeMethods(Path srcRoot) throws java.io.IOException String content = new String(Files.readAllBytes(nativeMethods), StandardCharsets.UTF_8); content = content.replaceFirst("#include \\\"java_lang_System.h\\\"\\n\\n", - "#include \"java_lang_System.h\"\n\nJAVA_OBJECT java_lang_Class_getName___R_java_lang_String(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT cls);\nJAVA_OBJECT java_lang_Throwable_getStack___R_java_lang_String(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT t);\n\n"); + "#include \"java_lang_System.h\"\n\n" + + "JAVA_OBJECT java_lang_Class_getName___R_java_lang_String(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT cls);\n" + + "JAVA_OBJECT java_lang_Throwable_getStack___R_java_lang_String(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT t);\n" + + "JAVA_VOID java_lang_Thread_runImpl___long(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT t, JAVA_LONG id);\n" + + "JAVA_OBJECT java_lang_String_toCharNoCopy___R_char_1ARRAY(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT str);\n" + + "JAVA_OBJECT java_lang_StringToReal_invalidReal___java_lang_String_boolean_R_java_lang_NumberFormatException(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT s, JAVA_BOOLEAN strict);\n\n"); String[][] duplicateRuntimeSymbols = new String[][] { {"JAVA_BOOLEAN java_lang_String_equals___java_lang_Object_R_boolean", "JAVA_BOOLEAN java_lang_String_equals___java_lang_Object_R_boolean\\(.*?\n}\\n"}, From b4e6e497d30601355ff42365ae6c37a5f4f3b721 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Sun, 4 Jan 2026 04:56:45 +0200 Subject: [PATCH 30/40] Add Throwable stack prototypes to clean-target native patching --- .../tools/translator/ReadWriteLockIntegrationTest.java | 2 ++ .../codename1/tools/translator/StampedLockIntegrationTest.java | 2 ++ 2 files changed, 4 insertions(+) diff --git a/vm/tests/src/test/java/com/codename1/tools/translator/ReadWriteLockIntegrationTest.java b/vm/tests/src/test/java/com/codename1/tools/translator/ReadWriteLockIntegrationTest.java index fa6b9c4fa5..970ff42317 100644 --- a/vm/tests/src/test/java/com/codename1/tools/translator/ReadWriteLockIntegrationTest.java +++ b/vm/tests/src/test/java/com/codename1/tools/translator/ReadWriteLockIntegrationTest.java @@ -771,6 +771,8 @@ private void patchHashMapNativeMethods(Path srcRoot) throws java.io.IOException "#include \"java_lang_System.h\"\n\n" + "JAVA_OBJECT java_lang_Class_getName___R_java_lang_String(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT cls);\n" + "JAVA_OBJECT java_lang_Throwable_getStack___R_java_lang_String(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT t);\n" + + "JAVA_OBJECT get_field_java_lang_Throwable_stack(JAVA_OBJECT t);\n" + + "JAVA_VOID set_field_java_lang_Throwable_stack(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT val, JAVA_OBJECT t);\n" + "JAVA_VOID java_lang_Thread_runImpl___long(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT t, JAVA_LONG id);\n" + "JAVA_OBJECT java_lang_String_toCharNoCopy___R_char_1ARRAY(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT str);\n" + "JAVA_OBJECT java_lang_StringToReal_invalidReal___java_lang_String_boolean_R_java_lang_NumberFormatException(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT s, JAVA_BOOLEAN strict);\n\n"); diff --git a/vm/tests/src/test/java/com/codename1/tools/translator/StampedLockIntegrationTest.java b/vm/tests/src/test/java/com/codename1/tools/translator/StampedLockIntegrationTest.java index 25ac94a183..f1c4f60bb8 100644 --- a/vm/tests/src/test/java/com/codename1/tools/translator/StampedLockIntegrationTest.java +++ b/vm/tests/src/test/java/com/codename1/tools/translator/StampedLockIntegrationTest.java @@ -742,6 +742,8 @@ private void patchHashMapNativeMethods(Path srcRoot) throws java.io.IOException "#include \"java_lang_System.h\"\n\n" + "JAVA_OBJECT java_lang_Class_getName___R_java_lang_String(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT cls);\n" + "JAVA_OBJECT java_lang_Throwable_getStack___R_java_lang_String(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT t);\n" + + "JAVA_OBJECT get_field_java_lang_Throwable_stack(JAVA_OBJECT t);\n" + + "JAVA_VOID set_field_java_lang_Throwable_stack(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT val, JAVA_OBJECT t);\n" + "JAVA_VOID java_lang_Thread_runImpl___long(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT t, JAVA_LONG id);\n" + "JAVA_OBJECT java_lang_String_toCharNoCopy___R_char_1ARRAY(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT str);\n" + "JAVA_OBJECT java_lang_StringToReal_invalidReal___java_lang_String_boolean_R_java_lang_NumberFormatException(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT s, JAVA_BOOLEAN strict);\n\n"); From fa8902158cec64dc32439b91294818ee79d9b9cd Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Sun, 4 Jan 2026 05:47:51 +0200 Subject: [PATCH 31/40] Add primitive wrapper mocks to lock integration test --- .../tools/translator/LockIntegrationTest.java | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/vm/tests/src/test/java/com/codename1/tools/translator/LockIntegrationTest.java b/vm/tests/src/test/java/com/codename1/tools/translator/LockIntegrationTest.java index bf997ba4ad..3e0f8eb703 100644 --- a/vm/tests/src/test/java/com/codename1/tools/translator/LockIntegrationTest.java +++ b/vm/tests/src/test/java/com/codename1/tools/translator/LockIntegrationTest.java @@ -150,6 +150,32 @@ private void writeMockJavaClasses(Path sourceDir) throws Exception { " public String(char[] v) { value = v; count=v.length; }\n" + "}\n").getBytes(StandardCharsets.UTF_8)); + // Primitive wrappers + Files.write(lang.resolve("Boolean.java"), ("package java.lang;\n" + + "public final class Boolean {\n" + + " private final boolean value;\n" + + " public Boolean(boolean value) { this.value = value; }\n" + + " public boolean booleanValue() { return value; }\n" + + "}\n").getBytes(StandardCharsets.UTF_8)); + Files.write(lang.resolve("Byte.java"), ("package java.lang;\n" + + "public final class Byte {\n" + + " private final byte value;\n" + + " public Byte(byte value) { this.value = value; }\n" + + " public byte byteValue() { return value; }\n" + + "}\n").getBytes(StandardCharsets.UTF_8)); + Files.write(lang.resolve("Short.java"), ("package java.lang;\n" + + "public final class Short {\n" + + " private final short value;\n" + + " public Short(short value) { this.value = value; }\n" + + " public short shortValue() { return value; }\n" + + "}\n").getBytes(StandardCharsets.UTF_8)); + Files.write(lang.resolve("Character.java"), ("package java.lang;\n" + + "public final class Character {\n" + + " private final char value;\n" + + " public Character(char value) { this.value = value; }\n" + + " public char charValue() { return value; }\n" + + "}\n").getBytes(StandardCharsets.UTF_8)); + // java.lang.Class Files.write(lang.resolve("Class.java"), ("package java.lang;\n" + "public final class Class {}\n").getBytes(StandardCharsets.UTF_8)); From f38d517f2cd7e475d09a55d054df635cccc68477 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Sun, 4 Jan 2026 07:23:42 +0200 Subject: [PATCH 32/40] Add Integer stub to Lock integration mock API --- .../com/codename1/tools/translator/LockIntegrationTest.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/vm/tests/src/test/java/com/codename1/tools/translator/LockIntegrationTest.java b/vm/tests/src/test/java/com/codename1/tools/translator/LockIntegrationTest.java index 3e0f8eb703..33388b59ae 100644 --- a/vm/tests/src/test/java/com/codename1/tools/translator/LockIntegrationTest.java +++ b/vm/tests/src/test/java/com/codename1/tools/translator/LockIntegrationTest.java @@ -163,6 +163,12 @@ private void writeMockJavaClasses(Path sourceDir) throws Exception { " public Byte(byte value) { this.value = value; }\n" + " public byte byteValue() { return value; }\n" + "}\n").getBytes(StandardCharsets.UTF_8)); + Files.write(lang.resolve("Integer.java"), ("package java.lang;\n" + + "public final class Integer {\n" + + " private final int value;\n" + + " public Integer(int value) { this.value = value; }\n" + + " public int intValue() { return value; }\n" + + "}\n").getBytes(StandardCharsets.UTF_8)); Files.write(lang.resolve("Short.java"), ("package java.lang;\n" + "public final class Short {\n" + " private final short value;\n" + From a7aef1a8acfd86e867f5c421bb27691e784b343b Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Sun, 4 Jan 2026 19:40:37 +0200 Subject: [PATCH 33/40] Add Long wrapper to Lock integration mocks --- .../com/codename1/tools/translator/LockIntegrationTest.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/vm/tests/src/test/java/com/codename1/tools/translator/LockIntegrationTest.java b/vm/tests/src/test/java/com/codename1/tools/translator/LockIntegrationTest.java index 33388b59ae..d2fdae3f26 100644 --- a/vm/tests/src/test/java/com/codename1/tools/translator/LockIntegrationTest.java +++ b/vm/tests/src/test/java/com/codename1/tools/translator/LockIntegrationTest.java @@ -169,6 +169,12 @@ private void writeMockJavaClasses(Path sourceDir) throws Exception { " public Integer(int value) { this.value = value; }\n" + " public int intValue() { return value; }\n" + "}\n").getBytes(StandardCharsets.UTF_8)); + Files.write(lang.resolve("Long.java"), ("package java.lang;\n" + + "public final class Long {\n" + + " private final long value;\n" + + " public Long(long value) { this.value = value; }\n" + + " public long longValue() { return value; }\n" + + "}\n").getBytes(StandardCharsets.UTF_8)); Files.write(lang.resolve("Short.java"), ("package java.lang;\n" + "public final class Short {\n" + " private final short value;\n" + From e9cbe12f136a5f7b8dd1500c5f5706376e7fb9ba Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Sun, 4 Jan 2026 20:55:54 +0200 Subject: [PATCH 34/40] Add float/double wrappers to Lock integration mock --- .../tools/translator/LockIntegrationTest.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/vm/tests/src/test/java/com/codename1/tools/translator/LockIntegrationTest.java b/vm/tests/src/test/java/com/codename1/tools/translator/LockIntegrationTest.java index d2fdae3f26..fb0dadf996 100644 --- a/vm/tests/src/test/java/com/codename1/tools/translator/LockIntegrationTest.java +++ b/vm/tests/src/test/java/com/codename1/tools/translator/LockIntegrationTest.java @@ -169,6 +169,18 @@ private void writeMockJavaClasses(Path sourceDir) throws Exception { " public Integer(int value) { this.value = value; }\n" + " public int intValue() { return value; }\n" + "}\n").getBytes(StandardCharsets.UTF_8)); + Files.write(lang.resolve("Float.java"), ("package java.lang;\n" + + "public final class Float {\n" + + " private final float value;\n" + + " public Float(float value) { this.value = value; }\n" + + " public float floatValue() { return value; }\n" + + "}\n").getBytes(StandardCharsets.UTF_8)); + Files.write(lang.resolve("Double.java"), ("package java.lang;\n" + + "public final class Double {\n" + + " private final double value;\n" + + " public Double(double value) { this.value = value; }\n" + + " public double doubleValue() { return value; }\n" + + "}\n").getBytes(StandardCharsets.UTF_8)); Files.write(lang.resolve("Long.java"), ("package java.lang;\n" + "public final class Long {\n" + " private final long value;\n" + From 9bd30f704426989bf404d997e65c2c34eb8a3063 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Mon, 5 Jan 2026 06:23:43 +0200 Subject: [PATCH 35/40] Add ArrayIndexOutOfBoundsException stub for lock integration --- .../com/codename1/tools/translator/LockIntegrationTest.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/vm/tests/src/test/java/com/codename1/tools/translator/LockIntegrationTest.java b/vm/tests/src/test/java/com/codename1/tools/translator/LockIntegrationTest.java index fb0dadf996..d8ed7ee271 100644 --- a/vm/tests/src/test/java/com/codename1/tools/translator/LockIntegrationTest.java +++ b/vm/tests/src/test/java/com/codename1/tools/translator/LockIntegrationTest.java @@ -237,6 +237,9 @@ private void writeMockJavaClasses(Path sourceDir) throws Exception { Files.write(lang.resolve("RuntimeException.java"), "package java.lang; public class RuntimeException extends Exception { public RuntimeException() {} public RuntimeException(String s) {} }".getBytes(StandardCharsets.UTF_8)); Files.write(lang.resolve("InterruptedException.java"), "package java.lang; public class InterruptedException extends Exception { public InterruptedException() {} }".getBytes(StandardCharsets.UTF_8)); Files.write(lang.resolve("NullPointerException.java"), "package java.lang; public class NullPointerException extends RuntimeException { public NullPointerException() {} }".getBytes(StandardCharsets.UTF_8)); + Files.write(lang.resolve("ArrayIndexOutOfBoundsException.java"), + "package java.lang; public class ArrayIndexOutOfBoundsException extends RuntimeException { public ArrayIndexOutOfBoundsException() {} public ArrayIndexOutOfBoundsException(String s) { super(s); } }" + .getBytes(StandardCharsets.UTF_8)); Files.write(lang.resolve("IllegalMonitorStateException.java"), "package java.lang; public class IllegalMonitorStateException extends RuntimeException { public IllegalMonitorStateException() {} }".getBytes(StandardCharsets.UTF_8)); Files.write(lang.resolve("IllegalArgumentException.java"), "package java.lang; public class IllegalArgumentException extends RuntimeException { public IllegalArgumentException(String s) {} }".getBytes(StandardCharsets.UTF_8)); From 9f6858835c4cec372f0f77919c67e298b7d8ab92 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Mon, 5 Jan 2026 07:17:51 +0200 Subject: [PATCH 36/40] Add GC stubs to lock integration System mock --- .../com/codename1/tools/translator/LockIntegrationTest.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/vm/tests/src/test/java/com/codename1/tools/translator/LockIntegrationTest.java b/vm/tests/src/test/java/com/codename1/tools/translator/LockIntegrationTest.java index d8ed7ee271..a16c77a1b5 100644 --- a/vm/tests/src/test/java/com/codename1/tools/translator/LockIntegrationTest.java +++ b/vm/tests/src/test/java/com/codename1/tools/translator/LockIntegrationTest.java @@ -229,6 +229,9 @@ private void writeMockJavaClasses(Path sourceDir) throws Exception { Files.write(lang.resolve("System.java"), ("package java.lang;\n" + "public final class System {\n" + " public static native long currentTimeMillis();\n" + + " public static void gc() {}\n" + + " public static void startGCThread() {}\n" + + " public static Thread gcThreadInstance;\n" + "}\n").getBytes(StandardCharsets.UTF_8)); // Exceptions From a43fd7175f5a1db361c2a4d36cbbd0495160ccf7 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Mon, 5 Jan 2026 07:53:08 +0200 Subject: [PATCH 37/40] Add getBytes stub to Lock integration String mock --- .../com/codename1/tools/translator/LockIntegrationTest.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/vm/tests/src/test/java/com/codename1/tools/translator/LockIntegrationTest.java b/vm/tests/src/test/java/com/codename1/tools/translator/LockIntegrationTest.java index a16c77a1b5..fdd9e2c789 100644 --- a/vm/tests/src/test/java/com/codename1/tools/translator/LockIntegrationTest.java +++ b/vm/tests/src/test/java/com/codename1/tools/translator/LockIntegrationTest.java @@ -148,6 +148,11 @@ private void writeMockJavaClasses(Path sourceDir) throws Exception { " private int offset;\n" + " private int count;\n" + " public String(char[] v) { value = v; count=v.length; }\n" + + " public byte[] getBytes(String charsetName) {\n" + + " byte[] out = new byte[count];\n" + + " for (int i = 0; i < count; i++) { out[i] = (byte)value[offset + i]; }\n" + + " return out;\n" + + " }\n" + "}\n").getBytes(StandardCharsets.UTF_8)); // Primitive wrappers From 4bbc16c1eed2967966df45f687964dd6fc5150a0 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Mon, 5 Jan 2026 18:02:02 +0200 Subject: [PATCH 38/40] Add File stub to lock integration mock --- .../tools/translator/LockIntegrationTest.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/vm/tests/src/test/java/com/codename1/tools/translator/LockIntegrationTest.java b/vm/tests/src/test/java/com/codename1/tools/translator/LockIntegrationTest.java index fdd9e2c789..c429874322 100644 --- a/vm/tests/src/test/java/com/codename1/tools/translator/LockIntegrationTest.java +++ b/vm/tests/src/test/java/com/codename1/tools/translator/LockIntegrationTest.java @@ -254,6 +254,20 @@ private void writeMockJavaClasses(Path sourceDir) throws Exception { // java.io.Serializable Files.write(io.resolve("Serializable.java"), "package java.io; public interface Serializable {}".getBytes(StandardCharsets.UTF_8)); + // java.io.File (minimal stub to force translator header generation) + Files.write(io.resolve("File.java"), ("package java.io;\n" + + "public class File {\n" + + " public static final String separator = \"/\";\n" + + " public static final char separatorChar = '/';\n" + + " public static final String pathSeparator = \":\";\n" + + " public static final char pathSeparatorChar = ':';\n" + + " private final String path;\n" + + " public File(String pathname) { this.path = pathname == null ? \"\" : pathname; }\n" + + " public File(File parent, String child) { this(parent == null ? child : parent.getPath() + separator + child); }\n" + + " public File(String parent, String child) { this(parent == null ? child : parent + separator + child); }\n" + + " public String getPath() { return path; }\n" + + "}\n").getBytes(StandardCharsets.UTF_8)); + // java.util.Collection Files.write(util.resolve("Collection.java"), "package java.util; public interface Collection {}".getBytes(StandardCharsets.UTF_8)); From 94b6511cffff48922484f0329e4dadfc9cef3c35 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Mon, 5 Jan 2026 20:51:11 +0200 Subject: [PATCH 39/40] Remove unused file stream natives from Lock integration harness --- .../CleanTargetIntegrationTest.java | 13 +++++ .../tools/translator/LockIntegrationTest.java | 53 ++++++++++++++++++- 2 files changed, 65 insertions(+), 1 deletion(-) diff --git a/vm/tests/src/test/java/com/codename1/tools/translator/CleanTargetIntegrationTest.java b/vm/tests/src/test/java/com/codename1/tools/translator/CleanTargetIntegrationTest.java index 48578de64e..142925bccb 100644 --- a/vm/tests/src/test/java/com/codename1/tools/translator/CleanTargetIntegrationTest.java +++ b/vm/tests/src/test/java/com/codename1/tools/translator/CleanTargetIntegrationTest.java @@ -348,6 +348,19 @@ static void patchFileHeader(Path srcRoot) throws IOException { } } + static void removeTranslatorFileNatives(Path srcRoot) throws IOException { + String[] fileArtifacts = { + "java_io_File.c", "java_io_File.h", + "java_io_FileInputStream.c", "java_io_FileInputStream.h", + "java_io_FileOutputStream.c", "java_io_FileOutputStream.h", + "java_io_FileWriter.c", "java_io_FileWriter.h", + "java_io_FileStreams.c", "java_io_FileStreams.h" + }; + for (String name : fileArtifacts) { + Files.deleteIfExists(srcRoot.resolve(name)); + } + } + static void writeRuntimeStubs(Path srcRoot) throws IOException { Path objectHeader = srcRoot.resolve("java_lang_Object.h"); if (!Files.exists(objectHeader)) { diff --git a/vm/tests/src/test/java/com/codename1/tools/translator/LockIntegrationTest.java b/vm/tests/src/test/java/com/codename1/tools/translator/LockIntegrationTest.java index c429874322..310da84027 100644 --- a/vm/tests/src/test/java/com/codename1/tools/translator/LockIntegrationTest.java +++ b/vm/tests/src/test/java/com/codename1/tools/translator/LockIntegrationTest.java @@ -88,6 +88,7 @@ void verifiesLockAndReentrantLockBehavior(CompilerHelper.CompilerConfig config) Path srcRoot = distDir.resolve("LockTestApp-src"); CleanTargetIntegrationTest.patchCn1Globals(srcRoot); + CleanTargetIntegrationTest.removeTranslatorFileNatives(srcRoot); writeRuntimeStubs(srcRoot); replaceLibraryWithExecutableTarget(cmakeLists, srcRoot.getFileName().toString()); @@ -121,10 +122,12 @@ private void writeMockJavaClasses(Path sourceDir) throws Exception { Path lang = sourceDir.resolve("java/lang"); Path util = sourceDir.resolve("java/util"); Path concurrent = sourceDir.resolve("java/util/concurrent"); + Path text = sourceDir.resolve("java/text"); Path io = sourceDir.resolve("java/io"); Files.createDirectories(lang); Files.createDirectories(util); Files.createDirectories(concurrent); + Files.createDirectories(text); Files.createDirectories(io); // java.lang.Object @@ -154,6 +157,14 @@ private void writeMockJavaClasses(Path sourceDir) throws Exception { " return out;\n" + " }\n" + "}\n").getBytes(StandardCharsets.UTF_8)); + Files.write(lang.resolve("StringBuilder.java"), ("package java.lang;\n" + + "public class StringBuilder {\n" + + " private String value = \"\";\n" + + " public StringBuilder() {}\n" + + " public StringBuilder(String s) { if (s != null) value = s; }\n" + + " public StringBuilder append(String s) { value += s; return this; }\n" + + " public String toString() { return value; }\n" + + "}\n").getBytes(StandardCharsets.UTF_8)); // Primitive wrappers Files.write(lang.resolve("Boolean.java"), ("package java.lang;\n" + @@ -239,17 +250,24 @@ private void writeMockJavaClasses(Path sourceDir) throws Exception { " public static Thread gcThreadInstance;\n" + "}\n").getBytes(StandardCharsets.UTF_8)); + // java.lang.StringToReal + Files.write(lang.resolve("StringToReal.java"), ("package java.lang;\n" + + "public final class StringToReal {\n" + + " public static NumberFormatException invalidReal(String s, boolean b) { return new NumberFormatException(s); }\n" + + "}\n").getBytes(StandardCharsets.UTF_8)); + // Exceptions Files.write(lang.resolve("Throwable.java"), "package java.lang; public class Throwable { public Throwable() {} public Throwable(String s) {} }".getBytes(StandardCharsets.UTF_8)); Files.write(lang.resolve("Exception.java"), "package java.lang; public class Exception extends Throwable { public Exception() {} public Exception(String s) {} }".getBytes(StandardCharsets.UTF_8)); Files.write(lang.resolve("RuntimeException.java"), "package java.lang; public class RuntimeException extends Exception { public RuntimeException() {} public RuntimeException(String s) {} }".getBytes(StandardCharsets.UTF_8)); + Files.write(lang.resolve("NumberFormatException.java"), "package java.lang; public class NumberFormatException extends IllegalArgumentException { public NumberFormatException() {} public NumberFormatException(String s) { super(s); } }".getBytes(StandardCharsets.UTF_8)); Files.write(lang.resolve("InterruptedException.java"), "package java.lang; public class InterruptedException extends Exception { public InterruptedException() {} }".getBytes(StandardCharsets.UTF_8)); Files.write(lang.resolve("NullPointerException.java"), "package java.lang; public class NullPointerException extends RuntimeException { public NullPointerException() {} }".getBytes(StandardCharsets.UTF_8)); Files.write(lang.resolve("ArrayIndexOutOfBoundsException.java"), "package java.lang; public class ArrayIndexOutOfBoundsException extends RuntimeException { public ArrayIndexOutOfBoundsException() {} public ArrayIndexOutOfBoundsException(String s) { super(s); } }" .getBytes(StandardCharsets.UTF_8)); Files.write(lang.resolve("IllegalMonitorStateException.java"), "package java.lang; public class IllegalMonitorStateException extends RuntimeException { public IllegalMonitorStateException() {} }".getBytes(StandardCharsets.UTF_8)); - Files.write(lang.resolve("IllegalArgumentException.java"), "package java.lang; public class IllegalArgumentException extends RuntimeException { public IllegalArgumentException(String s) {} }".getBytes(StandardCharsets.UTF_8)); + Files.write(lang.resolve("IllegalArgumentException.java"), "package java.lang; public class IllegalArgumentException extends RuntimeException { public IllegalArgumentException() {} public IllegalArgumentException(String s) {} }".getBytes(StandardCharsets.UTF_8)); // java.io.Serializable Files.write(io.resolve("Serializable.java"), "package java.io; public interface Serializable {}".getBytes(StandardCharsets.UTF_8)); @@ -271,6 +289,30 @@ private void writeMockJavaClasses(Path sourceDir) throws Exception { // java.util.Collection Files.write(util.resolve("Collection.java"), "package java.util; public interface Collection {}".getBytes(StandardCharsets.UTF_8)); + // java.util.Map + Files.write(util.resolve("Map.java"), ("package java.util;\n" + + "public interface Map {\n" + + " V put(K key, V value);\n" + + " V get(Object key);\n" + + " int size();\n" + + "}\n").getBytes(StandardCharsets.UTF_8)); + + // java.util.HashMap (minimal bucket list to trigger header generation) + Files.write(util.resolve("HashMap.java"), ("package java.util;\n" + + "public class HashMap implements Map {\n" + + " static class Entry {\n" + + " final K key;\n" + + " V value;\n" + + " Entry next;\n" + + " Entry(K k, V v, Entry n) { key = k; value = v; next = n; }\n" + + " }\n" + + " private Entry[] table;\n" + + " public HashMap() { table = new Entry[0]; }\n" + + " public V put(K key, V value) { return value; }\n" + + " public V get(Object key) { return null; }\n" + + " public int size() { return 0; }\n" + + "}\n").getBytes(StandardCharsets.UTF_8)); + // java.util.Date Files.write(util.resolve("Date.java"), "package java.util; public class Date { public long getTime() { return 0; } }".getBytes(StandardCharsets.UTF_8)); @@ -286,15 +328,24 @@ private void writeMockJavaClasses(Path sourceDir) throws Exception { " public long toNanos(long d) { return d; }\n" + " public long toMillis(long d) { return d; }\n" + "}\n").getBytes(StandardCharsets.UTF_8)); + + // java.text.DateFormat (placeholder) + Files.write(text.resolve("DateFormat.java"), "package java.text; public class DateFormat {}".getBytes(StandardCharsets.UTF_8)); } private String lockTestAppSource() { return "import java.util.concurrent.locks.*;\n" + "import java.util.concurrent.TimeUnit;\n" + + "import java.util.HashMap;\n" + + "import java.text.DateFormat;\n" + "public class LockTestApp {\n" + " private static native void report(String msg);\n" + " \n" + " public static void main(String[] args) {\n" + + " DateFormat df = null;\n" + + " HashMap map = new HashMap<>();\n" + + " map.size();\n" + + " StringToReal.invalidReal(\"0\", false);\n" + " testBasicLock();\n" + " testReentrancy();\n" + " testTryLock();\n" + From 666048fcfca5d9eb2e8d3ff73824e077027e9d25 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Tue, 6 Jan 2026 03:59:12 +0200 Subject: [PATCH 40/40] Keep java.io.File outputs for Lock integration builds --- .../codename1/tools/translator/CleanTargetIntegrationTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/vm/tests/src/test/java/com/codename1/tools/translator/CleanTargetIntegrationTest.java b/vm/tests/src/test/java/com/codename1/tools/translator/CleanTargetIntegrationTest.java index 142925bccb..18392ca94b 100644 --- a/vm/tests/src/test/java/com/codename1/tools/translator/CleanTargetIntegrationTest.java +++ b/vm/tests/src/test/java/com/codename1/tools/translator/CleanTargetIntegrationTest.java @@ -350,7 +350,6 @@ static void patchFileHeader(Path srcRoot) throws IOException { static void removeTranslatorFileNatives(Path srcRoot) throws IOException { String[] fileArtifacts = { - "java_io_File.c", "java_io_File.h", "java_io_FileInputStream.c", "java_io_FileInputStream.h", "java_io_FileOutputStream.c", "java_io_FileOutputStream.h", "java_io_FileWriter.c", "java_io_FileWriter.h",