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

feat: Preventing injecting methods on a non-exist PackageNode #3199

Draft
wants to merge 8 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 23 additions & 4 deletions gnovm/cmd/gno/lint_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,36 @@ func TestLintApp(t *testing.T) {
{
args: []string{"lint"},
errShouldBe: "flag: help requested",
}, {
},
{
args: []string{"lint", "../../tests/integ/run_main/"},
stderrShouldContain: "./../../tests/integ/run_main: gno.mod file not found in current or any parent directory (code=1)",
errShouldBe: "exit code: 1",
}, {
},
{
args: []string{"lint", "../../tests/integ/undefined_variable_test/undefined_variables_test.gno"},
stderrShouldContain: "undefined_variables_test.gno:6:28: name toto not declared (code=2)",
errShouldBe: "exit code: 1",
}, {
},
{
args: []string{"lint", "../../tests/integ/package_not_declared/main.gno"},
stderrShouldContain: "main.gno:4:2: name fmt not declared (code=2)",
errShouldBe: "exit code: 1",
},
{
args: []string{"lint", "../../tests/integ/several_lint_errors/main.gno"},
stderrShouldContain: "../../tests/integ/several_lint_errors/main.gno:5:5: expected ';', found example (code=2).\n../../tests/integ/several_lint_errors/main.gno:6:2: expected '}', found 'EOF' (code=2).\n",
errShouldBe: "exit code: 1",
},
{
args: []string{"lint", "../../tests/integ/several_files_multiple_errors/main.gno"},
stderrShouldContain: "../../tests/integ/several_files_multiple_errors/file2.gno:3:5: expected 'IDENT', found '{' (code=2).\n../../tests/integ/several_files_multiple_errors/file2.gno:5:1: expected type, found '}' (code=2).\n../../tests/integ/several_files_multiple_errors/main.gno:5:5: expected ';', found example (code=2).\n../../tests/integ/several_files_multiple_errors/main.gno:6:2: expected '}', found 'EOF' (code=2).\n",
errShouldBe: "exit code: 1",
},
{
args: []string{"lint", "../../tests/integ/run_main/"},
stderrShouldContain: "./../../tests/integ/run_main: missing 'gno.mod' file (code=1).",
errShouldBe: "exit code: 1",
}, {
args: []string{"lint", "../../tests/integ/several-lint-errors/main.gno"},
stderrShouldContain: "../../tests/integ/several-lint-errors/main.gno:5:5: expected ';', found example (code=2)\n../../tests/integ/several-lint-errors/main.gno:6",
Expand All @@ -41,7 +59,8 @@ func TestLintApp(t *testing.T) {
}, {
args: []string{"lint", "../../tests/integ/minimalist_gnomod/"},
// TODO: raise an error because there is a gno.mod, but no .gno files
}, {
},
{
args: []string{"lint", "../../tests/integ/invalid_module_name/"},
// TODO: raise an error because gno.mod is invalid
}, {
Expand Down
35 changes: 34 additions & 1 deletion gnovm/pkg/gnolang/nodes.go
Original file line number Diff line number Diff line change
Expand Up @@ -2169,7 +2169,40 @@ func (x *BasicLitExpr) GetInt() int {
return i
}

var rePkgName = regexp.MustCompile(`^[a-z][a-z0-9_]+$`)
var invalidPkgNames = map[string]struct{}{
"internal": {},
"crypto": {},
}

func validatePkgPath(pkgPath, pkgName string) {
parts := strings.Split(pkgPath, "/")
lastPart := parts[len(parts)-1]

// testing env
if lastPart == "." {
return
}

if _, ok := invalidPkgNames[lastPart]; ok {
panic(fmt.Sprintf("cannot create package with invalid name %q", pkgName))
}

if strings.HasSuffix(lastPart, "_test") {
return
}

validatePkgName(lastPart)

if len(parts) > 1 {
return
}

if !IsStdlib(pkgPath) {
panic(fmt.Sprintf("cannot create package with invalid path %q", pkgPath))
}
}

var rePkgName = regexp.MustCompile(`^[a-z0-9][a-z0-9_]+$`)

// TODO: consider length restrictions.
// If this function is changed, ReadMemPackage's documentation should be updated accordingly.
Expand Down
285 changes: 285 additions & 0 deletions gnovm/pkg/gnolang/nodes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ package gnolang_test

import (
"math"
"os"
"path/filepath"
"strings"
"testing"

"github.com/gnolang/gno/gnovm/pkg/gnolang"
Expand Down Expand Up @@ -42,3 +45,285 @@ func TestStaticBlock_Define2_MaxNames(t *testing.T) {
// This one should panic because the maximum number of names has been reached.
staticBlock.Define2(false, gnolang.Name("a"), gnolang.BoolType, gnolang.TypedValue{T: gnolang.BoolType})
}

func TestReadMemPackage(t *testing.T) {
t.Parallel()
tests := []struct {
name string
files map[string]string // map[filename]content
pkgPath string
shouldPanic bool
wantPkgName string
}{
{
name: "valid package - math",
files: map[string]string{
"math.gno": `package math
func Add(a, b int) int { return a + b }`,
},
pkgPath: "std/math",
shouldPanic: false,
wantPkgName: "math",
},
{
name: "valid package - bytealg",
files: map[string]string{
"bytealg.gno": `package bytealg
func Compare(a, b []byte) int { return 0 }`,
},
pkgPath: "std/bytealg",
shouldPanic: false,
wantPkgName: "bytealg",
},
{
name: "valid package - sha256",
files: map[string]string{
"sha256.gno": `package sha256
func Sum256(data []byte) [32]byte { var sum [32]byte; return sum }`,
},
pkgPath: "crypto/sha256",
shouldPanic: false,
wantPkgName: "sha256",
},
{
name: "nested package - foo/bar",
files: map[string]string{
"bar.gno": `package bar
func DoSomething() {}`,
},
pkgPath: "gno.land/foo/bar",
shouldPanic: false,
wantPkgName: "bar",
},
{
name: "package with README and LICENSE",
files: map[string]string{
"math.gno": `package math
func Add(a, b int) int { return a + b }`,
"README.md": "# Math Package",
"LICENSE": "MIT License",
},
pkgPath: "std/math",
shouldPanic: false,
wantPkgName: "math",
},
{
name: "stdlib with .go files",
files: map[string]string{
"math.gno": `package math
func Add(a, b int) int { return a + b }`,
"native.go": `package math
func NativeAdd(a, b int) int { return a + b }`,
},
pkgPath: "std/math",
shouldPanic: false,
wantPkgName: "math",
},
{
name: "stdlib with rejected .gen.go files",
files: map[string]string{
"math.gno": `package math
func Add(a, b int) int { return a + b }`,
"generated.gen.go": `package math
func GeneratedFunc() {}`,
},
pkgPath: "std/math",
shouldPanic: false,
wantPkgName: "math",
},
{
name: "valid nested package - gnoland/xxx/foo/bar",
files: map[string]string{
"bar.gno": `package bar
func DoSomething() string { return "hello" }`,
},
pkgPath: "gno.land/xxx/foo/bar",
shouldPanic: false,
wantPkgName: "bar",
},
{
name: "valid package - gno.land/p/demo/tests",
files: map[string]string{
"tests.gno": `package tests
const World = "world"`,
},
pkgPath: "gno.land/p/demo/tests",
shouldPanic: false,
wantPkgName: "tests",
},
{
name: "foo/bar with empty foo directory",
files: map[string]string{
"bar.gno": `package bar
func DoSomething() string { return "hello" }`,
},
pkgPath: "gno.land/r/foo/bar",
shouldPanic: false,
wantPkgName: "bar",
},
{
name: "invalid package - internal",
files: map[string]string{
"internal.gno": `package internal
func someFunc() {}`,
},
pkgPath: "std/internal",
shouldPanic: true,
},
{
name: "invalid package - crypto",
files: map[string]string{
"crypto.gno": `package crypto
func Hash(data []byte) []byte { return nil }`,
},
pkgPath: "std/crypto",
shouldPanic: true,
},
}

for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
tmpDir, err := os.MkdirTemp("", "test-*")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmpDir)

for fname, content := range tt.files {
fpath := filepath.Join(tmpDir, fname)
dir := filepath.Dir(fpath)
if err := os.MkdirAll(dir, 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(fpath, []byte(content), 0o644); err != nil {
t.Fatal(err)
}
}

if tt.shouldPanic {
defer func() {
if r := recover(); r == nil {
t.Errorf("expected panic for package %s", tt.pkgPath)
}
}()
}

memPkg := gnolang.ReadMemPackage(tmpDir, tt.pkgPath)

if !tt.shouldPanic {
if memPkg == nil {
t.Fatal("expected non-nil MemPackage")
}

if memPkg.Name != tt.wantPkgName {
t.Errorf("got package name %q, want %q", memPkg.Name, tt.wantPkgName)
}

if memPkg.Path != tt.pkgPath {
t.Errorf("got package path %q, want %q", memPkg.Path, tt.pkgPath)
}

expectedFiles := 0
for fname := range tt.files {
if strings.HasSuffix(fname, ".gen.go") {
continue
}
if strings.HasSuffix(fname, ".gno") ||
fname == "README.md" ||
fname == "LICENSE" ||
(gnolang.IsStdlib(tt.pkgPath) && strings.HasSuffix(fname, ".go")) {
expectedFiles++
}
}
if len(memPkg.Files) != expectedFiles {
t.Errorf("got %d files, want %d", len(memPkg.Files), expectedFiles)
}
}
})
}
}

func TestInjectNativeMethod(t *testing.T) {
t.Parallel()

tests := []struct {
name string
files map[string]string
pkgPath string
shouldPanic bool
}{
{
name: "inject to valid package",
files: map[string]string{
"foo.gno": `package foo
func RegularFunc() string { return "hello" }`,
},
pkgPath: "gno.land/r/test/foo",
shouldPanic: false,
},
{
name: "inject to invalid package name - crypto",
files: map[string]string{
"crypto.gno": `package crypto
func Hash(data []byte) []byte { return nil }`,
},
pkgPath: "std/crypto",
shouldPanic: true,
},
{
name: "inject to invalid package name - internal",
files: map[string]string{
"internal.gno": `package internal
func someFunc() {}`,
},
pkgPath: "std/internal",
shouldPanic: true,
},
}

for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()

tmpDir, err := os.MkdirTemp("", "test-*")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmpDir)

for fname, content := range tt.files {
fpath := filepath.Join(tmpDir, fname)
dir := filepath.Dir(fpath)
if err := os.MkdirAll(dir, 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(fpath, []byte(content), 0o644); err != nil {
t.Fatal(err)
}
}

defer func() {
if r := recover(); r != nil {
if !tt.shouldPanic {
t.Errorf("unexpected panic: %v", r)
}
} else if tt.shouldPanic {
t.Error("expected panic, but got none")
}
}()

memPkg := gnolang.ReadMemPackage(tmpDir, tt.pkgPath)
fset := gnolang.ParseMemPackage(memPkg)
pkgNode := gnolang.NewPackageNode(gnolang.Name(memPkg.Name), tt.pkgPath, fset)

pkgNode.DefineNative("NativeMethod",
gnolang.FieldTypeExprs{},
gnolang.FieldTypeExprs{},
func(m *gnolang.Machine) {},
)
})
}
}
Loading