From 9008e0d74104a625e4b0751ffe256080e85f9ad9 Mon Sep 17 00:00:00 2001 From: Divya Prakash Date: Sun, 1 Feb 2026 09:41:56 +0530 Subject: [PATCH 1/2] jextract: implement Java-side overflow checks using SwiftValueLayout.has32bitSwiftInt helper --- ...t2JavaGenerator+JavaBindingsPrinting.swift | 168 +++++--- ...MSwift2JavaGenerator+JavaTranslation.swift | 360 ++++++++++++------ .../FFM/FFMSwift2JavaGenerator.swift | 57 ++- .../swift/swiftkit/ffm/SwiftValueLayout.java | 26 +- .../JExtractSwiftTests/DataImportTests.swift | 83 ++-- .../MethodImportTests.swift | 40 +- .../UnsignedNumberTests.swift | 49 ++- .../VariableImportTests.swift | 3 + 8 files changed, 516 insertions(+), 270 deletions(-) diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift index 2ac5506c9..87295a696 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift @@ -19,7 +19,7 @@ extension FFMSwift2JavaGenerator { _ printer: inout CodePrinter, _ decl: ImportedFunc ) { - guard let _ = translatedDecl(for: decl) else { + guard translatedDecl(for: decl) != nil else { // Failed to translate. Skip. return } @@ -65,7 +65,7 @@ extension FFMSwift2JavaGenerator { printJavaBindingDowncallMethod(&printer, cFunc) if let outCallback = translated.translatedSignature.result.outCallback { printUpcallParameterDescriptorClasses(&printer, outCallback) - } else { // FIXME: not an "else" + } else { // FIXME: not an "else" printParameterDescriptorClasses(&printer, cFunc) } } @@ -109,13 +109,13 @@ extension FFMSwift2JavaGenerator { var params: [String] = [] var args: [String] = [] for param in cFunc.parameters { - let name = param.name! // !-safe, because cdecl lowering guarantees the parameter named. + let name = param.name! // !-safe, because cdecl lowering guarantees the parameter named. let annotationsStr = if param.type.javaType.parameterAnnotations.isEmpty { "" } else { - param.type.javaType.parameterAnnotations.map({$0.render()}).joined(separator: " ") + " " + param.type.javaType.parameterAnnotations.map({ $0.render() }).joined(separator: " ") + " " } params.append("\(annotationsStr)\(param.type.javaType) \(name)") args.append(name) @@ -156,14 +156,15 @@ extension FFMSwift2JavaGenerator { continue } } - } - + } + func printUpcallParameterDescriptorClasses( _ printer: inout CodePrinter, _ outCallback: OutCallback ) { let name = outCallback.name - printFunctionPointerParameterDescriptorClass(&printer, name, outCallback.cFunc.functionType, impl: outCallback) + printFunctionPointerParameterDescriptorClass( + &printer, name, outCallback.cFunc.functionType, impl: outCallback) } /// Print a class describing a function pointer parameter type. @@ -181,7 +182,7 @@ extension FFMSwift2JavaGenerator { /// } /// } /// ``` - /// + /// /// If a `functionBody` is provided, a `Function$Impl` class will be emitted as well. func printFunctionPointerParameterDescriptorClass( _ printer: inout CodePrinter, @@ -192,10 +193,10 @@ extension FFMSwift2JavaGenerator { let cResultType: CType let cParameterTypes: [CType] if case .pointer(.function(let _cResultType, let _cParameterTypes, variadic: false)) = cType { - cResultType = _cResultType + cResultType = _cResultType cParameterTypes = _cParameterTypes } else if case .function(let _cResultType, let _cParameterTypes, variadic: false) = cType { - cResultType = _cResultType + cResultType = _cResultType cParameterTypes = _cParameterTypes } else { fatalError("must be a C function (pointer) type; name=\(name), cType=\(cType)") @@ -204,7 +205,7 @@ extension FFMSwift2JavaGenerator { let cParams = cParameterTypes.enumerated().map { i, ty in CParameter(name: "_\(i)", type: ty) } - let paramDecls = cParams.map({"\($0.type.javaType) \($0.name!)"}) + let paramDecls = cParams.map({ "\($0.type.javaType) \($0.name!)" }) printer.printBraceBlock( """ @@ -224,7 +225,7 @@ extension FFMSwift2JavaGenerator { } """ ) - + if let impl { printer.print( """ @@ -236,7 +237,7 @@ extension FFMSwift2JavaGenerator { } """ ) - } + } printFunctionDescriptorDefinition(&printer, cResultType, cParams) printer.print( @@ -308,7 +309,7 @@ extension FFMSwift2JavaGenerator { """ ) - let cdeclParams = functionType.cdeclType.parameters.map( { "\($0.parameterName!)" }) + let cdeclParams = functionType.cdeclType.parameters.map({ "\($0.parameterName!)" }) printer.printBraceBlock( """ @@ -360,7 +361,8 @@ extension FFMSwift2JavaGenerator { let translatedSignature = translated.translatedSignature let returnTy = translatedSignature.result.javaResultType - var annotationsStr = translatedSignature.annotations.map({ $0.render() }).joined(separator: "\n") + var annotationsStr = translatedSignature.annotations.map({ $0.render() }).joined( + separator: "\n") if !annotationsStr.isEmpty { annotationsStr += "\n" } var paramDecls = translatedSignature.parameters @@ -380,7 +382,7 @@ extension FFMSwift2JavaGenerator { \(annotationsStr)\(modifiers) \(returnTy) \(methodName)(\(paramDecls.joined(separator: ", "))) """ ) { printer in - if case .instance(_) = decl.functionSignature.selfParameter { + if case .instance(_) = decl.functionSignature.selfParameter { // Make sure the object has not been destroyed. printer.print("$ensureAlive();") } @@ -401,7 +403,17 @@ extension FFMSwift2JavaGenerator { if translatedSignature.requiresTemporaryArena { printer.print("try(var arena$ = Arena.ofConfined()) {") - printer.indent(); + printer.indent() + } + + //=== Part 1.5: integer range checks (before any narrowing casts). + for (i, parameter) in translatedSignature.parameters.enumerated() { + guard let rangeCheck = parameter.integerRangeCheck else { + continue + } + let original = decl.functionSignature.parameters[i] + let parameterName = original.parameterName ?? "_\(i)" + printIntegerRangeCheck(&printer, parameterName: parameterName, rangeCheck: rangeCheck) } //=== Part 2: prepare all arguments. @@ -428,14 +440,16 @@ extension FFMSwift2JavaGenerator { } let memoryLayout = renderMemoryLayoutValue(for: type) - let arena = if let className = type.className, - analysis.importedTypes[className] != nil { - // Use passed-in 'SwiftArena' for 'SwiftValue'. - "swiftArena$" - } else { - // Otherwise use the temporary 'Arena'. - "arena$" - } + let arena = + if let className = type.className, + analysis.importedTypes[className] != nil + { + // Use passed-in 'SwiftArena' for 'SwiftValue'. + "swiftArena$" + } else { + // Otherwise use the temporary 'Arena'. + "arena$" + } // FIXME: use trailing$ convention let varName = outParameter.name.isEmpty ? "_result" : "_result_" + outParameter.name @@ -447,7 +461,7 @@ extension FFMSwift2JavaGenerator { } let thunkName = thunkNameRegistry.functionThunkName(decl: decl) - + if let outCallback = translatedSignature.result.outCallback { let funcName = outCallback.name assert(funcName.first == "$", "OutCallback names must start with $") @@ -469,9 +483,9 @@ extension FFMSwift2JavaGenerator { } else { let placeholder: String let placeholderForDowncall: String? - + if let outCallback = translatedSignature.result.outCallback { - placeholder = "\(outCallback.name)" // the result will be read out from the _result_initialize java class + placeholder = "\(outCallback.name)" // the result will be read out from the _result_initialize java class placeholderForDowncall = "\(downCall)" } else if translatedSignature.result.outParameters.isEmpty { placeholder = downCall @@ -482,13 +496,14 @@ extension FFMSwift2JavaGenerator { placeholderForDowncall = nil placeholder = "_result" } - let result = translatedSignature.result.conversion.render(&printer, placeholder, placeholderForDowncall: placeholderForDowncall) + let result = translatedSignature.result.conversion.render( + &printer, placeholder, 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 + 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 default: printer.print("return \(result);") } @@ -503,6 +518,26 @@ extension FFMSwift2JavaGenerator { } } + private func printIntegerRangeCheck( + _ printer: inout CodePrinter, + parameterName: String, + rangeCheck: IntegerRangeCheck + ) { + if rangeCheck.onlyWhenSwiftIntIs32Bit { + printer.print("if (SwiftValueLayout.has32bitSwiftInt) {") + printer.indent() + } + + printer.print( + "checkIntegerRange(\"\(rangeCheck.targetSwiftType)\", \(parameterName), \(rangeCheck.minValue), \(rangeCheck.maxValue));" + ) + + if rangeCheck.onlyWhenSwiftIntIs32Bit { + printer.outdent() + printer.print("}") + } + } + func renderMemoryLayoutValue(for javaType: JavaType) -> String { if let layout = ForeignValueLayout(javaType: javaType) { return layout.description @@ -535,11 +570,11 @@ extension FFMSwift2JavaGenerator.JavaConversionStep { case .call(let inner, let base, _, _): return inner.requiresSwiftArena || (base?.requiresSwiftArena == true) - case .cast(let inner, _), - .construct(let inner, _), - .method(let inner, _, _, _), - .property(let inner, _), - .swiftValueSelfSegment(let inner): + case .cast(let inner, _), + .construct(let inner, _), + .method(let inner, _, _, _), + .property(let inner, _), + .swiftValueSelfSegment(let inner): return inner.requiresSwiftArena case .commaSeparated(let list, _): @@ -562,16 +597,17 @@ extension FFMSwift2JavaGenerator.JavaConversionStep { return true case .introduceVariable(_, let value): return value.requiresTemporaryArena - case .cast(let inner, _), - .construct(let inner, _), - .constructSwiftValue(let inner, _), - .swiftValueSelfSegment(let inner), - .wrapMemoryAddressUnsafe(let inner, _): + case .cast(let inner, _), + .construct(let inner, _), + .constructSwiftValue(let inner, _), + .swiftValueSelfSegment(let inner), + .wrapMemoryAddressUnsafe(let inner, _): return inner.requiresSwiftArena case .call(let inner, let base, _, let withArena): return withArena || (base?.requiresTemporaryArena == true) || inner.requiresTemporaryArena case .method(let inner, _, let args, let withArena): - return withArena || inner.requiresTemporaryArena || args.contains(where: { $0.requiresTemporaryArena }) + return withArena || inner.requiresTemporaryArena + || args.contains(where: { $0.requiresTemporaryArena }) case .property(let inner, _): return inner.requiresTemporaryArena case .commaSeparated(let list, _): @@ -580,7 +616,9 @@ extension FFMSwift2JavaGenerator.JavaConversionStep { } /// Returns the conversion string applied to the placeholder. - func render(_ printer: inout CodePrinter, _ placeholder: String, placeholderForDowncall: String? = nil) -> String { + func render( + _ printer: inout CodePrinter, _ placeholder: String, placeholderForDowncall: String? = nil + ) -> String { // NOTE: 'printer' is used if the conversion wants to cause side-effects. // E.g. storing a temporary values into a variable. switch self { @@ -596,7 +634,7 @@ extension FFMSwift2JavaGenerator.JavaConversionStep { case .placeholderForSwiftThunkName: if let placeholderForDowncall { let downcall = "\(placeholderForDowncall)" - return String(downcall[..<(downcall.firstIndex(of: ".") ?? downcall.endIndex)]) // . separates thunk name from the `.call` + return String(downcall[..<(downcall.firstIndex(of: ".") ?? downcall.endIndex)]) // . separates thunk name from the `.call` } else { return "/*placeholderForDowncall undefined!*/" } @@ -609,24 +647,25 @@ extension FFMSwift2JavaGenerator.JavaConversionStep { case .swiftValueSelfSegment: return "\(placeholder).$memorySegment()" - + case .javaNew(let value): - return "new \(value.render(&printer, placeholder, placeholderForDowncall: placeholderForDowncall))" + return + "new \(value.render(&printer, placeholder, placeholderForDowncall: placeholderForDowncall))" case .initializeResultWithUpcall(let steps, _): // TODO: could we use the printing to introduce the upcall handle instead? - return steps.map { step in + return steps.map { step in var printer = CodePrinter() var out = "" out += step.render(&printer, placeholder, placeholderForDowncall: placeholderForDowncall) out += printer.contents - return out + return out }.joined(separator: ";\n") case .call(let inner, let base, let function, let withArena): let inner = inner.render(&printer, placeholder) let arenaArg = withArena ? ", arena$" : "" - let baseStr : String = + let baseStr: String = if let base { base.render(&printer, placeholder) + "." } else { @@ -636,38 +675,47 @@ extension FFMSwift2JavaGenerator.JavaConversionStep { // TODO: deduplicate with 'method' case .method(let inner, let methodName, let arguments, let withArena): - let inner = inner.render(&printer, placeholder, placeholderForDowncall: placeholderForDowncall) - let args = arguments.map { $0.render(&printer, placeholder, placeholderForDowncall: placeholderForDowncall) } + let inner = inner.render( + &printer, placeholder, placeholderForDowncall: placeholderForDowncall) + let args = arguments.map { + $0.render(&printer, placeholder, placeholderForDowncall: placeholderForDowncall) + } let argsStr = (args + (withArena ? ["arena$"] : [])).joined(separator: " ,") return "\(inner).\(methodName)(\(argsStr))" case .property(let inner, let propertyName): - let inner = inner.render(&printer, placeholder, placeholderForDowncall: placeholderForDowncall) + let inner = inner.render( + &printer, placeholder, placeholderForDowncall: placeholderForDowncall) return "\(inner).\(propertyName)" case .constructSwiftValue(let inner, let javaType): - let inner = inner.render(&printer, placeholder, placeholderForDowncall: placeholderForDowncall) + let inner = inner.render( + &printer, placeholder, placeholderForDowncall: placeholderForDowncall) return "new \(javaType.className!)(\(inner), swiftArena$)" case .wrapMemoryAddressUnsafe(let inner, let javaType): - let inner = inner.render(&printer, placeholder, placeholderForDowncall: placeholderForDowncall) + let inner = inner.render( + &printer, placeholder, placeholderForDowncall: placeholderForDowncall) return "\(javaType.className!).wrapMemoryAddressUnsafe(\(inner), swiftArena$)" case .construct(let inner, let javaType): - let inner = inner.render(&printer, placeholder, placeholderForDowncall: placeholderForDowncall) + let inner = inner.render( + &printer, placeholder, placeholderForDowncall: placeholderForDowncall) return "new \(javaType)(\(inner))" - + case .introduceVariable(let name, let value): - let value = value.render(&printer, placeholder, placeholderForDowncall: placeholderForDowncall) + let value = value.render( + &printer, placeholder, placeholderForDowncall: placeholderForDowncall) return "var \(name) = \(value);" case .cast(let inner, let javaType): - let inner = inner.render(&printer, placeholder, placeholderForDowncall: placeholderForDowncall) + let inner = inner.render( + &printer, placeholder, placeholderForDowncall: placeholderForDowncall) return "(\(javaType)) \(inner)" case .commaSeparated(let list, let separator): - return list.map({ - $0.render(&printer, placeholder, placeholderForDowncall: placeholderForDowncall) + return list.map({ + $0.render(&printer, placeholder, placeholderForDowncall: placeholderForDowncall) }).joined(separator: separator) case .constant(let value): diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift index da1914d45..4da8d4cac 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift @@ -36,34 +36,34 @@ extension FFMSwift2JavaGenerator { translatedDecls[decl] = translated return translated - } - - /// Represent a Swift API parameter translated to Java. - struct TranslatedParameter { - /// Java parameter(s) mapped to the Swift parameter. + (.int, + .cast(.placeholder, cJavaType), + IntegerRangeCheck(targetSwiftType: "Swift.Int8", minValue: "-128", maxValue: "127", onlyWhenSwiftIntIs32Bit: false)) /// - /// Array because one Swift parameter can be mapped to multiple parameters. - var javaParameters: [JavaParameter] - - /// Describes how to convert the Java parameter to the lowered arguments for - /// the foreign function. + (.int, + .cast(.placeholder, cJavaType), + IntegerRangeCheck(targetSwiftType: "Swift.UInt8", minValue: "0", maxValue: "0xFFL", onlyWhenSwiftIntIs32Bit: false)) var conversion: JavaConversionStep - } - - /// Represent a Swift API result translated to Java. - struct TranslatedResult { - /// Java type that represents the Swift result type. - var javaResultType: JavaType - + (.int, + .cast(.placeholder, cJavaType), + IntegerRangeCheck(targetSwiftType: "Swift.Int16", minValue: "-32768", maxValue: "32767", onlyWhenSwiftIntIs32Bit: false)) + struct IntegerRangeCheck { + (.int, + .cast(.placeholder, cJavaType), + IntegerRangeCheck(targetSwiftType: "Swift.UInt16", minValue: "0", maxValue: "0xFFFFL", onlyWhenSwiftIntIs32Bit: false)) + + (.long, + .cast(.placeholder, cJavaType), + IntegerRangeCheck(targetSwiftType: "Swift.UInt32", minValue: "0L", maxValue: "0xFFFF_FFFFL", onlyWhenSwiftIntIs32Bit: false)) /// Java annotations that should be propagated from the result type onto the method - var annotations: [JavaAnnotation] = [] - + (cJavaType, .placeholder, + IntegerRangeCheck(targetSwiftType: "Swift.UInt64", minValue: "0L", maxValue: "Long.MAX_VALUE", onlyWhenSwiftIntIs32Bit: false)) /// Required indirect return receivers for receiving the result. + (cJavaType, .placeholder, + IntegerRangeCheck(targetSwiftType: "Swift.Int", minValue: "Integer.MIN_VALUE", maxValue: "Integer.MAX_VALUE", onlyWhenSwiftIntIs32Bit: true)) /// - /// 'JavaParameter.name' is the suffix for the receiver variable names. For example - /// - /// var _result_pointer = MemorySegment.allocate(...) - /// var _result_count = MemorySegment.allocate(...) + (cJavaType, .placeholder, + IntegerRangeCheck(targetSwiftType: "Swift.UInt", minValue: "0L", maxValue: "0xFFFF_FFFFL", onlyWhenSwiftIntIs32Bit: true)) /// downCall(_result_pointer, _result_count) /// return constructResult(_result_pointer, _result_count) /// @@ -73,14 +73,14 @@ extension FFMSwift2JavaGenerator { /// Similar to out parameters, but instead of parameters we "fill in" in native, /// we create an upcall handle before the downcall and pass it to the downcall. /// Swift then invokes the upcall in order to populate some data in Java (our callback). - /// + /// /// After the call is made, we may need to further extact the result from the called-back-into /// Java function class, for example: - /// + /// /// var _result_initialize = new $result_initialize.Function(); /// downCall($result_initialize.toUpcallHandle(_result_initialize, arena)) /// return _result_initialize.result - /// + /// var outCallback: OutCallback? /// Describes how to construct the Java result from the foreign function return @@ -88,7 +88,6 @@ extension FFMSwift2JavaGenerator { var conversion: JavaConversionStep } - /// Translated Java API representing a Swift API. /// /// Since this holds the lowered signature, and the original `SwiftFunctionSignature` @@ -156,14 +155,16 @@ extension FFMSwift2JavaGenerator { let loweredSignature = try lowering.lowerFunctionSignature(decl.functionSignature) // Name. - let javaName = switch decl.apiKind { - case .getter, .subscriptGetter: decl.javaGetterName - case .setter, .subscriptSetter: decl.javaSetterName - case .function, .initializer, .enumCase: decl.name - } + let javaName = + switch decl.apiKind { + case .getter, .subscriptGetter: decl.javaGetterName + case .setter, .subscriptSetter: decl.javaSetterName + case .function, .initializer, .enumCase: decl.name + } // Signature. - let translatedSignature = try translate(loweredFunctionSignature: loweredSignature, methodName: javaName) + let translatedSignature = try translate( + loweredFunctionSignature: loweredSignature, methodName: javaName) // Closures. var funcTypes: [TranslatedFunctionType] = [] @@ -171,10 +172,13 @@ extension FFMSwift2JavaGenerator { switch param.type { case .function(let funcTy): let paramName = param.parameterName ?? "_\(idx)" - guard case .function( let cdeclTy) = loweredSignature.parameters[idx].cdeclParameters[0].type else { + guard + case .function(let cdeclTy) = loweredSignature.parameters[idx].cdeclParameters[0].type + else { preconditionFailure("closure parameter wasn't lowered to a function type; \(funcTy)") } - let translatedClosure = try translateFunctionType(name: paramName, swiftType: funcTy, cdeclType: cdeclTy) + let translatedClosure = try translateFunctionType( + name: paramName, swiftType: funcTy, cdeclType: cdeclTy) funcTypes.append(translatedClosure) case .tuple: // TODO: Implement @@ -203,7 +207,8 @@ extension FFMSwift2JavaGenerator { for (i, param) in swiftType.parameters.enumerated() { let paramName = param.parameterName ?? "_\(i)" translatedParams.append( - try translateClosureParameter(param.type, convention: param.convention, parameterName: paramName) + try translateClosureParameter( + param.type, convention: param.convention, parameterName: paramName) ) } @@ -334,7 +339,20 @@ extension FFMSwift2JavaGenerator { genericRequirements: [SwiftGenericRequirement] ) throws -> TranslatedParameter { // If the result type should cause any annotations on the method, include them here. - let parameterAnnotations: [JavaAnnotation] = getTypeAnnotations(swiftType: swiftType, config: config) + let parameterAnnotations: [JavaAnnotation] = getTypeAnnotations( + swiftType: swiftType, config: config) + + if case .nominal(let swiftNominalType) = swiftType, + let knownType = swiftNominalType.nominalTypeDecl.knownTypeKind, + let integerTranslation = try translateIntegerParameter( + swiftType: swiftType, + knownType: knownType, + parameterName: parameterName, + parameterAnnotations: parameterAnnotations + ) + { + return integerTranslation + } // If there is a 1:1 mapping between this Swift type and a C type, that can // be expressed as a Java primitive type. @@ -382,11 +400,11 @@ extension FFMSwift2JavaGenerator { case .unsafeRawBufferPointer, .unsafeMutableRawBufferPointer: return TranslatedParameter( javaParameters: [ - JavaParameter(name: parameterName, type: .javaForeignMemorySegment), + JavaParameter(name: parameterName, type: .javaForeignMemorySegment) ], conversion: .commaSeparated([ .placeholder, - .method(.placeholder, methodName: "byteSize", arguments: [], withArena: false) + .method(.placeholder, methodName: "byteSize", arguments: [], withArena: false), ]) ) @@ -444,13 +462,17 @@ extension FFMSwift2JavaGenerator { return TranslatedParameter( javaParameters: [ JavaParameter( - name: parameterName, type: JavaType.class(package: nil, name: "\(methodName).\(parameterName)")) + name: parameterName, + type: JavaType.class(package: nil, name: "\(methodName).\(parameterName)")) ], conversion: .call(.placeholder, function: "\(methodName).$toUpcallStub", withArena: true) ) case .existential, .opaque, .genericParameter: - if let concreteTy = swiftType.representativeConcreteTypeIn(knownTypes: knownTypes, genericParameters: genericParameters, genericRequirements: genericRequirements) { + if let concreteTy = swiftType.representativeConcreteTypeIn( + knownTypes: knownTypes, genericParameters: genericParameters, + genericRequirements: genericRequirements) + { return try translateParameter( type: concreteTy, convention: convention, @@ -482,18 +504,19 @@ extension FFMSwift2JavaGenerator { case .array(let wrapped) where wrapped == knownTypes.uint8: return TranslatedParameter( javaParameters: [ - JavaParameter(name: parameterName, type: .array(.byte), annotations: parameterAnnotations), + JavaParameter( + name: parameterName, type: .array(.byte), annotations: parameterAnnotations) ], - conversion: + conversion: .commaSeparated([ .call( - .commaSeparated([.constant("ValueLayout.JAVA_BYTE"), .placeholder]), + .commaSeparated([.constant("ValueLayout.JAVA_BYTE"), .placeholder]), base: .temporaryArena, - function: "allocateFrom", - withArena: false // this would pass the arena as last argument, but instead we make a call on the arena - ), - .property(.placeholder, propertyName: "length"), - ]) + function: "allocateFrom", + withArena: false // this would pass the arena as last argument, but instead we make a call on the arena + ), + .property(.placeholder, propertyName: "length"), + ]) ) case .array: @@ -501,6 +524,91 @@ extension FFMSwift2JavaGenerator { } } + func translateIntegerParameter( + swiftType: SwiftType, + knownType: SwiftKnownTypeDeclKind, + parameterName: String, + parameterAnnotations: [JavaAnnotation] + ) throws -> TranslatedParameter? { + let cType = try CType(cdeclType: swiftType) + let cJavaType = cType.javaType + + let (javaType, conversion, rangeCheck): (JavaType, JavaConversionStep, IntegerRangeCheck?) = + switch knownType { + case .int8: + ( + .int, + .cast(.placeholder, cJavaType), + IntegerRangeCheck( + targetSwiftType: "Swift.Int8", minValue: "-128", maxValue: "127", + onlyWhenSwiftIntIs32Bit: false) + ) + case .uint8: + ( + .int, + .cast(.placeholder, cJavaType), + IntegerRangeCheck( + targetSwiftType: "Swift.UInt8", minValue: "0", maxValue: "0xFFL", + onlyWhenSwiftIntIs32Bit: false) + ) + case .int16: + ( + .int, + .cast(.placeholder, cJavaType), + IntegerRangeCheck( + targetSwiftType: "Swift.Int16", minValue: "-32768", maxValue: "32767", + onlyWhenSwiftIntIs32Bit: false) + ) + case .uint16: + ( + .int, + .cast(.placeholder, cJavaType), + IntegerRangeCheck( + targetSwiftType: "Swift.UInt16", minValue: "0", maxValue: "0xFFFFL", + onlyWhenSwiftIntIs32Bit: false) + ) + case .uint32: + ( + .long, + .cast(.placeholder, cJavaType), + IntegerRangeCheck( + targetSwiftType: "Swift.UInt32", minValue: "0L", maxValue: "0xFFFF_FFFFL", + onlyWhenSwiftIntIs32Bit: false) + ) + case .uint64: + ( + cJavaType, .placeholder, + IntegerRangeCheck( + targetSwiftType: "Swift.UInt64", minValue: "0L", maxValue: "Long.MAX_VALUE", + onlyWhenSwiftIntIs32Bit: false) + ) + case .int: + ( + cJavaType, .placeholder, + IntegerRangeCheck( + targetSwiftType: "Swift.Int", minValue: "Integer.MIN_VALUE", + maxValue: "Integer.MAX_VALUE", onlyWhenSwiftIntIs32Bit: true) + ) + case .uint: + ( + cJavaType, .placeholder, + IntegerRangeCheck( + targetSwiftType: "Swift.UInt", minValue: "0L", maxValue: "0xFFFF_FFFFL", + onlyWhenSwiftIntIs32Bit: true) + ) + default: + return nil + } + + return TranslatedParameter( + javaParameters: [ + JavaParameter(name: parameterName, type: javaType, annotations: parameterAnnotations) + ], + conversion: conversion, + integerRangeCheck: rangeCheck + ) + } + /// Translate an Optional Swift API parameter to the user-facing Java API parameter. func translateOptionalParameter( wrappedType swiftType: SwiftType, @@ -514,18 +622,19 @@ extension FFMSwift2JavaGenerator { // If there is a 1:1 mapping between this Swift type and a C type, that can // be expressed as a Java primitive type. if let cType = try? CType(cdeclType: swiftType) { - let (translatedClass, lowerFunc) = switch cType.javaType { - case .int: ("OptionalInt", "toOptionalSegmentInt") - case .long: ("OptionalLong", "toOptionalSegmentLong") - case .double: ("OptionalDouble", "toOptionalSegmentDouble") - case .boolean: ("Optional", "toOptionalSegmentBoolean") - case .byte: ("Optional", "toOptionalSegmentByte") - case .char: ("Optional", "toOptionalSegmentCharacter") - case .short: ("Optional", "toOptionalSegmentShort") - case .float: ("Optional", "toOptionalSegmentFloat") - default: - throw JavaTranslationError.unhandledType(.optional(swiftType)) - } + let (translatedClass, lowerFunc) = + switch cType.javaType { + case .int: ("OptionalInt", "toOptionalSegmentInt") + case .long: ("OptionalLong", "toOptionalSegmentLong") + case .double: ("OptionalDouble", "toOptionalSegmentDouble") + case .boolean: ("Optional", "toOptionalSegmentBoolean") + case .byte: ("Optional", "toOptionalSegmentByte") + case .char: ("Optional", "toOptionalSegmentCharacter") + case .short: ("Optional", "toOptionalSegmentShort") + case .float: ("Optional", "toOptionalSegmentFloat") + default: + throw JavaTranslationError.unhandledType(.optional(swiftType)) + } return TranslatedParameter( javaParameters: [ JavaParameter(name: parameterName, type: JavaType(className: translatedClass)) @@ -548,12 +657,18 @@ extension FFMSwift2JavaGenerator { let translatedTy = try self.translate(swiftType: swiftType) return TranslatedParameter( javaParameters: [ - JavaParameter(name: parameterName, type: JavaType(className: "Optional<\(translatedTy.description)>")) + JavaParameter( + name: parameterName, + type: JavaType(className: "Optional<\(translatedTy.description)>")) ], - conversion: .call(.placeholder, function: "SwiftRuntime.toOptionalSegmentInstance", withArena: false) + conversion: .call( + .placeholder, function: "SwiftRuntime.toOptionalSegmentInstance", withArena: false) ) case .existential, .opaque, .genericParameter: - if let concreteTy = swiftType.representativeConcreteTypeIn(knownTypes: knownTypes, genericParameters: genericParameters, genericRequirements: genericRequirements) { + if let concreteTy = swiftType.representativeConcreteTypeIn( + knownTypes: knownTypes, genericParameters: genericParameters, + genericRequirements: genericRequirements) + { return try translateOptionalParameter( wrappedType: concreteTy, convention: convention, @@ -590,7 +705,8 @@ extension FFMSwift2JavaGenerator { ) throws -> TranslatedResult { let swiftType = swiftResult.type // If the result type should cause any annotations on the method, include them here. - let resultAnnotations: [JavaAnnotation] = getTypeAnnotations(swiftType: swiftType, config: config) + let resultAnnotations: [JavaAnnotation] = getTypeAnnotations( + swiftType: swiftType, config: config) // If there is a 1:1 mapping between this Swift type and a C type, that can // be expressed as a Java primitive type. @@ -627,10 +743,11 @@ extension FFMSwift2JavaGenerator { JavaParameter(name: "count", type: .long), ], conversion: .method( - .readMemorySegment(.explodedName(component: "pointer"), as: .javaForeignMemorySegment), + .readMemorySegment( + .explodedName(component: "pointer"), as: .javaForeignMemorySegment), methodName: "reinterpret", arguments: [ - .readMemorySegment(.explodedName(component: "count"), as: .long), + .readMemorySegment(.explodedName(component: "count"), as: .long) ], withArena: false ) @@ -674,40 +791,45 @@ extension FFMSwift2JavaGenerator { case .array(let wrapped) where wrapped == knownTypes.uint8: return TranslatedResult( - javaResultType: - .array(.byte), - annotations: [.unsigned], - outParameters: [], // no out parameters, but we do an "out" callback - outCallback: OutCallback( - name: "$_result_initialize", - members: [ - "byte[] result = null" - ], + javaResultType: + .array(.byte), + annotations: [.unsigned], + outParameters: [], // no out parameters, but we do an "out" callback + outCallback: OutCallback( + name: "$_result_initialize", + members: [ + "byte[] result = null" + ], + parameters: [ + JavaParameter(name: "pointer", type: .javaForeignMemorySegment), + JavaParameter(name: "count", type: .long), + ], + cFunc: CFunction( + resultType: .void, + name: "apply", parameters: [ - JavaParameter(name: "pointer", type: .javaForeignMemorySegment), - JavaParameter(name: "count", type: .long), + CParameter(type: .pointer(.void)), + CParameter(type: .integral(.size_t)), ], - cFunc: CFunction( - resultType: .void, - name: "apply", - parameters: [ - CParameter(type: .pointer(.void)), - CParameter(type: .integral(.size_t)), - ], - isVariadic: false), - body: "this.result = _0.reinterpret(_1).toArray(ValueLayout.JAVA_BYTE); // copy native Swift array to Java heap array" - ), - conversion: .initializeResultWithUpcall([ + isVariadic: false), + body: + "this.result = _0.reinterpret(_1).toArray(ValueLayout.JAVA_BYTE); // copy native Swift array to Java heap array" + ), + conversion: .initializeResultWithUpcall( + [ .introduceVariable( - name: "_result_initialize", - initializeWith: .javaNew(.commaSeparated([ - // We need to refer to the nested class that is created for this function. - // The class that contains all the related functional interfaces is called the same - // as the downcall function, so we use the thunk name to find this class/ - .placeholderForSwiftThunkName, .constant("$_result_initialize.Function$Impl()") - ], separator: "."))), + name: "_result_initialize", + initializeWith: .javaNew( + .commaSeparated( + [ + // We need to refer to the nested class that is created for this function. + // The class that contains all the related functional interfaces is called the same + // as the downcall function, so we use the thunk name to find this class/ + .placeholderForSwiftThunkName, + .constant("$_result_initialize.Function$Impl()"), + ], separator: "."))), // .constant("var = new \(.placeholderForDowncallThunkName).."), - .placeholderForDowncall, // perform the downcall here + .placeholderForDowncall, // perform the downcall here ], extractResult: .property(.constant("_result_initialize"), propertyName: "result")) ) @@ -732,19 +854,19 @@ extension FFMSwift2JavaGenerator { enum JavaConversionStep { /// The input case placeholder - + /// The "downcall", e.g. `swiftjava_SwiftModule_returnArray.call(...)`. /// This can be used in combination with aggregate conversion steps to prepare a setup and processing of the downcall. case placeholderForDowncall - + /// Placeholder for Swift thunk name, e.g. "swiftjava_SwiftModule_returnArray". - /// - /// This is derived from the placeholderForDowncall substitution - could be done more cleanly, + /// + /// This is derived from the placeholderForDowncall substitution - could be done more cleanly, /// however this has the benefit of not needing to pass the name substituion separately. case placeholderForSwiftThunkName /// The temporary `arena$` that is necessary to complete the conversion steps. - /// + /// /// This is distinct from just a constant 'arena$' string, since it forces the creation of a temporary arena. case temporaryArena @@ -755,22 +877,24 @@ extension FFMSwift2JavaGenerator { case constant(String) /// The result of the function will be initialized with a callback to Java (an upcall). - /// - /// The `extractResult` is used for the actual `return ...` statement, because we need to extract + /// + /// The `extractResult` is used for the actual `return ...` statement, because we need to extract /// the return value from the called back into class, e.g. `return _result_initialize.result`. - indirect case initializeResultWithUpcall([JavaConversionStep], extractResult: JavaConversionStep) + indirect case initializeResultWithUpcall( + [JavaConversionStep], extractResult: JavaConversionStep) /// 'value.$memorySegment()' indirect case swiftValueSelfSegment(JavaConversionStep) /// Call specified function using the placeholder as arguments. - /// + /// /// The 'base' is if the call should be performed as 'base.function', /// otherwise the function is assumed to be a free function. - /// + /// /// If `withArena` is true, `arena$` argument is added. - indirect case call(JavaConversionStep, base: JavaConversionStep?, function: String, withArena: Bool) - + indirect case call( + JavaConversionStep, base: JavaConversionStep?, function: String, withArena: Bool) + static func call(_ step: JavaConversionStep, function: String, withArena: Bool) -> Self { .call(step, base: nil, function: function, withArena: withArena) } @@ -778,8 +902,9 @@ extension FFMSwift2JavaGenerator { // TODO: just use make call more powerful and use it instead? /// Apply a method on the placeholder. /// If `withArena` is true, `arena$` argument is added. - indirect case method(JavaConversionStep, methodName: String, arguments: [JavaConversionStep] = [], withArena: Bool) - + indirect case method( + JavaConversionStep, methodName: String, arguments: [JavaConversionStep] = [], withArena: Bool) + /// Fetch a property from the placeholder. /// Similar to 'method', however for a property i.e. without adding the '()' after the name indirect case property(JavaConversionStep, propertyName: String) @@ -789,7 +914,7 @@ extension FFMSwift2JavaGenerator { /// Construct the type using the placeholder as arguments. indirect case construct(JavaConversionStep, JavaType) - + /// Call the `MyType.wrapMemoryAddressUnsafe` in order to wrap a memory address using the Java binding type indirect case wrapMemoryAddressUnsafe(JavaConversionStep, JavaType) @@ -798,9 +923,9 @@ extension FFMSwift2JavaGenerator { /// Casting the placeholder to the certain type. indirect case cast(JavaConversionStep, JavaType) - + /// Prefix the conversion step with a java `new`. - /// + /// /// This is useful if constructing the value is complex and we use /// a combination of separated values and constants to do so; Generally prefer using `construct` /// if you only want to construct a "wrapper" for the current `.placeholder`. @@ -818,7 +943,6 @@ extension FFMSwift2JavaGenerator { } } - extension FFMSwift2JavaGenerator.TranslatedFunctionSignature { /// Whether or not if the down-calling requires temporary "Arena" which is /// only used during the down-calling. @@ -854,7 +978,7 @@ extension CType { case .integral(.signed(bits: 32)): return .int case .integral(.signed(bits: 64)): return .long case .integral(.unsigned(bits: 8)): return .byte - case .integral(.unsigned(bits: 16)): return .char // char is Java's only unsigned primitive, we can use it! + case .integral(.unsigned(bits: 16)): return .char // char is Java's only unsigned primitive, we can use it! case .integral(.unsigned(bits: 32)): return .int case .integral(.unsigned(bits: 64)): return .long @@ -877,7 +1001,7 @@ extension CType { case .tag(_): fatalError("unsupported") - case .integral(.signed(bits: _)), .integral(.unsigned(bits: _)): + case .integral(.signed(bits: _)), .integral(.unsigned(bits: _)): fatalError("unreachable") } } @@ -910,7 +1034,7 @@ extension CType { case .tag(_): fatalError("unsupported") - case .void, .integral(.signed(bits: _)), .integral(.unsigned(bits: _)): + case .void, .integral(.signed(bits: _)), .integral(.unsigned(bits: _)): fatalError("unreachable") } } diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift index ae047284c..e98745732 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator.swift @@ -13,9 +13,10 @@ //===----------------------------------------------------------------------===// import JavaTypes +import SwiftJavaConfigurationShared import SwiftSyntax import SwiftSyntaxBuilder -import SwiftJavaConfigurationShared + import struct Foundation.URL package class FFMSwift2JavaGenerator: Swift2JavaGenerator { @@ -39,7 +40,7 @@ package class FFMSwift2JavaGenerator: Swift2JavaGenerator { /// Because we need to write empty files for SwiftPM, keep track which files we didn't write yet, /// and write an empty file for those. - /// + /// /// Since Swift files in SwiftPM builds needs to be unique, we use this fact to flatten paths into plain names here. /// For uniqueness checking "did we write this file already", just checking the name should be sufficient. var expectedOutputSwiftFileNames: Set @@ -63,16 +64,18 @@ package class FFMSwift2JavaGenerator: Swift2JavaGenerator { // If we are forced to write empty files, construct the expected outputs. // It is sufficient to use file names only, since SwiftPM requires names to be unique within a module anyway. if translator.config.writeEmptyFiles ?? false { - self.expectedOutputSwiftFileNames = Set(translator.inputs.compactMap { (input) -> String? in - guard let fileName = input.path.split(separator: PATH_SEPARATOR).last else { - return nil - } - guard fileName.hasSuffix(".swift") else { - return nil - } - return String(fileName.replacing(".swift", with: "+SwiftJava.swift")) - }) - self.expectedOutputSwiftFileNames.insert("\(translator.swiftModuleName)Module+SwiftJava.swift") + self.expectedOutputSwiftFileNames = Set( + translator.inputs.compactMap { (input) -> String? in + guard let fileName = input.path.split(separator: PATH_SEPARATOR).last else { + return nil + } + guard fileName.hasSuffix(".swift") else { + return nil + } + return String(fileName.replacing(".swift", with: "+SwiftJava.swift")) + }) + self.expectedOutputSwiftFileNames.insert( + "\(translator.swiftModuleName)Module+SwiftJava.swift") self.expectedOutputSwiftFileNames.insert("Foundation+SwiftJava.swift") } else { self.expectedOutputSwiftFileNames = [] @@ -81,7 +84,8 @@ package class FFMSwift2JavaGenerator: Swift2JavaGenerator { func generate() throws { try writeSwiftThunkSources() - log.info("Generated Swift sources (module: '\(self.swiftModuleName)') in: \(swiftOutputDirectory)/") + log.info( + "Generated Swift sources (module: '\(self.swiftModuleName)') in: \(swiftOutputDirectory)/") try writeExportedJavaSources() log.info("Generated Java sources (package: '\(javaPackage)') in: \(javaOutputDirectory)/") @@ -96,7 +100,7 @@ package class FFMSwift2JavaGenerator: Swift2JavaGenerator { extension FFMSwift2JavaGenerator { /// Default set Java imports for every generated file - static let defaultJavaImports: Array = [ + static let defaultJavaImports: [String] = [ "org.swift.swiftkit.core.*", "org.swift.swiftkit.core.util.*", "org.swift.swiftkit.ffm.*", @@ -115,7 +119,6 @@ extension FFMSwift2JavaGenerator { // ==== --------------------------------------------------------------------------------------------------------------- // MARK: File writing - extension FFMSwift2JavaGenerator { package func writeExportedJavaSources() throws { var printer = CodePrinter() @@ -134,7 +137,9 @@ extension FFMSwift2JavaGenerator { javaPackagePath: javaPackagePath, filename: filename ) { - log.info("Generated: \((ty.swiftNominal.name.bold + ".java").bold) (at \(outputFile.absoluteString))") + log.info( + "Generated: \((ty.swiftNominal.name.bold + ".java").bold) (at \(outputFile.absoluteString))" + ) } } @@ -148,7 +153,8 @@ extension FFMSwift2JavaGenerator { javaPackagePath: javaPackagePath, filename: filename) { - log.info("Generated: \((self.swiftModuleName + ".java").bold) (at \(outputFile.absoluteString))") + log.info( + "Generated: \((self.swiftModuleName + ".java").bold) (at \(outputFile.absoluteString))") } } } @@ -184,7 +190,7 @@ extension FFMSwift2JavaGenerator { func printImportedNominal(_ printer: inout CodePrinter, _ decl: ImportedNominalType) { printHeader(&printer) printPackage(&printer) - printImports(&printer) // TODO: we could have some imports be driven from types used in the generated decl + printImports(&printer) // TODO: we could have some imports be driven from types used in the generated decl printNominal(&printer, decl) { printer in // We use a static field to abuse the initialization order such that by the time we get type metadata, @@ -295,11 +301,23 @@ extension FFMSwift2JavaGenerator { if decl.swiftNominal.isSendable { printer.print("@ThreadSafe // Sendable") } - printer.printBraceBlock("public final class \(decl.swiftNominal.name) extends FFMSwiftInstance implements \(parentProtocol)") { + printer.printBraceBlock( + "public final class \(decl.swiftNominal.name) extends FFMSwiftInstance implements \(parentProtocol)" + ) { printer in // Constants printClassConstants(printer: &printer) + printer.print( + """ + private static void checkIntegerRange(String swiftType, long value, long min, long max) { + if (value < min || value > max) { + throw new ArithmeticException("Value out of range for " + swiftType + ": " + value); + } + } + """ + ) + body(&printer) } } @@ -418,4 +436,3 @@ extension FFMSwift2JavaGenerator { """) } } - 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..d113e24fa 100644 --- a/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftValueLayout.java +++ b/SwiftKitFFM/src/main/java/org/swift/swiftkit/ffm/SwiftValueLayout.java @@ -5,6 +5,7 @@ // Copyright (c) 2024 Apple Inc. and the Swift.org project authors // Licensed under Apache License v2.0 // + // See LICENSE.txt for license information // See CONTRIBUTORS.txt for the list of Swift.org project authors // @@ -22,7 +23,8 @@ import static java.lang.foreign.ValueLayout.*; /** - * Similar to {@link java.lang.foreign.ValueLayout} however with some Swift specifics. + * Similar to {@link java.lang.foreign.ValueLayout} however with some Swift + * specifics. */ public class SwiftValueLayout { @@ -50,23 +52,31 @@ public static long addressByteSize() { public static final ValueLayout.OfFloat SWIFT_FLOAT = ValueLayout.JAVA_FLOAT; public static final ValueLayout.OfDouble SWIFT_DOUBLE = ValueLayout.JAVA_DOUBLE; - // FIXME: this sequence layout is a workaround, we must properly size pointers when we get them. + // FIXME: this sequence layout is a workaround, we must properly size pointers + // when we get them. public static final AddressLayout SWIFT_POINTER = ValueLayout.ADDRESS - .withTargetLayout(MemoryLayout.sequenceLayout(Long.MAX_VALUE, JAVA_BYTE)); + .withTargetLayout(MemoryLayout.sequenceLayout(Long.MAX_VALUE, JAVA_BYTE)); public static final SequenceLayout SWIFT_BYTE_ARRAY = MemoryLayout.sequenceLayout(8, ValueLayout.JAVA_BYTE); /** - * The value layout for Swift's {@code Int} type, which is a signed type that follows + * The value layout for Swift's {@code Int} type, which is a signed type that + * follows * the size of a pointer (aka C's {@code ptrdiff_t}). */ - public static ValueLayout SWIFT_INT = (ValueLayout.ADDRESS.byteSize() == 4) ? - SWIFT_INT32 : SWIFT_INT64; + public static ValueLayout SWIFT_INT = (ValueLayout.ADDRESS.byteSize() == 4) ? SWIFT_INT32 : SWIFT_INT64; + + /** + * Returns true when Swift's {@code Int} is 32-bit on the current platform. + */ + public static final boolean has32bitSwiftInt = (SWIFT_INT == ValueLayout.JAVA_INT); /** - * The value layout for Swift's {@code UInt} type, which is an unsigned type that follows + * The value layout for Swift's {@code UInt} type, which is an unsigned type + * that follows * the size of a pointer (aka C's {@code size_t}). *

- * Java does not have unsigned integer types, so we use the layout for Swift's {@code Int}. + * Java does not have unsigned integer types, so we use the layout for Swift's + * {@code Int}. */ public static ValueLayout SWIFT_UINT = SWIFT_INT; } diff --git a/Tests/JExtractSwiftTests/DataImportTests.swift b/Tests/JExtractSwiftTests/DataImportTests.swift index bad4174db..771ac8cfc 100644 --- a/Tests/JExtractSwiftTests/DataImportTests.swift +++ b/Tests/JExtractSwiftTests/DataImportTests.swift @@ -17,16 +17,16 @@ import Testing final class DataImportTests { private static let ifConfigImport = """ - #if canImport(FoundationEssentials) - import FoundationEssentials - #else - import Foundation - #endif - """ + #if canImport(FoundationEssentials) + import FoundationEssentials + #else + import Foundation + #endif + """ private static let foundationData_interfaceFile = """ import Foundation - + public func receiveData(dat: Data) public func returnData() -> Data """ @@ -34,14 +34,14 @@ final class DataImportTests { private static let foundationDataProtocol_interfaceFile = """ import Foundation - + public func receiveDataProtocol(dat: some DataProtocol, dat2: T?) """ private static let essentialsData_interfaceFile = """ import FoundationEssentials - + public func receiveData(dat: Data) public func returnData() -> Data """ @@ -49,13 +49,13 @@ final class DataImportTests { private static let essentialsDataProtocol_interfaceFile = """ import FoundationEssentials - + public func receiveDataProtocol(dat: some DataProtocol, dat2: T?) """ private static let ifConfigData_interfaceFile = """ \(ifConfigImport) - + public func receiveData(dat: Data) public func returnData() -> Data """ @@ -63,14 +63,19 @@ final class DataImportTests { private static let ifConfigDataProtocol_interfaceFile = """ \(ifConfigImport) - + public func receiveDataProtocol(dat: some DataProtocol, dat2: T?) """ - @Test("Import Data: Swift thunks", arguments: zip( - [Self.foundationData_interfaceFile, Self.essentialsData_interfaceFile, Self.ifConfigData_interfaceFile], - ["import Foundation", "import FoundationEssentials", Self.ifConfigImport] - )) + @Test( + "Import Data: Swift thunks", + arguments: zip( + [ + Self.foundationData_interfaceFile, Self.essentialsData_interfaceFile, + Self.ifConfigData_interfaceFile, + ], + ["import Foundation", "import FoundationEssentials", Self.ifConfigImport] + )) func data_swiftThunk(fileContent: String, expectedImportChunk: String) throws { try assertOutput( @@ -123,10 +128,13 @@ final class DataImportTests { ] ) } - - @Test("Import Data: JavaBindings", arguments: [ - Self.foundationData_interfaceFile, Self.essentialsData_interfaceFile, Self.ifConfigData_interfaceFile - ]) + + @Test( + "Import Data: JavaBindings", + arguments: [ + Self.foundationData_interfaceFile, Self.essentialsData_interfaceFile, + Self.ifConfigData_interfaceFile, + ]) func data_javaBindings(fileContent: String) throws { try assertOutput( input: fileContent, .ffm, .java, @@ -209,7 +217,6 @@ final class DataImportTests { } """, - """ /** * {@snippet lang=c : @@ -246,6 +253,9 @@ final class DataImportTests { * } */ public static Data init(java.lang.foreign.MemorySegment bytes, long count, AllocatingSwiftArena swiftArena$) { + if (SwiftValueLayout.has32bitSwiftInt) { + checkIntegerRange("Swift.Int", count, Integer.MIN_VALUE, Integer.MAX_VALUE); + } MemorySegment _result = swiftArena$.allocate(Data.$LAYOUT); swiftjava_SwiftModule_Data_init_bytes_count.call(bytes, count, _result); return Data.wrapMemoryAddressUnsafe(_result, swiftArena$); @@ -352,7 +362,6 @@ final class DataImportTests { } """, - """ /** * Downcall to Swift: @@ -366,15 +375,20 @@ final class DataImportTests { swiftjava_SwiftModule_Data_withUnsafeBytes__.call(withUnsafeBytes.$toUpcallStub(body, arena$), this.$memorySegment()); } } - """ + """, ] ) } - @Test("Import DataProtocol: Swift thunks", arguments: zip( - [Self.foundationDataProtocol_interfaceFile, Self.essentialsDataProtocol_interfaceFile, Self.ifConfigDataProtocol_interfaceFile], - ["import Foundation", "import FoundationEssentials", Self.ifConfigImport] - )) + @Test( + "Import DataProtocol: Swift thunks", + arguments: zip( + [ + Self.foundationDataProtocol_interfaceFile, Self.essentialsDataProtocol_interfaceFile, + Self.ifConfigDataProtocol_interfaceFile, + ], + ["import Foundation", "import FoundationEssentials", Self.ifConfigImport] + )) func dataProtocol_swiftThunk(fileContent: String, expectedImportChunk: String) throws { try assertOutput( input: fileContent, .ffm, .swift, @@ -390,14 +404,17 @@ final class DataImportTests { // Just to make sure 'Data' is imported. """ @_cdecl("swiftjava_getType_SwiftModule_Data") - """ + """, ] ) } - @Test("Import DataProtocol: JavaBindings", arguments: [ - Self.foundationDataProtocol_interfaceFile, Self.essentialsDataProtocol_interfaceFile, Self.ifConfigDataProtocol_interfaceFile - ]) + @Test( + "Import DataProtocol: JavaBindings", + arguments: [ + Self.foundationDataProtocol_interfaceFile, Self.essentialsDataProtocol_interfaceFile, + Self.ifConfigDataProtocol_interfaceFile, + ]) func dataProtocol_javaBindings(fileContent: String) throws { try assertOutput( @@ -445,9 +462,9 @@ final class DataImportTests { // Just to make sure 'Data' is imported. """ public final class Data extends FFMSwiftInstance implements SwiftValue { - """ + """, ] ) } - + } diff --git a/Tests/JExtractSwiftTests/MethodImportTests.swift b/Tests/JExtractSwiftTests/MethodImportTests.swift index 32f607468..ba8f92c2a 100644 --- a/Tests/JExtractSwiftTests/MethodImportTests.swift +++ b/Tests/JExtractSwiftTests/MethodImportTests.swift @@ -39,11 +39,11 @@ final class MethodImportTests { l: Int64, s: String ) - + public func globalReturnClass() -> MySwiftClass public func globalReturnAny() -> Any - + public func swapRawBufferPointer(buffer: UnsafeRawBufferPointer) -> UnsafeMutableRawBufferPointer extension MySwiftClass { @@ -143,7 +143,10 @@ final class MethodImportTests { * } */ public static void globalTakeInt(long i) { - swiftjava___FakeModule_globalTakeInt_i.call(i); + if (SwiftValueLayout.has32bitSwiftInt) { + checkIntegerRange("Swift.Int", i, Integer.MIN_VALUE, Integer.MAX_VALUE); + } + swiftjava___FakeModule_globalTakeInt_i.call(i); } """ ) @@ -406,9 +409,15 @@ final class MethodImportTests { * } */ public static MySwiftClass init(long len, long cap, AllocatingSwiftArena swiftArena$) { - MemorySegment _result = swiftArena$.allocate(MySwiftClass.$LAYOUT); - swiftjava___FakeModule_MySwiftClass_init_len_cap.call(len, cap, _result) - return MySwiftClass.wrapMemoryAddressUnsafe(_result, swiftArena$); + if (SwiftValueLayout.has32bitSwiftInt) { + checkIntegerRange("Swift.Int", len, Integer.MIN_VALUE, Integer.MAX_VALUE); + } + if (SwiftValueLayout.has32bitSwiftInt) { + checkIntegerRange("Swift.Int", cap, Integer.MIN_VALUE, Integer.MAX_VALUE); + } + MemorySegment _result = swiftArena$.allocate(MySwiftClass.$LAYOUT); + swiftjava___FakeModule_MySwiftClass_init_len_cap.call(len, cap, _result) + return MySwiftClass.wrapMemoryAddressUnsafe(_result, swiftArena$); } """ ) @@ -451,9 +460,15 @@ final class MethodImportTests { * } */ public static MySwiftStruct init(long len, long cap, AllocatingSwiftArena swiftArena$) { - MemorySegment _result = swiftArena$.allocate(MySwiftStruct.$LAYOUT); - swiftjava___FakeModule_MySwiftStruct_init_len_cap.call(len, cap, _result) - return MySwiftStruct.wrapMemoryAddressUnsafe(_result, swiftArena$); + if (SwiftValueLayout.has32bitSwiftInt) { + checkIntegerRange("Swift.Int", len, Integer.MIN_VALUE, Integer.MAX_VALUE); + } + if (SwiftValueLayout.has32bitSwiftInt) { + checkIntegerRange("Swift.Int", cap, Integer.MIN_VALUE, Integer.MAX_VALUE); + } + MemorySegment _result = swiftArena$.allocate(MySwiftStruct.$LAYOUT); + swiftjava___FakeModule_MySwiftStruct_init_len_cap.call(len, cap, _result) + return MySwiftStruct.wrapMemoryAddressUnsafe(_result, swiftArena$); } """ ) @@ -468,8 +483,9 @@ final class MethodImportTests { try st.analyze(path: "Fake.swift", text: class_interfaceFile) - #expect(!st.importedGlobalFuncs.contains { - $0.name == "globalReturnAny" - }, "'Any' return type is not supported yet") + #expect( + !st.importedGlobalFuncs.contains { + $0.name == "globalReturnAny" + }, "'Any' return type is not supported yet") } } diff --git a/Tests/JExtractSwiftTests/UnsignedNumberTests.swift b/Tests/JExtractSwiftTests/UnsignedNumberTests.swift index 3604cfe89..68f84929b 100644 --- a/Tests/JExtractSwiftTests/UnsignedNumberTests.swift +++ b/Tests/JExtractSwiftTests/UnsignedNumberTests.swift @@ -37,8 +37,9 @@ final class UnsignedNumberTests { ); """, """ - public static void unsignedChar(@Unsigned char arg) { - swiftjava_SwiftModule_unsignedChar__.call(arg); + public static void unsignedChar(@Unsigned int arg) { + checkIntegerRange("Swift.UInt16", arg, 0, 0xFFFFL); + swiftjava_SwiftModule_unsignedChar__.call((char) arg); } """, ] @@ -52,9 +53,9 @@ final class UnsignedNumberTests { SwiftModule.$unsignedChar(arg); } private static native void $unsignedChar(char arg); - """, + """ ] - ) + ), ]) func unsignedChar(mode: JExtractGenerationMode, expectedChunks: [String]) throws { try assertOutput( @@ -84,8 +85,9 @@ final class UnsignedNumberTests { ); """, """ - public static void unsignedInt(@Unsigned int arg) { - swiftjava_SwiftModule_unsignedInt__.call(arg); + public static void unsignedInt(@Unsigned long arg) { + checkIntegerRange("Swift.UInt32", arg, 0L, 0xFFFF_FFFFL); + swiftjava_SwiftModule_unsignedInt__.call((int) arg); } """, ] @@ -99,9 +101,9 @@ final class UnsignedNumberTests { SwiftModule.$unsignedInt(arg); } private static native void $unsignedInt(int arg); - """, + """ ] - ) + ), ]) func unsignedIntAnnotate(mode: JExtractGenerationMode, expectedChunks: [String]) throws { var config = Configuration() @@ -151,9 +153,9 @@ final class UnsignedNumberTests { return SwiftModule.$returnUnsignedInt(); } private static native int $returnUnsignedInt(); - """, + """ ] - ) + ), ]) func returnUnsignedIntDefault(mode: JExtractGenerationMode, expectedChunks: [String]) throws { let config = Configuration() @@ -203,9 +205,9 @@ final class UnsignedNumberTests { return SwiftModule.$returnUnsignedLong(); } private static native long $returnUnsignedLong(); - """, + """ ] - ) + ), ]) func return_unsignedLong_annotate(mode: JExtractGenerationMode, expectedChunks: [String]) throws { var config = Configuration() @@ -239,6 +241,7 @@ final class UnsignedNumberTests { """, """ public static void takeUnsignedLong(@Unsigned long arg) { + checkIntegerRange("Swift.UInt64", arg, 0L, Long.MAX_VALUE); swiftjava_SwiftModule_takeUnsignedLong_arg.call(arg); } """, @@ -253,9 +256,9 @@ final class UnsignedNumberTests { SwiftModule.$takeUnsignedLong(arg); } private static native void $takeUnsignedLong(long arg); - """, + """ ] - ) + ), ]) func take_unsignedLong_annotate(mode: JExtractGenerationMode, expectedChunks: [String]) throws { var config = Configuration() @@ -291,8 +294,10 @@ final class UnsignedNumberTests { """, """ @Unsigned - public static int unsignedLong(@Unsigned long first, @Unsigned int second) { - return swiftjava_SwiftModule_unsignedLong_first_second.call(first, second); + public static int unsignedLong(@Unsigned long first, @Unsigned long second) { + checkIntegerRange("Swift.UInt64", first, 0L, Long.MAX_VALUE); + checkIntegerRange("Swift.UInt32", second, 0L, 0xFFFF_FFFFL); + return swiftjava_SwiftModule_unsignedLong_first_second.call(first, (int) second); } """, ] @@ -307,7 +312,7 @@ final class UnsignedNumberTests { return SwiftModule.$unsignedLong(first, second); } private static native int $unsignedLong(long first, int second); - """, + """ ] ), ]) @@ -322,7 +327,7 @@ final class UnsignedNumberTests { expectedChunks: expectedChunks ) } - + @Test( "Import: take UInt return UInt (annotate)", arguments: [ @@ -346,6 +351,12 @@ final class UnsignedNumberTests { """ @Unsigned public static long unsignedLong(@Unsigned long first, @Unsigned long second) { + if (SwiftValueLayout.has32bitSwiftInt) { + checkIntegerRange("Swift.UInt", first, 0L, 0xFFFF_FFFFL); + } + if (SwiftValueLayout.has32bitSwiftInt) { + checkIntegerRange("Swift.UInt", second, 0L, 0xFFFF_FFFFL); + } return swiftjava_SwiftModule_unsignedLong_first_second.call(first, second); } """, @@ -361,7 +372,7 @@ final class UnsignedNumberTests { return SwiftModule.$unsignedLong(first, second); } private static native long $unsignedLong(long first, long second); - """, + """ ] ), ]) diff --git a/Tests/JExtractSwiftTests/VariableImportTests.swift b/Tests/JExtractSwiftTests/VariableImportTests.swift index da0c1afaf..426992970 100644 --- a/Tests/JExtractSwiftTests/VariableImportTests.swift +++ b/Tests/JExtractSwiftTests/VariableImportTests.swift @@ -103,6 +103,9 @@ final class VariableImportTests { */ public void setCounterInt(long newValue) { $ensureAlive(); + if (SwiftValueLayout.has32bitSwiftInt) { + checkIntegerRange("Swift.Int", newValue, Integer.MIN_VALUE, Integer.MAX_VALUE); + } swiftjava_FakeModule_MySwiftClass_counterInt$set.call(newValue, this.$memorySegment()) } """, From aab3778fb4642b4dbade8b39cd08cb0598c3a134 Mon Sep 17 00:00:00 2001 From: Divya Prakash Date: Sun, 1 Feb 2026 11:36:54 +0530 Subject: [PATCH 2/2] for error solving --- .../swiftkit/ffm/StringPassingBenchmark.java | 32 +-- .../java/com/example/swift/FFMArraysTest.java | 21 +- .../MultipleTypesFromSingleFileTest.java | 11 +- .../swift/SwiftTypeInSubDirectoryTest.java | 7 +- .../com/example/swift/WithBufferTest.java | 9 +- ...t2JavaGenerator+JavaBindingsPrinting.swift | 6 +- ...MSwift2JavaGenerator+JavaTranslation.swift | 205 +++++++++--------- 7 files changed, 145 insertions(+), 146 deletions(-) diff --git a/Samples/SwiftJavaExtractFFMSampleApp/src/jmh/java/org/swift/swiftkit/ffm/StringPassingBenchmark.java b/Samples/SwiftJavaExtractFFMSampleApp/src/jmh/java/org/swift/swiftkit/ffm/StringPassingBenchmark.java index 258558422..c781289fe 100644 --- a/Samples/SwiftJavaExtractFFMSampleApp/src/jmh/java/org/swift/swiftkit/ffm/StringPassingBenchmark.java +++ b/Samples/SwiftJavaExtractFFMSampleApp/src/jmh/java/org/swift/swiftkit/ffm/StringPassingBenchmark.java @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Copyright (c) 2026 Apple Inc. and the Swift.org project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -18,7 +18,6 @@ import com.example.swift.MySwiftClass; import com.example.swift.MySwiftLibrary; import org.openjdk.jmh.annotations.*; -import org.swift.swiftkit.core.ClosableSwiftArena; import java.util.concurrent.TimeUnit; @@ -27,7 +26,7 @@ @Measurement(iterations = 10, time = 500, timeUnit = TimeUnit.MILLISECONDS) @OutputTimeUnit(TimeUnit.NANOSECONDS) @State(Scope.Thread) -@Fork(value = 2, jvmArgsAppend = {"--enable-native-access=ALL-UNNAMED"}) +@Fork(value = 2, jvmArgsAppend = { "--enable-native-access=ALL-UNNAMED" }) public class StringPassingBenchmark { @Param({ @@ -70,16 +69,23 @@ public long writeString_baseline() { } static String makeString(int size) { - var text = - "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut in augue ullamcorper, mattis lacus tincidunt, " + - "accumsan massa. Morbi gravida purus ut porttitor iaculis. Vestibulum lacinia, mi in tincidunt hendrerit," + - "lectus est placerat magna, vitae vestibulum nulla ligula at massa. Pellentesque nibh quam, pulvinar eu " + - "nunc congue, molestie molestie augue. Nam convallis consectetur velit, at dictum risus ullamcorper iaculis. " + - "Vestibulum lacinia nisi in elit consectetur vulputate. Praesent id odio tristique, tincidunt arcu et, convallis velit. " + - "Sed vitae pulvinar arcu. Curabitur euismod mattis dui in suscipit. Morbi aliquet facilisis vulputate. Phasellus " + - "non lectus dapibus, semper magna eu, aliquet magna. Suspendisse vel enim at augue luctus gravida. Suspendisse " + - "venenatis justo non accumsan sollicitudin. Suspendisse vitae ornare odio, id blandit nibh. Nulla facilisi. " + - "Nulla nulla orci, finibus nec luctus et, faucibus et ligula."; + var text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut in augue ullamcorper, mattis lacus tincidunt, " + + + "accumsan massa. Morbi gravida purus ut porttitor iaculis. Vestibulum lacinia, mi in tincidunt hendrerit," + + + "lectus est placerat magna, vitae vestibulum nulla ligula at massa. Pellentesque nibh quam, pulvinar eu " + + + "nunc congue, molestie molestie augue. Nam convallis consectetur velit, at dictum risus ullamcorper iaculis. " + + + "Vestibulum lacinia nisi in elit consectetur vulputate. Praesent id odio tristique, tincidunt arcu et, convallis velit. " + + + "Sed vitae pulvinar arcu. Curabitur euismod mattis dui in suscipit. Morbi aliquet facilisis vulputate. Phasellus " + + + "non lectus dapibus, semper magna eu, aliquet magna. Suspendisse vel enim at augue luctus gravida. Suspendisse " + + + "venenatis justo non accumsan sollicitudin. Suspendisse vitae ornare odio, id blandit nibh. Nulla facilisi. " + + + "Nulla nulla orci, finibus nec luctus et, faucibus et ligula."; return text.substring(0, size); } } diff --git a/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/FFMArraysTest.java b/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/FFMArraysTest.java index c195f11bf..38dc50a8b 100644 --- a/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/FFMArraysTest.java +++ b/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/FFMArraysTest.java @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Copyright (c) 2026 Apple Inc. and the Swift.org project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -15,28 +15,28 @@ package com.example.swift; import org.junit.jupiter.api.Test; -import org.swift.swiftkit.core.*; import org.swift.swiftkit.ffm.*; import static org.junit.jupiter.api.Assertions.*; import java.lang.foreign.ValueLayout; import java.util.Arrays; -import java.util.concurrent.atomic.AtomicLong; import java.util.stream.IntStream; public class FFMArraysTest { @Test void test_sumAllByteArrayElements_throughMemorySegment() { - byte[] bytes = new byte[124]; + byte[] bytes = new byte[124]; Arrays.fill(bytes, (byte) 1); try (var arena = AllocatingSwiftArena.ofConfined()) { - // NOTE: We cannot use MemorySegment.ofArray because that creates a HEAP backed segment and therefore cannot pass into native: - // java.lang.IllegalArgumentException: Heap segment not allowed: MemorySegment{ kind: heap, heapBase: [B@5b6ec132, address: 0x0, byteSize: 124 } - // MemorySegment bytesSegment = MemorySegment.ofArray(bytes); // NO COPY (!) - // MySwiftLibrary.sumAllByteArrayElements(bytesSegment, bytes.length); + // NOTE: We cannot use MemorySegment.ofArray because that creates a HEAP backed + // segment and therefore cannot pass into native: + // java.lang.IllegalArgumentException: Heap segment not allowed: MemorySegment{ + // kind: heap, heapBase: [B@5b6ec132, address: 0x0, byteSize: 124 } + // MemorySegment bytesSegment = MemorySegment.ofArray(bytes); // NO COPY (!) + // MySwiftLibrary.sumAllByteArrayElements(bytesSegment, bytes.length); var bytesCopy = arena.allocateFrom(ValueLayout.JAVA_BYTE, bytes); var swiftSideSum = MySwiftLibrary.sumAllByteArrayElements(bytesCopy, bytes.length); @@ -48,7 +48,7 @@ void test_sumAllByteArrayElements_throughMemorySegment() { @Test void test_sumAllByteArrayElements_arrayCopy() { - byte[] bytes = new byte[124]; + byte[] bytes = new byte[124]; Arrays.fill(bytes, (byte) 1); var swiftSideSum = MySwiftLibrary.sumAllByteArrayElements(bytes); @@ -59,9 +59,8 @@ void test_sumAllByteArrayElements_arrayCopy() { @Test void test_getArray() { - AtomicLong bufferSize = new AtomicLong(); byte[] javaBytes = MySwiftLibrary.getArray(); // automatically converted [UInt8] to byte[] - assertArrayEquals(new byte[]{1, 2, 3}, javaBytes); + assertArrayEquals(new byte[] { 1, 2, 3 }, javaBytes); } } diff --git a/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/MultipleTypesFromSingleFileTest.java b/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/MultipleTypesFromSingleFileTest.java index d3cd791c0..a86c5ae95 100644 --- a/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/MultipleTypesFromSingleFileTest.java +++ b/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/MultipleTypesFromSingleFileTest.java @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Copyright (c) 2026 Apple Inc. and the Swift.org project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -15,17 +15,10 @@ package com.example.swift; import org.junit.jupiter.api.Test; -import org.swift.swiftkit.core.*; import org.swift.swiftkit.ffm.*; -import java.lang.foreign.Arena; -import java.util.Optional; -import java.util.OptionalInt; - -import static org.junit.jupiter.api.Assertions.*; - public class MultipleTypesFromSingleFileTest { - + @Test void bothTypesMustHaveBeenGenerated() { try (var arena = AllocatingSwiftArena.ofConfined()) { diff --git a/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/SwiftTypeInSubDirectoryTest.java b/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/SwiftTypeInSubDirectoryTest.java index 87bcfde80..72a18e0a8 100644 --- a/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/SwiftTypeInSubDirectoryTest.java +++ b/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/SwiftTypeInSubDirectoryTest.java @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2024 Apple Inc. and the Swift.org project authors +// Copyright (c) 2026 Apple Inc. and the Swift.org project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -14,14 +14,9 @@ package com.example.swift; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import org.swift.swiftkit.core.SwiftLibraries; import org.swift.swiftkit.ffm.AllocatingSwiftArena; -import java.io.File; -import java.util.stream.Stream; - import static org.junit.jupiter.api.Assertions.*; public class SwiftTypeInSubDirectoryTest { diff --git a/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/WithBufferTest.java b/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/WithBufferTest.java index 9e0654767..015fb3aee 100644 --- a/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/WithBufferTest.java +++ b/Samples/SwiftJavaExtractFFMSampleApp/src/test/java/com/example/swift/WithBufferTest.java @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2025 Apple Inc. and the Swift.org project authors +// Copyright (c) 2026 Apple Inc. and the Swift.org project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -15,16 +15,11 @@ package com.example.swift; import org.junit.jupiter.api.Test; -import org.swift.swiftkit.core.*; -import org.swift.swiftkit.ffm.*; +import org.swift.swiftkit.core.CallTraces; import static org.junit.jupiter.api.Assertions.*; -import java.lang.foreign.*; -import java.lang.invoke.MethodHandle; -import java.util.Arrays; import java.util.concurrent.atomic.AtomicLong; -import java.util.stream.IntStream; public class WithBufferTest { diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift index 87295a696..79e589e62 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaBindingsPrinting.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2024-2025 Apple Inc. and the Swift.org project authors +// Copyright (c) 2024-2026 Apple Inc. and the Swift.org project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -309,7 +309,9 @@ extension FFMSwift2JavaGenerator { """ ) - let cdeclParams = functionType.cdeclType.parameters.map({ "\($0.parameterName!)" }) + let cdeclParams = functionType.cdeclType.parameters.enumerated().map { idx, param in + param.parameterName ?? "_\(idx)" + } printer.printBraceBlock( """ diff --git a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift index 4da8d4cac..4437e957a 100644 --- a/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift +++ b/Sources/JExtractSwiftLib/FFM/FFMSwift2JavaGenerator+JavaTranslation.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2024-2025 Apple Inc. and the Swift.org project authors +// Copyright (c) 2024-2026 Apple Inc. and the Swift.org project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -36,34 +36,24 @@ extension FFMSwift2JavaGenerator { translatedDecls[decl] = translated return translated - (.int, - .cast(.placeholder, cJavaType), - IntegerRangeCheck(targetSwiftType: "Swift.Int8", minValue: "-128", maxValue: "127", onlyWhenSwiftIntIs32Bit: false)) - /// - (.int, - .cast(.placeholder, cJavaType), - IntegerRangeCheck(targetSwiftType: "Swift.UInt8", minValue: "0", maxValue: "0xFFL", onlyWhenSwiftIntIs32Bit: false)) - var conversion: JavaConversionStep - (.int, - .cast(.placeholder, cJavaType), - IntegerRangeCheck(targetSwiftType: "Swift.Int16", minValue: "-32768", maxValue: "32767", onlyWhenSwiftIntIs32Bit: false)) + } + struct IntegerRangeCheck { - (.int, - .cast(.placeholder, cJavaType), - IntegerRangeCheck(targetSwiftType: "Swift.UInt16", minValue: "0", maxValue: "0xFFFFL", onlyWhenSwiftIntIs32Bit: false)) + let targetSwiftType: String + let minValue: String + let maxValue: String + let onlyWhenSwiftIntIs32Bit: Bool + } + + /// Represent a Swift API result translated to Java. + struct TranslatedResult { + var javaResultType: JavaType - (.long, - .cast(.placeholder, cJavaType), - IntegerRangeCheck(targetSwiftType: "Swift.UInt32", minValue: "0L", maxValue: "0xFFFF_FFFFL", onlyWhenSwiftIntIs32Bit: false)) /// Java annotations that should be propagated from the result type onto the method - (cJavaType, .placeholder, - IntegerRangeCheck(targetSwiftType: "Swift.UInt64", minValue: "0L", maxValue: "Long.MAX_VALUE", onlyWhenSwiftIntIs32Bit: false)) + var annotations: [JavaAnnotation] = [] + /// Required indirect return receivers for receiving the result. - (cJavaType, .placeholder, - IntegerRangeCheck(targetSwiftType: "Swift.Int", minValue: "Integer.MIN_VALUE", maxValue: "Integer.MAX_VALUE", onlyWhenSwiftIntIs32Bit: true)) /// - (cJavaType, .placeholder, - IntegerRangeCheck(targetSwiftType: "Swift.UInt", minValue: "0L", maxValue: "0xFFFF_FFFFL", onlyWhenSwiftIntIs32Bit: true)) /// downCall(_result_pointer, _result_count) /// return constructResult(_result_pointer, _result_count) /// @@ -88,6 +78,17 @@ extension FFMSwift2JavaGenerator { var conversion: JavaConversionStep } + /// Represent a Swift API parameter translated to Java. + struct TranslatedParameter { + /// One Swift parameter can be lowered to multiple parameters. + /// E.g. 'Data' as (baseAddress, length) pair. + var javaParameters: [JavaParameter] + + var conversion: JavaConversionStep + + var integerRangeCheck: IntegerRangeCheck? = nil + } + /// Translated Java API representing a Swift API. /// /// Since this holds the lowered signature, and the original `SwiftFunctionSignature` @@ -343,15 +344,21 @@ extension FFMSwift2JavaGenerator { swiftType: swiftType, config: config) if case .nominal(let swiftNominalType) = swiftType, - let knownType = swiftNominalType.nominalTypeDecl.knownTypeKind, - let integerTranslation = try translateIntegerParameter( - swiftType: swiftType, - knownType: knownType, - parameterName: parameterName, - parameterAnnotations: parameterAnnotations - ) + let knownType = swiftNominalType.nominalTypeDecl.knownTypeKind { - return integerTranslation + switch knownType { + case .int8, .uint8, .int16, .uint16, .uint32, .uint64, .int, .uint: + if let integerTranslation = try translateIntegerParameter( + swiftType: swiftType, + knownType: knownType, + parameterName: parameterName, + parameterAnnotations: parameterAnnotations + ) { + return integerTranslation + } + default: + break + } } // If there is a 1:1 mapping between this Swift type and a C type, that can @@ -533,72 +540,74 @@ extension FFMSwift2JavaGenerator { let cType = try CType(cdeclType: swiftType) let cJavaType = cType.javaType - let (javaType, conversion, rangeCheck): (JavaType, JavaConversionStep, IntegerRangeCheck?) = - switch knownType { - case .int8: - ( - .int, - .cast(.placeholder, cJavaType), - IntegerRangeCheck( - targetSwiftType: "Swift.Int8", minValue: "-128", maxValue: "127", - onlyWhenSwiftIntIs32Bit: false) - ) - case .uint8: - ( - .int, - .cast(.placeholder, cJavaType), - IntegerRangeCheck( - targetSwiftType: "Swift.UInt8", minValue: "0", maxValue: "0xFFL", - onlyWhenSwiftIntIs32Bit: false) - ) - case .int16: - ( - .int, - .cast(.placeholder, cJavaType), - IntegerRangeCheck( - targetSwiftType: "Swift.Int16", minValue: "-32768", maxValue: "32767", - onlyWhenSwiftIntIs32Bit: false) - ) - case .uint16: - ( - .int, - .cast(.placeholder, cJavaType), - IntegerRangeCheck( - targetSwiftType: "Swift.UInt16", minValue: "0", maxValue: "0xFFFFL", - onlyWhenSwiftIntIs32Bit: false) - ) - case .uint32: - ( - .long, - .cast(.placeholder, cJavaType), - IntegerRangeCheck( - targetSwiftType: "Swift.UInt32", minValue: "0L", maxValue: "0xFFFF_FFFFL", - onlyWhenSwiftIntIs32Bit: false) - ) - case .uint64: - ( - cJavaType, .placeholder, - IntegerRangeCheck( - targetSwiftType: "Swift.UInt64", minValue: "0L", maxValue: "Long.MAX_VALUE", - onlyWhenSwiftIntIs32Bit: false) - ) - case .int: - ( - cJavaType, .placeholder, - IntegerRangeCheck( - targetSwiftType: "Swift.Int", minValue: "Integer.MIN_VALUE", - maxValue: "Integer.MAX_VALUE", onlyWhenSwiftIntIs32Bit: true) - ) - case .uint: - ( - cJavaType, .placeholder, - IntegerRangeCheck( - targetSwiftType: "Swift.UInt", minValue: "0L", maxValue: "0xFFFF_FFFFL", - onlyWhenSwiftIntIs32Bit: true) - ) - default: - return nil - } + let translation: (JavaType, JavaConversionStep, IntegerRangeCheck?) + switch knownType { + case .int8: + translation = ( + .int, + .cast(.placeholder, cJavaType), + IntegerRangeCheck( + targetSwiftType: "Swift.Int8", minValue: "-128", maxValue: "127", + onlyWhenSwiftIntIs32Bit: false) + ) + case .uint8: + translation = ( + .int, + .cast(.placeholder, cJavaType), + IntegerRangeCheck( + targetSwiftType: "Swift.UInt8", minValue: "0", maxValue: "0xFFL", + onlyWhenSwiftIntIs32Bit: false) + ) + case .int16: + translation = ( + .int, + .cast(.placeholder, cJavaType), + IntegerRangeCheck( + targetSwiftType: "Swift.Int16", minValue: "-32768", maxValue: "32767", + onlyWhenSwiftIntIs32Bit: false) + ) + case .uint16: + translation = ( + .int, + .cast(.placeholder, cJavaType), + IntegerRangeCheck( + targetSwiftType: "Swift.UInt16", minValue: "0", maxValue: "0xFFFFL", + onlyWhenSwiftIntIs32Bit: false) + ) + case .uint32: + translation = ( + .long, + .cast(.placeholder, cJavaType), + IntegerRangeCheck( + targetSwiftType: "Swift.UInt32", minValue: "0L", maxValue: "0xFFFF_FFFFL", + onlyWhenSwiftIntIs32Bit: false) + ) + case .uint64: + translation = ( + cJavaType, .placeholder, + IntegerRangeCheck( + targetSwiftType: "Swift.UInt64", minValue: "0L", maxValue: "Long.MAX_VALUE", + onlyWhenSwiftIntIs32Bit: false) + ) + case .int: + translation = ( + cJavaType, .placeholder, + IntegerRangeCheck( + targetSwiftType: "Swift.Int", minValue: "Integer.MIN_VALUE", + maxValue: "Integer.MAX_VALUE", onlyWhenSwiftIntIs32Bit: true) + ) + case .uint: + translation = ( + cJavaType, .placeholder, + IntegerRangeCheck( + targetSwiftType: "Swift.UInt", minValue: "0L", maxValue: "0xFFFF_FFFFL", + onlyWhenSwiftIntIs32Bit: true) + ) + default: + return nil + } + + let (javaType, conversion, rangeCheck) = translation return TranslatedParameter( javaParameters: [