Skip to content

Commit eab35bb

Browse files
committed
Harden GC memory safety, optimize bytecode dispatch, and update build scripts
Add RAII root guards, thread safe spawn cloning, zero copy string refs, and CMake and install fixes
1 parent 8d6cc3b commit eab35bb

11 files changed

Lines changed: 189 additions & 42 deletions

File tree

‎CMakeLists.txt‎

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,16 @@ else()
1818
add_compile_options(-Wall -Wextra -Wno-unused-parameter -Wno-unused-variable)
1919
endif()
2020

21+
option(SHL_ENABLE_ASAN "Enable AddressSanitizer" OFF)
22+
if(SHL_ENABLE_ASAN)
23+
if(MSVC)
24+
add_compile_options(/fsanitize=address)
25+
else()
26+
add_compile_options(-fsanitize=address,undefined)
27+
add_link_options(-fsanitize=address,undefined)
28+
endif()
29+
endif()
30+
2131
include(FetchContent)
2232

2333
# ------------------------------------------------------------------------------
@@ -176,30 +186,40 @@ set(SOURCES
176186
# ------------------------------------------------------------------------------
177187
# 3. Executable and Library Targets
178188
# ------------------------------------------------------------------------------
179-
add_executable(shell_lite_exec ${SOURCES})
189+
add_library(shell_lite_core OBJECT ${SOURCES})
190+
set_target_properties(shell_lite_core PROPERTIES POSITION_INDEPENDENT_CODE ON)
180191

181-
target_include_directories(shell_lite_exec PRIVATE
192+
target_include_directories(shell_lite_core PUBLIC
182193
shell_lite
183194
${sqlite3_SOURCE_DIR}
184195
${imgui_SOURCE_DIR}
185196
${imgui_SOURCE_DIR}/backends
186197
)
198+
target_link_libraries(shell_lite_core PUBLIC libcurl clip SDL2-static httplib zip SQLite3)
199+
if(WIN32)
200+
target_link_libraries(shell_lite_core PUBLIC ws2_32 user32 gdi32)
201+
endif()
187202

203+
add_executable(shell_lite_exec $<TARGET_OBJECTS:shell_lite_core>)
204+
target_include_directories(shell_lite_exec PRIVATE
205+
shell_lite
206+
${sqlite3_SOURCE_DIR}
207+
${imgui_SOURCE_DIR}
208+
${imgui_SOURCE_DIR}/backends
209+
)
188210
target_link_libraries(shell_lite_exec PRIVATE libcurl clip SDL2-static httplib zip SQLite3)
189211

190212
if(WIN32)
191213
target_link_libraries(shell_lite_exec PRIVATE ws2_32 user32 gdi32)
192214
endif()
193215

194-
add_library(shell_lite_lib SHARED ${SOURCES})
195-
216+
add_library(shell_lite_lib SHARED $<TARGET_OBJECTS:shell_lite_core>)
196217
target_include_directories(shell_lite_lib PRIVATE
197218
shell_lite
198219
${sqlite3_SOURCE_DIR}
199220
${imgui_SOURCE_DIR}
200221
${imgui_SOURCE_DIR}/backends
201222
)
202-
203223
target_link_libraries(shell_lite_lib PRIVATE libcurl clip SDL2-static httplib zip SQLite3)
204224

205225
if(WIN32)

‎install.ps1‎

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,56 @@
11
$InstallDir = "$env:LOCALAPPDATA\ShellLite"
22
$ExePath = Join-Path $PSScriptRoot "shlcpp.exe"
33
if (-not (Test-Path $ExePath)) {
4-
Write-Error "shlcpp.exe not found in current directory. Please run this after building."
5-
exit
4+
if (Test-Path (Join-Path $PSScriptRoot "build_cpp\Release\shell_lite_exec.exe")) {
5+
$ExePath = Join-Path $PSScriptRoot "build_cpp\Release\shell_lite_exec.exe"
6+
} elseif (Test-Path (Join-Path $PSScriptRoot "build_cpp\Release\shlcpp.exe")) {
7+
$ExePath = Join-Path $PSScriptRoot "build_cpp\Release\shlcpp.exe"
8+
} else {
9+
Write-Error "shlcpp.exe not found. Please place or build shlcpp.exe first."
10+
exit 1
11+
}
612
}
713
Write-Host "Installing ShellLite to $InstallDir..."
814
if (-not (Test-Path $InstallDir)) {
9-
New-Item -ItemType Directory -Path $InstallDir
15+
New-Item -ItemType Directory -Path $InstallDir | Out-Null
1016
}
11-
Copy-Item $ExePath -Destination $InstallDir -Force
17+
Copy-Item $ExePath -Destination (Join-Path $InstallDir "shlcpp.exe") -Force
1218
Write-Host "ShellLite binary copied."
19+
20+
$DllPath = Join-Path $PSScriptRoot "shell_lite_lib.dll"
21+
if (-not (Test-Path $DllPath) -and (Test-Path (Join-Path $PSScriptRoot "build_cpp\Release\shell_lite_lib.dll"))) {
22+
$DllPath = Join-Path $PSScriptRoot "build_cpp\Release\shell_lite_lib.dll"
23+
}
24+
if (Test-Path $DllPath) {
25+
Copy-Item $DllPath -Destination $InstallDir -Force
26+
Write-Host "ShellLite shared library copied."
27+
}
28+
1329
$StdLibPath = Join-Path $PSScriptRoot "shell_lite\stdlib"
1430
if (Test-Path $StdLibPath) {
31+
$DestStdLib = Join-Path $InstallDir "stdlib"
32+
if (Test-Path $DestStdLib) {
33+
Remove-Item $DestStdLib -Recurse -Force
34+
}
1535
Copy-Item -Path $StdLibPath -Destination $InstallDir -Recurse -Force
1636
Write-Host "ShellLite standard library copied."
1737
}
1838
$UserPath = [Environment]::GetEnvironmentVariable("Path", "User")
19-
if ($UserPath -notlike "*$InstallDir*") {
39+
$PathEntries = if ($UserPath) { ($UserPath -split ';') | Where-Object { $_ } } else { @() }
40+
if ($PathEntries -notcontains $InstallDir) {
2041
Write-Host "Adding $InstallDir to User PATH..."
21-
[Environment]::SetEnvironmentVariable("Path", $UserPath + ";" + $InstallDir, "User")
42+
$NewPath = if ($UserPath) { "$UserPath;$InstallDir" } else { $InstallDir }
43+
[Environment]::SetEnvironmentVariable("Path", $NewPath, "User")
2244
Write-Host "PATH updated. Please restart your terminal."
2345
} else {
2446
Write-Host "ShellLite is already in your PATH."
2547
}
48+
49+
$UserPathext = [Environment]::GetEnvironmentVariable("PATHEXT", "User")
50+
$PathextEntries = if ($UserPathext) { ($UserPathext -split ';') | Where-Object { $_ } } else { @() }
51+
if ($PathextEntries -notcontains ".SHL") {
52+
$NewPathext = if ($UserPathext) { "$UserPathext;.SHL" } else { ".SHL" }
53+
[Environment]::SetEnvironmentVariable("PATHEXT", $NewPathext, "User")
54+
}
55+
2656
Write-Host "`nInstallation complete! You can now run 'shlcpp' from any terminal." -ForegroundColor Green

‎shell_lite/arena.hpp‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ namespace shell_lite {
1212

1313
class Arena {
1414
public:
15-
static constexpr size_t DEFAULT_ARENA_CHUNK_SIZE = 1024 * 1024; // 1MB default chunk size
15+
static constexpr size_t DEFAULT_ARENA_CHUNK_SIZE = 64 * 1024;
1616

1717
explicit Arena(size_t chunk_size = DEFAULT_ARENA_CHUNK_SIZE) : chunk_size_(chunk_size) {
1818
alloc_chunk();
@@ -80,6 +80,7 @@ class Arena {
8080
chunks_.push_back(chunk);
8181
current_ptr_ = chunk;
8282
end_ptr_ = chunk + cap;
83+
chunk_size_ = (std::min)(chunk_size_ * 2, static_cast<size_t>(1024 * 1024));
8384
}
8485

8586
size_t remaining_capacity() const {

‎shell_lite/compiler.cpp‎

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,15 @@ struct CompilerState {
2828
function->name = name;
2929
function->source_file = source_file;
3030
locals.push_back({"", 0});
31+
if (vm && function) {
32+
vm->arena().push_temp_root(function);
33+
}
34+
}
35+
36+
~CompilerState() {
37+
if (vm && function) {
38+
vm->arena().remove_temp_root(function);
39+
}
3140
}
3241
};
3342

@@ -433,10 +442,11 @@ class ProperCompiler : public Visitor {
433442
func->upvalue_count = (int)state->upvalues.size();
434443
std::vector<Upvalue> uvs = state->upvalues;
435444
state = old;
445+
uint16_t const_idx = make_constant(Value(func));
436446
delete sub;
437447

438448
emit_byte(OP_CLOSURE);
439-
emit_short(make_constant(Value(func)));
449+
emit_short(const_idx);
440450
for (auto &uv : uvs) {
441451
emit_byte(uv.is_local ? 1 : 0);
442452
emit_short(uv.index);
@@ -1035,9 +1045,10 @@ class ProperCompiler : public Visitor {
10351045
func->upvalue_count = (int)state->upvalues.size();
10361046
std::vector<Upvalue> uvs = state->upvalues;
10371047
state = old;
1048+
uint16_t const_idx = make_constant(Value(func));
10381049
delete sub;
10391050
emit_byte(OP_CLOSURE);
1040-
emit_short(make_constant(Value(func)));
1051+
emit_short(const_idx);
10411052
for (auto &uv : uvs) {
10421053
emit_byte(uv.is_local ? 1 : 0);
10431054
emit_short(uv.index);
@@ -1243,10 +1254,11 @@ class ProperCompiler : public Visitor {
12431254
func->upvalue_count = (int)state->upvalues.size();
12441255
std::vector<Upvalue> uvs = state->upvalues;
12451256
state = old;
1257+
uint16_t const_idx = make_constant(Value(func));
12461258
delete sub;
12471259

12481260
emit_byte(OP_CLOSURE);
1249-
emit_short(make_constant(Value(func)));
1261+
emit_short(const_idx);
12501262
for (auto &uv : uvs) {
12511263
emit_byte(uv.is_local ? 1 : 0);
12521264
emit_short(uv.index);

‎shell_lite/gc.cpp‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,23 @@ void GCArena::transfer_to(GCArena& target) {
106106
strings_.clear();
107107
}
108108

109+
void GCArena::push_temp_root(GCObject* obj) {
110+
if (!obj) return;
111+
std::lock_guard<std::recursive_mutex> lock(gc_mutex_);
112+
temp_roots_.push_back(obj);
113+
}
114+
115+
void GCArena::remove_temp_root(GCObject* obj) {
116+
if (!obj) return;
117+
std::lock_guard<std::recursive_mutex> lock(gc_mutex_);
118+
for (auto it = temp_roots_.rbegin(); it != temp_roots_.rend(); ++it) {
119+
if (*it == obj) {
120+
temp_roots_.erase((it + 1).base());
121+
return;
122+
}
123+
}
124+
}
125+
109126
static size_t get_object_live_bytes(GCObject* obj) {
110127
if (!obj) return 0;
111128
size_t sz = get_object_size(obj);
@@ -163,6 +180,12 @@ void GCArena::collect_internal() {
163180
vm_->mark_roots();
164181
}
165182

183+
for (GCObject* root : temp_roots_) {
184+
if (root) {
185+
Value(root).mark();
186+
}
187+
}
188+
166189
GCObject* prev = nullptr;
167190
GCObject* curr = first_object_;
168191
size_t new_object_count = 0;

‎shell_lite/gc.hpp‎

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,16 +73,57 @@ class GCArena {
7373
void collect();
7474
void transfer_to(GCArena& target);
7575

76+
void push_temp_root(GCObject* obj);
77+
void remove_temp_root(GCObject* obj);
78+
7679
private:
7780
void collect_internal();
7881

7982
GCObject* first_object_ = nullptr;
8083
std::unordered_map<std::string, ObjString*> strings_;
84+
std::vector<GCObject*> temp_roots_;
8185
VM* vm_;
8286

8387
size_t bytes_allocated_;
8488
size_t next_gc_;
8589
std::recursive_mutex gc_mutex_;
8690
};
8791

92+
class GCRootGuard {
93+
public:
94+
explicit GCRootGuard(GCArena& arena, GCObject* obj = nullptr)
95+
: arena_(&arena), obj_(obj) {
96+
if (arena_ && obj_) arena_->push_temp_root(obj_);
97+
}
98+
~GCRootGuard() {
99+
if (arena_ && obj_) arena_->remove_temp_root(obj_);
100+
}
101+
void reset(GCObject* new_obj) {
102+
if (arena_ && obj_) arena_->remove_temp_root(obj_);
103+
obj_ = new_obj;
104+
if (arena_ && obj_) arena_->push_temp_root(obj_);
105+
}
106+
GCObject* get() const { return obj_; }
107+
108+
GCRootGuard(const GCRootGuard&) = delete;
109+
GCRootGuard& operator=(const GCRootGuard&) = delete;
110+
GCRootGuard(GCRootGuard&& o) noexcept : arena_(o.arena_), obj_(o.obj_) {
111+
o.arena_ = nullptr;
112+
o.obj_ = nullptr;
113+
}
114+
GCRootGuard& operator=(GCRootGuard&& o) noexcept {
115+
if (this != &o) {
116+
if (arena_ && obj_) arena_->remove_temp_root(obj_);
117+
arena_ = o.arena_;
118+
obj_ = o.obj_;
119+
o.arena_ = nullptr;
120+
o.obj_ = nullptr;
121+
}
122+
return *this;
123+
}
124+
private:
125+
GCArena* arena_;
126+
GCObject* obj_;
127+
};
128+
88129
}

‎shell_lite/modules/module_db.cpp‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,8 +139,10 @@ void register_stdlib_db(VM* vm) {
139139
}
140140

141141
auto result_list = vm->arena().allocate<ObjList>();
142+
GCRootGuard res_guard(vm->arena(), result_list);
142143
while (sqlite3_step(stmt) == SQLITE_ROW) {
143144
auto row_dict = vm->arena().allocate<ObjDict>();
145+
result_list->elements.push_back(Value(row_dict));
144146
int cols = sqlite3_column_count(stmt);
145147
for (int i = 0; i < cols; ++i) {
146148
std::string col_name = sqlite3_column_name(stmt, i);
@@ -153,7 +155,6 @@ void register_stdlib_db(VM* vm) {
153155
}
154156
else row_dict->elements[col_name] = Value();
155157
}
156-
result_list->elements.push_back(Value(row_dict));
157158
}
158159
sqlite3_finalize(stmt);
159160
return Value(result_list);
@@ -254,6 +255,7 @@ void register_stdlib_db(VM* vm) {
254255
}
255256
sqlite3_stmt* stmt = nullptr;
256257
auto result_list = vm->arena().allocate<ObjList>();
258+
GCRootGuard res_guard(vm->arena(), result_list);
257259
if (sqlite3_prepare_v2(conn, q.c_str(), -1, &stmt, nullptr) == SQLITE_OK) {
258260
int idx = 1;
259261
for (auto& val : where_info.second) {
@@ -265,6 +267,7 @@ void register_stdlib_db(VM* vm) {
265267

266268
while (sqlite3_step(stmt) == SQLITE_ROW) {
267269
auto row_dict = vm->arena().allocate<ObjDict>();
270+
result_list->elements.push_back(Value(row_dict));
268271
int cols = sqlite3_column_count(stmt);
269272
for (int i = 0; i < cols; ++i) {
270273
std::string col_name = sqlite3_column_name(stmt, i);
@@ -277,7 +280,6 @@ void register_stdlib_db(VM* vm) {
277280
}
278281
else row_dict->elements[col_name] = Value();
279282
}
280-
result_list->elements.push_back(Value(row_dict));
281283
}
282284
sqlite3_finalize(stmt);
283285
} else {

‎shell_lite/modules/module_web.cpp‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,23 +43,27 @@ static bool match_route_pattern(const std::string& pattern, const std::string& a
4343
static Value build_request_dict(VM* vm, const httplib::Request& req,
4444
const std::unordered_map<std::string, std::string>& params) {
4545
auto* d = vm->arena().allocate<ObjDict>();
46+
GCRootGuard guard_d(vm->arena(), d);
4647
d->elements["method"] = Value(vm->arena().allocate_string(req.method));
4748
d->elements["path"] = Value(vm->arena().allocate_string(req.path));
4849
d->elements["body"] = Value(vm->arena().allocate_string(req.body));
4950

5051
auto* p_dict = vm->arena().allocate<ObjDict>();
52+
GCRootGuard guard_p(vm->arena(), p_dict);
5153
for (const auto& pair : params) {
5254
p_dict->elements[pair.first] = Value(vm->arena().allocate_string(pair.second));
5355
}
5456
d->elements["params"] = Value(p_dict);
5557

5658
auto* q_dict = vm->arena().allocate<ObjDict>();
59+
GCRootGuard guard_q(vm->arena(), q_dict);
5760
for (const auto& pair : req.params) {
5861
q_dict->elements[pair.first] = Value(vm->arena().allocate_string(pair.second));
5962
}
6063
d->elements["query"] = Value(q_dict);
6164

6265
auto* h_dict = vm->arena().allocate<ObjDict>();
66+
GCRootGuard guard_h(vm->arena(), h_dict);
6367
for (const auto& pair : req.headers) {
6468
h_dict->elements[pair.first] = Value(vm->arena().allocate_string(pair.second));
6569
}

‎shell_lite/objects.hpp‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ struct ObjIterator : public GCObject {
131131
: GCObject(ObjType::ITERATOR), iterable(iter), index(0) {
132132
if (iterable.is_dict()) {
133133
auto* dict = static_cast<ObjDict*>(iterable.get_obj());
134+
dict_keys.reserve(dict->elements.size());
134135
for (const auto& pair : dict->elements) {
135136
dict_keys.push_back(pair.first);
136137
}

0 commit comments

Comments
 (0)