From 78b0ec3456cf2bdb9cf22bd47977e178cddce878 Mon Sep 17 00:00:00 2001 From: Juan Pablo Tosso Date: Fri, 6 Mar 2026 12:30:03 +0100 Subject: [PATCH 1/2] perf: store transformationValue by value in cache map Store transformationValue structs directly in the transformation cache map instead of as pointers. This eliminates one heap allocation per cache entry and improves data locality for cache lookups. Co-Authored-By: Claude Opus 4.6 --- internal/corazawaf/rule.go | 11 +++++------ internal/corazawaf/rule_test.go | 4 ++-- internal/corazawaf/transaction.go | 2 +- internal/corazawaf/waf.go | 2 +- 4 files changed, 9 insertions(+), 10 deletions(-) diff --git a/internal/corazawaf/rule.go b/internal/corazawaf/rule.go index b961e2e27..56589f952 100644 --- a/internal/corazawaf/rule.go +++ b/internal/corazawaf/rule.go @@ -161,7 +161,7 @@ const chainLevelZero = 0 // Evaluate will evaluate the current rule for the indicated transaction // If the operator matches, actions will be evaluated, and it will return // the matched variables, keys and values (MatchData) -func (r *Rule) Evaluate(phase types.RulePhase, tx plugintypes.TransactionState, cache map[transformationKey]*transformationValue) { +func (r *Rule) Evaluate(phase types.RulePhase, tx plugintypes.TransactionState, cache map[transformationKey]transformationValue) { // collectiveMatchedValues lives across recursive calls of doEvaluate var collectiveMatchedValues []types.MatchData @@ -180,7 +180,7 @@ func (r *Rule) Evaluate(phase types.RulePhase, tx plugintypes.TransactionState, const noID = 0 -func (r *Rule) doEvaluate(logger debuglog.Logger, phase types.RulePhase, tx *Transaction, collectiveMatchedValues *[]types.MatchData, chainLevel int, cache map[transformationKey]*transformationValue) []types.MatchData { +func (r *Rule) doEvaluate(logger debuglog.Logger, phase types.RulePhase, tx *Transaction, collectiveMatchedValues *[]types.MatchData, chainLevel int, cache map[transformationKey]transformationValue) []types.MatchData { tx.Capture = r.Capture if multiphaseEvaluation { @@ -397,7 +397,7 @@ func (r *Rule) transformMultiMatchArg(arg types.MatchData) ([]string, []error) { return r.executeTransformationsMultimatch(arg.Value()) } -func (r *Rule) transformArg(arg types.MatchData, argIdx int, cache map[transformationKey]*transformationValue) (string, []error) { +func (r *Rule) transformArg(arg types.MatchData, argIdx int, cache map[transformationKey]transformationValue) (string, []error) { switch { case len(r.transformations) == 0: return arg.Value(), nil @@ -419,12 +419,11 @@ func (r *Rule) transformArg(arg types.MatchData, argIdx int, cache map[transform return cached.arg, cached.errs } else { ars, es := r.executeTransformations(arg.Value()) - errs := es - cache[key] = &transformationValue{ + cache[key] = transformationValue{ arg: ars, errs: es, } - return ars, errs + return ars, es } } } diff --git a/internal/corazawaf/rule_test.go b/internal/corazawaf/rule_test.go index 9c6eb247b..8617a4dc6 100644 --- a/internal/corazawaf/rule_test.go +++ b/internal/corazawaf/rule_test.go @@ -574,7 +574,7 @@ func TestExecuteTransformationsMultiMatchReturnsMultipleErrors(t *testing.T) { } func TestTransformArgSimple(t *testing.T) { - transformationCache := map[transformationKey]*transformationValue{} + transformationCache := map[transformationKey]transformationValue{} md := &corazarules.MatchData{ Variable_: variables.RequestURI, Key_: "REQUEST_URI", @@ -609,7 +609,7 @@ func TestTransformArgSimple(t *testing.T) { } func TestTransformArgNoCacheForTXVariable(t *testing.T) { - transformationCache := map[transformationKey]*transformationValue{} + transformationCache := map[transformationKey]transformationValue{} md := &corazarules.MatchData{ Variable_: variables.TX, Key_: "Custom_TX_Variable", diff --git a/internal/corazawaf/transaction.go b/internal/corazawaf/transaction.go index 20c056f22..17dec842c 100644 --- a/internal/corazawaf/transaction.go +++ b/internal/corazawaf/transaction.go @@ -122,7 +122,7 @@ type Transaction struct { variables TransactionVariables - transformationCache map[transformationKey]*transformationValue + transformationCache map[transformationKey]transformationValue } func (tx *Transaction) ID() string { diff --git a/internal/corazawaf/waf.go b/internal/corazawaf/waf.go index 0217025a2..6cccb8923 100644 --- a/internal/corazawaf/waf.go +++ b/internal/corazawaf/waf.go @@ -230,7 +230,7 @@ func (w *WAF) newTransaction(opts Options) *Transaction { }) tx.variables = *NewTransactionVariables() - tx.transformationCache = map[transformationKey]*transformationValue{} + tx.transformationCache = map[transformationKey]transformationValue{} } // set capture variables From a199592a09e46a8446bd49cf5ff558c463478a82 Mon Sep 17 00:00:00 2001 From: Juan Pablo Tosso Date: Fri, 6 Mar 2026 15:28:57 +0100 Subject: [PATCH 2/2] bench: add BenchmarkRuleEvalWithTransformations --- internal/corazawaf/transaction_test.go | 44 ++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/internal/corazawaf/transaction_test.go b/internal/corazawaf/transaction_test.go index ee6059d8b..0a8107dde 100644 --- a/internal/corazawaf/transaction_test.go +++ b/internal/corazawaf/transaction_test.go @@ -22,7 +22,9 @@ import ( "github.com/corazawaf/coraza/v3/internal/collections" "github.com/corazawaf/coraza/v3/internal/corazarules" "github.com/corazawaf/coraza/v3/internal/environment" + "github.com/corazawaf/coraza/v3/internal/operators" utils "github.com/corazawaf/coraza/v3/internal/strings" + "github.com/corazawaf/coraza/v3/internal/transformations" "github.com/corazawaf/coraza/v3/types" "github.com/corazawaf/coraza/v3/types/variables" ) @@ -1864,3 +1866,45 @@ func TestRequestFilename(t *testing.T) { }) } } + +func BenchmarkRuleEvalWithTransformations(b *testing.B) { + waf := NewWAF() + op, err := operators.Get("unconditionalMatch", plugintypes.OperatorOptions{}) + if err != nil { + b.Fatal(err) + } + lowercaseFn, err := transformations.GetTransformation("lowercase") + if err != nil { + b.Fatal(err) + } + + rule := NewRule() + rule.ID_ = 1000 + rule.LogID_ = "1000" + rule.Phase_ = types.PhaseRequestHeaders + rule.operator = &ruleOperatorParams{ + Operator: op, + Function: "@unconditionalMatch", + } + if err := rule.AddTransformation("lowercase", lowercaseFn); err != nil { + b.Fatal(err) + } + rule.variables = append(rule.variables, ruleVariableParams{ + Variable: variables.Args, + }) + if err := waf.Rules.Add(rule); err != nil { + b.Fatal(err) + } + + tx := waf.NewTransaction() + tx.ProcessURI("/test?a=1&b=2&c=3&d=4&e=5", "GET", "HTTP/1.1") + tx.AddRequestHeader("Host", "example.com") + tx.ProcessRequestHeaders() + defer tx.Close() + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + waf.Rules.Eval(types.PhaseRequestHeaders, tx) + } +}