diff --git a/engine/builtin.go b/engine/builtin.go index f13bfae..418db50 100644 --- a/engine/builtin.go +++ b/engine/builtin.go @@ -41,7 +41,7 @@ func Call(vm *VM, goal Term, k Cont, env *Env) *Promise { return Error(InstantiationError(env)) default: fvs := env.freeVariables(g) - args, err := makeSlice[Term](len(fvs)) + args, err := makeSlice(len(fvs)) if err != nil { return Error(resourceError(resourceMemory, env)) } @@ -98,7 +98,7 @@ func callN(vm *VM, closure Term, additional []Term, k Cont, env *Env) *Promise { if err != nil { return Error(err) } - args, err := makeSlice[Term](int(pi.arity) + len(additional)) + args, err := makeSlice(int(pi.arity) + len(additional)) if err != nil { return Error(resourceError(resourceMemory, env)) } @@ -283,7 +283,7 @@ func Functor(vm *VM, t, name, arity Term, k Cont, env *Env) *Promise { return Error(typeError(validTypeAtom, name, env)) } - vs, err := makeSlice[Term](int(arity)) + vs, err := makeSlice(int(arity)) if err != nil { return Error(resourceError(resourceMemory, env)) } @@ -381,42 +381,62 @@ func Univ(vm *VM, t, list Term, k Cont, env *Env) *Promise { // CopyTerm clones in as out. func CopyTerm(vm *VM, in, out Term, k Cont, env *Env) *Promise { - return Unify(vm, renamedCopy(in, nil, env), out, k, env) + c, err := renamedCopy(in, nil, env) + if err != nil { + return Error(err) + } + return Unify(vm, c, out, k, env) } -func renamedCopy(t Term, copied map[termID]Term, env *Env) Term { +func renamedCopy(t Term, copied map[termID]Term, env *Env) (Term, error) { if copied == nil { copied = map[termID]Term{} } t = env.Resolve(t) if c, ok := copied[id(t)]; ok { - return c + return c, nil } switch t := t.(type) { case Variable: v := NewVariable() copied[id(t)] = v - return v + return v, nil case charList, codeList: - return t + return t, nil case list: - l := make(list, len(t)) + s, err := makeSlice(len(t)) + if err != nil { + return nil, resourceError(resourceMemory, env) + } + l := list(s) copied[id(t)] = l for i := range t { - l[i] = renamedCopy(t[i], copied, env) + c, err := renamedCopy(t[i], copied, env) + if err != nil { + return nil, err + } + l[i] = c } - return l + return l, nil case *partial: var p partial copied[id(t)] = &p - p.Compound = renamedCopy(t.Compound, copied, env).(Compound) - tail := renamedCopy(*t.tail, copied, env) + cp, err := renamedCopy(t.Compound, copied, env) + if err != nil { + return nil, err + } + p.Compound = cp.(Compound) + cp, err = renamedCopy(*t.tail, copied, env) + if err != nil { + return nil, err + } + tail := cp p.tail = &tail - return &p + return &p, nil case Compound: - args, err := makeSlice[Term](t.Arity()) + args, err := makeSlice(t.Arity()) if err != nil { - return resourceError(resourceMemory, env) + return nil, resourceError(resourceMemory, env) } c := compound{ functor: t.Functor(), @@ -424,11 +444,15 @@ func renamedCopy(t Term, copied map[termID]Term, env *Env) Term { } copied[id(t)] = &c for i := 0; i < t.Arity(); i++ { - c.args[i] = renamedCopy(t.Arg(i), copied, env) + cp, err := renamedCopy(t.Arg(i), copied, env) + if err != nil { + return nil, err + } + c.args[i] = cp } - return &c + return &c, nil default: - return t + return t, nil } } @@ -449,7 +473,7 @@ func TermVariables(vm *VM, term, vars Term, k Cont, env *Env) *Promise { } witness[t] = struct{}{} case Compound: - args, err := makeSlice[Term](t.Arity()) + args, err := makeSlice(t.Arity()) if err != nil { return Error(resourceError(resourceMemory, env)) } @@ -714,7 +738,7 @@ func SetOf(vm *VM, template, goal, instances Term, k Cont, env *Env) *Promise { func collectionOf(vm *VM, agg func([]Term, *Env) Term, template, goal, instances Term, k Cont, env *Env) *Promise { fvs := newFreeVariablesSet(goal, template, env) - w, err := makeSlice[Term](len(fvs)) + w, err := makeSlice(len(fvs)) if err != nil { return Error(resourceError(resourceMemory, env)) } @@ -780,7 +804,11 @@ func FindAll(vm *VM, template, goal, instances Term, k Cont, env *Env) *Promise return Delay(func(ctx context.Context) *Promise { var answers []Term if _, err := Call(vm, goal, func(env *Env) *Promise { - answers = append(answers, renamedCopy(template, nil, env)) + c, err := renamedCopy(template, nil, env) + if err != nil { + return Error(err) + } + answers = append(answers, c) return Bool(false) // ask for more solutions }, env).Force(ctx); err != nil { return Error(err) @@ -1898,7 +1926,11 @@ func Clause(vm *VM, head, body Term, k Cont, env *Env) *Promise { ks := make([]func(context.Context) *Promise, len(u.clauses)) for i, c := range u.clauses { - r := rulify(renamedCopy(c.raw, nil, env), env) + cp, err := renamedCopy(c.raw, nil, env) + if err != nil { + return Error(err) + } + r := rulify(cp, env) ks[i] = func(context.Context) *Promise { return Unify(vm, atomIf.Apply(head, body), r, k, env) } @@ -2824,7 +2856,7 @@ func Length(vm *VM, list, length Term, k Cont, env *Env) *Promise { } func lengthRundown(vm *VM, list Variable, n Integer, k Cont, env *Env) *Promise { - elems, err := makeSlice[Term](int(n)) + elems, err := makeSlice(int(n)) if err != nil { return Error(resourceError(resourceMemory, env)) } diff --git a/engine/builtin_test.go b/engine/builtin_test.go index 58a643f..c1cf2db 100644 --- a/engine/builtin_test.go +++ b/engine/builtin_test.go @@ -48,7 +48,7 @@ f(g([a, [b|X]])). {title: `not callable: disjunction`, goal: atomSemiColon.Apply(Integer(1), atomTrue), ok: false, err: typeError(validTypeCallable, atomSemiColon.Apply(Integer(1), atomTrue), nil)}, {title: `cover all`, goal: atomComma.Apply(atomCut, NewAtom("f").Apply(NewAtom("g").Apply(List(NewAtom("a"), PartialList(NewVariable(), NewAtom("b")))))), ok: true}, - {title: `out of memory`, goal: NewAtom("foo").Apply(NewVariable(), NewVariable()), err: resourceError(resourceMemory, nil), mem: 1}, + {title: `out of memory`, goal: NewAtom("foo").Apply(NewVariable(), NewVariable(), NewVariable(), NewVariable(), NewVariable(), NewVariable(), NewVariable(), NewVariable(), NewVariable()), err: resourceError(resourceMemory, nil), mem: 1}, } for _, tt := range tests { @@ -74,7 +74,7 @@ func TestCall1(t *testing.T) { {title: "ok", closure: NewAtom("p").Apply(NewAtom("a")), additional: [1]Term{NewAtom("b")}, ok: true}, {title: "closure is a variable", closure: NewVariable(), additional: [1]Term{NewAtom("b")}, err: InstantiationError(nil)}, {title: "closure is neither a variable nor a callable term", closure: Integer(3), additional: [1]Term{NewAtom("b")}, err: typeError(validTypeCallable, Integer(3), nil)}, - {title: "out of memory", closure: NewAtom("p").Apply(NewAtom("a")), additional: [1]Term{NewAtom("b")}, err: resourceError(resourceMemory, nil), mem: 1}, + {title: "out of memory", closure: NewAtom("p").Apply(NewAtom("a"), NewAtom("a"), NewAtom("a"), NewAtom("a"), NewAtom("a"), NewAtom("a"), NewAtom("a"), NewAtom("a")), additional: [1]Term{NewAtom("b")}, err: resourceError(resourceMemory, nil), mem: 1}, } for _, tt := range tests { @@ -105,7 +105,7 @@ func TestCall2(t *testing.T) { {title: "ok", closure: NewAtom("p").Apply(NewAtom("a")), additional: [2]Term{NewAtom("b"), NewAtom("c")}, ok: true}, {title: "closure is a variable", closure: NewVariable(), additional: [2]Term{NewAtom("b"), NewAtom("c")}, err: InstantiationError(nil)}, {title: "closure is neither a variable nor a callable term", closure: Integer(3), additional: [2]Term{NewAtom("b"), NewAtom("c")}, err: typeError(validTypeCallable, Integer(3), nil)}, - {title: "out of memory", closure: NewAtom("p").Apply(NewAtom("a")), additional: [2]Term{NewAtom("b"), NewAtom("c")}, err: resourceError(resourceMemory, nil), mem: 1}, + {title: "out of memory", closure: NewAtom("p").Apply(NewAtom("a"), NewAtom("a"), NewAtom("a"), NewAtom("a"), NewAtom("a"), NewAtom("a"), NewAtom("a"), NewAtom("a")), additional: [2]Term{NewAtom("b"), NewAtom("c")}, err: resourceError(resourceMemory, nil), mem: 1}, } for _, tt := range tests { @@ -136,7 +136,7 @@ func TestCall3(t *testing.T) { {title: "ok", closure: NewAtom("p").Apply(NewAtom("a")), additional: [3]Term{NewAtom("b"), NewAtom("c"), NewAtom("d")}, ok: true}, {title: "closure is a variable", closure: NewVariable(), additional: [3]Term{NewAtom("b"), NewAtom("c"), NewAtom("d")}, err: InstantiationError(nil)}, {title: "closure is neither a variable nor a callable term", closure: Integer(3), additional: [3]Term{NewAtom("b"), NewAtom("c"), NewAtom("d")}, err: typeError(validTypeCallable, Integer(3), nil)}, - {title: "out of memory", closure: NewAtom("p").Apply(NewAtom("a")), additional: [3]Term{NewAtom("b"), NewAtom("c"), NewAtom("d")}, err: resourceError(resourceMemory, nil), mem: 1}, + {title: "out of memory", closure: NewAtom("p").Apply(NewAtom("a"), NewAtom("a"), NewAtom("a"), NewAtom("a"), NewAtom("a"), NewAtom("a"), NewAtom("a"), NewAtom("a")), additional: [3]Term{NewAtom("b"), NewAtom("c"), NewAtom("d")}, err: resourceError(resourceMemory, nil), mem: 1}, } for _, tt := range tests { @@ -167,7 +167,7 @@ func TestCall4(t *testing.T) { {title: "ok", closure: NewAtom("p").Apply(NewAtom("a")), additional: [4]Term{NewAtom("b"), NewAtom("c"), NewAtom("d"), NewAtom("e")}, ok: true}, {title: "closure is a variable", closure: NewVariable(), additional: [4]Term{NewAtom("b"), NewAtom("c"), NewAtom("d"), NewAtom("e")}, err: InstantiationError(nil)}, {title: "closure is neither a variable nor a callable term", closure: Integer(3), additional: [4]Term{NewAtom("b"), NewAtom("c"), NewAtom("d"), NewAtom("e")}, err: typeError(validTypeCallable, Integer(3), nil)}, - {title: "out of memory", closure: NewAtom("p").Apply(NewAtom("a")), additional: [4]Term{NewAtom("b"), NewAtom("c"), NewAtom("d"), NewAtom("e")}, err: resourceError(resourceMemory, nil), mem: 1}, + {title: "out of memory", closure: NewAtom("p").Apply(NewAtom("a"), NewAtom("a"), NewAtom("a"), NewAtom("a"), NewAtom("a"), NewAtom("a"), NewAtom("a"), NewAtom("a")), additional: [4]Term{NewAtom("b"), NewAtom("c"), NewAtom("d"), NewAtom("e")}, err: resourceError(resourceMemory, nil), mem: 1}, } for _, tt := range tests { @@ -198,7 +198,7 @@ func TestCall5(t *testing.T) { {title: "ok", closure: NewAtom("p").Apply(NewAtom("a")), additional: [5]Term{NewAtom("b"), NewAtom("c"), NewAtom("d"), NewAtom("e"), NewAtom("f")}, ok: true}, {title: "closure is a variable", closure: NewVariable(), additional: [5]Term{NewAtom("b"), NewAtom("c"), NewAtom("d"), NewAtom("e"), NewAtom("f")}, err: InstantiationError(nil)}, {title: "closure is neither a variable nor a callable term", closure: Integer(3), additional: [5]Term{NewAtom("b"), NewAtom("c"), NewAtom("d"), NewAtom("e"), NewAtom("f")}, err: typeError(validTypeCallable, Integer(3), nil)}, - {title: "out of memory", closure: NewAtom("p").Apply(NewAtom("a")), additional: [5]Term{NewAtom("b"), NewAtom("c"), NewAtom("d"), NewAtom("e"), NewAtom("f")}, err: resourceError(resourceMemory, nil), mem: 1}, + {title: "out of memory", closure: NewAtom("p").Apply(NewAtom("a"), NewAtom("a"), NewAtom("a"), NewAtom("a"), NewAtom("a"), NewAtom("a"), NewAtom("a"), NewAtom("a")), additional: [5]Term{NewAtom("b"), NewAtom("c"), NewAtom("d"), NewAtom("e"), NewAtom("f")}, err: resourceError(resourceMemory, nil), mem: 1}, } for _, tt := range tests { @@ -229,7 +229,7 @@ func TestCall6(t *testing.T) { {title: "ok", closure: NewAtom("p").Apply(NewAtom("a")), additional: [6]Term{NewAtom("b"), NewAtom("c"), NewAtom("d"), NewAtom("e"), NewAtom("f"), NewAtom("g")}, ok: true}, {title: "closure is a variable", closure: NewVariable(), additional: [6]Term{NewAtom("b"), NewAtom("c"), NewAtom("d"), NewAtom("e"), NewAtom("f"), NewAtom("g")}, err: InstantiationError(nil)}, {title: "closure is neither a variable nor a callable term", closure: Integer(3), additional: [6]Term{NewAtom("b"), NewAtom("c"), NewAtom("d"), NewAtom("e"), NewAtom("f"), NewAtom("g")}, err: typeError(validTypeCallable, Integer(3), nil)}, - {title: "out of memory", closure: NewAtom("p").Apply(NewAtom("a")), additional: [6]Term{NewAtom("b"), NewAtom("c"), NewAtom("d"), NewAtom("e"), NewAtom("f"), NewAtom("g")}, err: resourceError(resourceMemory, nil), mem: 1}, + {title: "out of memory", closure: NewAtom("p").Apply(NewAtom("a"), NewAtom("a"), NewAtom("a"), NewAtom("a"), NewAtom("a"), NewAtom("a"), NewAtom("a"), NewAtom("a")), additional: [6]Term{NewAtom("b"), NewAtom("c"), NewAtom("d"), NewAtom("e"), NewAtom("f"), NewAtom("g")}, err: resourceError(resourceMemory, nil), mem: 1}, } for _, tt := range tests { @@ -260,7 +260,7 @@ func TestCall7(t *testing.T) { {title: "ok", closure: NewAtom("p").Apply(NewAtom("a")), additional: [7]Term{NewAtom("b"), NewAtom("c"), NewAtom("d"), NewAtom("e"), NewAtom("f"), NewAtom("g"), NewAtom("h")}, ok: true}, {title: "closure is a variable", closure: NewVariable(), additional: [7]Term{NewAtom("b"), NewAtom("c"), NewAtom("d"), NewAtom("e"), NewAtom("f"), NewAtom("g"), NewAtom("h")}, err: InstantiationError(nil)}, {title: "closure is neither a variable nor a callable term", closure: Integer(3), additional: [7]Term{NewAtom("b"), NewAtom("c"), NewAtom("d"), NewAtom("e"), NewAtom("f"), NewAtom("g"), NewAtom("h")}, err: typeError(validTypeCallable, Integer(3), nil)}, - {title: "out of memory", closure: NewAtom("p").Apply(NewAtom("a")), additional: [7]Term{NewAtom("b"), NewAtom("c"), NewAtom("d"), NewAtom("e"), NewAtom("f"), NewAtom("g"), NewAtom("h")}, err: resourceError(resourceMemory, nil), mem: 1}, + {title: "out of memory", closure: NewAtom("p").Apply(NewAtom("a"), NewAtom("a"), NewAtom("a"), NewAtom("a"), NewAtom("a"), NewAtom("a"), NewAtom("a"), NewAtom("a")), additional: [7]Term{NewAtom("b"), NewAtom("c"), NewAtom("d"), NewAtom("e"), NewAtom("f"), NewAtom("g"), NewAtom("h")}, err: resourceError(resourceMemory, nil), mem: 1}, } for _, tt := range tests { @@ -822,6 +822,7 @@ func TestCopyTerm(t *testing.T) { ok bool err error env map[Variable]Term + mem int64 }{ // 8.5.4.4 Examples {title: "copy_term(X, Y).", in: x, out: y, ok: true}, @@ -841,10 +842,16 @@ func TestCopyTerm(t *testing.T) { {title: "codeList", in: CodeList("foo"), out: CodeList("foo"), ok: true}, {title: "list", in: List(NewAtom("a"), NewAtom("b"), NewAtom("c")), out: List(NewAtom("a"), NewAtom("b"), NewAtom("c")), ok: true}, {title: "partial", in: PartialList(x, NewAtom("a"), NewAtom("b")), out: PartialList(x, NewAtom("a"), NewAtom("b")), ok: true}, + + {title: "out of memory: list", in: List(List(NewVariable(), NewVariable(), NewVariable(), NewVariable(), NewVariable(), NewVariable(), NewVariable(), NewVariable(), NewVariable())), out: NewVariable(), mem: 1, err: resourceError(resourceMemory, nil)}, + {title: "out of memory: partial", in: PartialList(PartialList(NewVariable(), NewVariable(), NewVariable(), NewVariable(), NewVariable(), NewVariable(), NewVariable(), NewVariable(), NewVariable(), NewVariable()), NewVariable()), out: NewVariable(), mem: 1, err: resourceError(resourceMemory, nil)}, + {title: "out of memory: compound", in: NewAtom("f").Apply(NewAtom("f").Apply(NewVariable(), NewVariable(), NewVariable(), NewVariable(), NewVariable(), NewVariable(), NewVariable(), NewVariable(), NewVariable())), out: NewVariable(), mem: 1, err: resourceError(resourceMemory, nil)}, } for _, tt := range tests { t.Run(tt.title, func(t *testing.T) { + defer setMemFree(tt.mem)() + ok, err := CopyTerm(nil, tt.in, tt.out, func(env *Env) *Promise { for k, v := range tt.env { assert.Equal(t, v, env.Resolve(k)) @@ -897,7 +904,7 @@ func TestTermVariables(t *testing.T) { vars: List(b), }}, - {title: "out of memory", term: NewAtom("f").Apply(NewVariable(), NewVariable()), vars: vars, ok: false, err: resourceError(resourceMemory, nil), mem: 1}, + {title: "out of memory", term: NewAtom("f").Apply(NewVariable(), NewVariable(), NewVariable(), NewVariable(), NewVariable(), NewVariable(), NewVariable(), NewVariable(), NewVariable()), vars: vars, ok: false, err: resourceError(resourceMemory, nil), mem: 1}, } env := NewEnv(). @@ -1457,9 +1464,36 @@ func TestBagOf(t *testing.T) { }, { - title: "out of memory", - template: x, - goal: atomSemiColon.Apply(atomEqual.Apply(x, y), atomEqual.Apply(x, y)), + title: "out of memory: goal", + template: x, + goal: seq(atomSemiColon, + atomEqual.Apply(x, NewVariable()), + atomEqual.Apply(x, NewVariable()), + atomEqual.Apply(x, NewVariable()), + atomEqual.Apply(x, NewVariable()), + atomEqual.Apply(x, NewVariable()), + atomEqual.Apply(x, NewVariable()), + atomEqual.Apply(x, NewVariable()), + atomEqual.Apply(x, NewVariable()), + ), + instances: s, + err: resourceError(resourceMemory, nil), + mem: 1, + }, + { + title: "out of memory: free variables", + template: x, + goal: seq(atomSemiColon, + atomEqual.Apply(x, NewVariable()), + atomEqual.Apply(x, NewVariable()), + atomEqual.Apply(x, NewVariable()), + atomEqual.Apply(x, NewVariable()), + atomEqual.Apply(x, NewVariable()), + atomEqual.Apply(x, NewVariable()), + atomEqual.Apply(x, NewVariable()), + atomEqual.Apply(x, NewVariable()), + atomEqual.Apply(x, NewVariable()), + ), instances: s, err: resourceError(resourceMemory, nil), mem: 1, @@ -1829,9 +1863,36 @@ func TestSetOf(t *testing.T) { }, { - title: "out of memory", - template: x, - goal: atomSemiColon.Apply(atomEqual.Apply(x, y), atomEqual.Apply(x, y)), + title: "out of memory: goal", + template: x, + goal: seq(atomSemiColon, + atomEqual.Apply(x, NewVariable()), + atomEqual.Apply(x, NewVariable()), + atomEqual.Apply(x, NewVariable()), + atomEqual.Apply(x, NewVariable()), + atomEqual.Apply(x, NewVariable()), + atomEqual.Apply(x, NewVariable()), + atomEqual.Apply(x, NewVariable()), + atomEqual.Apply(x, NewVariable()), + ), + instances: s, + err: resourceError(resourceMemory, nil), + mem: 1, + }, + { + title: "out of memory: free variables", + template: x, + goal: seq(atomSemiColon, + atomEqual.Apply(x, NewVariable()), + atomEqual.Apply(x, NewVariable()), + atomEqual.Apply(x, NewVariable()), + atomEqual.Apply(x, NewVariable()), + atomEqual.Apply(x, NewVariable()), + atomEqual.Apply(x, NewVariable()), + atomEqual.Apply(x, NewVariable()), + atomEqual.Apply(x, NewVariable()), + atomEqual.Apply(x, NewVariable()), + ), instances: s, err: resourceError(resourceMemory, nil), mem: 1, @@ -1950,6 +2011,7 @@ func TestFindAll(t *testing.T) { ok bool err error env map[Variable]Term + mem int64 }{ // 8.10.1.4 Examples {title: "1", template: x, goal: atomSemiColon.Apply(atomEqual.Apply(x, Integer(1)), atomEqual.Apply(x, Integer(2))), instances: s, ok: true, env: map[Variable]Term{ @@ -1970,6 +2032,15 @@ func TestFindAll(t *testing.T) { // 8.10.1.3 Errors {title: "c", template: x, goal: atomSemiColon.Apply(atomEqual.Apply(x, Integer(1)), atomEqual.Apply(x, Integer(2))), instances: NewAtom("foo"), err: typeError(validTypeList, NewAtom("foo"), nil)}, + + { + title: "out of memory", + template: tuple(NewVariable(), NewVariable(), NewVariable(), NewVariable(), NewVariable(), NewVariable(), NewVariable(), NewVariable(), NewVariable()), + goal: atomEqual.Apply(x, Integer(1)), + instances: s, + err: Exception{term: atomError.Apply(atomResourceError.Apply(resourceMemory.Term()), atomSlash.Apply(atomEqual, Integer(2)))}, + mem: 1, + }, } var vm VM @@ -1987,6 +2058,8 @@ func TestFindAll(t *testing.T) { for _, tt := range tests { t.Run(tt.title, func(t *testing.T) { + defer setMemFree(tt.mem)() + ok, err := FindAll(&vm, tt.template, tt.goal, tt.instances, func(env *Env) *Promise { for k, v := range tt.env { _, ok := env.Unify(v, k) @@ -5383,6 +5456,21 @@ func TestClause(t *testing.T) { assert.Equal(t, typeError(validTypeCallable, Integer(0), nil), err) assert.False(t, ok) }) + + t.Run("out of memory", func(t *testing.T) { + defer setMemFree(1)() + + vm := VM{ + procedures: map[procedureIndicator]procedure{ + {name: NewAtom("green"), arity: 1}: &userDefined{public: true, clauses: []clause{ + {raw: NewAtom("green").Apply(NewVariable(), NewVariable(), NewVariable(), NewVariable(), NewVariable(), NewVariable(), NewVariable(), NewVariable(), NewVariable())}, + }}, + }, + } + ok, err := Clause(&vm, NewAtom("green").Apply(NewVariable()), NewVariable(), Success, nil).Force(context.Background()) + assert.Equal(t, resourceError(resourceMemory, nil), err) + assert.False(t, ok) + }) } func TestAtomLength(t *testing.T) { diff --git a/engine/exception.go b/engine/exception.go index e06ec47..be5da1f 100644 --- a/engine/exception.go +++ b/engine/exception.go @@ -11,7 +11,11 @@ type Exception struct { // NewException creates an Exception from a copy of the given Term. func NewException(term Term, env *Env) Exception { - return Exception{term: renamedCopy(term, nil, env)} + c, err := renamedCopy(term, nil, env) + if err != nil { + return err.(Exception) // Must be error(resource_error(memory), _). + } + return Exception{term: c} } // Term returns the underlying Term of the Exception. diff --git a/engine/exception_test.go b/engine/exception_test.go index eddc93b..eddcc2e 100644 --- a/engine/exception_test.go +++ b/engine/exception_test.go @@ -6,6 +6,13 @@ import ( "github.com/stretchr/testify/assert" ) +func TestNewException(t *testing.T) { + assert.Equal(t, Exception{term: NewAtom("foo").Apply(NewAtom("bar"))}, NewException(NewAtom("foo").Apply(NewAtom("bar")), nil)) + + defer setMemFree(1)() + assert.Equal(t, resourceError(resourceMemory, nil), NewException(NewAtom("foo").Apply(NewVariable(), NewVariable(), NewVariable(), NewVariable(), NewVariable(), NewVariable(), NewVariable(), NewVariable(), NewVariable()), nil)) +} + func TestException_Error(t *testing.T) { e := Exception{term: NewAtom("foo")} assert.Equal(t, "foo", e.Error()) diff --git a/engine/malloc.go b/engine/malloc.go index c931c83..19e2551 100644 --- a/engine/malloc.go +++ b/engine/malloc.go @@ -9,6 +9,8 @@ import ( var errOutOfMemory = errors.New("out of memory") +var termSize = int64(unsafe.Sizeof(Term(nil))) + var memFree = func() int64 { limit := debug.SetMemoryLimit(-1) var stats runtime.MemStats @@ -19,7 +21,11 @@ var memFree = func() int64 { // makeSlice tries to allocate a slice safely by respecting debug.SetMemoryLimit(). // There's still a chance to breach the limit due to a race condition. // Yet, it can still prevent allocation of unreasonably large slices. -func makeSlice[T any](n int) (_ []T, err error) { +func makeSlice(n int) (_ []Term, err error) { + if n <= 8 { // Overlook small slices for better performance. + return make([]Term, n), nil + } + defer func() { if r := recover(); r != nil { // e.g. "runtime error: makeslice: len out of range" @@ -29,12 +35,9 @@ func makeSlice[T any](n int) (_ []T, err error) { free := memFree() - var t T - size := int64(unsafe.Sizeof(t)) - - if free < int64(n)*size { + if free < int64(n)*termSize { return nil, errOutOfMemory } - return make([]T, n), nil + return make([]Term, n), nil }