From 27829d56cd897ed1eb25638d0b113320a24e7768 Mon Sep 17 00:00:00 2001 From: Gonzalo Chumillas Date: Sun, 11 May 2025 08:57:28 +0100 Subject: [PATCH] jsgen: cannot assign unsafe nil values (fix #24407, #24436) (#24458) --- vlib/v/gen/js/builtin_types.v | 14 +++++++ vlib/v/gen/js/js.v | 5 ++- vlib/v/gen/js/tests/unsafe.v | 74 +++++++++++++++++++++++++++++++++++ 3 files changed, 91 insertions(+), 2 deletions(-) create mode 100644 vlib/v/gen/js/tests/unsafe.v diff --git a/vlib/v/gen/js/builtin_types.v b/vlib/v/gen/js/builtin_types.v index 06a0265b2b..fae8c75e18 100644 --- a/vlib/v/gen/js/builtin_types.v +++ b/vlib/v/gen/js/builtin_types.v @@ -329,6 +329,20 @@ fn (mut g JsGen) gen_builtin_prototype(c BuiltinPrototypeConfig) { g.writeln('function ${c.typ_name}__eq(self,other) { return ${c.eq}; } ') } +fn (mut g JsGen) gen_nil_const() { + g.writeln('const nil__ = new \$ref(new nil());') + g.gen_builtin_prototype( + typ_name: 'nil' + val_name: 'str' + default_value: 'new String("&nil")' + constructor: 'this.str = str.toString(); this.len = this.str.length' + value_of: 'null' + to_string: '"&nil"' + eq: 'new bool(self.valueOf() === other.valueOf())' + to_jsval: 'null' + ) +} + // generate builtin type definitions, used for casting and methods. fn (mut g JsGen) gen_builtin_type_defs() { g.inc_indent() diff --git a/vlib/v/gen/js/js.v b/vlib/v/gen/js/js.v index 8463bfa72f..f4b8fcc605 100644 --- a/vlib/v/gen/js/js.v +++ b/vlib/v/gen/js/js.v @@ -143,6 +143,7 @@ pub fn gen(files []&ast.File, mut table ast.Table, pref_ &pref.Preferences) stri graph.add(g.file.mod.name, imports) // builtin types if g.file.mod.name == 'builtin' && !g.generated_builtin { + g.gen_nil_const() g.gen_builtin_type_defs() g.writeln('Object.defineProperty(array.prototype,"len", { get: function() {return new int(this.arr.arr.length);}, set: function(l) { this.arr.arr.length = l.valueOf(); } }); ') g.writeln('Object.defineProperty(map.prototype,"len", { get: function() {return new int(this.length);}, set: function(l) { } }); ') @@ -965,7 +966,7 @@ fn (mut g JsGen) expr(node_ ast.Expr) { g.gen_lock_expr(node) } ast.Nil { - g.write('null') + g.write('nil__') } ast.NodeError {} ast.None { @@ -1345,7 +1346,7 @@ fn (mut g JsGen) gen_assign_stmt(stmt ast.AssignStmt, semicolon bool) { g.expr(left) } - is_ptr := stmt.op == .assign && stmt.left_types[i].is_ptr() && !array_set + is_ptr := stmt.op == .assign && stmt.right_types[i].is_ptr() && !array_set if is_ptr { g.write('.val') } diff --git a/vlib/v/gen/js/tests/unsafe.v b/vlib/v/gen/js/tests/unsafe.v new file mode 100644 index 0000000000..2aa2035e43 --- /dev/null +++ b/vlib/v/gen/js/tests/unsafe.v @@ -0,0 +1,74 @@ +struct Bar { + value int +} + +struct Foo1 { +mut: + name &string +} + +struct Foo2 { +mut: + name ?&string +} + +struct Foo3 { +mut: + bar &Bar +} + +fn main() { + // Basic test of assigning nil to a string pointer + mut str := 'hi!' + mut str_ptr := &str + unsafe { + str_ptr = nil + } + println(str_ptr) // should print '&nil' + assert str_ptr == unsafe { nil } + + // Test initializing a pointer field with nil + f1 := Foo1{ + name: unsafe { nil } + } + assert f1.name == unsafe { nil } + + // Test assigning nil to an optional pointer field + mut f2 := Foo2{} + unsafe { + f2.name = nil + } + if f2.name != none { + assert f2.name == unsafe { nil } + } else { + assert false + } + + // Test assigning nil to a struct pointer + mut f3 := &Foo2{} + unsafe { + f3 = nil + } + assert f3 == unsafe { nil } + + // Test with custom struct fields + mut f4 := Foo3{ + bar: &Bar{42} + } + unsafe { + f4.bar = nil + } + assert f4.bar == unsafe { nil } + + // Test with nil pointers in arrays + mut ptrs := []&string{len: 3, init: unsafe { nil }} + p0, p1 := 'hello', 'world' + ptrs[0] = &p0 + ptrs[1] = &p1 + unsafe { + ptrs[2] = nil + } + assert ptrs[0] != unsafe { nil } + assert ptrs[1] != unsafe { nil } + assert ptrs[2] == unsafe { nil } +}