diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift index 15089b9c..2f2ceecc 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,17 @@ extension FFMSwift2JavaGenerator { paramDecls.append("AllocatingSwiftArena swiftArena") } + 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( """ - \(annotationsStr)\(modifiers) \(returnTy) \(methodName)(\(paramDecls.joined(separator: ", "))) + \(annotationsStr)\(modifiers) \(returnTy) \(methodName)(\(paramDecls.joined(separator: ", ")))\(throwsClause) """ ) { printer in if case .instance(_) = decl.functionSignature.selfParameter { @@ -406,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 @@ -473,6 +476,30 @@ extension FFMSwift2JavaGenerator { ) } + let hasOverflowChecks = translatedSignature.parameters.contains { $0.needs32BitIntOverflowCheck != .none } + if hasOverflowChecks { + 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));") + } + } + } + } + } + //=== Part 3: Downcall. let downCall = "\(thunkName).call(\(downCallArguments.joined(separator: ", ")))" @@ -499,16 +526,17 @@ extension FFMSwift2JavaGenerator { let result = translatedSignature.result.conversion.render( &printer, placeholder, - placeholderForDowncall: placeholderForDowncall + placeholderForDowncall: placeholderForDowncall, ) if translatedSignature.result.javaResultType != .void { 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) + printReturnWithOverflowCheck(&printer, value: extracted, overflowCheck: translatedSignature.result.needs32BitIntOverflowCheck) default: - printer.print("return \(result);") + printReturnWithOverflowCheck(&printer, value: result, overflowCheck: translatedSignature.result.needs32BitIntOverflowCheck) } } else { printer.print("\(result);") @@ -521,6 +549,38 @@ 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.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.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);") + } + } + func renderMemoryLayoutValue(for javaType: JavaType) -> String { if let layout = ForeignValueLayout(javaType: javaType) { return layout.description diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift index 4c474cca..a88f50b6 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift @@ -50,6 +50,15 @@ 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: 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. @@ -88,6 +97,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: OverflowCheckType = .none } /// Translated Java API representing a Swift API. @@ -342,6 +354,14 @@ extension FFMSwift2JavaGenerator { // be expressed as a Java primitive type. if let cType = try? CType(cdeclType: swiftType) { let javaType = cType.javaType + let overflowCheck: OverflowCheckType + if case .integral(.ptrdiff_t) = cType { + overflowCheck = .signedInt + } else if case .integral(.size_t) = cType { + overflowCheck = .unsignedInt + } else { + overflowCheck = .none + } return TranslatedParameter( javaParameters: [ JavaParameter( @@ -350,7 +370,8 @@ extension FFMSwift2JavaGenerator { annotations: parameterAnnotations ) ], - conversion: .placeholder + conversion: .placeholder, + needs32BitIntOverflowCheck: overflowCheck ) } @@ -620,11 +641,20 @@ extension FFMSwift2JavaGenerator { // be expressed as a Java primitive type. if let cType = try? CType(cdeclType: swiftType) { let javaType = cType.javaType + let overflowCheck: OverflowCheckType + if case .integral(.ptrdiff_t) = cType { + overflowCheck = .signedInt + } else if case .integral(.size_t) = cType { + overflowCheck = .unsignedInt + } else { + overflowCheck = .none + } return TranslatedResult( javaResultType: javaType, annotations: resultAnnotations, outParameters: [], - conversion: .placeholder + conversion: .placeholder, + needs32BitIntOverflowCheck: overflowCheck ) } 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 d77e42dd..bfe974f8 100644 --- a/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftIntegerOverflowException.java +++ b/SwiftKitCore/src/main/java/org/swift/swiftkit/core/SwiftIntegerOverflowException.java @@ -25,7 +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(BASE_MESSAGE + " " + message); } } \ No newline at end of file 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 be883e05..0c09cca4 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 a597038a..08652aca 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 be9984e0..c05e702a 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 65bb2a5c..58ceca9f 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 5a56524e..1979dc18 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 < 0 || first > 0xFFFFFFFFL) { + throw new SwiftIntegerOverflowException("Parameter 'first' overflow: " + first); + } + 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 < 0 || _result$checked > 0xFFFFFFFFL) { + throw new SwiftIntegerOverflowException("Return value overflow: " + _result$checked); + } + } + return _result$checked; } """, ] diff --git a/Tests/JExtractSwiftTests/VariableImportTests.swift b/Tests/JExtractSwiftTests/VariableImportTests.swift index e5837d2e..3b81ea9d 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()) } """,