Referencing peer macro expansion code in Release build causes EXC_BAD_ACCESS

Hello everyone,

We wrote several macros and included them in our production code. In Debug build config, everything works just fine. In Release build config, the member and memberAttribute macros work fine, but for some reason, when the app tries to use the expanded peer macro code, it crashes with BAD_ACCESS.

Example:
Macro declaration:

@attached(peer, names: suffixed(Impl))
public macro Repository() = #externalMacro(module: "CompanyMacrosMacros", type: "RepositoryMacro")

Is attached to e.g. ProductRepository and is expected to generate ProductRepositoryImpl class.

Any idea why this might be happening?

Hi there! :wave:

Could you please share the implementation of the macro? If it includes proprietary company code, would you be able to craft a simplified version that still reproduces the issue?

1 Like

Hi Matej, thanks for a quick reply.

Here is a redacted code of the macro implementation:


public struct RepositoryMacro: PeerMacro {
    public static func expansion(
        of node: AttributeSyntax,
        providingPeersOf declaration: some DeclSyntaxProtocol,
        in context: some MacroExpansionContext) throws -> [DeclSyntax] {
            if let functionDecl = declaration.as(FunctionDeclSyntax.self) {
                if functionDecl.body != nil {
                    let protocolError = Diagnostic(node: Syntax(node), message: RepositoryDiagnostics.hasToBeProtocolDeclaration)
                    context.diagnose(protocolError)
                }
                return []
            }
            
            guard let protocolDecl = declaration.as(ProtocolDeclSyntax.self) else {
                let protocolError = Diagnostic(node: Syntax(node), message: RepositoryDiagnostics.notAProtocolOrFunction)
                context.diagnose(protocolError)
                return []
            }
            
            let (functions, shouldGenerateAllFunctions) = getRelevantFunctions(from: protocolDecl)
            
            guard functions.count > 0 else {
                let protocolError = Diagnostic(node: Syntax(node), message: RepositoryDiagnostics.noFunctionToBeGenerated)
                context.diagnose(protocolError)
                return []
            }
            
            let protocolName = protocolDecl.name.text
            let endpointName = protocolName.replacingOccurrences(of: "Repository", with: "Endpoint")
            
            let extensionDecl = ClassDeclSyntax(
                attributes: AttributeListSyntax {
                    AttributeSyntax(stringLiteral: "final")
                },
                name: TokenSyntax(stringLiteral: "\(protocolName)Impl"),
                inheritanceClause: InheritanceClauseSyntax {
                    InheritedTypeSyntax(type: TypeSyntax(stringLiteral: "Repository"))
                    if shouldGenerateAllFunctions {
                        InheritedTypeSyntax(type: TypeSyntax(stringLiteral: protocolName))
                    }
                },
                memberBlock: MemberBlockSyntax {
                    for function in functions {
                        let functionName = function.name.text
                        let parameterNames = function.signature.parameterClause.parameters.map { $0.secondName?.text ?? $0.firstName.text }
                        getFunctionDeclSyntax(for: function, endpointName, parameterNames)
                    }
                }
            )
            return [DeclSyntax(extensionDecl)]
        }
}

Is it enough for you?

Thanks for the implementation. The attached code doesn't produce such an error on my side, are you sure that that's all?