This is an automated email from the ASF dual-hosted git repository.

hsluoyz pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/casbin.git

commit 74322db8ebd2fa7a9e15e7369db6add65498f83b
Author: Yang Luo <[email protected]>
AuthorDate: Fri Apr 24 21:52:57 2026 +0800

    fix: add gFunctionCache boolean option to enforcer
---
 enforcer.go               | 12 +++++++++-
 management_api.go         |  2 +-
 util/builtin_operators.go | 61 ++++++++++++++++++++++++++++++-----------------
 3 files changed, 51 insertions(+), 24 deletions(-)

diff --git a/enforcer.go b/enforcer.go
index a6bf174..c78350c 100644
--- a/enforcer.go
+++ b/enforcer.go
@@ -56,6 +56,7 @@ type Enforcer struct {
        autoNotifyWatcher    bool
        autoNotifyDispatcher bool
        acceptJsonRequest    bool
+       gFunctionCache       bool
 
        aiConfig AIConfig
 }
@@ -193,6 +194,7 @@ func (e *Enforcer) initialize() {
        e.autoBuildRoleLinks = true
        e.autoNotifyWatcher = true
        e.autoNotifyDispatcher = true
+       e.gFunctionCache = true
        e.initRmMap()
 
        // Initialize detectors with default detector if not already set
@@ -637,6 +639,14 @@ func (e *Enforcer) 
EnableAcceptJsonRequest(acceptJsonRequest bool) {
        e.acceptJsonRequest = acceptJsonRequest
 }
 
+// EnableGFunctionCache controls whether to cache g() function results.
+// Disable this when inputs are high-cardinality (e.g. UUIDs or dynamic paths)
+// to prevent unbounded memory growth.
+func (e *Enforcer) EnableGFunctionCache(enabled bool) {
+       e.gFunctionCache = enabled
+       e.invalidateMatcherMap()
+}
+
 // BuildRoleLinks manually rebuild the role inheritance relations.
 func (e *Enforcer) BuildRoleLinks() error {
        e.invalidateMatcherMap()
@@ -704,7 +714,7 @@ func (e *Enforcer) enforce(matcher string, explains 
*[]string, rvals ...interfac
                        //   or a conditional role definition (ast.CondRM != 
nil)
                        // ast.RM and ast.CondRM shouldn't be nil at the same 
time
                        if ast.RM != nil {
-                               functions[key] = util.GenerateGFunction(ast.RM)
+                               functions[key] = util.GenerateGFunction(ast.RM, 
e.gFunctionCache)
                        }
                        if ast.CondRM != nil {
                                functions[key] = 
util.GenerateConditionalGFunction(ast.CondRM)
diff --git a/management_api.go b/management_api.go
index 7a8f768..d18536c 100644
--- a/management_api.go
+++ b/management_api.go
@@ -145,7 +145,7 @@ func (e *Enforcer) GetFilteredNamedPolicyWithMatcher(ptype 
string, matcher strin
                        //   or a conditional role definition (ast.CondRM != 
nil)
                        // ast.RM and ast.CondRM shouldn't be nil at the same 
time
                        if ast.RM != nil {
-                               functions[key] = util.GenerateGFunction(ast.RM)
+                               functions[key] = util.GenerateGFunction(ast.RM, 
e.gFunctionCache)
                        }
                        if ast.CondRM != nil {
                                functions[key] = 
util.GenerateConditionalGFunction(ast.CondRM)
diff --git a/util/builtin_operators.go b/util/builtin_operators.go
index 37d9cb5..066a0bd 100644
--- a/util/builtin_operators.go
+++ b/util/builtin_operators.go
@@ -402,34 +402,53 @@ func GlobMatchFunc(args ...interface{}) (interface{}, 
error) {
 }
 
 // GenerateGFunction is the factory method of the g(_, _[, _]) function.
-func GenerateGFunction(rm rbac.RoleManager) govaluate.ExpressionFunction {
+// If useCache is true, results are memoized per unique argument combination 
for performance.
+// Set useCache to false when inputs are high-cardinality (e.g. UUIDs) to 
avoid unbounded memory growth.
+func GenerateGFunction(rm rbac.RoleManager, useCache bool) 
govaluate.ExpressionFunction {
        memorized := sync.Map{}
        return func(args ...interface{}) (interface{}, error) {
                // Like all our other govaluate functions, all args are strings.
 
-               // Allocate and generate a cache key from the arguments...
-               total := len(args)
-               for _, a := range args {
-                       aStr := a.(string)
-                       total += len(aStr)
-               }
-               builder := strings.Builder{}
-               builder.Grow(total)
-               for _, arg := range args {
-                       builder.WriteByte(0)
-                       builder.WriteString(arg.(string))
-               }
-               key := builder.String()
-
-               // ...and see if we've already calculated this.
-               v, found := memorized.Load(key)
-               if found {
+               if useCache {
+                       // Allocate and generate a cache key from the 
arguments...
+                       total := len(args)
+                       for _, a := range args {
+                               aStr := a.(string)
+                               total += len(aStr)
+                       }
+                       builder := strings.Builder{}
+                       builder.Grow(total)
+                       for _, arg := range args {
+                               builder.WriteByte(0)
+                               builder.WriteString(arg.(string))
+                       }
+                       key := builder.String()
+
+                       // ...and see if we've already calculated this.
+                       if v, found := memorized.Load(key); found {
+                               return v, nil
+                       }
+
+                       // If not, do the calculation.
+                       // There are guaranteed to be exactly 2 or 3 arguments.
+                       name1, name2 := args[0].(string), args[1].(string)
+                       var v interface{}
+                       if rm == nil {
+                               v = name1 == name2
+                       } else if len(args) == 2 {
+                               v, _ = rm.HasLink(name1, name2)
+                       } else {
+                               domain := args[2].(string)
+                               v, _ = rm.HasLink(name1, name2, domain)
+                       }
+
+                       memorized.Store(key, v)
                        return v, nil
                }
 
-               // If not, do the calculation.
-               // There are guaranteed to be exactly 2 or 3 arguments.
+               // No caching path.
                name1, name2 := args[0].(string), args[1].(string)
+               var v interface{}
                if rm == nil {
                        v = name1 == name2
                } else if len(args) == 2 {
@@ -438,8 +457,6 @@ func GenerateGFunction(rm rbac.RoleManager) 
govaluate.ExpressionFunction {
                        domain := args[2].(string)
                        v, _ = rm.HasLink(name1, name2, domain)
                }
-
-               memorized.Store(key, v)
                return v, nil
        }
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to