Skip to content

Commit 8a50360

Browse files
committed
feat: Use dlclark/regexp2 over standard library's package
Signed-off-by: Pranshu Srivastava <[email protected]>
1 parent 7f9b0d1 commit 8a50360

File tree

4 files changed

+123
-23
lines changed

4 files changed

+123
-23
lines changed

Diff for: go.mod

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ go 1.23.0
55
require (
66
github.com/KimMachineGun/automemlimit v0.7.0
77
github.com/dgryski/go-jump v0.0.0-20211018200510-ba001c3ffce0
8+
github.com/dlclark/regexp2 v1.11.5
89
github.com/fsnotify/fsnotify v1.8.0
910
github.com/go-logr/logr v1.4.2
1011
github.com/gobuffalo/flect v1.0.3

Diff for: go.sum

+2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1
1616
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
1717
github.com/dgryski/go-jump v0.0.0-20211018200510-ba001c3ffce0 h1:0wH6nO9QEa02Qx8sIQGw6ieKdz+BXjpccSOo9vXNl4U=
1818
github.com/dgryski/go-jump v0.0.0-20211018200510-ba001c3ffce0/go.mod h1:4hKCXuwrJoYvHZxJ86+bRVTOMyJ0Ej+RqfSm8mHi6KA=
19+
github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
20+
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
1921
github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
2022
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
2123
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=

Diff for: pkg/allowdenylist/allowdenylist.go

+38-11
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,23 @@ package allowdenylist
1818

1919
import (
2020
"errors"
21-
"regexp"
2221
"strings"
22+
"time"
23+
24+
regexp "github.com/dlclark/regexp2"
25+
"k8s.io/klog/v2"
2326

2427
generator "k8s.io/kube-state-metrics/v2/pkg/metric_generator"
2528
)
2629

27-
// AllowDenyList encapsulates the logic needed to filter based on a string.
30+
// Use ECMAScript as the default regexp spec to support lookarounds (#2594).
31+
var regexpDefaultSpec regexp.RegexOptions = regexp.ECMAScript
32+
33+
func init() {
34+
regexp.DefaultMatchTimeout = time.Minute
35+
}
36+
37+
// AllowDenyList namespaceencapsulates the logic needed to filter based on a string.
2838
type AllowDenyList struct {
2939
list map[string]struct{}
3040
rList []*regexp.Regexp
@@ -62,7 +72,7 @@ func New(allow, deny map[string]struct{}) (*AllowDenyList, error) {
6272
func (l *AllowDenyList) Parse() error {
6373
regexes := make([]*regexp.Regexp, 0, len(l.list))
6474
for item := range l.list {
65-
r, err := regexp.Compile(item)
75+
r, err := regexp.Compile(item, regexpDefaultSpec)
6676
if err != nil {
6777
return err
6878
}
@@ -99,25 +109,36 @@ func (l *AllowDenyList) Exclude(items []string) {
99109
}
100110

101111
// IsIncluded returns if the given item is included.
102-
func (l *AllowDenyList) IsIncluded(item string) bool {
103-
var matched bool
112+
func (l *AllowDenyList) IsIncluded(item string) (bool, error) {
113+
var (
114+
matched bool
115+
err error
116+
)
104117
for _, r := range l.rList {
105-
matched = r.MatchString(item)
118+
matched, err = r.MatchString(item)
119+
if err != nil {
120+
return false, err
121+
}
106122
if matched {
107123
break
108124
}
109125
}
110126

111127
if l.isAllowList {
112-
return matched
128+
return matched, nil
113129
}
114130

115-
return !matched
131+
return !matched, nil
116132
}
117133

118134
// IsExcluded returns if the given item is excluded.
119-
func (l *AllowDenyList) IsExcluded(item string) bool {
120-
return !l.IsIncluded(item)
135+
func (l *AllowDenyList) IsExcluded(item string) (bool, error) {
136+
isIncluded, err := l.IsIncluded(item)
137+
if err != nil {
138+
return false, err
139+
}
140+
141+
return !isIncluded, nil
121142
}
122143

123144
// Status returns the status of the AllowDenyList that can e.g. be passed into
@@ -137,7 +158,13 @@ func (l *AllowDenyList) Status() string {
137158

138159
// Test returns if the given family generator passes (is included in) the AllowDenyList
139160
func (l *AllowDenyList) Test(generator generator.FamilyGenerator) bool {
140-
return l.IsIncluded(generator.Name)
161+
isIncluded, err := l.IsIncluded(generator.Name)
162+
if err != nil {
163+
klog.ErrorS(err, "Error while processing allow-deny entries for generator", "generator", generator.Name)
164+
return false
165+
}
166+
167+
return isIncluded
141168
}
142169

143170
func copyList(l map[string]struct{}) map[string]struct{} {

Diff for: pkg/allowdenylist/allowdenylist_test.go

+82-12
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,12 @@ limitations under the License.
1717
package allowdenylist
1818

1919
import (
20-
"regexp"
20+
"fmt"
21+
"strings"
2122
"testing"
23+
"time"
24+
25+
regexp "github.com/dlclark/regexp2"
2226
)
2327

2428
func TestNew(t *testing.T) {
@@ -76,7 +80,11 @@ func TestInclude(t *testing.T) {
7680
t.Fatal("expected Parse() to not fail")
7781
}
7882

79-
if !allowlist.IsIncluded("item1") {
83+
isIncluded, err := allowlist.IsIncluded("item1")
84+
if err != nil {
85+
t.Fatal("expected IsIncluded() to not fail")
86+
}
87+
if !isIncluded {
8088
t.Fatal("expected included item to be included")
8189
}
8290
})
@@ -93,7 +101,11 @@ func TestInclude(t *testing.T) {
93101
t.Fatalf("expected Parse() to not fail, but got error : %v", err)
94102
}
95103

96-
if !denylist.IsIncluded(item1) {
104+
isIncluded, err := denylist.IsIncluded(item1)
105+
if err != nil {
106+
t.Fatal("expected IsIncluded() to not fail")
107+
}
108+
if !isIncluded {
97109
t.Fatal("expected included item to be included")
98110
}
99111
})
@@ -109,7 +121,11 @@ func TestInclude(t *testing.T) {
109121
t.Fatalf("expected Parse() to not fail, but got error : %v", err)
110122
}
111123

112-
if !allowlist.IsIncluded("kube_secret_info") {
124+
isIncluded, err := allowlist.IsIncluded("kube_secret_info")
125+
if err != nil {
126+
t.Fatal("expected IsIncluded() to not fail")
127+
}
128+
if !isIncluded {
113129
t.Fatal("expected included item to be included")
114130
}
115131
})
@@ -130,16 +146,32 @@ func TestInclude(t *testing.T) {
130146
t.Fatalf("expected Parse() to not fail, but got error : %v", err)
131147
}
132148

133-
if denylist.IsExcluded(item1) {
149+
isExcluded, err := denylist.IsExcluded(item1)
150+
if err != nil {
151+
t.Fatal("expected IsExcluded() to not fail")
152+
}
153+
if isExcluded {
134154
t.Fatalf("expected included %s to be included", item1)
135155
}
136-
if denylist.IsIncluded(item2) {
156+
isIncluded, err := denylist.IsIncluded(item2)
157+
if err != nil {
158+
t.Fatal("expected IsIncluded() to not fail")
159+
}
160+
if isIncluded {
137161
t.Fatalf("expected included %s to be excluded", item2)
138162
}
139-
if denylist.IsIncluded(item3) {
163+
isIncluded, err = denylist.IsIncluded(item3)
164+
if err != nil {
165+
t.Fatal("expected IsIncluded() to not fail")
166+
}
167+
if isIncluded {
140168
t.Fatalf("expected included %s to be excluded", item3)
141169
}
142-
if denylist.IsExcluded(item4) {
170+
isExcluded, err = denylist.IsExcluded(item4)
171+
if err != nil {
172+
t.Fatal("expected IsExcluded() to not fail")
173+
}
174+
if isExcluded {
143175
t.Fatalf("expected included %s to be included", item4)
144176
}
145177
})
@@ -159,7 +191,11 @@ func TestExclude(t *testing.T) {
159191
t.Fatalf("expected Parse() to not fail, but got error : %v", err)
160192
}
161193

162-
if allowlist.IsIncluded(item1) {
194+
isIncluded, err := allowlist.IsIncluded(item1)
195+
if err != nil {
196+
t.Fatal("expected IsIncluded() to not fail")
197+
}
198+
if isIncluded {
163199
t.Fatal("expected excluded item to be excluded")
164200
}
165201
})
@@ -176,7 +212,11 @@ func TestExclude(t *testing.T) {
176212
t.Fatalf("expected Parse() to not fail, but got error : %v", err)
177213
}
178214

179-
if denylist.IsIncluded(item1) {
215+
isIncluded, err := denylist.IsIncluded(item1)
216+
if err != nil {
217+
t.Fatal("expected IsIncluded() to not fail")
218+
}
219+
if isIncluded {
180220
t.Fatal("expected excluded item to be excluded")
181221
}
182222
})
@@ -224,7 +264,8 @@ func TestStatus(t *testing.T) {
224264
allowlist, _ := New(map[string]struct{}{item1: {}, item2: {}}, map[string]struct{}{})
225265
actualStatusString := allowlist.Status()
226266
expectedRegexPattern := `^Including the following lists that were on allowlist: (item1|item2), (item2|item1)$`
227-
matched, _ := regexp.MatchString(expectedRegexPattern, actualStatusString)
267+
re := regexp.MustCompile(expectedRegexPattern, regexpDefaultSpec)
268+
matched, _ := re.MatchString(actualStatusString)
228269
if !matched {
229270
t.Errorf("expected status %q but got %q", expectedRegexPattern, actualStatusString)
230271
}
@@ -244,9 +285,38 @@ func TestStatus(t *testing.T) {
244285
denylist, _ := New(map[string]struct{}{}, map[string]struct{}{item1: {}, item2: {}})
245286
actualStatusString := denylist.Status()
246287
expectedRegexPattern := `^Excluding the following lists that were on denylist: (item1|item2), (item2|item1)$`
247-
matched, _ := regexp.MatchString(expectedRegexPattern, actualStatusString)
288+
re := regexp.MustCompile(expectedRegexPattern, regexpDefaultSpec)
289+
matched, _ := re.MatchString(actualStatusString)
248290
if !matched {
249291
t.Errorf("expected status %q but got %q", expectedRegexPattern, actualStatusString)
250292
}
251293
})
252294
}
295+
296+
func TestCatastrophicBacktrackTimeout(t *testing.T) {
297+
r, err := regexp.Compile("(.+)*\\?", 0)
298+
if err != nil {
299+
t.Fatal(err)
300+
}
301+
var exp = "Lorem ipsum dolor sit amet, consectetur adipiscing elit"
302+
exp = strings.Repeat(exp, 2^10)
303+
304+
timeout := regexp.DefaultMatchTimeout
305+
t.Logf("regexp.DefaultMatchTimeout set to: %v", timeout)
306+
buffer := 500 * time.Millisecond
307+
t.Run(fmt.Sprint(timeout), func(t *testing.T) {
308+
r.MatchTimeout = timeout
309+
start := time.Now()
310+
_, err = r.FindStringMatch(exp)
311+
if err != nil && !strings.HasPrefix(err.Error(), "match timeout") {
312+
t.Fatal(err)
313+
}
314+
if err == nil {
315+
t.Fatal("expected catastrophic backtracking error")
316+
}
317+
elapsed := time.Since(start)
318+
if elapsed > timeout+buffer {
319+
t.Fatalf("timeout %v exceeded: %v", timeout, elapsed)
320+
}
321+
})
322+
}

0 commit comments

Comments
 (0)