@@ -402,34 +402,53 @@ func GlobMatchFunc(args ...interface{}) (interface{}, error) {
402402}
403403
404404// GenerateGFunction is the factory method of the g(_, _[, _]) function.
405- func GenerateGFunction (rm rbac.RoleManager ) govaluate.ExpressionFunction {
405+ // If useCache is true, results are memoized per unique argument combination for performance.
406+ // Set useCache to false when inputs are high-cardinality (e.g. UUIDs) to avoid unbounded memory growth.
407+ func GenerateGFunction (rm rbac.RoleManager , useCache bool ) govaluate.ExpressionFunction {
406408 memorized := sync.Map {}
407409 return func (args ... interface {}) (interface {}, error ) {
408410 // Like all our other govaluate functions, all args are strings.
409411
410- // Allocate and generate a cache key from the arguments...
411- total := len (args )
412- for _ , a := range args {
413- aStr := a .(string )
414- total += len (aStr )
415- }
416- builder := strings.Builder {}
417- builder .Grow (total )
418- for _ , arg := range args {
419- builder .WriteByte (0 )
420- builder .WriteString (arg .(string ))
421- }
422- key := builder .String ()
423-
424- // ...and see if we've already calculated this.
425- v , found := memorized .Load (key )
426- if found {
412+ if useCache {
413+ // Allocate and generate a cache key from the arguments...
414+ total := len (args )
415+ for _ , a := range args {
416+ aStr := a .(string )
417+ total += len (aStr )
418+ }
419+ builder := strings.Builder {}
420+ builder .Grow (total )
421+ for _ , arg := range args {
422+ builder .WriteByte (0 )
423+ builder .WriteString (arg .(string ))
424+ }
425+ key := builder .String ()
426+
427+ // ...and see if we've already calculated this.
428+ if v , found := memorized .Load (key ); found {
429+ return v , nil
430+ }
431+
432+ // If not, do the calculation.
433+ // There are guaranteed to be exactly 2 or 3 arguments.
434+ name1 , name2 := args [0 ].(string ), args [1 ].(string )
435+ var v interface {}
436+ if rm == nil {
437+ v = name1 == name2
438+ } else if len (args ) == 2 {
439+ v , _ = rm .HasLink (name1 , name2 )
440+ } else {
441+ domain := args [2 ].(string )
442+ v , _ = rm .HasLink (name1 , name2 , domain )
443+ }
444+
445+ memorized .Store (key , v )
427446 return v , nil
428447 }
429448
430- // If not, do the calculation.
431- // There are guaranteed to be exactly 2 or 3 arguments.
449+ // No caching path.
432450 name1 , name2 := args [0 ].(string ), args [1 ].(string )
451+ var v interface {}
433452 if rm == nil {
434453 v = name1 == name2
435454 } else if len (args ) == 2 {
@@ -438,8 +457,6 @@ func GenerateGFunction(rm rbac.RoleManager) govaluate.ExpressionFunction {
438457 domain := args [2 ].(string )
439458 v , _ = rm .HasLink (name1 , name2 , domain )
440459 }
441-
442- memorized .Store (key , v )
443460 return v , nil
444461 }
445462}
0 commit comments