-
Notifications
You must be signed in to change notification settings - Fork 398
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(gnovm): len/array of array with function calls should not be constant #3714
base: master
Are you sure you want to change the base?
Conversation
🛠 PR Checks SummaryAll Automated Checks passed. ✅ Manual Checks (for Reviewers):
Read More🤖 This bot helps streamline PR reviews by verifying automated checks and providing guidance for contributors and reviewers. ✅ Automated Checks (for Contributors):🟢 Maintainers must be able to edit this pull request (more info) ☑️ Contributor Actions:
☑️ Reviewer Actions:
📚 Resources:Debug
|
…tion with seen tracking
Codecov ReportAll modified and coverable lines are covered by tests ✅ 📢 Thoughts on this report? Let us know! |
@ltzmaxwell can you take a look for a first review? I think there must be something better than implementing |
gnovm/pkg/gnolang/type_check.go
Outdated
switch fv.Name { | ||
case "len", "cap": | ||
at := evalStaticTypeOf(store, last, currExpr.Args[0]) | ||
if _, ok := unwrapPointerType(baseOf(at)).(*ArrayType); ok { | ||
// ok | ||
break Main | ||
if AsExpr(currExpr.Args[0], &CallExpr{}, &seen[Expr]{}) { | ||
panic(fmt.Sprintf("%s (value of type %s) is not constant", currExpr.String(), at)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we just check into the compositLit's Elts, and do:
assertValidConstValue(store, last, elt.Value)
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Your point is that the composite literal expression I think can't be used here due to the rule:
The expressions len(s) and cap(s) are constants if the type of s is an array or pointer to an array and the expression s does not contain channel receives or (non-constant) function calls; in this case s is not evaluated.
For instance, in the following code:
package main
import "fmt"
func S() [2]int {
return [2]int{}
}
func main() {
const s = len(S())
fmt.Println("Hello, 世界", s)
}
the call to S()
is a function call, which makes len(S())
non-constant.
gnovm/tests/files/const53.gno
Outdated
package main | ||
|
||
import "fmt" | ||
|
||
func main() { | ||
const a = len([1]int{ | ||
func() int { println("hey!!"); return 1 }(), | ||
}) | ||
fmt.Println("Hello", a) | ||
} | ||
|
||
// Error: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we can also add another test to clarify, which is valid:
nestedArr := [1]int{func() int { println("hey!!"); return 1 }()}
const a = len(nestedArr)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should be done here 8c71cc6 ^^
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This change introduces a regression at least for the following case:
package main
func main() {
a := 1.0
const c1 = len([1]int{int(a)})
println(c1)
}
which now panics, but should not (and wasn't).
@omarsy can you address the feedback on this PR? |
…xpression retrieval to filter out constant functions
Good catch! I missed that in the spec. Constant function calls are indeed allowed. This should done here 8c71cc6. |
note this should be invalid: const a = len([1]int{len([]int{1})}) |
gnovm/pkg/gnolang/type_check.go
Outdated
break Main | ||
exps := getExpr(currExpr.Args[0], &CallExpr{}, &seen[Expr]{}) | ||
for _, exp := range exps { | ||
cx := exp.(*CallExpr) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand how they are all CallExprs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They are all CallExpr
s because the getExprs function is explicitly filtering for that type. When we call getExpr
, we pass a *CallExpr
as a template so that only nodes matching a call expression are collected.
gnovm/pkg/gnolang/type_check.go
Outdated
@@ -1276,3 +1286,192 @@ func isBlankIdentifier(x Expr) bool { | |||
} | |||
return false | |||
} | |||
|
|||
func getExpr(base Expr, expr Expr, seen *seen[Expr]) (res []Expr) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not sure why Gno needs to do this, and why Go's typechecker can't just pick up on it, but it seems this name should be changed, as it does not return a single expression, and getExprs() still isn't descriptive.
I'm also wondering if this could be implemented more succinctly using Transcribe().
See findGotoLoopDefines or findLoopUses1 for example usage, see usage of TRANS_SKIP in findGotoLoopDefines for example, to skip over function bodies.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice suggestion ^^! I was looking into this as well and couldn’t find anything existing that did it the way I needed. Indeed, using Transcribe()
is a better fit for this task, it simplifies the implementation. I've updated the code accordingly. Check out the commit here: [875c04f5].
Thanks again for the feedback ^^!
Deferring to post-mainnet launch, as this code is rejected by the type checker, and thus not accepted on-chain. See comment on #3701. |
closes: #3701