Skip to content

Commit 17c68cf

Browse files
Merge pull request #181 from CaptureContext/main
fix: Adjust recursive cycle detection to fix EXC_BAD_ACCESS
2 parents a722ef9 + 90876a2 commit 17c68cf

2 files changed

Lines changed: 49 additions & 49 deletions

File tree

.editorconfig

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[*.swift]
2+
indent_style = space
3+
indent_size = 4

Sources/GraphQL/Type/Validation.swift

Lines changed: 46 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -690,55 +690,7 @@ func validateOneOfInputObjectField(
690690
func createInputObjectCircularRefsValidator(
691691
context: SchemaValidationContext
692692
) throws -> (GraphQLInputObjectType) throws -> Void {
693-
// Modified copy of algorithm from 'src/validation/rules/NoFragmentCycles.js'.
694-
// Tracks already visited types to maintain O(N) and to ensure that cycles
695-
// are not redundantly reported.
696-
var visitedTypes = Set<GraphQLInputObjectType>()
697-
698-
// Array of types nodes used to produce meaningful errors
699-
var fieldPath: [InputObjectFieldDefinition] = []
700-
701-
// Position in the type path
702-
var fieldPathIndexByTypeName: [String: Int] = [:]
703-
704-
return detectCycleRecursive
705-
706-
/// This does a straight-forward DFS to find cycles.
707-
/// It does not terminate when a cycle is found but continues to explore
708-
/// the graph to find all possible cycles.
709-
func detectCycleRecursive(inputObj: GraphQLInputObjectType) throws {
710-
if visitedTypes.contains(inputObj) {
711-
return
712-
}
713-
714-
visitedTypes.insert(inputObj)
715-
fieldPathIndexByTypeName[inputObj.name] = fieldPath.count
716-
717-
let fields = try inputObj.getFields().values
718-
for field in fields {
719-
if let nonNullType = field.type as? GraphQLNonNull,
720-
let fieldType = nonNullType.ofType as? GraphQLInputObjectType
721-
{
722-
let cycleIndex = fieldPathIndexByTypeName[fieldType.name]
723-
724-
fieldPath.append(field)
725-
if let cycleIndex = cycleIndex {
726-
let cyclePath = fieldPath[cycleIndex..<fieldPath.count]
727-
let pathStr = cyclePath.map { fieldObj in fieldObj.name }.joined(separator: ".")
728-
context.reportError(
729-
message:
730-
"Cannot reference Input Object \"\(fieldType)\" within itself through a series of non-null fields: \"\(pathStr)\".",
731-
nodes: cyclePath.map { fieldObj in fieldObj.astNode }
732-
)
733-
} else {
734-
try detectCycleRecursive(inputObj: fieldType)
735-
}
736-
fieldPath.removeLast()
737-
}
738-
}
739-
740-
fieldPathIndexByTypeName[inputObj.name] = nil
741-
}
693+
return CircularRefsValidator(context: context).validate
742694
}
743695

744696
func getAllImplementsInterfaceNodes(
@@ -781,3 +733,48 @@ func getDeprecatedDirectiveNode(
781733
node.name.value == GraphQLDeprecatedDirective.name
782734
}
783735
}
736+
737+
private final class CircularRefsValidator {
738+
private let context: SchemaValidationContext
739+
private var visitedTypes: Set<GraphQLInputObjectType> = []
740+
private var fieldPath: [InputObjectFieldDefinition] = []
741+
private var fieldPathIndexByTypeName: [String: Int] = [:]
742+
743+
init(context: SchemaValidationContext) {
744+
self.context = context
745+
}
746+
747+
func validate(inputObj: GraphQLInputObjectType) throws {
748+
if visitedTypes.contains(inputObj) {
749+
return
750+
}
751+
752+
visitedTypes.insert(inputObj)
753+
fieldPathIndexByTypeName[inputObj.name] = fieldPath.count
754+
755+
let fields = try inputObj.getFields().values
756+
for field in fields {
757+
if
758+
let nonNullType = field.type as? GraphQLNonNull,
759+
let fieldType = nonNullType.ofType as? GraphQLInputObjectType
760+
{
761+
let cycleIndex = fieldPathIndexByTypeName[fieldType.name]
762+
763+
fieldPath.append(field)
764+
if let cycleIndex = cycleIndex {
765+
let cyclePath = fieldPath[cycleIndex ..< fieldPath.count]
766+
let pathStr = cyclePath.map { fieldObj in fieldObj.name }.joined(separator: ".")
767+
context.reportError(
768+
message: "Cannot reference Input Object \"\(fieldType)\" within itself through a series of non-null fields: \"\(pathStr)\".",
769+
nodes: cyclePath.map { fieldObj in fieldObj.astNode }
770+
)
771+
} else {
772+
try validate(inputObj: fieldType)
773+
}
774+
fieldPath.removeLast()
775+
}
776+
}
777+
778+
fieldPathIndexByTypeName[inputObj.name] = nil
779+
}
780+
}

0 commit comments

Comments
 (0)