From 90490ce86dbd1743bb21c5e91dc4cb5a718d5c1a Mon Sep 17 00:00:00 2001 From: Your Name Date: Fri, 3 Apr 2026 07:48:05 +0800 Subject: [PATCH] =?UTF-8?q?fix(gateway):=20=E4=BF=AE=E5=A4=8DRuleEngine?= =?UTF-8?q?=E4=B8=ADregexp=E7=BC=96=E8=AF=91=E9=94=99=E8=AF=AF=E5=92=8C?= =?UTF-8?q?=E5=B9=B6=E5=8F=91=E5=AE=89=E5=85=A8=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit P0-05: regexp.Compile错误被静默忽略 - extractMatch函数现在返回(string, error) - 正确处理regexp.Compile错误,返回格式化错误信息 - 修复无效正则导致的panic问题 P0-06: compiledPatterns非线程安全 - 添加sync.RWMutex保护map并发访问 - matchRegex和extractMatch使用读锁/写锁保护 - 实现双重检查锁定模式优化性能 测试验证: - 使用-race flag验证无数据竞争 - 并发100个goroutine测试通过 --- gateway/internal/compliance/rules/engine.go | 71 ++++++++--- .../internal/compliance/rules/engine_test.go | 111 ++++++++++++++++++ 2 files changed, 164 insertions(+), 18 deletions(-) create mode 100644 gateway/internal/compliance/rules/engine_test.go diff --git a/gateway/internal/compliance/rules/engine.go b/gateway/internal/compliance/rules/engine.go index d3457f2..da00c46 100644 --- a/gateway/internal/compliance/rules/engine.go +++ b/gateway/internal/compliance/rules/engine.go @@ -1,7 +1,9 @@ package rules import ( + "fmt" "regexp" + "sync" ) // MatchResult 匹配结果 @@ -22,8 +24,9 @@ type MatcherResult struct { // RuleEngine 规则引擎 type RuleEngine struct { - loader *RuleLoader + loader *RuleLoader compiledPatterns map[string][]*regexp.Regexp + patternMu sync.RWMutex } // NewRuleEngine 创建新的规则引擎 @@ -54,7 +57,7 @@ func (e *RuleEngine) Match(rule Rule, content string) MatchResult { case "regex_match": matcherResult.IsMatch = e.matchRegex(matcher.Pattern, content) if matcherResult.IsMatch { - matcherResult.MatchValue = e.extractMatch(matcher.Pattern, content) + matcherResult.MatchValue, _ = e.extractMatch(matcher.Pattern, content) } default: // 未知匹配器类型,默认不匹配 @@ -71,32 +74,64 @@ func (e *RuleEngine) Match(rule Rule, content string) MatchResult { // matchRegex 执行正则表达式匹配 func (e *RuleEngine) matchRegex(pattern string, content string) bool { - // 编译并缓存正则表达式 + // 先尝试读取缓存(使用读锁) + e.patternMu.RLock() regex, ok := e.compiledPatterns[pattern] - if !ok { - var err error - regex = make([]*regexp.Regexp, 1) - regex[0], err = regexp.Compile(pattern) - if err != nil { - return false - } - e.compiledPatterns[pattern] = regex + e.patternMu.RUnlock() + if ok { + return regex[0].MatchString(content) } + // 未命中,需要编译(使用写锁) + e.patternMu.Lock() + defer e.patternMu.Unlock() + + // 双重检查 + regex, ok = e.compiledPatterns[pattern] + if ok { + return regex[0].MatchString(content) + } + + var err error + regex = make([]*regexp.Regexp, 1) + regex[0], err = regexp.Compile(pattern) + if err != nil { + return false + } + e.compiledPatterns[pattern] = regex + return regex[0].MatchString(content) } // extractMatch 提取匹配值 -func (e *RuleEngine) extractMatch(pattern string, content string) string { +func (e *RuleEngine) extractMatch(pattern string, content string) (string, error) { + // 先尝试读取缓存(使用读锁) + e.patternMu.RLock() regex, ok := e.compiledPatterns[pattern] - if !ok { - regex = make([]*regexp.Regexp, 1) - regex[0], _ = regexp.Compile(pattern) - e.compiledPatterns[pattern] = regex + e.patternMu.RUnlock() + if ok { + return regex[0].FindString(content), nil } - matches := regex[0].FindString(content) - return matches + // 未命中,需要编译(使用写锁) + e.patternMu.Lock() + defer e.patternMu.Unlock() + + // 双重检查 + regex, ok = e.compiledPatterns[pattern] + if ok { + return regex[0].FindString(content), nil + } + + var err error + regex = make([]*regexp.Regexp, 1) + regex[0], err = regexp.Compile(pattern) + if err != nil { + return "", fmt.Errorf("invalid regex pattern '%s': %w", pattern, err) + } + e.compiledPatterns[pattern] = regex + + return regex[0].FindString(content), nil } // MatchFromConfig 从规则配置执行匹配 diff --git a/gateway/internal/compliance/rules/engine_test.go b/gateway/internal/compliance/rules/engine_test.go new file mode 100644 index 0000000..2425b9a --- /dev/null +++ b/gateway/internal/compliance/rules/engine_test.go @@ -0,0 +1,111 @@ +package rules + +import ( + "sync" + "testing" +) + +// ==================== P0-05 测试: regexp编译错误被静默忽略 ==================== + +// TestExtractMatch_InvalidRegex_P0_05 测试无效正则表达式被静默忽略的问题 +// 问题: extractMatch在regexp.Compile失败时会panic,因为错误被丢弃 +func TestExtractMatch_InvalidRegex_P0_05(t *testing.T) { + loader := NewRuleLoader() + engine := NewRuleEngine(loader) + + // 使用无效的正则表达式 - 这会导致panic因为错误被忽略 + invalidPattern := "[invalid" // 无效的正则表达式,缺少闭合括号 + + // 捕获panic来验证问题存在 + defer func() { + if r := recover(); r != nil { + t.Errorf("P0-05 问题确认: extractMatch对无效正则发生了panic: %v", r) + t.Log("问题: regexp.Compile错误被丢弃,导致后续操作panic") + } + }() + + // 如果没有panic,说明问题已修复 + result, err := engine.extractMatch(invalidPattern, "test content") + if err != nil { + t.Logf("P0-05 问题已修复: extractMatch正确返回错误: %v, result=%q", err, result) + } else { + t.Errorf("P0-05 未修复: extractMatch应返回错误但没有返回") + } +} + +// ==================== P0-06 测试: compiledPatterns非线程安全 ==================== + +// TestRuleEngine_ConcurrentAccess_P0_06 测试并发访问时的数据竞争 +// 使用race detector检测数据竞争 +func TestRuleEngine_ConcurrentAccess_P0_06(t *testing.T) { + loader := NewRuleLoader() + engine := NewRuleEngine(loader) + + pattern := "test" + content := "this is a test content" + + var wg sync.WaitGroup + numGoroutines := 100 + + // 并发调用matchRegex + for i := 0; i < numGoroutines; i++ { + wg.Add(1) + go func() { + defer wg.Done() + _ = engine.matchRegex(pattern, content) + }() + } + + // 同时并发调用extractMatch + for i := 0; i < numGoroutines; i++ { + wg.Add(1) + go func() { + defer wg.Done() + _, _ = engine.extractMatch(pattern, content) + }() + } + + // 同时并发调用Match + rule := Rule{ + ID: "test-rule", + Matchers: []Matcher{ + {Type: "regex_match", Pattern: pattern}, + }, + } + + for i := 0; i < numGoroutines; i++ { + wg.Add(1) + go func() { + defer wg.Done() + _ = engine.Match(rule, content) + }() + } + + wg.Wait() + t.Log("P0-06 验证: 并发测试完成") +} + +// TestRuleEngine_ConcurrentMapAccess_P0_06 测试map并发读写问题 +func TestRuleEngine_ConcurrentMapAccess_P0_06(t *testing.T) { + loader := NewRuleLoader() + engine := NewRuleEngine(loader) + + patterns := []string{"test1", "test2", "test3", "test4", "test5"} + content := "test1 test2 test3 test4 test5" + + var wg sync.WaitGroup + for _, pattern := range patterns { + p := pattern + wg.Add(1) + go func() { + defer wg.Done() + for i := 0; i < 50; i++ { + _ = engine.matchRegex(p, content) + _, _ = engine.extractMatch(p, content) + } + }() + } + + wg.Wait() + t.Log("P0-06 验证: 并发读写测试完成") +}