From d2323514deece16b777de9c7e48d7efeddf701ab Mon Sep 17 00:00:00 2001 From: Divya Prakash Date: Tue, 3 Feb 2026 07:23:48 +0530 Subject: [PATCH 1/5] jextract: implement Java-side integer overflow checks for FFM (#517) --- ...t2JavaGenerator+JavaBindingsPrinting.swift | 60 ++++++++++++++++++- ...MSwift2JavaGenerator+JavaTranslation.swift | 28 ++++++++- .../swift/swiftkit/ffm/SwiftValueLayout.java | 2 + .../JExtractSwiftTests/DataImportTests.swift | 17 +++++- .../MethodImportTests.swift | 37 ++++++++++-- .../StringPassingTests.swift | 10 +++- .../UnsignedNumberTests.swift | 18 +++++- .../VariableImportTests.swift | 17 +++++- 8 files changed, 169 insertions(+), 20 deletions(-) diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift index 15089b9c1..7261d7bd1 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift @@ -382,6 +382,10 @@ extension FFMSwift2JavaGenerator { paramDecls.append("AllocatingSwiftArena swiftArena") } + let needsThrows = translatedSignature.parameters.contains { $0.needs32BitIntOverflowCheck } || + translatedSignature.result.needs32BitIntOverflowCheck + let throwsClause = needsThrows ? " throws SwiftIntegerOverflowException" : "" + TranslatedDocumentation.printDocumentation( importedFunc: decl, translatedDecl: translated, @@ -389,7 +393,7 @@ extension FFMSwift2JavaGenerator { ) printer.printBraceBlock( """ - \(annotationsStr)\(modifiers) \(returnTy) \(methodName)(\(paramDecls.joined(separator: ", "))) + \(annotationsStr)\(modifiers) \(returnTy) \(methodName)(\(paramDecls.joined(separator: ", ")))\(throwsClause) """ ) { printer in if case .instance(_) = decl.functionSignature.selfParameter { @@ -473,6 +477,25 @@ extension FFMSwift2JavaGenerator { ) } + let hasOverflowChecks = translatedSignature.parameters.contains { $0.needs32BitIntOverflowCheck } + if hasOverflowChecks { + printer.print("if (SwiftValueLayout.has32bitSwiftInt) {") + printer.indent() + for (i, parameter) in translatedSignature.parameters.enumerated() { + if parameter.needs32BitIntOverflowCheck { + let original = decl.functionSignature.parameters[i] + let parameterName = original.parameterName ?? "_\(i)" + printer.print("if (\(parameterName) < Integer.MIN_VALUE || \(parameterName) > Integer.MAX_VALUE) {") + printer.indent() + printer.print("throw new SwiftIntegerOverflowException(\"Parameter '\(parameterName)' overflow: \" + \(parameterName));") + printer.outdent() + printer.print("}") + } + } + printer.outdent() + printer.print("}") + } + //=== Part 3: Downcall. let downCall = "\(thunkName).call(\(downCallArguments.joined(separator: ", ")))" @@ -506,9 +529,40 @@ extension FFMSwift2JavaGenerator { switch translatedSignature.result.conversion { case .initializeResultWithUpcall(_, let extractResult): printer.print("\(result);") // the result in the callback situation is a series of setup steps - printer.print("return \(extractResult.render(&printer, placeholder));") // extract the actual result + let extracted = extractResult.render(&printer, placeholder) + if translatedSignature.result.needs32BitIntOverflowCheck { + let resultVar = "_result$checked" + printer.print("long \(resultVar) = \(extracted);") + printer.print("if (SwiftValueLayout.has32bitSwiftInt) {") + printer.indent() + printer.print("if (\(resultVar) < Integer.MIN_VALUE || \(resultVar) > Integer.MAX_VALUE) {") + printer.indent() + printer.print("throw new SwiftIntegerOverflowException(\"Return value overflow: \" + \(resultVar));") + printer.outdent() + printer.print("}") + printer.outdent() + printer.print("}") + printer.print("return \(resultVar);") + } else { + printer.print("return \(extracted);") // extract the actual result + } default: - printer.print("return \(result);") + if translatedSignature.result.needs32BitIntOverflowCheck { + let resultVar = "_result$checked" + printer.print("long \(resultVar) = \(result);") + printer.print("if (SwiftValueLayout.has32bitSwiftInt) {") + printer.indent() + printer.print("if (\(resultVar) < Integer.MIN_VALUE || \(resultVar) > Integer.MAX_VALUE) {") + printer.indent() + printer.print("throw new SwiftIntegerOverflowException(\"Return value overflow: \" + \(resultVar));") + printer.outdent() + printer.print("}") + printer.outdent() + printer.print("}") + printer.print("return \(resultVar);") + } else { + printer.print("return \(result);") + } } } else { printer.print("\(result);") diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift index 4c474cca9..b96c7445b 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift @@ -50,6 +50,9 @@ extension FFMSwift2JavaGenerator { /// Describes how to convert the Java parameter to the lowered arguments for /// the foreign function. var conversion: JavaConversionStep + + /// Whether this parameter requires 32-bit integer overflow checking + var needs32BitIntOverflowCheck: Bool = false } /// Represent a Swift API result translated to Java. @@ -88,6 +91,9 @@ extension FFMSwift2JavaGenerator { /// Describes how to construct the Java result from the foreign function return /// value and/or the out parameters. var conversion: JavaConversionStep + + /// Whether this result requires 32-bit integer overflow checking + var needs32BitIntOverflowCheck: Bool = false } /// Translated Java API representing a Swift API. @@ -342,6 +348,14 @@ extension FFMSwift2JavaGenerator { // be expressed as a Java primitive type. if let cType = try? CType(cdeclType: swiftType) { let javaType = cType.javaType + let needsOverflowCheck: Bool + if case .integral(.ptrdiff_t) = cType { + needsOverflowCheck = true + } else if case .integral(.size_t) = cType { + needsOverflowCheck = true + } else { + needsOverflowCheck = false + } return TranslatedParameter( javaParameters: [ JavaParameter( @@ -350,7 +364,8 @@ extension FFMSwift2JavaGenerator { annotations: parameterAnnotations ) ], - conversion: .placeholder + conversion: .placeholder, + needs32BitIntOverflowCheck: needsOverflowCheck ) } @@ -620,11 +635,20 @@ extension FFMSwift2JavaGenerator { // be expressed as a Java primitive type. if let cType = try? CType(cdeclType: swiftType) { let javaType = cType.javaType + let needsOverflowCheck: Bool + if case .integral(.ptrdiff_t) = cType { + needsOverflowCheck = true + } else if case .integral(.size_t) = cType { + needsOverflowCheck = true + } else { + needsOverflowCheck = false + } return TranslatedResult( javaResultType: javaType, annotations: resultAnnotations, outParameters: [], - conversion: .placeholder + conversion: .placeholder, + needs32BitIntOverflowCheck: needsOverflowCheck ) } diff --git a/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftValueLayout.java b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftValueLayout.java index be883e051..0c09cca4d 100644 --- a/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftValueLayout.java +++ b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftValueLayout.java @@ -69,4 +69,6 @@ public static long addressByteSize() { * Java does not have unsigned integer types, so we use the layout for Swift's {@code Int}. */ public static ValueLayout SWIFT_UINT = SWIFT_INT; + + public static final boolean has32bitSwiftInt = (SWIFT_INT == ValueLayout.JAVA_INT); } diff --git a/Tests/JExtractSwiftTests/DataImportTests.swift b/Tests/JExtractSwiftTests/DataImportTests.swift index a597038a0..08652acaf 100644 --- a/Tests/JExtractSwiftTests/DataImportTests.swift +++ b/Tests/JExtractSwiftTests/DataImportTests.swift @@ -254,8 +254,13 @@ final class DataImportTests { * public init(bytes: UnsafeRawPointer, count: Int) * } */ - public static Data init(java.lang.foreign.MemorySegment bytes, long count, AllocatingSwiftArena swiftArena) { + public static Data init(java.lang.foreign.MemorySegment bytes, long count, AllocatingSwiftArena swiftArena) throws SwiftIntegerOverflowException { MemorySegment _result = swiftArena.allocate(Data.$LAYOUT); + if (SwiftValueLayout.has32bitSwiftInt) { + if (count < Integer.MIN_VALUE || count > Integer.MAX_VALUE) { + throw new SwiftIntegerOverflowException("Parameter 'count' overflow: " + count); + } + } swiftjava_SwiftModule_Data_init_bytes_count.call(bytes, count, _result); return Data.wrapMemoryAddressUnsafe(_result, swiftArena); } @@ -295,9 +300,15 @@ final class DataImportTests { * public var count: Int * } */ - public long getCount() { + public long getCount() throws SwiftIntegerOverflowException { $ensureAlive(); - return swiftjava_SwiftModule_Data_count$get.call(this.$memorySegment()); + long _result$checked = swiftjava_SwiftModule_Data_count$get.call(this.$memorySegment()); + if (SwiftValueLayout.has32bitSwiftInt) { + if (_result$checked < Integer.MIN_VALUE || _result$checked > Integer.MAX_VALUE) { + throw new SwiftIntegerOverflowException("Return value overflow: " + _result$checked); + } + } + return _result$checked; } """, diff --git a/Tests/JExtractSwiftTests/MethodImportTests.swift b/Tests/JExtractSwiftTests/MethodImportTests.swift index be9984e08..c05e702a5 100644 --- a/Tests/JExtractSwiftTests/MethodImportTests.swift +++ b/Tests/JExtractSwiftTests/MethodImportTests.swift @@ -143,7 +143,12 @@ final class MethodImportTests { * public func globalTakeInt(i: Int) * } */ - public static void globalTakeInt(long i) { + public static void globalTakeInt(long i) throws SwiftIntegerOverflowException { + if (SwiftValueLayout.has32bitSwiftInt) { + if (i < Integer.MIN_VALUE || i > Integer.MAX_VALUE) { + throw new SwiftIntegerOverflowException("Parameter 'i' overflow: " + i); + } + } swiftjava___FakeModule_globalTakeInt_i.call(i); } """ @@ -363,9 +368,15 @@ final class MethodImportTests { * public func makeInt() -> Int * } */ - public long makeInt() { + public long makeInt() throws SwiftIntegerOverflowException { $ensureAlive(); - return swiftjava___FakeModule_MySwiftClass_makeInt.call(this.$memorySegment()); + long _result$checked = swiftjava___FakeModule_MySwiftClass_makeInt.call(this.$memorySegment()); + if (SwiftValueLayout.has32bitSwiftInt) { + if (_result$checked < Integer.MIN_VALUE || _result$checked > Integer.MAX_VALUE) { + throw new SwiftIntegerOverflowException("Return value overflow: " + _result$checked); + } + } + return _result$checked; } """ ) @@ -406,8 +417,16 @@ final class MethodImportTests { * public init(len: Swift.Int, cap: Swift.Int) * } */ - public static MySwiftClass init(long len, long cap, AllocatingSwiftArena swiftArena) { + public static MySwiftClass init(long len, long cap, AllocatingSwiftArena swiftArena) throws SwiftIntegerOverflowException { MemorySegment _result = swiftArena.allocate(MySwiftClass.$LAYOUT); + if (SwiftValueLayout.has32bitSwiftInt) { + if (len < Integer.MIN_VALUE || len > Integer.MAX_VALUE) { + throw new SwiftIntegerOverflowException("Parameter 'len' overflow: " + len); + } + if (cap < Integer.MIN_VALUE || cap > Integer.MAX_VALUE) { + throw new SwiftIntegerOverflowException("Parameter 'cap' overflow: " + cap); + } + } swiftjava___FakeModule_MySwiftClass_init_len_cap.call(len, cap, _result) return MySwiftClass.wrapMemoryAddressUnsafe(_result, swiftArena); } @@ -451,8 +470,16 @@ final class MethodImportTests { * public init(len: Swift.Int, cap: Swift.Int) * } */ - public static MySwiftStruct init(long len, long cap, AllocatingSwiftArena swiftArena) { + public static MySwiftStruct init(long len, long cap, AllocatingSwiftArena swiftArena) throws SwiftIntegerOverflowException { MemorySegment _result = swiftArena.allocate(MySwiftStruct.$LAYOUT); + if (SwiftValueLayout.has32bitSwiftInt) { + if (len < Integer.MIN_VALUE || len > Integer.MAX_VALUE) { + throw new SwiftIntegerOverflowException("Parameter 'len' overflow: " + len); + } + if (cap < Integer.MIN_VALUE || cap > Integer.MAX_VALUE) { + throw new SwiftIntegerOverflowException("Parameter 'cap' overflow: " + cap); + } + } swiftjava___FakeModule_MySwiftStruct_init_len_cap.call(len, cap, _result) return MySwiftStruct.wrapMemoryAddressUnsafe(_result, swiftArena); } diff --git a/Tests/JExtractSwiftTests/StringPassingTests.swift b/Tests/JExtractSwiftTests/StringPassingTests.swift index 65bb2a5c3..58ceca9f1 100644 --- a/Tests/JExtractSwiftTests/StringPassingTests.swift +++ b/Tests/JExtractSwiftTests/StringPassingTests.swift @@ -64,9 +64,15 @@ final class StringPassingTests { * public func writeString(string: String) -> Int * } */ - public static long writeString(java.lang.String string) { + public static long writeString(java.lang.String string) throws SwiftIntegerOverflowException { try(var arena$ = Arena.ofConfined()) { - return swiftjava___FakeModule_writeString_string.call(SwiftRuntime.toCString(string, arena$)); + long _result$checked = swiftjava___FakeModule_writeString_string.call(SwiftRuntime.toCString(string, arena$)); + if (SwiftValueLayout.has32bitSwiftInt) { + if (_result$checked < Integer.MIN_VALUE || _result$checked > Integer.MAX_VALUE) { + throw new SwiftIntegerOverflowException("Return value overflow: " + _result$checked); + } + } + return _result$checked; } } """, diff --git a/Tests/JExtractSwiftTests/UnsignedNumberTests.swift b/Tests/JExtractSwiftTests/UnsignedNumberTests.swift index 5a56524e7..6e3cb9eb7 100644 --- a/Tests/JExtractSwiftTests/UnsignedNumberTests.swift +++ b/Tests/JExtractSwiftTests/UnsignedNumberTests.swift @@ -357,8 +357,22 @@ final class UnsignedNumberTests { """, """ @Unsigned - public static long unsignedLong(@Unsigned long first, @Unsigned long second) { - return swiftjava_SwiftModule_unsignedLong_first_second.call(first, second); + public static long unsignedLong(@Unsigned long first, @Unsigned long second) throws SwiftIntegerOverflowException { + if (SwiftValueLayout.has32bitSwiftInt) { + if (first < Integer.MIN_VALUE || first > Integer.MAX_VALUE) { + throw new SwiftIntegerOverflowException("Parameter 'first' overflow: " + first); + } + if (second < Integer.MIN_VALUE || second > Integer.MAX_VALUE) { + throw new SwiftIntegerOverflowException("Parameter 'second' overflow: " + second); + } + } + long _result$checked = swiftjava_SwiftModule_unsignedLong_first_second.call(first, second); + if (SwiftValueLayout.has32bitSwiftInt) { + if (_result$checked < Integer.MIN_VALUE || _result$checked > Integer.MAX_VALUE) { + throw new SwiftIntegerOverflowException("Return value overflow: " + _result$checked); + } + } + return _result$checked; } """, ] diff --git a/Tests/JExtractSwiftTests/VariableImportTests.swift b/Tests/JExtractSwiftTests/VariableImportTests.swift index e5837d2e0..3b81ea9db 100644 --- a/Tests/JExtractSwiftTests/VariableImportTests.swift +++ b/Tests/JExtractSwiftTests/VariableImportTests.swift @@ -70,9 +70,15 @@ final class VariableImportTests { * public var counterInt: Int * } */ - public long getCounterInt() { + public long getCounterInt() throws SwiftIntegerOverflowException { $ensureAlive(); - return swiftjava_FakeModule_MySwiftClass_counterInt$get.call(this.$memorySegment()); + long _result$checked = swiftjava_FakeModule_MySwiftClass_counterInt$get.call(this.$memorySegment()); + if (SwiftValueLayout.has32bitSwiftInt) { + if (_result$checked < Integer.MIN_VALUE || _result$checked > Integer.MAX_VALUE) { + throw new SwiftIntegerOverflowException("Return value overflow: " + _result$checked); + } + } + return _result$checked; } """, """ @@ -103,8 +109,13 @@ final class VariableImportTests { * public var counterInt: Int * } */ - public void setCounterInt(long newValue) { + public void setCounterInt(long newValue) throws SwiftIntegerOverflowException { $ensureAlive(); + if (SwiftValueLayout.has32bitSwiftInt) { + if (newValue < Integer.MIN_VALUE || newValue > Integer.MAX_VALUE) { + throw new SwiftIntegerOverflowException("Parameter 'newValue' overflow: " + newValue); + } + } swiftjava_FakeModule_MySwiftClass_counterInt$set.call(newValue, this.$memorySegment()) } """, From fbc948f79705911acc97d0e6baa0d1a8ee0df8a8 Mon Sep 17 00:00:00 2001 From: Divya Prakash Date: Tue, 3 Feb 2026 07:35:54 +0530 Subject: [PATCH 2/5] jextract: improve FFM overflow checks to distinguish signed/unsigned types (#517) --- ...t2JavaGenerator+JavaBindingsPrinting.swift | 59 +++++++++++++++---- ...MSwift2JavaGenerator+JavaTranslation.swift | 30 ++++++---- .../UnsignedNumberTests.swift | 6 +- 3 files changed, 70 insertions(+), 25 deletions(-) diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift index 7261d7bd1..d98fb29e9 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift @@ -382,8 +382,8 @@ extension FFMSwift2JavaGenerator { paramDecls.append("AllocatingSwiftArena swiftArena") } - let needsThrows = translatedSignature.parameters.contains { $0.needs32BitIntOverflowCheck } || - translatedSignature.result.needs32BitIntOverflowCheck + let needsThrows = translatedSignature.parameters.contains { $0.needs32BitIntOverflowCheck != .none } || + translatedSignature.result.needs32BitIntOverflowCheck != .none let throwsClause = needsThrows ? " throws SwiftIntegerOverflowException" : "" TranslatedDocumentation.printDocumentation( @@ -477,12 +477,15 @@ extension FFMSwift2JavaGenerator { ) } - let hasOverflowChecks = translatedSignature.parameters.contains { $0.needs32BitIntOverflowCheck } + let hasOverflowChecks = translatedSignature.parameters.contains { $0.needs32BitIntOverflowCheck != .none } if hasOverflowChecks { printer.print("if (SwiftValueLayout.has32bitSwiftInt) {") printer.indent() for (i, parameter) in translatedSignature.parameters.enumerated() { - if parameter.needs32BitIntOverflowCheck { + switch parameter.needs32BitIntOverflowCheck { + case .none: + break + case .signedInt: let original = decl.functionSignature.parameters[i] let parameterName = original.parameterName ?? "_\(i)" printer.print("if (\(parameterName) < Integer.MIN_VALUE || \(parameterName) > Integer.MAX_VALUE) {") @@ -490,6 +493,14 @@ extension FFMSwift2JavaGenerator { printer.print("throw new SwiftIntegerOverflowException(\"Parameter '\(parameterName)' overflow: \" + \(parameterName));") printer.outdent() printer.print("}") + case .unsignedInt: + let original = decl.functionSignature.parameters[i] + let parameterName = original.parameterName ?? "_\(i)" + printer.print("if (\(parameterName) < 0 || \(parameterName) > 0xFFFFFFFFL) {") + printer.indent() + printer.print("throw new SwiftIntegerOverflowException(\"Parameter '\(parameterName)' overflow: \" + \(parameterName));") + printer.outdent() + printer.print("}") } } printer.outdent() @@ -530,7 +541,10 @@ extension FFMSwift2JavaGenerator { case .initializeResultWithUpcall(_, let extractResult): printer.print("\(result);") // the result in the callback situation is a series of setup steps let extracted = extractResult.render(&printer, placeholder) - if translatedSignature.result.needs32BitIntOverflowCheck { + switch translatedSignature.result.needs32BitIntOverflowCheck { + case .none: + printer.print("return \(extracted);") // extract the actual result + case .signedInt: let resultVar = "_result$checked" printer.print("long \(resultVar) = \(extracted);") printer.print("if (SwiftValueLayout.has32bitSwiftInt) {") @@ -543,11 +557,25 @@ extension FFMSwift2JavaGenerator { printer.outdent() printer.print("}") printer.print("return \(resultVar);") - } else { - printer.print("return \(extracted);") // extract the actual result + case .unsignedInt: + let resultVar = "_result$checked" + printer.print("long \(resultVar) = \(extracted);") + printer.print("if (SwiftValueLayout.has32bitSwiftInt) {") + printer.indent() + printer.print("if (\(resultVar) < 0 || \(resultVar) > 0xFFFFFFFFL) {") + printer.indent() + printer.print("throw new SwiftIntegerOverflowException(\"Return value overflow: \" + \(resultVar));") + printer.outdent() + printer.print("}") + printer.outdent() + printer.print("}") + printer.print("return \(resultVar);") } default: - if translatedSignature.result.needs32BitIntOverflowCheck { + switch translatedSignature.result.needs32BitIntOverflowCheck { + case .none: + printer.print("return \(result);") + case .signedInt: let resultVar = "_result$checked" printer.print("long \(resultVar) = \(result);") printer.print("if (SwiftValueLayout.has32bitSwiftInt) {") @@ -560,8 +588,19 @@ extension FFMSwift2JavaGenerator { printer.outdent() printer.print("}") printer.print("return \(resultVar);") - } else { - printer.print("return \(result);") + case .unsignedInt: + let resultVar = "_result$checked" + printer.print("long \(resultVar) = \(result);") + printer.print("if (SwiftValueLayout.has32bitSwiftInt) {") + printer.indent() + printer.print("if (\(resultVar) < 0 || \(resultVar) > 0xFFFFFFFFL) {") + printer.indent() + printer.print("throw new SwiftIntegerOverflowException(\"Return value overflow: \" + \(resultVar));") + printer.outdent() + printer.print("}") + printer.outdent() + printer.print("}") + printer.print("return \(resultVar);") } } } else { diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift index b96c7445b..3e77da264 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift @@ -52,7 +52,13 @@ extension FFMSwift2JavaGenerator { var conversion: JavaConversionStep /// Whether this parameter requires 32-bit integer overflow checking - var needs32BitIntOverflowCheck: Bool = false + var needs32BitIntOverflowCheck: OverflowCheckType = .none + } + + enum OverflowCheckType { + case none + case signedInt // Int: -2147483648 to 2147483647 + case unsignedInt // UInt: 0 to 4294967295 } /// Represent a Swift API result translated to Java. @@ -93,7 +99,7 @@ extension FFMSwift2JavaGenerator { var conversion: JavaConversionStep /// Whether this result requires 32-bit integer overflow checking - var needs32BitIntOverflowCheck: Bool = false + var needs32BitIntOverflowCheck: OverflowCheckType = .none } /// Translated Java API representing a Swift API. @@ -348,13 +354,13 @@ extension FFMSwift2JavaGenerator { // be expressed as a Java primitive type. if let cType = try? CType(cdeclType: swiftType) { let javaType = cType.javaType - let needsOverflowCheck: Bool + let overflowCheck: OverflowCheckType if case .integral(.ptrdiff_t) = cType { - needsOverflowCheck = true + overflowCheck = .signedInt } else if case .integral(.size_t) = cType { - needsOverflowCheck = true + overflowCheck = .unsignedInt } else { - needsOverflowCheck = false + overflowCheck = .none } return TranslatedParameter( javaParameters: [ @@ -365,7 +371,7 @@ extension FFMSwift2JavaGenerator { ) ], conversion: .placeholder, - needs32BitIntOverflowCheck: needsOverflowCheck + needs32BitIntOverflowCheck: overflowCheck ) } @@ -635,20 +641,20 @@ extension FFMSwift2JavaGenerator { // be expressed as a Java primitive type. if let cType = try? CType(cdeclType: swiftType) { let javaType = cType.javaType - let needsOverflowCheck: Bool + let overflowCheck: OverflowCheckType if case .integral(.ptrdiff_t) = cType { - needsOverflowCheck = true + overflowCheck = .signedInt } else if case .integral(.size_t) = cType { - needsOverflowCheck = true + overflowCheck = .unsignedInt } else { - needsOverflowCheck = false + overflowCheck = .none } return TranslatedResult( javaResultType: javaType, annotations: resultAnnotations, outParameters: [], conversion: .placeholder, - needs32BitIntOverflowCheck: needsOverflowCheck + needs32BitIntOverflowCheck: overflowCheck ) } diff --git a/Tests/JExtractSwiftTests/UnsignedNumberTests.swift b/Tests/JExtractSwiftTests/UnsignedNumberTests.swift index 6e3cb9eb7..1979dc184 100644 --- a/Tests/JExtractSwiftTests/UnsignedNumberTests.swift +++ b/Tests/JExtractSwiftTests/UnsignedNumberTests.swift @@ -359,16 +359,16 @@ final class UnsignedNumberTests { @Unsigned public static long unsignedLong(@Unsigned long first, @Unsigned long second) throws SwiftIntegerOverflowException { if (SwiftValueLayout.has32bitSwiftInt) { - if (first < Integer.MIN_VALUE || first > Integer.MAX_VALUE) { + if (first < 0 || first > 0xFFFFFFFFL) { throw new SwiftIntegerOverflowException("Parameter 'first' overflow: " + first); } - if (second < Integer.MIN_VALUE || second > Integer.MAX_VALUE) { + if (second < 0 || second > 0xFFFFFFFFL) { throw new SwiftIntegerOverflowException("Parameter 'second' overflow: " + second); } } long _result$checked = swiftjava_SwiftModule_unsignedLong_first_second.call(first, second); if (SwiftValueLayout.has32bitSwiftInt) { - if (_result$checked < Integer.MIN_VALUE || _result$checked > Integer.MAX_VALUE) { + if (_result$checked < 0 || _result$checked > 0xFFFFFFFFL) { throw new SwiftIntegerOverflowException("Return value overflow: " + _result$checked); } } From d9f49163761e056103f7a3cf5740bd5e2fc17ad5 Mon Sep 17 00:00:00 2001 From: Konrad Malawski Date: Wed, 18 Mar 2026 15:24:57 +0900 Subject: [PATCH 3/5] Fix overflow checks: add exception constructor, rename swiftArena$, refactor duplication - Add String message constructor to SwiftIntegerOverflowException (fixes compilation error) - Rename swiftArena$ to swiftArena to match main branch convention - Extract printReturnWithOverflowCheck helper to reduce code duplication --- ...t2JavaGenerator+JavaBindingsPrinting.swift | 100 +++++++----------- .../core/SwiftIntegerOverflowException.java | 4 + 2 files changed, 44 insertions(+), 60 deletions(-) diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift index d98fb29e9..7b5537939 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift @@ -541,67 +541,9 @@ extension FFMSwift2JavaGenerator { case .initializeResultWithUpcall(_, let extractResult): printer.print("\(result);") // the result in the callback situation is a series of setup steps let extracted = extractResult.render(&printer, placeholder) - switch translatedSignature.result.needs32BitIntOverflowCheck { - case .none: - printer.print("return \(extracted);") // extract the actual result - case .signedInt: - let resultVar = "_result$checked" - printer.print("long \(resultVar) = \(extracted);") - printer.print("if (SwiftValueLayout.has32bitSwiftInt) {") - printer.indent() - printer.print("if (\(resultVar) < Integer.MIN_VALUE || \(resultVar) > Integer.MAX_VALUE) {") - printer.indent() - printer.print("throw new SwiftIntegerOverflowException(\"Return value overflow: \" + \(resultVar));") - printer.outdent() - printer.print("}") - printer.outdent() - printer.print("}") - printer.print("return \(resultVar);") - case .unsignedInt: - let resultVar = "_result$checked" - printer.print("long \(resultVar) = \(extracted);") - printer.print("if (SwiftValueLayout.has32bitSwiftInt) {") - printer.indent() - printer.print("if (\(resultVar) < 0 || \(resultVar) > 0xFFFFFFFFL) {") - printer.indent() - printer.print("throw new SwiftIntegerOverflowException(\"Return value overflow: \" + \(resultVar));") - printer.outdent() - printer.print("}") - printer.outdent() - printer.print("}") - printer.print("return \(resultVar);") - } + printReturnWithOverflowCheck(&printer, value: extracted, overflowCheck: translatedSignature.result.needs32BitIntOverflowCheck) default: - switch translatedSignature.result.needs32BitIntOverflowCheck { - case .none: - printer.print("return \(result);") - case .signedInt: - let resultVar = "_result$checked" - printer.print("long \(resultVar) = \(result);") - printer.print("if (SwiftValueLayout.has32bitSwiftInt) {") - printer.indent() - printer.print("if (\(resultVar) < Integer.MIN_VALUE || \(resultVar) > Integer.MAX_VALUE) {") - printer.indent() - printer.print("throw new SwiftIntegerOverflowException(\"Return value overflow: \" + \(resultVar));") - printer.outdent() - printer.print("}") - printer.outdent() - printer.print("}") - printer.print("return \(resultVar);") - case .unsignedInt: - let resultVar = "_result$checked" - printer.print("long \(resultVar) = \(result);") - printer.print("if (SwiftValueLayout.has32bitSwiftInt) {") - printer.indent() - printer.print("if (\(resultVar) < 0 || \(resultVar) > 0xFFFFFFFFL) {") - printer.indent() - printer.print("throw new SwiftIntegerOverflowException(\"Return value overflow: \" + \(resultVar));") - printer.outdent() - printer.print("}") - printer.outdent() - printer.print("}") - printer.print("return \(resultVar);") - } + printReturnWithOverflowCheck(&printer, value: result, overflowCheck: translatedSignature.result.needs32BitIntOverflowCheck) } } else { printer.print("\(result);") @@ -614,6 +556,44 @@ extension FFMSwift2JavaGenerator { } } + /// Print a return statement with an optional 32-bit integer overflow check. + private func printReturnWithOverflowCheck( + _ printer: inout CodePrinter, + value: String, + overflowCheck: OverflowCheckType + ) { + switch overflowCheck { + case .none: + printer.print("return \(value);") + case .signedInt: + let resultVar = "_result$checked" + printer.print("long \(resultVar) = \(value);") + printer.print("if (SwiftValueLayout.has32bitSwiftInt) {") + printer.indent() + printer.print("if (\(resultVar) < Integer.MIN_VALUE || \(resultVar) > Integer.MAX_VALUE) {") + printer.indent() + printer.print("throw new SwiftIntegerOverflowException(\"Return value overflow: \" + \(resultVar));") + printer.outdent() + printer.print("}") + printer.outdent() + printer.print("}") + printer.print("return \(resultVar);") + case .unsignedInt: + let resultVar = "_result$checked" + printer.print("long \(resultVar) = \(value);") + printer.print("if (SwiftValueLayout.has32bitSwiftInt) {") + printer.indent() + printer.print("if (\(resultVar) < 0 || \(resultVar) > 0xFFFFFFFFL) {") + printer.indent() + printer.print("throw new SwiftIntegerOverflowException(\"Return value overflow: \" + \(resultVar));") + printer.outdent() + printer.print("}") + printer.outdent() + printer.print("}") + printer.print("return \(resultVar);") + } + } + func renderMemoryLayoutValue(for javaType: JavaType) -> String { if let layout = ForeignValueLayout(javaType: javaType) { return layout.description diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftIntegerOverflowException.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftIntegerOverflowException.java index d77e42ddb..1acf4cc11 100644 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftIntegerOverflowException.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftIntegerOverflowException.java @@ -28,4 +28,8 @@ public class SwiftIntegerOverflowException extends RuntimeException { public SwiftIntegerOverflowException() { super("Swift runtime has detected IntegerOverflow! Most probably you are running 32-bit application while using Swift's Int type."); } + + public SwiftIntegerOverflowException(String message) { + super(message); + } } \ No newline at end of file From 275bc9ae4ae7c16ad19325907c0f613afe4166d0 Mon Sep 17 00:00:00 2001 From: Konrad Malawski Date: Wed, 18 Mar 2026 18:11:46 +0900 Subject: [PATCH 4/5] use printBraceBlock consistently --- ...t2JavaGenerator+JavaBindingsPrinting.swift | 105 ++++++++---------- .../core/SwiftIntegerOverflowException.java | 6 +- 2 files changed, 50 insertions(+), 61 deletions(-) diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift index 7b5537939..2f2ceecc1 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift @@ -18,7 +18,7 @@ import SwiftJavaJNICore extension FFMSwift2JavaGenerator { package func printFunctionDowncallMethods( _ printer: inout CodePrinter, - _ decl: ImportedFunc + _ decl: ImportedFunc, ) { guard let _ = translatedDecl(for: decl) else { // Failed to translate. Skip. @@ -38,7 +38,7 @@ extension FFMSwift2JavaGenerator { /// Print FFM Java binding descriptors for the imported Swift API. package func printJavaBindingDescriptorClass( _ printer: inout CodePrinter, - _ decl: ImportedFunc + _ decl: ImportedFunc, ) { let thunkName = thunkNameRegistry.functionThunkName(decl: decl) let translated = self.translatedDecl(for: decl)! @@ -58,7 +58,7 @@ extension FFMSwift2JavaGenerator { package func printJavaBindingDescriptorClass( _ printer: inout CodePrinter, _ cFunc: CFunction, - additionalContent: ((inout CodePrinter) -> Void)? = nil + additionalContent: ((inout CodePrinter) -> Void)? = nil, ) { printer.printBraceBlock( """ @@ -87,7 +87,7 @@ extension FFMSwift2JavaGenerator { func printFunctionDescriptorDefinition( _ printer: inout CodePrinter, _ resultType: CType, - _ parameters: [CParameter] + _ parameters: [CParameter], ) { printer.start("private static final FunctionDescriptor DESC = ") @@ -113,7 +113,7 @@ extension FFMSwift2JavaGenerator { func printJavaBindingDowncallMethod( _ printer: inout CodePrinter, - _ cFunc: CFunction + _ cFunc: CFunction, ) { let returnTy = cFunc.resultType.javaType let maybeReturn = cFunc.resultType.isVoid ? "" : "return (\(returnTy)) " @@ -157,7 +157,7 @@ extension FFMSwift2JavaGenerator { /// * Unnamed-struct parameter as a record. (unimplemented) func printParameterDescriptorClasses( _ printer: inout CodePrinter, - _ cFunc: CFunction + _ cFunc: CFunction, ) { for param in cFunc.parameters { switch param.type { @@ -172,7 +172,7 @@ extension FFMSwift2JavaGenerator { func printUpcallParameterDescriptorClasses( _ printer: inout CodePrinter, - _ outCallback: OutCallback + _ outCallback: OutCallback, ) { let name = outCallback.name printFunctionPointerParameterDescriptorClass(&printer, name, outCallback.cFunc.functionType, impl: outCallback) @@ -199,7 +199,7 @@ extension FFMSwift2JavaGenerator { _ printer: inout CodePrinter, _ name: String, _ cType: CType, - impl: OutCallback? + impl: OutCallback?, ) { let cResultType: CType let cParameterTypes: [CType] @@ -267,7 +267,7 @@ extension FFMSwift2JavaGenerator { /// * User-facing functional interfaces. func printJavaBindingWrapperHelperClass( _ printer: inout CodePrinter, - _ decl: ImportedFunc + _ decl: ImportedFunc, ) { let translated = self.translatedDecl(for: decl)! let bindingDescriptorName = self.thunkNameRegistry.functionThunkName(decl: decl) @@ -290,7 +290,7 @@ extension FFMSwift2JavaGenerator { func printJavaBindingWrapperFunctionTypeHelper( _ printer: inout CodePrinter, _ functionType: TranslatedFunctionType, - _ bindingDescriptorName: String + _ bindingDescriptorName: String, ) { let cdeclDescriptor = "\(bindingDescriptorName).$\(functionType.name)" if functionType.isCompatibleWithC { @@ -356,7 +356,7 @@ extension FFMSwift2JavaGenerator { /// with adding `SwiftArena.ofAuto()` at the end. package func printJavaBindingWrapperMethod( _ printer: inout CodePrinter, - _ decl: ImportedFunc + _ decl: ImportedFunc, ) { let translated = self.translatedDecl(for: decl)! let methodName = translated.name @@ -382,14 +382,13 @@ extension FFMSwift2JavaGenerator { paramDecls.append("AllocatingSwiftArena swiftArena") } - let needsThrows = translatedSignature.parameters.contains { $0.needs32BitIntOverflowCheck != .none } || - translatedSignature.result.needs32BitIntOverflowCheck != .none + let needsThrows = translatedSignature.parameters.contains { $0.needs32BitIntOverflowCheck != .none } || translatedSignature.result.needs32BitIntOverflowCheck != .none let throwsClause = needsThrows ? " throws SwiftIntegerOverflowException" : "" TranslatedDocumentation.printDocumentation( importedFunc: decl, translatedDecl: translated, - in: &printer + in: &printer, ) printer.printBraceBlock( """ @@ -410,7 +409,7 @@ extension FFMSwift2JavaGenerator { /// This assumes that all the parameters are passed-in with appropriate names. package func printDowncall( _ printer: inout CodePrinter, - _ decl: ImportedFunc + _ decl: ImportedFunc, ) { //=== Part 1: prepare temporary arena if needed. let translatedSignature = self.translatedDecl(for: decl)!.translatedSignature @@ -479,32 +478,26 @@ extension FFMSwift2JavaGenerator { let hasOverflowChecks = translatedSignature.parameters.contains { $0.needs32BitIntOverflowCheck != .none } if hasOverflowChecks { - printer.print("if (SwiftValueLayout.has32bitSwiftInt) {") - printer.indent() - for (i, parameter) in translatedSignature.parameters.enumerated() { - switch parameter.needs32BitIntOverflowCheck { - case .none: - break - case .signedInt: - let original = decl.functionSignature.parameters[i] - let parameterName = original.parameterName ?? "_\(i)" - printer.print("if (\(parameterName) < Integer.MIN_VALUE || \(parameterName) > Integer.MAX_VALUE) {") - printer.indent() - printer.print("throw new SwiftIntegerOverflowException(\"Parameter '\(parameterName)' overflow: \" + \(parameterName));") - printer.outdent() - printer.print("}") - case .unsignedInt: - let original = decl.functionSignature.parameters[i] - let parameterName = original.parameterName ?? "_\(i)" - printer.print("if (\(parameterName) < 0 || \(parameterName) > 0xFFFFFFFFL) {") - printer.indent() - printer.print("throw new SwiftIntegerOverflowException(\"Parameter '\(parameterName)' overflow: \" + \(parameterName));") - printer.outdent() - printer.print("}") + printer.printBraceBlock("if (SwiftValueLayout.has32bitSwiftInt)") { printer in + for (i, parameter) in translatedSignature.parameters.enumerated() { + switch parameter.needs32BitIntOverflowCheck { + case .none: + break + case .signedInt: + let original = decl.functionSignature.parameters[i] + let parameterName = original.parameterName ?? "_\(i)" + printer.printBraceBlock("if (\(parameterName) < Integer.MIN_VALUE || \(parameterName) > Integer.MAX_VALUE)") { printer in + printer.print("throw new SwiftIntegerOverflowException(\"Parameter '\(parameterName)' overflow: \" + \(parameterName));") + } + case .unsignedInt: + let original = decl.functionSignature.parameters[i] + let parameterName = original.parameterName ?? "_\(i)" + printer.printBraceBlock("if (\(parameterName) < 0 || \(parameterName) > 0xFFFFFFFFL)") { printer in + printer.print("throw new SwiftIntegerOverflowException(\"Parameter '\(parameterName)' overflow: \" + \(parameterName));") + } + } } } - printer.outdent() - printer.print("}") } //=== Part 3: Downcall. @@ -533,7 +526,7 @@ extension FFMSwift2JavaGenerator { let result = translatedSignature.result.conversion.render( &printer, placeholder, - placeholderForDowncall: placeholderForDowncall + placeholderForDowncall: placeholderForDowncall, ) if translatedSignature.result.javaResultType != .void { @@ -560,7 +553,7 @@ extension FFMSwift2JavaGenerator { private func printReturnWithOverflowCheck( _ printer: inout CodePrinter, value: String, - overflowCheck: OverflowCheckType + overflowCheck: OverflowCheckType, ) { switch overflowCheck { case .none: @@ -568,28 +561,22 @@ extension FFMSwift2JavaGenerator { case .signedInt: let resultVar = "_result$checked" printer.print("long \(resultVar) = \(value);") - printer.print("if (SwiftValueLayout.has32bitSwiftInt) {") - printer.indent() - printer.print("if (\(resultVar) < Integer.MIN_VALUE || \(resultVar) > Integer.MAX_VALUE) {") - printer.indent() - printer.print("throw new SwiftIntegerOverflowException(\"Return value overflow: \" + \(resultVar));") - printer.outdent() - printer.print("}") - printer.outdent() - printer.print("}") + + printer.printBraceBlock("if (SwiftValueLayout.has32bitSwiftInt)") { printer in + printer.printBraceBlock("if (\(resultVar) < Integer.MIN_VALUE || \(resultVar) > Integer.MAX_VALUE)") { printer in + printer.print("throw new SwiftIntegerOverflowException(\"Return value overflow: \" + \(resultVar));") + } + } printer.print("return \(resultVar);") case .unsignedInt: let resultVar = "_result$checked" printer.print("long \(resultVar) = \(value);") - printer.print("if (SwiftValueLayout.has32bitSwiftInt) {") - printer.indent() - printer.print("if (\(resultVar) < 0 || \(resultVar) > 0xFFFFFFFFL) {") - printer.indent() - printer.print("throw new SwiftIntegerOverflowException(\"Return value overflow: \" + \(resultVar));") - printer.outdent() - printer.print("}") - printer.outdent() - printer.print("}") + + printer.printBraceBlock("if (SwiftValueLayout.has32bitSwiftInt)") { printer in + printer.printBraceBlock("if (\(resultVar) < 0 || \(resultVar) > 0xFFFFFFFFL)") { printer in + printer.print("throw new SwiftIntegerOverflowException(\"Return value overflow: \" + \(resultVar));") + } + } printer.print("return \(resultVar);") } } diff --git a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftIntegerOverflowException.java b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftIntegerOverflowException.java index 1acf4cc11..bfe974f8b 100644 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftIntegerOverflowException.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftIntegerOverflowException.java @@ -25,11 +25,13 @@ * @see Swift Int documentation */ public class SwiftIntegerOverflowException extends RuntimeException { + static final String BASE_MESSAGE = "Swift runtime has detected IntegerOverflow! Most probably you are running 32-bit application while using Swift's Int type."; + public SwiftIntegerOverflowException() { - super("Swift runtime has detected IntegerOverflow! Most probably you are running 32-bit application while using Swift's Int type."); + super(BASE_MESSAGE); } public SwiftIntegerOverflowException(String message) { - super(message); + super(BASE_MESSAGE + " " + message); } } \ No newline at end of file From dbf305d1e231e96ef349afa3dff75a5bfa6cf10d Mon Sep 17 00:00:00 2001 From: Konrad Malawski Date: Wed, 18 Mar 2026 18:26:55 +0900 Subject: [PATCH 5/5] format --- .../FFM/FFMSwift2JavaGenerator+JavaTranslation.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift index 3e77da264..a88f50b66 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift @@ -57,8 +57,8 @@ extension FFMSwift2JavaGenerator { enum OverflowCheckType { case none - case signedInt // Int: -2147483648 to 2147483647 - case unsignedInt // UInt: 0 to 4294967295 + case signedInt // Int: -2147483648 to 2147483647 + case unsignedInt // UInt: 0 to 4294967295 } /// Represent a Swift API result translated to Java.