test(meta): raise meta coverage to >=90% + fix ffi CallbackRegistry crash#4
test(meta): raise meta coverage to >=90% + fix ffi CallbackRegistry crash#4MaxQian888 wants to merge 35 commits into
Conversation
updates: - [github.com/pre-commit/pre-commit-hooks: v4.6.0 → v6.0.0](pre-commit/pre-commit-hooks@v4.6.0...v6.0.0)
for more information, see https://pre-commit.ci
for more information, see https://pre-commit.ci
updates: - [github.com/pre-commit/pre-commit-hooks: v4.6.0 → v6.0.0](pre-commit/pre-commit-hooks@v4.6.0...v6.0.0)
Add the previously-missing test_refl_field.cpp covering FieldBase: construction/defaults, member-pointer binding, validators, and the C++23 deducing-this builders including derived-type preservation through chaining. Expand the meta test suites so every header with runtime code reaches at least 90% deduplicated source-line coverage (measured per-test to avoid gcov's per-instantiation line inflation). facade_any stops at its reachable ceiling: the remaining lines are defensive proxy-call catch blocks and the never-invoked less_than_impl, which cannot be reached without fault injection. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
CallbackRegistry stored the raw closure type in std::any while getCallback<Sig> retrieved it as std::function<Sig>. The pointer-form any_cast then returned nullptr on the type mismatch (it does not throw, so the surrounding catch was dead code), and getCallback wrapped that null pointer in a success result, so callers dereferenced null and crashed with SIGSEGV. Store the callable wrapped in a std::function with the deduced signature for both the sync and async paths (a private registerAsyncImpl recovers R(Args...) so the async entry is std::function<future<R>(Args...)>), and null-guard getCallback so a signature mismatch returns TypeMismatch. Also make DynamicLibrary::getHandle honour the load strategy like getFunction: Lazy and Immediate load on access, OnDemand still requires an explicit loadLibrary(). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Sorry @MaxQian888, your pull request is larger than the review limit of 150000 diff characters
for more information, see https://pre-commit.ci
Survey of atom/components found namespace chaos, a dead event system referencing undefined types, broken module macros, 6 disabled test files, trait/JSON duplication vs atom/meta and atom/type, and CMake debt. Spec defines 7 incremental work packages keeping the 332-test baseline green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
14 tasks covering the 7 work packages from the design spec, each gated on rebuild + test run against the 332-test baseline. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fix mis-indented reset()/updateExecutionTime()/legacy getter bodies and drop the MSVC-vs-GCC constexpr fork: atomics cannot be constexpr-reset, so a single plain noexcept variant is correct on all compilers. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…dex_sequence The 98-line manual arity-0..8 if-constexpr ladder, textually included inside Component::def via #include, is replaced by a single std::index_sequence generic-lambda expansion over FunctionTraits. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…d code OP_*/CONDITION_*/REGISTER_OPERATOR leaked from the public header into every consumer. registerOperators now uses std::equality_comparable / std::totally_ordered directly; the DEF_MEMBER_FUNC* helper macros are #undef-ed after their last expansion. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Per-dispatch and per-variable spdlog::info calls flooded output (every command execution logged 5+ info lines). Command/variable hot paths now log at trace, per-component registry operations at debug; lifecycle-level messages stay at info. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
atom::error::Exception stream-concatenates its args, so every THROW_*
call using fmt-style placeholders produced messages like
"Precondition failed for command '"'"'{}'"'"'alwaysFail". Module-local THROW
macros now wrap fmt::format (matching THROW_REGISTRY_EXCEPTION, which
already did), and atom/error macro call sites format explicitly.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…acros tests ATOM_MODULE_INIT and ATOM_HOT_COMPONENT passed 0-arg factory lambdas where Component::InitFunc (void(Component&)) was expected, so any ATOM_MODULE/ATOM_EMBED_MODULE expansion failed to compile, and ATOM_VERSION was never defined. Registry gains registerComponentInstance() to register externally created instances (the missing API the macros needed). The disabled types_and_macros test file targeted macros that never existed; rewritten against the real ones (ATOM_MODULE, ATOM_EMBED_MODULE, REGISTER_INITIALIZER, ATOM_COMPONENT). Tests: 332 -> 344. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The event system (Component::emitEvent/on/once/off and registry-level subscribe/trigger) referenced atom::components::Event, EventCallback and EventCallbackId, which were defined nowhere, so the code behind ENABLE_EVENT_SYSTEM could never compile. Define those types in core/types.hpp, add the ATOM_COMPONENTS_ENABLE_EVENTS CMake option (default ON), and fix two self-deadlocks the dead code hid: cleanupAll and initializeAll trigger events while holding mutex_, so event subscriptions now use a dedicated eventMutex_. Tests: 344 -> 349. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
data/type_conversion.hpp declared a TypeConverter/TypeRegistry template API with zero definitions (link error on any use) and re-implemented container/optional/smart-pointer/tuple traits that atom/meta already provides. Nothing in the module used it (advanced_bindings only included it), and its test was disabled for calling those non-existent methods. Removed outright per the no-parallel-dead-code convention. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
absl::StrContains was the only abseil use in the whole repository; std::string_view::find does the same job in a constexpr context. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
refactor: drop "advanced" qualifier and consolidate parallel files Merge the "advanced_*" implementations into their base counterparts and delete the redundant parallel files across the components, system, and meta modules, following the convention that qualifier words like "advanced" do not belong in names. - components: advanced_bindings -> scripting/bindings - system/command: fold advanced_executor into the executor; add command.hpp - system/env: fold env_advanced into env_core/utils - system/shortcut: advanced_shortcut -> shortcut_binding - system/scheduling, system/process: drop dead duplicate sources - meta: split refl_field.hpp out of refl; reorganize tests into per-area subdirectories and remove the old flat test files Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> @
for more information, see https://pre-commit.ci
There was a problem hiding this comment.
2 issues found across 193 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="atom/meta/constructor.hpp">
<violation number="1" location="atom/meta/constructor.hpp:547">
P2: Missing direct `#include <vector>` for newly introduced `std::vector` usage in `ObjectBuilder`</violation>
</file>
<file name="atom/meta/raw_name.hpp">
<violation number="1" location="atom/meta/raw_name.hpp:137">
P2: GCC member-name parser truncates overloaded operator member names like `operator()` due to forward search for `)` or `}` delimiters. The `find_first_of(")}", start)` call stops at the `)` inside `operator()` itself, returning `operator(` instead of the full name.</violation>
</file>
Note: This PR contains a large number of files. cubic only reviews up to 100 files per PR, so some files may not have been reviewed. cubic prioritizes the most important files to review.
On a pro plan you can use ultrareview for larger PRs.
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
| @@ -435,30 +435,13 @@ auto asyncConstructor(std::launch policy = std::launch::async) { | |||
| template <typename Class, bool ThreadSafe = true> | |||
There was a problem hiding this comment.
P2: Missing direct #include <vector> for newly introduced std::vector usage in ObjectBuilder
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At atom/meta/constructor.hpp, line 547:
<comment>Missing direct `#include <vector>` for newly introduced `std::vector` usage in `ObjectBuilder`</comment>
<file context>
@@ -559,40 +542,45 @@ auto factoryConstructor() {
- std::function<std::shared_ptr<Class>()> m_buildFunc;
+ // Steps are applied in order at build(); storing them flat avoids the
+ // O(n^2) closure-chain copies of composing a new lambda per step.
+ std::vector<std::function<void(Class&)>> m_steps;
public:
</file context>
| if (auto start = value.rfind("::"); start != std::string_view::npos) { | ||
| start += 2; | ||
| auto end = name.rfind('}'); | ||
| auto end = value.find_first_of(")}", start); |
There was a problem hiding this comment.
P2: GCC member-name parser truncates overloaded operator member names like operator() due to forward search for ) or } delimiters. The find_first_of(")}", start) call stops at the ) inside operator() itself, returning operator( instead of the full name.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At atom/meta/raw_name.hpp, line 137:
<comment>GCC member-name parser truncates overloaded operator member names like `operator()` due to forward search for `)` or `}` delimiters. The `find_first_of(")}", start)` call stops at the `)` inside `operator()` itself, returning `operator(` instead of the full name.</comment>
<file context>
@@ -108,19 +127,22 @@ constexpr std::string_view extract_enum_name(std::string_view name) noexcept {
+ if (auto start = value.rfind("::"); start != std::string_view::npos) {
start += 2;
- auto end = name.rfind('}');
+ auto end = value.find_first_of(")}", start);
if (end == std::string_view::npos) {
- end = name.size();
</file context>
| auto end = value.find_first_of(")}", start); | |
| auto end = value.find_last_of(")}"); |
concept.hpp used std::complex (ComplexNumber concept) without <complex>, and difflib.cpp used std::setprecision without <iomanip>. Both were latent after recent refactors and only surfaced when the components module / its tests pull these translation units in. No behavior change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The USE_BOOST_CONTAINERS branch in var.hpp and the ATOM_USE_BOOST_CONTAINERS branch in dispatch.hpp referenced namespaces that do not exist (atom::components::containers::flat_map, atom::container::string_hash_set), so the branches failed to compile whenever the macro was defined. Point both at the real high-performance container module (atom::containers, atom/containers/boost_containers.hpp): flat_map / flat_set for var, and fast_unordered_map / fast_unordered_set for dispatch. Standardize var.hpp on the ATOM_USE_BOOST_CONTAINERS macro to match dispatch.hpp. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ScriptLanguage and the engine factory advertised a ChaiScript backend that was never implemented, while the actually-shipped Python engine had no explicit createEngine case (only reachable via Auto). Rename the enumerator to Python, give it a real PythonEngineFactory branch, and switch language detection to the .py extension and Python content heuristics. Drop the unused ATOM_ENABLE_CHAISCRIPT test toggle and update the affected assertions. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…amicLibrary Registry::loadComponentFromFile was a stub that logged "not implemented yet". Implement it by reusing atom::meta::DynamicLibrary (atom/meta/ffi.hpp): load the shared object, resolve and call the C entry point exported by ATOM_MODULE (<name>_initialize_registry), retain the library handle for the component's lifetime, and record the module version from <name>_getVersion. Add an ATOM_COMPONENTS_ENABLE_HOT_RELOAD option (CMake + xmake, default OFF) so the ENABLE_HOT_RELOAD path is actually buildable, link CMAKE_DL_LIBS / dl for dlopen, and add the previously-missing <filesystem>/<future> includes guarded by the same macro. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The previous doc referenced deleted files (advanced_bindings, type_conversion)
and APIs that do not exist (getInstance, setProperty, a Var value type,
Dispatcher::subscribe). Replace it with the real Component / Registry /
CommandDispatcher / VariableManager interfaces, the atom::meta + atom::type
reuse, ScriptLanguage{Lua,Python,Auto}, and the new hot-reload option.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
atom::type::expected stores the error as Error<E>, so reaching the underlying value required the awkward .error().error() double access used in ~160 call sites. Add error_value() (const& and && overloads) that returns E directly, reusing error()'s bounds check. Non-breaking: error() and the existing double access are untouched. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…xpected Replace the bool-success/errorMessage result structs with expected<Payload, SerializationError>. SerializationResult / DeserializationResult now hold only the success payload (data/metadata); failures carry a structured SerializationError (code + message) reached via the new expected::error_value(). ISerializer, JsonSerializer, BinarySerializer and SerializationManager return SerializationOutcome / DeserializationOutcome. Callers switch from result.success / result.errorMessage to result.has_value() / result.error_value().message and reach the payload via operator->. Tests and the serialization example are updated accordingly. BREAKING CHANGE: serialize/deserialize now return expected<> instead of the old result structs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
alignas(64) was attached to the EventHandler struct definition, where the attribute is ignored (GCC warned), so the intended "separate cache line" for the event-system data never took effect. Move the alignment onto the first event-system data member (m_EventHandlers_) so it actually starts on its own cache line. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ding The InitializeAndCleanup and Reinitialize tests were commented out because they reused the name "Component1"; since Registry::instance() is a singleton and addInitializer skips already-registered names, they contaminated each other. Give each a unique name and update the lambdas to the current InitFunc(Component&) signature. Add error-path coverage for the new Registry::loadComponentFromFile (missing file and non-library file both fail), guarded by ENABLE_HOT_RELOAD. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Several type-module symbols (NonCopyable, StaticString, Trackable, PointerSentinel, and siblings) previously lived in the global namespace, polluting every consumer's namespace and risking name clashes. They now live under atom::type like the rest of the module. Consumers across algorithm, async, components, connection, meta, and utils are updated to qualify the symbols (or add a using-declaration) so they keep compiling. The module CLAUDE.md and header list are updated to match. BREAKING CHANGE: type-module symbols that were in the global namespace (NonCopyable, StaticString, Trackable, PointerSentinel, and siblings) now live in namespace atom::type. External code must qualify them as atom::type::<name> or add a using-declaration. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The per-header test_*.hpp suites are only reachable through test_header_only.cpp, but most were missing from that aggregation and so silently never compiled. Wire all 21 header-only suites in, and split rtype into its own translation unit -- its `using namespace atom::meta` makes the unqualified `detail` ambiguous inside the shared TU where an earlier `using namespace atom::type` is still active. Aggregating every suite into one TU overruns the COFF 2^16 section limit on MinGW (spurious "undefined reference to .refptr.*" link errors), so enable big-object output (-Wa,-mbig-obj on Windows, /bigobj on MSVC). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Every example under example/type was corrupted: comment text was glued
onto the following statement (e.g. "// demo handlingclass Foo {"), so
none of them compiled. Rewrite each as a focused, compiling demonstration
of its type. The net result is far smaller and actually runnable.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
27 issues found across 127 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="atom/type/robin_hood.hpp">
<violation number="1" location="atom/type/robin_hood.hpp:376">
P1: The write lock added to `insert()` only serializes that single method, leaving every other public access to `table_` unprotected. Both overloads of `find`, `at`, all iterator accessors, `clear`, and `max_load_factor(float)` read or mutate the underlying storage without acquiring `lock_read()` or `lock_write()`. In a concurrent workload, a `find` on one thread can race with an `insert` + `rehash` on another because `rehash` move-assigns `table_` while `find` is still indexing into the old vector. The `reader_lock` and `mutex` threading policies are therefore broken for any pattern beyond single-threaded `insert`-only use. A consistent locking policy should be applied across every public method that touches `table_`, or the policy enum should be removed if thread safety is not intended.</violation>
<violation number="2" location="atom/type/robin_hood.hpp:418">
P0: Default-key lookups can return the wrong element because the new empty-slot-skipping iterator is constructed for a bucket that `find` incorrectly accepts as a match. Empty slots have `dist == 0` and a default-constructed key. Because `find` starts with `dist == 0`, the termination check `table_[idx].dist < dist` is false for an empty ideal bucket, so `find` proceeds to `key_equal_`. If the searched key happens to be the default value, `key_equal_` succeeds and `find` returns an iterator to the empty bucket. The new iterator constructor then calls `skip_empty()`, advancing past the empty slot to the next occupied entry. This means `find(Key{})` can return an iterator to a completely different key, and `at(Key{})` can return the wrong value without throwing. The same issue exists in the const overload.</violation>
</file>
<file name="atom/type/concurrent_map.hpp">
<violation number="1" location="atom/type/concurrent_map.hpp:38">
P2: `ConcurrentMapError` hard-codes `ATOM_FILE_NAME`, `ATOM_FILE_LINE`, and `ATOM_FUNC_NAME` inside its constructor definition, so these macros expand at the class declaration site rather than at each `throw ConcurrentMapError(...)` expression. Every thrown instance therefore records the same fixed file/line/function regardless of which operation failed, defeating the framework exception metadata.
The codebase already has a correct pattern for this in `concurrent_vector.hpp`: a `THROW_CONCURRENT_VECTOR_ERROR` macro passes the macros at the throw site. Consider adding an equivalent `THROW_CONCURRENT_MAP_ERROR(...)` macro and using it at all throw sites instead of `throw ConcurrentMapError(...)`.</violation>
<violation number="2" location="atom/type/concurrent_map.hpp:871">
P0: Calling `adjust_thread_pool_size` from a task running on this pool's own worker thread causes a self-join: `std::thread::join()` throws `std::system_error(resource_deadlock_would_occur)` when invoked on the current thread. The exception leaves `stop_pool = true`, skips the restart block, and permanently disables the executor. Future `submit` calls will all reject with "Thread pool is stopped". Consider rejecting (or deferring) resize requests that originate from a worker thread, or skip `join()` for the calling thread and account for it in the restart logic.</violation>
</file>
<file name="atom/components/data/serialization.cpp">
<violation number="1" location="atom/components/data/serialization.cpp:73">
P2: The `DeserializeFailed` code returned here conflates all JSON deserialization exceptions, including malformed JSON (`nlohmann::json::parse_error`) and type mismatches (`nlohmann::json::type_error`), with genuine component reconstruction failures. The header's own taxonomy defines `InvalidData` for "Input data was malformed or truncated", so JSON parse/type errors should be reported as `InvalidData` so callers can differentiate corrupt input from internal decoding errors. Adding a preceding catch clause for `nlohmann::json::exception` would align the JSON serializer with the documented taxonomy and with the Binary serializer's choice of `InvalidData` for analogous errors.</violation>
<violation number="2" location="atom/components/data/serialization.cpp:334">
P2: Failed serialization and deserialization attempts are no longer counted in the aggregate timing statistics. Previously, `totalSerializationTime` and `totalDeserializationTime` included the elapsed time of every attempt (successful or failed). After the move to `SerializationOutcome`, the error paths return a `SerializationError` with no duration field, and the manager only adds timing inside `outcome.has_value()`. Since `totalSerializations`/`totalDeserializations` are still incremented unconditionally, derived metrics like average time become artificially low and failure-performance analysis is lost.
Consider measuring elapsed time in `SerializationManager` around the serializer call and always adding it to the total, or including a duration field in `SerializationError` so it can be propagated from the serializer on failure.</violation>
</file>
<file name="atom/components/CMakeLists.txt">
<violation number="1" location="atom/components/CMakeLists.txt:78">
P1: The hot-reload `watchComponentChanges` stop path can deadlock. `watchComponentChanges(false)` in `atom/components/core/registry.cpp` acquires a `std::unique_lock` at function scope while the async worker can try to acquire a `shared_lock` on the same mutex between its `while` check and lock acquisition. If the unique lock is held when the worker tries to get a shared lock, the worker blocks forever, and the caller then blocks forever on `fileWatcherFuture_.wait()` without ever releasing the unique lock. Consider unlocking the mutex before waiting on the future: release `unique_lock` before calling `fileWatcherFuture_.wait()`.</violation>
</file>
<file name="atom/type/concurrent_set.hpp">
<violation number="1" location="atom/type/concurrent_set.hpp:38">
P2: The `ConcurrentSetException` hierarchy hard-codes `ATOM_FILE_NAME`, `ATOM_FILE_LINE`, and `ATOM_FUNC_NAME` inside the class constructor definitions. These macros expand at the constructor's source location (inside `concurrent_set.hpp`) rather than at each actual throw site. Consequently, `getFile()`, `getLine()`, and `getFunction()` always return the same fixed location for every operation failure, defeating the framework's diagnostic metadata. Since the PR intent is to integrate with `atom::error::Exception` for framework error handling, the captured location should reflect the failing operation, not the exception class definition.</violation>
<violation number="2" location="atom/type/concurrent_set.hpp:1118">
P1: The `transaction` method snapshots `data` under a shared lock, releases it while running operations, and then restores the stale snapshot on rollback. This silently overwrites any concurrent modifications made by other threads between the snapshot and the rollback, violating the claimed atomicity. The rollback should either use change-based undo, acquire an exclusive lock for the entire operation window (avoiding the recursive-lock deadlock some other way), or the method should document that it does not provide isolation.</violation>
</file>
<file name="example/type/monadic_error_handling_example.cpp">
<violation number="1" location="example/type/monadic_error_handling_example.cpp:4">
P2: The file-level docstring claims this example demonstrates `and_then / map / or_else` chains, but the code never calls `or_else`. An example whose purpose is to document the API should match the operations it says it demonstrates. Either add an `or_else` recovery path to the chain or remove `or_else` from the header description.</violation>
<violation number="2" location="example/type/monadic_error_handling_example.cpp:22">
P2: The `parse` helper silently accepts numeric-prefix input (e.g., `"4oops"` parses as `4`) because `std::stoi` stops at the first non-numeric character when no consumed-length argument is supplied. This contradicts the example's purpose of demonstrating error-handling short-circuits with `expected`. The adjacent `example/type/expected.cpp` already validates the consumed length with `consumed != text.size()`, confirming this is the intended pattern. Pass a `pos` argument to `std::stoi` and return an error when `pos != s.size()`.</violation>
</file>
<file name="atom/type/flatmap.hpp">
<violation number="1" location="atom/type/flatmap.hpp:205">
P0: The newly-reachable large-container lookup path uses `std::lower_bound` on the internal `std::vector`, but the vector is maintained in insertion order (elements are appended by `insert_or_assign` and `operator[]`), not sorted by the comparator. Binary search on an unsorted range produces incorrect results: existing keys can be missed, leading to duplicate insertions and false negatives from `find`/`at`/`contains`. The previous `std::execution::par_unseq` overload prevented this branch from compiling on conforming implementations; removing the execution policy makes the bug reachable. Consider removing this threshold branch so all non-Boost lookups use linear (or SIMD) search, or alternatively ensure the vector stays sorted on insertion if `lower_bound` is intended.</violation>
</file>
<file name="atom/type/no_offset_ptr.hpp">
<violation number="1" location="atom/type/no_offset_ptr.hpp:248">
P1: The `reset` method advertises a strong exception guarantee in its comment, but when `T` is move-constructible with a throwing move constructor, `destroy()` runs before the placement-new move, so if the move throws the old value is already lost and the pointer is left empty. This also regresses behavior for types where direct `T(args...)` would succeed but moving throws: the old code could reset them, the new code cannot.
Consider switching the branch guard from `std::is_move_constructible_v<T>` to `std::is_nothrow_move_constructible_v<T>` so that only types whose move is guaranteed not to throw use the two-phase strategy, and types with a potentially-throwing move fall back to the basic-guarantee in-place path.</violation>
</file>
<file name="example/type/concurrent_vector.cpp">
<violation number="1" location="example/type/concurrent_vector.cpp:17">
P2: The single-argument constructor usage `ConcurrentVector<int> v(2);` is misdocumented as "2 worker threads". Evidence from the old example code shows the two-argument constructor is `(initial_capacity, thread_count)` and the single-argument overload is used as capacity (e.g. `conc_vec(benchmark_size)`). This means `v(2)` creates a capacity-2 vector with the default thread count, not a two-worker pool as the comment claims. Pass both arguments explicitly (e.g. `ConcurrentVector<int> v(0, 2);`) or update the comment to match the actual behavior.</violation>
</file>
<file name="atom/components/CLAUDE.md">
<violation number="1" location="atom/components/CLAUDE.md:149">
P2: The `ATOM_MODULE` example references `MyComponent`, which is never defined in this rewritten document. The preceding examples all use the `Counter` class introduced earlier. This makes the registration snippet inconsistent with the surrounding documentation and uncompilable if copied. Consider using `Counter` here instead, or explicitly defining `MyComponent` if a distinct example type is intended.</violation>
</file>
<file name="atom/type/weak_ptr.hpp">
<violation number="1" location="atom/type/weak_ptr.hpp:655">
P1: `tryLockPeriodic` is documented and named as a fixed-interval retry helper, but it delegates to `tryLockWithRetry`, which performs exponential backoff (multiplying the interval by `2^attempt` after each failure). Callers expecting regular periodic polling will instead get increasingly long delays. Consider either implementing a true fixed-interval loop inside `tryLockPeriodic`, or renaming and re-documenting the method to reflect the exponential-backoff behavior.</violation>
<violation number="2" location="atom/type/weak_ptr.hpp:842">
P2: `batchOperation` switched from the standard library feature-test macro `__cpp_lib_parallel_algorithm` to a project-specific `ATOM_USE_PARALLEL_ALGORITHMS` macro, but that macro is not defined anywhere in the build system or source code. This means builds that previously compiled the `std::for_each(std::execution::par_unseq, ...)` branch whenever the standard library supported parallel algorithms will now silently fall back to sequential processing for large batches. Either restore the feature-test macro (matching `small_vector.hpp` and `bit.hpp`), define `ATOM_USE_PARALLEL_ALGORITHMS` consistently in the build system, or align all headers on the same guard.</violation>
</file>
<file name="atom/components/xmake.lua">
<violation number="1" location="atom/components/xmake.lua:120">
P2: Enabling `hot_reload` defines `ENABLE_HOT_RELOAD=1`, which activates a watcher loop in `atom/components/core/registry.cpp` that calls `std::this_thread::sleep_for`. Neither `registry.cpp` nor `registry.hpp` directly includes `<thread>`; they rely on `<future>` (included conditionally) transitively providing it, which is not portable. Other files in the project that use the same API already include `<thread>` explicitly (e.g., `scripting_api.cpp`). To make the hot-reload build reliable across toolchains, add `#include <thread>` to `registry.cpp` or `registry.hpp`.</violation>
</file>
<file name="atom/type/concurrent_vector.hpp">
<violation number="1" location="atom/type/concurrent_vector.hpp:1021">
P2: Templating `submit_task` to accept move-only callables removed the previous guard against empty `std::function` tasks. When an empty `std::function` is passed, a `packaged_task` is queued and later executed by the worker. `packaged_task::operator()` internally catches the resulting `std::bad_function_call` and stores it in its inaccessible shared state rather than propagating it, so the worker’s own exception-capture logic never fires and the caller never learns of the failure. Consider restoring an emptiness check for `std::function`-like inputs (e.g. with `if constexpr (std::is_same_v<std::decay_t<F>, std::function<void()>>)`) or restricting `submit_task` to internal use if direct public calls with empty callables are not supported.</violation>
</file>
<file name="atom/type/rtype.hpp">
<violation number="1" location="atom/type/rtype.hpp:28">
P1: The new `StringSequence` concept allows deserializing into `std::vector<std::string_view>` because `StringType` includes `std::string_view`. `JsonValue::as_string()` returns a `const std::string&` backed by the JSON parser's internal storage. When that storage is destroyed (e.g., the temporary parsed object goes out of scope), the stored `string_view`s become dangling, leading to use-after-free. Consider narrowing `StringSequence` to exclude non-owning string types such as `std::string_view`.</violation>
<violation number="2" location="atom/type/rtype.hpp:144">
P1: The `from_json` and `from_yaml` deserializers cast untrusted parsed `double` values directly to the reflected member type with `static_cast<MemberType>(it->second.as_number())`. Because `Number<MemberType>` matches all arithmetic types (including unsigned and narrow integrals), this introduces undefined behavior when the parsed value is out of range for the destination type, negative for an unsigned member, NaN, or infinity. The optional validator runs after the cast, so it cannot guard against the UB. Range-checking or clamping the double before casting would make deserialization safe.</violation>
</file>
<file name="atom/type/uint.hpp">
<violation number="1" location="atom/type/uint.hpp:30">
P2: The switch from `throw std::out_of_range(...)` to `THROW_OUT_OF_RANGE(...)` changes the exception type from `std::out_of_range` to `atom::error::OutOfRange`, which does **not** derive from `std::out_of_range`. Callers catching `std::out_of_range` will silently miss the exception. The doc comments (`@throws std::out_of_range`) are now stale too. Either update the documentation to `@throws atom::error::OutOfRange` and note the contract change, or keep the standard exception to preserve backward compatibility.</violation>
</file>
<file name="atom/components/core/registry.cpp">
<violation number="1" location="atom/components/core/registry.cpp:523">
P1: The component name derived from `fs::path(path).stem()` will include the conventional Unix `lib` prefix (e.g., `libfoo` for `libfoo.so`), but the expected exported symbols use the bare module name (e.g., `foo_initialize_registry`). This causes symbol lookups to fail on Linux/macOS for conventionally-named shared libraries.
Consider stripping the `lib` prefix from the stem when it is present, or using a platform-aware extraction so the symbol name matches the module's actual export:</violation>
<violation number="2" location="atom/components/core/registry.cpp:558">
P1: The new library loading code in `loadComponentFromFile` correctly opens a `DynamicLibrary` and stores it in `loadedLibraries_[name]`, but the file-change handler `watchComponentChanges` only calls `reinitializeComponent` when a change is detected. `reinitializeComponent` re-runs the cached initializer and cleanup functions without ever unloading the old shared object or loading the updated binary from disk. As a result, the "hot reload" path executes the old library's code after a file change rather than the new one. To support actual hot reloading, the watcher should reload the shared object (updating `loadedLibraries_[name]`) and re-resolve the entry points before reinitializing the component.</violation>
<violation number="3" location="atom/components/core/registry.cpp:562">
P1: Components loaded from a file won't be watched for hot-reload changes because `loadComponentFromFile` never records the source path in `ComponentInfo::configPath`. The file watcher checks that field and silently skips components when it's empty, so any library loaded here will never trigger a reload. Recording `path` in `info.configPath` inside the post-load critical section fixes the gap.</violation>
</file>
<file name="atom/type/iter.hpp">
<violation number="1" location="atom/type/iter.hpp:531">
P1: `ZipIterator` now risks undefined behavior when the primary (first) range is longer than any non-primary range. `operator++()` increments every iterator unconditionally, and `operator*()` dereferences every iterator unconditionally, yet `operator==()` only checks the first iterator against its end. If a later range exhausts first, the next increment and dereference of that iterator are UB. The previous all-tuple comparison also failed for unequal-length ranges, but it caused an infinite loop rather than a memory-safety issue.
Consider either (a) tracking end iterators so termination can stop at the shortest range, or (b) adding runtime assertions in `operator++`/`operator*` when `ENABLE_DEBUG` is defined so callers detect precondition violations early. At minimum, the class docs should state that the first passed range must be the shortest.</violation>
</file>
<file name="atom/type/optional.hpp">
<violation number="1" location="atom/type/optional.hpp:403">
P1: The rvalue-ref-qualified `value_or` now calls `storage_.reset()` after moving the contained value, which destroys the standard library–compatible semantics of this accessor. `std::optional::value_or` and this class's own `value() &&` / `operator*() &&` all return the moved value without disengaging the optional. After this change, code that calls `std::move(opt).value_or(x)` and later checks `opt.has_value()` will unexpectedly see it empty. Remove the `storage_.reset()` line to restore the previous behavior, or introduce a separate named operation if destructive consumption is intentional.</violation>
</file>
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
| @@ -378,7 +415,7 @@ class unordered_flat_map { | |||
| } | |||
| if (table_[idx].dist == dist && | |||
| key_equal_(table_[idx].data.first, key)) { | |||
| return iterator(table_.begin() + idx); | |||
| return iterator(table_.begin() + idx, table_.end()); | |||
There was a problem hiding this comment.
P0: Default-key lookups can return the wrong element because the new empty-slot-skipping iterator is constructed for a bucket that find incorrectly accepts as a match. Empty slots have dist == 0 and a default-constructed key. Because find starts with dist == 0, the termination check table_[idx].dist < dist is false for an empty ideal bucket, so find proceeds to key_equal_. If the searched key happens to be the default value, key_equal_ succeeds and find returns an iterator to the empty bucket. The new iterator constructor then calls skip_empty(), advancing past the empty slot to the next occupied entry. This means find(Key{}) can return an iterator to a completely different key, and at(Key{}) can return the wrong value without throwing. The same issue exists in the const overload.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At atom/type/robin_hood.hpp, line 418:
<comment>Default-key lookups can return the wrong element because the new empty-slot-skipping iterator is constructed for a bucket that `find` incorrectly accepts as a match. Empty slots have `dist == 0` and a default-constructed key. Because `find` starts with `dist == 0`, the termination check `table_[idx].dist < dist` is false for an empty ideal bucket, so `find` proceeds to `key_equal_`. If the searched key happens to be the default value, `key_equal_` succeeds and `find` returns an iterator to the empty bucket. The new iterator constructor then calls `skip_empty()`, advancing past the empty slot to the next occupied entry. This means `find(Key{})` can return an iterator to a completely different key, and `at(Key{})` can return the wrong value without throwing. The same issue exists in the const overload.</comment>
<file context>
@@ -378,7 +415,7 @@ class unordered_flat_map {
if (table_[idx].dist == dist &&
key_equal_(table_[idx].data.first, key)) {
- return iterator(table_.begin() + idx);
+ return iterator(table_.begin() + idx, table_.end());
}
idx = (idx + 1) & mask;
</file context>
| } | ||
| } | ||
| pool_cv.notify_all(); | ||
| for (auto& t : thread_pool) { |
There was a problem hiding this comment.
P0: Calling adjust_thread_pool_size from a task running on this pool's own worker thread causes a self-join: std::thread::join() throws std::system_error(resource_deadlock_would_occur) when invoked on the current thread. The exception leaves stop_pool = true, skips the restart block, and permanently disables the executor. Future submit calls will all reject with "Thread pool is stopped". Consider rejecting (or deferring) resize requests that originate from a worker thread, or skip join() for the calling thread and account for it in the restart logic.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At atom/type/concurrent_map.hpp, line 871:
<comment>Calling `adjust_thread_pool_size` from a task running on this pool's own worker thread causes a self-join: `std::thread::join()` throws `std::system_error(resource_deadlock_would_occur)` when invoked on the current thread. The exception leaves `stop_pool = true`, skips the restart block, and permanently disables the executor. Future `submit` calls will all reject with "Thread pool is stopped". Consider rejecting (or deferring) resize requests that originate from a worker thread, or skip `join()` for the calling thread and account for it in the restart logic.</comment>
<file context>
@@ -784,50 +852,40 @@ class concurrent_map {
- }
+ }
+ pool_cv.notify_all();
+ for (auto& t : thread_pool) {
+ if (t && t->joinable()) {
+ t->join();
</file context>
| data_.end(), key, | ||
| // NOTE: std::lower_bound has no parallel execution-policy overload | ||
| // (unlike sort/find); it is already O(log n) binary search. | ||
| auto it = std::lower_bound(data_.begin(), data_.end(), key, |
There was a problem hiding this comment.
P0: The newly-reachable large-container lookup path uses std::lower_bound on the internal std::vector, but the vector is maintained in insertion order (elements are appended by insert_or_assign and operator[]), not sorted by the comparator. Binary search on an unsorted range produces incorrect results: existing keys can be missed, leading to duplicate insertions and false negatives from find/at/contains. The previous std::execution::par_unseq overload prevented this branch from compiling on conforming implementations; removing the execution policy makes the bug reachable. Consider removing this threshold branch so all non-Boost lookups use linear (or SIMD) search, or alternatively ensure the vector stays sorted on insertion if lower_bound is intended.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At atom/type/flatmap.hpp, line 205:
<comment>The newly-reachable large-container lookup path uses `std::lower_bound` on the internal `std::vector`, but the vector is maintained in insertion order (elements are appended by `insert_or_assign` and `operator[]`), not sorted by the comparator. Binary search on an unsorted range produces incorrect results: existing keys can be missed, leading to duplicate insertions and false negatives from `find`/`at`/`contains`. The previous `std::execution::par_unseq` overload prevented this branch from compiling on conforming implementations; removing the execution policy makes the bug reachable. Consider removing this threshold branch so all non-Boost lookups use linear (or SIMD) search, or alternatively ensure the vector stays sorted on insertion if `lower_bound` is intended.</comment>
<file context>
@@ -199,8 +200,9 @@ class FlatMap {
- data_.end(), key,
+ // NOTE: std::lower_bound has no parallel execution-policy overload
+ // (unlike sort/find); it is already O(log n) binary search.
+ auto it = std::lower_bound(data_.begin(), data_.end(), key,
[this](const auto& pair, const auto& k) {
return comp_(pair.first, k);
</file context>
| // Serialize writers per the threading policy. rehash() does not take a | ||
| // lock itself, so calling it here does not re-enter (the mutex is | ||
| // non-recursive). | ||
| lock_write(); |
There was a problem hiding this comment.
P1: The write lock added to insert() only serializes that single method, leaving every other public access to table_ unprotected. Both overloads of find, at, all iterator accessors, clear, and max_load_factor(float) read or mutate the underlying storage without acquiring lock_read() or lock_write(). In a concurrent workload, a find on one thread can race with an insert + rehash on another because rehash move-assigns table_ while find is still indexing into the old vector. The reader_lock and mutex threading policies are therefore broken for any pattern beyond single-threaded insert-only use. A consistent locking policy should be applied across every public method that touches table_, or the policy enum should be removed if thread safety is not intended.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At atom/type/robin_hood.hpp, line 376:
<comment>The write lock added to `insert()` only serializes that single method, leaving every other public access to `table_` unprotected. Both overloads of `find`, `at`, all iterator accessors, `clear`, and `max_load_factor(float)` read or mutate the underlying storage without acquiring `lock_read()` or `lock_write()`. In a concurrent workload, a `find` on one thread can race with an `insert` + `rehash` on another because `rehash` move-assigns `table_` while `find` is still indexing into the old vector. The `reader_lock` and `mutex` threading policies are therefore broken for any pattern beyond single-threaded `insert`-only use. A consistent locking policy should be applied across every public method that touches `table_`, or the policy enum should be removed if thread safety is not intended.</comment>
<file context>
@@ -339,6 +370,12 @@ class unordered_flat_map {
+ // Serialize writers per the threading policy. rehash() does not take a
+ // lock itself, so calling it here does not re-enter (the mutex is
+ // non-recursive).
+ lock_write();
+ WriteUnlock guard{this};
+
</file context>
|
|
||
| # Hot reload: load components from shared libraries at runtime | ||
| # (Registry::loadComponentFromFile / watchComponentChanges). | ||
| option(ATOM_COMPONENTS_ENABLE_HOT_RELOAD |
There was a problem hiding this comment.
P1: The hot-reload watchComponentChanges stop path can deadlock. watchComponentChanges(false) in atom/components/core/registry.cpp acquires a std::unique_lock at function scope while the async worker can try to acquire a shared_lock on the same mutex between its while check and lock acquisition. If the unique lock is held when the worker tries to get a shared lock, the worker blocks forever, and the caller then blocks forever on fileWatcherFuture_.wait() without ever releasing the unique lock. Consider unlocking the mutex before waiting on the future: release unique_lock before calling fileWatcherFuture_.wait().
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At atom/components/CMakeLists.txt, line 78:
<comment>The hot-reload `watchComponentChanges` stop path can deadlock. `watchComponentChanges(false)` in `atom/components/core/registry.cpp` acquires a `std::unique_lock` at function scope while the async worker can try to acquire a `shared_lock` on the same mutex between its `while` check and lock acquisition. If the unique lock is held when the worker tries to get a shared lock, the worker blocks forever, and the caller then blocks forever on `fileWatcherFuture_.wait()` without ever releasing the unique lock. Consider unlocking the mutex before waiting on the future: release `unique_lock` before calling `fileWatcherFuture_.wait()`.</comment>
<file context>
@@ -73,6 +73,11 @@ option(ATOM_ENABLE_PYTHON "Enable Python scripting support" OFF)
+# Hot reload: load components from shared libraries at runtime
+# (Registry::loadComponentFromFile / watchComponentChanges).
+option(ATOM_COMPONENTS_ENABLE_HOT_RELOAD
+ "Enable runtime loading of components from shared libraries" OFF)
+
</file context>
| lifecycle.initializeAll(); // Call onInitialize() | ||
| lifecycle.updateAll(0.016f); // Call onUpdate() with delta time | ||
| lifecycle.shutdownAll(); // Call onShutdown() | ||
| ATOM_MODULE(my_module, [] { return std::make_shared<MyComponent>("my_module"); }); |
There was a problem hiding this comment.
P2: The ATOM_MODULE example references MyComponent, which is never defined in this rewritten document. The preceding examples all use the Counter class introduced earlier. This makes the registration snippet inconsistent with the surrounding documentation and uncompilable if copied. Consider using Counter here instead, or explicitly defining MyComponent if a distinct example type is intended.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At atom/components/CLAUDE.md, line 149:
<comment>The `ATOM_MODULE` example references `MyComponent`, which is never defined in this rewritten document. The preceding examples all use the `Counter` class introduced earlier. This makes the registration snippet inconsistent with the surrounding documentation and uncompilable if copied. Consider using `Counter` here instead, or explicitly defining `MyComponent` if a distinct example type is intended.</comment>
<file context>
@@ -14,611 +14,284 @@
-lifecycle.initializeAll(); // Call onInitialize()
-lifecycle.updateAll(0.016f); // Call onUpdate() with delta time
-lifecycle.shutdownAll(); // Call onShutdown()
+ATOM_MODULE(my_module, [] { return std::make_shared<MyComponent>("my_module"); });
+// exports C entry points: my_module_initialize_registry / _cleanup_registry /
+// _getInstance / _getVersion — used by Registry::loadComponentFromFile.
</file context>
| ATOM_MODULE(my_module, [] { return std::make_shared<MyComponent>("my_module"); }); | |
| ATOM_MODULE(my_module, [] { return std::make_shared<Counter>("my_module"); }); |
| size_t successCount = 0; | ||
|
|
||
| #ifdef __cpp_lib_parallel_algorithm | ||
| #ifdef ATOM_USE_PARALLEL_ALGORITHMS |
There was a problem hiding this comment.
P2: batchOperation switched from the standard library feature-test macro __cpp_lib_parallel_algorithm to a project-specific ATOM_USE_PARALLEL_ALGORITHMS macro, but that macro is not defined anywhere in the build system or source code. This means builds that previously compiled the std::for_each(std::execution::par_unseq, ...) branch whenever the standard library supported parallel algorithms will now silently fall back to sequential processing for large batches. Either restore the feature-test macro (matching small_vector.hpp and bit.hpp), define ATOM_USE_PARALLEL_ALGORITHMS consistently in the build system, or align all headers on the same guard.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At atom/type/weak_ptr.hpp, line 842:
<comment>`batchOperation` switched from the standard library feature-test macro `__cpp_lib_parallel_algorithm` to a project-specific `ATOM_USE_PARALLEL_ALGORITHMS` macro, but that macro is not defined anywhere in the build system or source code. This means builds that previously compiled the `std::for_each(std::execution::par_unseq, ...)` branch whenever the standard library supported parallel algorithms will now silently fall back to sequential processing for large batches. Either restore the feature-test macro (matching `small_vector.hpp` and `bit.hpp`), define `ATOM_USE_PARALLEL_ALGORITHMS` consistently in the build system, or align all headers on the same guard.</comment>
<file context>
@@ -746,12 +834,12 @@ template <typename T>
size_t successCount = 0;
-#ifdef __cpp_lib_parallel_algorithm
+#ifdef ATOM_USE_PARALLEL_ALGORITHMS
if (parallelThreshold > 0 && weakPtrs.size() >= parallelThreshold) {
std::atomic<size_t> atomicCount{0};
</file context>
| #ifdef ATOM_USE_PARALLEL_ALGORITHMS | |
| #ifdef __cpp_lib_parallel_algorithm |
| add_defines("ATOM_ENABLE_PYTHON=1") | ||
| end | ||
|
|
||
| if has_config("hot_reload") then |
There was a problem hiding this comment.
P2: Enabling hot_reload defines ENABLE_HOT_RELOAD=1, which activates a watcher loop in atom/components/core/registry.cpp that calls std::this_thread::sleep_for. Neither registry.cpp nor registry.hpp directly includes <thread>; they rely on <future> (included conditionally) transitively providing it, which is not portable. Other files in the project that use the same API already include <thread> explicitly (e.g., scripting_api.cpp). To make the hot-reload build reliable across toolchains, add #include <thread> to registry.cpp or registry.hpp.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At atom/components/xmake.lua, line 120:
<comment>Enabling `hot_reload` defines `ENABLE_HOT_RELOAD=1`, which activates a watcher loop in `atom/components/core/registry.cpp` that calls `std::this_thread::sleep_for`. Neither `registry.cpp` nor `registry.hpp` directly includes `<thread>`; they rely on `<future>` (included conditionally) transitively providing it, which is not portable. Other files in the project that use the same API already include `<thread>` explicitly (e.g., `scripting_api.cpp`). To make the hot-reload build reliable across toolchains, add `#include <thread>` to `registry.cpp` or `registry.hpp`.</comment>
<file context>
@@ -111,6 +117,10 @@ target("atom-component")
add_defines("ATOM_ENABLE_PYTHON=1")
end
+ if has_config("hot_reload") then
+ add_defines("ENABLE_HOT_RELOAD=1")
+ end
</file context>
| // std::promise). A std::function parameter would reject them since | ||
| // std::function requires its target to be copy-constructible. | ||
| template <typename F> | ||
| void submit_task(F&& task) { |
There was a problem hiding this comment.
P2: Templating submit_task to accept move-only callables removed the previous guard against empty std::function tasks. When an empty std::function is passed, a packaged_task is queued and later executed by the worker. packaged_task::operator() internally catches the resulting std::bad_function_call and stores it in its inaccessible shared state rather than propagating it, so the worker’s own exception-capture logic never fires and the caller never learns of the failure. Consider restoring an emptiness check for std::function-like inputs (e.g. with if constexpr (std::is_same_v<std::decay_t<F>, std::function<void()>>)) or restricting submit_task to internal use if direct public calls with empty callables are not supported.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At atom/type/concurrent_vector.hpp, line 1021:
<comment>Templating `submit_task` to accept move-only callables removed the previous guard against empty `std::function` tasks. When an empty `std::function` is passed, a `packaged_task` is queued and later executed by the worker. `packaged_task::operator()` internally catches the resulting `std::bad_function_call` and stores it in its inaccessible shared state rather than propagating it, so the worker’s own exception-capture logic never fires and the caller never learns of the failure. Consider restoring an emptiness check for `std::function`-like inputs (e.g. with `if constexpr (std::is_same_v<std::decay_t<F>, std::function<void()>>)`) or restricting `submit_task` to internal use if direct public calls with empty callables are not supported.</comment>
<file context>
@@ -983,12 +1014,12 @@ class concurrent_vector {
+ // std::promise). A std::function parameter would reject them since
+ // std::function requires its target to be copy-constructible.
+ template <typename F>
+ void submit_task(F&& task) {
+ std::packaged_task<void()> packaged(std::forward<F>(task));
{
</file context>
| @@ -24,7 +27,7 @@ constexpr uint32_t MAX_UINT32 = 0xFFFFFFFF; | |||
| */ | |||
| constexpr auto operator""_u8(unsigned long long value) -> uint8_t { | |||
| if (value > MAX_UINT8) { // uint8_t maximum value is 0xFF | |||
| throw std::out_of_range("Value exceeds uint8_t range"); | |||
| THROW_OUT_OF_RANGE("Value exceeds uint8_t range"); | |||
There was a problem hiding this comment.
P2: The switch from throw std::out_of_range(...) to THROW_OUT_OF_RANGE(...) changes the exception type from std::out_of_range to atom::error::OutOfRange, which does not derive from std::out_of_range. Callers catching std::out_of_range will silently miss the exception. The doc comments (@throws std::out_of_range) are now stale too. Either update the documentation to @throws atom::error::OutOfRange and note the contract change, or keep the standard exception to preserve backward compatibility.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At atom/type/uint.hpp, line 30:
<comment>The switch from `throw std::out_of_range(...)` to `THROW_OUT_OF_RANGE(...)` changes the exception type from `std::out_of_range` to `atom::error::OutOfRange`, which does **not** derive from `std::out_of_range`. Callers catching `std::out_of_range` will silently miss the exception. The doc comments (`@throws std::out_of_range`) are now stale too. Either update the documentation to `@throws atom::error::OutOfRange` and note the contract change, or keep the standard exception to preserve backward compatibility.</comment>
<file context>
@@ -24,7 +27,7 @@ constexpr uint32_t MAX_UINT32 = 0xFFFFFFFF;
constexpr auto operator""_u8(unsigned long long value) -> uint8_t {
if (value > MAX_UINT8) { // uint8_t maximum value is 0xFF
- throw std::out_of_range("Value exceeds uint8_t range");
+ THROW_OUT_OF_RANGE("Value exceeds uint8_t range");
}
return static_cast<uint8_t>(value);
</file context>
Summary
Brings the
atom/metatest suite to comprehensive, 1:1-with-headers coverage and fixes two latent bugs inffi.hppuncovered along the way.Tests
tests/meta/reflection/test_refl_field.cpp(coversFieldBase: construction/defaults, member-pointer binding, validators, and the C++23 deducing-thisbuilders incl. derived-type preservation).facade_anystops at its reachable ceiling (~88%): the remaining lines are defensive proxy-callcatchblocks and the never-invokedless_than_impl, unreachable without fault injection.ffi fixes
registerCallbackstored the raw closure type instd::anywhilegetCallback<Sig>retrieved it asstd::function<Sig>; the pointer-formany_castreturnednullptron the mismatch (it does not throw, so the surroundingcatchwas dead code) and the null was wrapped in a success result, so callers dereferenced null and crashed. Now the callable is wrapped in astd::functionwith the deduced signature (sync + async paths), andgetCallbacknull-guards to returnTypeMismatch.getHandle:DynamicLibrary::getHandlenow honours the load strategy likegetFunction(Lazy/Immediate load on access; OnDemand still needs an explicitloadLibrary()).Note on scope
This branch also carries 6 pre-existing branch-sync / pre-commit.ci bot commits that were already on local
devbut not yet on upstreamdev; they appear in the diff but are not part of the substantive change above.🤖 Generated with Claude Code
Summary by cubic
Raises
atom/metaruntime-header coverage to ≥90% and fixes anfficallback crash. Adds hot-reloadable components viameta::DynamicLibrary, switches scripting toPython, and adoptsatom::type::expectedfor serialization results.New Features
Registry::loadComponentFromFileloads modules viameta::DynamicLibrary(opt-inATOM_COMPONENTS_ENABLE_HOT_RELOAD/hot_reload).tests/meta/reflection/test_refl_field.cppand split shared field logic intoatom/meta/refl_field.hpp.componentsevent system withEvent,EventCallback,EventCallbackIdincore/types.hpp; enabled events by default; addedRegistry::registerComponentInstance.Refactors and Fixes
Pythonsupport and updated language detection.atom::type::expected(SerializationOutcome/DeserializationOutcome); structuredSerializationErroradded.atom::type(e.g.,NonCopyable,StaticString,Trackable,PointerSentinel) and updated call sites.componentsoptionally usesatom::containersfast containers whenATOM_USE_BOOST_CONTAINERSis set.CallbackRegistrystoresstd::function<Sig>and returnsTypeMismatchon signature mismatch;DynamicLibrary::getHandlerespects load strategy.componentsbindings moved toscripting/bindings;system/command’s advanced executor merged intoexecutorwith a newcommand/command.hppumbrella.fmt::format; removed deadcomponents/data/type_conversion.hppand the Abseil dependency frompackage.hpp.WARP.md,components/CLAUDE.md) and bumpedpre-commit-hookstov6.0.0.Written for commit f17f583. Summary will update on new commits.