From 0d9707656b7eb673db99475992f1acca015c1be2 Mon Sep 17 00:00:00 2001 From: "Henrik G. Olsson" Date: Fri, 9 May 2025 10:27:27 -0700 Subject: [PATCH] Prevent silgen for macro expansions with type errors (#81396) Due to a bug in how macros on nodes imported from clang are evaluated, their function body is not always type checked. This forces type checking before silgen of a macro originating on a node imported from clang, to prevent crashing in silgen. rdar://150940383 (cherry picked from commit efd70b1f54c563be46178bfa8c85937b60ac3b15) --- include/swift/AST/Decl.h | 4 +++ lib/AST/Decl.cpp | 30 +++++++++++++++++++ lib/SILGen/SILGen.cpp | 6 ++-- .../usr/include/imported_macro_error.h | 7 +++++ .../usr/include/module.modulemap | 6 +++- test/Macros/Inputs/macro_library.swift | 3 ++ .../Inputs/syntax_macro_definitions.swift | 23 ++++++++++++++ test/Macros/imported_type_error.swift | 20 +++++++++++++ 8 files changed, 96 insertions(+), 3 deletions(-) create mode 100644 test/Inputs/clang-importer-sdk/usr/include/imported_macro_error.h create mode 100644 test/Macros/imported_type_error.swift diff --git a/include/swift/AST/Decl.h b/include/swift/AST/Decl.h index b5248b9cdf7c8..dc727939de6dd 100644 --- a/include/swift/AST/Decl.h +++ b/include/swift/AST/Decl.h @@ -1154,6 +1154,10 @@ class alignas(1 << DeclAlignInBits) Decl : public ASTAllocated, public Swi /// constructed from a serialized module. bool isInMacroExpansionInContext() const; + /// Whether this declaration is within a macro expansion relative to + /// its decl context, and the macro was attached to a node imported from clang. + bool isInMacroExpansionFromClangHeader() const; + /// Returns the appropriate kind of entry point to generate for this class, /// based on its attributes. /// diff --git a/lib/AST/Decl.cpp b/lib/AST/Decl.cpp index ad9a05829803e..fe85743310a2d 100644 --- a/lib/AST/Decl.cpp +++ b/lib/AST/Decl.cpp @@ -1016,6 +1016,36 @@ bool Decl::isInMacroExpansionInContext() const { return file->getFulfilledMacroRole() != std::nullopt; } +bool Decl::isInMacroExpansionFromClangHeader() const { + SourceLoc declLoc = getLoc(); + if (declLoc.isInvalid()) + return false; + + auto &ctx = getASTContext(); + auto &SourceMgr = ctx.SourceMgr; + + auto declBufferID = SourceMgr.findBufferContainingLoc(declLoc); + auto declGeneratedSourceInfo = SourceMgr.getGeneratedSourceInfo(declBufferID); + if (!declGeneratedSourceInfo) + return false; + CustomAttr *attr = declGeneratedSourceInfo->attachedMacroCustomAttr; + if (!attr) + return false; + + SourceLoc macroAttrLoc = attr->AtLoc; + if (macroAttrLoc.isInvalid()) + return false; + + auto macroAttrBufferID = SourceMgr.findBufferContainingLoc(macroAttrLoc); + auto macroAttrGeneratedSourceInfo = + SourceMgr.getGeneratedSourceInfo(macroAttrBufferID); + if (!macroAttrGeneratedSourceInfo) + return false; + + return macroAttrGeneratedSourceInfo->kind == + GeneratedSourceInfo::AttributeFromClang; +} + SourceLoc Decl::getLocFromSource() const { switch (getKind()) { #define DECL(ID, X) \ diff --git a/lib/SILGen/SILGen.cpp b/lib/SILGen/SILGen.cpp index 87c17bf3fb179..6f544a5e477aa 100644 --- a/lib/SILGen/SILGen.cpp +++ b/lib/SILGen/SILGen.cpp @@ -689,9 +689,11 @@ static bool shouldEmitFunctionBody(const AbstractFunctionDecl *AFD) { return false; auto &ctx = AFD->getASTContext(); - if (ctx.TypeCheckerOpts.EnableLazyTypecheck) { + if (ctx.TypeCheckerOpts.EnableLazyTypecheck || AFD->isInMacroExpansionFromClangHeader()) { // Force the function body to be type-checked and then skip it if there - // have been any errors. + // have been any errors. Normally macro expansions are type checked in the module they + // expand in - this does not apply to swift macros applied to nodes imported from clang, + // so force type checking of them here if they haven't already, to prevent crashing. (void)AFD->getTypecheckedBody(); // FIXME: Only skip bodies that contain type checking errors. diff --git a/test/Inputs/clang-importer-sdk/usr/include/imported_macro_error.h b/test/Inputs/clang-importer-sdk/usr/include/imported_macro_error.h new file mode 100644 index 0000000000000..3498d99cbbd51 --- /dev/null +++ b/test/Inputs/clang-importer-sdk/usr/include/imported_macro_error.h @@ -0,0 +1,7 @@ +#if __SWIFT_ATTR_SUPPORTS_MACROS +#define ERROR_MACRO __attribute__((swift_attr("@macro_library.ExpandTypeError"))) +#else +#define ERROR_MACRO +#endif + +void foo() ERROR_MACRO; diff --git a/test/Inputs/clang-importer-sdk/usr/include/module.modulemap b/test/Inputs/clang-importer-sdk/usr/include/module.modulemap index c396aac08d3d2..750a21baab1a6 100644 --- a/test/Inputs/clang-importer-sdk/usr/include/module.modulemap +++ b/test/Inputs/clang-importer-sdk/usr/include/module.modulemap @@ -156,4 +156,8 @@ module IncompleteTypes { module CompletionHandlerGlobals { header "completion_handler_globals.h" -} \ No newline at end of file +} + +module ImportedMacroError { + header "imported_macro_error.h" +} diff --git a/test/Macros/Inputs/macro_library.swift b/test/Macros/Inputs/macro_library.swift index ac1b4212ae97e..0a8e835f2d28a 100644 --- a/test/Macros/Inputs/macro_library.swift +++ b/test/Macros/Inputs/macro_library.swift @@ -66,3 +66,6 @@ case something @attached(peer, names: overloaded) public macro AcceptedDotted(_: Something) = #externalMacro(module: "MacroDefinition", type: "EmptyPeerMacro") + +@attached(peer, names: overloaded) +public macro ExpandTypeError() = #externalMacro(module: "MacroDefinition", type: "ExpandTypeErrorMacro") diff --git a/test/Macros/Inputs/syntax_macro_definitions.swift b/test/Macros/Inputs/syntax_macro_definitions.swift index 2cfce74ce198a..d03ab50743d61 100644 --- a/test/Macros/Inputs/syntax_macro_definitions.swift +++ b/test/Macros/Inputs/syntax_macro_definitions.swift @@ -1215,6 +1215,29 @@ public struct AddCompletionHandler: PeerMacro { } } +public struct ExpandTypeErrorMacro: PeerMacro { + public static func expansion< + Context: MacroExpansionContext, + Declaration: DeclSyntaxProtocol + >( + of node: AttributeSyntax, + providingPeersOf declaration: Declaration, + in context: Context + ) throws -> [DeclSyntax] { + guard let funcDecl = declaration.as(FunctionDeclSyntax.self) else { + throw CustomError.message("@ExpandTypeError only works on functions") + } + return [ + """ + public func \(funcDecl.name)(_ bar: Int) { + callToMissingFunction(foo) + } + """ + ] + } +} + + public struct InvalidMacro: PeerMacro, DeclarationMacro { public static func expansion( of node: AttributeSyntax, diff --git a/test/Macros/imported_type_error.swift b/test/Macros/imported_type_error.swift new file mode 100644 index 0000000000000..a6dc68e47cb3e --- /dev/null +++ b/test/Macros/imported_type_error.swift @@ -0,0 +1,20 @@ +// REQUIRES: swift_swift_parser, executable_test +// REQUIRES: swift_feature_MacrosOnImports + +// RUN: %empty-directory(%t) +// RUN: %host-build-swift -swift-version 5 -emit-library -o %t/%target-library-name(MacroDefinition) -module-name=MacroDefinition %S/Inputs/syntax_macro_definitions.swift -g -no-toolchain-stdlib-rpath -swift-version 5 + +// Build the macro library to give us access to ExpandTypeError. +// RUN: %target-swift-frontend -swift-version 5 -emit-module -o %t/macro_library.swiftmodule %S/Inputs/macro_library.swift -module-name macro_library -load-plugin-library %t/%target-library-name(MacroDefinition) + +// FIXME: we should typecheck these macro expansions before silgen +// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -typecheck -verify -swift-version 5 -enable-experimental-feature MacrosOnImports -load-plugin-library %t/%target-library-name(MacroDefinition) -module-name ErrorModuleUser %s -I %t + +// RUN: not %target-swift-frontend(mock-sdk: %clang-importer-sdk) -emit-module -swift-version 5 -enable-experimental-feature MacrosOnImports -load-plugin-library %t/%target-library-name(MacroDefinition) -module-name ErrorModuleUser %s -I %t 2>&1 | %FileCheck %s + +import ImportedMacroError +import macro_library + +foo(42) + +// CHECK: error: cannot find 'callToMissingFunction' in scope