Skip to content

Commit 5597289

Browse files
committed
Allow to disable builtins
1 parent ab22e90 commit 5597289

File tree

10 files changed

+141
-52
lines changed

10 files changed

+141
-52
lines changed

builtin/builtin.go

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,35 +12,28 @@ import (
1212
)
1313

1414
type Function struct {
15-
Name string
16-
Func func(args ...interface{}) (interface{}, error)
17-
Types []reflect.Type
18-
19-
builtin bool // true if function is builtin
15+
Name string
16+
Func func(args ...interface{}) (interface{}, error)
17+
Types []reflect.Type
2018
Builtin1 func(arg interface{}) interface{}
2119
Validate func(args []reflect.Type) (reflect.Type, error)
2220
}
2321

24-
func (f Function) Builtin() bool {
25-
return f.builtin
26-
}
27-
2822
var (
2923
Index map[string]int
3024
Names []string
3125
)
3226

3327
func init() {
3428
Index = make(map[string]int)
35-
Names = make([]string, len(Functions))
36-
for i, fn := range Functions {
37-
fn.builtin = true
29+
Names = make([]string, len(Builtins))
30+
for i, fn := range Builtins {
3831
Index[fn.Name] = i
3932
Names[i] = fn.Name
4033
}
4134
}
4235

43-
var Functions = []*Function{
36+
var Builtins = []*Function{
4437
{
4538
Name: "len",
4639
Builtin1: Len,

builtin/builtin_test.go

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"time"
88

99
"github.com/antonmedv/expr"
10+
"github.com/antonmedv/expr/builtin"
1011
"github.com/antonmedv/expr/checker"
1112
"github.com/antonmedv/expr/conf"
1213
"github.com/antonmedv/expr/parser"
@@ -218,3 +219,73 @@ func TestBuiltin_disallow_builtins_override(t *testing.T) {
218219
})
219220
})
220221
}
222+
223+
func TestBuiltin_DisableBuiltin(t *testing.T) {
224+
t.Run("via env", func(t *testing.T) {
225+
for _, name := range builtin.Names {
226+
t.Run(name, func(t *testing.T) {
227+
env := map[string]interface{}{
228+
name: func() int { return 42 },
229+
}
230+
program, err := expr.Compile(name+"()", expr.Env(env), expr.DisableBuiltin(name))
231+
require.NoError(t, err)
232+
233+
out, err := expr.Run(program, env)
234+
require.NoError(t, err)
235+
assert.Equal(t, 42, out)
236+
})
237+
}
238+
})
239+
t.Run("via expr.Function", func(t *testing.T) {
240+
for _, name := range builtin.Names {
241+
t.Run(name, func(t *testing.T) {
242+
fn := expr.Function(name,
243+
func(params ...interface{}) (interface{}, error) {
244+
return 42, nil
245+
},
246+
new(func() int),
247+
)
248+
program, err := expr.Compile(name+"()", fn, expr.DisableBuiltin(name))
249+
require.NoError(t, err)
250+
251+
out, err := expr.Run(program, nil)
252+
require.NoError(t, err)
253+
assert.Equal(t, 42, out)
254+
})
255+
}
256+
})
257+
}
258+
259+
func TestBuiltin_DisableAllBuiltins(t *testing.T) {
260+
_, err := expr.Compile(`len("foo")`, expr.Env(nil), expr.DisableAllBuiltins())
261+
require.Error(t, err)
262+
assert.Contains(t, err.Error(), "unknown name len")
263+
}
264+
265+
func TestBuiltin_EnableBuiltin(t *testing.T) {
266+
t.Run("via env", func(t *testing.T) {
267+
env := map[string]interface{}{
268+
"repeat": func() string { return "repeat" },
269+
}
270+
program, err := expr.Compile(`len(repeat())`, expr.Env(env), expr.DisableAllBuiltins(), expr.EnableBuiltin("len"))
271+
require.NoError(t, err)
272+
273+
out, err := expr.Run(program, env)
274+
require.NoError(t, err)
275+
assert.Equal(t, 6, out)
276+
})
277+
t.Run("via expr.Function", func(t *testing.T) {
278+
fn := expr.Function("repeat",
279+
func(params ...interface{}) (interface{}, error) {
280+
return "repeat", nil
281+
},
282+
new(func() string),
283+
)
284+
program, err := expr.Compile(`len(repeat())`, fn, expr.DisableAllBuiltins(), expr.EnableBuiltin("len"))
285+
require.NoError(t, err)
286+
287+
out, err := expr.Run(program, nil)
288+
require.NoError(t, err)
289+
assert.Equal(t, 6, out)
290+
})
291+
}

checker/checker.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,9 @@ func (v *visitor) IdentifierNode(node *ast.IdentifierNode) (reflect.Type, info)
138138
if node.Value == "$env" {
139139
return mapType, info{}
140140
}
141+
if fn, ok := v.config.Builtins[node.Value]; ok {
142+
return functionType, info{fn: fn}
143+
}
141144
if fn, ok := v.config.Functions[node.Value]; ok {
142145
return functionType, info{fn: fn}
143146
}
@@ -664,7 +667,7 @@ func (v *visitor) BuiltinNode(node *ast.BuiltinNode) (reflect.Type, info) {
664667
case "get":
665668
return v.checkBuiltinGet(node)
666669
}
667-
return v.checkFunction(builtin.Functions[id], node, node.Arguments)
670+
return v.checkFunction(builtin.Builtins[id], node, node.Arguments)
668671
}
669672

670673
return v.error(node, "unknown builtin %v", node.Name)

compiler/compiler.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -700,7 +700,7 @@ func (c *compiler) BuiltinNode(node *ast.BuiltinNode) {
700700
}
701701

702702
if id, ok := builtin.Index[node.Name]; ok {
703-
f := builtin.Functions[id]
703+
f := builtin.Builtins[id]
704704
for _, arg := range node.Arguments {
705705
c.compile(arg)
706706
}

conf/config.go

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -19,22 +19,26 @@ type Config struct {
1919
ExpectAny bool
2020
Optimize bool
2121
Strict bool
22+
Pipes bool
2223
ConstFns map[string]reflect.Value
2324
Visitors []ast.Visitor
2425
Functions map[string]*builtin.Function
25-
Pipes bool
26+
Builtins map[string]*builtin.Function
27+
Disabled map[string]bool // disabled builtins
2628
}
2729

2830
// CreateNew creates new config with default values.
2931
func CreateNew() *Config {
3032
c := &Config{
33+
Optimize: true,
3134
Operators: make(map[string][]string),
3235
ConstFns: make(map[string]reflect.Value),
3336
Functions: make(map[string]*builtin.Function),
34-
Optimize: true,
37+
Builtins: make(map[string]*builtin.Function),
38+
Disabled: make(map[string]bool),
3539
}
36-
for _, f := range builtin.Functions {
37-
c.Functions[f.Name] = f
40+
for _, f := range builtin.Builtins {
41+
c.Builtins[f.Name] = f
3842
}
3943
return c
4044
}
@@ -95,23 +99,19 @@ func (c *Config) Check() {
9599
}
96100
}
97101
}
98-
for name, t := range c.Types {
99-
if kind(t.Type) != reflect.Func {
100-
continue
101-
}
102-
for _, b := range builtin.Names {
103-
if b == name {
104-
panic(fmt.Errorf(`cannot override builtin %s(); it is already defined in expr`, name))
102+
for fnName, t := range c.Types {
103+
if kind(t.Type) == reflect.Func {
104+
for _, b := range c.Builtins {
105+
if b.Name == fnName {
106+
panic(fmt.Errorf(`cannot override builtin %s(): use expr.DisableBuiltin("%s") to override`, b.Name, b.Name))
107+
}
105108
}
106109
}
107110
}
108111
for _, f := range c.Functions {
109-
if f.Builtin() {
110-
continue
111-
}
112-
for _, b := range builtin.Names {
113-
if b == f.Name {
114-
panic(fmt.Errorf(`cannot override builtin %s(); it is already defined in expr`, f.Name))
112+
for _, b := range c.Builtins {
113+
if b.Name == f.Name {
114+
panic(fmt.Errorf(`cannot override builtin %s(); use expr.DisableBuiltin("%s") to override`, f.Name, f.Name))
115115
}
116116
}
117117
}

expr.go

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -137,13 +137,38 @@ func ExperimentalPipes() Option {
137137
}
138138
}
139139

140+
// DisableAllBuiltins disables all builtins.
141+
func DisableAllBuiltins() Option {
142+
return func(c *conf.Config) {
143+
for name := range c.Builtins {
144+
c.Disabled[name] = true
145+
}
146+
}
147+
}
148+
149+
// DisableBuiltin disables builtin function.
150+
func DisableBuiltin(name string) Option {
151+
return func(c *conf.Config) {
152+
c.Disabled[name] = true
153+
}
154+
}
155+
156+
// EnableBuiltin enables builtin function.
157+
func EnableBuiltin(name string) Option {
158+
return func(c *conf.Config) {
159+
delete(c.Disabled, name)
160+
}
161+
}
162+
140163
// Compile parses and compiles given input expression to bytecode program.
141164
func Compile(input string, ops ...Option) (*vm.Program, error) {
142165
config := conf.CreateNew()
143-
144166
for _, op := range ops {
145167
op(config)
146168
}
169+
for name := range config.Disabled {
170+
delete(config.Builtins, name)
171+
}
147172
config.Check()
148173

149174
if len(config.Operators) > 0 {
@@ -153,11 +178,7 @@ func Compile(input string, ops ...Option) (*vm.Program, error) {
153178
})
154179
}
155180

156-
parseConfig := parser.Config{
157-
Pipes: config.Pipes,
158-
}
159-
160-
tree, err := parser.ParseWithConfig(input, parseConfig)
181+
tree, err := parser.ParseWithConfig(input, config)
161182
if err != nil {
162183
return nil, err
163184
}

parser/parser.go

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77

88
. "github.com/antonmedv/expr/ast"
99
"github.com/antonmedv/expr/builtin"
10+
"github.com/antonmedv/expr/conf"
1011
"github.com/antonmedv/expr/file"
1112
. "github.com/antonmedv/expr/parser/lexer"
1213
"github.com/antonmedv/expr/parser/operator"
@@ -31,23 +32,22 @@ type parser struct {
3132
pos int
3233
err *file.Error
3334
depth int // closure call depth
34-
pipes bool
35+
config *conf.Config
3536
}
3637

3738
type Tree struct {
3839
Node Node
3940
Source *file.Source
4041
}
4142

42-
type Config struct {
43-
Pipes bool
44-
}
45-
4643
func Parse(input string) (*Tree, error) {
47-
return ParseWithConfig(input, Config{})
44+
return ParseWithConfig(input, &conf.Config{
45+
Pipes: false,
46+
Disabled: map[string]bool{},
47+
})
4848
}
4949

50-
func ParseWithConfig(input string, config Config) (*Tree, error) {
50+
func ParseWithConfig(input string, config *conf.Config) (*Tree, error) {
5151
source := file.NewSource(input)
5252

5353
tokens, err := Lex(source)
@@ -58,7 +58,7 @@ func ParseWithConfig(input string, config Config) (*Tree, error) {
5858
p := &parser{
5959
tokens: tokens,
6060
current: tokens[0],
61-
pipes: config.Pipes,
61+
config: config,
6262
}
6363

6464
node := p.parseExpression(0)
@@ -340,7 +340,7 @@ func (p *parser) parseCall(token Token) Node {
340340
Arguments: arguments,
341341
}
342342
node.SetLocation(token.Location)
343-
} else if _, ok := builtin.Index[token.Value]; ok {
343+
} else if _, ok := builtin.Index[token.Value]; ok && !p.config.Disabled[token.Value] {
344344
node = &BuiltinNode{
345345
Name: token.Value,
346346
Arguments: p.parseArguments(),
@@ -556,7 +556,7 @@ func (p *parser) parsePostfixExpression(node Node) Node {
556556
}
557557

558558
func (p *parser) parsePipe(node Node) Node {
559-
if !p.pipes {
559+
if !p.config.Pipes {
560560
p.error("enable Pipes via expr.ExperimentalPipes()")
561561
return &NilNode{}
562562
}

parser/parser_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"testing"
77

88
. "github.com/antonmedv/expr/ast"
9+
"github.com/antonmedv/expr/conf"
910
"github.com/antonmedv/expr/parser"
1011
"github.com/stretchr/testify/assert"
1112
"github.com/stretchr/testify/require"
@@ -453,7 +454,7 @@ func TestParse(t *testing.T) {
453454
&BoolNode{Value: true}}}},
454455
}
455456
for _, test := range parseTests {
456-
actual, err := parser.ParseWithConfig(test.input, parser.Config{Pipes: true})
457+
actual, err := parser.ParseWithConfig(test.input, &conf.Config{Pipes: true})
457458
if err != nil {
458459
t.Errorf("%s:\n%v", test.input, err)
459460
continue
@@ -657,7 +658,7 @@ func TestParse_pipe_operator(t *testing.T) {
657658
Property: &StringNode{Value: "foo"},
658659
}}}}}}}}
659660

660-
actual, err := parser.ParseWithConfig(input, parser.Config{Pipes: true})
661+
actual, err := parser.ParseWithConfig(input, &conf.Config{Pipes: true})
661662
require.NoError(t, err)
662663
assert.Equal(t, Dump(expect), Dump(actual.Node))
663664
}

vm/program.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ func (program *Program) Opcodes(w io.Writer) {
7373
_, _ = fmt.Fprintf(w, "%v\t%v\t<%v>\t%v\n", pp, label, arg, c)
7474
}
7575
builtin := func(label string) {
76-
_, _ = fmt.Fprintf(w, "%v\t%v\t<%v>\t%v\n", pp, label, arg, builtin.Functions[arg].Name)
76+
_, _ = fmt.Fprintf(w, "%v\t%v\t<%v>\t%v\n", pp, label, arg, builtin.Builtins[arg].Name)
7777
}
7878
funcName := func(label string) {
7979
_, _ = fmt.Fprintf(w, "%v\t%v\t<%v>\t%v()\n", pp, label, arg, program.FuncNames[arg])

vm/vm.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -380,7 +380,7 @@ func (vm *VM) Run(program *Program, env interface{}) (_ interface{}, err error)
380380
vm.push(vm.call(vm.pop(), arg))
381381

382382
case OpCallBuiltin1:
383-
vm.push(builtin.Functions[arg].Builtin1(vm.pop()))
383+
vm.push(builtin.Builtins[arg].Builtin1(vm.pop()))
384384

385385
case OpArray:
386386
size := vm.pop().(int)

0 commit comments

Comments
 (0)