Skip to content

Commit 1c6d7f9

Browse files
committed
fix: clang-analyzer errors in script loader
This changes script loader paths to use owned string instead of raw pointers to fix potential memory leaks reported by clang. The tradeof is that now we call c_str in few places, but the impact on performance should be negligible since we call this only once.
1 parent a49cfeb commit 1c6d7f9

File tree

1 file changed

+83
-94
lines changed

1 file changed

+83
-94
lines changed

runtime/script_loader.cpp

Lines changed: 83 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,27 @@
22
#include "encode.h"
33

44
#include <cstdio>
5-
#include <iostream>
65
#include <js/CompilationAndEvaluation.h>
76
#include <js/MapAndSet.h>
87
#include <js/Value.h>
98
#include <jsfriendapi.h>
109
#include <sys/stat.h>
1110

11+
namespace {
12+
1213
static api::Engine* ENGINE;
1314
static ScriptLoader* SCRIPT_LOADER;
15+
static bool MODULE_MODE = true;
16+
static std::string BASE_PATH;
17+
1418
JS::PersistentRootedObject moduleRegistry;
1519
JS::PersistentRootedObject builtinModules;
16-
static bool MODULE_MODE = true;
17-
static char* BASE_PATH = nullptr;
18-
mozilla::Maybe<std::string> PATH_PREFIX = mozilla::Nothing();
1920
JS::CompileOptions *COMPILE_OPTS;
2021

22+
mozilla::Maybe<std::string> PATH_PREFIX = mozilla::Nothing();
23+
24+
} // namespace
25+
2126
using host_api::HostString;
2227

2328
namespace ScriptLoaderErrors {
@@ -40,107 +45,92 @@ class AutoCloseFile {
4045
success = !fclose(file);
4146
}
4247
file = nullptr;
48+
// Clang analyzer complains about the stream leaking when stream is stdin, stdout, or stderr.
49+
// NOLINTNEXTLINE(clang-analyzer-unix.Stream)
4350
return success;
4451
}
4552
};
4653

4754
// strip off the given prefix when possible for nicer debugging stacks
48-
static const char* strip_prefix(const char* resolved_path,
55+
static std::string strip_prefix(std::string_view resolved_path,
4956
mozilla::Maybe<std::string> path_prefix) {
5057
if (!path_prefix) {
51-
return strdup(resolved_path);
58+
return std::string(resolved_path);
5259
}
53-
auto& base = *path_prefix;
54-
if (strncmp(resolved_path, base.data(), base.length()) != 0) {
55-
return strdup(resolved_path);
60+
61+
const auto& base = *path_prefix;
62+
if (resolved_path.rfind(base, 0) != 0) {
63+
return std::string(resolved_path);
5664
}
57-
size_t path_len = strlen(resolved_path);
58-
char* buf(js_pod_malloc<char>(path_len - base.length() + 1));
59-
strncpy(buf, &resolved_path[base.length()], path_len - base.length() + 1);
60-
return buf;
65+
66+
return std::string(resolved_path.substr(base.size()));
6167
}
6268

6369
struct stat s;
6470

65-
static const char* resolve_extension(const char* resolved_path) {
66-
if (stat(resolved_path, &s) == 0) {
71+
72+
static std::string resolve_extension(std::string resolved_path) {
73+
if (stat(resolved_path.c_str(), &s) == 0) {
6774
return resolved_path;
6875
}
69-
size_t len = strlen(resolved_path);
70-
if (strncmp(resolved_path + len - 3, ".js", 3) == 0) {
76+
77+
if (resolved_path.size() >= 3 &&
78+
resolved_path.compare(resolved_path.size() - 3, 3, ".js") == 0) {
7179
return resolved_path;
7280
}
73-
char* resolved_path_ext = new char[len + 4];
74-
strncpy(resolved_path_ext, resolved_path, len);
75-
strncpy(resolved_path_ext + len, ".js", 4);
76-
MOZ_ASSERT(strlen(resolved_path_ext) == len + 3);
77-
if (stat(resolved_path_ext, &s) == 0) {
78-
delete[] resolved_path;
79-
return resolved_path_ext;
81+
82+
std::string with_ext = resolved_path + ".js";
83+
if (stat(with_ext.c_str(), &s) == 0) {
84+
return with_ext;
8085
}
81-
delete[] resolved_path_ext;
8286
return resolved_path;
8387
}
8488

85-
static const char* resolve_path(const char* path, const char* base, size_t base_len) {
86-
MOZ_ASSERT(base);
87-
while (base_len > 0 && base[base_len - 1] != '/') {
88-
base_len--;
89+
static std::string resolve_path(std::string_view path,
90+
std::string_view base) {
91+
// Prepare initial resolved (with trailing '/')
92+
std::string resolved;
93+
if (!path.empty() && path[0] == '/') {
94+
// absolute path
95+
} else {
96+
resolved.assign(base);
97+
auto slash = resolved.find_last_of('/');
98+
if (slash != std::string::npos) {
99+
resolved.resize(slash + 1);
100+
}
89101
}
90-
size_t path_len = strlen(path);
91-
92-
// create the maximum buffer size as a working buffer
93-
char* resolved_path = new char[base_len + path_len + 1];
94102

95-
// copy the base in if used
96-
size_t resolved_len = base_len;
97-
if (path[0] == '/') {
98-
resolved_len = 0;
99-
} else {
100-
strncpy(resolved_path, base, base_len);
103+
// Split into segments
104+
std::vector<std::string_view> parts;
105+
size_t start = 0;
106+
while (start <= path.size()) {
107+
auto end = path.find('/', start);
108+
if (end == std::string_view::npos) end = path.size();
109+
parts.emplace_back(path.substr(start, end - start));
110+
start = end + 1;
101111
}
102112

103113
// Iterate through each segment of the path, copying each segment into the resolved path,
104114
// while handling backtracking for .. segments and skipping . segments appropriately.
105-
size_t path_from_idx = 0;
106-
size_t path_cur_idx = 0;
107-
while (path_cur_idx < path_len) {
108-
// read until the end or the next / to get the segment position
109-
// as the substring between path_from_idx and path_cur_idx
110-
while (path_cur_idx < path_len && path[path_cur_idx] != '/')
111-
path_cur_idx++;
112-
if (path_cur_idx == path_from_idx)
113-
break;
114-
// . segment to skip
115-
if (path_cur_idx - path_from_idx == 1 && path[path_from_idx] == '.') {
116-
path_cur_idx++;
117-
path_from_idx = path_cur_idx;
115+
std::string result = resolved;
116+
for (auto seg : parts) {
117+
if (seg.empty() || seg == ".") {
118118
continue;
119119
}
120-
// .. segment backtracking
121-
if (path_cur_idx - path_from_idx == 2 && path[path_from_idx] == '.' && path[path_from_idx + 1] == '.') {
122-
path_cur_idx++;
123-
path_from_idx = path_cur_idx;
124-
if (resolved_len > 0 && resolved_path[resolved_len - 1] == '/') {
125-
resolved_len --;
120+
if (seg == "..") {
121+
auto pos = result.find_last_of('/');
122+
if (pos != std::string::npos) {
123+
result.resize(pos);
126124
}
127-
while (resolved_len > 0 && resolved_path[resolved_len - 1] != '/') {
128-
resolved_len--;
125+
} else {
126+
if (!result.empty() && result.back() != '/') {
127+
result.push_back('/');
129128
}
130-
continue;
129+
result.append(seg);
131130
}
132-
// normal segment to copy (with the trailing / if not the last segment)
133-
if (path[path_cur_idx] == '/')
134-
path_cur_idx++;
135-
strncpy(resolved_path + resolved_len, path + path_from_idx, path_cur_idx - path_from_idx);
136-
resolved_len += path_cur_idx - path_from_idx;
137-
path_from_idx = path_cur_idx;
138-
}
139-
140-
// finalize the buffer
141-
resolved_path[resolved_len] = '\0';
142-
MOZ_ASSERT(strlen(resolved_path) == resolved_len);
143-
return resolve_extension(resolved_path);
131+
}
132+
133+
return resolve_extension(result);
144134
}
145135

146136
static JSObject* get_module(JSContext* cx, JS::SourceText<mozilla::Utf8Unit> &source,
@@ -178,11 +168,12 @@ static JSObject* get_module(JSContext* cx, JS::SourceText<mozilla::Utf8Unit> &so
178168
static JSObject* get_module(JSContext* cx, const char* specifier, const char* resolved_path,
179169
const JS::CompileOptions &opts) {
180170
RootedString resolved_path_str(cx, JS_NewStringCopyZ(cx, resolved_path));
171+
181172
if (!resolved_path_str) {
182173
return nullptr;
183174
}
184-
RootedValue resolved_path_val(cx, StringValue(resolved_path_str));
185175

176+
RootedValue resolved_path_val(cx, StringValue(resolved_path_str));
186177
RootedValue module_val(cx);
187178
if (!JS::MapGet(cx, moduleRegistry, resolved_path_val, &module_val)) {
188179
return nullptr;
@@ -327,11 +318,12 @@ JSObject* module_resolve_hook(JSContext* cx, HandleValue referencingPrivate,
327318
}
328319

329320
HostString str = core::encode(cx, parent_path_val);
330-
const char* resolved_path = resolve_path(path.get(), str.ptr.get(), str.len);
321+
auto resolved_path = resolve_path(path.get(), str.ptr.get());
331322

332323
JS::CompileOptions opts(cx, *COMPILE_OPTS);
333-
opts.setFileAndLine(strip_prefix(resolved_path, PATH_PREFIX), 1);
334-
return get_module(cx, path.get(), resolved_path, opts);
324+
auto stripped = strip_prefix(resolved_path, PATH_PREFIX);
325+
opts.setFileAndLine(stripped.c_str(), 1);
326+
return get_module(cx, path.get(), resolved_path.c_str(), opts);
335327
}
336328

337329
bool module_metadata_hook(JSContext* cx, HandleValue referencingPrivate, HandleObject metaObject) {
@@ -432,34 +424,31 @@ bool ScriptLoader::load_resolved_script(JSContext *cx, const char *specifier,
432424

433425
bool ScriptLoader::load_script(JSContext *cx, const char *script_path,
434426
JS::SourceText<mozilla::Utf8Unit> &script) {
435-
const char *resolved_path;
436-
if (!BASE_PATH) {
437-
auto last_slash = strrchr(script_path, '/');
438-
size_t base_len;
439-
if (last_slash) {
440-
last_slash++;
441-
base_len = last_slash - script_path;
442-
BASE_PATH = new char[base_len + 1];
443-
MOZ_ASSERT(BASE_PATH);
444-
strncpy(BASE_PATH, script_path, base_len);
445-
BASE_PATH[base_len] = '\0';
427+
428+
std::string path(script_path);
429+
std::string resolved;
430+
if (BASE_PATH.empty()) {
431+
auto pos = path.find_last_of('/');
432+
if (pos != std::string::npos) {
433+
BASE_PATH = path.substr(0, pos + 1);
446434
} else {
447-
BASE_PATH = strdup("./");
435+
BASE_PATH = "./";
448436
}
449-
resolved_path = script_path;
437+
resolved = path;
450438
} else {
451-
resolved_path = resolve_path(script_path, BASE_PATH, strlen(BASE_PATH));
439+
resolved = resolve_path(path, BASE_PATH);
452440
}
453-
454-
return load_resolved_script(cx, script_path, resolved_path, script);
441+
return load_resolved_script(cx, script_path, resolved.c_str(), script);
455442
}
456443

457444
bool ScriptLoader::eval_top_level_script(const char *path, JS::SourceText<mozilla::Utf8Unit> &source,
458445
MutableHandleValue result, MutableHandleValue tla_promise) {
459446
JSContext *cx = ENGINE->cx();
460447

461448
JS::CompileOptions opts(cx, *COMPILE_OPTS);
462-
opts.setFileAndLine(strip_prefix(path, PATH_PREFIX), 1);
449+
auto stripped = strip_prefix(path, PATH_PREFIX);
450+
opts.setFileAndLine(stripped.c_str(), 1);
451+
463452
JS::RootedScript script(cx);
464453
RootedObject module(cx);
465454
if (MODULE_MODE) {

0 commit comments

Comments
 (0)