Skip to content

Commit d55b35c

Browse files
committed
jextract: implement Java-side overflow checks using has32bitSwiftInt helper
1 parent d087f65 commit d55b35c

8 files changed

Lines changed: 471 additions & 242 deletions

File tree

Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift

Lines changed: 149 additions & 63 deletions
Large diffs are not rendered by default.

Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift

Lines changed: 151 additions & 98 deletions
Large diffs are not rendered by default.

SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftValueLayout.java

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//
33
// This source file is part of the Swift.org open source project
44
//
5-
// Copyright (c) 2024 Apple Inc. and the Swift.org project authors
5+
// Copyright (c) 2026 Apple Inc. and the Swift.org project authors
66
// Licensed under Apache License v2.0
77
//
88
// See LICENSE.txt for license information
@@ -22,7 +22,8 @@
2222
import static java.lang.foreign.ValueLayout.*;
2323

2424
/**
25-
* Similar to {@link java.lang.foreign.ValueLayout} however with some Swift specifics.
25+
* Similar to {@link java.lang.foreign.ValueLayout} however with some Swift
26+
* specifics.
2627
*/
2728
public class SwiftValueLayout {
2829

@@ -50,23 +51,28 @@ public static long addressByteSize() {
5051
public static final ValueLayout.OfFloat SWIFT_FLOAT = ValueLayout.JAVA_FLOAT;
5152
public static final ValueLayout.OfDouble SWIFT_DOUBLE = ValueLayout.JAVA_DOUBLE;
5253

53-
// FIXME: this sequence layout is a workaround, we must properly size pointers when we get them.
54+
// FIXME: this sequence layout is a workaround, we must properly size pointers
55+
// when we get them.
5456
public static final AddressLayout SWIFT_POINTER = ValueLayout.ADDRESS
55-
.withTargetLayout(MemoryLayout.sequenceLayout(Long.MAX_VALUE, JAVA_BYTE));
57+
.withTargetLayout(MemoryLayout.sequenceLayout(Long.MAX_VALUE, JAVA_BYTE));
5658
public static final SequenceLayout SWIFT_BYTE_ARRAY = MemoryLayout.sequenceLayout(8, ValueLayout.JAVA_BYTE);
5759

5860
/**
59-
* The value layout for Swift's {@code Int} type, which is a signed type that follows
61+
* The value layout for Swift's {@code Int} type, which is a signed type that
62+
* follows
6063
* the size of a pointer (aka C's {@code ptrdiff_t}).
6164
*/
62-
public static ValueLayout SWIFT_INT = (ValueLayout.ADDRESS.byteSize() == 4) ?
63-
SWIFT_INT32 : SWIFT_INT64;
65+
public static ValueLayout SWIFT_INT = (ValueLayout.ADDRESS.byteSize() == 4) ? SWIFT_INT32 : SWIFT_INT64;
6466

6567
/**
66-
* The value layout for Swift's {@code UInt} type, which is an unsigned type that follows
68+
* The value layout for Swift's {@code UInt} type, which is an unsigned type
69+
* that follows
6770
* the size of a pointer (aka C's {@code size_t}).
6871
* <p/>
69-
* Java does not have unsigned integer types, so we use the layout for Swift's {@code Int}.
72+
* Java does not have unsigned integer types, so we use the layout for Swift's
73+
* {@code Int}.
7074
*/
7175
public static ValueLayout SWIFT_UINT = SWIFT_INT;
76+
77+
public static final boolean has32bitSwiftInt = (SWIFT_INT == ValueLayout.JAVA_INT);
7278
}

Tests/JExtractSwiftTests/DataImportTests.swift

Lines changed: 62 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//
33
// This source file is part of the Swift.org open source project
44
//
5-
// Copyright (c) 2024 Apple Inc. and the Swift.org project authors
5+
// Copyright (c) 2026 Apple Inc. and the Swift.org project authors
66
// Licensed under Apache License v2.0
77
//
88
// See LICENSE.txt for license information
@@ -17,60 +17,65 @@ import Testing
1717

1818
final class DataImportTests {
1919
private static let ifConfigImport = """
20-
#if canImport(FoundationEssentials)
21-
import FoundationEssentials
22-
#else
23-
import Foundation
24-
#endif
25-
"""
20+
#if canImport(FoundationEssentials)
21+
import FoundationEssentials
22+
#else
23+
import Foundation
24+
#endif
25+
"""
2626
private static let foundationData_interfaceFile =
2727
"""
2828
import Foundation
29-
29+
3030
public func receiveData(dat: Data)
3131
public func returnData() -> Data
3232
"""
3333

3434
private static let foundationDataProtocol_interfaceFile =
3535
"""
3636
import Foundation
37-
37+
3838
public func receiveDataProtocol<T: DataProtocol>(dat: some DataProtocol, dat2: T?)
3939
"""
4040

4141
private static let essentialsData_interfaceFile =
4242
"""
4343
import FoundationEssentials
44-
44+
4545
public func receiveData(dat: Data)
4646
public func returnData() -> Data
4747
"""
4848

4949
private static let essentialsDataProtocol_interfaceFile =
5050
"""
5151
import FoundationEssentials
52-
52+
5353
public func receiveDataProtocol<T: DataProtocol>(dat: some DataProtocol, dat2: T?)
5454
"""
5555
private static let ifConfigData_interfaceFile =
5656
"""
5757
\(ifConfigImport)
58-
58+
5959
public func receiveData(dat: Data)
6060
public func returnData() -> Data
6161
"""
6262

6363
private static let ifConfigDataProtocol_interfaceFile =
6464
"""
6565
\(ifConfigImport)
66-
66+
6767
public func receiveDataProtocol<T: DataProtocol>(dat: some DataProtocol, dat2: T?)
6868
"""
6969

70-
@Test("Import Data: Swift thunks", arguments: zip(
71-
[Self.foundationData_interfaceFile, Self.essentialsData_interfaceFile, Self.ifConfigData_interfaceFile],
72-
["import Foundation", "import FoundationEssentials", Self.ifConfigImport]
73-
))
70+
@Test(
71+
"Import Data: Swift thunks",
72+
arguments: zip(
73+
[
74+
Self.foundationData_interfaceFile, Self.essentialsData_interfaceFile,
75+
Self.ifConfigData_interfaceFile,
76+
],
77+
["import Foundation", "import FoundationEssentials", Self.ifConfigImport]
78+
))
7479
func data_swiftThunk(fileContent: String, expectedImportChunk: String) throws {
7580

7681
try assertOutput(
@@ -123,10 +128,13 @@ final class DataImportTests {
123128
]
124129
)
125130
}
126-
127-
@Test("Import Data: JavaBindings", arguments: [
128-
Self.foundationData_interfaceFile, Self.essentialsData_interfaceFile, Self.ifConfigData_interfaceFile
129-
])
131+
132+
@Test(
133+
"Import Data: JavaBindings",
134+
arguments: [
135+
Self.foundationData_interfaceFile, Self.essentialsData_interfaceFile,
136+
Self.ifConfigData_interfaceFile,
137+
])
130138
func data_javaBindings(fileContent: String) throws {
131139
try assertOutput(
132140
input: fileContent, .ffm, .java,
@@ -209,7 +217,6 @@ final class DataImportTests {
209217
}
210218
""",
211219

212-
213220
"""
214221
/**
215222
* {@snippet lang=c :
@@ -245,8 +252,13 @@ final class DataImportTests {
245252
* public init(bytes: UnsafeRawPointer, count: Int)
246253
* }
247254
*/
248-
public static Data init(java.lang.foreign.MemorySegment bytes, long count, AllocatingSwiftArena swiftArena$) {
255+
public static Data init(java.lang.foreign.MemorySegment bytes, long count, AllocatingSwiftArena swiftArena$) throws SwiftIntegerOverflowException {
249256
MemorySegment _result = swiftArena$.allocate(Data.$LAYOUT);
257+
if (SwiftValueLayout.has32bitSwiftInt) {
258+
if (count < Integer.MIN_VALUE || count > Integer.MAX_VALUE) {
259+
throw new SwiftIntegerOverflowException("Parameter 'count' overflow: " + count);
260+
}
261+
}
250262
swiftjava_SwiftModule_Data_init_bytes_count.call(bytes, count, _result);
251263
return Data.wrapMemoryAddressUnsafe(_result, swiftArena$);
252264
}
@@ -286,9 +298,15 @@ final class DataImportTests {
286298
* public var count: Int
287299
* }
288300
*/
289-
public long getCount() {
301+
public long getCount() throws SwiftIntegerOverflowException {
290302
$ensureAlive();
291-
return swiftjava_SwiftModule_Data_count$get.call(this.$memorySegment());
303+
long _result$checked = swiftjava_SwiftModule_Data_count$get.call(this.$memorySegment());
304+
if (SwiftValueLayout.has32bitSwiftInt) {
305+
if (_result$checked < Integer.MIN_VALUE || _result$checked > Integer.MAX_VALUE) {
306+
throw new SwiftIntegerOverflowException("Return value overflow: " + _result$checked);
307+
}
308+
}
309+
return _result$checked;
292310
}
293311
""",
294312

@@ -352,7 +370,6 @@ final class DataImportTests {
352370
}
353371
""",
354372

355-
356373
"""
357374
/**
358375
* Downcall to Swift:
@@ -366,15 +383,20 @@ final class DataImportTests {
366383
swiftjava_SwiftModule_Data_withUnsafeBytes__.call(withUnsafeBytes.$toUpcallStub(body, arena$), this.$memorySegment());
367384
}
368385
}
369-
"""
386+
""",
370387
]
371388
)
372389
}
373390

374-
@Test("Import DataProtocol: Swift thunks", arguments: zip(
375-
[Self.foundationDataProtocol_interfaceFile, Self.essentialsDataProtocol_interfaceFile, Self.ifConfigDataProtocol_interfaceFile],
376-
["import Foundation", "import FoundationEssentials", Self.ifConfigImport]
377-
))
391+
@Test(
392+
"Import DataProtocol: Swift thunks",
393+
arguments: zip(
394+
[
395+
Self.foundationDataProtocol_interfaceFile, Self.essentialsDataProtocol_interfaceFile,
396+
Self.ifConfigDataProtocol_interfaceFile,
397+
],
398+
["import Foundation", "import FoundationEssentials", Self.ifConfigImport]
399+
))
378400
func dataProtocol_swiftThunk(fileContent: String, expectedImportChunk: String) throws {
379401
try assertOutput(
380402
input: fileContent, .ffm, .swift,
@@ -390,14 +412,17 @@ final class DataImportTests {
390412
// Just to make sure 'Data' is imported.
391413
"""
392414
@_cdecl("swiftjava_getType_SwiftModule_Data")
393-
"""
415+
""",
394416
]
395417
)
396418
}
397419

398-
@Test("Import DataProtocol: JavaBindings", arguments: [
399-
Self.foundationDataProtocol_interfaceFile, Self.essentialsDataProtocol_interfaceFile, Self.ifConfigDataProtocol_interfaceFile
400-
])
420+
@Test(
421+
"Import DataProtocol: JavaBindings",
422+
arguments: [
423+
Self.foundationDataProtocol_interfaceFile, Self.essentialsDataProtocol_interfaceFile,
424+
Self.ifConfigDataProtocol_interfaceFile,
425+
])
401426
func dataProtocol_javaBindings(fileContent: String) throws {
402427

403428
try assertOutput(
@@ -445,9 +470,9 @@ final class DataImportTests {
445470
// Just to make sure 'Data' is imported.
446471
"""
447472
public final class Data extends FFMSwiftInstance implements SwiftValue {
448-
"""
473+
""",
449474
]
450475
)
451476
}
452-
477+
453478
}

Tests/JExtractSwiftTests/MethodImportTests.swift

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//
33
// This source file is part of the Swift.org open source project
44
//
5-
// Copyright (c) 2024 Apple Inc. and the Swift.org project authors
5+
// Copyright (c) 2026 Apple Inc. and the Swift.org project authors
66
// Licensed under Apache License v2.0
77
//
88
// See LICENSE.txt for license information
@@ -39,11 +39,11 @@ final class MethodImportTests {
3939
l: Int64,
4040
s: String
4141
)
42-
42+
4343
public func globalReturnClass() -> MySwiftClass
4444
4545
public func globalReturnAny() -> Any
46-
46+
4747
public func swapRawBufferPointer(buffer: UnsafeRawBufferPointer) -> UnsafeMutableRawBufferPointer
4848
4949
extension MySwiftClass {
@@ -142,7 +142,12 @@ final class MethodImportTests {
142142
* public func globalTakeInt(i: Int)
143143
* }
144144
*/
145-
public static void globalTakeInt(long i) {
145+
public static void globalTakeInt(long i) throws SwiftIntegerOverflowException {
146+
if (SwiftValueLayout.has32bitSwiftInt) {
147+
if (i < Integer.MIN_VALUE || i > Integer.MAX_VALUE) {
148+
throw new SwiftIntegerOverflowException("Parameter 'i' overflow: " + i);
149+
}
150+
}
146151
swiftjava___FakeModule_globalTakeInt_i.call(i);
147152
}
148153
"""
@@ -362,9 +367,15 @@ final class MethodImportTests {
362367
* public func makeInt() -> Int
363368
* }
364369
*/
365-
public long makeInt() {
370+
public long makeInt() throws SwiftIntegerOverflowException {
366371
$ensureAlive();
367-
return swiftjava___FakeModule_MySwiftClass_makeInt.call(this.$memorySegment());
372+
long _result$checked = swiftjava___FakeModule_MySwiftClass_makeInt.call(this.$memorySegment());
373+
if (SwiftValueLayout.has32bitSwiftInt) {
374+
if (_result$checked < Integer.MIN_VALUE || _result$checked > Integer.MAX_VALUE) {
375+
throw new SwiftIntegerOverflowException("Return value overflow: " + _result$checked);
376+
}
377+
}
378+
return _result$checked;
368379
}
369380
"""
370381
)
@@ -405,8 +416,16 @@ final class MethodImportTests {
405416
* public init(len: Swift.Int, cap: Swift.Int)
406417
* }
407418
*/
408-
public static MySwiftClass init(long len, long cap, AllocatingSwiftArena swiftArena$) {
419+
public static MySwiftClass init(long len, long cap, AllocatingSwiftArena swiftArena$) throws SwiftIntegerOverflowException {
409420
MemorySegment _result = swiftArena$.allocate(MySwiftClass.$LAYOUT);
421+
if (SwiftValueLayout.has32bitSwiftInt) {
422+
if (len < Integer.MIN_VALUE || len > Integer.MAX_VALUE) {
423+
throw new SwiftIntegerOverflowException("Parameter 'len' overflow: " + len);
424+
}
425+
if (cap < Integer.MIN_VALUE || cap > Integer.MAX_VALUE) {
426+
throw new SwiftIntegerOverflowException("Parameter 'cap' overflow: " + cap);
427+
}
428+
}
410429
swiftjava___FakeModule_MySwiftClass_init_len_cap.call(len, cap, _result)
411430
return MySwiftClass.wrapMemoryAddressUnsafe(_result, swiftArena$);
412431
}
@@ -450,8 +469,16 @@ final class MethodImportTests {
450469
* public init(len: Swift.Int, cap: Swift.Int)
451470
* }
452471
*/
453-
public static MySwiftStruct init(long len, long cap, AllocatingSwiftArena swiftArena$) {
472+
public static MySwiftStruct init(long len, long cap, AllocatingSwiftArena swiftArena$) throws SwiftIntegerOverflowException {
454473
MemorySegment _result = swiftArena$.allocate(MySwiftStruct.$LAYOUT);
474+
if (SwiftValueLayout.has32bitSwiftInt) {
475+
if (len < Integer.MIN_VALUE || len > Integer.MAX_VALUE) {
476+
throw new SwiftIntegerOverflowException("Parameter 'len' overflow: " + len);
477+
}
478+
if (cap < Integer.MIN_VALUE || cap > Integer.MAX_VALUE) {
479+
throw new SwiftIntegerOverflowException("Parameter 'cap' overflow: " + cap);
480+
}
481+
}
455482
swiftjava___FakeModule_MySwiftStruct_init_len_cap.call(len, cap, _result)
456483
return MySwiftStruct.wrapMemoryAddressUnsafe(_result, swiftArena$);
457484
}
@@ -468,8 +495,9 @@ final class MethodImportTests {
468495

469496
try st.analyze(path: "Fake.swift", text: class_interfaceFile)
470497

471-
#expect(!st.importedGlobalFuncs.contains {
472-
$0.name == "globalReturnAny"
473-
}, "'Any' return type is not supported yet")
498+
#expect(
499+
!st.importedGlobalFuncs.contains {
500+
$0.name == "globalReturnAny"
501+
}, "'Any' return type is not supported yet")
474502
}
475503
}

0 commit comments

Comments
 (0)