Skip to content

Commit

Permalink
feat: simple sandbox by extra scope
Browse files Browse the repository at this point in the history
  • Loading branch information
qjpcpu committed Jul 23, 2022
1 parent 82e342e commit 4a607b0
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 34 deletions.
98 changes: 69 additions & 29 deletions environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,21 @@ type PreHook func(*Environment, string, []Sexp)
type PostHook func(*Environment, string, Sexp)

type Environment struct {
datastack *Stack
scopestack *Stack
addrstack *Stack
stackstack *Stack
symtable map[string]int
revsymtable map[int]string
builtins map[int]SexpFunction
macros map[int]SexpFunction
curfunc SexpFunction
mainfunc SexpFunction
pc int
nextsymbol int
before []PreHook
after []PostHook
datastack *Stack
scopestack *Stack
addrstack *Stack
stackstack *Stack
symtable map[string]int
revsymtable map[int]string
builtins map[int]SexpFunction
macros map[int]SexpFunction
curfunc SexpFunction
mainfunc SexpFunction
pc int
nextsymbol int
before []PreHook
after []PostHook
extraGlobolCount int
}

const CallStackSize = 25
Expand Down Expand Up @@ -79,7 +80,8 @@ func (env *Environment) Clone() *Environment {
dupenv.before = env.before
dupenv.after = env.after

dupenv.scopestack.Push(env.scopestack.elements[0])
dupenv.scopestack.PushMulti(env.globalScopes()...)
dupenv.extraGlobolCount = env.extraGlobolCount

dupenv.mainfunc = MakeFunction("__main", 0, false, make([]Instruction, 0))
dupenv.curfunc = dupenv.mainfunc
Expand All @@ -101,7 +103,8 @@ func (env *Environment) Duplicate() *Environment {
dupenv.before = env.before
dupenv.after = env.after

dupenv.scopestack.Push(env.scopestack.elements[0])
dupenv.scopestack.PushMulti(env.globalScopes()...)
dupenv.extraGlobolCount = env.extraGlobolCount

dupenv.mainfunc = MakeFunction("__main", 0, false, make([]Instruction, 0))
dupenv.curfunc = dupenv.mainfunc
Expand Down Expand Up @@ -174,10 +177,10 @@ func (env *Environment) CallFunction(function SexpFunction, nargs int) error {
if env.scopestack.IsEmpty() {
panic("where's the global scope?")
}
globalScope := env.scopestack.elements[0]
globalScopes := env.globalScopes()
env.stackstack.Push(env.scopestack)
env.scopestack = NewStack(ScopeStackSize)
env.scopestack.Push(globalScope)
env.scopestack.PushMulti(globalScopes...)

if function.closeScope != nil {
function.closeScope.PushAllTo(env.scopestack)
Expand All @@ -191,11 +194,38 @@ func (env *Environment) CallFunction(function SexpFunction, nargs int) error {
return nil
}

func (env *Environment) BindObject(name string, expr Sexp) error {
func (env *Environment) Bind(name string, expr Sexp) error {
sym := env.MakeSymbol(name)
return env.scopestack.BindSymbol(sym, expr)
}

func (env *Environment) PushGlobalScope() error {
if env.scopestack.Top() != env.extraGlobolCount {
return errors.New("not in global scope")
}
env.scopestack.PushScope()
env.extraGlobolCount++
return nil
}

func (env *Environment) PopGlobalScope() error {
if env.scopestack.Top() != env.extraGlobolCount {
return errors.New("not in global scope")
}
if env.extraGlobolCount <= 0 {
return errors.New("no extra global scope")
}
if err := env.scopestack.PopScope(); err != nil {
return err
}
env.extraGlobolCount--
return nil
}

func (env *Environment) globalScopes() []StackElem {
return env.scopestack.elements[0 : env.extraGlobolCount+1]
}

func (env *Environment) ReturnFromFunction() error {
for _, posthook := range env.after {
retval, err := env.datastack.GetExpr(0)
Expand Down Expand Up @@ -375,16 +405,11 @@ func (env *Environment) LoadString(str string) error {
}

func (env *Environment) AddFunction(name string, function UserFunction) {
env.AddGlobal(name, MakeUserFunction(name, function))
env.Bind(name, MakeUserFunction(name, function))
}

func (env *Environment) AddFunctionByConstructor(name string, function UserFunctionConstructor) {
env.AddGlobal(name, MakeUserFunction(name, function(name)))
}

func (env *Environment) AddGlobal(name string, obj Sexp) {
sym := env.MakeSymbol(name)
env.scopestack.elements[0].(Scope)[sym.number] = obj
env.Bind(name, MakeUserFunction(name, function(name)))
}

func (env *Environment) AddMacro(name string, function UserFunction) {
Expand Down Expand Up @@ -452,6 +477,7 @@ func (env *Environment) Clear() {
env.datastack.tos = -1
env.scopestack.tos = 0
env.addrstack.tos = -1
env.extraGlobolCount = 0
env.mainfunc = MakeFunction("__main", 0, false, make([]Instruction, 0))
env.curfunc = env.mainfunc
env.pc = 0
Expand All @@ -466,6 +492,18 @@ func (env *Environment) FindObject(name string) (Sexp, bool) {
return obj, true
}

func (env *Environment) ApplyByName(fun string, args []Sexp) (Sexp, error) {
f, ok := env.FindObject(fun)
if !ok {
return SexpNull, fmt.Errorf("function %s not found", fun)
}
fn, ok := f.(SexpFunction)
if !ok {
return SexpNull, fmt.Errorf("%s(%T) is not a function", fun, f)
}
return env.Apply(fn, args)
}

func (env *Environment) Apply(fun SexpFunction, args []Sexp) (Sexp, error) {
if fun.user {
return fun.userfun(env, args)
Expand Down Expand Up @@ -511,9 +549,11 @@ func (env *Environment) AddPostHook(fun PostHook) {

func (env *Environment) GlobalFunctions() []string {
var ret []string
for _, v := range env.scopestack.elements[0].(Scope) {
if fn, ok := v.(SexpFunction); ok {
ret = append(ret, fn.name)
for _, scope := range env.globalScopes() {
for _, v := range scope.(Scope) {
if fn, ok := v.(SexpFunction); ok {
ret = append(ret, fn.name)
}
}
}
for _, fn := range env.macros {
Expand Down
5 changes: 0 additions & 5 deletions scopes.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,6 @@ func (stack *Stack) LookupSymbol(sym SexpSymbol) (Sexp, error) {
return stack.lookupSymbol(sym, 0)
}

// LookupSymbolNonGlobal - closures use this to only find symbols below the global scope, to avoid copying globals it'll always be-able to ref
func (stack *Stack) LookupSymbolNonGlobal(sym SexpSymbol) (Sexp, error) {
return stack.lookupSymbol(sym, 1)
}

func (stack *Stack) BindSymbol(sym SexpSymbol, expr Sexp) error {
if stack.IsEmpty() {
return errors.New("no scope available")
Expand Down
6 changes: 6 additions & 0 deletions stack.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@ func (stack *Stack) IsEmpty() bool {
return stack.tos < 0
}

func (stack *Stack) PushMulti(elems ...StackElem) {
for i := range elems {
stack.Push(elems[i])
}
}

func (stack *Stack) Push(elem StackElem) {
stack.tos++

Expand Down
20 changes: 20 additions & 0 deletions tests/glisp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,3 +159,23 @@ func testReadFile(name string) glisp.UserFunction {
return glisp.SexpStr(string(bytes)), nil
}
}

func TestSandBox(t *testing.T) {
vm := loadAllExtensions(glisp.New())
vm.PushGlobalScope()
if _, err := vm.EvalString(`(defn sb [a b ] (+ a b))`); err != nil {
t.Fatal(err)
}
ret, err := vm.ApplyByName("sb", []glisp.Sexp{glisp.NewSexpInt(1), glisp.NewSexpInt(2)})
if err != nil {
t.Fatal(err)
}
if !glisp.IsInt(ret) {
t.Fatal(ret.SexpString() + " is not =3")
}
vm.PopGlobalScope()
_, err = vm.ApplyByName("sb", []glisp.Sexp{glisp.NewSexpInt(1), glisp.NewSexpInt(2)})
if err == nil {
t.Fatal("should not found function")
}
}

0 comments on commit 4a607b0

Please sign in to comment.