fix(gateway): 修复RuleEngine中regexp编译错误和并发安全问题
P0-05: regexp.Compile错误被静默忽略 - extractMatch函数现在返回(string, error) - 正确处理regexp.Compile错误,返回格式化错误信息 - 修复无效正则导致的panic问题 P0-06: compiledPatterns非线程安全 - 添加sync.RWMutex保护map并发访问 - matchRegex和extractMatch使用读锁/写锁保护 - 实现双重检查锁定模式优化性能 测试验证: - 使用-race flag验证无数据竞争 - 并发100个goroutine测试通过
This commit is contained in:
@@ -1,7 +1,9 @@
|
|||||||
package rules
|
package rules
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MatchResult 匹配结果
|
// MatchResult 匹配结果
|
||||||
@@ -22,8 +24,9 @@ type MatcherResult struct {
|
|||||||
|
|
||||||
// RuleEngine 规则引擎
|
// RuleEngine 规则引擎
|
||||||
type RuleEngine struct {
|
type RuleEngine struct {
|
||||||
loader *RuleLoader
|
loader *RuleLoader
|
||||||
compiledPatterns map[string][]*regexp.Regexp
|
compiledPatterns map[string][]*regexp.Regexp
|
||||||
|
patternMu sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRuleEngine 创建新的规则引擎
|
// NewRuleEngine 创建新的规则引擎
|
||||||
@@ -54,7 +57,7 @@ func (e *RuleEngine) Match(rule Rule, content string) MatchResult {
|
|||||||
case "regex_match":
|
case "regex_match":
|
||||||
matcherResult.IsMatch = e.matchRegex(matcher.Pattern, content)
|
matcherResult.IsMatch = e.matchRegex(matcher.Pattern, content)
|
||||||
if matcherResult.IsMatch {
|
if matcherResult.IsMatch {
|
||||||
matcherResult.MatchValue = e.extractMatch(matcher.Pattern, content)
|
matcherResult.MatchValue, _ = e.extractMatch(matcher.Pattern, content)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
// 未知匹配器类型,默认不匹配
|
// 未知匹配器类型,默认不匹配
|
||||||
@@ -71,32 +74,64 @@ func (e *RuleEngine) Match(rule Rule, content string) MatchResult {
|
|||||||
|
|
||||||
// matchRegex 执行正则表达式匹配
|
// matchRegex 执行正则表达式匹配
|
||||||
func (e *RuleEngine) matchRegex(pattern string, content string) bool {
|
func (e *RuleEngine) matchRegex(pattern string, content string) bool {
|
||||||
// 编译并缓存正则表达式
|
// 先尝试读取缓存(使用读锁)
|
||||||
|
e.patternMu.RLock()
|
||||||
regex, ok := e.compiledPatterns[pattern]
|
regex, ok := e.compiledPatterns[pattern]
|
||||||
if !ok {
|
e.patternMu.RUnlock()
|
||||||
var err error
|
if ok {
|
||||||
regex = make([]*regexp.Regexp, 1)
|
return regex[0].MatchString(content)
|
||||||
regex[0], err = regexp.Compile(pattern)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
e.compiledPatterns[pattern] = regex
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 未命中,需要编译(使用写锁)
|
||||||
|
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)
|
return regex[0].MatchString(content)
|
||||||
}
|
}
|
||||||
|
|
||||||
// extractMatch 提取匹配值
|
// 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]
|
regex, ok := e.compiledPatterns[pattern]
|
||||||
if !ok {
|
e.patternMu.RUnlock()
|
||||||
regex = make([]*regexp.Regexp, 1)
|
if ok {
|
||||||
regex[0], _ = regexp.Compile(pattern)
|
return regex[0].FindString(content), nil
|
||||||
e.compiledPatterns[pattern] = regex
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 从规则配置执行匹配
|
// MatchFromConfig 从规则配置执行匹配
|
||||||
|
|||||||
111
gateway/internal/compliance/rules/engine_test.go
Normal file
111
gateway/internal/compliance/rules/engine_test.go
Normal file
@@ -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 验证: 并发读写测试完成")
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user