From ce27af61f7c5feb150b9f335c51d78160ea142dd Mon Sep 17 00:00:00 2001 From: "Henrik G. Olsson" Date: Fri, 24 Oct 2025 13:11:49 -0700 Subject: [PATCH 1/4] [Swiftify] make protocol extension methods public --- .../Sources/SwiftMacros/SwiftifyImportMacro.swift | 14 ++++++++++++-- .../Macros/SwiftifyImport/CountedBy/Protocol.swift | 14 +++++++------- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/lib/Macros/Sources/SwiftMacros/SwiftifyImportMacro.swift b/lib/Macros/Sources/SwiftMacros/SwiftifyImportMacro.swift index ea5af950daf3a..461807dbe4847 100644 --- a/lib/Macros/Sources/SwiftMacros/SwiftifyImportMacro.swift +++ b/lib/Macros/Sources/SwiftMacros/SwiftifyImportMacro.swift @@ -1808,11 +1808,21 @@ public struct SwiftifyImportProtocolMacro: ExtensionMacro { } let overloads = try arguments.map { let (method, args) = try parseProtocolMacroParam($0, methods: methods) - let function = try constructOverloadFunction( + let hasVisibilityModifier = method.modifiers.contains { modifier in + let modName = modifier.name.trimmed.text + return modName == "public" || modName == "internal" || modName == "open" + || modName == "private" || modName == "filePrivate" + } + let result = try constructOverloadFunction( forDecl: method, leadingTrivia: Trivia(), args: args, spanAvailability: spanAvailability, typeMappings: typeMappings) - return MemberBlockItemSyntax(decl: function) + guard let newMethod = result.as(FunctionDeclSyntax.self)? + .with(\.modifiers, method.modifiers + + (hasVisibilityModifier ? [] : [DeclModifierSyntax(name: .identifier("public"))])) else { + throw RuntimeError("expected FunctionDeclSyntax but got \(result.kind) for \(method.description)") + } + return MemberBlockItemSyntax(decl: newMethod) } return [ExtensionDeclSyntax(extensionKeyword: .identifier("extension"), extendedType: type, diff --git a/test/Macros/SwiftifyImport/CountedBy/Protocol.swift b/test/Macros/SwiftifyImport/CountedBy/Protocol.swift index 38729d224b9d9..3623caa797a71 100644 --- a/test/Macros/SwiftifyImport/CountedBy/Protocol.swift +++ b/test/Macros/SwiftifyImport/CountedBy/Protocol.swift @@ -42,7 +42,7 @@ protocol OverloadedProtocol { ------------------------------ extension SimpleProtocol { /// This is an auto-generated wrapper for safer interop -@_alwaysEmitIntoClient @_disfavoredOverload +@_alwaysEmitIntoClient @_disfavoredOverload public func myFunc(_ ptr: UnsafeBufferPointer) { let len = CInt(exactly: ptr.count)! return unsafe myFunc(ptr.baseAddress!, len) @@ -53,7 +53,7 @@ extension SimpleProtocol { ------------------------------ extension SpanProtocol { /// This is an auto-generated wrapper for safer interop -@_alwaysEmitIntoClient @_disfavoredOverload +@_alwaysEmitIntoClient @_disfavoredOverload public func foo(_ ptr: Span) { let len = CInt(exactly: ptr.count)! return unsafe ptr.withUnsafeBufferPointer { _ptrPtr in @@ -61,7 +61,7 @@ extension SpanProtocol { } } /// This is an auto-generated wrapper for safer interop -@_alwaysEmitIntoClient @_lifetime(borrow self) @_disfavoredOverload +@_alwaysEmitIntoClient @_lifetime(borrow self) @_disfavoredOverload public func bar(_ len: CInt) -> Span { return unsafe _swiftifyOverrideLifetime(Span(_unsafeStart: unsafe bar(len), count: Int(len)), copying: ()) } @@ -71,7 +71,7 @@ extension SpanProtocol { ------------------------------ extension MixedProtocol { /// This is an auto-generated wrapper for safer interop -@_alwaysEmitIntoClient @_disfavoredOverload +@_alwaysEmitIntoClient @_disfavoredOverload public /// Some doc comment func foo(_ ptr: Span) { let len = CInt(exactly: ptr.count)! @@ -80,7 +80,7 @@ extension MixedProtocol { } } /// This is an auto-generated wrapper for safer interop -@_alwaysEmitIntoClient @_disfavoredOverload +@_alwaysEmitIntoClient @_disfavoredOverload public func bar(_ ptr: UnsafeBufferPointer) { let len = CInt(exactly: ptr.count)! return unsafe bar(ptr.baseAddress!, len) @@ -91,13 +91,13 @@ extension MixedProtocol { ------------------------------ extension OverloadedProtocol { /// This is an auto-generated wrapper for safer interop -@_alwaysEmitIntoClient @_disfavoredOverload +@_alwaysEmitIntoClient @_disfavoredOverload public func foo(_ ptr: UnsafeBufferPointer) { let len1 = CInt(exactly: ptr.count)! return unsafe foo(ptr.baseAddress!, len1) } /// This is an auto-generated wrapper for safer interop -@_alwaysEmitIntoClient @_disfavoredOverload +@_alwaysEmitIntoClient @_disfavoredOverload public func foo(bar: UnsafeBufferPointer) { let len2 = CInt(exactly: bar.count)! return unsafe foo(bar: bar.baseAddress!, len2) From af819b94285dc0f979263891b7565639cae59521 Mon Sep 17 00:00:00 2001 From: "Henrik G. Olsson" Date: Wed, 22 Oct 2025 13:56:11 -0700 Subject: [PATCH 2/4] [ClangImporter] Attach _SwiftifyImportProtocol to imported protocols ...with bounds attributes This creates safe overloads for any methods in the protocol annotated with bounds information. Updates _SwiftifyImportProtocol to make the added overloads in the protocol public. rdar://144335990 --- lib/ClangImporter/ImportDecl.cpp | 2 + lib/ClangImporter/ImporterImpl.h | 1 + lib/ClangImporter/SwiftifyDecl.cpp | 133 +++++++++++++++--- lib/Sema/TypeCheckMacros.cpp | 7 +- .../swiftify-import/counted-by-protocol.swift | 133 ++++++++++++++++++ 5 files changed, 257 insertions(+), 19 deletions(-) create mode 100644 test/Interop/ObjC/swiftify-import/counted-by-protocol.swift diff --git a/lib/ClangImporter/ImportDecl.cpp b/lib/ClangImporter/ImportDecl.cpp index 17dc9aa1a7194..8d6c763018baa 100644 --- a/lib/ClangImporter/ImportDecl.cpp +++ b/lib/ClangImporter/ImportDecl.cpp @@ -5881,6 +5881,8 @@ namespace { result->setMemberLoader(&Impl, 0); + Impl.swiftifyProtocol(result); + return result; } diff --git a/lib/ClangImporter/ImporterImpl.h b/lib/ClangImporter/ImporterImpl.h index e17f4d8d20d6e..fef4067233d05 100644 --- a/lib/ClangImporter/ImporterImpl.h +++ b/lib/ClangImporter/ImporterImpl.h @@ -1827,6 +1827,7 @@ class LLVM_LIBRARY_VISIBILITY ClangImporter::Implementation void addOptionSetTypealiases(NominalTypeDecl *nominal); void swiftify(AbstractFunctionDecl *MappedDecl); + void swiftifyProtocol(NominalTypeDecl *MappedDecl); /// Find the lookup table that corresponds to the given Clang module. /// diff --git a/lib/ClangImporter/SwiftifyDecl.cpp b/lib/ClangImporter/SwiftifyDecl.cpp index 7be08be0032a9..9b9293a2de502 100644 --- a/lib/ClangImporter/SwiftifyDecl.cpp +++ b/lib/ClangImporter/SwiftifyDecl.cpp @@ -16,6 +16,7 @@ #include "ImporterImpl.h" #include "swift/AST/ASTContext.h" +#include "swift/AST/ASTPrinter.h" #include "swift/AST/Decl.h" #include "swift/AST/DiagnosticsClangImporter.h" #include "swift/AST/ParameterList.h" @@ -151,6 +152,7 @@ class SwiftifyInfoPrinter { out << "\""; } +protected: void printSeparator() { if (!firstParam) { out << ", "; @@ -159,6 +161,7 @@ class SwiftifyInfoPrinter { } } +private: void printParamOrReturn(ssize_t pointerIndex) { if (pointerIndex == SELF_PARAM_INDEX) out << ".self"; @@ -296,12 +299,25 @@ static StringRef getAttributeName(const clang::CountAttributedType *CAT) { llvm_unreachable("CountAttributedType cannot be ended_by"); } } + +template +static bool getImplicitObjectParamAnnotation(const clang::ObjCMethodDecl* D) { + return false; // Only C++ methods have implicit params +} + +static size_t getNumParams(const clang::ObjCMethodDecl* D) { + return D->param_size(); +} +static size_t getNumParams(const clang::FunctionDecl* D) { + return D->getNumParams(); +} } // namespace +template static bool swiftifyImpl(ClangImporter::Implementation &Self, SwiftifyInfoPrinter &printer, const AbstractFunctionDecl *MappedDecl, - const clang::FunctionDecl *ClangDecl) { + const T *ClangDecl) { DLOG("Checking " << *ClangDecl << " for bounds and lifetime info\n"); // FIXME: for private macro generated functions we do not serialize the @@ -310,13 +326,6 @@ static bool swiftifyImpl(ClangImporter::Implementation &Self, ClangDecl->getAccess() == clang::AS_private) return false; - { - UnaliasedInstantiationVisitor visitor; - visitor.TraverseType(ClangDecl->getType()); - if (visitor.hasUnaliasedInstantiation) - return false; - } - clang::ASTContext &clangASTContext = Self.getClangASTContext(); // We only attach the macro if it will produce an overload. Any __counted_by @@ -332,9 +341,10 @@ static bool swiftifyImpl(ClangImporter::Implementation &Self, swiftReturnTy = ctorDecl->getResultInterfaceType(); else ABORT("Unexpected AbstractFunctionDecl subclass."); + clang::QualType clangReturnTy = ClangDecl->getReturnType(); bool returnIsStdSpan = printer.registerStdSpanTypeMapping( - swiftReturnTy, ClangDecl->getReturnType()); - auto *CAT = ClangDecl->getReturnType()->getAs(); + swiftReturnTy, clangReturnTy); + auto *CAT = clangReturnTy->getAs(); if (SwiftifiableCAT(clangASTContext, CAT, swiftReturnTy)) { printer.printCountedBy(CAT, SwiftifyInfoPrinter::RETURN_VALUE_INDEX); DLOG(" Found bounds info '" << clang::QualType(CAT, 0) << "' on return value\n"); @@ -361,13 +371,13 @@ static bool swiftifyImpl(ClangImporter::Implementation &Self, size_t swiftNumParams = MappedDecl->getParameters()->size() - (ClangDecl->isVariadic() ? 1 : 0); ASSERT((MappedDecl->isImportAsInstanceMember() == isClangInstanceMethod) == - (ClangDecl->getNumParams() == swiftNumParams)); + (getNumParams(ClangDecl) == swiftNumParams)); size_t selfParamIndex = MappedDecl->isImportAsInstanceMember() ? MappedDecl->getSelfIndex() - : ClangDecl->getNumParams(); + : getNumParams(ClangDecl); for (auto [index, clangParam] : llvm::enumerate(ClangDecl->parameters())) { - auto clangParamTy = clangParam->getType(); + clang::QualType clangParamTy = clangParam->getType(); int mappedIndex = index < selfParamIndex ? index : index > selfParamIndex ? index - 1 : SwiftifyInfoPrinter::SELF_PARAM_INDEX; @@ -383,13 +393,12 @@ static bool swiftifyImpl(ClangImporter::Implementation &Self, auto *CAT = clangParamTy->getAs(); if (CAT && mappedIndex == SwiftifyInfoPrinter::SELF_PARAM_INDEX) { Self.diagnose(HeaderLoc(clangParam->getLocation()), - diag::warn_clang_ignored_bounds_on_self, - getAttributeName(CAT)); - auto swiftName = ClangDecl->getAttr(); + diag::warn_clang_ignored_bounds_on_self, getAttributeName(CAT)); + auto swiftName = ClangDecl->template getAttr(); ASSERT(swiftName && "free function mapped to instance method without swift_name??"); Self.diagnose(HeaderLoc(swiftName->getLocation()), - diag::note_swift_name_instance_method); + diag::note_swift_name_instance_method); } else if (SwiftifiableCAT(clangASTContext, CAT, swiftParamTy)) { printer.printCountedBy(CAT, mappedIndex); DLOG(" Found bounds info '" << clangParamTy << "' on parameter '" @@ -401,7 +410,7 @@ static bool swiftifyImpl(ClangImporter::Implementation &Self, paramHasBoundsInfo |= paramIsStdSpan; bool paramHasLifetimeInfo = false; - if (clangParam->hasAttr()) { + if (clangParam->template hasAttr()) { DLOG(" Found noescape attribute on parameter '" << *clangParam << "'\n"); printer.printNonEscaping(mappedIndex); paramHasLifetimeInfo = true; @@ -435,6 +444,47 @@ static bool swiftifyImpl(ClangImporter::Implementation &Self, return attachMacro; } +class SwiftifyProtocolInfoPrinter : public SwiftifyInfoPrinter { +private: + ClangImporter::Implementation &ImporterImpl; + +public: + SwiftifyProtocolInfoPrinter(ClangImporter::Implementation &ImporterImpl, + clang::ASTContext &ctx, ASTContext &SwiftContext, + llvm::raw_ostream &out, + MacroDecl &SwiftifyImportDecl) + : SwiftifyInfoPrinter(ctx, SwiftContext, out, SwiftifyImportDecl), + ImporterImpl(ImporterImpl) {} + + bool printMethod(const FuncDecl *Method) { + // don't emit .method() if we know it's going to be empty + auto ClangDecl = dyn_cast_or_null(Method->getClangDecl()); + if (!ClangDecl) + return false; + + printSeparator(); + out << ".method(signature: \""; + printMethodSignature(Method); + out << "\", paramInfo: ["; + // reset firstParam inside paramInfo array. At this point firstParam will + // always be false, so no need to save the current value. + assert(!firstParam); + firstParam = true; + bool hadAttributes = swiftifyImpl(ImporterImpl, *this, Method, ClangDecl); + firstParam = false; + out << "])"; + return hadAttributes; + } + +private: + void printMethodSignature(const FuncDecl *Method) { + auto options = + PrintOptions::printForDiagnostics(AccessLevel::Private, true); + StreamPrinter printer(out); + Method->print(printer, options); + } +}; + void ClangImporter::Implementation::swiftify(AbstractFunctionDecl *MappedDecl) { if (!SwiftContext.LangOpts.hasFeature(Feature::SafeInteropWrappers) || SwiftContext.ClangImporterOpts.DisableSafeInteropWrappers) @@ -443,6 +493,13 @@ void ClangImporter::Implementation::swiftify(AbstractFunctionDecl *MappedDecl) { if (!ClangDecl) return; + { + UnaliasedInstantiationVisitor visitor; + visitor.TraverseType(ClangDecl->getType()); + if (visitor.hasUnaliasedInstantiation) + return; + } + MacroDecl *SwiftifyImportDecl = dyn_cast_or_null(getKnownSingleDecl(SwiftContext, "_SwiftifyImport")); if (!SwiftifyImportDecl) return; @@ -479,3 +536,43 @@ void ClangImporter::Implementation::swiftify(AbstractFunctionDecl *MappedDecl) { } } +void ClangImporter::Implementation::swiftifyProtocol( + NominalTypeDecl *MappedDecl) { + if (!SwiftContext.LangOpts.hasFeature(Feature::SafeInteropWrappers) || + SwiftContext.ClangImporterOpts.DisableSafeInteropWrappers) + return; + if (!isa(MappedDecl)) + return; + + MacroDecl *SwiftifyImportDecl = dyn_cast_or_null( + getKnownSingleDecl(SwiftContext, "_SwiftifyImportProtocol")); + if (!SwiftifyImportDecl) + return; + + DLOG("Checking " << MappedDecl->getName() << " protocol for methods with bounds and lifetime info\n"); + llvm::SmallString<128> MacroString; + { + llvm::raw_svector_ostream out(MacroString); + out << "@_SwiftifyImportProtocol"; + + bool hasBoundsAttributes = false; + SwiftifyProtocolInfoPrinter printer(*this, getClangASTContext(), + SwiftContext, out, *SwiftifyImportDecl); + for (Decl *SwiftMember : + cast(MappedDecl)->getAllMembers()) { + FuncDecl *SwiftDecl = dyn_cast(SwiftMember); + if (!SwiftDecl) + continue; + hasBoundsAttributes |= printer.printMethod(SwiftDecl); + } + + if (!hasBoundsAttributes) + return; + printer.printAvailability(); + printer.printTypeMapping(); + } + + DLOG("Attaching safe interop macro: " << MacroString << "\n"); + importNontrivialAttribute(MappedDecl, MacroString); +} + diff --git a/lib/Sema/TypeCheckMacros.cpp b/lib/Sema/TypeCheckMacros.cpp index 9e57b5a012a13..6df797d829003 100644 --- a/lib/Sema/TypeCheckMacros.cpp +++ b/lib/Sema/TypeCheckMacros.cpp @@ -1360,6 +1360,7 @@ swift::expandFreestandingMacro(MacroExpansionDecl *med) { return macroSourceFile->getBufferID(); } + static SourceFile *evaluateAttachedMacro(MacroDecl *macro, Decl *attachedTo, CustomAttr *attr, bool passParentContext, MacroRole role, @@ -1370,7 +1371,11 @@ static SourceFile *evaluateAttachedMacro(MacroDecl *macro, Decl *attachedTo, dc = attachedTo->getDeclContext(); } else if (role == MacroRole::Conformance || role == MacroRole::Extension) { // Conformance macros always expand to extensions at file-scope. - dc = attachedTo->getDeclContext()->getParentSourceFile(); + dc = attachedTo->getDeclContext(); + if (!isa(dc->getModuleScopeContext())) + dc = dc->getParentSourceFile(); + else // decls imported from clang do not have a SourceFile + ASSERT(isa(dc) && !isa(dc)); } else { dc = attachedTo->getInnermostDeclContext(); } diff --git a/test/Interop/ObjC/swiftify-import/counted-by-protocol.swift b/test/Interop/ObjC/swiftify-import/counted-by-protocol.swift new file mode 100644 index 0000000000000..efe843e4d6037 --- /dev/null +++ b/test/Interop/ObjC/swiftify-import/counted-by-protocol.swift @@ -0,0 +1,133 @@ +// REQUIRES: swift_feature_SafeInteropWrappers + +// RUN: %empty-directory(%t) +// RUN: split-file %s %t + +// RUN: %target-swift-frontend -emit-module -plugin-path %swift-plugin-dir -o %t/CountedByProtocol.swiftmodule -I %t/Inputs -enable-experimental-feature SafeInteropWrappers %t/counted-by-protocol.swift -verify -Xcc -Wno-nullability-completeness +// RUN: %target-swift-frontend -emit-module -plugin-path %swift-plugin-dir -o %t/CountedByProtocol.swiftmodule -I %t/Inputs -enable-experimental-feature SafeInteropWrappers %t/counted-by-protocol.swift -dump-macro-expansions 2> %t/expansions.out +// RUN: %diff %t/expansions.out %t/expansions.expected + +//--- Inputs/module.modulemap +module CountedByProtocolClang { + header "counted-by-protocol.h" + export * +} + +//--- Inputs/counted-by-protocol.h +#pragma once + +#define __counted_by(x) __attribute__((__counted_by__(x))) + +int foo(int len, int * __counted_by(len) p); +@protocol CountedByProtocol + - (void) simple:(int)len :(int * __counted_by(len))p; + - (void) shared:(int)len :(int * __counted_by(len))p1 :(int * __counted_by(len))p2; + - (void) complexExpr:(int)len :(int) offset :(int * __counted_by(len - offset))p; + - (void) nullUnspecified:(int)len :(int * __counted_by(len) _Null_unspecified)p; + - (void) nonnull:(int)len :(int * __counted_by(len) _Nonnull)p; + - (void) nullable:(int)len :(int * __counted_by(len) _Nullable)p; + - (int * __counted_by(len)) returnPointer:(int)len; + + + (void) staticMethod:(int)len :(int * __counted_by(len))p; +@end + +__attribute__((swift_attr("@_SwiftifyImportProtocol(.method(signature: \"func swiftAttr(_ len: Int32, _ p: UnsafeMutablePointer!)\", paramInfo: [.countedBy(pointer: .param(2), count: \"len\")]))"))) +@protocol SwiftAttrProtocol + - (void)swiftAttr:(int)len :(int *)p; +@end + +//--- expansions.expected +@__swiftmacro_So17CountedByProtocol015_SwiftifyImportC0fMe_.swift +------------------------------ +extension CountedByProtocol { + /// This is an auto-generated wrapper for safer interop +@_alwaysEmitIntoClient @_disfavoredOverload public + func simple(_ p: UnsafeMutableBufferPointer) { + let len = Int32(exactly: p.count)! + return unsafe simple(len, p.baseAddress!) + } + /// This is an auto-generated wrapper for safer interop +@_alwaysEmitIntoClient @_disfavoredOverload public + func shared(_ p1: UnsafeMutableBufferPointer, _ p2: UnsafeMutableBufferPointer) { + let len = Int32(exactly: p1.count)! + if p2.count != len { + fatalError("bounds check failure in shared: expected \(len) but got \(p2.count)") + } + return unsafe shared(len, p1.baseAddress!, p2.baseAddress!) + } + /// This is an auto-generated wrapper for safer interop +@_alwaysEmitIntoClient @_disfavoredOverload public + func complexExpr(_ len: Int32, _ offset: Int32, _ p: UnsafeMutableBufferPointer) { + let _pCount = p.count + if _pCount != len - offset { + fatalError("bounds check failure in complexExpr: expected \(len - offset) but got \(_pCount)") + } + return unsafe complexExpr(len, offset, p.baseAddress!) + } + /// This is an auto-generated wrapper for safer interop +@_alwaysEmitIntoClient @_disfavoredOverload public + func nullUnspecified(_ p: UnsafeMutableBufferPointer) { + let len = Int32(exactly: p.count)! + return unsafe nullUnspecified(len, p.baseAddress!) + } + /// This is an auto-generated wrapper for safer interop +@_alwaysEmitIntoClient @_disfavoredOverload public + func nonnull(_ p: UnsafeMutableBufferPointer) { + let len = Int32(exactly: p.count)! + return unsafe nonnull(len, p.baseAddress!) + } + /// This is an auto-generated wrapper for safer interop +@_alwaysEmitIntoClient @_disfavoredOverload public + func nullable(_ p: UnsafeMutableBufferPointer?) { + let len = Int32(exactly: unsafe p?.count ?? 0)! + return unsafe nullable(len, p?.baseAddress) + } + /// This is an auto-generated wrapper for safer interop +@_alwaysEmitIntoClient @_disfavoredOverload public + func returnPointer(_ len: Int32) -> UnsafeMutableBufferPointer { + return unsafe UnsafeMutableBufferPointer(start: unsafe returnPointer(len), count: Int(len)) + } + /// This is an auto-generated wrapper for safer interop +@_alwaysEmitIntoClient @_disfavoredOverload + static public func staticMethod(_ p: UnsafeMutableBufferPointer) { + let len = Int32(exactly: p.count)! + return unsafe staticMethod(len, p.baseAddress!) + } +} +------------------------------ +@__swiftmacro_So17SwiftAttrProtocol015_SwiftifyImportC0fMe_.swift +------------------------------ +extension SwiftAttrProtocol { + /// This is an auto-generated wrapper for safer interop +@_alwaysEmitIntoClient @_disfavoredOverload public + func swiftAttr(_ p: UnsafeMutableBufferPointer) { + let len = Int32(exactly: p.count)! + return unsafe swiftAttr(len, p.baseAddress!) + } +} +------------------------------ +@__swiftmacro_So3foo15_SwiftifyImportfMp_.swift +------------------------------ +/// This is an auto-generated wrapper for safer interop +@_alwaysEmitIntoClient @_disfavoredOverload public func foo(_ p: UnsafeMutableBufferPointer) -> Int32 { + let len = Int32(exactly: p.count)! + return unsafe foo(len, p.baseAddress!) +} +------------------------------ +//--- counted-by-protocol.swift +import CountedByProtocolClang + +@inlinable +public func call(p: UnsafeMutableBufferPointer, x: CInt, y: CInt, a: CountedByProtocol, b: SwiftAttrProtocol) { + a.simple(p) + a.shared(p, p) + a.complexExpr(x, y, p) + a.nullUnspecified(p) + a.nonnull(p) + a.nullable(p) + let _: UnsafeMutableBufferPointer = a.returnPointer(x) + let r2 = a.returnPointer(x) + let _: UnsafeMutablePointer? = r2 // make sure the original is the favored overload + b.swiftAttr(p) + let _ = foo(p) +} From 72439d35a7b50d8b9c048804c6779ced7293d604 Mon Sep 17 00:00:00 2001 From: "Henrik G. Olsson" Date: Fri, 24 Oct 2025 13:14:21 -0700 Subject: [PATCH 3/4] assert -> ASSERT --- lib/ClangImporter/SwiftifyDecl.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/ClangImporter/SwiftifyDecl.cpp b/lib/ClangImporter/SwiftifyDecl.cpp index 9b9293a2de502..67976d27c085b 100644 --- a/lib/ClangImporter/SwiftifyDecl.cpp +++ b/lib/ClangImporter/SwiftifyDecl.cpp @@ -37,7 +37,7 @@ namespace { ValueDecl *getKnownSingleDecl(ASTContext &SwiftContext, StringRef DeclName) { SmallVector decls; SwiftContext.lookupInSwiftModule(DeclName, decls); - assert(decls.size() < 2); + ASSERT(decls.size() < 2); if (decls.size() != 1) return nullptr; return decls[0]; } @@ -468,7 +468,7 @@ class SwiftifyProtocolInfoPrinter : public SwiftifyInfoPrinter { out << "\", paramInfo: ["; // reset firstParam inside paramInfo array. At this point firstParam will // always be false, so no need to save the current value. - assert(!firstParam); + ASSERT(!firstParam); firstParam = true; bool hadAttributes = swiftifyImpl(ImporterImpl, *this, Method, ClangDecl); firstParam = false; From b472bfa3380d47de0d0030090f5d30d400691017 Mon Sep 17 00:00:00 2001 From: "Henrik G. Olsson" Date: Fri, 24 Oct 2025 16:07:22 -0700 Subject: [PATCH 4/4] [ClangImporter] improve structure of swiftify debug logs This adds automatic scope management and indentation to the logs to make them more structured. Also adds some additional logging. Here's an example of what it can look like now: ``` [swiftify:593] Checking 'CountedByProtocol' protocol for methods with bounds and lifetime info [swiftify:350] | Checking 'simple::' for bounds and lifetime info [swiftify:411] | | Checking parameter 'len' with type 'int' [swiftify:411] | | Checking parameter 'p' with type 'int * __counted_by(len)' [swiftify:435] | | | Found bounds info 'int * __counted_by(len)' [swiftify:350] | Checking 'shared:::' for bounds and lifetime info [swiftify:411] | | Checking parameter 'len' with type 'int' [swiftify:411] | | Checking parameter 'p1' with type 'int * __counted_by(len)' [swiftify:435] | | | Found bounds info 'int * __counted_by(len)' [swiftify:411] | | Checking parameter 'p2' with type 'int * __counted_by(len)' [swiftify:435] | | | Found bounds info 'int * __counted_by(len)' [swiftify:350] | Checking 'complexExpr:::' for bounds and lifetime info [swiftify:411] | | Checking parameter 'len' with type 'int' [swiftify:411] | | Checking parameter 'offset' with type 'int' [swiftify:411] | | Checking parameter 'p' with type 'int * __counted_by(len - offset)' [swiftify:435] | | | Found bounds info 'int * __counted_by(len - offset)' [swiftify:350] | Checking 'nullUnspecified::' for bounds and lifetime info [swiftify:411] | | Checking parameter 'len' with type 'int' [swiftify:411] | | Checking parameter 'p' with type 'int * __counted_by(len) _Null_unspecified' [swiftify:435] | | | Found bounds info 'int * __counted_by(len) _Null_unspecified' [swiftify:350] | Checking 'nonnull::' for bounds and lifetime info [swiftify:411] | | Checking parameter 'len' with type 'int' [swiftify:411] | | Checking parameter 'p' with type 'int * __counted_by(len) _Nonnull' [swiftify:435] | | | Found bounds info 'int * __counted_by(len) _Nonnull' [swiftify:350] | Checking 'nullable::' for bounds and lifetime info [swiftify:411] | | Checking parameter 'len' with type 'int' [swiftify:411] | | Checking parameter 'p' with type 'int * __counted_by(len) _Nullable' [swiftify:435] | | | Found bounds info 'int * __counted_by(len) _Nullable' [swiftify:350] | Checking 'returnPointer:' for bounds and lifetime info [swiftify:379] | | Found bounds info 'int * __counted_by(len)' on return value [swiftify:411] | | Checking parameter 'len' with type 'int' [swiftify:350] | Checking 'staticMethod::' for bounds and lifetime info [swiftify:411] | | Checking parameter 'len' with type 'int' [swiftify:411] | | Checking parameter 'p' with type 'int * __counted_by(len)' [swiftify:435] | | | Found bounds info 'int * __counted_by(len)' ``` --- lib/ClangImporter/SwiftifyDecl.cpp | 103 ++++++++++++++++++++++------- 1 file changed, 80 insertions(+), 23 deletions(-) diff --git a/lib/ClangImporter/SwiftifyDecl.cpp b/lib/ClangImporter/SwiftifyDecl.cpp index 67976d27c085b..06a96050849b1 100644 --- a/lib/ClangImporter/SwiftifyDecl.cpp +++ b/lib/ClangImporter/SwiftifyDecl.cpp @@ -31,9 +31,38 @@ using namespace swift; using namespace importer; #define DEBUG_TYPE "safe-interop-wrappers" -#define DLOG(x) LLVM_DEBUG(llvm::dbgs() << x) + +#define DLOG(x) LLVM_DEBUG(LogIndentTracker::indent(DBGS) << x) + +#ifndef NDEBUG +#define DBGS llvm::dbgs() << "[swiftify:" << __LINE__ << "] " +#define DUMP(x) DLOG(""); x->dump() +#define DLOG_SCOPE(x) DLOG(x); LogIndentTracker Scope +#else +#define DLOG_SCOPE(x) do {} while(false); +#endif + namespace { +#ifndef NDEBUG +struct LogIndentTracker { + static thread_local uint8_t LogIndent; + static llvm::raw_ostream &indent(llvm::raw_ostream &out) { + for (uint8_t i = 0; i < LogIndent; i++) + out << "| "; + return out; + } + + LogIndentTracker() { + LogIndent++; + } + ~LogIndentTracker() { + LogIndent--; + } +}; +thread_local uint8_t LogIndentTracker::LogIndent = 0; +#endif + ValueDecl *getKnownSingleDecl(ASTContext &SwiftContext, StringRef DeclName) { SmallVector decls; SwiftContext.lookupInSwiftModule(DeclName, decls); @@ -189,8 +218,7 @@ struct CountedByExpressionValidator case clang::BuiltinType::ULong: case clang::BuiltinType::LongLong: case clang::BuiltinType::ULongLong: - // These integer literals are printed with a suffix that isn't valid Swift - // syntax + DLOG("Ignoring count parameter with non-portable integer literal"); return false; default: return true; @@ -229,7 +257,10 @@ struct CountedByExpressionValidator SUPPORTED_BINOP(Or) #undef SUPPORTED_BINOP - bool VisitStmt(const clang::Stmt *) { return false; } + bool VisitStmt(const clang::Stmt *) { + DLOG("Ignoring count parameter with unsupported expression\n"); + return false; + } }; @@ -248,16 +279,24 @@ static bool SwiftifiableSizedByPointerType(const clang::ASTContext &ctx, if (nonnullType->isOpaquePointer()) return true; PointerTypeKind PTK; - if (!nonnullType->getAnyPointerElementType(PTK)) + if (!nonnullType->getAnyPointerElementType(PTK)) { + DLOG("Ignoring sized_by on non-pointer type\n"); return false; + } if (PTK == PTK_UnsafeRawPointer || PTK == PTK_UnsafeMutableRawPointer) return true; - if (PTK != PTK_UnsafePointer && PTK != PTK_UnsafeMutablePointer) + if (PTK != PTK_UnsafePointer && PTK != PTK_UnsafeMutablePointer) { + DLOG("Ignoring sized_by on Autoreleasing pointer\n"); + CONDITIONAL_ASSERT(PTK == PTK_AutoreleasingUnsafeMutablePointer); return false; + } // We have a pointer to a type with a size. Verify that it is char-sized. auto PtrT = CAT->getAs(); auto PointeeT = PtrT->getPointeeType(); - return ctx.getTypeSizeInChars(PointeeT).isOne(); + bool isByteSized ctx.getTypeSizeInChars(PointeeT).isOne(); + if (!isByteSized) + DLOG("Ignoring sized_by on non-byte-sized pointer\n"); + return isByteSized; } static bool SwiftifiableCAT(const clang::ASTContext &ctx, const clang::CountAttributedType *CAT, @@ -280,6 +319,7 @@ struct UnaliasedInstantiationVisitor bool VisitTemplateSpecializationType(const clang::TemplateSpecializationType *) { hasUnaliasedInstantiation = true; + DLOG("Signature contains raw template, skipping"); return false; } }; @@ -318,7 +358,7 @@ static bool swiftifyImpl(ClangImporter::Implementation &Self, SwiftifyInfoPrinter &printer, const AbstractFunctionDecl *MappedDecl, const T *ClangDecl) { - DLOG("Checking " << *ClangDecl << " for bounds and lifetime info\n"); + DLOG_SCOPE("Checking '" << *ClangDecl << "' for bounds and lifetime info\n"); // FIXME: for private macro generated functions we do not serialize the // SILFunction's body anywhere triggering assertions. @@ -347,7 +387,7 @@ static bool swiftifyImpl(ClangImporter::Implementation &Self, auto *CAT = clangReturnTy->getAs(); if (SwiftifiableCAT(clangASTContext, CAT, swiftReturnTy)) { printer.printCountedBy(CAT, SwiftifyInfoPrinter::RETURN_VALUE_INDEX); - DLOG(" Found bounds info '" << clang::QualType(CAT, 0) << "' on return value\n"); + DLOG("Found bounds info '" << clang::QualType(CAT, 0) << "' on return value\n"); attachMacro = true; } auto dependsOnClass = [](const ParamDecl *fromParam) { @@ -355,12 +395,14 @@ static bool swiftifyImpl(ClangImporter::Implementation &Self, }; bool returnHasLifetimeInfo = false; if (getImplicitObjectParamAnnotation(ClangDecl)) { - DLOG(" Found lifetimebound attribute on implicit 'this'\n"); + DLOG("Found lifetimebound attribute on implicit 'this'\n"); if (!dependsOnClass( MappedDecl->getImplicitSelfDecl(/*createIfNeeded*/ true))) { printer.printLifetimeboundReturn(SwiftifyInfoPrinter::SELF_PARAM_INDEX, true); returnHasLifetimeInfo = true; + } else { + DLOG("lifetimebound ignored because it depends on class with refcount\n"); } } @@ -378,6 +420,8 @@ static bool swiftifyImpl(ClangImporter::Implementation &Self, : getNumParams(ClangDecl); for (auto [index, clangParam] : llvm::enumerate(ClangDecl->parameters())) { clang::QualType clangParamTy = clangParam->getType(); + DLOG_SCOPE("Checking parameter '" << *clangParam << "' with type '" + << clangParamTy << "'\n"); int mappedIndex = index < selfParamIndex ? index : index > selfParamIndex ? index - 1 : SwiftifyInfoPrinter::SELF_PARAM_INDEX; @@ -401,8 +445,7 @@ static bool swiftifyImpl(ClangImporter::Implementation &Self, diag::note_swift_name_instance_method); } else if (SwiftifiableCAT(clangASTContext, CAT, swiftParamTy)) { printer.printCountedBy(CAT, mappedIndex); - DLOG(" Found bounds info '" << clangParamTy << "' on parameter '" - << *clangParam << "'\n"); + DLOG("Found bounds info '" << clangParamTy << "'\n"); attachMacro = paramHasBoundsInfo = true; } bool paramIsStdSpan = @@ -411,13 +454,12 @@ static bool swiftifyImpl(ClangImporter::Implementation &Self, bool paramHasLifetimeInfo = false; if (clangParam->template hasAttr()) { - DLOG(" Found noescape attribute on parameter '" << *clangParam << "'\n"); + DLOG("Found noescape attribute\n"); printer.printNonEscaping(mappedIndex); paramHasLifetimeInfo = true; } if (clangParam->template hasAttr()) { - DLOG(" Found lifetimebound attribute on parameter '" << *clangParam - << "'\n"); + DLOG("Found lifetimebound attribute\n"); if (!dependsOnClass(swiftParam)) { // If this parameter has bounds info we will tranform it into a Span, // so then it will no longer be Escapable. @@ -428,16 +470,17 @@ static bool swiftifyImpl(ClangImporter::Implementation &Self, printer.printLifetimeboundReturn(mappedIndex, willBeEscapable); paramHasLifetimeInfo = true; returnHasLifetimeInfo = true; + } else { + DLOG("lifetimebound ignored because it depends on class with refcount\n"); } } if (paramIsStdSpan && paramHasLifetimeInfo) { - DLOG(" Found both std::span and lifetime info for parameter '" - << *clangParam << "'\n"); + DLOG("Found both std::span and lifetime info\n"); attachMacro = true; } } if (returnIsStdSpan && returnHasLifetimeInfo) { - DLOG(" Found both std::span and lifetime info for return value\n"); + DLOG("Found both std::span and lifetime info for return value\n"); attachMacro = true; } } @@ -493,6 +536,11 @@ void ClangImporter::Implementation::swiftify(AbstractFunctionDecl *MappedDecl) { if (!ClangDecl) return; + DLOG_SCOPE( + "Checking '" << *ClangDecl << "' (imported as '" + << MappedDecl->getName().getBaseName().userFacingName() + << "')\n"); + { UnaliasedInstantiationVisitor visitor; visitor.TraverseType(ClangDecl->getType()); @@ -501,8 +549,10 @@ void ClangImporter::Implementation::swiftify(AbstractFunctionDecl *MappedDecl) { } MacroDecl *SwiftifyImportDecl = dyn_cast_or_null(getKnownSingleDecl(SwiftContext, "_SwiftifyImport")); - if (!SwiftifyImportDecl) + if (!SwiftifyImportDecl) { + DLOG("_SwiftifyImport macro not found"); return; + } llvm::SmallString<128> MacroString; { @@ -510,8 +560,10 @@ void ClangImporter::Implementation::swiftify(AbstractFunctionDecl *MappedDecl) { out << "@_SwiftifyImport"; SwiftifyInfoPrinter printer(getClangASTContext(), SwiftContext, out, *SwiftifyImportDecl); - if (!swiftifyImpl(*this, printer, MappedDecl, ClangDecl)) + if (!swiftifyImpl(*this, printer, MappedDecl, ClangDecl)) { + DLOG("No relevant bounds or lifetime info found\n"); return; + } printer.printAvailability(); printer.printTypeMapping(); } @@ -546,10 +598,13 @@ void ClangImporter::Implementation::swiftifyProtocol( MacroDecl *SwiftifyImportDecl = dyn_cast_or_null( getKnownSingleDecl(SwiftContext, "_SwiftifyImportProtocol")); - if (!SwiftifyImportDecl) + if (!SwiftifyImportDecl) { + DLOG("_SwiftifyImportProtocol macro not found"); return; + } - DLOG("Checking " << MappedDecl->getName() << " protocol for methods with bounds and lifetime info\n"); + DLOG_SCOPE("Checking '" << MappedDecl->getName() + << "' protocol for methods with bounds and lifetime info\n"); llvm::SmallString<128> MacroString; { llvm::raw_svector_ostream out(MacroString); @@ -566,8 +621,10 @@ void ClangImporter::Implementation::swiftifyProtocol( hasBoundsAttributes |= printer.printMethod(SwiftDecl); } - if (!hasBoundsAttributes) + if (!hasBoundsAttributes) { + DLOG("No relevant bounds or lifetime info found\n"); return; + } printer.printAvailability(); printer.printTypeMapping(); }