diff --git a/CMakeLists.txt b/CMakeLists.txt index 0885b3785ff0a..f93c026e41a0a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -747,6 +747,10 @@ option(SWIFT_ENABLE_EXPERIMENTAL_PARSER_VALIDATION "Enable experimental SwiftParser validation by default" FALSE) +option(SWIFT_ENABLE_EXPERIMENTAL_POINTER_BOUNDS + "Enable experimental safe wrappers around external functions" + FALSE) + cmake_dependent_option(SWIFT_BUILD_SOURCEKIT "Build SourceKit" TRUE "SWIFT_ENABLE_DISPATCH" FALSE) @@ -1399,6 +1403,7 @@ if(SWIFT_BUILD_STDLIB OR SWIFT_BUILD_SDK_OVERLAY) message(STATUS "Observation Support: ${SWIFT_ENABLE_EXPERIMENTAL_OBSERVATION}") message(STATUS "Synchronization Support: ${SWIFT_ENABLE_SYNCHRONIZATION}") message(STATUS "Volatile Support: ${SWIFT_ENABLE_VOLATILE}") + message(STATUS "Pointer Bounds Support: ${SWIFT_ENABLE_EXPERIMENTAL_POINTER_BOUNDS}") message(STATUS "") else() message(STATUS "Not building Swift standard library, SDK overlays, and runtime") diff --git a/lib/Macros/Sources/SwiftMacros/CMakeLists.txt b/lib/Macros/Sources/SwiftMacros/CMakeLists.txt index 9fc3f26ada073..5c5ab1bd4b616 100644 --- a/lib/Macros/Sources/SwiftMacros/CMakeLists.txt +++ b/lib/Macros/Sources/SwiftMacros/CMakeLists.txt @@ -16,6 +16,7 @@ add_swift_macro_library(SwiftMacros DistributedResolvableMacro.swift SyntaxExtensions.swift TaskLocalMacro.swift + PointerBoundsMacro.swift SWIFT_DEPENDENCIES SwiftDiagnostics SwiftSyntax diff --git a/lib/Macros/Sources/SwiftMacros/PointerBoundsMacro.swift b/lib/Macros/Sources/SwiftMacros/PointerBoundsMacro.swift new file mode 100644 index 0000000000000..a445a19b9b320 --- /dev/null +++ b/lib/Macros/Sources/SwiftMacros/PointerBoundsMacro.swift @@ -0,0 +1,759 @@ +import SwiftDiagnostics +import SwiftParser +import SwiftSyntax +import SwiftSyntaxBuilder +import SwiftSyntaxMacros + +protocol ParamInfo: CustomStringConvertible { + var description: String { get } + var original: ExprSyntax { get } + var pointerIndex: Int { get } + var nonescaping: Bool { get set } + + func getBoundsCheckedThunkBuilder( + _ base: BoundsCheckedThunkBuilder, _ funcDecl: FunctionDeclSyntax, + _ variant: Variant + ) -> BoundsCheckedThunkBuilder +} + +struct CountedBy: ParamInfo { + var pointerIndex: Int + var count: ExprSyntax + var sizedBy: Bool + var nonescaping: Bool + var original: ExprSyntax + + var description: String { + if sizedBy { + return ".sizedBy(pointer: \(pointerIndex), size: \"\(count)\", nonescaping: \(nonescaping))" + } + return ".countedBy(pointer: \(pointerIndex), count: \"\(count)\", nonescaping: \(nonescaping))" + } + + func getBoundsCheckedThunkBuilder( + _ base: BoundsCheckedThunkBuilder, _ funcDecl: FunctionDeclSyntax, + _ variant: Variant + ) -> BoundsCheckedThunkBuilder { + let funcParam = getParam(funcDecl, pointerIndex - 1) + let paramName = funcParam.secondName ?? funcParam.firstName + let isNullable = funcParam.type.is(OptionalTypeSyntax.self) + return CountedOrSizedPointerThunkBuilder( + base: base, index: pointerIndex - 1, countExpr: count, + name: paramName, nullable: isNullable, signature: funcDecl.signature, + nonescaping: nonescaping, isSizedBy: sizedBy) + } +} +struct EndedBy: ParamInfo { + var pointerIndex: Int + var endIndex: Int + var nonescaping: Bool + var original: ExprSyntax + + var description: String { + return ".endedBy(start: \(pointerIndex), end: \(endIndex), nonescaping: \(nonescaping))" + } + + func getBoundsCheckedThunkBuilder( + _ base: BoundsCheckedThunkBuilder, _ funcDecl: FunctionDeclSyntax, + _ variant: Variant + ) -> BoundsCheckedThunkBuilder { + let funcParam = getParam(funcDecl, pointerIndex - 1) + let paramName = funcParam.secondName ?? funcParam.firstName + let isNullable = funcParam.type.is(OptionalTypeSyntax.self) + return EndedByPointerThunkBuilder( + base: base, startIndex: pointerIndex - 1, endIndex: endIndex - 1, + name: paramName, nullable: isNullable, signature: funcDecl.signature, nonescaping: nonescaping + ) + } +} + +struct RuntimeError: Error { + let description: String + + init(_ description: String) { + self.description = description + } + + var errorDescription: String? { + description + } +} + +struct DiagnosticError: Error { + let description: String + let node: SyntaxProtocol + let notes: [Note] + + init(_ description: String, node: SyntaxProtocol, notes: [Note] = []) { + self.description = description + self.node = node + self.notes = notes + } + + var errorDescription: String? { + description + } +} + +enum Mutability { + case Immutable + case Mutable +} + +func getTypeName(_ type: TypeSyntax) throws -> TokenSyntax { + switch type.kind { + case .memberType: + let memberType = type.as(MemberTypeSyntax.self)! + if !memberType.baseType.isSwiftCoreModule { + throw DiagnosticError( + "expected pointer type in Swift core module, got type \(type) with base type \(memberType.baseType)", + node: type) + } + return memberType.name + case .identifierType: + return type.as(IdentifierTypeSyntax.self)!.name + default: + throw DiagnosticError("expected pointer type, got \(type) with kind \(type.kind)", node: type) + } +} + +func replaceTypeName(_ type: TypeSyntax, _ name: TokenSyntax) -> TypeSyntax { + if let memberType = type.as(MemberTypeSyntax.self) { + return TypeSyntax(memberType.with(\.name, name)) + } + let idType = type.as(IdentifierTypeSyntax.self)! + return TypeSyntax(idType.with(\.name, name)) +} + +func getPointerMutability(text: String) -> Mutability? { + switch text { + case "UnsafePointer": return .Immutable + case "UnsafeMutablePointer": return .Mutable + case "UnsafeRawPointer": return .Immutable + case "UnsafeMutableRawPointer": return .Mutable + default: + return nil + } +} + +func getSafePointerName(mut: Mutability, generateSpan: Bool, isRaw: Bool) -> TokenSyntax { + switch (mut, generateSpan, isRaw) { + case (.Immutable, true, true): return "RawSpan" + case (.Mutable, true, true): return "MutableRawSpan" + case (.Immutable, false, true): return "UnsafeRawBufferPointer" + case (.Mutable, false, true): return "UnsafeMutableRawBufferPointer" + + case (.Immutable, true, false): return "Span" + case (.Mutable, true, false): return "MutableSpan" + case (.Immutable, false, false): return "UnsafeBufferPointer" + case (.Mutable, false, false): return "UnsafeMutableBufferPointer" + } +} + +func transformType(_ prev: TypeSyntax, _ variant: Variant, _ isSizedBy: Bool) throws -> TypeSyntax { + if let optType = prev.as(OptionalTypeSyntax.self) { + return TypeSyntax( + optType.with(\.wrappedType, try transformType(optType.wrappedType, variant, isSizedBy))) + } + if let impOptType = prev.as(ImplicitlyUnwrappedOptionalTypeSyntax.self) { + return try transformType(impOptType.wrappedType, variant, isSizedBy) + } + let name = try getTypeName(prev) + let text = name.text + if !isSizedBy && (text == "UnsafeRawPointer" || text == "UnsafeMutableRawPointer") { + throw DiagnosticError("raw pointers only supported for SizedBy", node: name) + } + + guard let kind: Mutability = getPointerMutability(text: text) else { + throw DiagnosticError( + "expected Unsafe[Mutable][Raw]Pointer type for type \(prev)" + + " - first type token is '\(text)'", node: name) + } + let token = getSafePointerName(mut: kind, generateSpan: variant.generateSpan, isRaw: isSizedBy) + if isSizedBy { + return TypeSyntax(IdentifierTypeSyntax(name: token)) + } + return replaceTypeName(prev, token) +} + +struct Variant { + public let generateSpan: Bool + public let skipTrivialCount: Bool +} + +protocol BoundsCheckedThunkBuilder { + func buildFunctionCall(_ pointerArgs: [Int: ExprSyntax], _ variant: Variant) throws -> ExprSyntax + func buildBoundsChecks(_ variant: Variant) throws -> [CodeBlockItemSyntax.Item] + func buildFunctionSignature(_ argTypes: [Int: TypeSyntax?], _ variant: Variant) throws + -> FunctionSignatureSyntax +} + +func getParam(_ signature: FunctionSignatureSyntax, _ paramIndex: Int) -> FunctionParameterSyntax { + let params = signature.parameterClause.parameters + if paramIndex > 0 { + return params[params.index(params.startIndex, offsetBy: paramIndex)] + } else { + return params[params.startIndex] + } +} +func getParam(_ funcDecl: FunctionDeclSyntax, _ paramIndex: Int) -> FunctionParameterSyntax { + return getParam(funcDecl.signature, paramIndex) +} + +struct FunctionCallBuilder: BoundsCheckedThunkBuilder { + let base: FunctionDeclSyntax + init(_ function: FunctionDeclSyntax) { + base = function + } + + func buildBoundsChecks(_ variant: Variant) throws -> [CodeBlockItemSyntax.Item] { + return [] + } + + func buildFunctionSignature(_ argTypes: [Int: TypeSyntax?], _ variant: Variant) throws + -> FunctionSignatureSyntax + { + var newParams = base.signature.parameterClause.parameters.enumerated().filter { + let type = argTypes[$0.offset] + // filter out deleted parameters, i.e. ones where argTypes[i] _contains_ nil + return type == nil || type! != nil + }.map { (i: Int, e: FunctionParameterSyntax) in + e.with(\.type, (argTypes[i] ?? e.type)!) + } + let last = newParams.popLast()! + newParams.append(last.with(\.trailingComma, nil)) + + return base.signature.with(\.parameterClause.parameters, FunctionParameterListSyntax(newParams)) + } + + func buildFunctionCall(_ pointerArgs: [Int: ExprSyntax], _: Variant) throws -> ExprSyntax { + let functionRef = DeclReferenceExprSyntax(baseName: base.name) + let args: [ExprSyntax] = base.signature.parameterClause.parameters.enumerated() + .map { (i: Int, param: FunctionParameterSyntax) in + let name = param.secondName ?? param.firstName + let declref = DeclReferenceExprSyntax(baseName: name) + return pointerArgs[i] ?? ExprSyntax(declref) + } + let labels: [TokenSyntax?] = base.signature.parameterClause.parameters.map { param in + let firstName = param.firstName + if firstName.trimmed.text == "_" { + return nil + } + return firstName + } + let labeledArgs: [LabeledExprSyntax] = zip(labels, args).enumerated().map { (i, e) in + let (label, arg) = e + var comma: TokenSyntax? = nil + if i < args.count - 1 { + comma = .commaToken() + } + return LabeledExprSyntax(label: label, expression: arg, trailingComma: comma) + } + return ExprSyntax( + FunctionCallExprSyntax( + calledExpression: functionRef, leftParen: .leftParenToken(), + arguments: LabeledExprListSyntax(labeledArgs), rightParen: .rightParenToken())) + } +} + +protocol PointerBoundsThunkBuilder: BoundsCheckedThunkBuilder { + var name: TokenSyntax { get } + var nullable: Bool { get } + var signature: FunctionSignatureSyntax { get } + var nonescaping: Bool { get } +} + +struct CountedOrSizedPointerThunkBuilder: PointerBoundsThunkBuilder { + public let base: BoundsCheckedThunkBuilder + public let index: Int + public let countExpr: ExprSyntax + public let name: TokenSyntax + public let nullable: Bool + public let signature: FunctionSignatureSyntax + public let nonescaping: Bool + public let isSizedBy: Bool + + func buildFunctionSignature(_ argTypes: [Int: TypeSyntax?], _ variant: Variant) throws + -> FunctionSignatureSyntax + { + var types = argTypes + let param = getParam(signature, index) + types[index] = try transformType(param.type, variant, isSizedBy) + if variant.skipTrivialCount { + if let countVar = countExpr.as(DeclReferenceExprSyntax.self) { + let i = try getParameterIndexForDeclRef(signature.parameterClause.parameters, countVar) + types[i] = nil as TypeSyntax? + } + } + return try base.buildFunctionSignature(types, variant) + } + + func buildBoundsChecks(_ variant: Variant) throws -> [CodeBlockItemSyntax.Item] { + var res = try base.buildBoundsChecks(variant) + let countName: TokenSyntax = "_\(raw: name)Count" + let count: VariableDeclSyntax = try VariableDeclSyntax( + "let \(countName): some BinaryInteger = \(countExpr)") + res.append(CodeBlockItemSyntax.Item(count)) + + let countCheck = ExprSyntax( + """ + if \(getCount(variant)) < \(countName) || \(countName) < 0 { + fatalError("bounds check failure when calling unsafe function") + } + """) + res.append(CodeBlockItemSyntax.Item(countCheck)) + return res + } + + func unwrapIfNullable(_ expr: ExprSyntax) -> ExprSyntax { + if nullable { + return ExprSyntax(ForceUnwrapExprSyntax(expression: expr)) + } + return expr + } + + func unwrapIfNonnullable(_ expr: ExprSyntax) -> ExprSyntax { + if !nullable { + return ExprSyntax(ForceUnwrapExprSyntax(expression: expr)) + } + return expr + } + + func castIntToTargetType(expr: ExprSyntax, type: TypeSyntax) -> ExprSyntax { + if type.canRepresentBasicType(type: Int.self) { + return expr + } + return ExprSyntax("\(type)(exactly: \(expr))!") + } + + func buildUnwrapCall(_ argOverrides: [Int: ExprSyntax], _ variant: Variant) throws -> ExprSyntax { + let unwrappedName = TokenSyntax("_\(name)Ptr") + var args = argOverrides + let argExpr = ExprSyntax("\(unwrappedName).baseAddress") + assert(args[index] == nil) + args[index] = unwrapIfNonnullable(argExpr) + let call = try base.buildFunctionCall(args, variant) + let ptrRef = unwrapIfNullable(ExprSyntax(DeclReferenceExprSyntax(baseName: name))) + + let funcName = isSizedBy ? "withUnsafeBytes" : "withUnsafeBufferPointer" + let unwrappedCall = ExprSyntax( + """ + \(ptrRef).\(raw: funcName) { \(unwrappedName) in + return \(call) + } + """) + return unwrappedCall + } + + func getCount(_ variant: Variant) -> ExprSyntax { + let countName = isSizedBy && variant.generateSpan ? "byteCount" : "count" + if nullable { + return ExprSyntax("\(name)?.\(raw: countName) ?? 0") + } + return ExprSyntax("\(name).\(raw: countName)") + } + + func getPointerArg() -> ExprSyntax { + if nullable { + return ExprSyntax("\(name)?.baseAddress") + } + return ExprSyntax("\(name).baseAddress!") + } + + func buildFunctionCall(_ argOverrides: [Int: ExprSyntax], _ variant: Variant) throws -> ExprSyntax + { + var args = argOverrides + if variant.skipTrivialCount { + assert( + countExpr.is(DeclReferenceExprSyntax.self) || countExpr.is(IntegerLiteralExprSyntax.self)) + if let countVar = countExpr.as(DeclReferenceExprSyntax.self) { + let i = try getParameterIndexForDeclRef(signature.parameterClause.parameters, countVar) + assert(args[i] == nil) + args[i] = castIntToTargetType(expr: getCount(variant), type: getParam(signature, i).type) + } + } + assert(args[index] == nil) + if variant.generateSpan { + assert(nonescaping) + let unwrappedCall = try buildUnwrapCall(args, variant) + if nullable { + var nullArgs = args + nullArgs[index] = ExprSyntax(NilLiteralExprSyntax(nilKeyword: .keyword(.nil))) + return ExprSyntax( + """ + if \(name) == nil { + \(try base.buildFunctionCall(nullArgs, variant)) + } else { + \(unwrappedCall) + } + """) + } + return unwrappedCall + } + + args[index] = getPointerArg() + return try base.buildFunctionCall(args, variant) + } +} + +struct EndedByPointerThunkBuilder: PointerBoundsThunkBuilder { + public let base: BoundsCheckedThunkBuilder + public let startIndex: Int + public let endIndex: Int + public let name: TokenSyntax + public let nullable: Bool + public let signature: FunctionSignatureSyntax + public let nonescaping: Bool + + func buildFunctionSignature(_ argTypes: [Int: TypeSyntax?], _ variant: Variant) throws + -> FunctionSignatureSyntax + { + throw RuntimeError("endedBy support not yet implemented") + } + + func buildBoundsChecks(_ variant: Variant) throws -> [CodeBlockItemSyntax.Item] { + throw RuntimeError("endedBy support not yet implemented") + } + + func buildFunctionCall(_ argOverrides: [Int: ExprSyntax], _ variant: Variant) throws -> ExprSyntax + { + throw RuntimeError("endedBy support not yet implemented") + } +} + +func getArgumentByName(_ argumentList: LabeledExprListSyntax, _ name: String) throws -> ExprSyntax { + guard + let arg = argumentList.first(where: { + return $0.label?.text == name + }) + else { + throw DiagnosticError( + "no argument with name '\(name)' in '\(argumentList)'", node: argumentList) + } + return arg.expression +} + +func getOptionalArgumentByName(_ argumentList: LabeledExprListSyntax, _ name: String) -> ExprSyntax? +{ + return argumentList.first(where: { + $0.label?.text == name + })?.expression +} + +func getParameterIndexForDeclRef( + _ parameterList: FunctionParameterListSyntax, _ ref: DeclReferenceExprSyntax +) throws -> Int { + let name = ref.baseName.text + guard + let index = parameterList.enumerated().first(where: { + (_: Int, param: FunctionParameterSyntax) in + let paramenterName = param.secondName ?? param.firstName + return paramenterName.trimmed.text == name + })?.offset + else { + throw DiagnosticError("no parameter with name '\(name)' in '\(parameterList)'", node: ref) + } + return index +} + +/// A macro that adds safe(r) wrappers for functions with unsafe pointer types. +/// Depends on bounds, escapability and lifetime information for each pointer. +/// Intended to map to C attributes like __counted_by, __ended_by and __no_escape, +/// for automatic application by ClangImporter when the C declaration is annotated +/// appropriately. +public struct PointerBoundsMacro: PeerMacro { + static func parseEnumName(_ enumConstructorExpr: FunctionCallExprSyntax) throws -> String { + guard let calledExpr = enumConstructorExpr.calledExpression.as(MemberAccessExprSyntax.self) + else { + throw DiagnosticError( + "expected PointerParam enum literal as argument, got '\(enumConstructorExpr)'", + node: enumConstructorExpr) + } + return calledExpr.declName.baseName.text + } + + static func getIntLiteralValue(_ expr: ExprSyntax) throws -> Int { + guard let intLiteral = expr.as(IntegerLiteralExprSyntax.self) else { + throw DiagnosticError("expected integer literal, got '\(expr)'", node: expr) + } + guard let res = intLiteral.representedLiteralValue else { + throw DiagnosticError("expected integer literal, got '\(expr)'", node: expr) + } + return res + } + + static func getBoolLiteralValue(_ expr: ExprSyntax) throws -> Bool { + guard let boolLiteral = expr.as(BooleanLiteralExprSyntax.self) else { + throw DiagnosticError("expected boolean literal, got '\(expr)'", node: expr) + } + switch boolLiteral.literal.tokenKind { + case .keyword(.true): + return true + case .keyword(.false): + return false + default: + throw DiagnosticError("expected bool literal, got '\(expr)'", node: expr) + } + } + + static func parseCountedByEnum( + _ enumConstructorExpr: FunctionCallExprSyntax, _ signature: FunctionSignatureSyntax + ) throws -> ParamInfo { + let argumentList = enumConstructorExpr.arguments + let pointerParamIndexArg = try getArgumentByName(argumentList, "pointer") + let pointerParamIndex: Int = try getIntLiteralValue(pointerParamIndexArg) + let countExprArg = try getArgumentByName(argumentList, "count") + guard let countExprStringLit = countExprArg.as(StringLiteralExprSyntax.self) else { + throw DiagnosticError( + "expected string literal for 'count' parameter, got \(countExprArg)", node: countExprArg) + } + let unwrappedCountExpr = ExprSyntax(stringLiteral: countExprStringLit.representedLiteralValue!) + if let countVar = unwrappedCountExpr.as(DeclReferenceExprSyntax.self) { + // Perform this lookup here so we can override the position to point to the string literal + // instead of line 1, column 1 + do { + _ = try getParameterIndexForDeclRef(signature.parameterClause.parameters, countVar) + } catch let error as DiagnosticError { + throw DiagnosticError(error.description, node: countExprStringLit, notes: error.notes) + } + } + return CountedBy( + pointerIndex: pointerParamIndex, count: unwrappedCountExpr, sizedBy: false, + nonescaping: false, original: ExprSyntax(enumConstructorExpr)) + } + + static func parseSizedByEnum(_ enumConstructorExpr: FunctionCallExprSyntax) throws -> ParamInfo { + let argumentList = enumConstructorExpr.arguments + let pointerParamIndexArg = try getArgumentByName(argumentList, "pointer") + let pointerParamIndex: Int = try getIntLiteralValue(pointerParamIndexArg) + let sizeExprArg = try getArgumentByName(argumentList, "size") + guard let sizeExprStringLit = sizeExprArg.as(StringLiteralExprSyntax.self) else { + throw DiagnosticError( + "expected string literal for 'size' parameter, got \(sizeExprArg)", node: sizeExprArg) + } + let unwrappedCountExpr = ExprSyntax(stringLiteral: sizeExprStringLit.representedLiteralValue!) + return CountedBy( + pointerIndex: pointerParamIndex, count: unwrappedCountExpr, sizedBy: true, nonescaping: false, + original: ExprSyntax(enumConstructorExpr)) + } + + static func parseEndedByEnum(_ enumConstructorExpr: FunctionCallExprSyntax) throws -> ParamInfo { + let argumentList = enumConstructorExpr.arguments + let startParamIndexArg = try getArgumentByName(argumentList, "start") + let startParamIndex: Int = try getIntLiteralValue(startParamIndexArg) + let endParamIndexArg = try getArgumentByName(argumentList, "end") + let endParamIndex: Int = try getIntLiteralValue(endParamIndexArg) + let nonescapingExprArg = getOptionalArgumentByName(argumentList, "nonescaping") + let nonescaping = try nonescapingExprArg != nil && getBoolLiteralValue(nonescapingExprArg!) + return EndedBy( + pointerIndex: startParamIndex, endIndex: endParamIndex, nonescaping: nonescaping, + original: ExprSyntax(enumConstructorExpr)) + } + + static func parseNonEscaping(_ enumConstructorExpr: FunctionCallExprSyntax) throws -> Int { + let argumentList = enumConstructorExpr.arguments + let pointerParamIndexArg = try getArgumentByName(argumentList, "pointer") + let pointerParamIndex: Int = try getIntLiteralValue(pointerParamIndexArg) + return pointerParamIndex + } + + static func parseMacroParam( + _ paramAST: LabeledExprSyntax, _ signature: FunctionSignatureSyntax, + nonescapingPointers: inout Set + ) throws -> ParamInfo? { + let paramExpr = paramAST.expression + guard let enumConstructorExpr = paramExpr.as(FunctionCallExprSyntax.self) else { + throw DiagnosticError( + "expected PointerParam enum literal as argument, got '\(paramExpr)'", node: paramExpr) + } + let enumName = try parseEnumName(enumConstructorExpr) + switch enumName { + case "countedBy": return try parseCountedByEnum(enumConstructorExpr, signature) + case "sizedBy": return try parseSizedByEnum(enumConstructorExpr) + case "endedBy": return try parseEndedByEnum(enumConstructorExpr) + case "nonescaping": + let index = try parseNonEscaping(enumConstructorExpr) + nonescapingPointers.insert(index) + return nil + default: + throw DiagnosticError( + "expected 'countedBy', 'sizedBy', 'endedBy' or 'nonescaping', got '\(enumName)'", + node: enumConstructorExpr) + } + } + + static func hasSafeVariants(_ parsedArgs: [ParamInfo]) -> Bool { + return parsedArgs.contains { $0.nonescaping } + } + + static func hasTrivialCountVariants(_ parsedArgs: [ParamInfo]) -> Bool { + let countExprs = parsedArgs.compactMap { + switch $0 { + case let c as CountedBy: return c.count + default: return nil + } + } + let trivialCounts = countExprs.filter { + $0.is(DeclReferenceExprSyntax.self) || $0.is(IntegerLiteralExprSyntax.self) + } + // don't generate trivial count variants if there are any non-trivial counts + if trivialCounts.count < countExprs.count { + return false + } + let countVars = trivialCounts.filter { $0.is(DeclReferenceExprSyntax.self) } + let distinctCountVars = Set( + countVars.map { + return $0.as(DeclReferenceExprSyntax.self)!.baseName.text + }) + // don't generate trivial count variants if two count expressions refer to the same parameter + return countVars.count == distinctCountVars.count + } + + static func checkArgs(_ args: [ParamInfo], _ funcDecl: FunctionDeclSyntax) throws { + var argByIndex: [Int: ParamInfo] = [:] + let paramCount = funcDecl.signature.parameterClause.parameters.count + try args.forEach { pointerArg in + let i = pointerArg.pointerIndex + if i < 1 || i > paramCount { + let noteMessage = + paramCount > 0 + ? "function \(funcDecl.name) has parameter indices 1..\(paramCount)" + : "function \(funcDecl.name) has no parameters" + throw DiagnosticError( + "pointer index out of bounds", node: pointerArg.original, + notes: [ + Note(node: Syntax(funcDecl.name), message: MacroExpansionNoteMessage(noteMessage)) + ]) + } + if argByIndex[i] != nil { + throw DiagnosticError( + "multiple PointerParams referring to parameter with index " + + "\(i): \(pointerArg) and \(argByIndex[i]!)", node: pointerArg.original) + } + argByIndex[i] = pointerArg + } + } + + static func setNonescapingPointers(_ args: inout [ParamInfo], _ nonescapingPointers: Set) { + for i in 0...args.count - 1 where nonescapingPointers.contains(args[i].pointerIndex) { + args[i].nonescaping = true + } + } + + public static func expansion( + of node: AttributeSyntax, + providingPeersOf declaration: some DeclSyntaxProtocol, + in context: some MacroExpansionContext + ) throws -> [DeclSyntax] { + do { + guard let funcDecl = declaration.as(FunctionDeclSyntax.self) else { + throw DiagnosticError("@PointerBounds only works on functions", node: declaration) + } + + let argumentList = node.arguments!.as(LabeledExprListSyntax.self)! + var nonescapingPointers = Set() + var parsedArgs = try argumentList.compactMap { + try parseMacroParam($0, funcDecl.signature, nonescapingPointers: &nonescapingPointers) + } + setNonescapingPointers(&parsedArgs, nonescapingPointers) + try checkArgs(parsedArgs, funcDecl) + let baseBuilder = FunctionCallBuilder(funcDecl) + + let variant = Variant( + generateSpan: hasSafeVariants(parsedArgs), + skipTrivialCount: hasTrivialCountVariants(parsedArgs)) + + let builder: BoundsCheckedThunkBuilder = parsedArgs.reduce( + baseBuilder, + { (prev, parsedArg) in + parsedArg.getBoundsCheckedThunkBuilder(prev, funcDecl, variant) + }) + let newSignature = try builder.buildFunctionSignature([:], variant) + let checks = + variant.skipTrivialCount + ? [] as [CodeBlockItemSyntax] + : try builder.buildBoundsChecks(variant).map { e in + CodeBlockItemSyntax(leadingTrivia: "\n", item: e) + } + let call = CodeBlockItemSyntax( + item: CodeBlockItemSyntax.Item( + ReturnStmtSyntax( + returnKeyword: .keyword(.return, trailingTrivia: " "), + expression: try builder.buildFunctionCall([:], variant)))) + let body = CodeBlockSyntax(statements: CodeBlockItemListSyntax(checks + [call])) + let newFunc = + funcDecl + .with(\.signature, newSignature) + .with(\.body, body) + .with( + \.attributes, + funcDecl.attributes.filter { e in + switch e { + case .attribute(let attr): + // don't apply this macro recursively, and avoid dupe _alwaysEmitIntoClient + let name = attr.attributeName.as(IdentifierTypeSyntax.self)?.name.text + return name == nil || (name != "PointerBounds" && name != "_alwaysEmitIntoClient") + default: return true + } + } + [ + .attribute( + AttributeSyntax( + atSign: .atSignToken(), + attributeName: IdentifierTypeSyntax(name: "_alwaysEmitIntoClient"))) + ]) + return [DeclSyntax(newFunc)] + } catch let error as DiagnosticError { + context.diagnose( + Diagnostic( + node: error.node, message: MacroExpansionErrorMessage(error.description), + notes: error.notes)) + return [] + } + } +} + +// MARK: syntax utils +extension TypeSyntaxProtocol { + public var isSwiftCoreModule: Bool { + guard let identifierType = self.as(IdentifierTypeSyntax.self) else { + return false + } + return identifierType.name.text == "Swift" + } + + /// Check if this syntax could resolve to the type passed. Only supports types where the canonical type + /// can be named using only IdentifierTypeSyntax and MemberTypeSyntax. A non-exhaustive list of unsupported + /// types includes: + /// * array types + /// * function types + /// * optional types + /// * tuple types (including Void!) + /// The type syntax is allowed to use any level of qualified name for the type, e.g. Swift.Int.self + /// will match against both "Swift.Int" and "Int". + /// + /// - Parameter type: Type to check against. NB: if passing a type alias, the canonical type will be used. + /// - Returns: true if `self` spells out some suffix of the fully qualified name of `type`, otherwise false + public func canRepresentBasicType(type: Any.Type) -> Bool { + let qualifiedTypeName = String(reflecting: type) + var typeNames = qualifiedTypeName.split(separator: ".") + var currType: TypeSyntaxProtocol = self + + while !typeNames.isEmpty { + let typeName = typeNames.popLast()! + if let identifierType = currType.as(IdentifierTypeSyntax.self) { + // It doesn't matter whether this is the final element of typeNames, because we don't know + // surrounding context - the Foo.Bar.Baz type can be referred to as `Baz` inside Foo.Bar + return identifierType.name.text == typeName + } else if let memberType = currType.as(MemberTypeSyntax.self) { + if memberType.name.text != typeName { + return false + } + currType = memberType.baseType + } else { + return false + } + } + + return false + } +} diff --git a/stdlib/cmake/modules/SwiftSource.cmake b/stdlib/cmake/modules/SwiftSource.cmake index dfa7b1baec24b..617b55836b08c 100644 --- a/stdlib/cmake/modules/SwiftSource.cmake +++ b/stdlib/cmake/modules/SwiftSource.cmake @@ -326,6 +326,10 @@ function(_add_target_variant_swift_compile_flags list(APPEND result "-D" "SWIFT_ENABLE_EXPERIMENTAL_OBSERVATION") endif() + if(SWIFT_ENABLE_EXPERIMENTAL_POINTER_BOUNDS) + list(APPEND result "-D" "SWIFT_ENABLE_EXPERIMENTAL_POINTER_BOUNDS") + endif() + if(SWIFT_ENABLE_SYNCHRONIZATION) list(APPEND result "-D" "SWIFT_ENABLE_SYNCHRONIZATION") endif() diff --git a/stdlib/public/core/CMakeLists.txt b/stdlib/public/core/CMakeLists.txt index 56948d72dcde3..4c59125f7dcfc 100644 --- a/stdlib/public/core/CMakeLists.txt +++ b/stdlib/public/core/CMakeLists.txt @@ -261,6 +261,11 @@ if(SWIFT_STDLIB_ENABLE_VECTOR_TYPES) list(APPEND SWIFTLIB_EMBEDDED_GYB_SOURCES SIMDConcreteOperations.swift.gyb SIMDVectorTypes.swift.gyb) endif() +if (SWIFT_ENABLE_EXPERIMENTAL_POINTER_BOUNDS) + list(APPEND SWIFTLIB_SOURCES PointerBounds.swift) + list(APPEND SWIFTLIB_EMBEDDED_SOURCES PointerBounds.swift) +endif() + # Freestanding and Linux/Android builds both have failures to resolve. if(NOT BOOTSTRAPPING_MODE STREQUAL "OFF" AND NOT SWIFT_FREESTANDING_FLAVOR AND NOT SWIFT_HOST_VARIANT_SDK STREQUAL "LINUX" AND NOT SWIFT_HOST_VARIANT_SDK STREQUAL "ANDROID") list(APPEND SWIFTLIB_SOURCES ObjectIdentifier+DebugDescription.swift) diff --git a/stdlib/public/core/GroupInfo.json b/stdlib/public/core/GroupInfo.json index dd218333d84e5..2f4c20d6533ab 100644 --- a/stdlib/public/core/GroupInfo.json +++ b/stdlib/public/core/GroupInfo.json @@ -190,6 +190,7 @@ ], "Pointer": [ "Pointer.swift", + "PointerBounds.swift", "TemporaryAllocation.swift", "UnsafePointer.swift", "UnsafeRawPointer.swift", diff --git a/stdlib/public/core/PointerBounds.swift b/stdlib/public/core/PointerBounds.swift new file mode 100644 index 0000000000000..6528a079f55b6 --- /dev/null +++ b/stdlib/public/core/PointerBounds.swift @@ -0,0 +1,44 @@ +/// Different ways to annotate pointer parameters using the `@PointerBounds` macro. +/// All indices into parameter lists start at 1. Indices __must__ be integer literals, and strings +/// __must__ be string literals, because their contents are parsed by the `@PointerBounds` macro. +/// Only 1 instance of `countedBy`, `sizedBy` or `endedBy` can refer to each pointer index, however +/// `nonescaping` is orthogonal to the rest and can (and should) overlap with other annotations. +public enum PointerParam { + /// Corresponds to the C `__counted_by(count)` attribute. + /// Parameter pointer: index of pointer in function parameter list. Must be of type + /// `Unsafe[Mutable]Pointer[?]`, i.e. not an `UnsafeRawPointer`. + /// Parameter count: string containing valid Swift syntax containing the number of elements in + /// the buffer. + case countedBy(pointer: Int, count: String) + /// Corresponds to the C `__sized_by(size)` attribute. + /// Parameter pointer: index of pointer in function parameter list. Must be of type + /// `Unsafe[Mutable]RawPointer[?]`, i.e. not an `UnsafePointer`. + /// Parameter count: string containing valid Swift syntax containing the size of the buffer, + /// in bytes. + case sizedBy(pointer: Int, size: String) + /// Corresponds to the C `__ended_by(end)` attribute. + /// Parameter start: index of pointer in function parameter list. + /// Parameter end: index of pointer in function parameter list, pointing one past the end of + /// the same buffer as `start`. + case endedBy(start: Int, end: Int) + /// Corresponds to the C `noescape` attribute. Allows generated wrapper to use `Span`-types + /// instead of `UnsafeBuffer`-types, because it is known that the function doesn't capture the + /// object past the lifetime of the function. + /// Parameter pointer: index of pointer in function parameter list. + case nonescaping(pointer: Int) +} + +/// Generates a bounds safe wrapper for function with Unsafe[Mutable][Raw]Pointer[?] arguments. +/// Intended to be automatically attached to function declarations imported by ClangImporter. +/// The wrapper function will replace Unsafe[Mutable][Raw]Pointer[?] parameters with +/// [Mutable][Raw]Span[?] or Unsafe[Mutable][Raw]BufferPointer[?] if they have bounds information +/// attached. Where possible "count" parameters will be elided from the wrapper signature, instead +/// fetching the count from the buffer pointer. In these cases the bounds check is also skipped. +/// +/// Currently not supported: return pointers, nested pointers, pointee "count" parameters, endedBy. +/// +/// Parameter paramInfo: information about how the function uses the pointer passed to it. The +/// safety of the generated wrapper function depends on this info being extensive and accurate. +@attached(peer, names: overloaded) +public macro PointerBounds(_ paramInfo: PointerParam...) = + #externalMacro(module: "SwiftMacros", type: "PointerBoundsMacro") diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index d6e8a2151d47d..02b7ae89379cc 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -202,6 +202,7 @@ normalize_boolean_spelling(SWIFT_ENABLE_EXPERIMENTAL_CONCURRENCY) normalize_boolean_spelling(SWIFT_ENABLE_EXPERIMENTAL_DISTRIBUTED) normalize_boolean_spelling(SWIFT_ENABLE_EXPERIMENTAL_STRING_PROCESSING) normalize_boolean_spelling(SWIFT_ENABLE_EXPERIMENTAL_OBSERVATION) +normalize_boolean_spelling(SWIFT_ENABLE_EXPERIMENTAL_POINTER_BOUNDS) normalize_boolean_spelling(SWIFT_ENABLE_MACCATALYST) normalize_boolean_spelling(SWIFT_RUN_TESTS_WITH_HOST_COMPILER) normalize_boolean_spelling(SWIFT_RUNTIME_ENABLE_LEAK_CHECKER) @@ -458,6 +459,10 @@ foreach(SDK ${SWIFT_SDKS}) list(APPEND LIT_ARGS "--param" "string_processing") endif() + if(SWIFT_ENABLE_EXPERIMENTAL_POINTER_BOUNDS) + list(APPEND LIT_ARGS "--param" "pointer_bounds") + endif() + if(SWIFT_ENABLE_BACKTRACING) list(APPEND LIT_ARGS "--param" "backtracing") endif() diff --git a/test/Macros/PointerBounds/CountedBy/CountExpr.swift b/test/Macros/PointerBounds/CountedBy/CountExpr.swift new file mode 100644 index 0000000000000..cc14ba48d398d --- /dev/null +++ b/test/Macros/PointerBounds/CountedBy/CountExpr.swift @@ -0,0 +1,18 @@ +// REQUIRES: swift_swift_parser +// REQUIRES: pointer_bounds + +// RUN: %target-swift-frontend %s -swift-version 5 -module-name main -disable-availability-checking -typecheck -plugin-path %swift-plugin-dir -dump-macro-expansions 2>&1 | %FileCheck --match-full-lines %s + +@PointerBounds(.countedBy(pointer: 1, count: "size * count")) +func myFunc(_ ptr: UnsafePointer, _ size: CInt, _ count: CInt) { +} + +// CHECK: @_alwaysEmitIntoClient +// CHECK-NEXT: func myFunc(_ ptr: UnsafeBufferPointer, _ size: CInt, _ count: CInt) { +// CHECK-NEXT: let _ptrCount: some BinaryInteger = size * count +// CHECK-NEXT: if ptr.count < _ptrCount || _ptrCount < 0 { +// CHECK-NEXT: fatalError("bounds check failure when calling unsafe function") +// CHECK-NEXT: } +// CHECK-NEXT: return myFunc(ptr.baseAddress!, size, count) +// CHECK-NEXT: } + diff --git a/test/Macros/PointerBounds/CountedBy/MultipleParams.swift b/test/Macros/PointerBounds/CountedBy/MultipleParams.swift new file mode 100644 index 0000000000000..9f7bc53e8e98b --- /dev/null +++ b/test/Macros/PointerBounds/CountedBy/MultipleParams.swift @@ -0,0 +1,13 @@ +// REQUIRES: swift_swift_parser +// REQUIRES: pointer_bounds + +// RUN: %target-swift-frontend %s -swift-version 5 -module-name main -disable-availability-checking -typecheck -plugin-path %swift-plugin-dir -dump-macro-expansions 2>&1 | %FileCheck --match-full-lines %s + +@PointerBounds(.countedBy(pointer: 1, count: "len"), .countedBy(pointer: 3, count: "len2")) +func myFunc(_ ptr: UnsafePointer, _ len: CInt, _ ptr2: UnsafePointer, _ len2: CInt) { +} + +// CHECK: @_alwaysEmitIntoClient +// CHECK-NEXT: func myFunc(_ ptr: UnsafeBufferPointer, _ ptr2: UnsafeBufferPointer) { +// CHECK-NEXT: return myFunc(ptr.baseAddress!, CInt(exactly: ptr.count)!, ptr2.baseAddress!, CInt(exactly: ptr2.count)!) +// CHECK-NEXT: } diff --git a/test/Macros/PointerBounds/CountedBy/Mutable.swift b/test/Macros/PointerBounds/CountedBy/Mutable.swift new file mode 100644 index 0000000000000..380b4836eb1af --- /dev/null +++ b/test/Macros/PointerBounds/CountedBy/Mutable.swift @@ -0,0 +1,14 @@ +// REQUIRES: swift_swift_parser +// REQUIRES: pointer_bounds + +// RUN: %target-swift-frontend %s -swift-version 5 -module-name main -disable-availability-checking -typecheck -plugin-path %swift-plugin-dir -dump-macro-expansions 2>&1 | %FileCheck --match-full-lines %s + +@PointerBounds(.countedBy(pointer: 1, count: "len")) +func myFunc(_ ptr: UnsafeMutablePointer, _ len: CInt) { +} + +// CHECK: @_alwaysEmitIntoClient +// CHECK-NEXT: func myFunc(_ ptr: UnsafeMutableBufferPointer) { +// CHECK-NEXT: return myFunc(ptr.baseAddress!, CInt(exactly: ptr.count)!) +// CHECK-NEXT: } + diff --git a/test/Macros/PointerBounds/CountedBy/MutableSpan.swift b/test/Macros/PointerBounds/CountedBy/MutableSpan.swift new file mode 100644 index 0000000000000..e874fbf3aa463 --- /dev/null +++ b/test/Macros/PointerBounds/CountedBy/MutableSpan.swift @@ -0,0 +1,16 @@ +// REQUIRES: swift_swift_parser +// REQUIRES: pointer_bounds +// XFAIL: OS=windows-msvc +// RUN: %target-swift-frontend %s -swift-version 5 -module-name main -disable-availability-checking -typecheck -plugin-path %swift-plugin-dir -dump-macro-expansions 2>&1 | %FileCheck --match-full-lines %s + +@PointerBounds(.countedBy(pointer: 1, count: "len"), .nonescaping(pointer: 1)) +func myFunc(_ ptr: UnsafeMutablePointer, _ len: CInt) { +} + +// CHECK: @_alwaysEmitIntoClient +// CHECK-NEXT: func myFunc(_ ptr: MutableSpan) { +// CHECK-NEXT: return ptr.withUnsafeBufferPointer { _ptrPtr in +// CHECK-NEXT: return myFunc(_ptrPtr.baseAddress!, CInt(exactly: ptr.count)!) +// CHECK-NEXT: } +// CHECK-NEXT: } + diff --git a/test/Macros/PointerBounds/CountedBy/Nullable.swift b/test/Macros/PointerBounds/CountedBy/Nullable.swift new file mode 100644 index 0000000000000..31f33ff26fddd --- /dev/null +++ b/test/Macros/PointerBounds/CountedBy/Nullable.swift @@ -0,0 +1,13 @@ +// REQUIRES: swift_swift_parser +// REQUIRES: pointer_bounds + +// RUN: %target-swift-frontend %s -swift-version 5 -module-name main -disable-availability-checking -typecheck -plugin-path %swift-plugin-dir -dump-macro-expansions 2>&1 | %FileCheck --match-full-lines %s + +@PointerBounds(.countedBy(pointer: 1, count: "len")) +func myFunc(_ ptr: UnsafePointer?, _ len: CInt) { +} + +// CHECK: @_alwaysEmitIntoClient +// CHECK-NEXT: func myFunc(_ ptr: UnsafeBufferPointer?) { +// CHECK-NEXT: return myFunc(ptr?.baseAddress, CInt(exactly: ptr?.count ?? 0)!) +// CHECK-NEXT: } diff --git a/test/Macros/PointerBounds/CountedBy/QualifiedTypes.swift b/test/Macros/PointerBounds/CountedBy/QualifiedTypes.swift new file mode 100644 index 0000000000000..638f8d1229189 --- /dev/null +++ b/test/Macros/PointerBounds/CountedBy/QualifiedTypes.swift @@ -0,0 +1,24 @@ +// REQUIRES: swift_swift_parser +// REQUIRES: pointer_bounds + +// RUN: %target-swift-frontend %s -swift-version 5 -module-name main -disable-availability-checking -typecheck -plugin-path %swift-plugin-dir -dump-macro-expansions 2>&1 | %FileCheck --match-full-lines %s + +@PointerBounds(.countedBy(pointer: 1, count: "len")) +func foo(_ ptr: Swift.UnsafePointer, _ len: Swift.Int) -> Swift.Void { +} + +@PointerBounds(.countedBy(pointer: 1, count: "len")) +func bar(_ ptr: Swift.UnsafePointer, _ len: Swift.Int) -> () { +} + +// CHECK: @_alwaysEmitIntoClient +// CHECK-NEXT: func foo(_ ptr: Swift.UnsafeBufferPointer) -> Swift.Void { +// CHECK-NEXT: return foo(ptr.baseAddress!, ptr.count) +// CHECK-NEXT: } + +// CHECK: @_alwaysEmitIntoClient +// CHECK-NEXT: func bar(_ ptr: Swift.UnsafeBufferPointer) -> () { +// CHECK-NEXT: return bar(ptr.baseAddress!, ptr.count) +// CHECK-NEXT: } + + diff --git a/test/Macros/PointerBounds/CountedBy/Return.swift b/test/Macros/PointerBounds/CountedBy/Return.swift new file mode 100644 index 0000000000000..c4612de8cd081 --- /dev/null +++ b/test/Macros/PointerBounds/CountedBy/Return.swift @@ -0,0 +1,13 @@ +// REQUIRES: swift_swift_parser +// REQUIRES: pointer_bounds + +// RUN: %target-swift-frontend %s -swift-version 5 -module-name main -disable-availability-checking -typecheck -plugin-path %swift-plugin-dir -dump-macro-expansions 2>&1 | %FileCheck --match-full-lines %s + +@PointerBounds(.countedBy(pointer: 1, count: "len")) +func myFunc(_ ptr: UnsafePointer, _ len: CInt) -> CInt { +} + +// CHECK: @_alwaysEmitIntoClient +// CHECK-NEXT: func myFunc(_ ptr: UnsafeBufferPointer) -> CInt { +// CHECK-NEXT: return myFunc(ptr.baseAddress!, CInt(exactly: ptr.count)!) +// CHECK-NEXT: } diff --git a/test/Macros/PointerBounds/CountedBy/SharedCount.swift b/test/Macros/PointerBounds/CountedBy/SharedCount.swift new file mode 100644 index 0000000000000..5e90c77b0188b --- /dev/null +++ b/test/Macros/PointerBounds/CountedBy/SharedCount.swift @@ -0,0 +1,21 @@ +// REQUIRES: swift_swift_parser +// REQUIRES: pointer_bounds + +// RUN: %target-swift-frontend %s -swift-version 5 -module-name main -disable-availability-checking -typecheck -plugin-path %swift-plugin-dir -dump-macro-expansions 2>&1 | %FileCheck --match-full-lines %s + +@PointerBounds(.countedBy(pointer: 1, count: "len"), .countedBy(pointer: 2, count: "len")) +func myFunc(_ ptr: UnsafePointer, _ ptr2: UnsafePointer, _ len: CInt) { +} + +// CHECK: @_alwaysEmitIntoClient +// CHECK-NEXT: func myFunc(_ ptr: UnsafeBufferPointer, _ ptr2: UnsafeBufferPointer, _ len: CInt) { +// CHECK-NEXT: let _ptrCount: some BinaryInteger = len +// CHECK-NEXT: if ptr.count < _ptrCount || _ptrCount < 0 { +// CHECK-NEXT: fatalError("bounds check failure when calling unsafe function") +// CHECK-NEXT: } +// CHECK-NEXT: let _ptr2Count: some BinaryInteger = len +// CHECK-NEXT: if ptr2.count < _ptr2Count || _ptr2Count < 0 { +// CHECK-NEXT: fatalError("bounds check failure when calling unsafe function") +// CHECK-NEXT: } +// CHECK-NEXT: return myFunc(ptr.baseAddress!, ptr2.baseAddress!, len) +// CHECK-NEXT: } diff --git a/test/Macros/PointerBounds/CountedBy/SimpleCount.swift b/test/Macros/PointerBounds/CountedBy/SimpleCount.swift new file mode 100644 index 0000000000000..5c7794c941b70 --- /dev/null +++ b/test/Macros/PointerBounds/CountedBy/SimpleCount.swift @@ -0,0 +1,13 @@ +// REQUIRES: swift_swift_parser +// REQUIRES: pointer_bounds + +// RUN: %target-swift-frontend %s -swift-version 5 -module-name main -disable-availability-checking -typecheck -plugin-path %swift-plugin-dir -dump-macro-expansions 2>&1 | %FileCheck --match-full-lines %s + +@PointerBounds(.countedBy(pointer: 1, count: "len")) +func myFunc(_ ptr: UnsafePointer, _ len: CInt) { +} + +// CHECK: @_alwaysEmitIntoClient +// CHECK-NEXT: func myFunc(_ ptr: UnsafeBufferPointer) { +// CHECK-NEXT: return myFunc(ptr.baseAddress!, CInt(exactly: ptr.count)!) +// CHECK-NEXT: } diff --git a/test/Macros/PointerBounds/CountedBy/SimpleSpan.swift b/test/Macros/PointerBounds/CountedBy/SimpleSpan.swift new file mode 100644 index 0000000000000..d291c25d0e54d --- /dev/null +++ b/test/Macros/PointerBounds/CountedBy/SimpleSpan.swift @@ -0,0 +1,15 @@ +// REQUIRES: swift_swift_parser +// REQUIRES: pointer_bounds + +// RUN: %target-swift-frontend %s -swift-version 5 -module-name main -disable-availability-checking -typecheck -plugin-path %swift-plugin-dir -dump-macro-expansions 2>&1 | %FileCheck --match-full-lines %s + +@PointerBounds(.countedBy(pointer: 1, count: "len"), .nonescaping(pointer: 1)) +func myFunc(_ ptr: UnsafePointer, _ len: CInt) { +} + +// CHECK: @_alwaysEmitIntoClient +// CHECK-NEXT: func myFunc(_ ptr: Span) { +// CHECK-NEXT: return ptr.withUnsafeBufferPointer { _ptrPtr in +// CHECK-NEXT: return myFunc(_ptrPtr.baseAddress!, CInt(exactly: ptr.count)!) +// CHECK-NEXT: } +// CHECK-NEXT: } diff --git a/test/Macros/PointerBounds/CountedBy/SimpleSpanWithReturn.swift b/test/Macros/PointerBounds/CountedBy/SimpleSpanWithReturn.swift new file mode 100644 index 0000000000000..26ccc76f05a41 --- /dev/null +++ b/test/Macros/PointerBounds/CountedBy/SimpleSpanWithReturn.swift @@ -0,0 +1,15 @@ +// REQUIRES: swift_swift_parser +// REQUIRES: pointer_bounds + +// RUN: %target-swift-frontend %s -swift-version 5 -module-name main -disable-availability-checking -typecheck -plugin-path %swift-plugin-dir -dump-macro-expansions 2>&1 | %FileCheck --match-full-lines %s + +@PointerBounds(.countedBy(pointer: 1, count: "len"), .nonescaping(pointer: 1)) +func myFunc(_ ptr: UnsafePointer, _ len: CInt) -> CInt { +} + +// CHECK: @_alwaysEmitIntoClient +// CHECK-NEXT: func myFunc(_ ptr: Span) -> CInt { +// CHECK-NEXT: return ptr.withUnsafeBufferPointer { _ptrPtr in +// CHECK-NEXT: return myFunc(_ptrPtr.baseAddress!, CInt(exactly: ptr.count)!) +// CHECK-NEXT: } +// CHECK-NEXT: } diff --git a/test/Macros/PointerBounds/CountedBy/Unwrapped.swift b/test/Macros/PointerBounds/CountedBy/Unwrapped.swift new file mode 100644 index 0000000000000..c25cc2ee0ea5c --- /dev/null +++ b/test/Macros/PointerBounds/CountedBy/Unwrapped.swift @@ -0,0 +1,14 @@ +// REQUIRES: swift_swift_parser +// REQUIRES: pointer_bounds + +// RUN: %target-swift-frontend %s -swift-version 5 -module-name main -disable-availability-checking -typecheck -plugin-path %swift-plugin-dir -dump-macro-expansions 2>&1 | %FileCheck --match-full-lines %s + +@PointerBounds(.countedBy(pointer: 1, count: "len")) +func myFunc(_ ptr: UnsafePointer!, _ len: CInt) { +} + +// CHECK: @_alwaysEmitIntoClient +// CHECK-NEXT: func myFunc(_ ptr: UnsafeBufferPointer) { +// CHECK-NEXT: return myFunc(ptr.baseAddress!, CInt(exactly: ptr.count)!) +// CHECK-NEXT: } + diff --git a/test/Macros/PointerBounds/MacroErrors/ArrayType.swift b/test/Macros/PointerBounds/MacroErrors/ArrayType.swift new file mode 100644 index 0000000000000..f6429cb8033e0 --- /dev/null +++ b/test/Macros/PointerBounds/MacroErrors/ArrayType.swift @@ -0,0 +1,9 @@ +// REQUIRES: swift_swift_parser +// REQUIRES: pointer_bounds + +// RUN: %target-typecheck-verify-swift %s -swift-version 5 -module-name main -disable-availability-checking -typecheck -plugin-path %swift-plugin-dir -verify + +@PointerBounds(.countedBy(pointer: 1, count: "len")) +// expected-error@+1{{expected pointer type, got [CInt] with kind arrayType}} +func myFunc(_ ptr: [CInt], _ len: String) { +} diff --git a/test/Macros/PointerBounds/MacroErrors/DynamicCountExpr.swift b/test/Macros/PointerBounds/MacroErrors/DynamicCountExpr.swift new file mode 100644 index 0000000000000..b70ca9fd06e37 --- /dev/null +++ b/test/Macros/PointerBounds/MacroErrors/DynamicCountExpr.swift @@ -0,0 +1,10 @@ +// REQUIRES: swift_swift_parser +// REQUIRES: pointer_bounds + +// RUN: %target-typecheck-verify-swift %s -swift-version 5 -module-name main -disable-availability-checking -typecheck -plugin-path %swift-plugin-dir -verify + +let countString = "len" +// expected-error@+1{{expected string literal for 'count' parameter, got countString}} +@PointerBounds(.countedBy(pointer: 1, count: countString)) +func myFunc(_ ptr: UnsafePointer, _ len: String) { +} diff --git a/test/Macros/PointerBounds/MacroErrors/DynamicEnum.swift b/test/Macros/PointerBounds/MacroErrors/DynamicEnum.swift new file mode 100644 index 0000000000000..04ab7b39dffcf --- /dev/null +++ b/test/Macros/PointerBounds/MacroErrors/DynamicEnum.swift @@ -0,0 +1,10 @@ +// REQUIRES: swift_swift_parser +// REQUIRES: pointer_bounds + +// RUN: %target-typecheck-verify-swift %s -swift-version 5 -module-name main -disable-availability-checking -typecheck -plugin-path %swift-plugin-dir -verify + +let countedBy = PointerParam.countedBy(pointer: 1, count: "len") +// expected-error@+1{{expected PointerParam enum literal as argument, got 'countedBy'}} +@PointerBounds(countedBy) +func myFunc(_ ptr: UnsafePointer, _ len: String) { +} diff --git a/test/Macros/PointerBounds/MacroErrors/DynamicPointerIndex.swift b/test/Macros/PointerBounds/MacroErrors/DynamicPointerIndex.swift new file mode 100644 index 0000000000000..63430a0420707 --- /dev/null +++ b/test/Macros/PointerBounds/MacroErrors/DynamicPointerIndex.swift @@ -0,0 +1,10 @@ +// REQUIRES: swift_swift_parser +// REQUIRES: pointer_bounds + +// RUN: %target-typecheck-verify-swift %s -swift-version 5 -module-name main -disable-availability-checking -typecheck -plugin-path %swift-plugin-dir -verify + +let pointerIndex = 1 +// expected-error@+1{{expected integer literal, got 'pointerIndex'}} +@PointerBounds(.countedBy(pointer: pointerIndex, count: "len")) +func myFunc(_ ptr: UnsafePointer, _ len: String) { +} diff --git a/test/Macros/PointerBounds/MacroErrors/InvalidCount.swift b/test/Macros/PointerBounds/MacroErrors/InvalidCount.swift new file mode 100644 index 0000000000000..6c7e8b1683861 --- /dev/null +++ b/test/Macros/PointerBounds/MacroErrors/InvalidCount.swift @@ -0,0 +1,9 @@ +// REQUIRES: swift_swift_parser +// REQUIRES: pointer_bounds + +// RUN: %target-typecheck-verify-swift %s -swift-version 5 -module-name main -disable-availability-checking -typecheck -plugin-path %swift-plugin-dir -verify + +// expected-error@+1{{no parameter with name 'foo' in '_ ptr: UnsafePointer, _ len: CInt'}} +@PointerBounds(.countedBy(pointer: 1, count: "foo")) +func myFunc(_ ptr: UnsafePointer, _ len: CInt) { +} diff --git a/test/Macros/PointerBounds/MacroErrors/PointerIndexOutOfBounds.swift b/test/Macros/PointerBounds/MacroErrors/PointerIndexOutOfBounds.swift new file mode 100644 index 0000000000000..2db45850e5928 --- /dev/null +++ b/test/Macros/PointerBounds/MacroErrors/PointerIndexOutOfBounds.swift @@ -0,0 +1,20 @@ +// REQUIRES: swift_swift_parser +// REQUIRES: pointer_bounds + +// RUN: %target-typecheck-verify-swift %s -swift-version 5 -module-name main -disable-availability-checking -typecheck -plugin-path %swift-plugin-dir -verify + +// expected-error@+1{{pointer index out of bounds}} +@PointerBounds(.countedBy(pointer: 3, count: "len")) +// expected-note@+1{{function myFunc has parameter indices 1..2}} +func myFunc(_ ptr: UnsafePointer, _ len: CInt) { +} +// expected-error@+1{{pointer index out of bounds}} +@PointerBounds(.countedBy(pointer: 0, count: "len")) +// expected-note@+1{{function myFunc2 has parameter indices 1..2}} +func myFunc2(_ ptr: UnsafePointer, _ len: CInt) { +} +// expected-error@+1{{pointer index out of bounds}} +@PointerBounds(.countedBy(pointer: 0, count: "1")) +// expected-note@+1{{function myFunc3 has no parameters}} +func myFunc3() { +} diff --git a/test/Macros/PointerBounds/MacroErrors/SamePointer.swift b/test/Macros/PointerBounds/MacroErrors/SamePointer.swift new file mode 100644 index 0000000000000..198af2f890434 --- /dev/null +++ b/test/Macros/PointerBounds/MacroErrors/SamePointer.swift @@ -0,0 +1,9 @@ +// REQUIRES: swift_swift_parser +// REQUIRES: pointer_bounds + +// RUN: %target-typecheck-verify-swift %s -swift-version 5 -module-name main -disable-availability-checking -typecheck -plugin-path %swift-plugin-dir + +// expected-error@+1{{multiple PointerParams referring to parameter with index 1: .countedBy(pointer: 1, count: "dummy", nonescaping: false) and .countedBy(pointer: 1, count: "len", nonescaping: false)}} +@PointerBounds(.countedBy(pointer: 1, count: "len"), .countedBy(pointer: 1, count: "dummy")) +func myFunc(_ ptr: UnsafePointer, _ len: CInt, _ dummy: CInt) { +} diff --git a/test/Macros/PointerBounds/MacroErrors/UnexpectedCountExpr.swift b/test/Macros/PointerBounds/MacroErrors/UnexpectedCountExpr.swift new file mode 100644 index 0000000000000..f20f55e1034eb --- /dev/null +++ b/test/Macros/PointerBounds/MacroErrors/UnexpectedCountExpr.swift @@ -0,0 +1,9 @@ +// REQUIRES: swift_swift_parser +// REQUIRES: pointer_bounds + +// RUN: %target-typecheck-verify-swift %s -swift-version 5 -module-name main -disable-availability-checking -typecheck -plugin-path %swift-plugin-dir -verify + +// expected-error@+1{{cannot convert value of type 'Int' to expected argument type 'String'}} +@PointerBounds(.countedBy(pointer: 1, count: 2)) +func myFunc(_ ptr: UnsafePointer, _ len: String) { +} diff --git a/test/Macros/PointerBounds/MacroErrors/UnexpectedCountType.swift b/test/Macros/PointerBounds/MacroErrors/UnexpectedCountType.swift new file mode 100644 index 0000000000000..bee40d6023fd1 --- /dev/null +++ b/test/Macros/PointerBounds/MacroErrors/UnexpectedCountType.swift @@ -0,0 +1,18 @@ +// REQUIRES: swift_swift_parser +// REQUIRES: pointer_bounds + +// XFAIL: * + +// RUN: not %target-swift-frontend %s -swift-version 5 -module-name main -disable-availability-checking -typecheck -plugin-path %swift-plugin-dir -dump-macro-expansions 2>&1 | %FileCheck --match-full-lines %s +// RUN: %target-typecheck-verify-swift %s -swift-version 5 -module-name main -disable-availability-checking -typecheck -plugin-path %swift-plugin-dir -verify + +@PointerBounds(.countedBy(pointer: 1, count: "len")) +func myFunc(_ ptr: UnsafePointer, _ len: String) { +} + +// CHECK: @_alwaysEmitIntoClient +// CHECK-NEXT: func myFunc(_ ptr: UnsafeBufferPointer) { +// CHECK-NEXT: myFunc(ptr.baseAddress!, String(exactly: ptr.count)!) +// CHECK-NEXT: } + +// expected-error@PointerBounds:2{{no exact matches in call to initializer}} diff --git a/test/Macros/PointerBounds/MacroErrors/UnexpectedPointerType.swift b/test/Macros/PointerBounds/MacroErrors/UnexpectedPointerType.swift new file mode 100644 index 0000000000000..a70af64b0807e --- /dev/null +++ b/test/Macros/PointerBounds/MacroErrors/UnexpectedPointerType.swift @@ -0,0 +1,13 @@ +// REQUIRES: swift_swift_parser +// REQUIRES: pointer_bounds + +// RUN: %target-typecheck-verify-swift -swift-version 5 -module-name main -disable-availability-checking -typecheck -plugin-path %swift-plugin-dir -verify + +// expected-error@+2{{expected Unsafe[Mutable][Raw]Pointer type for type CInt - first type token is 'CInt'}} +@PointerBounds(.countedBy(pointer: 1, count: "len")) +func myFunc(_ ptr: CInt, _ len: CInt) { +} +// expected-error@+2{{expected Unsafe[Mutable][Raw]Pointer type for type UnsafeBufferPointer - first type token is 'UnsafeBufferPointer'}} +@PointerBounds(.countedBy(pointer: 1, count: "len")) +func myFunc2(_ ptr: UnsafeBufferPointer, _ len: CInt) { +} diff --git a/test/Macros/PointerBounds/SizedBy/MultipleParams.swift b/test/Macros/PointerBounds/SizedBy/MultipleParams.swift new file mode 100644 index 0000000000000..951014bfa9332 --- /dev/null +++ b/test/Macros/PointerBounds/SizedBy/MultipleParams.swift @@ -0,0 +1,13 @@ +// REQUIRES: swift_swift_parser +// REQUIRES: pointer_bounds + +// RUN: %target-swift-frontend %s -swift-version 5 -module-name main -disable-availability-checking -typecheck -plugin-path %swift-plugin-dir -dump-macro-expansions 2>&1 | %FileCheck --match-full-lines %s + +@PointerBounds(.sizedBy(pointer: 1, size: "size"), .sizedBy(pointer: 3, size: "size2")) +func myFunc(_ ptr: UnsafeRawPointer, _ size: CInt, _ ptr2: UnsafeRawPointer, _ size2: CInt) { +} + +// CHECK: @_alwaysEmitIntoClient +// CHECK-NEXT: func myFunc(_ ptr: UnsafeRawBufferPointer, _ ptr2: UnsafeRawBufferPointer) { +// CHECK-NEXT: return myFunc(ptr.baseAddress!, CInt(exactly: ptr.count)!, ptr2.baseAddress!, CInt(exactly: ptr2.count)!) +// CHECK-NEXT: } diff --git a/test/Macros/PointerBounds/SizedBy/Mutable.swift b/test/Macros/PointerBounds/SizedBy/Mutable.swift new file mode 100644 index 0000000000000..5dae9d0128866 --- /dev/null +++ b/test/Macros/PointerBounds/SizedBy/Mutable.swift @@ -0,0 +1,13 @@ +// REQUIRES: swift_swift_parser +// REQUIRES: pointer_bounds + +// RUN: %target-swift-frontend %s -swift-version 5 -module-name main -disable-availability-checking -typecheck -plugin-path %swift-plugin-dir -dump-macro-expansions 2>&1 | %FileCheck --match-full-lines %s + +@PointerBounds(.sizedBy(pointer: 1, size: "size")) +func myFunc(_ ptr: UnsafeMutableRawPointer, _ size: CInt) { +} + +// CHECK: @_alwaysEmitIntoClient +// CHECK-NEXT: func myFunc(_ ptr: UnsafeMutableRawBufferPointer) { +// CHECK-NEXT: return myFunc(ptr.baseAddress!, CInt(exactly: ptr.count)!) +// CHECK-NEXT: } diff --git a/test/Macros/PointerBounds/SizedBy/MutableRawSpan.swift b/test/Macros/PointerBounds/SizedBy/MutableRawSpan.swift new file mode 100644 index 0000000000000..1d216e02d62ba --- /dev/null +++ b/test/Macros/PointerBounds/SizedBy/MutableRawSpan.swift @@ -0,0 +1,15 @@ +// REQUIRES: swift_swift_parser +// REQUIRES: pointer_bounds +// XFAIL: OS=windows-msvc +// RUN: %target-swift-frontend %s -swift-version 5 -module-name main -disable-availability-checking -typecheck -plugin-path %swift-plugin-dir -dump-macro-expansions 2>&1 | %FileCheck --match-full-lines %s + +@PointerBounds(.sizedBy(pointer: 1, size: "size"), .nonescaping(pointer: 1)) +func myFunc(_ ptr: UnsafeMutableRawPointer, _ size: CInt) { +} + +// CHECK: @_alwaysEmitIntoClient +// CHECK-NEXT: func myFunc(_ ptr: MutableRawSpan) { +// CHECK-NEXT: return ptr.withUnsafeBytes { _ptrPtr in +// CHECK-NEXT: return myFunc(_ptrPtr.baseAddress!, CInt(exactly: ptr.byteCount)!) +// CHECK-NEXT: } +// CHECK-NEXT: } diff --git a/test/Macros/PointerBounds/SizedBy/Nullable.swift b/test/Macros/PointerBounds/SizedBy/Nullable.swift new file mode 100644 index 0000000000000..1b78dd9d96b22 --- /dev/null +++ b/test/Macros/PointerBounds/SizedBy/Nullable.swift @@ -0,0 +1,13 @@ +// REQUIRES: swift_swift_parser +// REQUIRES: pointer_bounds + +// RUN: %target-swift-frontend %s -swift-version 5 -module-name main -disable-availability-checking -typecheck -plugin-path %swift-plugin-dir -dump-macro-expansions 2>&1 | %FileCheck --match-full-lines %s + +@PointerBounds(.sizedBy(pointer: 1, size: "size")) +func myFunc(_ ptr: UnsafeRawPointer?, _ size: CInt) { +} + +// CHECK: @_alwaysEmitIntoClient +// CHECK-NEXT: func myFunc(_ ptr: UnsafeRawBufferPointer?) { +// CHECK-NEXT: return myFunc(ptr?.baseAddress, CInt(exactly: ptr?.count ?? 0)!) +// CHECK-NEXT: } diff --git a/test/Macros/PointerBounds/SizedBy/Return.swift b/test/Macros/PointerBounds/SizedBy/Return.swift new file mode 100644 index 0000000000000..2f12e191de42c --- /dev/null +++ b/test/Macros/PointerBounds/SizedBy/Return.swift @@ -0,0 +1,13 @@ +// REQUIRES: swift_swift_parser +// REQUIRES: pointer_bounds + +// RUN: %target-swift-frontend %s -swift-version 5 -module-name main -disable-availability-checking -typecheck -plugin-path %swift-plugin-dir -dump-macro-expansions 2>&1 | %FileCheck --match-full-lines %s + +@PointerBounds(.sizedBy(pointer: 1, size: "size")) +func myFunc(_ ptr: UnsafeRawPointer, _ size: CInt) -> CInt { +} + +// CHECK: @_alwaysEmitIntoClient +// CHECK-NEXT: func myFunc(_ ptr: UnsafeRawBufferPointer) -> CInt { +// CHECK-NEXT: return myFunc(ptr.baseAddress!, CInt(exactly: ptr.count)!) +// CHECK-NEXT: } diff --git a/test/Macros/PointerBounds/SizedBy/SharedCount.swift b/test/Macros/PointerBounds/SizedBy/SharedCount.swift new file mode 100644 index 0000000000000..8a56736116b3c --- /dev/null +++ b/test/Macros/PointerBounds/SizedBy/SharedCount.swift @@ -0,0 +1,21 @@ +// REQUIRES: swift_swift_parser +// REQUIRES: pointer_bounds + +// RUN: %target-swift-frontend %s -swift-version 5 -module-name main -disable-availability-checking -typecheck -plugin-path %swift-plugin-dir -dump-macro-expansions 2>&1 | %FileCheck --match-full-lines %s + +@PointerBounds(.sizedBy(pointer: 1, size: "size"), .sizedBy(pointer: 2, size: "size")) +func myFunc(_ ptr: UnsafeRawPointer, _ ptr2: UnsafeRawPointer, _ size: CInt) { +} + +// CHECK: @_alwaysEmitIntoClient +// CHECK-NEXT: func myFunc(_ ptr: UnsafeRawBufferPointer, _ ptr2: UnsafeRawBufferPointer, _ size: CInt) { +// CHECK-NEXT: let _ptrCount: some BinaryInteger = size +// CHECK-NEXT: if ptr.count < _ptrCount || _ptrCount < 0 { +// CHECK-NEXT: fatalError("bounds check failure when calling unsafe function") +// CHECK-NEXT: } +// CHECK-NEXT: let _ptr2Count: some BinaryInteger = size +// CHECK-NEXT: if ptr2.count < _ptr2Count || _ptr2Count < 0 { +// CHECK-NEXT: fatalError("bounds check failure when calling unsafe function") +// CHECK-NEXT: } +// CHECK-NEXT: return myFunc(ptr.baseAddress!, ptr2.baseAddress!, size) +// CHECK-NEXT: } diff --git a/test/Macros/PointerBounds/SizedBy/SimpleRawSpan.swift b/test/Macros/PointerBounds/SizedBy/SimpleRawSpan.swift new file mode 100644 index 0000000000000..2cafac4e37c9c --- /dev/null +++ b/test/Macros/PointerBounds/SizedBy/SimpleRawSpan.swift @@ -0,0 +1,15 @@ +// REQUIRES: swift_swift_parser +// REQUIRES: pointer_bounds + +// RUN: %target-swift-frontend %s -swift-version 5 -module-name main -disable-availability-checking -typecheck -plugin-path %swift-plugin-dir -dump-macro-expansions 2>&1 | %FileCheck --match-full-lines %s + +@PointerBounds(.sizedBy(pointer: 1, size: "size"), .nonescaping(pointer: 1)) +func myFunc(_ ptr: UnsafeRawPointer, _ size: CInt) { +} + +// CHECK: @_alwaysEmitIntoClient +// CHECK-NEXT: func myFunc(_ ptr: RawSpan) { +// CHECK-NEXT: return ptr.withUnsafeBytes { _ptrPtr in +// CHECK-NEXT: return myFunc(_ptrPtr.baseAddress!, CInt(exactly: ptr.byteCount)!) +// CHECK-NEXT: } +// CHECK-NEXT: } diff --git a/test/Macros/PointerBounds/SizedBy/SimpleRawSpanWithReturn.swift b/test/Macros/PointerBounds/SizedBy/SimpleRawSpanWithReturn.swift new file mode 100644 index 0000000000000..d81971afc02b5 --- /dev/null +++ b/test/Macros/PointerBounds/SizedBy/SimpleRawSpanWithReturn.swift @@ -0,0 +1,15 @@ +// REQUIRES: swift_swift_parser +// REQUIRES: pointer_bounds + +// RUN: %target-swift-frontend %s -swift-version 5 -module-name main -disable-availability-checking -typecheck -plugin-path %swift-plugin-dir -dump-macro-expansions 2>&1 | %FileCheck --match-full-lines %s + +@PointerBounds(.sizedBy(pointer: 1, size: "size"), .nonescaping(pointer: 1)) +func myFunc(_ ptr: UnsafeRawPointer, _ size: CInt) -> CInt { +} + +// CHECK: @_alwaysEmitIntoClient +// CHECK-NEXT: func myFunc(_ ptr: RawSpan) -> CInt { +// CHECK-NEXT: return ptr.withUnsafeBytes { _ptrPtr in +// CHECK-NEXT: return myFunc(_ptrPtr.baseAddress!, CInt(exactly: ptr.byteCount)!) +// CHECK-NEXT: } +// CHECK-NEXT: } diff --git a/test/Macros/PointerBounds/SizedBy/SimpleSize.swift b/test/Macros/PointerBounds/SizedBy/SimpleSize.swift new file mode 100644 index 0000000000000..78745c8f76030 --- /dev/null +++ b/test/Macros/PointerBounds/SizedBy/SimpleSize.swift @@ -0,0 +1,13 @@ +// REQUIRES: swift_swift_parser +// REQUIRES: pointer_bounds + +// RUN: %target-swift-frontend %s -swift-version 5 -module-name main -disable-availability-checking -typecheck -plugin-path %swift-plugin-dir -dump-macro-expansions 2>&1 | %FileCheck --match-full-lines %s + +@PointerBounds(.sizedBy(pointer: 1, size: "size")) +func myFunc(_ ptr: UnsafeRawPointer, _ size: CInt) { +} + +// CHECK: @_alwaysEmitIntoClient +// CHECK-NEXT: func myFunc(_ ptr: UnsafeRawBufferPointer) { +// CHECK-NEXT: return myFunc(ptr.baseAddress!, CInt(exactly: ptr.count)!) +// CHECK-NEXT: } diff --git a/test/Macros/PointerBounds/SizedBy/SizeExpr.swift b/test/Macros/PointerBounds/SizedBy/SizeExpr.swift new file mode 100644 index 0000000000000..db8d93d87366d --- /dev/null +++ b/test/Macros/PointerBounds/SizedBy/SizeExpr.swift @@ -0,0 +1,17 @@ +// REQUIRES: swift_swift_parser +// REQUIRES: pointer_bounds + +// RUN: %target-swift-frontend %s -swift-version 5 -module-name main -disable-availability-checking -typecheck -plugin-path %swift-plugin-dir -dump-macro-expansions 2>&1 | %FileCheck --match-full-lines %s + +@PointerBounds(.sizedBy(pointer: 1, size: "size * count")) +func myFunc(_ ptr: UnsafeRawPointer, _ size: CInt, _ count: CInt) { +} + +// CHECK: @_alwaysEmitIntoClient +// CHECK-NEXT: func myFunc(_ ptr: UnsafeRawBufferPointer, _ size: CInt, _ count: CInt) { +// CHECK-NEXT: let _ptrCount: some BinaryInteger = size * count +// CHECK-NEXT: if ptr.count < _ptrCount || _ptrCount < 0 { +// CHECK-NEXT: fatalError("bounds check failure when calling unsafe function") +// CHECK-NEXT: } +// CHECK-NEXT: return myFunc(ptr.baseAddress!, size, count) +// CHECK-NEXT: } diff --git a/test/Macros/PointerBounds/SizedBy/Unwrapped.swift b/test/Macros/PointerBounds/SizedBy/Unwrapped.swift new file mode 100644 index 0000000000000..1402e81e731e9 --- /dev/null +++ b/test/Macros/PointerBounds/SizedBy/Unwrapped.swift @@ -0,0 +1,14 @@ +// REQUIRES: swift_swift_parser +// REQUIRES: pointer_bounds + +// RUN: %target-swift-frontend %s -swift-version 5 -module-name main -disable-availability-checking -typecheck -plugin-path %swift-plugin-dir -dump-macro-expansions 2>&1 | %FileCheck --match-full-lines %s + +@PointerBounds(.sizedBy(pointer: 1, size: "len")) +func myFunc(_ ptr: UnsafeRawPointer!, _ len: CInt) { +} + +// CHECK: @_alwaysEmitIntoClient +// CHECK-NEXT: func myFunc(_ ptr: UnsafeRawBufferPointer) { +// CHECK-NEXT: return myFunc(ptr.baseAddress!, CInt(exactly: ptr.count)!) +// CHECK-NEXT: } + diff --git a/test/lit.site.cfg.in b/test/lit.site.cfg.in index 4b4290aa04e0d..ba75d4ed07ea9 100644 --- a/test/lit.site.cfg.in +++ b/test/lit.site.cfg.in @@ -156,6 +156,8 @@ if "@SWIFT_ENABLE_EXPERIMENTAL_STRING_PROCESSING@" == "TRUE": config.available_features.add('string_processing') if "@SWIFT_ENABLE_EXPERIMENTAL_OBSERVATION@" == "TRUE": config.available_features.add('observation') +if "@SWIFT_ENABLE_EXPERIMENTAL_POINTER_BOUNDS@" == "TRUE": + config.available_features.add('pointer_bounds') if "@SWIFT_STDLIB_ENABLE_DEBUG_PRECONDITIONS_IN_RELEASE@" == "TRUE": config.available_features.add('swift_stdlib_debug_preconditions_in_release') if "@SWIFT_ENABLE_VOLATILE@" == "TRUE": diff --git a/utils/build-script-impl b/utils/build-script-impl index 83a3f3ea6ec80..a7040be8f3006 100755 --- a/utils/build-script-impl +++ b/utils/build-script-impl @@ -2091,6 +2091,13 @@ for host in "${ALL_HOSTS[@]}"; do ) fi + if [[ "${SWIFT_ENABLE_EXPERIMENTAL_POINTER_BOUNDS}" ]] ; then + cmake_options=( + "${cmake_options[@]}" + -DSWIFT_ENABLE_EXPERIMENTAL_POINTER_BOUNDS:BOOL=$(true_false "${SWIFT_ENABLE_EXPERIMENTAL_POINTER_BOUNDS}") + ) + fi + # SWIFT_THREADING_PACKAGE can be: # # - Empty diff --git a/utils/build.ps1 b/utils/build.ps1 index 913988e5e519f..5dd9259c15c23 100644 --- a/utils/build.ps1 +++ b/utils/build.ps1 @@ -1491,6 +1491,7 @@ function Build-Compilers() { SWIFT_ENABLE_EXPERIMENTAL_DISTRIBUTED = "YES"; SWIFT_ENABLE_EXPERIMENTAL_OBSERVATION = "YES"; SWIFT_ENABLE_EXPERIMENTAL_STRING_PROCESSING = "YES"; + SWIFT_ENABLE_EXPERIMENTAL_POINTER_BOUNDS = "YES"; SWIFT_ENABLE_SYNCHRONIZATION = "YES"; SWIFT_ENABLE_VOLATILE = "YES"; SWIFT_PATH_TO_LIBDISPATCH_SOURCE = "$SourceCache\swift-corelibs-libdispatch"; @@ -1796,6 +1797,7 @@ function Build-Runtime([Platform]$Platform, $Arch) { SWIFT_ENABLE_EXPERIMENTAL_DISTRIBUTED = "YES"; SWIFT_ENABLE_EXPERIMENTAL_OBSERVATION = "YES"; SWIFT_ENABLE_EXPERIMENTAL_STRING_PROCESSING = "YES"; + SWIFT_ENABLE_EXPERIMENTAL_POINTER_BOUNDS = "YES"; SWIFT_ENABLE_SYNCHRONIZATION = "YES"; SWIFT_ENABLE_VOLATILE = "YES"; SWIFT_NATIVE_SWIFT_TOOLS_PATH = (Join-Path -Path $CompilersBinaryCache -ChildPath "bin"); diff --git a/utils/build_swift/build_swift/driver_arguments.py b/utils/build_swift/build_swift/driver_arguments.py index e8362fb148d82..e6bddcf8ba6a0 100644 --- a/utils/build_swift/build_swift/driver_arguments.py +++ b/utils/build_swift/build_swift/driver_arguments.py @@ -1497,6 +1497,10 @@ def create_argument_parser(): default=True, help='Enable experimental Swift Parser validation by default.') + option('--enable-experimental-pointer-bounds', toggle_true, + default=False, + help='Enable experimental bounds safe C interop.') + # ------------------------------------------------------------------------- in_group('Unsupported options') diff --git a/utils/build_swift/tests/expected_options.py b/utils/build_swift/tests/expected_options.py index 08b64f7f7db99..f2eaa18ca227a 100644 --- a/utils/build_swift/tests/expected_options.py +++ b/utils/build_swift/tests/expected_options.py @@ -185,6 +185,7 @@ 'enable_experimental_string_processing': True, 'enable_experimental_observation': True, 'enable_experimental_parser_validation': True, + 'enable_experimental_pointer_bounds': False, 'swift_enable_backtracing': True, 'enable_synchronization': True, 'enable_volatile': True, @@ -629,6 +630,7 @@ class BuildScriptImplOption(_BaseOption): EnableOption('--enable-experimental-string-processing'), EnableOption('--enable-experimental-observation'), EnableOption('--enable-experimental-parser-validation'), + EnableOption('--enable-experimental-pointer-bounds'), EnableOption('--enable-lsan'), EnableOption('--enable-sanitize-coverage'), EnableOption('--enable-tsan'), diff --git a/utils/swift_build_support/swift_build_support/products/minimalstdlib.py b/utils/swift_build_support/swift_build_support/products/minimalstdlib.py index 38f0b73402f7b..007f0f6b7f75c 100644 --- a/utils/swift_build_support/swift_build_support/products/minimalstdlib.py +++ b/utils/swift_build_support/swift_build_support/products/minimalstdlib.py @@ -104,6 +104,8 @@ def build(self, host_target): 'SWIFT_ENABLE_EXPERIMENTAL_DISTRIBUTED:BOOL', 'FALSE') self.cmake_options.define( 'SWIFT_ENABLE_EXPERIMENTAL_OBSERVATION:BOOL', 'FALSE') + self.cmake_options.define( + 'SWIFT_ENABLE_EXPERIMENTAL_POINTER_BOUNDS:BOOL', 'FALSE') self.cmake_options.define('SWIFT_ENABLE_REFLECTION:BOOL', 'FALSE') self.cmake_options.define( 'SWIFT_ENABLE_RUNTIME_FUNCTION_COUNTERS:BOOL', 'FALSE') diff --git a/utils/swift_build_support/swift_build_support/products/swift.py b/utils/swift_build_support/swift_build_support/products/swift.py index b56f116309036..224adfbd0cad2 100644 --- a/utils/swift_build_support/swift_build_support/products/swift.py +++ b/utils/swift_build_support/swift_build_support/products/swift.py @@ -57,6 +57,9 @@ def __init__(self, args, toolchain, source_dir, build_dir): self.cmake_options.extend(self._enable_experimental_cxx_interop) self.cmake_options.extend(self._enable_cxx_interop_swift_bridging_header) + # Add experimental c interop flag. + self.cmake_options.extend(self._enable_experimental_pointer_bounds) + # Add experimental distributed flag. self.cmake_options.extend(self._enable_experimental_distributed) @@ -215,6 +218,11 @@ def _enable_experimental_observation(self): return [('SWIFT_ENABLE_EXPERIMENTAL_OBSERVATION:BOOL', self.args.enable_experimental_observation)] + @property + def _enable_experimental_pointer_bounds(self): + return [('SWIFT_ENABLE_EXPERIMENTAL_POINTER_BOUNDS:BOOL', + self.args.enable_experimental_pointer_bounds)] + @property def _enable_synchronization(self): return [('SWIFT_ENABLE_SYNCHRONIZATION:BOOL', diff --git a/utils/swift_build_support/swift_build_support/products/wasmstdlib.py b/utils/swift_build_support/swift_build_support/products/wasmstdlib.py index 70299e3788558..a0558ffa82703 100644 --- a/utils/swift_build_support/swift_build_support/products/wasmstdlib.py +++ b/utils/swift_build_support/swift_build_support/products/wasmstdlib.py @@ -164,6 +164,8 @@ def _build_stdlib(self, host_target, target_triple, llvm_cmake_dir): self.cmake_options.define('SWIFT_ENABLE_SYNCHRONIZATION:BOOL', 'TRUE') self.cmake_options.define('SWIFT_ENABLE_VOLATILE:BOOL', 'TRUE') self.cmake_options.define('SWIFT_ENABLE_EXPERIMENTAL_OBSERVATION:BOOL', 'TRUE') + self.cmake_options.define('SWIFT_ENABLE_EXPERIMENTAL_POINTER_BOUNDS:BOOL', + 'TRUE') self.add_extra_cmake_options() diff --git a/utils/swift_build_support/tests/products/test_swift.py b/utils/swift_build_support/tests/products/test_swift.py index 6a395197412c4..da32303acb70f 100644 --- a/utils/swift_build_support/tests/products/test_swift.py +++ b/utils/swift_build_support/tests/products/test_swift.py @@ -61,6 +61,7 @@ def setUp(self): enable_experimental_nonescapable_types=False, enable_experimental_observation=False, enable_experimental_parser_validation=False, + enable_experimental_pointer_bounds=False, swift_enable_backtracing=False, enable_synchronization=False, enable_volatile=False, @@ -110,6 +111,7 @@ def test_by_default_no_cmake_options(self): '-DSWIFT_ENABLE_EXPERIMENTAL_DISTRIBUTED:BOOL=FALSE', '-DSWIFT_ENABLE_EXPERIMENTAL_OBSERVATION:BOOL=FALSE', '-DSWIFT_ENABLE_EXPERIMENTAL_PARSER_VALIDATION:BOOL=FALSE', + '-DSWIFT_ENABLE_EXPERIMENTAL_POINTER_BOUNDS:BOOL=FALSE', '-DSWIFT_ENABLE_BACKTRACING:BOOL=FALSE', '-DSWIFT_ENABLE_SYNCHRONIZATION:BOOL=FALSE', '-DSWIFT_ENABLE_VOLATILE:BOOL=FALSE', @@ -144,6 +146,7 @@ def test_swift_runtime_tsan(self): '-DSWIFT_ENABLE_EXPERIMENTAL_DISTRIBUTED:BOOL=FALSE', '-DSWIFT_ENABLE_EXPERIMENTAL_OBSERVATION:BOOL=FALSE', '-DSWIFT_ENABLE_EXPERIMENTAL_PARSER_VALIDATION:BOOL=FALSE', + '-DSWIFT_ENABLE_EXPERIMENTAL_POINTER_BOUNDS:BOOL=FALSE', '-DSWIFT_ENABLE_BACKTRACING:BOOL=FALSE', '-DSWIFT_ENABLE_SYNCHRONIZATION:BOOL=FALSE', '-DSWIFT_ENABLE_VOLATILE:BOOL=FALSE', @@ -432,6 +435,19 @@ def test_experimental_observation_flags(self): [x for x in swift.cmake_options if 'DSWIFT_ENABLE_EXPERIMENTAL_OBSERVATION' in x]) + def test_experimental_pointer_bounds_flags(self): + self.args.enable_experimental_pointer_bounds = True + swift = Swift( + args=self.args, + toolchain=self.toolchain, + source_dir='/path/to/src', + build_dir='/path/to/build') + self.assertEqual( + ['-DSWIFT_ENABLE_EXPERIMENTAL_POINTER_BOUNDS:BOOL=' + 'TRUE'], + [x for x in swift.cmake_options + if 'DSWIFT_ENABLE_EXPERIMENTAL_POINTER_BOUNDS' in x]) + def test_backtracing_flags(self): self.args.swift_enable_backtracing = True swift = Swift( diff --git a/validation-test/lit.site.cfg.in b/validation-test/lit.site.cfg.in index c627bb5421b5e..325ca1ce5fe5c 100644 --- a/validation-test/lit.site.cfg.in +++ b/validation-test/lit.site.cfg.in @@ -133,6 +133,8 @@ if "@SWIFT_ENABLE_EXPERIMENTAL_DISTRIBUTED@" == "TRUE": config.available_features.add('distributed') if "@SWIFT_ENABLE_EXPERIMENTAL_STRING_PROCESSING@" == "TRUE": config.available_features.add('string_processing') +if "@SWIFT_ENABLE_EXPERIMENTAL_POINTER_BOUNDS@" == "TRUE": + config.available_features.add('pointer_bounds') if "@SWIFT_STDLIB_ENABLE_DEBUG_PRECONDITIONS_IN_RELEASE@" == "TRUE": config.available_features.add('swift_stdlib_debug_preconditions_in_release')