apollographql / apollo-ios

📱  A strongly-typed, caching GraphQL client for iOS, written in Swift.
https://www.apollographql.com/docs/ios/
MIT License
3.87k stars 718 forks source link

Segmentation fault 11 on codegen #3445

Open Almaz5200 opened 11 hours ago

Almaz5200 commented 11 hours ago

Summary

I’m experiencing a runtime crash when using Apollo codegen with Xcode 16 in Swift 5 language mode. The project compiles successfully, but upon running, it crashes with a “Segmentation fault: 11” error. When debugging, the crash points to line 15 in Collection+ConcurrentCompactMap.swift.

Version

1.15.1

Steps to reproduce the behavior

1.  Use Apollo codegen in a project with Xcode 16 and Swift 5 language mode.
2.  Compile the project (compiles without errors).
3.  Run the application.
4.  Encounter the runtime crash with “Segmentation fault: 11”.

Logs

Thread 1 Queue : com.apple.main-thread (serial)
#0  0x000000019ab63e34 in mach_msg2_trap ()
#1  0x000000019ab765d0 in mach_msg2_internal ()
#2  0x000000019ab6c9d8 in mach_msg_overwrite ()
#3  0x000000019ab6417c in mach_msg ()
#4  0x000000019ac8e73c in __CFRunLoopServiceMachPort ()
#5  0x000000019ac8cfec in __CFRunLoopRun ()
#6  0x000000019ac8c4b4 in CFRunLoopRunSpecific ()
#7  0x000000019ad0737c in CFRunLoopRun ()
#8  0x0000000274452dc4 in swift_task_asyncMainDrainQueueImpl ()
#9  0x0000000274452d84 in swift_task_asyncMainDrainQueue ()
#10 0x00000001000053dc in main ()
#11 0x000000019a823274 in start ()
Thread 2#0  0x0000000100aacce8 in start_wqthread ()
JavaScriptCore libpas scavenger (3)#0   0x000000019ab675cc in __psynch_cvwait ()
#1  0x0000000100aa6fc0 in _pthread_cond_wait ()
#2  0x00000001ba5cbb7c in scavenger_thread_main ()
#3  0x0000000100aa29ac in _pthread_start ()
Thread 4 Queue : com.apple.root.default-qos.cooperative (concurrent)
#0  0x000000010019b79c in withThrowingTaskGroup<τ_0_0, τ_0_1>(of:returning:isolation:body:) ()
#1  0x0000000100199f2c in Collection.concurrentCompactMap<OrderedCollections.OrderedSet<String>>(_:) at /Users/almaz5200/Library/Developer/Xcode/DerivedData/ApolloCodegen-atnkdqhvebnanxgbiafqqdikldql/SourcePackages/checkouts/apollo-ios-codegen/Sources/Utilities/Collection+ConcurrentCompactMap.swift:15
#2  0x00000001000125c0 in ApolloCodegen.createSchema(_:) at /Users/almaz5200/Library/Developer/Xcode/DerivedData/ApolloCodegen-atnkdqhvebnanxgbiafqqdikldql/SourcePackages/checkouts/apollo-ios-codegen/Sources/ApolloCodegenLib/ApolloCodegen.swift:210
#3  0x0000000100011688 in implicit closure #1 in ApolloCodegen.compileGraphQLResult() at /Users/almaz5200/Library/Developer/Xcode/DerivedData/ApolloCodegen-atnkdqhvebnanxgbiafqqdikldql/SourcePackages/checkouts/apollo-ios-codegen/Sources/ApolloCodegenLib/ApolloCodegen.swift:167
#4  0x0000000100011848 in partial apply for implicit closure #1 in ApolloCodegen.compileGraphQLResult() ()
Thread 5 Queue : com.apple.root.default-qos.cooperative (concurrent)
#0  0x000000019ab67574 in getattrlistbulk ()
#1  0x000000019e5d124c in NextEntryFromParent ()
#2  0x000000019e5d0fdc in DirEnumRead ()
#3  0x000000019e5d09e8 in _GetDirectoryURLs ()
#4  0x000000019e5cf5a8 in _URLEnumeratorGetNextURL ()
#5  0x000000019be95530 in -[NSURLDirectoryEnumerator nextObject] ()
#6  0x000000019ac663fc in -[NSEnumerator countByEnumeratingWithState:objects:count:] ()
#7  0x000000019c32de2c in Foundation.NSFastEnumerationIterator.next() -> Swift.Optional<Any> ()
#8  0x000000010009120c in Glob.expand(_:excludingDirectories:) at /Users/almaz5200/Library/Developer/Xcode/DerivedData/ApolloCodegen-atnkdqhvebnanxgbiafqqdikldql/SourcePackages/checkouts/apollo-ios-codegen/Sources/ApolloCodegenLib/Glob.swift:176
#9  0x000000010008ff80 in Glob.expand(_:excludingDirectories:) at /Users/almaz5200/Library/Developer/Xcode/DerivedData/ApolloCodegen-atnkdqhvebnanxgbiafqqdikldql/SourcePackages/checkouts/apollo-ios-codegen/Sources/ApolloCodegenLib/Glob.swift:111
#10 0x000000010008f764 in Glob.match(excludingDirectories:) at /Users/almaz5200/Library/Developer/Xcode/DerivedData/ApolloCodegen-atnkdqhvebnanxgbiafqqdikldql/SourcePackages/checkouts/apollo-ios-codegen/Sources/ApolloCodegenLib/Glob.swift:74
#11 0x0000000100012b30 in static ApolloCodegen.match(searchPaths:relativeTo:) at /Users/almaz5200/Library/Developer/Xcode/DerivedData/ApolloCodegen-atnkdqhvebnanxgbiafqqdikldql/SourcePackages/checkouts/apollo-ios-codegen/Sources/ApolloCodegenLib/ApolloCodegen.swift:242
#12 0x0000000100013298 in ApolloCodegen.createOperationsDocument(_:) at /Users/almaz5200/Library/Developer/Xcode/DerivedData/ApolloCodegen-atnkdqhvebnanxgbiafqqdikldql/SourcePackages/checkouts/apollo-ios-codegen/Sources/ApolloCodegenLib/ApolloCodegen.swift:220
#13 0x00000001000119bc in implicit closure #2 in ApolloCodegen.compileGraphQLResult() at /Users/almaz5200/Library/Developer/Xcode/DerivedData/ApolloCodegen-atnkdqhvebnanxgbiafqqdikldql/SourcePackages/checkouts/apollo-ios-codegen/Sources/ApolloCodegenLib/ApolloCodegen.swift:168
#14 0x0000000100011b7c in partial apply for implicit closure #2 in ApolloCodegen.compileGraphQLResult() ()

Anything else?

Here's our full script:

@available(macOS 10.15, macCatalyst 13, iOS 13, tvOS 13, watchOS 6, *)
struct SwiftScript: ParsableCommand {

    static let configuration = CommandConfiguration(
        abstract: """
            A swift-based utility for performing Apollo-related tasks.

            NOTE: If running from a compiled binary, prefix subcommands with `swift-script`. Otherwise use `swift run ApolloCodegen [subcommand]`.
            """,
        subcommands: [DownloadSchema.self, GenerateCode.self])

    /// The URL to the source root for your main project.
    /// Defaults to the folder containing the `ApolloCodgen` Package folder.
    ///
    /// NOTE: - You may need to change this if your project has a different structure
    /// than the suggested structure.
    static let SourceRootURL: URL = {
        let parentFolderOfScriptFile = URL(string: FileManager.default.currentDirectoryPath)!
        let sourceRootURL =
            parentFolderOfScriptFile
            .parentFolderURL()  // Result: Project source root folder
        CodegenLogger.log("Source Root URL: \(sourceRootURL)")
        return sourceRootURL
    }()

    // The URL where the downloaded schema will be written to.
    // The default writes the schema to your project's root.
    static let SchemaOutputURL: URL = {
        SourceRootURL
            .appendingPathComponent("NetworkCore")
            .appendingPathComponent("Sources")
            .appendingPathComponent("NetworkService")
            .appendingPathComponent("Generated")
            .appendingPathComponent("schema.graphqls")
    }()

    /// The sub-command to download a schema from a provided endpoint.
    struct DownloadSchema: AsyncParsableCommand {
        static let configuration = CommandConfiguration(
            commandName: "downloadSchema",
            abstract:
                "Downloads the schema with the settings you've set up in the `DownloadSchema` command in `main.swift`."
        )

        mutating func run() async throws {
            // Set up the URL you want to use to download the project
            let endpoint = URL(string: "*our url*")!

            // Make sure the folder is created before trying to download something to it.
            let folderForDownloadedSchema = SchemaOutputURL.deletingLastPathComponent()

            // Create a configuration object for downloading the schema.
            // Provided code will download the schema via an introspection query to the provided URL as
            // SDL (GraphQL Schema Definition Language).
            // For all configuration parameters see:
            // https://www.apollographql.com/docs/ios/api/ApolloCodegenLib/structs/ApolloSchemaDownloadConfiguration/
            let schemaDownloadOptions = ApolloSchemaDownloadConfiguration(
                using: .introspection(endpointURL: endpoint),
                outputPath: SchemaOutputURL.path
            )

            // Actually attempt to download the schema.
            try await ApolloSchemaDownloader.fetch(configuration: schemaDownloadOptions)
        }
    }

    /// The sub-command to actually generate code.
    struct GenerateCode: AsyncParsableCommand {
        static let configuration = CommandConfiguration(
            commandName: "generate",
            abstract:
                "Generates swift code from your schema + your operations based on information set up in the `GenerateCode` command."
        )

        mutating func run() async throws {
            /// The root of the target for which you want to generate code.
            let targetSchemaTypesURL =
                SourceRootURL
                .childFolderURL(folderName: "NetworkCore/Sources/APITypes/Generated")
            let targetRootURL =
                SourceRootURL
                .childFolderURL(folderName: "NetworkCore/Sources/NetworkService/Generated")

            /// The name of the module that will contain your generated schema objects.
            let generatedSchemaModuleName: String = "APITypes"

            // Make sure the folders exists before trying to generate code.

            // Create the Codegen configuration object. For all configuration parameters see: https://www.apollographql.com/docs/ios/api/ApolloCodegenLib/structs/ApolloCodegenConfiguration/
            let codegenConfiguration = ApolloCodegenConfiguration(
                schemaNamespace: generatedSchemaModuleName,
                input: ApolloCodegenConfiguration.FileInput(
                    schemaPath: SchemaOutputURL.path,
                    operationSearchPaths: [
                        "\(SourceRootURL.path)/DataMonolith/Sources/**/*.graphql",
                        "\(SourceRootURL.path)/NetworkCore/Sources/**/*.graphql",
                    ]),
                output: ApolloCodegenConfiguration.FileOutput(
                    schemaTypes: ApolloCodegenConfiguration.SchemaTypesFileOutput(
                        path: targetSchemaTypesURL.path,
                        moduleType: .swiftPackageManager
                    ),
                    operations: .relative(subpath: "../Generated/Operations/")
                )
            )

            // Actually attempt to generate code.
            try await ApolloCodegen.build(with: codegenConfiguration)
        }
    }

}
calvincestari commented 4 hours ago

Thanks for reporting the issue @Almaz5200. Unfortunately there isn't enough information here to determine the cause. I also cannot replicate the error in our test projects.

If you can supply a standalone project that fails in the same way it would definitely help to debug the issue. If you have the full Xcode build log that may help too.

Almaz5200 commented 4 hours ago

Thank you for a quick response @calvincestari. I was able to come up with a simple project that replicates the issue: https://github.com/Almaz5200/ApolloSegFault You should be able to just cd into ApolloCodegen and use swift run there, see screenshot bellow

Screenshot 2024-09-23 at 8 19 14 PM

Also note, that for demonstration purposes I've hardcoded project root path, so you'll have to change it in order to run it

Almaz5200 commented 4 hours ago

If you’re unable to reproduce the issue, please let me know, and I’ll gladly share build logs and any other information you may need.

calvincestari commented 2 hours ago

That project isn't failing for me @Almaz5200. There are some oddities in the output but I can get the generated operation as below:

// @generated
// This file was automatically generated and should not be edited.

@_exported import ApolloAPI
import APITypes

public class TestQQuery: GraphQLQuery {
  public static let operationName: String = "TestQ"
  public static let operationDocument: ApolloAPI.OperationDocument = .init(
    definition: .init(
      #"query TestQ { sectionQueries }"#
    ))

  public init() {}

  public struct Data: APITypes.SelectionSet {
    public let __data: DataDict
    public init(_dataDict: DataDict) { __data = _dataDict }

    public static var __parentType: any ApolloAPI.ParentType { APITypes.Objects.Query }
    public static var __selections: [ApolloAPI.Selection] { [
      .field("sectionQueries", String.self),
    ] }

    public var sectionQueries: String { __data["sectionQueries"] }
  }
}

Which version of Xcode are you using?

Almaz5200 commented 2 hours ago

I've tried it with Xcode 15.4 and 16.0. It works fine on 15.4 and fails on 16.0 Regarding oddities in the output, I wouldn't read into that too much, I've made this scheme in a rush to be barely valid just for a demonstration, so there may be something wrong with it. In my case however it doesn't seem to even get to validating scheme or parsing it in any way

calvincestari commented 1 hour ago

I've tried it with Xcode 15.4 and 16.0. It works fine on 15.4 and fails on 16.0

Are you using the latest Xcode 16.0 release, build 16A242d? If you want to send me the Xcode build log I'll take a look at to see if I can glean anything else from that.