diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation/validation.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation/validation.go index be4252c14c4..ab69389e675 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation/validation.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation/validation.go @@ -23,6 +23,7 @@ import ( "reflect" "regexp" "strings" + "sync" "unicode" "unicode/utf8" @@ -1364,6 +1365,27 @@ func HasSchemaWith(spec *apiextensions.CustomResourceDefinitionSpec, pred func(s return false } +var schemaPool = sync.Pool{ + New: func() any { + return new(apiextensions.JSONSchemaProps) + }, +} + +func schemaHasRecurse(s *apiextensions.JSONSchemaProps, pred func(s *apiextensions.JSONSchemaProps) bool) bool { + if s == nil { + return false + } + schema := schemaPool.Get().(*apiextensions.JSONSchemaProps) + defer schemaPool.Put(schema) + *schema = *s + return SchemaHas(schema, pred) +} + +// SchemaHas recursively traverses the Schema and calls the `pred` +// predicate to see if the schema contains specific values. +// +// The predicate MUST NOT keep a copy of the json schema NOR modify the +// schema. func SchemaHas(s *apiextensions.JSONSchemaProps, pred func(s *apiextensions.JSONSchemaProps) bool) bool { if s == nil { return false @@ -1374,60 +1396,60 @@ func SchemaHas(s *apiextensions.JSONSchemaProps, pred func(s *apiextensions.JSON } if s.Items != nil { - if s.Items != nil && SchemaHas(s.Items.Schema, pred) { + if s.Items != nil && schemaHasRecurse(s.Items.Schema, pred) { return true } for i := range s.Items.JSONSchemas { - if SchemaHas(&s.Items.JSONSchemas[i], pred) { + if schemaHasRecurse(&s.Items.JSONSchemas[i], pred) { return true } } } for i := range s.AllOf { - if SchemaHas(&s.AllOf[i], pred) { + if schemaHasRecurse(&s.AllOf[i], pred) { return true } } for i := range s.AnyOf { - if SchemaHas(&s.AnyOf[i], pred) { + if schemaHasRecurse(&s.AnyOf[i], pred) { return true } } for i := range s.OneOf { - if SchemaHas(&s.OneOf[i], pred) { + if schemaHasRecurse(&s.OneOf[i], pred) { return true } } - if SchemaHas(s.Not, pred) { + if schemaHasRecurse(s.Not, pred) { return true } for _, s := range s.Properties { - if SchemaHas(&s, pred) { + if schemaHasRecurse(&s, pred) { return true } } if s.AdditionalProperties != nil { - if SchemaHas(s.AdditionalProperties.Schema, pred) { + if schemaHasRecurse(s.AdditionalProperties.Schema, pred) { return true } } for _, s := range s.PatternProperties { - if SchemaHas(&s, pred) { + if schemaHasRecurse(&s, pred) { return true } } if s.AdditionalItems != nil { - if SchemaHas(s.AdditionalItems.Schema, pred) { + if schemaHasRecurse(s.AdditionalItems.Schema, pred) { return true } } for _, s := range s.Definitions { - if SchemaHas(&s, pred) { + if schemaHasRecurse(&s, pred) { return true } } for _, d := range s.Dependencies { - if SchemaHas(d.Schema, pred) { + if schemaHasRecurse(d.Schema, pred) { return true } } diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation/validation_test.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation/validation_test.go index 853110c2d6d..687d238b455 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation/validation_test.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation/validation_test.go @@ -8624,6 +8624,30 @@ func TestSchemaHasDefaults(t *testing.T) { } } +func BenchmarkSchemaHas(b *testing.B) { + scheme := runtime.NewScheme() + codecs := serializer.NewCodecFactory(scheme) + if err := apiextensions.AddToScheme(scheme); err != nil { + b.Fatal(err) + } + fuzzerFuncs := fuzzer.MergeFuzzerFuncs(apiextensionsfuzzer.Funcs) + seed := int64(5577006791947779410) + f := fuzzer.FuzzerFor(fuzzerFuncs, rand.NewSource(seed), codecs) + // fuzz internal types + schema := &apiextensions.JSONSchemaProps{} + f.NilChance(0).NumElements(10, 10).MaxDepth(10).Fuzz(schema) + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + if SchemaHas(schema, func(_ *apiextensions.JSONSchemaProps) bool { + return false + }) { + b.Errorf("Function returned true") + } + } +} + var example = apiextensions.JSON(`"This is an example"`) var validValidationSchema = &apiextensions.JSONSchemaProps{