-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Open
Description
Describe the bug
The spec prohibits the definitions of directives to reference each other directly or indirectly via applied directives. GraphQL Java (25) correctly detects the direct case, but overflows the stack during validation for the indirect case.
To Reproduce
import graphql.schema.idl.SchemaParser;
import graphql.schema.idl.TypeDefinitionRegistry;
import graphql.schema.idl.UnExecutableSchemaGenerator;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertThrows;
/**
* Reproduction test for graphql-java circular directive dependency bug.
*
* When directives mutually reference each other via applied directives on their
* arguments, UnExecutableSchemaGenerator.makeUnExecutableSchema() throws
* StackOverflowError instead of detecting the cycle and reporting a proper error.
*
* NOTE: graphql-java correctly detects direct self-reference (A -> A) and throws
* SchemaProblem, but fails to detect indirect cycles (A -> B -> A) or longer
* cycles (A -> B -> C -> A), resulting in StackOverflowError.
*
* Dependencies: graphql-java, JUnit 5
*/
public class CircularDirectiveStackOverflowTest {
@Test
void selfReferencingDirectiveCorrectlyThrowsSchemaProblem() {
// A directive that references itself via applied directive on its argument
// graphql-java correctly detects this direct self-reference
String sdl = """
directive @recursive(depth: Int @recursive(depth: 0)) on FIELD_DEFINITION | ARGUMENT_DEFINITION
type Query { field: String @recursive(depth: 5) }
""";
TypeDefinitionRegistry registry = new SchemaParser().parse(sdl);
// This correctly throws SchemaProblem (not a bug)
assertThrows(graphql.schema.idl.errors.SchemaProblem.class, () -> {
UnExecutableSchemaGenerator.makeUnExecutableSchema(registry);
});
}
@Test
void mutualDirectiveReferenceCausesStackOverflowError() {
// Two directives that reference each other via applied directives on arguments
// This is an indirect cycle: @foo -> @bar -> @foo
String sdl = """
directive @foo(x: Int @bar(y: 1)) on FIELD_DEFINITION | ARGUMENT_DEFINITION
directive @bar(y: Int @foo(x: 2)) on FIELD_DEFINITION | ARGUMENT_DEFINITION
type Query { field: String @foo(x: 10) @bar(y: 20) }
""";
TypeDefinitionRegistry registry = new SchemaParser().parse(sdl);
// BUG: This throws StackOverflowError instead of a proper validation error
assertThrows(StackOverflowError.class, () -> {
UnExecutableSchemaGenerator.makeUnExecutableSchema(registry);
});
}
@Test
void threeWayCircularDirectiveReferenceCausesStackOverflowError() {
// Three directives forming a cycle: @dirA -> @dirB -> @dirC -> @dirA
String sdl = """
directive @dirA(x: Int @dirB(y: 1)) on FIELD_DEFINITION | ARGUMENT_DEFINITION
directive @dirB(y: Int @dirC(z: 2)) on FIELD_DEFINITION | ARGUMENT_DEFINITION
directive @dirC(z: Int @dirA(x: 3)) on FIELD_DEFINITION | ARGUMENT_DEFINITION
type Query { field: String @dirA(x: 10) @dirB(y: 20) @dirC(z: 30) }
""";
TypeDefinitionRegistry registry = new SchemaParser().parse(sdl);
// BUG: This throws StackOverflowError instead of a proper validation error
assertThrows(StackOverflowError.class, () -> {
UnExecutableSchemaGenerator.makeUnExecutableSchema(registry);
});
}
}
Copilot