diff --git a/go.mod b/go.mod index 4e8956f83..8b057355d 100644 --- a/go.mod +++ b/go.mod @@ -53,7 +53,7 @@ require ( github.com/bombsimon/logrusr/v4 v4.1.0 // indirect github.com/bradleyfalzon/ghinstallation/v2 v2.14.0 // indirect github.com/casbin/casbin/v2 v2.103.0 // indirect - github.com/casbin/govaluate v1.3.0 // indirect + github.com/casbin/govaluate v1.10.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/chai2010/gettext-go v1.0.2 // indirect github.com/cloudflare/circl v1.6.1 // indirect diff --git a/go.sum b/go.sum index fadb0b434..f3ec6092e 100644 --- a/go.sum +++ b/go.sum @@ -72,8 +72,9 @@ github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= github.com/casbin/casbin/v2 v2.103.0 h1:dHElatNXNrr8XcseUov0ZSiWjauwmZZE6YMV3eU1yic= github.com/casbin/casbin/v2 v2.103.0/go.mod h1:Ee33aqGrmES+GNL17L0h9X28wXuo829wnNUnS0edAco= -github.com/casbin/govaluate v1.3.0 h1:VA0eSY0M2lA86dYd5kPPuNZMUD9QkWnOCnavGrw9myc= github.com/casbin/govaluate v1.3.0/go.mod h1:G/UnbIjZk/0uMNaLwZZmFQrR72tYRZWQkO70si/iR7A= +github.com/casbin/govaluate v1.10.0 h1:ffGw51/hYH3w3rZcxO/KcaUIDOLP84w7nsidMVgaDG0= +github.com/casbin/govaluate v1.10.0/go.mod h1:G/UnbIjZk/0uMNaLwZZmFQrR72tYRZWQkO70si/iR7A= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= diff --git a/vendor/github.com/casbin/govaluate/.golangci.yml b/vendor/github.com/casbin/govaluate/.golangci.yml new file mode 100644 index 000000000..89fdec496 --- /dev/null +++ b/vendor/github.com/casbin/govaluate/.golangci.yml @@ -0,0 +1,62 @@ +# golangci-lint configuration file +# See https://golangci-lint.run/usage/configuration/ + +run: + timeout: 10m + go: "1.21" + +linters: + enable: + - govet + - staticcheck + - unused + - ineffassign + - errcheck + - gosec + - misspell + + disable: + - lll # Line length limit, can be re-enabled later after code formatting + - wsl + - gomnd # Magic number checks, too strict for tests and constants + - gocyclo # Cyclomatic complexity, too strict for existing code + - goconst # String constants, too strict for test strings + - dupl # Duplicate code detection, too strict for existing code + - unparam # Unused parameters, too strict for interface implementations + - revive # Too many style warnings for existing code + - prealloc # Pre-allocation suggestions, too strict + - structcheck + - varcheck + - deadcode + +linters-settings: + staticcheck: + go: "1.21" + + gosec: + excludes: + - G404 # Disable weak random number generator check for test files + +issues: + exclude-rules: + - path: _test\.go + linters: + - gocyclo + - dupl + - goconst + - gosec + - path: benchmarks_test\.go + linters: + - gocyclo + - dupl + - goconst + - gosec + - path: torture_test\.go + linters: + - gocyclo + - dupl + - goconst + - gosec + + max-issues-per-linter: 0 + max-same-issues: 0 \ No newline at end of file diff --git a/vendor/github.com/casbin/govaluate/EvaluableExpression.go b/vendor/github.com/casbin/govaluate/EvaluableExpression.go index a5fe50d47..79a0bbfd4 100644 --- a/vendor/github.com/casbin/govaluate/EvaluableExpression.go +++ b/vendor/github.com/casbin/govaluate/EvaluableExpression.go @@ -3,6 +3,7 @@ package govaluate import ( "errors" "fmt" + "sync" ) const isoDateFormat string = "2006-01-02T15:04:05.999999999Z0700" @@ -11,8 +12,8 @@ const shortCircuitHolder int = -1 var DUMMY_PARAMETERS = MapParameters(map[string]interface{}{}) /* - EvaluableExpression represents a set of ExpressionTokens which, taken together, - are an expression that can be evaluated down into a single value. +EvaluableExpression represents a set of ExpressionTokens which, taken together, +are an expression that can be evaluated down into a single value. */ type EvaluableExpression struct { @@ -38,8 +39,8 @@ type EvaluableExpression struct { } /* - Parses a new EvaluableExpression from the given [expression] string. - Returns an error if the given expression has invalid syntax. +Parses a new EvaluableExpression from the given [expression] string. +Returns an error if the given expression has invalid syntax. */ func NewEvaluableExpression(expression string) (*EvaluableExpression, error) { @@ -48,8 +49,8 @@ func NewEvaluableExpression(expression string) (*EvaluableExpression, error) { } /* - Similar to [NewEvaluableExpression], except that instead of a string, an already-tokenized expression is given. - This is useful in cases where you may be generating an expression automatically, or using some other parser (e.g., to parse from a query language) +Similar to [NewEvaluableExpression], except that instead of a string, an already-tokenized expression is given. +This is useful in cases where you may be generating an expression automatically, or using some other parser (e.g., to parse from a query language) */ func NewEvaluableExpressionFromTokens(tokens []ExpressionToken) (*EvaluableExpression, error) { @@ -84,8 +85,8 @@ func NewEvaluableExpressionFromTokens(tokens []ExpressionToken) (*EvaluableExpre } /* - Similar to [NewEvaluableExpression], except enables the use of user-defined functions. - Functions passed into this will be available to the expression. +Similar to [NewEvaluableExpression], except enables the use of user-defined functions. +Functions passed into this will be available to the expression. */ func NewEvaluableExpressionWithFunctions(expression string, functions map[string]ExpressionFunction) (*EvaluableExpression, error) { @@ -126,50 +127,64 @@ func NewEvaluableExpressionWithFunctions(expression string, functions map[string } /* - Same as `Eval`, but automatically wraps a map of parameters into a `govalute.Parameters` structure. +Same as `Eval`, but automatically wraps a map of parameters into a `govalute.Parameters` structure. */ -func (this EvaluableExpression) Evaluate(parameters map[string]interface{}) (interface{}, error) { +func (e EvaluableExpression) Evaluate(parameters map[string]interface{}) (interface{}, error) { if parameters == nil { - return this.Eval(nil) + return e.Eval(nil) } - return this.Eval(MapParameters(parameters)) + return e.Eval(MapParameters(parameters)) +} + +var sanitizedParamsPool = sync.Pool{ + New: func() interface{} { + return &sanitizedParameters{} + }, } /* - Runs the entire expression using the given [parameters]. - e.g., If the expression contains a reference to the variable "foo", it will be taken from `parameters.Get("foo")`. +Runs the entire expression using the given [parameters]. +e.g., If the expression contains a reference to the variable "foo", it will be taken from `parameters.Get("foo")`. - This function returns errors if the combination of expression and parameters cannot be run, - such as if a variable in the expression is not present in [parameters]. +This function returns errors if the combination of expression and parameters cannot be run, +such as if a variable in the expression is not present in [parameters]. - In all non-error circumstances, this returns the single value result of the expression and parameters given. - e.g., if the expression is "1 + 1", this will return 2.0. - e.g., if the expression is "foo + 1" and parameters contains "foo" = 2, this will return 3.0 +In all non-error circumstances, this returns the single value result of the expression and parameters given. +e.g., if the expression is "1 + 1", this will return 2.0. +e.g., if the expression is "foo + 1" and parameters contains "foo" = 2, this will return 3.0 */ -func (this EvaluableExpression) Eval(parameters Parameters) (interface{}, error) { +func (e EvaluableExpression) Eval(parameters Parameters) (interface{}, error) { - if this.evaluationStages == nil { + if e.evaluationStages == nil { return nil, nil } + free := false if parameters != nil { - parameters = &sanitizedParameters{parameters} + free = true + tmp := sanitizedParamsPool.Get().(*sanitizedParameters) + tmp.orig = parameters + parameters = tmp } else { parameters = DUMMY_PARAMETERS } - return this.evaluateStage(this.evaluationStages, parameters) + ret, err := e.evaluateStage(e.evaluationStages, parameters) + if free { + sanitizedParamsPool.Put(parameters) + } + return ret, err } -func (this EvaluableExpression) evaluateStage(stage *evaluationStage, parameters Parameters) (interface{}, error) { +func (e EvaluableExpression) evaluateStage(stage *evaluationStage, parameters Parameters) (interface{}, error) { var left, right interface{} var err error if stage.leftStage != nil { - left, err = this.evaluateStage(stage.leftStage, parameters) + left, err = e.evaluateStage(stage.leftStage, parameters) if err != nil { return nil, err } @@ -202,13 +217,13 @@ func (this EvaluableExpression) evaluateStage(stage *evaluationStage, parameters } if right != shortCircuitHolder && stage.rightStage != nil { - right, err = this.evaluateStage(stage.rightStage, parameters) + right, err = e.evaluateStage(stage.rightStage, parameters) if err != nil { return nil, err } } - if this.ChecksTypes { + if e.ChecksTypes { if stage.typeCheck == nil { err = typeCheck(stage.leftTypeCheck, left, stage.symbol, stage.typeErrorFormat) @@ -247,30 +262,37 @@ func typeCheck(check stageTypeCheck, value interface{}, symbol OperatorSymbol, f } /* - Returns an array representing the ExpressionTokens that make up this expression. +Returns an array representing the ExpressionTokens that make up this expression. */ -func (this EvaluableExpression) Tokens() []ExpressionToken { +func (e EvaluableExpression) Tokens() []ExpressionToken { - return this.tokens + return e.tokens } /* - Returns the original expression used to create this EvaluableExpression. +Returns the original expression used to create this EvaluableExpression. */ -func (this EvaluableExpression) String() string { +func (e EvaluableExpression) String() string { - return this.inputExpression + return e.inputExpression } /* - Returns an array representing the variables contained in this EvaluableExpression. +Returns an array representing the variables contained in this EvaluableExpression. */ -func (this EvaluableExpression) Vars() []string { +func (e EvaluableExpression) Vars() []string { var varlist []string - for _, val := range this.Tokens() { + for _, val := range e.Tokens() { if val.Kind == VARIABLE { varlist = append(varlist, val.Value.(string)) } } return varlist } + +/* +Removes the tokens from the EvaluableExpression. This will cause the Tokens() and Vars() functions to no longer operate, but will save memory. +*/ +func (e *EvaluableExpression) CleanupTokens() { + e.tokens = e.tokens[:0] +} diff --git a/vendor/github.com/casbin/govaluate/EvaluableExpression_sql.go b/vendor/github.com/casbin/govaluate/EvaluableExpression_sql.go index 52409fa24..408a48388 100644 --- a/vendor/github.com/casbin/govaluate/EvaluableExpression_sql.go +++ b/vendor/github.com/casbin/govaluate/EvaluableExpression_sql.go @@ -18,19 +18,19 @@ Boolean values are considered to be "1" for true, "0" for false. Times are formatted according to this.QueryDateFormat. */ -func (this EvaluableExpression) ToSQLQuery() (string, error) { +func (e EvaluableExpression) ToSQLQuery() (string, error) { var stream *tokenStream var transactions *expressionOutputStream var transaction string var err error - stream = newTokenStream(this.tokens) + stream = newTokenStream(e.tokens) transactions = new(expressionOutputStream) for stream.hasNext() { - transaction, err = this.findNextSQLString(stream, transactions) + transaction, err = e.findNextSQLString(stream, transactions) if err != nil { return "", err } @@ -41,7 +41,7 @@ func (this EvaluableExpression) ToSQLQuery() (string, error) { return transactions.createString(" "), nil } -func (this EvaluableExpression) findNextSQLString(stream *tokenStream, transactions *expressionOutputStream) (string, error) { +func (e EvaluableExpression) findNextSQLString(stream *tokenStream, transactions *expressionOutputStream) (string, error) { var token ExpressionToken var ret string @@ -55,7 +55,7 @@ func (this EvaluableExpression) findNextSQLString(stream *tokenStream, transacti case PATTERN: ret = fmt.Sprintf("'%s'", token.Value.(*regexp.Regexp).String()) case TIME: - ret = fmt.Sprintf("'%s'", token.Value.(time.Time).Format(this.QueryDateFormat)) + ret = fmt.Sprintf("'%s'", token.Value.(time.Time).Format(e.QueryDateFormat)) case LOGICALOP: switch logicalSymbols[token.Value.(string)] { @@ -101,7 +101,7 @@ func (this EvaluableExpression) findNextSQLString(stream *tokenStream, transacti case COALESCE: left := transactions.rollback() - right, err := this.findNextSQLString(stream, transactions) + right, err := e.findNextSQLString(stream, transactions) if err != nil { return "", err } @@ -110,7 +110,7 @@ func (this EvaluableExpression) findNextSQLString(stream *tokenStream, transacti case TERNARY_TRUE: fallthrough case TERNARY_FALSE: - return "", errors.New("Ternary operators are unsupported in SQL output") + return "", errors.New("ternary operators are unsupported in sql output") } case PREFIX: switch prefixSymbols[token.Value.(string)] { @@ -119,7 +119,7 @@ func (this EvaluableExpression) findNextSQLString(stream *tokenStream, transacti ret = "NOT" default: - right, err := this.findNextSQLString(stream, transactions) + right, err := e.findNextSQLString(stream, transactions) if err != nil { return "", err } @@ -133,7 +133,7 @@ func (this EvaluableExpression) findNextSQLString(stream *tokenStream, transacti case EXPONENT: left := transactions.rollback() - right, err := this.findNextSQLString(stream, transactions) + right, err := e.findNextSQLString(stream, transactions) if err != nil { return "", err } @@ -142,7 +142,7 @@ func (this EvaluableExpression) findNextSQLString(stream *tokenStream, transacti case MODULUS: left := transactions.rollback() - right, err := this.findNextSQLString(stream, transactions) + right, err := e.findNextSQLString(stream, transactions) if err != nil { return "", err } diff --git a/vendor/github.com/casbin/govaluate/ExpressionToken.go b/vendor/github.com/casbin/govaluate/ExpressionToken.go index f849f3813..d83c6c98c 100644 --- a/vendor/github.com/casbin/govaluate/ExpressionToken.go +++ b/vendor/github.com/casbin/govaluate/ExpressionToken.go @@ -1,7 +1,7 @@ package govaluate /* - Represents a single parsed token. +Represents a single parsed token. */ type ExpressionToken struct { Kind TokenKind diff --git a/vendor/github.com/casbin/govaluate/OperatorSymbol.go b/vendor/github.com/casbin/govaluate/OperatorSymbol.go index 4b810658b..fcebe33ba 100644 --- a/vendor/github.com/casbin/govaluate/OperatorSymbol.go +++ b/vendor/github.com/casbin/govaluate/OperatorSymbol.go @@ -1,8 +1,7 @@ package govaluate /* - Represents the valid symbols for operators. - +Represents the valid symbols for operators. */ type OperatorSymbol int @@ -142,9 +141,9 @@ func findOperatorPrecedenceForSymbol(symbol OperatorSymbol) operatorPrecedence { } /* - Map of all valid comparators, and their string equivalents. - Used during parsing of expressions to determine if a symbol is, in fact, a comparator. - Also used during evaluation to determine exactly which comparator is being used. +Map of all valid comparators, and their string equivalents. +Used during parsing of expressions to determine if a symbol is, in fact, a comparator. +Also used during evaluation to determine exactly which comparator is being used. */ var comparatorSymbols = map[string]OperatorSymbol{ "==": EQ, @@ -221,13 +220,13 @@ var separatorSymbols = map[string]OperatorSymbol{ } /* - Returns true if this operator is contained by the given array of candidate symbols. - False otherwise. +Returns true if this operator is contained by the given array of candidate symbols. +False otherwise. */ -func (this OperatorSymbol) IsModifierType(candidate []OperatorSymbol) bool { +func (o OperatorSymbol) IsModifierType(candidate []OperatorSymbol) bool { for _, symbolType := range candidate { - if this == symbolType { + if o == symbolType { return true } } @@ -236,14 +235,14 @@ func (this OperatorSymbol) IsModifierType(candidate []OperatorSymbol) bool { } /* - Generally used when formatting type check errors. - We could store the stringified symbol somewhere else and not require a duplicated codeblock to translate - OperatorSymbol to string, but that would require more memory, and another field somewhere. - Adding operators is rare enough that we just stringify it here instead. +Generally used when formatting type check errors. +We could store the stringified symbol somewhere else and not require a duplicated codeblock to translate +OperatorSymbol to string, but that would require more memory, and another field somewhere. +Adding operators is rare enough that we just stringify it here instead. */ -func (this OperatorSymbol) String() string { +func (o OperatorSymbol) String() string { - switch this { + switch o { case NOOP: return "NOOP" case VALUE: diff --git a/vendor/github.com/casbin/govaluate/TokenKind.go b/vendor/github.com/casbin/govaluate/TokenKind.go index 7c9516d2d..9b174ca22 100644 --- a/vendor/github.com/casbin/govaluate/TokenKind.go +++ b/vendor/github.com/casbin/govaluate/TokenKind.go @@ -1,7 +1,7 @@ package govaluate /* - Represents all valid types of tokens that a token can be. +Represents all valid types of tokens that a token can be. */ type TokenKind int @@ -30,8 +30,8 @@ const ( ) /* - GetTokenKindString returns a string that describes the given TokenKind. - e.g., when passed the NUMERIC TokenKind, this returns the string "NUMERIC". +GetTokenKindString returns a string that describes the given TokenKind. +e.g., when passed the NUMERIC TokenKind, this returns the string "NUMERIC". */ func (kind TokenKind) String() string { diff --git a/vendor/github.com/casbin/govaluate/cache.go b/vendor/github.com/casbin/govaluate/cache.go new file mode 100644 index 000000000..8eca1064a --- /dev/null +++ b/vendor/github.com/casbin/govaluate/cache.go @@ -0,0 +1,75 @@ +//go:build go1.24 && cache + +package govaluate + +import ( + "sync" + "weak" +) + +var ( + paramMap = sync.Map{} + + constMap = sync.Map{} +) + +func getParameterStage(name string) (*evaluationStage, error) { + stage, ok := getParamFromMap(name) + if ok { + return stage, nil + } + + operator := makeParameterStage(name) + ret := &evaluationStage{ + operator: operator, + } + storeVal := weak.Make(ret) + paramMap.Store(name, storeVal) + return ret, nil +} + +func getParamFromMap(name string) (*evaluationStage, bool) { + stage, ok := paramMap.Load(name) + if ok { + ptr, ok := stage.(weak.Pointer[evaluationStage]) + if ok { + ret := ptr.Value() + if ret != nil { + return ret, true + } + paramMap.Delete(name) + } + } + return nil, false +} + +func getConstantStage(value any) (*evaluationStage, error) { + stage, ok := getConstantFromMap(value) + if ok { + return stage, nil + } + + operator := makeLiteralStage(value) + ret := &evaluationStage{ + symbol: LITERAL, + operator: operator, + } + storeVal := weak.Make(ret) + constMap.Store(value, storeVal) + return ret, nil +} + +func getConstantFromMap(value any) (*evaluationStage, bool) { + stage, ok := constMap.Load(value) + if ok { + ptr, ok := stage.(weak.Pointer[evaluationStage]) + if ok { + ret := ptr.Value() + if ret != nil { + return ret, true + } + constMap.Delete(value) + } + } + return nil, false +} diff --git a/vendor/github.com/casbin/govaluate/evaluationStage.go b/vendor/github.com/casbin/govaluate/evaluationStage.go index a22ade858..cc8ee10f3 100644 --- a/vendor/github.com/casbin/govaluate/evaluationStage.go +++ b/vendor/github.com/casbin/govaluate/evaluationStage.go @@ -48,26 +48,26 @@ var ( _false = interface{}(false) ) -func (this *evaluationStage) swapWith(other *evaluationStage) { +func (e *evaluationStage) swapWith(other *evaluationStage) { temp := *other - other.setToNonStage(*this) - this.setToNonStage(temp) + other.setToNonStage(*e) + e.setToNonStage(temp) } -func (this *evaluationStage) setToNonStage(other evaluationStage) { +func (e *evaluationStage) setToNonStage(other evaluationStage) { - this.symbol = other.symbol - this.operator = other.operator - this.leftTypeCheck = other.leftTypeCheck - this.rightTypeCheck = other.rightTypeCheck - this.typeCheck = other.typeCheck - this.typeErrorFormat = other.typeErrorFormat + e.symbol = other.symbol + e.operator = other.operator + e.leftTypeCheck = other.leftTypeCheck + e.rightTypeCheck = other.rightTypeCheck + e.typeCheck = other.typeCheck + e.typeErrorFormat = other.typeErrorFormat } -func (this *evaluationStage) isShortCircuitable() bool { +func (e *evaluationStage) isShortCircuitable() bool { - switch this.symbol { + switch e.symbol { case AND: fallthrough case OR: @@ -178,13 +178,13 @@ func regexStage(left interface{}, right interface{}, parameters Parameters) (int case string: pattern, err = regexp.Compile(right) if err != nil { - return nil, fmt.Errorf("Unable to compile regexp pattern '%v': %v", right, err) + return nil, fmt.Errorf("unable to compile regexp pattern '%v': %w", right, err) } case *regexp.Regexp: pattern = right } - return pattern.Match([]byte(left.(string))), nil + return pattern.MatchString(left.(string)), nil } func notRegexStage(left interface{}, right interface{}, parameters Parameters) (interface{}, error) { @@ -268,9 +268,9 @@ func typeConvertParams(method reflect.Value, params []reflect.Value) ([]reflect. if numIn != numParams { if numIn > numParams { - return nil, fmt.Errorf("Too few arguments to parameter call: got %d arguments, expected %d", len(params), numIn) + return nil, fmt.Errorf("too few arguments to parameter call: got %d arguments, expected %d", len(params), numIn) } - return nil, fmt.Errorf("Too many arguments to parameter call: got %d arguments, expected %d", len(params), numIn) + return nil, fmt.Errorf("too many arguments to parameter call: got %d arguments, expected %d", len(params), numIn) } for i := 0; i < numIn; i++ { diff --git a/vendor/github.com/casbin/govaluate/expressionFunctions.go b/vendor/github.com/casbin/govaluate/expressionFunctions.go index ac6592b3f..142cf5281 100644 --- a/vendor/github.com/casbin/govaluate/expressionFunctions.go +++ b/vendor/github.com/casbin/govaluate/expressionFunctions.go @@ -1,8 +1,8 @@ package govaluate /* - Represents a function that can be called from within an expression. - This method must return an error if, for any reason, it is unable to produce exactly one unambiguous result. - An error returned will halt execution of the expression. +Represents a function that can be called from within an expression. +This method must return an error if, for any reason, it is unable to produce exactly one unambiguous result. +An error returned will halt execution of the expression. */ type ExpressionFunction func(arguments ...interface{}) (interface{}, error) diff --git a/vendor/github.com/casbin/govaluate/expressionOutputStream.go b/vendor/github.com/casbin/govaluate/expressionOutputStream.go index 88a841639..517bbf193 100644 --- a/vendor/github.com/casbin/govaluate/expressionOutputStream.go +++ b/vendor/github.com/casbin/govaluate/expressionOutputStream.go @@ -5,42 +5,42 @@ import ( ) /* - Holds a series of "transactions" which represent each token as it is output by an outputter (such as ToSQLQuery()). - Some outputs (such as SQL) require a function call or non-c-like syntax to represent an expression. - To accomplish this, this struct keeps track of each translated token as it is output, and can return and rollback those transactions. +Holds a series of "transactions" which represent each token as it is output by an outputter (such as ToSQLQuery()). +Some outputs (such as SQL) require a function call or non-c-like syntax to represent an expression. +To accomplish this, this struct keeps track of each translated token as it is output, and can return and rollback those transactions. */ type expressionOutputStream struct { transactions []string } -func (this *expressionOutputStream) add(transaction string) { - this.transactions = append(this.transactions, transaction) +func (e *expressionOutputStream) add(transaction string) { + e.transactions = append(e.transactions, transaction) } -func (this *expressionOutputStream) rollback() string { +func (e *expressionOutputStream) rollback() string { - index := len(this.transactions) - 1 - ret := this.transactions[index] + index := len(e.transactions) - 1 + ret := e.transactions[index] - this.transactions = this.transactions[:index] + e.transactions = e.transactions[:index] return ret } -func (this *expressionOutputStream) createString(delimiter string) string { +func (e *expressionOutputStream) createString(delimiter string) string { var retBuffer bytes.Buffer var transaction string - penultimate := len(this.transactions) - 1 + penultimate := len(e.transactions) - 1 for i := 0; i < penultimate; i++ { - transaction = this.transactions[i] + transaction = e.transactions[i] retBuffer.WriteString(transaction) retBuffer.WriteString(delimiter) } - retBuffer.WriteString(this.transactions[penultimate]) + retBuffer.WriteString(e.transactions[penultimate]) return retBuffer.String() } diff --git a/vendor/github.com/casbin/govaluate/lexerState.go b/vendor/github.com/casbin/govaluate/lexerState.go index 6726e909e..d879f40bb 100644 --- a/vendor/github.com/casbin/govaluate/lexerState.go +++ b/vendor/github.com/casbin/govaluate/lexerState.go @@ -16,7 +16,7 @@ type lexerState struct { // Constant for all purposes except compiler. var validLexerStates = []lexerState{ - lexerState{ + { kind: UNKNOWN, isEOF: false, isNullable: true, @@ -35,7 +35,7 @@ var validLexerStates = []lexerState{ }, }, - lexerState{ + { kind: CLAUSE, isEOF: false, @@ -56,7 +56,7 @@ var validLexerStates = []lexerState{ }, }, - lexerState{ + { kind: CLAUSE_CLOSE, isEOF: true, @@ -79,7 +79,7 @@ var validLexerStates = []lexerState{ }, }, - lexerState{ + { kind: NUMERIC, isEOF: true, @@ -94,7 +94,7 @@ var validLexerStates = []lexerState{ SEPARATOR, }, }, - lexerState{ + { kind: BOOLEAN, isEOF: true, @@ -109,7 +109,7 @@ var validLexerStates = []lexerState{ SEPARATOR, }, }, - lexerState{ + { kind: STRING, isEOF: true, @@ -124,7 +124,7 @@ var validLexerStates = []lexerState{ SEPARATOR, }, }, - lexerState{ + { kind: TIME, isEOF: true, @@ -138,7 +138,7 @@ var validLexerStates = []lexerState{ SEPARATOR, }, }, - lexerState{ + { kind: PATTERN, isEOF: true, @@ -152,7 +152,7 @@ var validLexerStates = []lexerState{ SEPARATOR, }, }, - lexerState{ + { kind: VARIABLE, isEOF: true, @@ -167,7 +167,7 @@ var validLexerStates = []lexerState{ SEPARATOR, }, }, - lexerState{ + { kind: MODIFIER, isEOF: false, @@ -185,7 +185,7 @@ var validLexerStates = []lexerState{ CLAUSE_CLOSE, }, }, - lexerState{ + { kind: COMPARATOR, isEOF: false, @@ -205,7 +205,7 @@ var validLexerStates = []lexerState{ PATTERN, }, }, - lexerState{ + { kind: LOGICALOP, isEOF: false, @@ -224,7 +224,7 @@ var validLexerStates = []lexerState{ CLAUSE_CLOSE, }, }, - lexerState{ + { kind: PREFIX, isEOF: false, @@ -241,7 +241,7 @@ var validLexerStates = []lexerState{ }, }, - lexerState{ + { kind: TERNARY, isEOF: false, @@ -260,7 +260,7 @@ var validLexerStates = []lexerState{ SEPARATOR, }, }, - lexerState{ + { kind: FUNCTION, isEOF: false, @@ -269,7 +269,7 @@ var validLexerStates = []lexerState{ CLAUSE, }, }, - lexerState{ + { kind: ACCESSOR, isEOF: true, @@ -284,7 +284,7 @@ var validLexerStates = []lexerState{ SEPARATOR, }, }, - lexerState{ + { kind: SEPARATOR, isEOF: false, @@ -304,9 +304,9 @@ var validLexerStates = []lexerState{ }, } -func (this lexerState) canTransitionTo(kind TokenKind) bool { +func (l lexerState) canTransitionTo(kind TokenKind) bool { - for _, validKind := range this.validNextKinds { + for _, validKind := range l.validNextKinds { if validKind == kind { return true @@ -346,7 +346,7 @@ func checkExpressionSyntax(tokens []ExpressionToken) error { if !state.isNullable && token.Value == nil { - errorMsg := fmt.Sprintf("Token kind '%v' cannot have a nil value", token.Kind.String()) + errorMsg := fmt.Sprintf("token kind '%v' cannot have a nil value", token.Kind.String()) return errors.New(errorMsg) } @@ -354,7 +354,7 @@ func checkExpressionSyntax(tokens []ExpressionToken) error { } if !state.isEOF { - return errors.New("Unexpected end of expression") + return errors.New("unexpected end of expression") } return nil } @@ -368,6 +368,6 @@ func getLexerStateForToken(kind TokenKind) (lexerState, error) { } } - errorMsg := fmt.Sprintf("No lexer state found for token kind '%v'\n", kind.String()) + errorMsg := fmt.Sprintf("no lexer state found for token kind '%v'\n", kind.String()) return validLexerStates[0], errors.New(errorMsg) } diff --git a/vendor/github.com/casbin/govaluate/lexerStream.go b/vendor/github.com/casbin/govaluate/lexerStream.go index c6ed76ec4..7faa52f98 100644 --- a/vendor/github.com/casbin/govaluate/lexerStream.go +++ b/vendor/github.com/casbin/govaluate/lexerStream.go @@ -1,37 +1,69 @@ package govaluate +import ( + "sync" + "unicode/utf8" +) + type lexerStream struct { - source []rune - position int - length int + sourceString string + source []rune + strPosition int + position int + length int } -func newLexerStream(source string) *lexerStream { - - var ret *lexerStream - var runes []rune +var lexerStreamPool = sync.Pool{ + New: func() interface{} { + return new(lexerStream) + }, +} +func newLexerStream(source string) *lexerStream { + ret := lexerStreamPool.Get().(*lexerStream) + if ret.source == nil { + ret.source = make([]rune, 0, len(source)) + } for _, character := range source { - runes = append(runes, character) + ret.source = append(ret.source, character) } - - ret = new(lexerStream) - ret.source = runes - ret.length = len(runes) + ret.sourceString = source + ret.position = 0 + ret.strPosition = 0 + ret.length = len(ret.source) return ret } -func (this *lexerStream) readCharacter() rune { - - character := this.source[this.position] - this.position += 1 +func (stream *lexerStream) readCharacter() rune { + character := stream.source[stream.position] + stream.position += 1 + stream.strPosition += utf8.RuneLen(character) return character } -func (this *lexerStream) rewind(amount int) { - this.position -= amount +func (stream *lexerStream) rewind(amount int) { + if amount < 0 { + stream.position -= amount + stream.strPosition -= amount + } + strAmount := 0 + for i := 0; i < amount; i++ { + if stream.position >= stream.length { + strAmount += 1 + stream.position -= 1 + continue + } + strAmount += utf8.RuneLen(stream.source[stream.position-1]) + stream.position -= 1 + } + stream.strPosition -= strAmount +} + +func (stream lexerStream) canRead() bool { + return stream.position < stream.length } -func (this lexerStream) canRead() bool { - return this.position < this.length +func (stream *lexerStream) close() { + stream.source = stream.source[:0] + lexerStreamPool.Put(stream) } diff --git a/vendor/github.com/casbin/govaluate/noCache.go b/vendor/github.com/casbin/govaluate/noCache.go new file mode 100644 index 000000000..afec35cb8 --- /dev/null +++ b/vendor/github.com/casbin/govaluate/noCache.go @@ -0,0 +1,18 @@ +//go:build !go1.24 || !cache + +package govaluate + +func getParameterStage(name string) (*evaluationStage, error) { + operator := makeParameterStage(name) + return &evaluationStage{ + operator: operator, + }, nil +} + +func getConstantStage(value interface{}) (*evaluationStage, error) { + operator := makeLiteralStage(value) + return &evaluationStage{ + symbol: LITERAL, + operator: operator, + }, nil +} diff --git a/vendor/github.com/casbin/govaluate/parameters.go b/vendor/github.com/casbin/govaluate/parameters.go index 6c5b9ecb5..c84b5778e 100644 --- a/vendor/github.com/casbin/govaluate/parameters.go +++ b/vendor/github.com/casbin/govaluate/parameters.go @@ -5,8 +5,8 @@ import ( ) /* - Parameters is a collection of named parameters that can be used by an EvaluableExpression to retrieve parameters - when an expression tries to use them. +Parameters is a collection of named parameters that can be used by an EvaluableExpression to retrieve parameters +when an expression tries to use them. */ type Parameters interface { diff --git a/vendor/github.com/casbin/govaluate/parsing.go b/vendor/github.com/casbin/govaluate/parsing.go index dae78f7d2..98189c072 100644 --- a/vendor/github.com/casbin/govaluate/parsing.go +++ b/vendor/github.com/casbin/govaluate/parsing.go @@ -7,13 +7,22 @@ import ( "regexp" "strconv" "strings" + "sync" "time" "unicode" + "unicode/utf8" ) -func parseTokens(expression string, functions map[string]ExpressionFunction) ([]ExpressionToken, error) { +var ( + averageTokens = 1 + samplesMu = sync.Mutex{} + samples = make([]int, 0, 10) +) - var ret []ExpressionToken +func parseTokens(expression string, functions map[string]ExpressionFunction) ([]ExpressionToken, error) { + samplesMu.Lock() + ret := make([]ExpressionToken, 0, averageTokens) + samplesMu.Unlock() var token ExpressionToken var stream *lexerStream var state lexerState @@ -43,6 +52,20 @@ func parseTokens(expression string, functions map[string]ExpressionFunction) ([] // append this valid token ret = append(ret, token) } + stream.close() + samplesMu.Lock() + if len(samples) == cap(samples) { + copy(samples, samples[1:]) + samples[len(samples)-1] = len(ret) + } else { + samples = append(samples, len(ret)) + } + total := 0 + for _, val := range samples { + total += val + } + averageTokens = total / len(samples) + samplesMu.Unlock() err = checkBalance(ret) if err != nil { @@ -128,7 +151,7 @@ func readToken(stream *lexerStream, state lexerState, functions map[string]Expre kind = VARIABLE if !completed { - return ExpressionToken{}, errors.New("Unclosed parameter bracket"), false + return ExpressionToken{}, errors.New("unclosed parameter bracket"), false } // above method normally rewinds us to the closing bracket, which we want to skip. @@ -140,30 +163,23 @@ func readToken(stream *lexerStream, state lexerState, functions map[string]Expre if unicode.IsLetter(character) { tokenString = readTokenUntilFalse(stream, isVariableName) - - tokenValue = tokenString - kind = VARIABLE - - // boolean? - if tokenValue == "true" { - + switch tokenString { + case "true": kind = BOOLEAN tokenValue = true - } else { - - if tokenValue == "false" { - - kind = BOOLEAN - tokenValue = false - } - } - - // textual operator? - if tokenValue == "in" || tokenValue == "IN" { - + case "false": + kind = BOOLEAN + tokenValue = false + case "in": + fallthrough + case "IN": // force lower case for consistency tokenValue = "in" kind = COMPARATOR + default: + // This causes an alloc, avoid it if we can + tokenValue = tokenString + kind = VARIABLE } // function? @@ -194,7 +210,7 @@ func readToken(stream *lexerStream, state lexerState, functions map[string]Expre tokenValue, completed = readUntilFalse(stream, true, false, true, isNotQuote) if !completed { - return ExpressionToken{}, errors.New("Unclosed string literal"), false + return ExpressionToken{}, errors.New("unclosed string literal"), false } // advance the stream one position, since reading until false assumes the terminator is a real token @@ -284,37 +300,51 @@ func readTokenUntilFalse(stream *lexerStream, condition func(rune) bool) string return ret } +var tokenBufferPool = sync.Pool{ + New: func() interface{} { + return &bytes.Buffer{} + }, +} + /* Returns the string that was read until the given [condition] was false, or whitespace was broken. Returns false if the stream ended before whitespace was broken or condition was met. */ func readUntilFalse(stream *lexerStream, includeWhitespace bool, breakWhitespace bool, allowEscaping bool, condition func(rune) bool) (string, bool) { - var tokenBuffer bytes.Buffer + tokenBuffer := tokenBufferPool.Get().(*bytes.Buffer) + tokenBuffer.Reset() var character rune - var conditioned bool - conditioned = false + startPosition := stream.strPosition + reuseString := true + trimString := false + conditioned := false for stream.canRead() { character = stream.readCharacter() + if character > utf8.RuneSelf { + // International runes, we can't just grab from the string in this case + reuseString = false + } // Use backslashes to escape anything if allowEscaping && character == '\\' { - + reuseString = false character = stream.readCharacter() tokenBuffer.WriteString(string(character)) continue } if unicode.IsSpace(character) { - if breakWhitespace && tokenBuffer.Len() > 0 { conditioned = true + trimString = true break } if !includeWhitespace { + reuseString = false continue } } @@ -328,7 +358,21 @@ func readUntilFalse(stream *lexerStream, includeWhitespace bool, breakWhitespace } } - return tokenBuffer.String(), conditioned + // This reduces allocations by just reusing parts of the original source string if applicable + if reuseString { + tokenBuffer.Reset() + tokenBufferPool.Put(tokenBuffer) + ret := stream.sourceString[startPosition:stream.strPosition] + if trimString { + ret = ret[:len(ret)-1] + } + return ret, conditioned + } + + ret := tokenBuffer.String() + tokenBuffer.Reset() + tokenBufferPool.Put(tokenBuffer) + return ret, conditioned } /* @@ -395,8 +439,10 @@ func checkBalance(tokens []ExpressionToken) error { } } + stream.close() + if parens != 0 { - return errors.New("Unbalanced parenthesis") + return errors.New("unbalanced parenthesis") } return nil } @@ -426,13 +472,13 @@ func isNotQuote(character rune) bool { func isNotAlphanumeric(character rune) bool { - return !(unicode.IsDigit(character) || - unicode.IsLetter(character) || - character == '(' || - character == ')' || - character == '[' || - character == ']' || // starting to feel like there needs to be an `isOperation` func (#59) - !isNotQuote(character)) + return !unicode.IsDigit(character) && + !unicode.IsLetter(character) && + character != '(' && + character != ')' && + character != '[' && + character != ']' && // starting to feel like there needs to be an `isOperation` func (#59) + isNotQuote(character) } func isVariableName(character rune) bool { @@ -448,6 +494,12 @@ func isNotClosingBracket(character rune) bool { return character != ']' } +type timeFormat struct { + format string + minLength int + maxLength int +} + /* Attempts to parse the [candidate] as a Time. Tries a series of standardized date formats, returns the Time if one applies, @@ -458,26 +510,34 @@ func tryParseTime(candidate string) (time.Time, bool) { var ret time.Time var found bool - timeFormats := [...]string{ - time.ANSIC, - time.UnixDate, - time.RubyDate, - time.Kitchen, - time.RFC3339, - time.RFC3339Nano, - "2006-01-02", // RFC 3339 - "2006-01-02 15:04", // RFC 3339 with minutes - "2006-01-02 15:04:05", // RFC 3339 with seconds - "2006-01-02 15:04:05-07:00", // RFC 3339 with seconds and timezone - "2006-01-02T15Z0700", // ISO8601 with hour - "2006-01-02T15:04Z0700", // ISO8601 with minutes - "2006-01-02T15:04:05Z0700", // ISO8601 with seconds - "2006-01-02T15:04:05.999999999Z0700", // ISO8601 with nanoseconds + if !strings.Contains(candidate, ":") && !strings.Contains(candidate, "-") { + // The blow formats either have a : or a - in them. If the string contains neither it cannot be a time string + return time.Now(), false } - for _, format := range timeFormats { + timeFormats := [...]timeFormat{ + {time.ANSIC, len(time.ANSIC) - 1, len(time.ANSIC)}, + {time.UnixDate, len(time.UnixDate) - 1, len(time.ANSIC)}, + {time.RubyDate, len(time.RubyDate), len(time.RubyDate)}, + {time.Kitchen, len(time.Kitchen), len(time.Kitchen) + 1}, + {time.RFC3339, len(time.RFC3339), len(time.RFC3339)}, + {time.RFC3339Nano, len(time.RFC3339Nano), len(time.RFC3339Nano)}, + {"2006-01-02", 10, 10}, // RFC 3339 + {"2006-01-02 15:04", 16, 16}, // RFC 3339 with minutes + {"2006-01-02 15:04:05", 19, 19}, // RFC 3339 with seconds + {"2006-01-02 15:04:05-07:00", 25, 25}, // RFC 3339 with seconds and timezone + {"2006-01-02T15Z0700", 18, 18}, // ISO8601 with hour + {"2006-01-02T15:04Z0700", 21, 21}, // ISO8601 with minutes + {"2006-01-02T15:04:05Z0700", 24, 24}, // ISO8601 with seconds + {"2006-01-02T15:04:05.999999999Z0700", 34, 34}, // ISO8601 with nanoseconds + } - ret, found = tryParseExactTime(candidate, format) + for _, format := range timeFormats { + // Avoid trying to parse formats it could not be to reduce allocation of time parse errors + if len(candidate) < format.minLength || len(candidate) > format.maxLength { + continue + } + ret, found = tryParseExactTime(candidate, format.format) if found { return ret, true } diff --git a/vendor/github.com/casbin/govaluate/stagePlanner.go b/vendor/github.com/casbin/govaluate/stagePlanner.go index d18be4b8b..770af5aee 100644 --- a/vendor/github.com/casbin/govaluate/stagePlanner.go +++ b/vendor/github.com/casbin/govaluate/stagePlanner.go @@ -186,6 +186,7 @@ func planStages(tokens []ExpressionToken) (*evaluationStage, error) { if err != nil { return nil, err } + stream.close() // while we're now fully-planned, we now need to re-order same-precedence operators. // this could probably be avoided with a different planning method @@ -402,7 +403,10 @@ func planValue(stream *tokenStream) (*evaluationStage, error) { // clauses with single elements don't trigger SEPARATE stage planner // this ensures that when used as part of an "in" comparison, the array requirement passes if prev.Kind == COMPARATOR && prev.Value == "in" && ret.symbol == LITERAL { - ret.operator = ensureSliceStage(ret.operator) + // We need to copy this in case we are using the cached value... + tmp := *ret + tmp.operator = ensureSliceStage(ret.operator) + ret = &tmp } // advance past the CLAUSE_CLOSE token. We know that it's a CLAUSE_CLOSE, because at parse-time we check for unbalanced parens. @@ -427,7 +431,7 @@ func planValue(stream *tokenStream) (*evaluationStage, error) { return nil, nil case VARIABLE: - operator = makeParameterStage(token.Value.(string)) + return getParameterStage(token.Value.(string)) case NUMERIC: fallthrough @@ -436,11 +440,9 @@ func planValue(stream *tokenStream) (*evaluationStage, error) { case PATTERN: fallthrough case BOOLEAN: - symbol = LITERAL - operator = makeLiteralStage(token.Value) + return getConstantStage(token.Value) case TIME: - symbol = LITERAL - operator = makeLiteralStage(float64(token.Value.(time.Time).Unix())) + return getConstantStage(float64(token.Value.(time.Time).Unix())) case PREFIX: stream.rewind() diff --git a/vendor/github.com/casbin/govaluate/tokenStream.go b/vendor/github.com/casbin/govaluate/tokenStream.go index 7c7c40abd..b530fd7ab 100644 --- a/vendor/github.com/casbin/govaluate/tokenStream.go +++ b/vendor/github.com/casbin/govaluate/tokenStream.go @@ -1,30 +1,43 @@ package govaluate +import "sync" + type tokenStream struct { tokens []ExpressionToken index int tokenLength int } +var tokenStreamPool = sync.Pool{ + New: func() interface{} { + return new(tokenStream) + }, +} + func newTokenStream(tokens []ExpressionToken) *tokenStream { - ret := new(tokenStream) + ret := tokenStreamPool.Get().(*tokenStream) ret.tokens = tokens + ret.index = 0 ret.tokenLength = len(tokens) return ret } -func (this *tokenStream) rewind() { - this.index -= 1 +func (t *tokenStream) rewind() { + t.index -= 1 } -func (this *tokenStream) next() ExpressionToken { - token := this.tokens[this.index] +func (t *tokenStream) next() ExpressionToken { + token := t.tokens[t.index] - this.index += 1 + t.index += 1 return token } -func (this tokenStream) hasNext() bool { +func (t tokenStream) hasNext() bool { + + return t.index < t.tokenLength +} - return this.index < this.tokenLength +func (t *tokenStream) close() { + tokenStreamPool.Put(t) } diff --git a/vendor/modules.txt b/vendor/modules.txt index 6e8815d2d..7e1e43333 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -209,7 +209,7 @@ github.com/casbin/casbin/v2/persist/file-adapter github.com/casbin/casbin/v2/rbac github.com/casbin/casbin/v2/rbac/default-role-manager github.com/casbin/casbin/v2/util -# github.com/casbin/govaluate v1.3.0 +# github.com/casbin/govaluate v1.10.0 ## explicit; go 1.13 github.com/casbin/govaluate # github.com/cespare/xxhash/v2 v2.3.0