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

Open
wants to merge 8 commits into
base: master
Choose a base branch
from

Conversation

petar-dambovaliev
Copy link
Contributor

@petar-dambovaliev petar-dambovaliev commented Nov 4, 2024

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

This maintains the distinction between VM panics and user panics.

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

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

Copy link

codecov bot commented Nov 4, 2024

Codecov Report

Attention: Patch coverage is 41.37931% with 68 lines in your changes missing coverage. Please review.

Project coverage is 63.74%. Comparing base (4f27a57) to head (6b6ab58).

Files with missing lines Patch % Lines
gnovm/pkg/gnolang/op_binary.go 22.22% 29 Missing and 13 partials ⚠️
gnovm/pkg/gnolang/values.go 35.29% 21 Missing and 1 partial ⚠️
gnovm/pkg/gnolang/alloc.go 50.00% 1 Missing and 1 partial ⚠️
gnovm/pkg/gnolang/op_assign.go 33.33% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #3067      +/-   ##
==========================================
- Coverage   63.77%   63.74%   -0.04%     
==========================================
  Files         548      548              
  Lines       78681    78773      +92     
==========================================
+ Hits        50180    50215      +35     
- Misses      25117    25164      +47     
- Partials     3384     3394      +10     
Flag Coverage Δ
contribs/gnodev 61.16% <ø> (+0.62%) ⬆️
contribs/gnofaucet 15.77% <ø> (+0.94%) ⬆️
gno.land 73.62% <ø> (ø)
gnovm 67.86% <41.37%> (-0.07%) ⬇️
misc/genstd 79.72% <ø> (ø)
tm2 62.39% <ø> (-0.08%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@ltzmaxwell
Copy link
Contributor

can you please add some tests for this?

@omarsy
Copy link
Member

omarsy commented Nov 5, 2024

I tried a fix similar to this one: #2484, but my approach wasn't preferred. They favor an indiscriminate runtime panic, which complicates things for me (it is possible, but need a refactoring). I see two options:

  1. Do not indiscriminate runtime panics (like your fix and my fix)
  2. Accept that runtime errors are not recoverable

@petar-dambovaliev
Copy link
Contributor Author

can you please add some tests for this?

its still WIP

gnovm/pkg/gnolang/machine.go Dismissed Show dismissed Hide dismissed
@petar-dambovaliev
Copy link
Contributor Author

can you please add some tests for this?

tests added

@Kouteki Kouteki added the in focus Core team is prioritizing this work label Nov 10, 2024
@Kouteki Kouteki added this to the 🚀 Mainnet launch milestone Nov 10, 2024
Copy link
Contributor

@mvertes mvertes left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me

Copy link
Member

@omarsy omarsy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GG for this PR, it's a great improvement! However, I have a small issue regarding some cases where error handling with recover doesn't seem to work as expected. For example, in the Gno Native packages, like this one:

package main

import "time"

func main() {
    defer func() {
        r := recover()
        println("recover:", r)
    }()
    t := time.Now()
    t.In(nil)  
}

The issue here is that t.In(nil) causes an error, but there's no recovery mechanism for this situation. However, since Gno Native will no longer be supported in the future, perhaps we don't need to handle this case at this point.

We also have cases where panic occurs when we inject Go code injected into a Gno package. For example, this code:

package test

import "std"

func main() {
    defer func() {
        r := recover()
        println("recover:", r)
    }()
    banker := std.GetBanker(std.BankerTypeRealmSend)
    banker.SendCoins(std.CurrentRealm().Addr(), "", std.Coins{{"ugnot", 2000000000}})
}

I’ve seen this kind of panic occur, and I’m unsure if this should be recoverable.

Also, consider this case:

package test

import "time"

func main() {
    defer func() {
        r := recover()
        println("recover:", r)
    }()
    println(time.UTC == nil)
    time.UTC = nil
}

This could also cause an issue, and I’m not sure if this is something we should be able to recover from.

Just wanted to raise these points, but again, great job on this PR!

Comment on lines +1291 to +1296
panicStmt := &PanicStmt{
Exception: &BasicLitExpr{Value: `"` + r.Sprint(m) + `"`, Kind: STRING},
}

m.PushStmt(panicStmt)
m.PushOp(OpExec)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe this ?

Suggested change
panicStmt := &PanicStmt{
Exception: &BasicLitExpr{Value: `"` + r.Sprint(m) + `"`, Kind: STRING},
}
m.PushStmt(panicStmt)
m.PushOp(OpExec)
m.Panic(r.Value)

Or Instead of panicking upon detecting a runtime panic, you can use m.Panic.

@@ -131,7 +131,11 @@ func (m *Machine) doOpQuoAssign() {
}
}
// lv /= rv
quoAssign(lv.TV, rv)
err := quoAssign(lv.TV, rv)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe we should hande this case too:

package main


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

    x, y := 10, 0
    _ = x % y 
}

@@ -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")})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this case will failed:

package main

type s struct {
	el func()
}


func main() {
	var el *s
	defer func() {
		recover()
	}()
	el.el()
}

@@ -0,0 +1,15 @@
package main
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should consider this case too.

package main


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

We cannot recover it but maybe this should be failed on the preprocess

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in focus Core team is prioritizing this work 📦 🤖 gnovm Issues or PRs gnovm related
Projects
Status: In Progress
Status: In Review
Development

Successfully merging this pull request may close these issues.

6 participants