commit
1315489a4a
3 changed files with 134 additions and 14 deletions
|
@ -4,9 +4,9 @@
|
||||||
|
|
||||||
- [Using Scripts](#using-scripts)
|
- [Using Scripts](#using-scripts)
|
||||||
- [Type Conversion Table](#type-conversion-table)
|
- [Type Conversion Table](#type-conversion-table)
|
||||||
- [Compiler and VM](#compiler-and-vm)
|
|
||||||
- [User Types](#user-types)
|
- [User Types](#user-types)
|
||||||
- [Sandbox Environments](#sandbox-environments)
|
- [Sandbox Environments](#sandbox-environments)
|
||||||
|
- [Compiler and VM](#compiler-and-vm)
|
||||||
|
|
||||||
## Using Scripts
|
## Using Scripts
|
||||||
|
|
||||||
|
@ -102,15 +102,52 @@ When adding a Variable _([Script.Add](https://godoc.org/github.com/d5/tengo/scri
|
||||||
|
|
||||||
One can easily add and use customized value types in Tengo code by implementing [Object](https://godoc.org/github.com/d5/tengo/objects#Object) interface. Tengo runtime will treat the user types exactly in the same way it does to the runtime types with no performance overhead. See [Tengo Objects](https://github.com/d5/tengo/blob/master/docs/objects.md) for more details.
|
One can easily add and use customized value types in Tengo code by implementing [Object](https://godoc.org/github.com/d5/tengo/objects#Object) interface. Tengo runtime will treat the user types exactly in the same way it does to the runtime types with no performance overhead. See [Tengo Objects](https://github.com/d5/tengo/blob/master/docs/objects.md) for more details.
|
||||||
|
|
||||||
|
## Sandbox Environments
|
||||||
|
|
||||||
|
To securely compile and execute _potentially_ unsafe script code, you can use the following Script functions.
|
||||||
|
|
||||||
|
#### Script.DisableBuiltinFunction(name string)
|
||||||
|
|
||||||
|
DisableBuiltinFunction disables and removes a builtin function from the compiler. Compiler will reports a compile-time error if the given name is referenced.
|
||||||
|
|
||||||
|
```golang
|
||||||
|
s := script.New([]byte(`print([1, 2, 3])`))
|
||||||
|
|
||||||
|
s.DisableBuiltinFunction("print")
|
||||||
|
|
||||||
|
_, err := s.Run() // compile error
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Script.DisableStdModule(name string)
|
||||||
|
|
||||||
|
DisableStdModule disables a [standard library](https://github.com/d5/tengo/blob/master/docs/stdlib.md) module. Compile will report a compile-time error if the code tries to import the module with the given name.
|
||||||
|
|
||||||
|
```golang
|
||||||
|
s := script.New([]byte(`import("exec")`))
|
||||||
|
|
||||||
|
s.DisableStdModule("exec")
|
||||||
|
|
||||||
|
_, err := s.Run() // compile error
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Script.SetUserModuleLoader(loader compiler.ModuleLoader)
|
||||||
|
|
||||||
|
SetUserModuleLoader replaces the default user-module loader of the compiler, which tries to read the source from a local file.
|
||||||
|
|
||||||
|
```golang
|
||||||
|
s := script.New([]byte(`math := import("mod1"); a := math.foo()`))
|
||||||
|
|
||||||
|
s.SetUserModuleLoader(func(moduleName string) ([]byte, error) {
|
||||||
|
if moduleName == "mod1" {
|
||||||
|
return []byte(`foo := func() { return 5 }`), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errors.New("module not found")
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
## Compiler and VM
|
## Compiler and VM
|
||||||
|
|
||||||
Although it's not recommended, you can directly create and run the Tengo [Parser](https://godoc.org/github.com/d5/tengo/compiler/parser#Parser), [Compiler](https://godoc.org/github.com/d5/tengo/compiler#Compiler), and [VM](https://godoc.org/github.com/d5/tengo/runtime#VM) for yourself instead of using Scripts and Script Variables. It's a bit more involved as you have to manage the symbol tables and global variables between them, but, basically that's what Script and Script Variable is doing internally.
|
Although it's not recommended, you can directly create and run the Tengo [Parser](https://godoc.org/github.com/d5/tengo/compiler/parser#Parser), [Compiler](https://godoc.org/github.com/d5/tengo/compiler#Compiler), and [VM](https://godoc.org/github.com/d5/tengo/runtime#VM) for yourself instead of using Scripts and Script Variables. It's a bit more involved as you have to manage the symbol tables and global variables between them, but, basically that's what Script and Script Variable is doing internally.
|
||||||
|
|
||||||
_TODO: add more information here_
|
_TODO: add more information here_
|
||||||
|
|
||||||
## Sandbox Environments
|
|
||||||
|
|
||||||
In an environment where a _(potentially)_ unsafe script code needs to be executed,
|
|
||||||
|
|
||||||
_TODO: add more information here_
|
|
||||||
|
|
||||||
|
|
|
@ -7,14 +7,18 @@ import (
|
||||||
"github.com/d5/tengo/compiler"
|
"github.com/d5/tengo/compiler"
|
||||||
"github.com/d5/tengo/compiler/parser"
|
"github.com/d5/tengo/compiler/parser"
|
||||||
"github.com/d5/tengo/compiler/source"
|
"github.com/d5/tengo/compiler/source"
|
||||||
|
"github.com/d5/tengo/compiler/stdlib"
|
||||||
"github.com/d5/tengo/objects"
|
"github.com/d5/tengo/objects"
|
||||||
"github.com/d5/tengo/runtime"
|
"github.com/d5/tengo/runtime"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Script can simplify compilation and execution of embedded scripts.
|
// Script can simplify compilation and execution of embedded scripts.
|
||||||
type Script struct {
|
type Script struct {
|
||||||
variables map[string]*Variable
|
variables map[string]*Variable
|
||||||
input []byte
|
removedBuiltins map[string]bool
|
||||||
|
removedStdModules map[string]bool
|
||||||
|
userModuleLoader compiler.ModuleLoader
|
||||||
|
input []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a Script instance with an input script.
|
// New creates a Script instance with an input script.
|
||||||
|
@ -52,9 +56,32 @@ func (s *Script) Remove(name string) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DisableBuiltinFunction disables a builtin function.
|
||||||
|
func (s *Script) DisableBuiltinFunction(name string) {
|
||||||
|
if s.removedBuiltins == nil {
|
||||||
|
s.removedBuiltins = make(map[string]bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.removedBuiltins[name] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// DisableStdModule disables a standard library module.
|
||||||
|
func (s *Script) DisableStdModule(name string) {
|
||||||
|
if s.removedStdModules == nil {
|
||||||
|
s.removedStdModules = make(map[string]bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.removedStdModules[name] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetUserModuleLoader sets the user module loader for the compiler.
|
||||||
|
func (s *Script) SetUserModuleLoader(loader compiler.ModuleLoader) {
|
||||||
|
s.userModuleLoader = loader
|
||||||
|
}
|
||||||
|
|
||||||
// Compile compiles the script with all the defined variables, and, returns Compiled object.
|
// Compile compiles the script with all the defined variables, and, returns Compiled object.
|
||||||
func (s *Script) Compile() (*Compiled, error) {
|
func (s *Script) Compile() (*Compiled, error) {
|
||||||
symbolTable, globals := s.prepCompile()
|
symbolTable, stdModules, globals := s.prepCompile()
|
||||||
|
|
||||||
fileSet := source.NewFileSet()
|
fileSet := source.NewFileSet()
|
||||||
|
|
||||||
|
@ -64,7 +91,12 @@ func (s *Script) Compile() (*Compiled, error) {
|
||||||
return nil, fmt.Errorf("parse error: %s", err.Error())
|
return nil, fmt.Errorf("parse error: %s", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
c := compiler.NewCompiler(symbolTable, nil, nil)
|
c := compiler.NewCompiler(symbolTable, stdModules, nil)
|
||||||
|
|
||||||
|
if s.userModuleLoader != nil {
|
||||||
|
c.SetModuleLoader(s.userModuleLoader)
|
||||||
|
}
|
||||||
|
|
||||||
if err := c.Compile(file); err != nil {
|
if err := c.Compile(file); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -100,7 +132,7 @@ func (s *Script) RunContext(ctx context.Context) (compiled *Compiled, err error)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Script) prepCompile() (symbolTable *compiler.SymbolTable, globals []*objects.Object) {
|
func (s *Script) prepCompile() (symbolTable *compiler.SymbolTable, stdModules map[string]*objects.ImmutableMap, globals []*objects.Object) {
|
||||||
var names []string
|
var names []string
|
||||||
for name := range s.variables {
|
for name := range s.variables {
|
||||||
names = append(names, name)
|
names = append(names, name)
|
||||||
|
@ -108,7 +140,16 @@ func (s *Script) prepCompile() (symbolTable *compiler.SymbolTable, globals []*ob
|
||||||
|
|
||||||
symbolTable = compiler.NewSymbolTable()
|
symbolTable = compiler.NewSymbolTable()
|
||||||
for idx, fn := range objects.Builtins {
|
for idx, fn := range objects.Builtins {
|
||||||
symbolTable.DefineBuiltin(idx, fn.Name)
|
if !s.removedBuiltins[fn.Name] {
|
||||||
|
symbolTable.DefineBuiltin(idx, fn.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stdModules = make(map[string]*objects.ImmutableMap)
|
||||||
|
for name, mod := range stdlib.Modules {
|
||||||
|
if !s.removedStdModules[name] {
|
||||||
|
stdModules[name] = mod
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
globals = make([]*objects.Object, len(names), len(names))
|
globals = make([]*objects.Object, len(names), len(names))
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package script_test
|
package script_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/d5/tengo/assert"
|
"github.com/d5/tengo/assert"
|
||||||
|
@ -36,3 +37,44 @@ func TestScript_Run(t *testing.T) {
|
||||||
assert.NotNil(t, c)
|
assert.NotNil(t, c)
|
||||||
compiledGet(t, c, "a", int64(5))
|
compiledGet(t, c, "a", int64(5))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestScript_DisableBuiltinFunction(t *testing.T) {
|
||||||
|
s := script.New([]byte(`a := len([1, 2, 3])`))
|
||||||
|
c, err := s.Run()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, c)
|
||||||
|
compiledGet(t, c, "a", int64(3))
|
||||||
|
s.DisableBuiltinFunction("len")
|
||||||
|
_, err = s.Run()
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestScript_DisableStdModule(t *testing.T) {
|
||||||
|
s := script.New([]byte(`math := import("math"); a := math.abs(-19.84)`))
|
||||||
|
c, err := s.Run()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, c)
|
||||||
|
compiledGet(t, c, "a", 19.84)
|
||||||
|
s.DisableStdModule("math")
|
||||||
|
_, err = s.Run()
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestScript_SetUserModuleLoader(t *testing.T) {
|
||||||
|
s := script.New([]byte(`math := import("mod1"); a := math.foo()`))
|
||||||
|
_, err := s.Run()
|
||||||
|
assert.Error(t, err)
|
||||||
|
s.SetUserModuleLoader(func(moduleName string) (res []byte, err error) {
|
||||||
|
if moduleName == "mod1" {
|
||||||
|
res = []byte(`foo := func() { return 5 }`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = errors.New("module not found")
|
||||||
|
return
|
||||||
|
})
|
||||||
|
c, err := s.Run()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, c)
|
||||||
|
compiledGet(t, c, "a", int64(5))
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue