7 Commits
Author SHA1 Message Date
weaselbot abeaae7ed7 schemagen: handle WeaselJsonParser_create failure in RootBuilder
If WeaselJsonParser_create returns nullptr (e.g. negative stack size or allocation failure), set the existing error_ flag so that subsequent feed()/finish() calls return WeaselJson_REJECT instead of dereferencing the null parser_.

Also add a regression test in test_gen.cpp that constructs a RootBuilder with an invalid stack size and verifies it rejects without crashing.

Closes #36
2026-06-30 11:26:59 -04:00
andrew 4bd1088018 Merge pull request 'Include <cstdint> in json_value.h for uint8_t' (#45) from weaselbot/weaseljson:weaselbot/issue-37 into main
Reviewed-on: weaselab/weaseljson#45
2026-06-29 18:57:40 +00:00
andrew 82bdc8a080 Merge pull request 'python: raise OSError when shared library is missing' (#44) from weaselbot/weaseljson:weaselbot/issue-38 into main
Reviewed-on: weaselab/weaseljson#44
2026-06-29 18:53:49 +00:00
andrew 6508616edc Merge pull request 'Handle null parser in WeaselJsonParser_reset and _destroy' (#42) from weaselbot/weaseljson:weaselbot/issue-41 into main
Reviewed-on: weaselab/weaseljson#42
2026-06-29 18:26:48 +00:00
weaselbot e5c970a605 Include <cstdint> in json_value.h for uint8_t
`escapeAsJsonString` uses `uint8_t` but the header did not include
`<cstdint>`, making it dependent on other headers to define the type.
Add the missing include so `json_value.h` is self-contained.
2026-06-29 14:04:04 -04:00
weaselbot 96f61665bf python: raise OSError when shared library is missing
Replace sys.exit(1) in WeaselJsonParser.__init__ with an OSError so
callers can handle a missing libweaseljson gracefully. Also add a test
that verifies the constructor raises OSError for a non-existent build
directory.

Closes #38
2026-06-29 14:03:03 -04:00
weaselbot 34fc22a7c2 Handle null parser in WeaselJsonParser_reset and _destroy
WeaselJsonParser_create can return nullptr when allocation fails or the
requested stack size is too small. Previously, passing that nullptr to
WeaselJsonParser_reset or WeaselJsonParser_destroy dereferenced it before
doing any work, causing immediate undefined behavior.

Add an early null check to both functions so they behave like free(nullptr)
(i.e., are a safe no-op). Also add a doctest case covering both a null
returned from create and a literal nullptr.

Closes #41
2026-06-29 13:53:31 -04:00
7 changed files with 47 additions and 7 deletions
+9
View File
@@ -68,6 +68,15 @@ int main() {
expectReject(json, "unknown key in strict root"); expectReject(json, "unknown key in strict root");
} }
// ---- invalid stack size is rejected without crashing ----
{
RootBuilder b(-1);
char buf[] = "null";
WeaselJsonStatus s = b.feed(buf, sizeof(buf) - 1);
CHECK(s == WeaselJson_REJECT);
printf("ok invalid stack size rejected, not crashed\n");
}
{ {
std::string json = R"({ std::string json = R"({
"name": "Ada É", "name": "Ada É",
@@ -892,6 +892,10 @@ public:
explicit RootBuilder(int stackSize = 1024) {{ explicit RootBuilder(int stackSize = 1024) {{
cb_ = makeCallbacks(); cb_ = makeCallbacks();
parser_ = WeaselJsonParser_create(stackSize, &cb_, this, 0); parser_ = WeaselJsonParser_create(stackSize, &cb_, this, 0);
if (!parser_) {{
error_ = true;
return;
}}
{self._ctor_body()} {self._ctor_body()}
}} }}
~RootBuilder() {{ if (parser_) WeaselJsonParser_destroy(parser_); }} ~RootBuilder() {{ if (parser_) WeaselJsonParser_destroy(parser_); }}
+1
View File
@@ -1,6 +1,7 @@
#pragma once #pragma once
#include <cstddef> #include <cstddef>
#include <cstdint>
#include <map> #include <map>
#include <memory> #include <memory>
#include <optional> #include <optional>
+6
View File
@@ -29,11 +29,17 @@ WeaselJsonParser_create(int stackSize, const WeaselJsonCallbacks *callbacks,
__attribute__((visibility("default"))) void __attribute__((visibility("default"))) void
WeaselJsonParser_reset(WeaselJsonParser *parser) { WeaselJsonParser_reset(WeaselJsonParser *parser) {
if (parser == nullptr) {
return;
}
((Parser3 *)parser)->reset(); ((Parser3 *)parser)->reset();
} }
__attribute__((visibility("default"))) void __attribute__((visibility("default"))) void
WeaselJsonParser_destroy(WeaselJsonParser *parser) { WeaselJsonParser_destroy(WeaselJsonParser *parser) {
if (parser == nullptr) {
return;
}
((Parser3 *)parser)->~Parser3(); ((Parser3 *)parser)->~Parser3();
free(parser); free(parser);
} }
+14
View File
@@ -246,6 +246,20 @@ TEST_CASE("create rejects too-small stack") {
WeaselJsonParser_destroy(parser); WeaselJsonParser_destroy(parser);
} }
TEST_CASE("reset and destroy accept null parser") {
// Creation can legitimately fail and return null. The cleanup functions must
// tolerate a null pointer the same way free(nullptr) is a no-op.
auto c = noopCallbacks();
WeaselJsonParser *parser = WeaselJsonParser_create(-1, &c, nullptr, 0);
REQUIRE(parser == nullptr);
WeaselJsonParser_reset(parser); // must not crash
WeaselJsonParser_destroy(parser); // must not crash
// Calling reset/destroy on literal nullptr directly must also be safe.
WeaselJsonParser_reset(nullptr);
WeaselJsonParser_destroy(nullptr);
}
TEST_CASE("parse rejects negative length") { TEST_CASE("parse rejects negative length") {
auto c = noopCallbacks(); auto c = noopCallbacks();
auto *parser = WeaselJsonParser_create(1024, &c, nullptr, 0); auto *parser = WeaselJsonParser_create(1024, &c, nullptr, 0);
+12
View File
@@ -95,8 +95,20 @@ def test_create_rejects_too_small_stack():
raise AssertionError(f"expected ValueError for stackSize={stack_size}") raise AssertionError(f"expected ValueError for stackSize={stack_size}")
def test_missing_library_raises_oserror():
try:
weaseljson.WeaselJsonParser(
weaseljson.WeaselJsonCallbacksBase(),
build_dir="/nonexistent",
)
except OSError:
return
raise AssertionError("expected OSError when the shared library is missing")
if __name__ == "__main__": if __name__ == "__main__":
test_object_keys_routed_correctly() test_object_keys_routed_correctly()
test_mixed_values() test_mixed_values()
test_create_rejects_too_small_stack() test_create_rejects_too_small_stack()
test_missing_library_raises_oserror()
print("python bindings ok") print("python bindings ok")
+1 -7
View File
@@ -84,13 +84,7 @@ class WeaselJsonParser:
pass pass
if self._lib is None: if self._lib is None:
import sys raise OSError(f"Could not load libweaseljson from {build_dir}")
print(
"Could not find libweaseljson implementation",
file=sys.stderr,
)
sys.exit(1)
self._lib.WeaselJsonParser_create.argtypes = ( self._lib.WeaselJsonParser_create.argtypes = (
ctypes.c_int, ctypes.c_int,