diff --git a/src/engines/v8/generate.zig b/src/engines/v8/generate.zig index 7dcfbc9..9dec573 100644 --- a/src/engines/v8/generate.zig +++ b/src/engines/v8/generate.zig @@ -210,7 +210,6 @@ fn getArgs( // take all trailing JS arg as variadic members const rest_nb = js_args_nb - i + func.index_offset; const slice = alloc.alloc(arg_real.T, rest_nb) catch unreachable; - // TODO: alloc.free slice after func call var iter: usize = 0; while (iter < rest_nb) { const slice_value = getArg( @@ -237,6 +236,25 @@ fn getArgs( return args; } +fn freeArgs(comptime func: refl.Func, obj: anytype) !void { + inline for (func.args) |arg_T| { + + // free char slices + // the API functions will be responsible of copying the slice + // in their implementations if they want to keep it afterwards + if (arg_T.underT() == []u8 or arg_T.underT() == []const u8) { + utils.allocator.free(@field(obj, arg_T.name.?)); + } + + // free varidadic slices + if (try refl.Type.variadic(arg_T.underT()) != null) { + const val = @field(obj, arg_T.name.?).?; + // NOTE: variadic are optional by design + utils.allocator.free(@field(val, "slice")); + } + } +} + pub fn setNativeObject( alloc: std.mem.Allocator, comptime T_refl: refl.Struct, @@ -473,6 +491,9 @@ fn generateConstructor( isolate, ) catch unreachable; // TODO: we choose for now not throw user error + + // free memory if required + freeArgs(func, args) catch unreachable; } }.constructor; } @@ -530,6 +551,9 @@ fn generateGetter( return throwError(@errorName(err), info.getReturnValue(), isolate); }; info.getReturnValue().setValueHandle(js_val.handle); + + // free memory if required + freeArgs(func, args) catch unreachable; } }.getter; } @@ -603,6 +627,9 @@ fn generateSetter( // return to javascript the provided value info.getReturnValue().setValueHandle(raw_value.?); + + // free memory if required + freeArgs(func, args) catch unreachable; } }.setter; } @@ -665,6 +692,9 @@ fn generateMethod( }; info.getReturnValue().setValueHandle(js_val.handle); + // free memory if required + freeArgs(func, args) catch unreachable; + // sync callback // for test purpose, does not have any sense in real case if (comptime func.callback_index != null) { diff --git a/src/tests/proto_test.zig b/src/tests/proto_test.zig index f777333..04786b7 100644 --- a/src/tests/proto_test.zig +++ b/src/tests/proto_test.zig @@ -21,10 +21,16 @@ const Person = struct { pub const _AGE_MIN = 18; pub const _NATIONALITY = "French"; - pub fn constructor(_: std.mem.Allocator, first_name: []u8, last_name: []u8, age: u32) Person { + pub fn constructor(alloc: std.mem.Allocator, first_name: []u8, last_name: []u8, age: u32) Person { + + // alloc last_name slice to keep them after function returns + // NOTE: we do not alloc first_name on purpose to check freeArgs + const last_name_alloc = alloc.alloc(u8, last_name.len) catch unreachable; + @memcpy(last_name_alloc, last_name); + return .{ .first_name = first_name, - .last_name = last_name, + .last_name = last_name_alloc, .age = age, }; } @@ -47,6 +53,10 @@ const Person = struct { Person.allocTest(alloc) catch unreachable; } + pub fn get_nonAllocFirstName(self: Person) []const u8 { + return self.first_name; + } + pub fn set_age(self: *Person, age: u32) void { self.age = age; } @@ -91,8 +101,11 @@ const PersonPtr = struct { name: []u8, pub fn constructor(alloc: std.mem.Allocator, name: []u8) *PersonPtr { + const name_alloc = alloc.alloc(u8, name.len) catch unreachable; + @memcpy(name_alloc, name); + var person_ptr = alloc.create(PersonPtr) catch unreachable; - person_ptr.* = .{ .name = name }; + person_ptr.* = .{ .name = name_alloc }; return person_ptr; } @@ -100,8 +113,10 @@ const PersonPtr = struct { return self.name; } - pub fn set_name(self: *PersonPtr, name: []u8) void { - self.name = name; + pub fn set_name(self: *PersonPtr, alloc: std.mem.Allocator, name: []u8) void { + const name_alloc = alloc.alloc(u8, name.len) catch unreachable; + @memcpy(name_alloc, name); + self.name = name_alloc; } }; @@ -160,8 +175,10 @@ const PersonProtoCast = struct { return @ptrCast(child_ptr); } - pub fn constructor(first_name: []u8) PersonProtoCast { - return .{ .first_name = first_name }; + pub fn constructor(alloc: std.mem.Allocator, first_name: []u8) PersonProtoCast { + const first_name_alloc = alloc.alloc(u8, first_name.len) catch unreachable; + @memcpy(first_name_alloc, first_name); + return .{ .first_name = first_name_alloc }; } pub fn get_name(self: PersonProtoCast) []const u8 { @@ -174,8 +191,8 @@ const UserProtoCast = struct { pub const prototype = *PersonProtoCast; - pub fn constructor(first_name: []u8) UserProtoCast { - return .{ .not_proto = PersonProtoCast.constructor(first_name) }; + pub fn constructor(alloc: std.mem.Allocator, first_name: []u8) UserProtoCast { + return .{ .not_proto = PersonProtoCast.constructor(alloc, first_name) }; } }; @@ -239,6 +256,9 @@ pub fn exec( var cases2 = [_]tests.Case{ .{ .src = "p.age === 40", .ex = "true" }, .{ .src = "p.allocator", .ex = "true" }, + // first name has not been allocated, so it's a normal behavior + // here we check that freeArgs works well + .{ .src = "p.nonAllocFirstName !== 'Francis'", .ex = "true" }, }; try tests.checkCases(js_env, &cases2); diff --git a/src/tests/types_native_test.zig b/src/tests/types_native_test.zig index e9acd41..3bdf449 100644 --- a/src/tests/types_native_test.zig +++ b/src/tests/types_native_test.zig @@ -10,16 +10,20 @@ const tests = public.test_utils; const Brand = struct { name: []const u8, - pub fn constructor(name: []const u8) Brand { - return .{ .name = name }; + pub fn constructor(alloc: std.mem.Allocator, name: []const u8) Brand { + const name_alloc = alloc.alloc(u8, name.len) catch unreachable; + @memcpy(name_alloc, name); + return .{ .name = name_alloc }; } pub fn get_name(self: Brand) []const u8 { return self.name; } - pub fn set_name(self: *Brand, name: []u8) void { - self.name = name; + pub fn set_name(self: *Brand, alloc: std.mem.Allocator, name: []u8) void { + const name_alloc = alloc.alloc(u8, name.len) catch unreachable; + @memcpy(name_alloc, name); + self.name = name_alloc; } };