Skip to content

Commit a85f66d

Browse files
committed
Add a noexprlang build tag
This module uses `github.com/expr-lang/expr` which, because of the extensive use of the `reflect` package, disables go's compiler dead code elimination which can lead to bigger binaries. This commit adds a `noexprlang` build tag that allows jsm.go users that do not use expression matching to entirely disable the use of the expr module so that they can benefit from go's dead code elimination if they are eligible to outside jsm.go. References: - https://golab.io/talks/getting-the-most-out-of-dead-code-elimination - https://github.com/aarzilli/whydeadcode - spf13/cobra#1956 Signed-off-by: Sylvain Rabot <[email protected]>
1 parent 12d0253 commit a85f66d

File tree

6 files changed

+175
-118
lines changed

6 files changed

+175
-118
lines changed

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,3 +175,10 @@ This can be used by the `Manager` to validate all API access.
175175
```go
176176
mgr, _ := jsm.New(nc, jsm.WithAPIValidation(new(SchemaValidator)))
177177
```
178+
179+
## Build tag
180+
181+
This library provides a `noexprlang` build tag that disables expression matching
182+
for Streams and Consumers queries. The purpose of this build tag is to disable
183+
the use of the `github.com/expr-lang/expr` module that disables go compiler's dead
184+
code elimination because it uses some types and functions of the `reflect` package.

consumer_query.go

Lines changed: 0 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,7 @@ import (
1919
"strconv"
2020
"time"
2121

22-
"github.com/expr-lang/expr"
2322
"github.com/nats-io/jsm.go/api"
24-
"gopkg.in/yaml.v3"
2523
)
2624

2725
type consumerMatcher func([]*Consumer) ([]*Consumer, error)
@@ -59,14 +57,6 @@ func ConsumerQueryApiLevelMin(level int) ConsumerQueryOpt {
5957
}
6058
}
6159

62-
// ConsumerQueryExpression filters the consumers using the expr expression language
63-
func ConsumerQueryExpression(e string) ConsumerQueryOpt {
64-
return func(q *consumerQuery) error {
65-
q.expression = e
66-
return nil
67-
}
68-
}
69-
7060
// ConsumerQueryLeaderServer finds clustered consumers where a certain node is the leader
7161
func ConsumerQueryLeaderServer(server string) ConsumerQueryOpt {
7262
return func(q *consumerQuery) error {
@@ -355,50 +345,3 @@ func (q *consumerQuery) matchApiLevel(consumers []*Consumer) ([]*Consumer, error
355345
return (!q.invert && requiredLevel >= q.apiLevel) || (q.invert && requiredLevel < q.apiLevel)
356346
})
357347
}
358-
359-
func (q *consumerQuery) matchExpression(consumers []*Consumer) ([]*Consumer, error) {
360-
if q.expression == "" {
361-
return consumers, nil
362-
}
363-
364-
var matched []*Consumer
365-
366-
for _, consumer := range consumers {
367-
cfg := map[string]any{}
368-
state := map[string]any{}
369-
370-
cfgBytes, _ := yaml.Marshal(consumer.Configuration())
371-
yaml.Unmarshal(cfgBytes, &cfg)
372-
nfo, _ := consumer.LatestState()
373-
stateBytes, _ := yaml.Marshal(nfo)
374-
yaml.Unmarshal(stateBytes, &state)
375-
376-
env := map[string]any{
377-
"config": cfg,
378-
"state": state,
379-
"info": state,
380-
"Info": nfo,
381-
}
382-
383-
program, err := expr.Compile(q.expression, expr.Env(env), expr.AsBool())
384-
if err != nil {
385-
return nil, err
386-
}
387-
388-
out, err := expr.Run(program, env)
389-
if err != nil {
390-
return nil, err
391-
}
392-
393-
should, ok := out.(bool)
394-
if !ok {
395-
return nil, fmt.Errorf("expression did not return a boolean")
396-
}
397-
398-
if should {
399-
matched = append(matched, consumer)
400-
}
401-
}
402-
403-
return matched, nil
404-
}

jsm.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ import (
3131
"github.com/nats-io/jsm.go/api"
3232
)
3333

34+
// ErrNoExprLangBuild warns that expression matching is disabled when compiling
35+
// a go binary with the `noexprlang` build tag.
36+
var ErrNoExprLangBuild = fmt.Errorf("binary has been built with `noexprlang` build tag and thus does not support expression matching")
37+
3438
// standard api responses with error embedded
3539
type jetStreamResponseError interface {
3640
ToError() error

match.go

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
//go:build !noexprlang
2+
3+
package jsm
4+
5+
import (
6+
"fmt"
7+
8+
"github.com/expr-lang/expr"
9+
"gopkg.in/yaml.v3"
10+
)
11+
12+
// StreamQueryExpression filters the stream using the expr expression language
13+
// Using this option with a binary built with the `noexprlang` build tag will
14+
// always return [ErrNoExprLangBuild].
15+
func StreamQueryExpression(e string) StreamQueryOpt {
16+
return func(q *streamQuery) error {
17+
q.expression = e
18+
return nil
19+
}
20+
}
21+
22+
func (q *streamQuery) matchExpression(streams []*Stream) ([]*Stream, error) {
23+
if q.expression == "" {
24+
return streams, nil
25+
}
26+
27+
var matched []*Stream
28+
29+
for _, stream := range streams {
30+
cfg := map[string]any{}
31+
state := map[string]any{}
32+
info := map[string]any{}
33+
34+
cfgBytes, _ := yaml.Marshal(stream.Configuration())
35+
yaml.Unmarshal(cfgBytes, &cfg)
36+
nfo, _ := stream.LatestInformation()
37+
nfoBytes, _ := yaml.Marshal(nfo)
38+
yaml.Unmarshal(nfoBytes, &info)
39+
stateBytes, _ := yaml.Marshal(nfo.State)
40+
yaml.Unmarshal(stateBytes, &state)
41+
42+
env := map[string]any{
43+
"config": cfg,
44+
"state": state,
45+
"info": info,
46+
"Info": nfo,
47+
}
48+
49+
program, err := expr.Compile(q.expression, expr.Env(env), expr.AsBool())
50+
if err != nil {
51+
return nil, err
52+
}
53+
54+
out, err := expr.Run(program, env)
55+
if err != nil {
56+
return nil, err
57+
}
58+
59+
should, ok := out.(bool)
60+
if !ok {
61+
return nil, fmt.Errorf("expression did not return a boolean")
62+
}
63+
64+
if should {
65+
matched = append(matched, stream)
66+
}
67+
}
68+
69+
return matched, nil
70+
}
71+
72+
// ConsumerQueryExpression filters the consumers using the expr expression language
73+
// Using this option with a binary built with the `noexprlang` build tag will
74+
// always return [ErrNoExprLangBuild].
75+
func ConsumerQueryExpression(e string) ConsumerQueryOpt {
76+
return func(q *consumerQuery) error {
77+
q.expression = e
78+
return nil
79+
}
80+
}
81+
82+
func (q *consumerQuery) matchExpression(consumers []*Consumer) ([]*Consumer, error) {
83+
if q.expression == "" {
84+
return consumers, nil
85+
}
86+
87+
var matched []*Consumer
88+
89+
for _, consumer := range consumers {
90+
cfg := map[string]any{}
91+
state := map[string]any{}
92+
93+
cfgBytes, _ := yaml.Marshal(consumer.Configuration())
94+
yaml.Unmarshal(cfgBytes, &cfg)
95+
nfo, _ := consumer.LatestState()
96+
stateBytes, _ := yaml.Marshal(nfo)
97+
yaml.Unmarshal(stateBytes, &state)
98+
99+
env := map[string]any{
100+
"config": cfg,
101+
"state": state,
102+
"info": state,
103+
"Info": nfo,
104+
}
105+
106+
program, err := expr.Compile(q.expression, expr.Env(env), expr.AsBool())
107+
if err != nil {
108+
return nil, err
109+
}
110+
111+
out, err := expr.Run(program, env)
112+
if err != nil {
113+
return nil, err
114+
}
115+
116+
should, ok := out.(bool)
117+
if !ok {
118+
return nil, fmt.Errorf("expression did not return a boolean")
119+
}
120+
121+
if should {
122+
matched = append(matched, consumer)
123+
}
124+
}
125+
126+
return matched, nil
127+
}

match_noexpr.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
//go:build noexprlang
2+
3+
package jsm
4+
5+
// StreamQueryExpression filters the stream using the expr expression language
6+
// Using this option with a binary built with the `noexprlang` build tag will
7+
// always return [ErrNoExprLangBuild].
8+
func StreamQueryExpression(e string) StreamQueryOpt {
9+
return func(q *streamQuery) error {
10+
q.expression = e
11+
return ErrNoExprLangBuild
12+
}
13+
}
14+
15+
func (q *streamQuery) matchExpression(streams []*Stream) ([]*Stream, error) {
16+
if q.expression == "" {
17+
return streams, nil
18+
}
19+
return nil, ErrNoExprLangBuild
20+
}
21+
22+
// ConsumerQueryExpression filters the consumers using the expr expression language
23+
// Using this option with a binary built with the `noexprlang` build tag will
24+
// always return [ErrNoExprLangBuild].
25+
func ConsumerQueryExpression(e string) ConsumerQueryOpt {
26+
return func(q *consumerQuery) error {
27+
q.expression = e
28+
return ErrNoExprLangBuild
29+
}
30+
}
31+
32+
func (q *consumerQuery) matchExpression(consumers []*Consumer) ([]*Consumer, error) {
33+
if q.expression == "" {
34+
return consumers, nil
35+
}
36+
return nil, ErrNoExprLangBuild
37+
}

stream_query.go

Lines changed: 0 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,12 @@
1414
package jsm
1515

1616
import (
17-
"fmt"
1817
"regexp"
1918
"strconv"
2019
"strings"
2120
"time"
2221

23-
"github.com/expr-lang/expr"
2422
"github.com/nats-io/jsm.go/api"
25-
"gopkg.in/yaml.v3"
2623
)
2724

2825
type streamMatcher func([]*Stream) ([]*Stream, error)
@@ -57,14 +54,6 @@ func StreamQueryApiLevelMin(level int) StreamQueryOpt {
5754
}
5855
}
5956

60-
// StreamQueryExpression filters the stream using the expr expression language
61-
func StreamQueryExpression(e string) StreamQueryOpt {
62-
return func(q *streamQuery) error {
63-
q.expression = e
64-
return nil
65-
}
66-
}
67-
6857
func StreamQueryIsSourced() StreamQueryOpt {
6958
return func(q *streamQuery) error {
7059
q.sourced = true
@@ -231,56 +220,6 @@ func (q *streamQuery) Filter(streams []*Stream) ([]*Stream, error) {
231220
return matched, nil
232221
}
233222

234-
func (q *streamQuery) matchExpression(streams []*Stream) ([]*Stream, error) {
235-
if q.expression == "" {
236-
return streams, nil
237-
}
238-
239-
var matched []*Stream
240-
241-
for _, stream := range streams {
242-
cfg := map[string]any{}
243-
state := map[string]any{}
244-
info := map[string]any{}
245-
246-
cfgBytes, _ := yaml.Marshal(stream.Configuration())
247-
yaml.Unmarshal(cfgBytes, &cfg)
248-
nfo, _ := stream.LatestInformation()
249-
nfoBytes, _ := yaml.Marshal(nfo)
250-
yaml.Unmarshal(nfoBytes, &info)
251-
stateBytes, _ := yaml.Marshal(nfo.State)
252-
yaml.Unmarshal(stateBytes, &state)
253-
254-
env := map[string]any{
255-
"config": cfg,
256-
"state": state,
257-
"info": info,
258-
"Info": nfo,
259-
}
260-
261-
program, err := expr.Compile(q.expression, expr.Env(env), expr.AsBool())
262-
if err != nil {
263-
return nil, err
264-
}
265-
266-
out, err := expr.Run(program, env)
267-
if err != nil {
268-
return nil, err
269-
}
270-
271-
should, ok := out.(bool)
272-
if !ok {
273-
return nil, fmt.Errorf("expression did not return a boolean")
274-
}
275-
276-
if should {
277-
matched = append(matched, stream)
278-
}
279-
}
280-
281-
return matched, nil
282-
}
283-
284223
func (q *streamQuery) matchLeaderServer(streams []*Stream) ([]*Stream, error) {
285224
if q.leader == "" {
286225
return streams, nil

0 commit comments

Comments
 (0)