swiftlang / swift

The Swift Programming Language
https://swift.org
Apache License 2.0
67.27k stars 10.33k forks source link

Building a very specific macro that references a parameter pack type crashes #74865

Open mattmassicotte opened 2 months ago

mattmassicotte commented 2 months ago

Description

I swear I tried my best to reduce this. Every time I tried, the crash would go away. So, what I did instead was create a branch that will reproduce the issue. I know this is pretty far for ideal, but is the best I could do. I would be happy to include more information if there's anything I can do to help.

https://github.com/mattmassicotte/Empire/tree/macro-parse-crash

Running the tests for EmpireTests will reproduce the crash.

The line that is ultimately responsible seems to be Sources/EmpireMacros/IndexKeyRecordMacro.swift:186

Relevent line in the macro generation that kills it:

queryArguments = "a, last: b"

public static func select(in context: TransactionContext, \(raw: selectArguments)) throws -> [Self] {
    try context.select(query: Query(\(raw: queryArguments)))
}

It is something about the formation of Query(\(raw: queryArguments)) + the content of queryArguments that is the problem. I have been able to make the crash disappear by changing and/or removing the label. It is matching an initializer that looks like this:

public struct Query<each Component: QueryComponent, Last: QueryComponent> {
    public let last: ComparisonOperator<Last>
    public let components: (repeat each Component)

    public init(_ value: repeat each Component, last: ComparisonOperator<Last>) {
        self.components = (repeat each value)
        self.last = last
    }
}

Reproduction

See the attached branch. I was unable to reduce this further.

Stack dump

Thread 0 Crashed::  Dispatch queue: com.apple.main-thread
0   libsystem_kernel.dylib                 0x192746a60 __pthread_kill + 8
1   libsystem_pthread.dylib                0x19277ec20 pthread_kill + 288
2   libsystem_c.dylib                      0x19268bac4 __abort + 136
3   libsystem_c.dylib                      0x19268ba3c abort + 192
4   libsystem_c.dylib                      0x19268ad20 __assert_rtn + 284
5   swift-frontend                         0x105e12dc8 swift::constraints::ArgumentMismatchFailure::diagnoseAttemptedRegexBuilder() const (.cold.2) + 36
6   swift-frontend                         0x10168a4a0 swift::constraints::ArgumentMismatchFailure::diagnoseAttemptedRegexBuilder() const + 716
7   swift-frontend                         0x10167509c swift::constraints::ArgumentMismatchFailure::diagnoseAsError() + 148
8   swift-frontend                         0x10165c76c swift::constraints::AllowArgumentMismatch::diagnose(swift::constraints::Solution const&, bool) const + 112
9   swift-frontend                         0x101589acc swift::constraints::ConstraintSystem::applySolutionFixes(swift::constraints::Solution const&) + 1284
10  swift-frontend                         0x10158a15c swift::constraints::ConstraintSystem::applySolution(swift::constraints::Solution&, swift::constraints::SyntacticElementTarget) + 96
11  swift-frontend                         0x1017d2840 swift::TypeChecker::typeCheckTarget(swift::constraints::SyntacticElementTarget&, swift::optionset::OptionSet<swift::TypeCheckExprFlags, unsigned int>) + 396
12  swift-frontend                         0x1018a8ad8 swift::ASTVisitor<(anonymous namespace)::StmtChecker, void, swift::Stmt*, void, void, void, void>::visit(swift::Stmt*) + 1116
13  swift-frontend                         0x1018a8648 bool (anonymous namespace)::StmtChecker::typeCheckStmt<swift::Stmt>(swift::Stmt*&) + 136
14  swift-frontend                         0x1018a4e9c (anonymous namespace)::StmtChecker::typeCheckASTNode(swift::ASTNode&) + 192
15  swift-frontend                         0x1018a871c swift::ASTVisitor<(anonymous namespace)::StmtChecker, void, swift::Stmt*, void, void, void, void>::visit(swift::Stmt*) + 160
16  swift-frontend                         0x1018a6d3c bool (anonymous namespace)::StmtChecker::typeCheckStmt<swift::BraceStmt>(swift::BraceStmt*&) + 136
17  swift-frontend                         0x1018a6490 (anonymous namespace)::StmtChecker::typeCheckBody(swift::BraceStmt*&) + 32
18  swift-frontend                         0x1018a61f8 swift::TypeCheckFunctionBodyRequest::evaluate(swift::Evaluator&, swift::AbstractFunctionDecl*) const + 1356
19  swift-frontend                         0x101cedf18 swift::TypeCheckFunctionBodyRequest::OutputType swift::Evaluator::getResultUncached<swift::TypeCheckFunctionBodyRequest, swift::TypeCheckFunctionBodyRequest::OutputType swift::evaluateOrDefault<swift::TypeCheckFunctionBodyRequest>(swift::Evaluator&, swift::TypeCheckFunctionBodyRequest, swift::TypeCheckFunctionBodyRequest::OutputType)::'lambda'()>(swift::TypeCheckFunctionBodyRequest const&, swift::TypeCheckFunctionBodyRequest::OutputType swift::evaluateOrDefault<swift::TypeCheckFunctionBodyRequest>(swift::Evaluator&, swift::TypeCheckFunctionBodyRequest, swift::TypeCheckFunctionBodyRequest::OutputType)::'lambda'()) + 240
20  swift-frontend                         0x101c5b4cc swift::AbstractFunctionDecl::getTypecheckedBody() const + 120
21  swift-frontend                         0x101d95274 swift::SourceFile::typeCheckDelayedFunctions() + 104
22  swift-frontend                         0x1018e6de0 swift::TypeCheckSourceFileRequest::evaluate(swift::Evaluator&, swift::SourceFile*) const + 256
23  swift-frontend                         0x1018e8938 swift::TypeCheckSourceFileRequest::OutputType swift::Evaluator::getResultUncached<swift::TypeCheckSourceFileRequest, swift::TypeCheckSourceFileRequest::OutputType swift::evaluateOrDefault<swift::TypeCheckSourceFileRequest>(swift::Evaluator&, swift::TypeCheckSourceFileRequest, swift::TypeCheckSourceFileRequest::OutputType)::'lambda'()>(swift::TypeCheckSourceFileRequest const&, swift::TypeCheckSourceFileRequest::OutputType swift::evaluateOrDefault<swift::TypeCheckSourceFileRequest>(swift::Evaluator&, swift::TypeCheckSourceFileRequest, swift::TypeCheckSourceFileRequest::OutputType)::'lambda'()) + 232
24  swift-frontend                         0x1018e6cb8 swift::performTypeChecking(swift::SourceFile&) + 84
25  swift-frontend                         0x1007c29a0 bool llvm::function_ref<bool (swift::SourceFile&)>::callback_fn<swift::CompilerInstance::performSema()::$_7>(long, swift::SourceFile&) + 16
26  swift-frontend                         0x1007bc3e4 swift::CompilerInstance::forEachFileToTypeCheck(llvm::function_ref<bool (swift::SourceFile&)>) + 76
27  swift-frontend                         0x1007bc378 swift::CompilerInstance::performSema() + 76
28  swift-frontend                         0x10058db9c withSemanticAnalysis(swift::CompilerInstance&, swift::FrontendObserver*, llvm::function_ref<bool (swift::CompilerInstance&)>, bool) + 60
29  swift-frontend                         0x10057ef48 performCompile(swift::CompilerInstance&, int&, swift::FrontendObserver*) + 708
30  swift-frontend                         0x10057deb4 swift::performFrontend(llvm::ArrayRef<char const*>, char const*, void*, swift::FrontendObserver*) + 2376
31  swift-frontend                         0x100397808 swift::mainEntry(int, char const**) + 3096
32  dyld                                   0x1923f60e0 start + 2360

Expected behavior

The macro should build valid code.

Environment

Apple Swift version 6.0-dev (LLVM 5b37bd4c1c578e9, Swift 54898ce105c7185) Target: arm64-apple-macosx14.0

Additional information

No response

mattmassicotte commented 1 month ago

Ok it is possible I have made progress despite me having aboslutely no idea what I am doing.

I've narrowed down the problem to a constructor:

  ArgumentMismatchFailure(const Solution &solution, Type argType,
                          Type paramType, ConstraintLocator *locator,
                          FixBehavior fixBehavior =
                              FixBehavior::Error)
      : ContextualFailure(solution, argType, paramType, locator, fixBehavior),
        Info(*getFunctionArgApplyInfo(getLocator())) {}

This ends up invoking:

std::optional<FunctionArgApplyInfo>
Solution::getFunctionArgApplyInfo(ConstraintLocator *locator) const

which is, as you might have guessed, returning null. That's being unconditionally dereferenced in the constructor above, causing the crash.

mattmassicotte commented 1 month ago

Ok! Solution::getFunctionArgApplyInfo is getting all the way to the end, calling FunctionArgApplyInfo::get. That function is the one returning std::nullopt, because the following is true:

paramIdx >= fnType->getNumParams()

mattmassicotte commented 1 month ago

Thanks to the forums, I have more information by running this with -Xfrontend -debug-constraints. A lot of output was produced, but I'm including the last bit becuase it seems the most relevant. If more is needed, I can get it!

Overload choices:
  locator@0x1569ea850 [DeclRef@@__swiftmacro_11EmpireTests10TestRecord08IndexKeyD0fMe_.swift:30:44] with EmpireTests.(file).TestRecord extension.select(in:a:).a@@__swiftmacro_11EmpireTests10TestRecord08IndexKeyD0fMe_.swift:29:63 as a: String
  locator@0x1569ea200 [DeclRef@@__swiftmacro_11EmpireTests10TestRecord08IndexKeyD0fMe_.swift:30:10] with EmpireTests.(file).TestRecord extension.select(in:a:).context@@__swiftmacro_11EmpireTests10TestRecord08IndexKeyD0fMe_.swift:29:34 as context: TransactionContext
  locator@0x1569eaa10 [Call@@__swiftmacro_11EmpireTests10TestRecord08IndexKeyD0fMe_.swift:30:32 → apply function → constructor member] with Empire.(file).Query.init(_:last:)@/Users/matt/Developer/Empire/Sources/Empire/Query.swift:42:9 as Query<Pack{/* shape: $T2 */ repeat $T2}, $T3>.Type.init: ($T9, ComparisonOperator<$T8>) -> Query<Pack{/* shape: $T2 */ repeat $T2}, $T3>
  locator@0x1569ea278 [UnresolvedDot@@__swiftmacro_11EmpireTests10TestRecord08IndexKeyD0fMe_.swift:30:18 → member] with Empire.(file).TransactionContext.select(query:)@/Users/matt/Developer/Empire/Sources/Empire/Store.swift:74:14 as TransactionContext.select: (Query<Pack{$T14}, $T13>) throws -> sending [$T11]
Constraint restrictions:
  Query<<<hole>>> to Query<<<hole>>> is [deep equality]
  Array<TestRecord> to Array<TestRecord> is [deep equality]
Trailing closure matching:
  locator@0x1569ea250 [UnresolvedDot@@__swiftmacro_11EmpireTests10TestRecord08IndexKeyD0fMe_.swift:30:18 → apply argument]: forward
  locator@0x1569ea978 [Call@@__swiftmacro_11EmpireTests10TestRecord08IndexKeyD0fMe_.swift:30:32 → apply argument]: forward
  locator@0x156c49df8 [Call@@__swiftmacro_11EmpireTests10TestRecord08IndexKeyD0fMe_.swift:30:18 → apply argument]: forward
Opened types:
  locator@0x1569ea418 [Type@@__swiftmacro_11EmpireTests10TestRecord08IndexKeyD0fMe_.swift:30:32] opens 'Last' (τ_0_1) -> <<hole>> [from $T3], 'each Component' (each τ_0_0) -> Pack{} [from $T2]
  locator@0x1569eaa10 [Call@@__swiftmacro_11EmpireTests10TestRecord08IndexKeyD0fMe_.swift:30:32 → apply function → constructor member] opens 'Last' (τ_0_1) -> <<hole>> [from $T8], 'each Component' (each τ_0_0) -> Pack{} [from $T7]
  locator@0x1569ea278 [UnresolvedDot@@__swiftmacro_11EmpireTests10TestRecord08IndexKeyD0fMe_.swift:30:18 → member] opens 'Last' (τ_0_2) -> <<hole>> [from $T13], 'Record' (τ_0_0) -> TestRecord [from $T11], 'each Component' (each τ_0_1) -> Pack{} [from $T12]
Defaulted constraints: locator@0x1569ea490 [Type@@__swiftmacro_11EmpireTests10TestRecord08IndexKeyD0fMe_.swift:30:32 → generic parameter 'Last']
Fixes:
  [fix: allow argument to parameter type conversion mismatch] @ locator@0x156c493d8 [Call@@__swiftmacro_11EmpireTests10TestRecord08IndexKeyD0fMe_.swift:30:32 → apply argument → comparing call argument #0 to parameter #1]
  [fix: default generic argument 'Last' to 'Any'] @ locator@0x1569ea418 [Type@@__swiftmacro_11EmpireTests10TestRecord08IndexKeyD0fMe_.swift:30:32]
Assertion failed: (!HasOriginalArgs && "Query original args instead"), function isTrailingClosureIndex, file ArgumentList.h, line 434.
mattmassicotte commented 1 month ago

Here are sample sources that reproduce the issue.

Input file:

public enum ComparisonOperator<Value: Comparable> {
}

public struct Query<each Component: Comparable, Last: Comparable> {
    public let last: ComparisonOperator<Last>
    public let components: (repeat each Component)

    public init(_ value: repeat each Component, last: ComparisonOperator<Last>) {
        self.components = (repeat each value)
        self.last = last
    }
}

public protocol IndexKeyRecord {
}

@attached(
    extension,
    conformances: IndexKeyRecord,
    names:
        named(select)
)
public macro IndexKeyRecord(
    _ first: StaticString,
    _ remaining: StaticString...
) = #externalMacro(module: "EmpireMacros", type: "IndexKeyRecordMacro")

@IndexKeyRecord("a")
struct TestRecord: Hashable {
    let a: String
}

Macro definition:

import SwiftSyntax
import SwiftSyntaxBuilder
import SwiftSyntaxMacros

public struct IndexKeyRecordMacro: ExtensionMacro {
    public static func expansion(
        of node: AttributeSyntax,
        attachedTo declaration: some DeclGroupSyntax,
        providingExtensionsOf type: some TypeSyntaxProtocol,
        conformingTo protocols: [TypeSyntax],
        in context: some MacroExpansionContext
    ) throws -> [ExtensionDeclSyntax] {
        return [
            try dataManipulationExtensionDecl(type: type),
        ]
    }
}

extension IndexKeyRecordMacro {
    private static func dataManipulationExtensionDecl(
        type: some TypeSyntaxProtocol
    ) throws -> ExtensionDeclSyntax {
        let queryArguments = "last: a"
        let funcDecl = try FunctionDeclSyntax(
"""
public static func select() {
Query(\(raw: queryArguments))
}
"""
        )

        return ExtensionDeclSyntax(
            extendedType: type,
            memberBlock: MemberBlockSyntax(
                members: MemberBlockItemListSyntax(
                    [MemberBlockItemSyntax(decl: funcDecl)]
                )
            )
        )
    }
}