4 Commits

Author SHA1 Message Date
anth64 fb0d8adb8f chore: bump version to 0.0.4 2026-02-11 00:22:59 +01:00
anth64 2c4d27f915 fix(linux): prevent segfault from invalid module indices during rapid reloads
When spamming file changes rapidly, inotify can report stale UNLOAD/RELOAD
events for modules that were already unloaded by previous events in the same
poll cycle. This caused is_mod_loaded() to return -1, which was then cast to
size_t (18446744073709551615) and used as an array index, causing segfaults.

Additionally, event counts were calculated before validation, causing loops to
run more iterations than valid indices were populated, reading garbage values.

Changes:
- stk.c: Check if is_mod_loaded() returns valid index (>= 0) before adding
  to unload/reload lists
- stk.c: Reset and recalculate counts after populating arrays with only valid
  indices to prevent loop overrun
- Skip processing events for modules that are no longer loaded

This completes the Linux stability fixes started in v0.0.2.
2026-02-11 00:17:33 +01:00
anth64 96fb957991 chore: bump version to 0.0.3 2026-02-09 23:03:35 +01:00
anth64 0cbee45ad2 fix(platform): replace strncpy with memmove to silence -Wrestrict warning 2026-02-09 22:53:42 +01:00
5 changed files with 48 additions and 23 deletions
+18 -1
View File
@@ -7,6 +7,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
## [0.0.4] - 2026-02-11
### Fixed
- **Linux**: Fixed segfault from invalid module indices during extremely rapid file changes
- Added validation check to skip stale UNLOAD/RELOAD events for already-unloaded modules
- Prevents is_mod_loaded() returning -1 from being used as array index (SIZE_MAX)
- Fixed event count mismatch where loops would run more iterations than valid indices populated
- Completes the Linux hot-reload stability fixes from v0.0.2
## [0.0.3] - 2026-02-10
### Fixed
- **Compilation**: Fixed GCC `-Wrestrict` warning in Linux directory watching code by replacing `strncpy` with `memmove` for overlapping memory operations
- Ensures clean compilation without warnings while maintaining identical runtime behavior
- Uses semantically correct function for moving memory within the same buffer
## [0.0.2] - 2026-02-09 ## [0.0.2] - 2026-02-09
### Fixed ### Fixed
@@ -37,6 +53,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Dependency management and versioning not yet implemented - Dependency management and versioning not yet implemented
- API is unstable and subject to change in future releases - API is unstable and subject to change in future releases
[Unreleased]: https://github.com/anth64/stk/compare/v0.0.2...HEAD [Unreleased]: https://github.com/anth64/stk/compare/v0.0.3...HEAD
[0.0.3]: https://github.com/anth64/stk/releases/tag/v0.0.3
[0.0.2]: https://github.com/anth64/stk/releases/tag/v0.0.2 [0.0.2]: https://github.com/anth64/stk/releases/tag/v0.0.2
[0.0.1]: https://github.com/anth64/stk/releases/tag/v0.0.1 [0.0.1]: https://github.com/anth64/stk/releases/tag/v0.0.1
+3 -3
View File
@@ -173,15 +173,15 @@ stk_init();
## Project Status ## Project Status
**Current Version:** 0.0.2 (Pre-release) **Current Version:** 0.0.4 (Pre-release)
This is an early bugfix release improving hot-reload stability on Linux. Phase 1 is still in progress. This is a bugfix release completing the Linux hot-reload stability improvements. Phase 1 is still in progress.
### What Works ### What Works
- Cross-platform module loading and hot-reloading - Cross-platform module loading and hot-reloading
- File watching (inotify/kqueue/FindFirstFile) - File watching (inotify/kqueue/FindFirstFile)
- Basic error handling - Basic error handling
- Stable hot-reload even during rapid file changes (Linux fix in 0.0.2) - Robust hot-reload even during extremely rapid file changes (Linux fixes in 0.0.2-0.0.4)
### In Progress (Phase 1) ### In Progress (Phase 1)
- Complete logging system (log levels, verbosity, output configuration) - Complete logging system (log levels, verbosity, output configuration)
+1 -1
View File
@@ -3,7 +3,7 @@
#define STK_VERSION_MAJOR 0 #define STK_VERSION_MAJOR 0
#define STK_VERSION_MINOR 0 #define STK_VERSION_MINOR 0
#define STK_VERSION_PATCH 2 #define STK_VERSION_PATCH 4
#define STK_STRINGIFY_HELPER(x) #x #define STK_STRINGIFY_HELPER(x) #x
#define STK_STRINGIFY(x) STK_STRINGIFY_HELPER(x) #define STK_STRINGIFY(x) STK_STRINGIFY_HELPER(x)
+8 -10
View File
@@ -662,7 +662,7 @@ stk_module_event_t *platform_directory_watch_check(
int fd = (int)(long)handle; int fd = (int)(long)handle;
char buf[STK_EVENT_BUFFER]; char buf[STK_EVENT_BUFFER];
ssize_t len; ssize_t len;
size_t index = 0, count = 0, i, write_idx; size_t index = 0, count = 0, i, write_index;
stk_module_event_t *evs; stk_module_event_t *evs;
char *ptr, *end; char *ptr, *end;
struct inotify_event *e; struct inotify_event *e;
@@ -760,20 +760,18 @@ stk_module_event_t *platform_directory_watch_check(
} }
} }
write_idx = 0; write_index = 0;
for (i = 0; i < index; ++i) { for (i = 0; i < index; ++i) {
if (evs[i] != -1) { if (evs[i] != -1) {
if (write_idx != i) { if (write_index != i) {
evs[write_idx] = evs[i]; evs[write_index] = evs[i];
strncpy((*file_list)[write_idx], memmove((*file_list)[write_index],
(*file_list)[i], STK_PATH_MAX - 1); (*file_list)[i], STK_PATH_MAX);
(*file_list)[write_idx][STK_PATH_MAX - 1] =
'\0';
} }
write_idx++; write_index++;
} }
} }
index = write_idx; index = write_index;
*out_count = index; *out_count = index;
return evs; return evs;
+18 -8
View File
@@ -172,8 +172,7 @@ size_t stk_poll(void)
char (*file_list)[STK_PATH_MAX] = NULL; char (*file_list)[STK_PATH_MAX] = NULL;
stk_module_event_t *events = NULL; stk_module_event_t *events = NULL;
size_t i, file_count = 0, reload_count = 0, load_count = 0, size_t i, file_count = 0, reload_count = 0, load_count = 0,
unload_count = 0, reload_index = 0, load_index = 0, unload_count = 0;
unload_index = 0;
int *reloaded_mod_indices = NULL, *reloaded_mod_file_indices = NULL, int *reloaded_mod_indices = NULL, *reloaded_mod_file_indices = NULL,
*unloaded_mod_indices = NULL, *loaded_mod_indices = NULL; *unloaded_mod_indices = NULL, *loaded_mod_indices = NULL;
size_t remaining_loads, new_capacity, holes_to_fill; size_t remaining_loads, new_capacity, holes_to_fill;
@@ -208,20 +207,31 @@ size_t stk_poll(void)
unloaded_mod_indices = malloc(unload_count * sizeof(int)); unloaded_mod_indices = malloc(unload_count * sizeof(int));
loaded_mod_indices = malloc(load_count * sizeof(int)); loaded_mod_indices = malloc(load_count * sizeof(int));
reload_count = 0;
unload_count = 0;
load_count = 0;
for (i = 0; i < file_count; ++i) { for (i = 0; i < file_count; ++i) {
int mod_index;
extract_module_id(file_list[i], mod_id); extract_module_id(file_list[i], mod_id);
switch (events[i]) { switch (events[i]) {
case STK_MOD_LOAD: case STK_MOD_LOAD:
loaded_mod_indices[load_index++] = i; loaded_mod_indices[load_count++] = i;
break; break;
case STK_MOD_RELOAD: case STK_MOD_RELOAD:
reloaded_mod_file_indices[reload_index] = i; mod_index = is_mod_loaded(mod_id);
reloaded_mod_indices[reload_index++] = if (mod_index >= 0) {
is_mod_loaded(mod_id); reloaded_mod_file_indices[reload_count] = i;
reloaded_mod_indices[reload_count] = mod_index;
reload_count++;
}
break; break;
case STK_MOD_UNLOAD: case STK_MOD_UNLOAD:
unloaded_mod_indices[unload_index++] = mod_index = is_mod_loaded(mod_id);
is_mod_loaded(mod_id); if (mod_index >= 0) {
unloaded_mod_indices[unload_count] = mod_index;
unload_count++;
}
break; break;
} }
} }