From 90876a23ca7e54f0273eced0a269d68fdda102df Mon Sep 17 00:00:00 2001 From: maximkrouk Date: Tue, 14 Apr 2026 17:08:34 +0200 Subject: [PATCH] fix: Adjust recursive cycle detection Prev implementation triggered EXC_BAD_ACCESS exception on line 711 > `fieldPathIndexByTypeName[inputObj.name] = fieldPath.count` could not access `count` since `fieldPath` was `` even tho it clearly was This commit wraps this logic into a class so `createInputObjectCircularRefsValidator` function no longer mutably captures a value type --- .editorconfig | 3 + Sources/GraphQL/Type/Validation.swift | 95 +++++++++++++-------------- 2 files changed, 49 insertions(+), 49 deletions(-) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..6573f019 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,3 @@ +[*.swift] +indent_style = space +indent_size = 4 \ No newline at end of file diff --git a/Sources/GraphQL/Type/Validation.swift b/Sources/GraphQL/Type/Validation.swift index a2cb28bc..7bf8a215 100644 --- a/Sources/GraphQL/Type/Validation.swift +++ b/Sources/GraphQL/Type/Validation.swift @@ -690,55 +690,7 @@ func validateOneOfInputObjectField( func createInputObjectCircularRefsValidator( context: SchemaValidationContext ) throws -> (GraphQLInputObjectType) throws -> Void { - // Modified copy of algorithm from 'src/validation/rules/NoFragmentCycles.js'. - // Tracks already visited types to maintain O(N) and to ensure that cycles - // are not redundantly reported. - var visitedTypes = Set() - - // Array of types nodes used to produce meaningful errors - var fieldPath: [InputObjectFieldDefinition] = [] - - // Position in the type path - var fieldPathIndexByTypeName: [String: Int] = [:] - - return detectCycleRecursive - - /// This does a straight-forward DFS to find cycles. - /// It does not terminate when a cycle is found but continues to explore - /// the graph to find all possible cycles. - func detectCycleRecursive(inputObj: GraphQLInputObjectType) throws { - if visitedTypes.contains(inputObj) { - return - } - - visitedTypes.insert(inputObj) - fieldPathIndexByTypeName[inputObj.name] = fieldPath.count - - let fields = try inputObj.getFields().values - for field in fields { - if let nonNullType = field.type as? GraphQLNonNull, - let fieldType = nonNullType.ofType as? GraphQLInputObjectType - { - let cycleIndex = fieldPathIndexByTypeName[fieldType.name] - - fieldPath.append(field) - if let cycleIndex = cycleIndex { - let cyclePath = fieldPath[cycleIndex.. = [] + private var fieldPath: [InputObjectFieldDefinition] = [] + private var fieldPathIndexByTypeName: [String: Int] = [:] + + init(context: SchemaValidationContext) { + self.context = context + } + + func validate(inputObj: GraphQLInputObjectType) throws { + if visitedTypes.contains(inputObj) { + return + } + + visitedTypes.insert(inputObj) + fieldPathIndexByTypeName[inputObj.name] = fieldPath.count + + let fields = try inputObj.getFields().values + for field in fields { + if + let nonNullType = field.type as? GraphQLNonNull, + let fieldType = nonNullType.ofType as? GraphQLInputObjectType + { + let cycleIndex = fieldPathIndexByTypeName[fieldType.name] + + fieldPath.append(field) + if let cycleIndex = cycleIndex { + let cyclePath = fieldPath[cycleIndex ..< fieldPath.count] + let pathStr = cyclePath.map { fieldObj in fieldObj.name }.joined(separator: ".") + context.reportError( + message: "Cannot reference Input Object \"\(fieldType)\" within itself through a series of non-null fields: \"\(pathStr)\".", + nodes: cyclePath.map { fieldObj in fieldObj.astNode } + ) + } else { + try validate(inputObj: fieldType) + } + fieldPath.removeLast() + } + } + + fieldPathIndexByTypeName[inputObj.name] = nil + } +}