Skip to content

Commit 831397d

Browse files
authored
jextract: Indirect returns and more value type support (#211)
* ignore .index-build directory * Implement SwiftValue cleanup * SourceGen: Handle init() of value types with indirect return * Followups for handling value inits, various helper funcs * SwiftArena is now a segment allocator * Add $destroyed flag to wrapper types to prevent use-after-free bugs * Use varHandle instead of offset for reading data off VWT * testing: skip empty lines in output matching * gitignore vscode build outputs * test fixups * more tests for protecting access to destroyed objects * remove commented out code * fix formatting
1 parent cb4fa3d commit 831397d

31 files changed

+580
-185
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ DerivedData/
1111
*.class
1212
bin/
1313
BuildLogic/out/
14+
.index-build
15+
.build-vscode
1416

1517
# Ignore gradle build artifacts
1618
.gradle

Samples/SwiftKitSampleApp/Sources/MySwiftLibrary/MySwiftStruct.swift

+19-3
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,12 @@
1414

1515
public struct MySwiftStruct {
1616

17-
public var number: Int
17+
private var cap: Int
18+
private var len: Int
1819

19-
public init(number: Int) {
20-
self.number = number
20+
public init(cap: Int, len: Int) {
21+
self.cap = cap
22+
self.len = len
2123
}
2224

2325
public func voidMethod() {
@@ -38,6 +40,20 @@ public struct MySwiftStruct {
3840
return 12
3941
}
4042

43+
public func getCapacity() -> Int {
44+
self.cap
45+
}
46+
47+
public func getLength() -> Int {
48+
self.len
49+
}
50+
51+
public mutating func increaseCap(by value: Int) -> Int {
52+
precondition(value > 0)
53+
self.cap += value
54+
return self.cap
55+
}
56+
4157
public func makeRandomIntMethod() -> Int {
4258
return Int.random(in: 1..<256)
4359
}

Samples/SwiftKitSampleApp/src/main/java/com/example/swift/HelloJava2Swift.java

+9-11
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,13 @@
2424
import org.swift.swiftkit.SwiftKit;
2525
import org.swift.swiftkit.SwiftValueWitnessTable;
2626

27-
import java.util.Arrays;
28-
2927
public class HelloJava2Swift {
3028

3129
public static void main(String[] args) {
3230
boolean traceDowncalls = Boolean.getBoolean("jextract.trace.downcalls");
3331
System.out.println("Property: jextract.trace.downcalls = " + traceDowncalls);
3432

35-
System.out.print("Property: java.library.path = " +SwiftKit.getJavaLibraryPath());
33+
System.out.print("Property: java.library.path = " + SwiftKit.getJavaLibraryPath());
3634

3735
examples();
3836
}
@@ -44,23 +42,23 @@ static void examples() {
4442

4543
// Example of using an arena; MyClass.deinit is run at end of scope
4644
try (var arena = SwiftArena.ofConfined()) {
47-
MySwiftClass obj = new MySwiftClass(arena, 2222, 7777);
48-
49-
// just checking retains/releases work
50-
SwiftKit.retain(obj.$memorySegment());
51-
SwiftKit.release(obj.$memorySegment());
45+
MySwiftClass obj = new MySwiftClass(arena, 2222, 7777);
5246

53-
obj.voidMethod();
54-
obj.takeIntMethod(42);
47+
// just checking retains/releases work
48+
SwiftKit.retain(obj.$memorySegment());
49+
SwiftKit.release(obj.$memorySegment());
5550

56-
MySwiftStruct swiftValue = new MySwiftStruct(12);
51+
obj.voidMethod();
52+
obj.takeIntMethod(42);
5753

54+
MySwiftStruct swiftValue = new MySwiftStruct(arena, 2222, 1111);
5855
}
5956

6057
System.out.println("DONE.");
6158
}
6259

6360
public static native long jniWriteString(String str);
61+
6462
public static native long jniGetInt();
6563

6664
}

Samples/SwiftKitSampleApp/src/test/java/com/example/swift/MySwiftClassTest.java

-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ public class MySwiftClassTest {
2828
void checkPaths(Throwable throwable) {
2929
var paths = SwiftKit.getJavaLibraryPath().split(":");
3030
for (var path : paths) {
31-
System.out.println("CHECKING PATH: " + path);
3231
Stream.of(new File(path).listFiles())
3332
.filter(file -> !file.isDirectory())
3433
.forEach((file) -> {

Samples/SwiftKitSampleApp/src/test/java/com/example/swift/MySwiftStructTest.java renamed to Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/MySwiftStructTest.java

+9-11
Original file line numberDiff line numberDiff line change
@@ -12,26 +12,24 @@
1212
//
1313
//===----------------------------------------------------------------------===//
1414

15-
package com.example.swift;
15+
package org.swift.swiftkit;
1616

17-
import org.junit.jupiter.api.Disabled;
17+
import com.example.swift.MySwiftStruct;
1818
import org.junit.jupiter.api.Test;
19-
import org.swift.swiftkit.SwiftArena;
20-
import org.swift.swiftkit.SwiftKit;
21-
22-
import java.io.File;
23-
import java.util.stream.Stream;
2419

2520
import static org.junit.jupiter.api.Assertions.assertEquals;
2621

2722
public class MySwiftStructTest {
2823

2924
@Test
30-
void test_MySwiftClass_voidMethod() {
25+
void create_struct() {
3126
try (var arena = SwiftArena.ofConfined()) {
32-
MySwiftStruct o = new MySwiftStruct(12);
33-
// o.voidMethod();
27+
long cap = 12;
28+
long len = 34;
29+
var struct = new MySwiftStruct(arena, cap, len);
30+
31+
assertEquals(cap, struct.getCapacity());
32+
assertEquals(len, struct.getLength());
3433
}
3534
}
36-
3735
}

Samples/SwiftKitSampleApp/src/test/java/org/swift/swiftkit/SwiftArenaTest.java

+38-1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
package org.swift.swiftkit;
1616

1717
import com.example.swift.MySwiftClass;
18+
import com.example.swift.MySwiftStruct;
1819
import org.junit.jupiter.api.BeforeAll;
1920
import org.junit.jupiter.api.Test;
2021
import org.junit.jupiter.api.condition.DisabledIf;
@@ -47,8 +48,44 @@ public void arena_releaseClassOnClose_class_ok() {
4748
release(obj.$memorySegment());
4849
assertEquals(1, retainCount(obj.$memorySegment()));
4950
}
51+
}
52+
53+
// FIXME: The destroy witness table call hangs on x86_64 platforms during the destroy witness table call
54+
// See: https://github.com/swiftlang/swift-java/issues/97
55+
@Test
56+
public void arena_markAsDestroyed_preventUseAfterFree_class() {
57+
MySwiftClass unsafelyEscapedOutsideArenaScope = null;
58+
59+
try (var arena = SwiftArena.ofConfined()) {
60+
var obj = new MySwiftClass(arena,1, 2);
61+
unsafelyEscapedOutsideArenaScope = obj;
62+
}
63+
64+
try {
65+
unsafelyEscapedOutsideArenaScope.echoIntMethod(1);
66+
fail("Expected exception to be thrown! Object was suposed to be dead.");
67+
} catch (IllegalStateException ex) {
68+
return;
69+
}
70+
}
71+
72+
// FIXME: The destroy witness table call hangs on x86_64 platforms during the destroy witness table call
73+
// See: https://github.com/swiftlang/swift-java/issues/97
74+
@Test
75+
public void arena_markAsDestroyed_preventUseAfterFree_struct() {
76+
MySwiftStruct unsafelyEscapedOutsideArenaScope = null;
5077

51-
// TODO: should we zero out the $memorySegment perhaps?
78+
try (var arena = SwiftArena.ofConfined()) {
79+
var s = new MySwiftStruct(arena,1, 2);
80+
unsafelyEscapedOutsideArenaScope = s;
81+
}
82+
83+
try {
84+
unsafelyEscapedOutsideArenaScope.echoIntMethod(1);
85+
fail("Expected exception to be thrown! Object was suposed to be dead.");
86+
} catch (IllegalStateException ex) {
87+
return;
88+
}
5289
}
5390

5491
@Test

Sources/JExtractSwift/Convenience/SwiftSyntax+Extensions.swift

+16-10
Original file line numberDiff line numberDiff line change
@@ -52,13 +52,22 @@ extension ImplicitlyUnwrappedOptionalTypeSyntax {
5252
}
5353
}
5454

55-
extension TypeSyntax {
56-
fileprivate var isActorSystem: Bool {
57-
self.trimmedDescription == "ActorSystem"
55+
extension SyntaxProtocol {
56+
57+
var asNominalTypeKind: NominalTypeKind {
58+
if isClass {
59+
.class
60+
} else if isActor {
61+
.actor
62+
} else if isStruct {
63+
.struct
64+
} else if isEnum {
65+
.enum
66+
} else {
67+
fatalError("Unknown nominal kind: \(self)")
68+
}
5869
}
59-
}
6070

61-
extension DeclSyntaxProtocol {
6271
var isClass: Bool {
6372
return self.is(ClassDeclSyntax.self)
6473
}
@@ -79,11 +88,8 @@ extension DeclSyntaxProtocol {
7988
extension DeclModifierSyntax {
8089
var isAccessControl: Bool {
8190
switch self.name.tokenKind {
82-
case .keyword(.private): fallthrough
83-
case .keyword(.fileprivate): fallthrough
84-
case .keyword(.internal): fallthrough
85-
case .keyword(.package): fallthrough
86-
case .keyword(.public):
91+
case .keyword(.private), .keyword(.fileprivate), .keyword(.internal), .keyword(.package),
92+
.keyword(.public):
8793
return true
8894
default:
8995
return false

Sources/JExtractSwift/ImportedDecls.swift

+50-12
Original file line numberDiff line numberDiff line change
@@ -44,18 +44,19 @@ public struct ImportedNominalType: ImportedDecl {
4444
TranslatedType(
4545
cCompatibleConvention: .direct,
4646
originalSwiftType: "\(raw: swiftTypeName)",
47+
originalSwiftTypeKind: self.kind,
4748
cCompatibleSwiftType: "UnsafeRawPointer",
4849
cCompatibleJavaMemoryLayout: .heapObject,
4950
javaType: javaType
5051
)
5152
}
52-
53+
5354
public var isReferenceType: Bool {
5455
switch self.kind {
5556
case .class, .actor:
56-
return true
57-
case .enum, .struct:
58-
return false
57+
true
58+
default:
59+
false
5960
}
6061
}
6162

@@ -68,11 +69,40 @@ public struct ImportedNominalType: ImportedDecl {
6869
}
6970
}
7071

72+
// TODO: replace this with `SwiftNominalTypeDeclaration.Kind`
7173
public enum NominalTypeKind {
7274
case `actor`
7375
case `class`
7476
case `enum`
7577
case `struct`
78+
case `void` // TODO: NOT NOMINAL, BUT...
79+
case function // TODO: NOT NOMINAL, BUT...
80+
case primitive // TODO: NOT NOMINAL, BUT...
81+
82+
var isReferenceType: Bool {
83+
switch self {
84+
case .actor, .class: true
85+
case .enum, .struct: false
86+
case .void, .function, .primitive: false
87+
}
88+
}
89+
90+
var isValueType: Bool {
91+
switch self {
92+
case .actor, .class: false
93+
case .enum, .struct: true
94+
case .void, .function, .primitive: false
95+
}
96+
}
97+
98+
var isVoid: Bool {
99+
switch self {
100+
case .actor, .class: false
101+
case .enum, .struct: false
102+
case .void: true
103+
case .function, .primitive: false
104+
}
105+
}
76106
}
77107

78108
public struct ImportedParam {
@@ -99,7 +129,7 @@ public struct ImportedParam {
99129
var effectiveName: String? {
100130
firstName ?? secondName
101131
}
102-
132+
103133
var effectiveValueName: String {
104134
secondName ?? firstName ?? "_"
105135
}
@@ -199,13 +229,13 @@ public struct ImportedFunc: ImportedDecl, CustomStringConvertible {
199229
case nil, .wrapper:
200230
break
201231

202-
case .pointer:
232+
case .pointer where !isInit:
203233
let selfParam: FunctionParameterSyntax = "self$: $swift_pointer"
204234
params.append(
205235
ImportedParam(syntax: selfParam, type: parent)
206236
)
207237

208-
case .memorySegment:
238+
case .memorySegment where !isInit:
209239
let selfParam: FunctionParameterSyntax = "self$: $java_lang_foreign_MemorySegment"
210240
var parentForSelf = parent
211241
parentForSelf.javaType = .javaForeignMemorySegment
@@ -215,6 +245,9 @@ public struct ImportedFunc: ImportedDecl, CustomStringConvertible {
215245

216246
case .swiftThunkSelf:
217247
break
248+
249+
default:
250+
break
218251
}
219252

220253
// TODO: add any metadata for generics and other things we may need to add here
@@ -233,6 +266,11 @@ public struct ImportedFunc: ImportedDecl, CustomStringConvertible {
233266

234267
public var isInit: Bool = false
235268

269+
public var isIndirectReturn: Bool {
270+
returnType.isValueType ||
271+
(isInit && (parent?.isValueType ?? false))
272+
}
273+
236274
public init(
237275
module: String,
238276
decl: any DeclSyntaxProtocol,
@@ -269,9 +307,9 @@ extension ImportedFunc: Hashable {
269307
self.swiftDecl.id.hash(into: &hasher)
270308
}
271309

272-
public static func ==(lhs: ImportedFunc, rhs: ImportedFunc) -> Swift.Bool {
273-
lhs.parent?.originalSwiftType.id == rhs.parent?.originalSwiftType.id &&
274-
lhs.swiftDecl.id == rhs.swiftDecl.id
310+
public static func == (lhs: ImportedFunc, rhs: ImportedFunc) -> Swift.Bool {
311+
lhs.parent?.originalSwiftType.id == rhs.parent?.originalSwiftType.id
312+
&& lhs.swiftDecl.id == rhs.swiftDecl.id
275313
}
276314
}
277315

@@ -394,8 +432,8 @@ public struct ImportedVariable: ImportedDecl, CustomStringConvertible {
394432
)
395433

396434
case nil,
397-
.wrapper,
398-
.swiftThunkSelf:
435+
.wrapper,
436+
.swiftThunkSelf:
399437
break
400438
}
401439
}

0 commit comments

Comments
 (0)