From 6f8647d560ec46892c5b69ed5e464f3cd61096ee Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Sun, 26 Jan 2025 09:43:34 +0000 Subject: [PATCH 01/13] ignore .index-build directory --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 11bbeceb..a20b93ce 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ DerivedData/ *.class bin/ BuildLogic/out/ +.index-build # Ignore gradle build artifacts .gradle From a7787f82c34bfc39437c350437882750a1da4039 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Sun, 26 Jan 2025 09:44:30 +0000 Subject: [PATCH 02/13] Implement SwiftValue cleanup --- .../swiftkit/AutoSwiftMemorySession.java | 23 +++++++++++++-- .../swiftkit/ConfinedSwiftMemorySession.java | 28 +++++++++++++++++-- .../java/org/swift/swiftkit/SwiftAnyType.java | 10 ++++++- .../swift/swiftkit/SwiftInstanceCleanup.java | 25 +++++++++++++---- 4 files changed, 75 insertions(+), 11 deletions(-) diff --git a/SwiftKit/src/main/java/org/swift/swiftkit/AutoSwiftMemorySession.java b/SwiftKit/src/main/java/org/swift/swiftkit/AutoSwiftMemorySession.java index 9d25ad12..2de79393 100644 --- a/SwiftKit/src/main/java/org/swift/swiftkit/AutoSwiftMemorySession.java +++ b/SwiftKit/src/main/java/org/swift/swiftkit/AutoSwiftMemorySession.java @@ -14,6 +14,7 @@ package org.swift.swiftkit; +import java.lang.foreign.Arena; import java.lang.foreign.MemorySegment; import java.lang.ref.Cleaner; import java.util.Objects; @@ -38,15 +39,24 @@ */ final class AutoSwiftMemorySession implements SwiftArena { + private final Arena arena; private final Cleaner cleaner; public AutoSwiftMemorySession(ThreadFactory cleanerThreadFactory) { this.cleaner = Cleaner.create(cleanerThreadFactory); + this.arena = Arena.ofAuto(); } @Override public void register(SwiftHeapObject object) { - SwiftHeapObjectCleanup cleanupAction = new SwiftHeapObjectCleanup(object.$memorySegment(), object.$swiftType()); + var statusDestroyedFlag = object.$statusDestroyedFlag(); + Runnable markAsDestroyed = () -> statusDestroyedFlag.set(true); + + SwiftHeapObjectCleanup cleanupAction = new SwiftHeapObjectCleanup( + object.$memorySegment(), + object.$swiftType(), + markAsDestroyed + ); register(object, cleanupAction); } @@ -62,9 +72,18 @@ void register(SwiftHeapObject object, SwiftHeapObjectCleanup cleanupAction) { @Override public void register(SwiftValue value) { Objects.requireNonNull(value, "value"); + + // We're doing this dance to avoid keeping a strong reference to the value itself + var statusDestroyedFlag = value.$statusDestroyedFlag(); + Runnable markAsDestroyed = () -> statusDestroyedFlag.set(true); + MemorySegment resource = value.$memorySegment(); - var cleanupAction = new SwiftValueCleanup(resource); + var cleanupAction = new SwiftValueCleanup(resource, value.$swiftType(), markAsDestroyed); cleaner.register(value, cleanupAction); } + @Override + public MemorySegment allocate(long byteSize, long byteAlignment) { + return arena.allocate(byteSize, byteAlignment); + } } diff --git a/SwiftKit/src/main/java/org/swift/swiftkit/ConfinedSwiftMemorySession.java b/SwiftKit/src/main/java/org/swift/swiftkit/ConfinedSwiftMemorySession.java index 36d7be77..317ebcd4 100644 --- a/SwiftKit/src/main/java/org/swift/swiftkit/ConfinedSwiftMemorySession.java +++ b/SwiftKit/src/main/java/org/swift/swiftkit/ConfinedSwiftMemorySession.java @@ -14,6 +14,8 @@ package org.swift.swiftkit; +import java.lang.foreign.Arena; +import java.lang.foreign.MemorySegment; import java.util.LinkedList; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; @@ -26,12 +28,15 @@ final class ConfinedSwiftMemorySession implements ClosableSwiftArena { final Thread owner; final AtomicInteger state; + final Arena arena; final ConfinedResourceList resources; public ConfinedSwiftMemorySession(Thread owner) { this.owner = owner; this.state = new AtomicInteger(ACTIVE); this.resources = new ConfinedResourceList(); + + this.arena = Arena.ofConfined(); } public void checkValid() throws RuntimeException { @@ -51,13 +56,20 @@ public void close() { if (this.state.compareAndExchange(ACTIVE, CLOSED) == ACTIVE) { this.resources.runCleanup(); } // else, was already closed; do nothing + + this.arena.close(); } @Override public void register(SwiftHeapObject object) { checkValid(); - var cleanup = new SwiftHeapObjectCleanup(object.$memorySegment(), object.$swiftType()); + var statusDestroyedFlag = object.$statusDestroyedFlag(); + Runnable markAsDestroyed = () -> statusDestroyedFlag.set(true); + + var cleanup = new SwiftHeapObjectCleanup( + object.$memorySegment(), object.$swiftType(), + markAsDestroyed); this.resources.add(cleanup); } @@ -65,10 +77,21 @@ public void register(SwiftHeapObject object) { public void register(SwiftValue value) { checkValid(); - var cleanup = new SwiftValueCleanup(value.$memorySegment()); + var statusDestroyedFlag = value.$statusDestroyedFlag(); + Runnable markAsDestroyed = () -> statusDestroyedFlag.set(true); + + var cleanup = new SwiftValueCleanup( + value.$memorySegment(), + value.$swiftType(), + markAsDestroyed); this.resources.add(cleanup); } + @Override + public MemorySegment allocate(long byteSize, long byteAlignment) { + return arena.allocate(byteSize, byteAlignment); + } + static final class ConfinedResourceList implements SwiftResourceList { // TODO: Could use intrusive linked list to avoid one indirection here final List resourceCleanups = new LinkedList<>(); @@ -82,6 +105,7 @@ public void runCleanup() { for (SwiftInstanceCleanup cleanup : resourceCleanups) { cleanup.run(); } + resourceCleanups.clear(); } } } diff --git a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftAnyType.java b/SwiftKit/src/main/java/org/swift/swiftkit/SwiftAnyType.java index cf7cc238..8b2d94de 100644 --- a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftAnyType.java +++ b/SwiftKit/src/main/java/org/swift/swiftkit/SwiftAnyType.java @@ -56,11 +56,19 @@ public SwiftAnyType(SwiftHeapObject object) { return $LAYOUT; } + /** + * Get the human-readable Swift type name of this type. + */ + public String getSwiftName() { + return SwiftKit.nameOfSwiftType(memorySegment, true); + } + @Override public String toString() { return "AnySwiftType{" + - "name=" + SwiftKit.nameOfSwiftType(memorySegment, true) + + "name=" + getSwiftName() + ", memorySegment=" + memorySegment + '}'; } + } diff --git a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftInstanceCleanup.java b/SwiftKit/src/main/java/org/swift/swiftkit/SwiftInstanceCleanup.java index 8ac62793..4dc22127 100644 --- a/SwiftKit/src/main/java/org/swift/swiftkit/SwiftInstanceCleanup.java +++ b/SwiftKit/src/main/java/org/swift/swiftkit/SwiftInstanceCleanup.java @@ -31,18 +31,21 @@ interface SwiftInstanceCleanup extends Runnable { // non-final for testing class SwiftHeapObjectCleanup implements SwiftInstanceCleanup { - final MemorySegment selfPointer; - final SwiftAnyType selfType; + private final MemorySegment selfPointer; + private final SwiftAnyType selfType; + private final Runnable markAsDestroyed; /** * This constructor on purpose does not just take a {@link SwiftHeapObject} in order to make it very * clear that it does not take ownership of it, but we ONLY manage the native resource here. - * + *

* This is important for {@link AutoSwiftMemorySession} which relies on the wrapper type to be GC-able, * when no longer "in use" on the Java side. */ - SwiftHeapObjectCleanup(MemorySegment selfPointer, SwiftAnyType selfType) { + SwiftHeapObjectCleanup(MemorySegment selfPointer, + SwiftAnyType selfType, Runnable markAsDestroyed) { this.selfPointer = selfPointer; + this.markAsDestroyed = markAsDestroyed; this.selfType = selfType; } @@ -54,6 +57,8 @@ public void run() throws UnexpectedRetainCountException { throw new UnexpectedRetainCountException(selfPointer, retainedCount, 1); } + this.markAsDestroyed.run(); + // Destroy (and deinit) the object: SwiftValueWitnessTable.destroy(selfType, selfPointer); @@ -62,9 +67,17 @@ public void run() throws UnexpectedRetainCountException { } } -record SwiftValueCleanup(MemorySegment resource) implements SwiftInstanceCleanup { +record SwiftValueCleanup( + MemorySegment selfPointer, + SwiftAnyType selfType, + Runnable markAsDestroyed +) implements SwiftInstanceCleanup { + @Override public void run() { - throw new RuntimeException("not implemented yet"); + System.out.println("[debug] Destroy swift value [" + selfType.getSwiftName() + "]: " + selfPointer); + + markAsDestroyed.run(); + SwiftValueWitnessTable.destroy(selfType, selfPointer); } } From 62b898e5b490c1fb05241b1dea1e5d3cc0a551ab Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Sun, 26 Jan 2025 12:15:15 +0000 Subject: [PATCH 03/13] SourceGen: Handle init() of value types with indirect return --- .../Swift2JavaTranslator+Printing.swift | 111 +++++++++++++----- .../MethodImportTests.swift | 73 +++++++++++- 2 files changed, 153 insertions(+), 31 deletions(-) diff --git a/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift b/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift index bac029ec..c043167e 100644 --- a/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift +++ b/Sources/JExtractSwift/Swift2JavaTranslator+Printing.swift @@ -53,7 +53,8 @@ extension Swift2JavaTranslator { if let outputFile = try printer.writeContents( outputDirectory: outputDirectory, javaPackagePath: javaPackagePath, - filename: filename) { + filename: filename) + { print("[swift-java] Generated: \(self.swiftModuleName).java (at \(outputFile)") } } @@ -76,7 +77,8 @@ extension Swift2JavaTranslator { if let outputFile = try printer.writeContents( outputDirectory: outputDirectory, javaPackagePath: nil, - filename: moduleFilename) { + filename: moduleFilename) + { print("[swift-java] Generated: \(moduleFilenameBase.bold).swift (at \(outputFile)") } } catch { @@ -95,7 +97,8 @@ extension Swift2JavaTranslator { if let outputFile = try printer.writeContents( outputDirectory: outputDirectory, javaPackagePath: nil, - filename: filename) { + filename: filename) + { print("[swift-java] Generated: \(fileNameBase.bold).swift (at \(outputFile)") } } catch { @@ -133,12 +136,12 @@ extension Swift2JavaTranslator { let stt = SwiftThunkTranslator(self) printer.print( - """ - // Generated by swift-java + """ + // Generated by swift-java - import SwiftKitSwift + import SwiftKitSwift - """ + """ ) for thunk in stt.renderThunks(forType: ty) { @@ -291,6 +294,7 @@ extension Swift2JavaTranslator { printer in // ==== Storage of the class printClassSelfProperty(&printer, decl) + printStatusFlagsField(&printer, decl) // Constants printClassConstants(printer: &printer) @@ -412,6 +416,21 @@ extension Swift2JavaTranslator { ) } + private func printStatusFlagsField(_ printer: inout CodePrinter, _ decl: ImportedNominalType) { + printer.print( + """ + // TODO: make this a flagset integer and/or use a field updater + /** Used to track additional state of the underlying object, e.g. if it was explicitly destroyed. */ + private final AtomicBoolean $state$destroyed = new AtomicBoolean(false); + + @Override + public final AtomicBoolean $statusDestroyedFlag() { + return this.$state$destroyed; + } + """ + ) + } + private func printClassMemoryLayout(_ printer: inout CodePrinter, _ decl: ImportedNominalType) { printer.print( """ @@ -476,10 +495,10 @@ extension Swift2JavaTranslator { printMethodDowncallHandleForAddrDesc(&printer) } - printClassInitializerConstructors(&printer, decl, parentName: parentName) + printNominalInitializerConstructors(&printer, decl, parentName: parentName) } - public func printClassInitializerConstructors( + public func printNominalInitializerConstructors( _ printer: inout CodePrinter, _ decl: ImportedFunc, parentName: TranslatedType @@ -499,6 +518,25 @@ extension Swift2JavaTranslator { """ ) + let initializeMemorySegment = + if let parent = decl.parent, + parent.isReferenceType + { + """ + this.selfMemorySegment = (MemorySegment) mh$.invokeExact( + \(renderForwardJavaParams(decl, paramPassingStyle: nil)) + ); + """ + } else { + """ + this.selfMemorySegment = arena.allocate($layout()); + mh$.invokeExact( + \(renderForwardJavaParams(decl, paramPassingStyle: nil)), + /* indirect return buffer */this.selfMemorySegment + ); + """ + } + printer.print( """ /** @@ -514,7 +552,8 @@ extension Swift2JavaTranslator { SwiftKit.traceDowncall(\(renderForwardJavaParams(decl, paramPassingStyle: nil))); } - this.selfMemorySegment = (MemorySegment) mh$.invokeExact(\(renderForwardJavaParams(decl, paramPassingStyle: nil)), TYPE_METADATA.$memorySegment()); + \(initializeMemorySegment) + if (arena != null) { arena.register(this); } @@ -666,9 +705,9 @@ extension Swift2JavaTranslator { _ printer: inout CodePrinter, _ decl: ImportedFunc, accessorKind: VariableAccessorKind? = nil ) { -// var thunkName = SwiftKitPrinting.Names.functionThunk( -// thunkNameRegistry: &self.thunkNameRegistry, -// module: self.swiftModuleName, function: decl) + // var thunkName = SwiftKitPrinting.Names.functionThunk( + // thunkNameRegistry: &self.thunkNameRegistry, + // module: self.swiftModuleName, function: decl) let thunkName = thunkNameRegistry.functionThunkName(module: self.swiftModuleName, decl: decl) printer.print( """ @@ -850,7 +889,9 @@ extension Swift2JavaTranslator { } } - public func renderJavaParamDecls(_ decl: ImportedFunc, paramPassingStyle: SelfParameterVariant?) -> String { + public func renderJavaParamDecls(_ decl: ImportedFunc, paramPassingStyle: SelfParameterVariant?) + -> String + { var ps: [String] = [] var pCounter = 0 @@ -872,7 +913,8 @@ extension Swift2JavaTranslator { public func renderSwiftParamDecls( _ decl: ImportedFunc, paramPassingStyle: SelfParameterVariant?, - style: ParameterVariant? = nil) -> String { + style: ParameterVariant? = nil + ) -> String { var ps: [String] = [] var pCounter = 0 @@ -887,7 +929,7 @@ extension Swift2JavaTranslator { let paramTy: String = if style == .cDeclThunk, p.type.javaType.isString { - "UnsafeMutablePointer" // TODO: is this ok? + "UnsafeMutablePointer" // TODO: is this ok? } else if paramPassingStyle == .swiftThunkSelf { "\(p.type.cCompatibleSwiftType)" } else { @@ -906,7 +948,7 @@ extension Swift2JavaTranslator { if paramPassingStyle == .swiftThunkSelf { ps.append("_self: UnsafeMutableRawPointer") -// ps.append("_self: Any") + // ps.append("_self: Any") } let res = ps.joined(separator: ", ") @@ -942,7 +984,9 @@ extension Swift2JavaTranslator { return printer.contents } - public func renderForwardJavaParams(_ decl: ImportedFunc, paramPassingStyle: SelfParameterVariant?) -> String { + public func renderForwardJavaParams( + _ decl: ImportedFunc, paramPassingStyle: SelfParameterVariant? + ) -> String { var ps: [String] = [] var pCounter = 0 @@ -980,20 +1024,16 @@ extension Swift2JavaTranslator { } // TODO: these are stateless, find new place for them? - public func renderForwardSwiftParams(_ decl: ImportedFunc, paramPassingStyle: SelfParameterVariant?) -> String { + public func renderForwardSwiftParams( + _ decl: ImportedFunc, paramPassingStyle: SelfParameterVariant? + ) -> String { var ps: [String] = [] - var pCounter = 0 - - func nextUniqueParamName() -> String { - pCounter += 1 - return "p\(pCounter)" - } for p in decl.effectiveParameters(paramPassingStyle: paramPassingStyle) { if let firstName = p.firstName { - ps.append("\(firstName): \(p.effectiveName ?? nextUniqueParamName())") + ps.append("\(firstName): \(p.effectiveValueName)") } else { - ps.append("\(p.effectiveName ?? nextUniqueParamName())") + ps.append("\(p.effectiveValueName)") } } @@ -1008,12 +1048,20 @@ extension Swift2JavaTranslator { let fieldName = accessorKind.renderDescFieldName printer.start("public static final FunctionDescriptor \(fieldName) = ") - let parameterLayoutDescriptors = javaMemoryLayoutDescriptors( + let isIndirectReturn = + decl.returnType.isValueType || (decl.isInit && (decl.parent?.isValueType ?? false)) + + var parameterLayoutDescriptors: [ForeignValueLayout] = javaMemoryLayoutDescriptors( forParametersOf: decl, paramPassingStyle: .pointer ) - if decl.returnType.javaType == .void { + // 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() } else { @@ -1040,6 +1088,11 @@ 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/Tests/JExtractSwiftTests/MethodImportTests.swift b/Tests/JExtractSwiftTests/MethodImportTests.swift index 67c7c210..1702feac 100644 --- a/Tests/JExtractSwiftTests/MethodImportTests.swift +++ b/Tests/JExtractSwiftTests/MethodImportTests.swift @@ -53,6 +53,10 @@ final class MethodImportTests { @objc deinit } + + public struct MySwiftStruct { + public init(len: Swift.Int, cap: Swift.Int) {} + } """ @Test("Import: public func helloWorld()") @@ -397,7 +401,7 @@ final class MethodImportTests { }! let output = CodePrinter.toString { printer in - st.printClassInitializerConstructors(&printer, initDecl, parentName: initDecl.parent!) + st.printNominalInitializerConstructors(&printer, initDecl, parentName: initDecl.parent!) } assertOutput( @@ -428,7 +432,72 @@ final class MethodImportTests { if (SwiftKit.TRACE_DOWNCALLS) { SwiftKit.traceDowncall(len, cap); } - this.selfMemorySegment = (MemorySegment) mh$.invokeExact(len, cap, TYPE_METADATA.$memorySegment()); + this.selfMemorySegment = (MemorySegment) mh$.invokeExact( + len, cap + ); + if (arena != null) { + arena.register(this); + } + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + """ + ) + } + + @Test + func struct_constructor() throws { + let st = Swift2JavaTranslator( + javaPackage: "com.example.swift", + swiftModuleName: "__FakeModule" + ) + st.log.logLevel = .info + + try st.analyze(file: "Fake.swift", text: class_interfaceFile) + + let initDecl: ImportedFunc = st.importedTypes["MySwiftStruct"]!.initializers.first { + $0.identifier == "init(len:cap:)" + }! + + let output = CodePrinter.toString { printer in + st.printNominalInitializerConstructors(&printer, initDecl, parentName: initDecl.parent!) + } + + assertOutput( + output, + expected: + """ + /** + * Create an instance of {@code MySwiftStruct}. + * + * {@snippet lang=swift : + * public init(len: Swift.Int, cap: Swift.Int) {} + * } + */ + public MySwiftStruct(long len, long cap) { + this(/*arena=*/null, len, cap); + } + /** + * Create an instance of {@code MySwiftStruct}. + * This instance is managed by the passed in {@link SwiftArena} and may not outlive the arena's lifetime. + * + * {@snippet lang=swift : + * public init(len: Swift.Int, cap: Swift.Int) {} + * } + */ + + public MySwiftStruct(SwiftArena arena, long len, long cap) { + var mh$ = init_len_cap.HANDLE; + try { + if (SwiftKit.TRACE_DOWNCALLS) { + SwiftKit.traceDowncall(len, cap); + } + this.selfMemorySegment = arena.allocate($layout()); + mh$.invokeExact( + len, cap, + /* indirect return buffer */this.selfMemorySegment + ); if (arena != null) { arena.register(this); } From 7d4b57b37671379af7b9cfa0f5ee7526b7a5a705 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Sun, 26 Jan 2025 12:35:35 +0000 Subject: [PATCH 04/13] Followups for handling value inits, various helper funcs --- .../Convenience/SwiftSyntax+Extensions.swift | 26 ++++--- Sources/JExtractSwift/ImportedDecls.swift | 49 +++++++++++--- .../Swift2JavaTranslator+MemoryLayouts.swift | 4 +- .../JExtractSwift/Swift2JavaTranslator.swift | 2 + Sources/JExtractSwift/Swift2JavaVisitor.swift | 27 ++++---- .../JExtractSwift/SwiftThunkTranslator.swift | 67 +++++++++++++------ Sources/JExtractSwift/TranslatedType.swift | 51 +++++++++++--- .../FunctionDescriptorImportTests.swift | 18 ++--- .../VariableImportTests.swift | 6 +- 9 files changed, 175 insertions(+), 75 deletions(-) diff --git a/Sources/JExtractSwift/Convenience/SwiftSyntax+Extensions.swift b/Sources/JExtractSwift/Convenience/SwiftSyntax+Extensions.swift index 0a8f5533..6b814f8a 100644 --- a/Sources/JExtractSwift/Convenience/SwiftSyntax+Extensions.swift +++ b/Sources/JExtractSwift/Convenience/SwiftSyntax+Extensions.swift @@ -52,13 +52,22 @@ extension ImplicitlyUnwrappedOptionalTypeSyntax { } } -extension TypeSyntax { - fileprivate var isActorSystem: Bool { - self.trimmedDescription == "ActorSystem" +extension SyntaxProtocol { + + var asNominalTypeKind: NominalTypeKind { + if isClass { + .class + } else if isActor { + .actor + } else if isStruct { + .struct + } else if isEnum { + .enum + } else { + fatalError("Unknown nominal kind: \(self)") + } } -} -extension DeclSyntaxProtocol { var isClass: Bool { return self.is(ClassDeclSyntax.self) } @@ -79,11 +88,8 @@ extension DeclSyntaxProtocol { extension DeclModifierSyntax { var isAccessControl: Bool { switch self.name.tokenKind { - case .keyword(.private): fallthrough - case .keyword(.fileprivate): fallthrough - case .keyword(.internal): fallthrough - case .keyword(.package): fallthrough - case .keyword(.public): + case .keyword(.private), .keyword(.fileprivate), .keyword(.internal), .keyword(.package), + .keyword(.public): return true default: return false diff --git a/Sources/JExtractSwift/ImportedDecls.swift b/Sources/JExtractSwift/ImportedDecls.swift index 6edc263c..eae4424a 100644 --- a/Sources/JExtractSwift/ImportedDecls.swift +++ b/Sources/JExtractSwift/ImportedDecls.swift @@ -44,18 +44,19 @@ public struct ImportedNominalType: ImportedDecl { TranslatedType( cCompatibleConvention: .direct, originalSwiftType: "\(raw: swiftTypeName)", + originalSwiftTypeKind: self.kind, cCompatibleSwiftType: "UnsafeRawPointer", cCompatibleJavaMemoryLayout: .heapObject, javaType: javaType ) } - + public var isReferenceType: Bool { switch self.kind { case .class, .actor: - return true - case .enum, .struct: - return false + true + default: + false } } @@ -73,6 +74,34 @@ public enum NominalTypeKind { case `class` case `enum` case `struct` + case `void` // TODO: NOT NOMINAL, BUT... + case function // TODO: NOT NOMINAL, BUT... + case primitive // TODO: NOT NOMINAL, BUT... + + var isReferenceType: Bool { + switch self { + case .actor, .class: true + case .enum, .struct: false + case .void, .function, .primitive: false + } + } + + var isValueType: Bool { + switch self { + case .actor, .class: false + case .enum, .struct: true + case .void, .function, .primitive: false + } + } + + var isVoid: Bool { + switch self { + case .actor, .class: false + case .enum, .struct: false + case .void: true + case .function, .primitive: false + } + } } public struct ImportedParam { @@ -99,7 +128,7 @@ public struct ImportedParam { var effectiveName: String? { firstName ?? secondName } - + var effectiveValueName: String { secondName ?? firstName ?? "_" } @@ -269,9 +298,9 @@ extension ImportedFunc: Hashable { self.swiftDecl.id.hash(into: &hasher) } - public static func ==(lhs: ImportedFunc, rhs: ImportedFunc) -> Swift.Bool { - lhs.parent?.originalSwiftType.id == rhs.parent?.originalSwiftType.id && - lhs.swiftDecl.id == rhs.swiftDecl.id + public static func == (lhs: ImportedFunc, rhs: ImportedFunc) -> Swift.Bool { + lhs.parent?.originalSwiftType.id == rhs.parent?.originalSwiftType.id + && lhs.swiftDecl.id == rhs.swiftDecl.id } } @@ -394,8 +423,8 @@ public struct ImportedVariable: ImportedDecl, CustomStringConvertible { ) case nil, - .wrapper, - .swiftThunkSelf: + .wrapper, + .swiftThunkSelf: break } } diff --git a/Sources/JExtractSwift/Swift2JavaTranslator+MemoryLayouts.swift b/Sources/JExtractSwift/Swift2JavaTranslator+MemoryLayouts.swift index 21d6946b..fd2dbcb9 100644 --- a/Sources/JExtractSwift/Swift2JavaTranslator+MemoryLayouts.swift +++ b/Sources/JExtractSwift/Swift2JavaTranslator+MemoryLayouts.swift @@ -34,7 +34,9 @@ extension Swift2JavaTranslator { continue } - layouts.append(param.type.foreignValueLayout) + var layout = param.type.foreignValueLayout + layout.inlineComment = "\(param.effectiveValueName)" + layouts.append(layout) } return layouts diff --git a/Sources/JExtractSwift/Swift2JavaTranslator.swift b/Sources/JExtractSwift/Swift2JavaTranslator.swift index 9894244b..5b5fd5f2 100644 --- a/Sources/JExtractSwift/Swift2JavaTranslator.swift +++ b/Sources/JExtractSwift/Swift2JavaTranslator.swift @@ -120,6 +120,8 @@ extension Swift2JavaTranslator { "java.util.Arrays", "java.util.stream.Collectors", "java.nio.charset.StandardCharsets", + + "java.util.concurrent.atomic.*", ] } diff --git a/Sources/JExtractSwift/Swift2JavaVisitor.swift b/Sources/JExtractSwift/Swift2JavaVisitor.swift index b9ba3bd4..5b577589 100644 --- a/Sources/JExtractSwift/Swift2JavaVisitor.swift +++ b/Sources/JExtractSwift/Swift2JavaVisitor.swift @@ -26,8 +26,10 @@ final class Swift2JavaVisitor: SyntaxVisitor { /// store this along with type names as we import them. let targetJavaPackage: String + var currentType: ImportedNominalType? = nil + /// The current type name as a nested name like A.B.C. - var currentTypeName: String? = nil + var currentTypeName: String? { self.currentType?.swiftTypeName } var log: Logger { translator.log } @@ -45,34 +47,34 @@ final class Swift2JavaVisitor: SyntaxVisitor { return .skipChildren } - currentTypeName = importedNominalType.swiftTypeName + self.currentType = importedNominalType return .visitChildren } override func visitPost(_ node: ClassDeclSyntax) { - if currentTypeName != nil { + if currentType != nil { log.debug("Completed import: \(node.kind) \(node.name)") - currentTypeName = nil + self.currentType = nil } } - + override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind { log.debug("Visit \(node.kind): \(node)") guard let importedNominalType = translator.importedNominalType(node) else { return .skipChildren } - currentTypeName = importedNominalType.swiftTypeName + self.currentType = importedNominalType return .visitChildren } override func visitPost(_ node: StructDeclSyntax) { - if currentTypeName != nil { + if currentType != nil { log.debug("Completed import: \(node.kind) \(node.name)") - currentTypeName = nil + self.currentType = nil } } - + override func visit(_ node: ExtensionDeclSyntax) -> SyntaxVisitorContinueKind { // Resolve the extended type of the extension as an imported nominal, and // recurse if we found it. @@ -82,13 +84,13 @@ final class Swift2JavaVisitor: SyntaxVisitor { return .skipChildren } - currentTypeName = importedNominalType.swiftTypeName + self.currentType = importedNominalType return .visitChildren } override func visitPost(_ node: ExtensionDeclSyntax) { - if currentTypeName != nil { - currentTypeName = nil + if currentType != nil { + self.currentType = nil } } @@ -266,6 +268,7 @@ extension InitializerDeclSyntax { } // Ok, import it + log.warning("Import initializer: \(self)") return true } } diff --git a/Sources/JExtractSwift/SwiftThunkTranslator.swift b/Sources/JExtractSwift/SwiftThunkTranslator.swift index c530be3b..551b1056 100644 --- a/Sources/JExtractSwift/SwiftThunkTranslator.swift +++ b/Sources/JExtractSwift/SwiftThunkTranslator.swift @@ -80,23 +80,45 @@ struct SwiftThunkTranslator { func renderSwiftInitAccessor(_ function: ImportedFunc) -> [DeclSyntax] { guard let parent = function.parent else { - fatalError("Cannot render initializer accessor if init function has no parent! Was: \(function)") + fatalError( + "Cannot render initializer accessor if init function has no parent! Was: \(function)") } let thunkName = self.st.thunkNameRegistry.functionThunkName( module: st.swiftModuleName, decl: function) - return - [ + let cDecl = + """ + @_cdecl("\(thunkName)") + """ + let typeName = "\(parent.swiftTypeName)" + + if parent.isReferenceType { + return [ """ - @_cdecl("\(raw: thunkName)") - public func \(raw: thunkName)(\(raw: st.renderSwiftParamDecls(function, paramPassingStyle: nil))) -> UnsafeMutableRawPointer /* \(raw: parent.swiftTypeName) */ { - let _self = \(raw: parent.swiftTypeName)(\(raw: st.renderForwardSwiftParams(function, paramPassingStyle: nil))) + \(raw: cDecl) + public func \(raw: thunkName)(\(raw: st.renderSwiftParamDecls(function, paramPassingStyle: nil))) -> UnsafeMutableRawPointer /* \(raw: typeName) */ { + var _self = \(raw: typeName)(\(raw: st.renderForwardSwiftParams(function, paramPassingStyle: nil))) let self$ = unsafeBitCast(_self, to: UnsafeMutableRawPointer.self) - return _swiftjava_swift_retain(object: self$) + _swiftjava_swift_retain(object: self$) + return self$ + } + """ + ] + } else { + return [ + """ + \(raw: cDecl) + public func \(raw: thunkName)( + \(raw: st.renderSwiftParamDecls(function, paramPassingStyle: nil)), + resultBuffer: /* \(raw: typeName) */ UnsafeMutableRawPointer + ) { + var _self = \(raw: typeName)(\(raw: st.renderForwardSwiftParams(function, paramPassingStyle: nil))) + resultBuffer.assumingMemoryBound(to: \(raw: typeName).self).initialize(to: _self) } """ ] + } } func render(forFunc decl: ImportedFunc) -> [DeclSyntax] { @@ -109,20 +131,25 @@ struct SwiftThunkTranslator { } else { "-> \(decl.returnType.cCompatibleSwiftType) /* \(decl.returnType.swiftTypeName) */" } - + // Do we need to pass a self parameter? let paramPassingStyle: SelfParameterVariant? let callBase: String let callBaseDot: String - if let parent = decl.parent { - paramPassingStyle = .swiftThunkSelf - callBase = "let self$ = unsafeBitCast(_self, to: \(parent.originalSwiftType).self)" - callBaseDot = "self$." - } else { - paramPassingStyle = nil - callBase = "" - callBaseDot = "" - } + if let parent = decl.parent, parent.isReferenceType { + paramPassingStyle = .swiftThunkSelf + callBase = "let self$ = unsafeBitCast(_self, to: \(parent.originalSwiftType).self)" + callBaseDot = "self$." + } else if let parent = decl.parent, !parent.isReferenceType { + paramPassingStyle = .swiftThunkSelf + callBase = + "var self$ = _self.assumingMemoryBound(to: \(parent.originalSwiftType).self).pointee" + callBaseDot = "self$." + } else { + paramPassingStyle = nil + callBase = "" + callBaseDot = "" + } // FIXME: handle in thunk: errors @@ -155,7 +182,7 @@ struct SwiftThunkTranslator { """ ] } - + func adaptArgumentsInThunk(_ decl: ImportedFunc) -> String { var lines: [String] = [] for p in decl.parameters { @@ -165,11 +192,11 @@ struct SwiftThunkTranslator { """ let \(p.effectiveValueName) = String(cString: \(p.effectiveValueName)) """ - + lines += [adaptedType] } } - + return lines.joined(separator: "\n") } } diff --git a/Sources/JExtractSwift/TranslatedType.swift b/Sources/JExtractSwift/TranslatedType.swift index eef4140d..6866b738 100644 --- a/Sources/JExtractSwift/TranslatedType.swift +++ b/Sources/JExtractSwift/TranslatedType.swift @@ -21,10 +21,10 @@ extension Swift2JavaVisitor { func cCompatibleType(for type: TypeSyntax) throws -> TranslatedType { switch type.as(TypeSyntaxEnum.self) { case .arrayType, .attributedType, .classRestrictionType, .compositionType, - .dictionaryType, .implicitlyUnwrappedOptionalType, .metatypeType, - .missingType, .namedOpaqueReturnType, - .optionalType, .packElementType, .packExpansionType, .someOrAnyType, - .suppressedType, .tupleType: + .dictionaryType, .implicitlyUnwrappedOptionalType, .metatypeType, + .missingType, .namedOpaqueReturnType, + .optionalType, .packElementType, .packExpansionType, .someOrAnyType, + .suppressedType, .tupleType: throw TypeTranslationError.unimplementedType(type) case .functionType(let functionType): @@ -33,6 +33,7 @@ extension Swift2JavaVisitor { return TranslatedType( cCompatibleConvention: .direct, originalSwiftType: type, + originalSwiftTypeKind: .function, cCompatibleSwiftType: "@convention(c) () -> Void", cCompatibleJavaMemoryLayout: .cFunction, javaType: .javaLangRunnable @@ -64,6 +65,7 @@ extension Swift2JavaVisitor { for: type, parent: parentType, name: memberType.name.text, + kind: nil, genericArguments: genericArgs ) @@ -80,6 +82,7 @@ extension Swift2JavaVisitor { for: type, parent: nil, name: identifierType.name.text, + kind: nil, genericArguments: genericArgs ) } @@ -90,6 +93,7 @@ extension Swift2JavaVisitor { for type: TypeSyntax, parent: TranslatedType?, name: String, + kind: NominalTypeKind?, genericArguments: [TranslatedType]? ) throws -> TranslatedType { // Look for a primitive type with this name. @@ -97,6 +101,7 @@ extension Swift2JavaVisitor { return TranslatedType( cCompatibleConvention: .direct, originalSwiftType: "\(raw: name)", + originalSwiftTypeKind: .primitive, cCompatibleSwiftType: "Swift.\(raw: name)", cCompatibleJavaMemoryLayout: .primitive(primitiveType), javaType: primitiveType @@ -110,6 +115,7 @@ extension Swift2JavaVisitor { return TranslatedType( cCompatibleConvention: .direct, originalSwiftType: "\(raw: name)", + originalSwiftTypeKind: .primitive, cCompatibleSwiftType: "Swift.\(raw: name)", cCompatibleJavaMemoryLayout: .int, javaType: translator.javaPrimitiveForSwiftInt @@ -121,8 +127,9 @@ extension Swift2JavaVisitor { return TranslatedType( cCompatibleConvention: .direct, originalSwiftType: "\(raw: name)", + originalSwiftTypeKind: kind, cCompatibleSwiftType: "Swift.\(raw: name)", - cCompatibleJavaMemoryLayout: .heapObject, // FIXME: or specialize string? + cCompatibleJavaMemoryLayout: .heapObject, // FIXME: or specialize string? javaType: .javaLangString ) } @@ -157,9 +164,10 @@ extension Swift2JavaVisitor { } // Look up the imported types by name to resolve it to a nominal type. - let swiftTypeName = type.trimmedDescription // FIXME: This is a hack. + let swiftTypeName = type.trimmedDescription // FIXME: This is a hack. guard let resolvedNominal = translator.nominalResolution.resolveNominalType(swiftTypeName), - let importedNominal = translator.importedNominalType(resolvedNominal) else { + let importedNominal = translator.importedNominalType(resolvedNominal) + else { throw TypeTranslationError.unknown(type) } @@ -176,7 +184,8 @@ extension String { /// 2. Whether the memory referenced by the pointer is mutable. /// 3. Whether the pointer type has a `count` property describing how /// many elements it points to. - fileprivate var isNameOfSwiftPointerType: (requiresArgument: Bool, mutable: Bool, hasCount: Bool)? { + fileprivate var isNameOfSwiftPointerType: (requiresArgument: Bool, mutable: Bool, hasCount: Bool)? + { switch self { case "COpaquePointer", "UnsafeRawPointer": return (requiresArgument: false, mutable: true, hasCount: false) @@ -220,6 +229,9 @@ public struct TranslatedType { /// The original Swift type, as written in the source. var originalSwiftType: TypeSyntax + /// + var originalSwiftTypeKind: NominalTypeKind? + /// The C-compatible Swift type that should be used in any C -> Swift thunks /// emitted in Swift. var cCompatibleSwiftType: TypeSyntax @@ -239,10 +251,18 @@ public struct TranslatedType { /// Produce the "unqualified" Java type name. var unqualifiedJavaTypeName: String { switch javaType { - case .class(package: _, name: let name): name + case .class(package: _, let name): name default: javaType.description } } + + var isReferenceType: Bool { + originalSwiftTypeKind == .class || originalSwiftTypeKind == .actor + } + + var isValueType: Bool { + originalSwiftTypeKind == .struct || originalSwiftTypeKind == .enum + } } extension TranslatedType { @@ -250,6 +270,7 @@ extension TranslatedType { TranslatedType( cCompatibleConvention: .direct, originalSwiftType: "Void", + originalSwiftTypeKind: .void, cCompatibleSwiftType: "Swift.Void", cCompatibleJavaMemoryLayout: .primitive(.void), javaType: JavaType.void) @@ -274,6 +295,15 @@ enum CCompatibleJavaMemoryLayout: Hashable { case cFunction } +enum SwiftTypeKind { + case `class` + case `actor` + case `enum` + case `struct` + case primitive + case `void` +} + extension TranslatedType { /// Determine the foreign value layout to use for the translated type with /// the Java Foreign Function and Memory API. @@ -289,7 +319,8 @@ extension TranslatedType { case .long: return .SwiftInt64 case .float: return .SwiftFloat case .double: return .SwiftDouble - case .array, .class, .void: fatalError("Not a primitive type: \(cCompatibleJavaMemoryLayout) in \(self)") + case .array, .class, .void: + fatalError("Not a primitive type: \(cCompatibleJavaMemoryLayout) in \(self)") } case .int: diff --git a/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift b/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift index 3160d55f..734d9eab 100644 --- a/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift +++ b/Tests/JExtractSwiftTests/FunctionDescriptorImportTests.swift @@ -48,8 +48,8 @@ final class FunctionDescriptorTests { // #MySwiftClass.counter!getter: (MySwiftClass) -> () -> Int32 : @$s14MySwiftLibrary0aB5ClassC7counters5Int32Vvg\t// MySwiftClass.counter.getter // #MySwiftClass.counter!setter: (MySwiftClass) -> (Int32) -> () : @$s14MySwiftLibrary0aB5ClassC7counters5Int32Vvs\t// MySwiftClass.counter.setter - // #MySwiftClass.counter!modify: (MySwiftClass) -> () -> () : @$s14MySwiftLibrary0aB5ClassC7counters5Int32VvM\t// MySwiftClass.counter.modify - var counter: Int32 + // #MySwiftClass.counter!modify: (MySwiftClass) -> () -> () : @$s14MySwiftLibrary0aB5ClassC7counters5Int32VvM\t// MySwiftClass.counter.modify + var counter: Int32 } """ @@ -61,7 +61,7 @@ final class FunctionDescriptorTests { expected: """ public static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid( - SWIFT_INT + /*i*/SWIFT_INT ); """ ) @@ -76,8 +76,8 @@ final class FunctionDescriptorTests { expected: """ public static final FunctionDescriptor DESC = FunctionDescriptor.ofVoid( - SWIFT_INT64, - SWIFT_INT32 + /*l*/SWIFT_INT64, + /*i32*/SWIFT_INT32 ); """ ) @@ -93,7 +93,7 @@ final class FunctionDescriptorTests { """ public static final FunctionDescriptor DESC = FunctionDescriptor.of( /* -> */SWIFT_INT, - SWIFT_INT + /*i*/SWIFT_INT ); """ ) @@ -109,7 +109,7 @@ final class FunctionDescriptorTests { """ public static final FunctionDescriptor DESC_GET = FunctionDescriptor.of( /* -> */SWIFT_INT32, - SWIFT_POINTER + /*self$*/SWIFT_POINTER ); """ ) @@ -123,8 +123,8 @@ final class FunctionDescriptorTests { expected: """ public static final FunctionDescriptor DESC_SET = FunctionDescriptor.ofVoid( - SWIFT_INT32, - SWIFT_POINTER + /*newValue*/SWIFT_INT32, + /*self$*/SWIFT_POINTER ); """ ) diff --git a/Tests/JExtractSwiftTests/VariableImportTests.swift b/Tests/JExtractSwiftTests/VariableImportTests.swift index ee562cbd..0ac71ffc 100644 --- a/Tests/JExtractSwiftTests/VariableImportTests.swift +++ b/Tests/JExtractSwiftTests/VariableImportTests.swift @@ -50,15 +50,15 @@ final class VariableImportTests { private static class counterInt { public static final FunctionDescriptor DESC_GET = FunctionDescriptor.of( /* -> */SWIFT_INT, - SWIFT_POINTER + /*self$*/SWIFT_POINTER ); public static final MemorySegment ADDR_GET = FakeModule.findOrThrow("swiftjava_FakeModule_MySwiftClass_counterInt"); public static final MethodHandle HANDLE_GET = Linker.nativeLinker().downcallHandle(ADDR_GET, DESC_GET); public static final FunctionDescriptor DESC_SET = FunctionDescriptor.ofVoid( - SWIFT_INT, - SWIFT_POINTER + /*newValue*/SWIFT_INT, + /*self$*/SWIFT_POINTER ); public static final MemorySegment ADDR_SET = FakeModule.findOrThrow("swiftjava_FakeModule_MySwiftClass_counterInt"); From 5c585e8b7925c5c98f98becb24ad3c560cc1ba29 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski 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;