Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: invoke user recover with implicit panics #3067

Merged
merged 15 commits into from
Nov 22, 2024
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) 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")})

Check warning on line 208 in gnovm/pkg/gnolang/alloc.go

View check run for this annotation

Codecov / codecov/patch

gnovm/pkg/gnolang/alloc.go#L208

Added line #L208 was not covered by tests
}

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
24 changes: 23 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,26 @@ const (
// main run loop.

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

if r != nil {
switch r := r.(type) {
case *Exception:
panicStmt := &PanicStmt{
Exception: &BasicLitExpr{Value: `"` + r.Sprint(m) + `"`, Kind: STRING},
Fixed Show fixed Hide fixed
}

m.PushStmt(panicStmt)
m.PushOp(OpExec)
petar-dambovaliev marked this conversation as resolved.
Show resolved Hide resolved

m.Run()
default:
panic(r)
}
}
}()

for {
if m.Debugger.enabled {
m.Debug()
Expand Down
6 changes: 5 additions & 1 deletion gnovm/pkg/gnolang/op_assign.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,11 @@
}
}
// lv /= rv
quoAssign(lv.TV, rv)
err := quoAssign(lv.TV, rv)
petar-dambovaliev marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
panic(err)

Check warning on line 136 in gnovm/pkg/gnolang/op_assign.go

View check run for this annotation

Codecov / codecov/patch

gnovm/pkg/gnolang/op_assign.go#L136

Added line #L136 was not covered by tests
}

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

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

func (m *Machine) doOpRem() {
Expand Down Expand Up @@ -845,45 +848,94 @@
}

// 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
}

Check warning on line 867 in gnovm/pkg/gnolang/op_binary.go

View check run for this annotation

Codecov / codecov/patch

gnovm/pkg/gnolang/op_binary.go#L866-L867

Added lines #L866 - L867 were not covered by tests
lv.SetInt8(lv.GetInt8() / rv.GetInt8())
case Int16Type:
if rv.GetInt16() == 0 {
return expt
}

Check warning on line 872 in gnovm/pkg/gnolang/op_binary.go

View check run for this annotation

Codecov / codecov/patch

gnovm/pkg/gnolang/op_binary.go#L871-L872

Added lines #L871 - L872 were not covered by tests
lv.SetInt16(lv.GetInt16() / rv.GetInt16())
case Int32Type, UntypedRuneType:
if rv.GetInt32() == 0 {
return expt
}

Check warning on line 877 in gnovm/pkg/gnolang/op_binary.go

View check run for this annotation

Codecov / codecov/patch

gnovm/pkg/gnolang/op_binary.go#L876-L877

Added lines #L876 - L877 were not covered by tests
lv.SetInt32(lv.GetInt32() / rv.GetInt32())
case Int64Type:
if rv.GetInt64() == 0 {
return expt
}

Check warning on line 882 in gnovm/pkg/gnolang/op_binary.go

View check run for this annotation

Codecov / codecov/patch

gnovm/pkg/gnolang/op_binary.go#L881-L882

Added lines #L881 - L882 were not covered by tests
lv.SetInt64(lv.GetInt64() / rv.GetInt64())
case UintType:
if rv.GetUint() == 0 {
return expt
}

Check warning on line 887 in gnovm/pkg/gnolang/op_binary.go

View check run for this annotation

Codecov / codecov/patch

gnovm/pkg/gnolang/op_binary.go#L886-L887

Added lines #L886 - L887 were not covered by tests
lv.SetUint(lv.GetUint() / rv.GetUint())
case Uint8Type:
if rv.GetUint8() == 0 {
return expt
}

Check warning on line 892 in gnovm/pkg/gnolang/op_binary.go

View check run for this annotation

Codecov / codecov/patch

gnovm/pkg/gnolang/op_binary.go#L891-L892

Added lines #L891 - L892 were not covered by tests
lv.SetUint8(lv.GetUint8() / rv.GetUint8())
case DataByteType:
if rv.GetUint8() == 0 {
return expt
}

Check warning on line 897 in gnovm/pkg/gnolang/op_binary.go

View check run for this annotation

Codecov / codecov/patch

gnovm/pkg/gnolang/op_binary.go#L896-L897

Added lines #L896 - L897 were not covered by tests
lv.SetDataByte(lv.GetDataByte() / rv.GetUint8())
case Uint16Type:
if rv.GetUint16() == 0 {
return expt
}

Check warning on line 902 in gnovm/pkg/gnolang/op_binary.go

View check run for this annotation

Codecov / codecov/patch

gnovm/pkg/gnolang/op_binary.go#L901-L902

Added lines #L901 - L902 were not covered by tests
lv.SetUint16(lv.GetUint16() / rv.GetUint16())
case Uint32Type:
if rv.GetUint32() == 0 {
return expt
}

Check warning on line 907 in gnovm/pkg/gnolang/op_binary.go

View check run for this annotation

Codecov / codecov/patch

gnovm/pkg/gnolang/op_binary.go#L906-L907

Added lines #L906 - L907 were not covered by tests
lv.SetUint32(lv.GetUint32() / rv.GetUint32())
case Uint64Type:
if rv.GetUint64() == 0 {
return expt
}

Check warning on line 912 in gnovm/pkg/gnolang/op_binary.go

View check run for this annotation

Codecov / codecov/patch

gnovm/pkg/gnolang/op_binary.go#L911-L912

Added lines #L911 - L912 were not covered by tests
lv.SetUint64(lv.GetUint64() / rv.GetUint64())
case Float32Type:
// NOTE: gno doesn't fuse *+.
if rv.GetFloat32() == 0 {
return expt
}

Check warning on line 918 in gnovm/pkg/gnolang/op_binary.go

View check run for this annotation

Codecov / codecov/patch

gnovm/pkg/gnolang/op_binary.go#L916-L918

Added lines #L916 - L918 were not covered by tests
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
}

Check warning on line 925 in gnovm/pkg/gnolang/op_binary.go

View check run for this annotation

Codecov / codecov/patch

gnovm/pkg/gnolang/op_binary.go#L924-L925

Added lines #L924 - L925 were not covered by tests
lv.SetFloat64(lv.GetFloat64() / rv.GetFloat64())
// XXX FOR DETERMINISM, PANIC IF NAN.
case BigintType, UntypedBigintType:
if rv.GetBigInt().Sign() == 0 {
return expt
}

Check warning on line 931 in gnovm/pkg/gnolang/op_binary.go

View check run for this annotation

Codecov / codecov/patch

gnovm/pkg/gnolang/op_binary.go#L930-L931

Added lines #L930 - L931 were not covered by tests
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
}

Check warning on line 938 in gnovm/pkg/gnolang/op_binary.go

View check run for this annotation

Codecov / codecov/patch

gnovm/pkg/gnolang/op_binary.go#L937-L938

Added lines #L937 - L938 were not covered by tests
lb := lv.GetBigDec()
rb := rv.GetBigDec()
quo := apd.New(0, 0)
Expand All @@ -898,6 +950,8 @@
lv.T,
))
}

return nil
}

// for doOpRem and doOpRemAssign.
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")})
petar-dambovaliev marked this conversation as resolved.
Show resolved Hide resolved
}

pv := xv.V.(PointerValue)
if pv.TV.T == DataByteType {
tv := TypedValue{T: bt.Elt}
Expand Down
57 changes: 37 additions & 20 deletions gnovm/pkg/gnolang/values.go
Original file line number Diff line number Diff line change
Expand Up @@ -436,12 +436,18 @@
func (sv *SliceValue) GetPointerAtIndexInt2(store Store, ii int, et Type) PointerValue {
// Necessary run-time slice bounds check
if ii < 0 {
panic(fmt.Sprintf(
"slice index out of bounds: %d", ii))
excpt := &Exception{
Value: typedString(fmt.Sprintf(
"slice index out of bounds: %d", ii)),
}
panic(excpt)

Check warning on line 443 in gnovm/pkg/gnolang/values.go

View check run for this annotation

Codecov / codecov/patch

gnovm/pkg/gnolang/values.go#L439-L443

Added lines #L439 - L443 were not covered by tests
} else if sv.Length <= ii {
panic(fmt.Sprintf(
"slice index out of bounds: %d (len=%d)",
ii, sv.Length))
excpt := &Exception{
Value: typedString(fmt.Sprintf(
"slice index out of bounds: %d (len=%d)",
ii, sv.Length)),
}
panic(excpt)
}
return sv.GetBase(store).GetPointerAtIndexInt2(store, sv.Offset+ii, et)
}
Expand Down Expand Up @@ -1975,6 +1981,14 @@
bv := &TypedValue{ // heap alloc
T: Uint8Type,
}

if ii >= len(sv) {
panic(&Exception{Value: typedString(fmt.Sprintf("index out of range [%d] with length %d", ii, len(sv)))})
}
if ii < 0 {
panic(&Exception{Value: typedString(fmt.Sprintf("invalid slice index %d (index must be non-negative)", ii))})

Check warning on line 1989 in gnovm/pkg/gnolang/values.go

View check run for this annotation

Codecov / codecov/patch

gnovm/pkg/gnolang/values.go#L1989

Added line #L1989 was not covered by tests
}

bv.SetUint8(sv[ii])
return PointerValue{
TV: bv,
Expand All @@ -1997,7 +2011,7 @@
return sv.GetPointerAtIndexInt2(store, ii, bt.Elt)
case *MapType:
if tv.V == nil {
panic("uninitialized map index")
panic(&Exception{Value: typedString("uninitialized map index")})
}
mv := tv.V.(*MapValue)
pv := mv.GetPointerForKey(alloc, store, iv)
Expand Down Expand Up @@ -2149,39 +2163,41 @@

func (tv *TypedValue) GetSlice(alloc *Allocator, low, high int) TypedValue {
if low < 0 {
panic(fmt.Sprintf(
panic(&Exception{Value: typedString(fmt.Sprintf(
"invalid slice index %d (index must be non-negative)",
low))
low))})
}
if high < 0 {
panic(fmt.Sprintf(
panic(&Exception{Value: typedString(fmt.Sprintf(

Check warning on line 2171 in gnovm/pkg/gnolang/values.go

View check run for this annotation

Codecov / codecov/patch

gnovm/pkg/gnolang/values.go#L2171

Added line #L2171 was not covered by tests
"invalid slice index %d (index must be non-negative)",
high))
low))})

Check warning on line 2173 in gnovm/pkg/gnolang/values.go

View check run for this annotation

Codecov / codecov/patch

gnovm/pkg/gnolang/values.go#L2173

Added line #L2173 was not covered by tests
}
if low > high {
panic(fmt.Sprintf(
panic(&Exception{Value: typedString(fmt.Sprintf(

Check warning on line 2176 in gnovm/pkg/gnolang/values.go

View check run for this annotation

Codecov / codecov/patch

gnovm/pkg/gnolang/values.go#L2176

Added line #L2176 was not covered by tests
"invalid slice index %d > %d",
low, high))
low, high))})

Check warning on line 2178 in gnovm/pkg/gnolang/values.go

View check run for this annotation

Codecov / codecov/patch

gnovm/pkg/gnolang/values.go#L2178

Added line #L2178 was not covered by tests
}
switch t := baseOf(tv.T).(type) {
case PrimitiveType:
if tv.GetLength() < high {
panic(fmt.Sprintf(
panic(&Exception{Value: typedString(fmt.Sprintf(

Check warning on line 2183 in gnovm/pkg/gnolang/values.go

View check run for this annotation

Codecov / codecov/patch

gnovm/pkg/gnolang/values.go#L2183

Added line #L2183 was not covered by tests
"slice bounds out of range [%d:%d] with string length %d",
low, high, tv.GetLength()))
low, high, tv.GetLength()))})

Check warning on line 2185 in gnovm/pkg/gnolang/values.go

View check run for this annotation

Codecov / codecov/patch

gnovm/pkg/gnolang/values.go#L2185

Added line #L2185 was not covered by tests
}
if t == StringType || t == UntypedStringType {
return TypedValue{
T: tv.T,
V: alloc.NewString(tv.GetString()[low:high]),
}
}
panic("non-string primitive type cannot be sliced")
panic(&Exception{Value: typedString(fmt.Sprintf(
"non-string primitive type cannot be sliced",
))})

Check warning on line 2195 in gnovm/pkg/gnolang/values.go

View check run for this annotation

Codecov / codecov/patch

gnovm/pkg/gnolang/values.go#L2193-L2195

Added lines #L2193 - L2195 were not covered by tests
case *ArrayType:
if tv.GetLength() < high {
panic(fmt.Sprintf(
panic(&Exception{Value: typedString(fmt.Sprintf(

Check warning on line 2198 in gnovm/pkg/gnolang/values.go

View check run for this annotation

Codecov / codecov/patch

gnovm/pkg/gnolang/values.go#L2198

Added line #L2198 was not covered by tests
"slice bounds out of range [%d:%d] with array length %d",
low, high, tv.GetLength()))
low, high, tv.GetLength()))})

Check warning on line 2200 in gnovm/pkg/gnolang/values.go

View check run for this annotation

Codecov / codecov/patch

gnovm/pkg/gnolang/values.go#L2200

Added line #L2200 was not covered by tests
}
av := tv.V.(*ArrayValue)
st := alloc.NewType(&SliceType{
Expand All @@ -2199,13 +2215,14 @@
}
case *SliceType:
if tv.GetCapacity() < high {
panic(fmt.Sprintf(
panic(&Exception{Value: typedString(fmt.Sprintf(

Check warning on line 2218 in gnovm/pkg/gnolang/values.go

View check run for this annotation

Codecov / codecov/patch

gnovm/pkg/gnolang/values.go#L2218

Added line #L2218 was not covered by tests
"slice bounds out of range [%d:%d] with capacity %d",
low, high, tv.GetCapacity()))
low, high, tv.GetCapacity()))})

Check warning on line 2220 in gnovm/pkg/gnolang/values.go

View check run for this annotation

Codecov / codecov/patch

gnovm/pkg/gnolang/values.go#L2220

Added line #L2220 was not covered by tests
}
if tv.V == nil {
if low != 0 || high != 0 {
panic("nil slice index out of range")
panic(&Exception{Value: typedString(fmt.Sprintf(
"nil slice index out of range"))})

Check warning on line 2225 in gnovm/pkg/gnolang/values.go

View check run for this annotation

Codecov / codecov/patch

gnovm/pkg/gnolang/values.go#L2224-L2225

Added lines #L2224 - L2225 were not covered by tests
}
return TypedValue{
T: tv.T,
Expand Down
15 changes: 15 additions & 0 deletions gnovm/tests/files/recover12.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package main


func main() {
defer func() {
r := recover()
println("recover:", r)
}()

arr := []int{1, 2, 3}
_ = arr[3] // Panics because index 3 is out of bounds
}

// Output:
// recover: slice index out of bounds: 3 (len=3)
15 changes: 15 additions & 0 deletions gnovm/tests/files/recover13.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package main


func main() {
defer func() {
r := recover()
println("recover:", r)
}()

arr := []int{1, 2, 3}
_ = arr[-1:] // Panics because of negative index
}

// Output:
// recover: invalid slice index -1 (index must be non-negative)
15 changes: 15 additions & 0 deletions gnovm/tests/files/recover14.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package main
petar-dambovaliev marked this conversation as resolved.
Show resolved Hide resolved


func main() {
defer func() {
r := recover()
println("recover:", r)
}()

x, y := 10, 0
_ = x / y // Panics because of division by zero
}

// Output:
// recover: division by zero
15 changes: 15 additions & 0 deletions gnovm/tests/files/recover15.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package main


func main() {
defer func() {
r := recover()
println("recover:", r)
}()

var i interface{} = "hello"
_ = i.(int) // Panics because i holds a string, not an int
}

// Output:
// recover: string is not of type int
Loading
Loading