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
This commit is contained in:
Henrik G. Olsson
2025-05-09 10:27:27 -07:00
committed by GitHub
parent 6bb2b64b4d
commit efd70b1f54
8 changed files with 96 additions and 3 deletions

View File

@@ -1151,6 +1151,10 @@ public:
/// 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.
///

View File

@@ -1020,6 +1020,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) \

View File

@@ -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.

View File

@@ -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;

View File

@@ -156,4 +156,8 @@ module IncompleteTypes {
module CompletionHandlerGlobals {
header "completion_handler_globals.h"
}
}
module ImportedMacroError {
header "imported_macro_error.h"
}

View File

@@ -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")

View File

@@ -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,

View File

@@ -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