Date: Sun, 26 Jan 2025 12:37:18 +0000
Subject: [PATCH 05/13] SwiftArena is now a segment allocator
---
SwiftKit/src/main/java/org/swift/swiftkit/SwiftArena.java | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftArena.java b/SwiftKit/src/main/java/org/swift/swiftkit/SwiftArena.java
index 02c1b599..fa89fd1b 100644
--- a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftArena.java
+++ b/SwiftKit/src/main/java/org/swift/swiftkit/SwiftArena.java
@@ -14,6 +14,7 @@
package org.swift.swiftkit;
+import java.lang.foreign.SegmentAllocator;
import java.util.concurrent.ThreadFactory;
/**
@@ -23,7 +24,7 @@
* A confined arena has an associated owner thread that confines some operations to
* associated owner thread such as {@link ClosableSwiftArena#close()}.
*/
-public interface SwiftArena {
+public interface SwiftArena extends SegmentAllocator {
static ClosableSwiftArena ofConfined() {
return new ConfinedSwiftMemorySession(Thread.currentThread());
@@ -65,4 +66,3 @@ public UnexpectedRetainCountException(Object resource, long retainCount, int exp
).formatted(resource, expectedRetainCount, retainCount));
}
}
-
From 6a07e93cdd9a7d17b782bed4c2d0c4bb904aed96 Mon Sep 17 00:00:00 2001
From: Konrad `ktoso` Malawski
Date: Sun, 26 Jan 2025 12:37:49 +0000
Subject: [PATCH 06/13] Add $destroyed flag to wrapper types to prevent
use-after-free bugs
---
.../org/swift/swiftkit/SwiftInstance.java | 10 +++++++
.../org/swift/swiftkit/AutoArenaTest.java | 27 ++++++++++++-------
2 files changed, 28 insertions(+), 9 deletions(-)
diff --git a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftInstance.java b/SwiftKit/src/main/java/org/swift/swiftkit/SwiftInstance.java
index 08cce158..de642a78 100644
--- a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftInstance.java
+++ b/SwiftKit/src/main/java/org/swift/swiftkit/SwiftInstance.java
@@ -16,6 +16,7 @@
import java.lang.foreign.GroupLayout;
import java.lang.foreign.MemorySegment;
+import java.util.concurrent.atomic.AtomicBoolean;
public interface SwiftInstance {
@@ -39,4 +40,13 @@ public interface SwiftInstance {
default boolean isReferenceType() {
return this instanceof SwiftHeapObject;
}
+
+ /**
+ * Exposes a boolean value which can be used to indicate if the object was destroyed.
+ *
+ * This is exposing the object, rather than performing the action because we don't want to accidentally
+ * form a strong reference to the {@code SwiftInstance} which could prevent the cleanup from running,
+ * if using an GC managed instance (e.g. using an {@link AutoSwiftMemorySession}.
+ */
+ AtomicBoolean $statusDestroyedFlag();
}
diff --git a/SwiftKit/src/test/java/org/swift/swiftkit/AutoArenaTest.java b/SwiftKit/src/test/java/org/swift/swiftkit/AutoArenaTest.java
index 7ba215ab..c57daf16 100644
--- a/SwiftKit/src/test/java/org/swift/swiftkit/AutoArenaTest.java
+++ b/SwiftKit/src/test/java/org/swift/swiftkit/AutoArenaTest.java
@@ -18,12 +18,11 @@
import java.lang.foreign.GroupLayout;
import java.lang.foreign.MemorySegment;
-import java.lang.ref.Cleaner;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicBoolean;
public class AutoArenaTest {
-
@Test
@SuppressWarnings("removal") // System.runFinalization() will be removed
public void cleaner_releases_native_resource() {
@@ -34,16 +33,21 @@ public void cleaner_releases_native_resource() {
// we're retaining the `object`, register it with the arena:
AutoSwiftMemorySession arena = (AutoSwiftMemorySession) SwiftArena.ofAuto();
- arena.register(object, new SwiftHeapObjectCleanup(object.$memorySegment(), object.$swiftType()) {
- @Override
- public void run() throws UnexpectedRetainCountException {
- cleanupLatch.countDown();
- }
- });
+
+ var statusDestroyedFlag = object.$statusDestroyedFlag();
+ Runnable markAsDestroyed = () -> statusDestroyedFlag.set(true);
+
+ arena.register(object,
+ new SwiftHeapObjectCleanup(object.$memorySegment(), object.$swiftType(), markAsDestroyed) {
+ @Override
+ public void run() throws UnexpectedRetainCountException {
+ cleanupLatch.countDown();
+ }
+ });
// Release the object and hope it gets GC-ed soon
- //noinspection UnusedAssignment
+ // noinspection UnusedAssignment
object = null;
var i = 1_000;
@@ -76,5 +80,10 @@ public FakeSwiftHeapObject() {
public SwiftAnyType $swiftType() {
return null;
}
+
+ @Override
+ public AtomicBoolean $statusDestroyedFlag() {
+ return new AtomicBoolean();
+ }
}
}
From 185a88076c4a6ef2ccc49c2dac4a3d9f350ed8cc Mon Sep 17 00:00:00 2001
From: Konrad `ktoso` Malawski
Date: Sun, 26 Jan 2025 12:38:25 +0000
Subject: [PATCH 07/13] Use varHandle instead of offset for reading data off
VWT
---
SwiftKit/src/main/java/org/swift/swiftkit/SwiftKit.java | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftKit.java b/SwiftKit/src/main/java/org/swift/swiftkit/SwiftKit.java
index 858f5500..dadb87ff 100644
--- a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftKit.java
+++ b/SwiftKit/src/main/java/org/swift/swiftkit/SwiftKit.java
@@ -20,6 +20,7 @@
import java.io.IOException;
import java.lang.foreign.*;
import java.lang.invoke.MethodHandle;
+import java.lang.invoke.VarHandle;
import java.nio.file.CopyOption;
import java.nio.file.FileSystems;
import java.nio.file.Files;
@@ -437,6 +438,14 @@ public static long getSwiftInt(MemorySegment memorySegment, long offset) {
}
}
+ public static long getSwiftInt(MemorySegment memorySegment, VarHandle handle) {
+ if (SwiftValueLayout.SWIFT_INT == ValueLayout.JAVA_LONG) {
+ return (long) handle.get(memorySegment, 0);
+ } else {
+ return (int) handle.get(memorySegment, 0);
+ }
+ }
+
private static class swift_getTypeName {
From 79e1339cb072df4c51b0f42dd0d3d2eb9645fb64 Mon Sep 17 00:00:00 2001
From: Konrad `ktoso` Malawski
Date: Mon, 27 Jan 2025 11:00:38 +0000
Subject: [PATCH 08/13] testing: skip empty lines in output matching
---
Tests/JExtractSwiftTests/Asserts/TextAssertions.swift | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift b/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift
index 0efff0c7..8d1e606d 100644
--- a/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift
+++ b/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift
@@ -131,8 +131,12 @@ func assertOutput(
line: Int = #line,
column: Int = #column
) {
- let gotLines = got.split(separator: "\n")
- let expectedLines = expected.split(separator: "\n")
+ let gotLines = got.split(separator: "\n").filter { l in
+ l.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines).count > 0
+ }
+ let expectedLines = expected.split(separator: "\n").filter { l in
+ l.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines).count > 0
+ }
var diffLineNumbers: [Int] = []
From 3f2922267502d2b00861253684630d3149776da7 Mon Sep 17 00:00:00 2001
From: Konrad `ktoso` Malawski
Date: Mon, 27 Jan 2025 11:45:52 +0000
Subject: [PATCH 09/13] gitignore vscode build outputs
---
.gitignore | 1 +
1 file changed, 1 insertion(+)
diff --git a/.gitignore b/.gitignore
index a20b93ce..6712182f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,6 +12,7 @@ DerivedData/
bin/
BuildLogic/out/
.index-build
+.build-vscode
# Ignore gradle build artifacts
.gradle
From 5984bb2e0159841cebb27c062a6016b3bb644d81 Mon Sep 17 00:00:00 2001
From: Konrad `ktoso` Malawski
Date: Mon, 27 Jan 2025 11:46:08 +0000
Subject: [PATCH 10/13] test fixups
---
.../com/example/swift/MySwiftClassTest.java | 3 ++-
.../MySwiftLibrary/MySwiftStruct.swift | 22 +++++++++++++++---
.../com/example/swift/HelloJava2Swift.java | 20 ++++++++--------
.../com/example/swift/MySwiftClassTest.java | 1 -
.../swift/swiftkit}/MySwiftStructTest.java | 22 +++++++++---------
Sources/JExtractSwift/ImportedDecls.swift | 12 ++++++++--
.../Swift2JavaTranslator+MemoryLayouts.swift | 12 ++++++----
.../Swift2JavaTranslator+Printing.swift | 23 +++++++++----------
.../JExtractSwift/SwiftThunkTranslator.swift | 18 +++++++--------
.../swiftkit/SwiftValueWitnessTable.java | 2 +-
.../Asserts/TextAssertions.swift | 4 +++-
.../ClassPrintingTests.swift | 1 -
.../FuncCallbackImportTests.swift | 1 +
.../MethodImportTests.swift | 13 ++++++++++-
.../JExtractSwiftTests/MethodThunkTests.swift | 6 +++--
.../StringPassingTests.swift | 3 +--
.../VariableImportTests.swift | 20 +++++++++-------
17 files changed, 113 insertions(+), 70 deletions(-)
rename Samples/SwiftKitSampleApp/src/test/java/{com/example/swift => org/swift/swiftkit}/MySwiftStructTest.java (69%)
diff --git a/Samples/SwiftAndJavaJarSampleLib/src/test/java/com/example/swift/MySwiftClassTest.java b/Samples/SwiftAndJavaJarSampleLib/src/test/java/com/example/swift/MySwiftClassTest.java
index fa17ef1a..a52879bb 100644
--- a/Samples/SwiftAndJavaJarSampleLib/src/test/java/com/example/swift/MySwiftClassTest.java
+++ b/Samples/SwiftAndJavaJarSampleLib/src/test/java/com/example/swift/MySwiftClassTest.java
@@ -45,7 +45,8 @@ void test_MySwiftClass_voidMethod() {
MySwiftClass o = new MySwiftClass(12, 42);
o.voidMethod();
} catch (Throwable throwable) {
- checkPaths(throwable);
+ throw throwable;
+// checkPaths(throwable);
}
}
diff --git a/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftStruct.swift b/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftStruct.swift
index 85bcb4c7..8feb3d2b 100644
--- a/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftStruct.swift
+++ b/Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftStruct.swift
@@ -14,10 +14,12 @@
public struct MySwiftStruct {
- public var number: Int
+ private var cap: Int
+ private var len: Int
- public init(number: Int) {
- self.number = number
+ public init(cap: Int, len: Int) {
+ self.cap = cap
+ self.len = len
}
public func voidMethod() {
@@ -38,6 +40,20 @@ public struct MySwiftStruct {
return 12
}
+ public func getCapacity() -> Int {
+ self.cap
+ }
+
+ public func getLength() -> Int {
+ self.len
+ }
+
+ public mutating func increaseCap(by value: Int) -> Int {
+ precondition(value > 0)
+ self.cap += value
+ return self.cap
+ }
+
public func makeRandomIntMethod() -> Int {
return Int.random(in: 1..<256)
}
diff --git a/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java b/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java
index 8c343ab5..81c6dd0f 100644
--- a/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java
+++ b/Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java
@@ -24,15 +24,13 @@
import org.swift.swiftkit.SwiftKit;
import org.swift.swiftkit.SwiftValueWitnessTable;
-import java.util.Arrays;
-
public class HelloJava2Swift {
public static void main(String[] args) {
boolean traceDowncalls = Boolean.getBoolean("jextract.trace.downcalls");
System.out.println("Property: jextract.trace.downcalls = " + traceDowncalls);
- System.out.print("Property: java.library.path = " +SwiftKit.getJavaLibraryPath());
+ System.out.print("Property: java.library.path = " + SwiftKit.getJavaLibraryPath());
examples();
}
@@ -44,23 +42,23 @@ static void examples() {
// Example of using an arena; MyClass.deinit is run at end of scope
try (var arena = SwiftArena.ofConfined()) {
- MySwiftClass obj = new MySwiftClass(arena, 2222, 7777);
-
- // just checking retains/releases work
- SwiftKit.retain(obj.$memorySegment());
- SwiftKit.release(obj.$memorySegment());
+ MySwiftClass obj = new MySwiftClass(arena, 2222, 7777);
- obj.voidMethod();
- obj.takeIntMethod(42);
+ // just checking retains/releases work
+ SwiftKit.retain(obj.$memorySegment());
+ SwiftKit.release(obj.$memorySegment());
- MySwiftStruct swiftValue = new MySwiftStruct(12);
+ obj.voidMethod();
+ obj.takeIntMethod(42);
+ MySwiftStruct swiftValue = new MySwiftStruct(arena, 2222, 1111);
}
System.out.println("DONE.");
}
public static native long jniWriteString(String str);
+
public static native long jniGetInt();
}
diff --git a/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/MySwiftClassTest.java b/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/MySwiftClassTest.java
index fa17ef1a..f0a45c62 100644
--- a/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/MySwiftClassTest.java
+++ b/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/MySwiftClassTest.java
@@ -28,7 +28,6 @@ public class MySwiftClassTest {
void checkPaths(Throwable throwable) {
var paths = SwiftKit.getJavaLibraryPath().split(":");
for (var path : paths) {
- System.out.println("CHECKING PATH: " + path);
Stream.of(new File(path).listFiles())
.filter(file -> !file.isDirectory())
.forEach((file) -> {
diff --git a/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/MySwiftStructTest.java b/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/MySwiftStructTest.java
similarity index 69%
rename from Samples/SwiftKitSampleApp/src/test/java/com/example/swift/MySwiftStructTest.java
rename to Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/MySwiftStructTest.java
index 27126587..fbdbf3a3 100644
--- a/Samples/SwiftKitSampleApp/src/test/java/com/example/swift/MySwiftStructTest.java
+++ b/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/MySwiftStructTest.java
@@ -9,29 +9,29 @@
// See CONTRIBUTORS.txt for the list of Swift.org project authors
//
// SPDX-License-Identifier: Apache-2.0
+
+
//
//===----------------------------------------------------------------------===//
-package com.example.swift;
+package org.swift.swiftkit;
-import org.junit.jupiter.api.Disabled;
+import com.example.swift.MySwiftStruct;
import org.junit.jupiter.api.Test;
-import org.swift.swiftkit.SwiftArena;
-import org.swift.swiftkit.SwiftKit;
-
-import java.io.File;
-import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class MySwiftStructTest {
@Test
- void test_MySwiftClass_voidMethod() {
+ void create_struct() {
try (var arena = SwiftArena.ofConfined()) {
- MySwiftStruct o = new MySwiftStruct(12);
-// o.voidMethod();
+ long cap = 12;
+ long len = 34;
+ var struct = new MySwiftStruct(arena, cap, len);
+
+ assertEquals(cap, struct.getCapacity());
+ assertEquals(len, struct.getLength());
}
}
-
}
diff --git a/Sources/JExtractSwift/ImportedDecls.swift b/Sources/JExtractSwift/ImportedDecls.swift
index eae4424a..be533916 100644
--- a/Sources/JExtractSwift/ImportedDecls.swift
+++ b/Sources/JExtractSwift/ImportedDecls.swift
@@ -228,13 +228,13 @@ public struct ImportedFunc: ImportedDecl, CustomStringConvertible {
case nil, .wrapper:
break
- case .pointer:
+ case .pointer where !isInit:
let selfParam: FunctionParameterSyntax = "self$: $swift_pointer"
params.append(
ImportedParam(syntax: selfParam, type: parent)
)
- case .memorySegment:
+ case .memorySegment where !isInit:
let selfParam: FunctionParameterSyntax = "self$: $java_lang_foreign_MemorySegment"
var parentForSelf = parent
parentForSelf.javaType = .javaForeignMemorySegment
@@ -244,6 +244,9 @@ public struct ImportedFunc: ImportedDecl, CustomStringConvertible {
case .swiftThunkSelf:
break
+
+ default:
+ break
}
// TODO: add any metadata for generics and other things we may need to add here
@@ -262,6 +265,11 @@ public struct ImportedFunc: ImportedDecl, CustomStringConvertible {
public var isInit: Bool = false
+ public var isIndirectReturn: Bool {
+ returnType.isValueType ||
+ (isInit && (parent?.isValueType ?? false))
+ }
+
public init(
module: String,
decl: any DeclSyntaxProtocol,
diff --git a/Sources/JExtractSwift/Swift2JavaTranslator+MemoryLayouts.swift b/Sources/JExtractSwift/Swift2JavaTranslator+MemoryLayouts.swift
index fd2dbcb9..fa5b6318 100644
--- a/Sources/JExtractSwift/Swift2JavaTranslator+MemoryLayouts.swift
+++ b/Sources/JExtractSwift/Swift2JavaTranslator+MemoryLayouts.swift
@@ -18,6 +18,7 @@ import SwiftParser
import SwiftSyntax
extension Swift2JavaTranslator {
+
public func javaMemoryLayoutDescriptors(
forParametersOf decl: ImportedFunc,
paramPassingStyle: SelfParameterVariant?
@@ -25,10 +26,6 @@ extension Swift2JavaTranslator {
var layouts: [ForeignValueLayout] = []
layouts.reserveCapacity(decl.parameters.count + 1)
- // // When the method is `init()` it does not accept a self (well, unless allocating init but we don't import those)
- // let paramPassingStyle: SelfParameterVariant? =
- // decl.isInit ? nil : .wrapper
-
for param in decl.effectiveParameters(paramPassingStyle: paramPassingStyle) {
if param.type.cCompatibleJavaMemoryLayout == CCompatibleJavaMemoryLayout.primitive(.void) {
continue
@@ -39,6 +36,13 @@ extension Swift2JavaTranslator {
layouts.append(layout)
}
+ // an indirect return passes the buffer as the last parameter to our thunk
+ if decl.isIndirectReturn {
+ var layout = ForeignValueLayout.SwiftPointer
+ layout.inlineComment = "indirect return buffer"
+ layouts.append(layout)
+ }
+
return layouts
}
}
diff --git a/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift b/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift
index c043167e..d67f008c 100644
--- a/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift
+++ b/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift
@@ -755,11 +755,21 @@ extension Swift2JavaTranslator {
let identifier = accessorKind.renderMethodName(decl)
if paramPassingStyle == SelfParameterVariant.wrapper {
+ let guardFromDestroyedObjectCalls: String =
+ if decl.hasParent {
+ """
+ if (this.$state$destroyed.get()) {
+ throw new IllegalStateException("Attempted to call method on already destroyed instance of " + getClass().getSimpleName() + "!");
+ }
+ """
+ } else { "" }
+
// delegate to the MemorySegment "self" accepting overload
printer.print(
"""
\(javaDocComment)
public \(returnTy) \(identifier)(\(renderJavaParamDecls(decl, paramPassingStyle: .wrapper))) {
+ \(guardFromDestroyedObjectCalls)
\(maybeReturnCast) \(identifier)(\(renderForwardJavaParams(decl, paramPassingStyle: .wrapper)));
}
"""
@@ -1048,19 +1058,13 @@ extension Swift2JavaTranslator {
let fieldName = accessorKind.renderDescFieldName
printer.start("public static final FunctionDescriptor \(fieldName) = ")
- let isIndirectReturn =
- decl.returnType.isValueType || (decl.isInit && (decl.parent?.isValueType ?? false))
+ let isIndirectReturn = decl.isIndirectReturn
var parameterLayoutDescriptors: [ForeignValueLayout] = javaMemoryLayoutDescriptors(
forParametersOf: decl,
paramPassingStyle: .pointer
)
- // Write indirect return buffer parameter after formal
- // if isIndirectReturn || decl.isInit {
- // parameterLayoutDescriptors.append(.SwiftPointer)
- // }
-
if decl.returnType.javaType == .void || isIndirectReturn {
printer.print("FunctionDescriptor.ofVoid(")
printer.indent()
@@ -1088,11 +1092,6 @@ extension Swift2JavaTranslator {
printer.print(desc, .parameterNewlineSeparator(isLast))
}
- // // Write indirect return buffer parameter after formal
- // if isIndirectReturn {
- // printer.print(", /* indirect return buffer */SWIFT_POINTER")
- // }
-
printer.outdent()
printer.print(");")
}
diff --git a/Sources/JExtractSwift/SwiftThunkTranslator.swift b/Sources/JExtractSwift/SwiftThunkTranslator.swift
index 551b1056..2676ef2e 100644
--- a/Sources/JExtractSwift/SwiftThunkTranslator.swift
+++ b/Sources/JExtractSwift/SwiftThunkTranslator.swift
@@ -50,15 +50,15 @@ struct SwiftThunkTranslator {
decls.append(contentsOf: render(forFunc: decl))
}
-// TODO: handle variables
-// for v in nominal.variables {
-// if let acc = v.accessorFunc(kind: .get) {
-// decls.append(contentsOf: render(forFunc: acc))
-// }
-// if let acc = v.accessorFunc(kind: .set) {
-// decls.append(contentsOf: render(forFunc: acc))
-// }
-// }
+ // TODO: handle variables
+ // for v in nominal.variables {
+ // if let acc = v.accessorFunc(kind: .get) {
+ // decls.append(contentsOf: render(forFunc: acc))
+ // }
+ // if let acc = v.accessorFunc(kind: .set) {
+ // decls.append(contentsOf: render(forFunc: acc))
+ // }
+ // }
return decls
}
diff --git a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftValueWitnessTable.java b/SwiftKit/src/main/java/org/swift/swiftkit/SwiftValueWitnessTable.java
index 8a092bc2..38071e82 100644
--- a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftValueWitnessTable.java
+++ b/SwiftKit/src/main/java/org/swift/swiftkit/SwiftValueWitnessTable.java
@@ -106,7 +106,7 @@ public static long sizeOfSwiftType(MemorySegment typeMetadata) {
* Variable handle for the "stride" field within the value witness table.
*/
static final VarHandle $stride$mh =
- $LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("size"));
+ $LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("stride"));
/**
* Determine the stride of a Swift type given its type metadata, which is
diff --git a/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift b/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift
index 8d1e606d..1686f0ab 100644
--- a/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift
+++ b/Tests/JExtractSwiftTests/Asserts/TextAssertions.swift
@@ -45,7 +45,9 @@ func assertOutput(
}
output = printer.finalize()
- let gotLines = output.split(separator: "\n")
+ let gotLines = output.split(separator: "\n").filter { l in
+ l.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines).count > 0
+ }
for expected in expectedChunks {
let expectedLines = expected.split(separator: "\n")
diff --git a/Tests/JExtractSwiftTests/ClassPrintingTests.swift b/Tests/JExtractSwiftTests/ClassPrintingTests.swift
index 57c06f7e..768f1480 100644
--- a/Tests/JExtractSwiftTests/ClassPrintingTests.swift
+++ b/Tests/JExtractSwiftTests/ClassPrintingTests.swift
@@ -54,7 +54,6 @@ struct ClassPrintingTests {
return TYPE_METADATA;
}
-
private static final GroupLayout $LAYOUT = (GroupLayout) SwiftValueWitnessTable.layoutOfSwiftType(TYPE_METADATA.$memorySegment());
public final GroupLayout $layout() {
return $LAYOUT;
diff --git a/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift b/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift
index 696c4b93..739cbb79 100644
--- a/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift
+++ b/Tests/JExtractSwiftTests/FuncCallbackImportTests.swift
@@ -72,6 +72,7 @@ final class FuncCallbackImportTests {
if (SwiftKit.TRACE_DOWNCALLS) {
SwiftKit.traceDowncall(callback$);
}
+
mh$.invokeExact(callback$);
} catch (Throwable ex$) {
throw new AssertionError("should not reach here", ex$);
diff --git a/Tests/JExtractSwiftTests/MethodImportTests.swift b/Tests/JExtractSwiftTests/MethodImportTests.swift
index 1702feac..39d72dc0 100644
--- a/Tests/JExtractSwiftTests/MethodImportTests.swift
+++ b/Tests/JExtractSwiftTests/MethodImportTests.swift
@@ -91,6 +91,7 @@ final class MethodImportTests {
if (SwiftKit.TRACE_DOWNCALLS) {
SwiftKit.traceDowncall();
}
+
mh$.invokeExact();
} catch (Throwable ex$) {
throw new AssertionError("should not reach here", ex$);
@@ -132,8 +133,9 @@ final class MethodImportTests {
var mh$ = globalTakeInt.HANDLE;
try {
if (SwiftKit.TRACE_DOWNCALLS) {
- SwiftKit.traceDowncall(i);
+ SwiftKit.traceDowncall(i);
}
+
mh$.invokeExact(i);
} catch (Throwable ex$) {
throw new AssertionError("should not reach here", ex$);
@@ -162,6 +164,7 @@ final class MethodImportTests {
}
assertOutput(
+ dump: true,
output,
expected:
"""
@@ -178,6 +181,7 @@ final class MethodImportTests {
if (SwiftKit.TRACE_DOWNCALLS) {
SwiftKit.traceDowncall(i32, l, s$);
}
+
mh$.invokeExact(i32, l, s$);
} catch (Throwable ex$) {
throw new AssertionError("should not reach here", ex$);
@@ -345,6 +349,9 @@ final class MethodImportTests {
* }
*/
public void helloMemberFunction() {
+ if (this.$state$destroyed.get()) {
+ throw new IllegalStateException("Attempted to call method on already destroyed instance of " + getClass().getSimpleName() + "!")
+ }
helloMemberFunction($memorySegment());
}
"""
@@ -380,6 +387,10 @@ final class MethodImportTests {
* }
*/
public long makeInt() {
+ if (this.$state$destroyed.get()) {
+ throw new IllegalStateException("Attempted to call method on already destroyed instance of " + getClass().getSimpleName() + "!")
+ }
+
return (long) makeInt($memorySegment());
}
"""
diff --git a/Tests/JExtractSwiftTests/MethodThunkTests.swift b/Tests/JExtractSwiftTests/MethodThunkTests.swift
index 56eedfa5..7de47816 100644
--- a/Tests/JExtractSwiftTests/MethodThunkTests.swift
+++ b/Tests/JExtractSwiftTests/MethodThunkTests.swift
@@ -40,13 +40,15 @@ final class MethodThunkTests {
"""
@_cdecl("swiftjava_FakeModule_globalFunc_a_b")
public func swiftjava_FakeModule_globalFunc_a_b(a: Int32, b: Int64) /* Void */ {
- return globalFunc(a: a, b: b)
+ let returnValue = globalFunc(a: a, b: b)
+ return returnValue
}
""",
"""
@_cdecl("swiftjava_FakeModule_globalFunc_a_b$1")
public func swiftjava_FakeModule_globalFunc_a_b$1(a: Double, b: Int64) /* Void */ {
- return globalFunc(a: a, b: b)
+ let returnValue = globalFunc(a: a, b: b)
+ return returnValue
}
"""
]
diff --git a/Tests/JExtractSwiftTests/StringPassingTests.swift b/Tests/JExtractSwiftTests/StringPassingTests.swift
index c51d3549..cc488dfc 100644
--- a/Tests/JExtractSwiftTests/StringPassingTests.swift
+++ b/Tests/JExtractSwiftTests/StringPassingTests.swift
@@ -34,8 +34,7 @@ final class StringPassingTests {
try assertOutput(
st, input: class_interfaceFile, .java,
detectChunkByInitialLines: 1,
- expectedChunks:
- [
+ expectedChunks: [
"""
public static long writeString(java.lang.String string) {
var mh$ = writeString.HANDLE;
diff --git a/Tests/JExtractSwiftTests/VariableImportTests.swift b/Tests/JExtractSwiftTests/VariableImportTests.swift
index 0ac71ffc..0d45b08b 100644
--- a/Tests/JExtractSwiftTests/VariableImportTests.swift
+++ b/Tests/JExtractSwiftTests/VariableImportTests.swift
@@ -44,8 +44,7 @@ final class VariableImportTests {
try assertOutput(
st, input: class_interfaceFile, .java,
detectChunkByInitialLines: 7,
- expectedChunks:
- [
+ expectedChunks: [
"""
private static class counterInt {
public static final FunctionDescriptor DESC_GET = FunctionDescriptor.of(
@@ -109,8 +108,7 @@ final class VariableImportTests {
public static FunctionDescriptor counterInt$set$descriptor() {
return counterInt.DESC_SET;
}
- """
- ,
+ """,
"""
/**
* Downcall method handle for:
@@ -160,6 +158,9 @@ final class VariableImportTests {
* }
*/
public long getCounterInt() {
+ if (this.$state$destroyed.get()) {
+ throw new IllegalStateException("Attempted to call method on already destroyed instance of " + getClass().
+ }
return (long) getCounterInt($memorySegment());
}
""",
@@ -174,9 +175,9 @@ final class VariableImportTests {
var mh$ = counterInt.HANDLE_SET;
try {
if (SwiftKit.TRACE_DOWNCALLS) {
- SwiftKit.traceDowncall(newValue, self$);
+ SwiftKit.traceDowncall(newValue, self$);
}
- mh$.invokeExact(newValue, self$);
+ mh$.invokeExact(newValue, self$);
} catch (Throwable ex$) {
throw new AssertionError("should not reach here", ex$);
}
@@ -190,9 +191,12 @@ final class VariableImportTests {
* }
*/
public void setCounterInt(long newValue) {
- setCounterInt(newValue, $memorySegment());
+ if (this.$state$destroyed.get()) {
+ throw new IllegalStateException("Attempted to call method on already destroyed instance of " + getClass().getSimpleName() + "!");
+ }
+ setCounterInt(newValue, $memorySegment());
}
- """
+ """,
]
)
}
From 0467deb98b8df7ef8d2ad441c47a3e77e8406523 Mon Sep 17 00:00:00 2001
From: Konrad `ktoso` Malawski
Date: Mon, 27 Jan 2025 12:22:08 +0000
Subject: [PATCH 11/13] more tests for protecting access to destroyed objects
---
.../org/swift/swiftkit/SwiftArenaTest.java | 39 ++++++++++++++++++-
1 file changed, 38 insertions(+), 1 deletion(-)
diff --git a/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/SwiftArenaTest.java b/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/SwiftArenaTest.java
index 1bf3c388..d9b7bebd 100644
--- a/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/SwiftArenaTest.java
+++ b/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/SwiftArenaTest.java
@@ -15,6 +15,7 @@
package org.swift.swiftkit;
import com.example.swift.MySwiftClass;
+import com.example.swift.MySwiftStruct;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledIf;
@@ -47,8 +48,44 @@ public void arena_releaseClassOnClose_class_ok() {
release(obj.$memorySegment());
assertEquals(1, retainCount(obj.$memorySegment()));
}
+ }
+
+ // FIXME: The destroy witness table call hangs on x86_64 platforms during the destroy witness table call
+ // See: https://github.com/swiftlang/swift-java/issues/97
+ @Test
+ public void arena_markAsDestroyed_preventUseAfterFree_class() {
+ MySwiftClass unsafelyEscapedOutsideArenaScope = null;
+
+ try (var arena = SwiftArena.ofConfined()) {
+ var obj = new MySwiftClass(arena,1, 2);
+ unsafelyEscapedOutsideArenaScope = obj;
+ }
+
+ try {
+ unsafelyEscapedOutsideArenaScope.echoIntMethod(1);
+ fail("Expected exception to be thrown! Object was suposed to be dead.");
+ } catch (IllegalStateException ex) {
+ return;
+ }
+ }
+
+ // FIXME: The destroy witness table call hangs on x86_64 platforms during the destroy witness table call
+ // See: https://github.com/swiftlang/swift-java/issues/97
+ @Test
+ public void arena_markAsDestroyed_preventUseAfterFree_struct() {
+ MySwiftStruct unsafelyEscapedOutsideArenaScope = null;
- // TODO: should we zero out the $memorySegment perhaps?
+ try (var arena = SwiftArena.ofConfined()) {
+ var s = new MySwiftStruct(arena,1, 2);
+ unsafelyEscapedOutsideArenaScope = s;
+ }
+
+ try {
+ unsafelyEscapedOutsideArenaScope.echoIntMethod(1);
+ fail("Expected exception to be thrown! Object was suposed to be dead.");
+ } catch (IllegalStateException ex) {
+ return;
+ }
}
@Test
From 0430ee14089c8c4b18762bc41a0c0973a4d0acc1 Mon Sep 17 00:00:00 2001
From: Konrad `ktoso` Malawski
Date: Mon, 27 Jan 2025 12:22:20 +0000
Subject: [PATCH 12/13] remove commented out code
---
Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift | 8 +-------
1 file changed, 1 insertion(+), 7 deletions(-)
diff --git a/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift b/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift
index d67f008c..af85a214 100644
--- a/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift
+++ b/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift
@@ -705,9 +705,6 @@ extension Swift2JavaTranslator {
_ printer: inout CodePrinter, _ decl: ImportedFunc,
accessorKind: VariableAccessorKind? = nil
) {
- // var thunkName = SwiftKitPrinting.Names.functionThunk(
- // thunkNameRegistry: &self.thunkNameRegistry,
- // module: self.swiftModuleName, function: decl)
let thunkName = thunkNameRegistry.functionThunkName(module: self.swiftModuleName, decl: decl)
printer.print(
"""
@@ -899,9 +896,7 @@ extension Swift2JavaTranslator {
}
}
- public func renderJavaParamDecls(_ decl: ImportedFunc, paramPassingStyle: SelfParameterVariant?)
- -> String
- {
+ public func renderJavaParamDecls(_ decl: ImportedFunc, paramPassingStyle: SelfParameterVariant?) -> String {
var ps: [String] = []
var pCounter = 0
@@ -958,7 +953,6 @@ extension Swift2JavaTranslator {
if paramPassingStyle == .swiftThunkSelf {
ps.append("_self: UnsafeMutableRawPointer")
- // ps.append("_self: Any")
}
let res = ps.joined(separator: ", ")
From d4b26a9e63914cf79c30db129485cd06d60aa578 Mon Sep 17 00:00:00 2001
From: Konrad `ktoso` Malawski
Date: Mon, 27 Jan 2025 12:29:32 +0000
Subject: [PATCH 13/13] fix formatting
---
.../src/test/java/com/example/swift/MySwiftClassTest.java | 3 +--
.../src/test/java/org/swift/swiftkit/MySwiftStructTest.java | 2 --
Sources/JExtractSwift/ImportedDecls.swift | 1 +
Sources/JExtractSwift/Swift2JavaTranslator.swift | 3 +--
.../main/java/org/swift/swiftkit/SwiftValueWitnessTable.java | 2 +-
5 files changed, 4 insertions(+), 7 deletions(-)
diff --git a/Samples/SwiftAndJavaJarSampleLib/src/test/java/com/example/swift/MySwiftClassTest.java b/Samples/SwiftAndJavaJarSampleLib/src/test/java/com/example/swift/MySwiftClassTest.java
index a52879bb..fa17ef1a 100644
--- a/Samples/SwiftAndJavaJarSampleLib/src/test/java/com/example/swift/MySwiftClassTest.java
+++ b/Samples/SwiftAndJavaJarSampleLib/src/test/java/com/example/swift/MySwiftClassTest.java
@@ -45,8 +45,7 @@ void test_MySwiftClass_voidMethod() {
MySwiftClass o = new MySwiftClass(12, 42);
o.voidMethod();
} catch (Throwable throwable) {
- throw throwable;
-// checkPaths(throwable);
+ checkPaths(throwable);
}
}
diff --git a/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/MySwiftStructTest.java b/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/MySwiftStructTest.java
index fbdbf3a3..b48e28d3 100644
--- a/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/MySwiftStructTest.java
+++ b/Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/MySwiftStructTest.java
@@ -9,8 +9,6 @@
// See CONTRIBUTORS.txt for the list of Swift.org project authors
//
// SPDX-License-Identifier: Apache-2.0
-
-
//
//===----------------------------------------------------------------------===//
diff --git a/Sources/JExtractSwift/ImportedDecls.swift b/Sources/JExtractSwift/ImportedDecls.swift
index be533916..72204e66 100644
--- a/Sources/JExtractSwift/ImportedDecls.swift
+++ b/Sources/JExtractSwift/ImportedDecls.swift
@@ -69,6 +69,7 @@ public struct ImportedNominalType: ImportedDecl {
}
}
+// TODO: replace this with `SwiftNominalTypeDeclaration.Kind`
public enum NominalTypeKind {
case `actor`
case `class`
diff --git a/Sources/JExtractSwift/Swift2JavaTranslator.swift b/Sources/JExtractSwift/Swift2JavaTranslator.swift
index 8b1ffa14..ff81ef16 100644
--- a/Sources/JExtractSwift/Swift2JavaTranslator.swift
+++ b/Sources/JExtractSwift/Swift2JavaTranslator.swift
@@ -147,9 +147,8 @@ extension Swift2JavaTranslator {
"java.lang.invoke.*",
"java.util.Arrays",
"java.util.stream.Collectors",
- "java.nio.charset.StandardCharsets",
-
"java.util.concurrent.atomic.*",
+ "java.nio.charset.StandardCharsets",
]
}
diff --git a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftValueWitnessTable.java b/SwiftKit/src/main/java/org/swift/swiftkit/SwiftValueWitnessTable.java
index 589bd72d..4bc86786 100644
--- a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftValueWitnessTable.java
+++ b/SwiftKit/src/main/java/org/swift/swiftkit/SwiftValueWitnessTable.java
@@ -15,7 +15,7 @@
package org.swift.swiftkit;
import java.lang.foreign.*;
-import java.lang.invoke.MethodHandle;
+import java.lang.invoke.*;
import static java.lang.foreign.ValueLayout.JAVA_BYTE;
import static org.swift.swiftkit.SwiftKit.getSwiftInt;