Skip to content

Commit

Permalink
fix: invoke user recover with implicit panics (#3067)
Browse files Browse the repository at this point in the history
Currently only explicit panic invocations are recovered in the user
code.
This PR covers the implicit panics that happen because of invalid
operations.
Associated [issue](#1148)

This maintains the distinction between VM panics and user panics.

Here is a list of possible runtime panics that we will cover.

- [x] Out-of-Bounds Slice or Array Access
- [x] Invalid Slice Indexing
- [x] Division by Zero and MOD zero
- [x] Type Assertion Failure
- [x] Invalid Memory Allocation (bad call to make())
- [x] Out-of-Bounds String Indexing
- [x] nil pointer dereference
- [x] Write to a nil map

Also, fixed a small bug with the builtint function `make`.
It wasn't panicking when cap > len
  • Loading branch information
petar-dambovaliev authored Nov 22, 2024
1 parent b3e4aed commit 77e6606
Show file tree
Hide file tree
Showing 16 changed files with 307 additions and 28 deletions.
7 changes: 7 additions & 0 deletions gnovm/pkg/gnolang/alloc.go
Original file line number Diff line number Diff line change
Expand Up @@ -194,13 +194,20 @@ func (alloc *Allocator) NewString(s string) StringValue {
}

func (alloc *Allocator) NewListArray(n int) *ArrayValue {
if n < 0 {
panic(&Exception{Value: typedString("len out of range")})
}
alloc.AllocateListArray(int64(n))
return &ArrayValue{
List: make([]TypedValue, n),
}
}

func (alloc *Allocator) NewDataArray(n int) *ArrayValue {
if n < 0 {
panic(&Exception{Value: typedString("len out of range")})
}

alloc.AllocateDataArray(int64(n))
return &ArrayValue{
Data: make([]byte, n),
Expand Down
2 changes: 1 addition & 1 deletion gnovm/pkg/gnolang/debugger_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ func TestDebug(t *testing.T) {
{in: "up xxx", out: `"xxx": invalid syntax`},
{in: "b 37\nc\np b\n", out: "(3 int)"},
{in: "b 27\nc\np b\n", out: `("!zero" string)`},
{in: "b 22\nc\np t.A[3]\n", out: "Command failed: slice index out of bounds: 3 (len=3)"},
{in: "b 22\nc\np t.A[3]\n", out: "Command failed: &{(\"slice index out of bounds: 3 (len=3)\" string) <nil> }"},
{in: "b 43\nc\nc\nc\np i\ndetach\n", out: "(1 int)"},
})

Expand Down
18 changes: 17 additions & 1 deletion gnovm/pkg/gnolang/machine.go
Original file line number Diff line number Diff line change
Expand Up @@ -829,7 +829,9 @@ func (m *Machine) RunFunc(fn Name) {

func (m *Machine) RunMain() {
defer func() {
if r := recover(); r != nil {
r := recover()

if r != nil {
switch r := r.(type) {
case UnhandledPanicError:
fmt.Printf("Machine.RunMain() panic: %s\nStacktrace: %s\n",
Expand Down Expand Up @@ -1280,6 +1282,20 @@ const (
// main run loop.

func (m *Machine) Run() {
defer func() {
r := recover()

if r != nil {
switch r := r.(type) {
case *Exception:
m.Panic(r.Value)
m.Run()
default:
panic(r)
}
}
}()

for {
if m.Debugger.enabled {
m.Debug()
Expand Down
12 changes: 10 additions & 2 deletions gnovm/pkg/gnolang/op_assign.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,11 @@ func (m *Machine) doOpQuoAssign() {
}
}
// lv /= rv
quoAssign(lv.TV, rv)
err := quoAssign(lv.TV, rv)
if err != nil {
panic(err)
}

if lv.Base != nil {
m.Realm.DidUpdate(lv.Base.(Object), nil, nil)
}
Expand All @@ -154,7 +158,11 @@ func (m *Machine) doOpRemAssign() {
}
}
// lv %= rv
remAssign(lv.TV, rv)
err := remAssign(lv.TV, rv)
if err != nil {
panic(err)
}

if lv.Base != nil {
m.Realm.DidUpdate(lv.Base.(Object), nil, nil)
}
Expand Down
108 changes: 104 additions & 4 deletions gnovm/pkg/gnolang/op_binary.go
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,10 @@ func (m *Machine) doOpQuo() {
}

// lv / rv
quoAssign(lv, rv)
err := quoAssign(lv, rv)
if err != nil {
panic(err)
}
}

func (m *Machine) doOpRem() {
Expand All @@ -266,7 +269,10 @@ func (m *Machine) doOpRem() {
}

// lv % rv
remAssign(lv, rv)
err := remAssign(lv, rv)
if err != nil {
panic(err)
}
}

func (m *Machine) doOpShl() {
Expand Down Expand Up @@ -845,45 +851,94 @@ func mulAssign(lv, rv *TypedValue) {
}

// for doOpQuo and doOpQuoAssign.
func quoAssign(lv, rv *TypedValue) {
func quoAssign(lv, rv *TypedValue) *Exception {
expt := &Exception{
Value: typedString("division by zero"),
}

// set the result in lv.
// NOTE this block is replicated in op_assign.go
switch baseOf(lv.T) {
case IntType:
if rv.GetInt() == 0 {
return expt
}
lv.SetInt(lv.GetInt() / rv.GetInt())
case Int8Type:
if rv.GetInt8() == 0 {
return expt
}
lv.SetInt8(lv.GetInt8() / rv.GetInt8())
case Int16Type:
if rv.GetInt16() == 0 {
return expt
}
lv.SetInt16(lv.GetInt16() / rv.GetInt16())
case Int32Type, UntypedRuneType:
if rv.GetInt32() == 0 {
return expt
}
lv.SetInt32(lv.GetInt32() / rv.GetInt32())
case Int64Type:
if rv.GetInt64() == 0 {
return expt
}
lv.SetInt64(lv.GetInt64() / rv.GetInt64())
case UintType:
if rv.GetUint() == 0 {
return expt
}
lv.SetUint(lv.GetUint() / rv.GetUint())
case Uint8Type:
if rv.GetUint8() == 0 {
return expt
}
lv.SetUint8(lv.GetUint8() / rv.GetUint8())
case DataByteType:
if rv.GetUint8() == 0 {
return expt
}
lv.SetDataByte(lv.GetDataByte() / rv.GetUint8())
case Uint16Type:
if rv.GetUint16() == 0 {
return expt
}
lv.SetUint16(lv.GetUint16() / rv.GetUint16())
case Uint32Type:
if rv.GetUint32() == 0 {
return expt
}
lv.SetUint32(lv.GetUint32() / rv.GetUint32())
case Uint64Type:
if rv.GetUint64() == 0 {
return expt
}
lv.SetUint64(lv.GetUint64() / rv.GetUint64())
case Float32Type:
// NOTE: gno doesn't fuse *+.
if rv.GetFloat32() == 0 {
return expt
}
lv.SetFloat32(lv.GetFloat32() / rv.GetFloat32())
// XXX FOR DETERMINISM, PANIC IF NAN.
case Float64Type:
// NOTE: gno doesn't fuse *+.
if rv.GetFloat64() == 0 {
return expt
}
lv.SetFloat64(lv.GetFloat64() / rv.GetFloat64())
// XXX FOR DETERMINISM, PANIC IF NAN.
case BigintType, UntypedBigintType:
if rv.GetBigInt().Sign() == 0 {
return expt
}
lb := lv.GetBigInt()
lb = big.NewInt(0).Quo(lb, rv.GetBigInt())
lv.V = BigintValue{V: lb}
case BigdecType, UntypedBigdecType:
if rv.GetBigDec().Cmp(apd.New(0, 0)) == 0 {
return expt
}
lb := lv.GetBigDec()
rb := rv.GetBigDec()
quo := apd.New(0, 0)
Expand All @@ -898,36 +953,79 @@ func quoAssign(lv, rv *TypedValue) {
lv.T,
))
}

return nil
}

// for doOpRem and doOpRemAssign.
func remAssign(lv, rv *TypedValue) {
func remAssign(lv, rv *TypedValue) *Exception {
expt := &Exception{
Value: typedString("division by zero"),
}

// set the result in lv.
// NOTE this block is replicated in op_assign.go
switch baseOf(lv.T) {
case IntType:
if rv.GetInt() == 0 {
return expt
}
lv.SetInt(lv.GetInt() % rv.GetInt())
case Int8Type:
if rv.GetInt8() == 0 {
return expt
}
lv.SetInt8(lv.GetInt8() % rv.GetInt8())
case Int16Type:
if rv.GetInt16() == 0 {
return expt
}
lv.SetInt16(lv.GetInt16() % rv.GetInt16())
case Int32Type, UntypedRuneType:
if rv.GetInt32() == 0 {
return expt
}
lv.SetInt32(lv.GetInt32() % rv.GetInt32())
case Int64Type:
if rv.GetInt64() == 0 {
return expt
}
lv.SetInt64(lv.GetInt64() % rv.GetInt64())
case UintType:
if rv.GetUint() == 0 {
return expt
}
lv.SetUint(lv.GetUint() % rv.GetUint())
case Uint8Type:
if rv.GetUint8() == 0 {
return expt
}
lv.SetUint8(lv.GetUint8() % rv.GetUint8())
case DataByteType:
if rv.GetUint8() == 0 {
return expt
}
lv.SetDataByte(lv.GetDataByte() % rv.GetUint8())
case Uint16Type:
if rv.GetUint16() == 0 {
return expt
}
lv.SetUint16(lv.GetUint16() % rv.GetUint16())
case Uint32Type:
if rv.GetUint32() == 0 {
return expt
}
lv.SetUint32(lv.GetUint32() % rv.GetUint32())
case Uint64Type:
if rv.GetUint64() == 0 {
return expt
}
lv.SetUint64(lv.GetUint64() % rv.GetUint64())
case BigintType, UntypedBigintType:
if rv.GetBigInt().Sign() == 0 {
return expt
}

lb := lv.GetBigInt()
lb = big.NewInt(0).Rem(lb, rv.GetBigInt())
lv.V = BigintValue{V: lb}
Expand All @@ -937,6 +1035,8 @@ func remAssign(lv, rv *TypedValue) {
lv.T,
))
}

return nil
}

// for doOpBand and doOpBandAssign.
Expand Down
4 changes: 4 additions & 0 deletions gnovm/pkg/gnolang/op_expressions.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,10 @@ func (m *Machine) doOpStar() {
xv := m.PopValue()
switch bt := baseOf(xv.T).(type) {
case *PointerType:
if xv.V == nil {
panic(&Exception{Value: typedString("nil pointer dereference")})
}

pv := xv.V.(PointerValue)
if pv.TV.T == DataByteType {
tv := TypedValue{T: bt.Elt}
Expand Down
5 changes: 5 additions & 0 deletions gnovm/pkg/gnolang/uverse.go
Original file line number Diff line number Diff line change
Expand Up @@ -838,6 +838,11 @@ func makeUverseNode() {
li := lv.ConvertGetInt()
cv := vargs.TV.GetPointerAtIndexInt(m.Store, 1).Deref()
ci := cv.ConvertGetInt()

if ci < li {
panic(&Exception{Value: typedString(`makeslice: cap out of range`)})
}

if et.Kind() == Uint8Kind {
arrayValue := m.Alloc.NewDataArray(ci)
m.PushValue(TypedValue{
Expand Down
Loading

0 comments on commit 77e6606

Please sign in to comment.