Compare commits

...

1 Commits

Author SHA1 Message Date
Don Isaac
a634127dca fix(napi): memory leak when creating strings 2025-02-13 13:23:41 -08:00
4 changed files with 86 additions and 14 deletions

View File

@@ -340,16 +340,9 @@ pub export fn napi_create_string_latin1(env: napi_env, str: ?[*]const u8, length
log("napi_create_string_latin1: {s}", .{slice});
if (slice.len == 0) {
result.set(env, bun.String.empty.toJS(env.toJS()));
return env.ok();
}
var string, const bytes = bun.String.createUninitialized(.latin1, slice.len);
var string = bun.String.createWTF(.latin1, slice) catch bun.outOfMemory();
defer string.deref();
@memcpy(bytes, slice);
result.set(env, string.toJS(env.toJS()));
return env.ok();
}
@@ -401,12 +394,8 @@ pub export fn napi_create_string_utf16(env: napi_env, str: ?[*]const char16_t, l
if (comptime bun.Environment.allow_assert)
log("napi_create_string_utf16: {d} {any}", .{ slice.len, bun.fmt.FormatUTF16{ .buf = slice[0..@min(slice.len, 512)] } });
if (slice.len == 0) {
result.set(env, bun.String.empty.toJS(env.toJS()));
}
var string, const chars = bun.String.createUninitialized(.utf16, slice.len);
@memcpy(chars, slice);
var string = bun.String.createWTF(.utf16, slice) catch bun.outOfMemory();
defer string.deref();
result.set(env, string.transferToJS(env.toJS()));
return env.ok();

View File

@@ -433,6 +433,28 @@ pub const String = extern struct {
};
}
/// Allocate memory for a WTF::String of a given length and encoding, and
/// return the string and a mutable slice for that string.
///
/// Provides `String.empty` when `bytes` is empty.
///
/// ## Memory Characteristics
/// - Usually allocates. No allocation for empty `bytes`.
pub fn createWTF(
comptime kind: WTFStringEncoding,
bytes: []const WTFStringEncoding.Byte(kind),
) OOM!String {
if (bytes.len == 0) {
@branchHint(.unlikely);
return String.empty;
}
const str = switch (comptime kind) {
.latin1 => createLatin1(bytes),
.utf16 => createUTF16(bytes),
};
return str.oomIfDead();
}
pub fn createLatin1(bytes: []const u8) String {
JSC.markBinding(@src());
if (bytes.len == 0) return String.empty;
@@ -450,6 +472,13 @@ pub const String = extern struct {
return this;
}
pub inline fn oomIfDead(this: String) OOM!String {
if (this.tag == .Dead) {
@branchHint(.unlikely);
return error.OutOfMemory;
}
return this;
}
pub fn createUTF8(bytes: []const u8) String {
return JSC.WebCore.Encoder.toBunStringComptime(bytes, .utf8);

View File

@@ -6,6 +6,7 @@
#include <cassert>
#include <cinttypes>
#include <cmath>
#include <codecvt>
#include <cstdarg>
#include <cstdint>
#include <cstdio>
@@ -155,6 +156,53 @@ test_napi_get_value_string_utf8_with_buffer(const Napi::CallbackInfo &info) {
return ok(env);
}
napi_value test_napi_create_string_utf16(const Napi::CallbackInfo &info) {
const size_t BUFSIZE = 256;
Napi::Env env = info.Env();
napi_value *result = nullptr;
// test with a string that will definitely end up utf16-encoded.
std::u16string cpp_str_utf16 = u"Bun's napi support is 👌";
assert(napi_create_string_utf16(env, cpp_str_utf16.data(),
cpp_str_utf16.length(), result) == napi_ok);
assert(result != nullptr);
char16_t utf16buf[BUFSIZE];
size_t result_len = 0;
assert(napi_get_value_string_utf16(env, *result, utf16buf, BUFSIZE,
&result_len) == napi_ok);
assert(result_len == cpp_str_utf16.length());
std::u16string recast_str_utf16(utf16buf, result_len);
assert(recast_str_utf16 == cpp_str_utf16);
delete result;
// since this string is all ASCII, it should end up Latin-1 encoded.
result = nullptr;
std::u16string cpp_str_latin1 = u"Bun's napi support is awesome";
assert(napi_create_string_utf16(env, cpp_str_latin1.data(),
cpp_str_latin1.length(), result) == napi_ok);
assert(result != nullptr);
char latin1buf[BUFSIZE];
result_len = 0;
assert(napi_get_value_string_latin1(env, *result, latin1buf, BUFSIZE,
&result_len) == napi_ok);
// stop
assert(result_len == cpp_str_latin1.length());
std::string recast_str_latin1(latin1buf, result_len);
assert(recast_str_latin1 == "Bun's napi support is awesome");
return ok(env);
}
napi_value test_napi_handle_scope_string(const Napi::CallbackInfo &info) {
// this is mostly a copy of test_handle_scope_gc from
// test/v8/v8-module/main.cpp -- see comments there for explanation

View File

@@ -178,6 +178,12 @@ describe("napi", () => {
});
});
describe("napi_create_string_*", () => {
it("ut16 strings", () => {
checkSameOutput("test_napi_create_string_utf16");
});
});
it("#1288", async () => {
const result = checkSameOutput("self", []);
expect(result).toBe("hello world!");