From 840f83801ed12210fe041bb747267f10a96014ac Mon Sep 17 00:00:00 2001 From: Arthur Chan Date: Tue, 3 Nov 2026 08:22:52 +0000 Subject: [PATCH 1/2] OSS-Fuzz: Add new fuzzer/corpus/dict targets tree convertion Signed-off-by: Arthur Chan --- cmake/fuzzing_build.cmake | 2 +- fuzzing/bt_convert_fuzzer.cpp | 391 ++++++++++++++++++ fuzzing/corpus/bt_convert_fuzzer/bool-yes | Bin 0 -> 13 bytes fuzzing/corpus/bt_convert_fuzzer/double-nan | Bin 0 -> 12 bytes .../corpus/bt_convert_fuzzer/enum-nodetype | Bin 0 -> 18 bytes fuzzing/corpus/bt_convert_fuzzer/enum-status | Bin 0 -> 16 bytes .../corpus/bt_convert_fuzzer/float-overflow | Bin 0 -> 14 bytes fuzzing/corpus/bt_convert_fuzzer/int-max | Bin 0 -> 28 bytes .../corpus/bt_convert_fuzzer/int-underflow | Bin 0 -> 29 bytes fuzzing/corpus/bt_convert_fuzzer/junk-mixed | Bin 0 -> 26 bytes fuzzing/corpus/bt_convert_fuzzer/queue-int | Bin 0 -> 14 bytes fuzzing/corpus/bt_convert_fuzzer/uint-max | Bin 0 -> 29 bytes fuzzing/corpus/bt_convert_fuzzer/vec-bool | Bin 0 -> 23 bytes fuzzing/corpus/bt_convert_fuzzer/vec-double | Bin 0 -> 23 bytes fuzzing/corpus/bt_convert_fuzzer/vec-int | Bin 0 -> 17 bytes fuzzing/corpus/bt_convert_fuzzer/vec-str | Bin 0 -> 14 bytes fuzzing/dictionaries/bt_convert_fuzzer.dict | 50 +++ 17 files changed, 442 insertions(+), 1 deletion(-) create mode 100644 fuzzing/bt_convert_fuzzer.cpp create mode 100644 fuzzing/corpus/bt_convert_fuzzer/bool-yes create mode 100644 fuzzing/corpus/bt_convert_fuzzer/double-nan create mode 100644 fuzzing/corpus/bt_convert_fuzzer/enum-nodetype create mode 100644 fuzzing/corpus/bt_convert_fuzzer/enum-status create mode 100644 fuzzing/corpus/bt_convert_fuzzer/float-overflow create mode 100644 fuzzing/corpus/bt_convert_fuzzer/int-max create mode 100644 fuzzing/corpus/bt_convert_fuzzer/int-underflow create mode 100644 fuzzing/corpus/bt_convert_fuzzer/junk-mixed create mode 100644 fuzzing/corpus/bt_convert_fuzzer/queue-int create mode 100644 fuzzing/corpus/bt_convert_fuzzer/uint-max create mode 100644 fuzzing/corpus/bt_convert_fuzzer/vec-bool create mode 100644 fuzzing/corpus/bt_convert_fuzzer/vec-double create mode 100644 fuzzing/corpus/bt_convert_fuzzer/vec-int create mode 100644 fuzzing/corpus/bt_convert_fuzzer/vec-str create mode 100644 fuzzing/dictionaries/bt_convert_fuzzer.dict diff --git a/cmake/fuzzing_build.cmake b/cmake/fuzzing_build.cmake index 43f367001..7c7cb0f2c 100644 --- a/cmake/fuzzing_build.cmake +++ b/cmake/fuzzing_build.cmake @@ -106,7 +106,7 @@ endfunction() # Function to add fuzzing targets - compatible with both local and OSS-Fuzz builds function(add_fuzzing_targets) - set(FUZZERS bt_fuzzer script_fuzzer bb_fuzzer) + set(FUZZERS bt_fuzzer script_fuzzer bb_fuzzer bt_convert_fuzzer) foreach(fuzzer ${FUZZERS}) add_executable(${fuzzer} fuzzing/${fuzzer}.cpp) diff --git a/fuzzing/bt_convert_fuzzer.cpp b/fuzzing/bt_convert_fuzzer.cpp new file mode 100644 index 000000000..7bfd8b142 --- /dev/null +++ b/fuzzing/bt_convert_fuzzer.cpp @@ -0,0 +1,391 @@ +// Fuzz target: bt_convert_fuzzer +// +// Threat model: every XML attribute, script literal and blackboard value that +// enters a BehaviorTree.CPP application passes through BT::convertFromString +// (port parsing), SafeAny/Any type-erased casts, or typed blackboard access. +// These string->type and type->type conversions are the numeric- and +// string-parsing funnel for untrusted data (attacker-supplied XML documents, +// Groot files, scripting expressions). polymorphic_cast_registry powers +// shared_ptr port compatibility (Issue #943) and is dark in production +// coverage (220 lines, 0.9%). +// +// Strategy (component-API harness, typed inputs): +// 1. convertFromString over the full builtin type matrix with a fuzzed +// string (numerics incl. int8..64/uint8..64/float/double overflow and +// underflow, bool, enums NodeStatus/NodeType/PortDirection, split +// containers vector, SharedQueue), +// 2. Any value storage + tryCast/cast cross-type matrix, +// 3. PolymorphicCastRegistry upcasts/downcasts/transitive chains over a +// harness-defined class hierarchy (Animal/Cat/Dog/Sphynx), including via +// a blackboard with the registry attached, +// 4. blackboard typed set/get mismatch matrix. +// +// All thrown BT exceptions (RuntimeError/LogicError on invalid input) are +// expected and caught; the point is exercising the conversion code paths, not +// crashing on them. +// +// Expected bug classes: integer/float overflow and truncation handling, +// out-of-bounds reads in split-string parsing, enum parsing mismatches, +// type-confusion in Any casts, registry lookup errors on shared_ptr ports. + +#include "behaviortree_cpp/basic_types.h" +#include "behaviortree_cpp/blackboard.h" +#include "behaviortree_cpp/decorators/loop_node.h" +#include "behaviortree_cpp/utils/polymorphic_cast_registry.hpp" +#include "behaviortree_cpp/utils/safe_any.hpp" + +#include + +#include +#include +#include + +namespace +{ + +// Harness-defined hierarchy for the polymorphic cast registry. No upstream +// product code is involved: the registry API is public and standalone. +struct Animal +{ + virtual ~Animal() = default; + virtual const char* species() const + { + return "Animal"; + } +}; +struct Cat : Animal +{ + const char* species() const override + { + return "Cat"; + } +}; +struct Dog : Animal +{ + const char* species() const override + { + return "Dog"; + } +}; +struct Sphynx : Cat +{ + const char* species() const override + { + return "Sphynx"; + } +}; + +using AnimalPtr = std::shared_ptr; +using CatPtr = std::shared_ptr; +using DogPtr = std::shared_ptr; +using SphynxPtr = std::shared_ptr; + +} // namespace + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) +{ + if(size < 8) + { + return 0; + } + FuzzedDataProvider fdp(data, size); + + const std::string text = fdp.ConsumeRandomLengthString(); + const BT::StringView sv(text); + + // ---- Stage 1: convertFromString over the builtin type matrix + try + { + switch(fdp.ConsumeIntegralInRange(0, 19)) + { + case 0: + (void)BT::convertFromString(sv); + break; + case 1: + (void)BT::convertFromString(sv); + break; + case 2: + (void)BT::convertFromString(sv); + break; + case 3: + (void)BT::convertFromString(sv); + break; + case 4: + (void)BT::convertFromString(sv); + break; + case 5: + (void)BT::convertFromString(sv); + break; + case 6: + (void)BT::convertFromString(sv); + break; + case 7: + (void)BT::convertFromString(sv); + break; + case 8: + (void)BT::convertFromString(sv); + break; + case 9: + (void)BT::convertFromString(sv); + break; + case 10: + (void)BT::convertFromString(sv); + break; + case 11: + (void)BT::convertFromString(sv); + break; + case 12: + (void)BT::convertFromString(sv); + break; + case 13: + (void)BT::convertFromString(sv); + break; + case 14: + (void)BT::convertFromString(sv); + break; + case 15: + (void)BT::convertFromString>(sv); + break; + case 16: + (void)BT::convertFromString>(sv); + break; + case 17: + (void)BT::convertFromString>(sv); + break; + case 18: + (void)BT::convertFromString>(sv); + break; + default: + (void)BT::convertFromString>(sv); + break; + } + } + catch(const std::exception&) + { + // invalid numeric/enum input raises RuntimeError: expected + } + + // Stages 2-4 are wrapped so that BT exceptions raised by the conversion + // APIs are contained per-iteration and never escape LLVMFuzzerTestOneInput. + try + { + // ---- Stage 2: Any storage + cross-type cast matrix + { + BT::Any any; + switch(fdp.ConsumeIntegralInRange(0, 2)) + { + case 0: + any = BT::Any(text); + break; + case 1: + any = BT::Any(static_cast(text.size()) * 1000000); + break; + default: + any = BT::Any(static_cast(text.size()) * 1.5); + break; + } + + (void)any.isNumber(); + (void)any.isIntegral(); + (void)any.isString(); + + switch(fdp.ConsumeIntegralInRange(0, 5)) + { + case 0: + { + auto res = any.tryCast(); + (void)res.has_value(); + break; + } + case 1: + { + auto res = any.tryCast(); + (void)res.has_value(); + break; + } + case 2: + { + auto res = any.tryCast(); + (void)res.has_value(); + break; + } + case 3: + { + auto res = any.tryCast(); + (void)res.has_value(); + break; + } + case 4: + { + auto res = any.tryCast(); + (void)res.has_value(); + break; + } + default: + { + // cast() throws RuntimeError on a lossy/invalid cast + try + { + (void)any.cast(); + } + catch(const std::exception&) + { + // expected on invalid casts + } + break; + } + } + } + + // ---- Stage 3: polymorphic cast registry + { + BT::PolymorphicCastRegistry registry; + registry.registerCast(); + registry.registerCast(); + registry.registerCast(); + + BT::Any animal_any; + switch(fdp.ConsumeIntegralInRange(0, 3)) + { + case 0: + animal_any = BT::Any(AnimalPtr(std::make_shared())); + break; + case 1: + animal_any = BT::Any(CatPtr(std::make_shared())); + break; + case 2: + animal_any = BT::Any(DogPtr(std::make_shared())); + break; + default: + animal_any = BT::Any(SphynxPtr(std::make_shared())); + break; + } + + switch(fdp.ConsumeIntegralInRange(0, 4)) + { + case 0: + { + auto res = animal_any.tryCastWithRegistry(registry); + (void)res.has_value(); + break; + } + case 1: + { + auto res = animal_any.tryCastWithRegistry(registry); + (void)res.has_value(); + break; + } + case 2: + { + auto res = animal_any.tryCastWithRegistry(registry); + (void)res.has_value(); + break; + } + case 3: + { + auto res = animal_any.tryCastWithRegistry(registry); + (void)res.has_value(); + break; + } + default: + { + try + { + auto p = animal_any.cast(); + (void)p; + } + catch(const std::exception&) + { + // expected when the stored type is not Cat-compatible + } + break; + } + } + + // Blackboard with an attached registry: typed set/get through the + // polymorphic path (Issue #943 usage pattern). + auto reg2 = std::make_shared(); + reg2->registerCast(); + reg2->registerCast(); + auto bb_poly = BT::Blackboard::create(); + bb_poly->setPolymorphicCastRegistry(reg2); + bb_poly->set("obj", CatPtr(std::make_shared())); + try + { + auto up = bb_poly->get("obj"); + (void)up; + } + catch(const std::exception&) + { + // expected when the registry cannot satisfy the cast + } + try + { + auto down = bb_poly->get("obj"); + (void)down; + } + catch(const std::exception&) + { + // expected on unsupported downcasts + } + } + + // ---- Stage 4: blackboard typed set/get mismatch matrix + { + auto bb = BT::Blackboard::create(); + switch(fdp.ConsumeIntegralInRange(0, 3)) + { + case 0: + bb->set("k", static_cast(text.size())); + break; + case 1: + bb->set("k", static_cast(text.size())); + break; + case 2: + bb->set("k", text); + break; + default: + bb->set("k", !text.empty()); + break; + } + const auto fetch = [&bb](auto type_tag) { + using T = decltype(type_tag); + try + { + // typed get: mismatched entry type raises RuntimeError (expected) + (void)bb->get("k"); + } + catch(const std::exception&) + { + // expected: type mismatch or missing key + } + }; + switch(fdp.ConsumeIntegralInRange(0, 4)) + { + case 0: + fetch(int64_t{}); + break; + case 1: + fetch(double{}); + break; + case 2: + fetch(std::string{}); + break; + case 3: + fetch(bool{}); + break; + default: + fetch(int64_t{}); // on a missing key: RuntimeError path + break; + } + } + } + catch(const std::exception&) + { + // expected: Any/registry/blackboard APIs raise RuntimeError on + // lossy or mismatched conversions + } + + return 0; +} + diff --git a/fuzzing/corpus/bt_convert_fuzzer/bool-yes b/fuzzing/corpus/bt_convert_fuzzer/bool-yes new file mode 100644 index 0000000000000000000000000000000000000000..e433dab88d2e92bc998469f874dcc0bbed84ab41 GIT binary patch literal 13 UcmXRZDou^4XJ%$#WM<$702{Reu>b%7 literal 0 HcmV?d00001 diff --git a/fuzzing/corpus/bt_convert_fuzzer/double-nan b/fuzzing/corpus/bt_convert_fuzzer/double-nan new file mode 100644 index 0000000000000000000000000000000000000000..7c4a54e405c5fb97da5fc74468a59e6febc99bea GIT binary patch literal 12 Tcmc~x%!{dKU}RumVd4S+6#oJ$ literal 0 HcmV?d00001 diff --git a/fuzzing/corpus/bt_convert_fuzzer/enum-nodetype b/fuzzing/corpus/bt_convert_fuzzer/enum-nodetype new file mode 100644 index 0000000000000000000000000000000000000000..e2a72a1700083303e0cf236eea75c1a45f4f1ea5 GIT binary patch literal 18 ZcmZ=sP0lY$EXgm5sb^qfU}k3E1pqm81e5>( literal 0 HcmV?d00001 diff --git a/fuzzing/corpus/bt_convert_fuzzer/enum-status b/fuzzing/corpus/bt_convert_fuzzer/enum-status new file mode 100644 index 0000000000000000000000000000000000000000..8da63d33c98a18a43751c758d464b1e22fe1c65b GIT binary patch literal 16 XcmWFzb#`_Q4vwj3U}9imX5aw;BD4bt literal 0 HcmV?d00001 diff --git a/fuzzing/corpus/bt_convert_fuzzer/float-overflow b/fuzzing/corpus/bt_convert_fuzzer/float-overflow new file mode 100644 index 0000000000000000000000000000000000000000..cd90582b2e8f6b8f503b49835d88498072a4d1b1 GIT binary patch literal 14 VcmXp^wY0R1sb^wjU|?e61OOJ10xbXl literal 0 HcmV?d00001 diff --git a/fuzzing/corpus/bt_convert_fuzzer/int-max b/fuzzing/corpus/bt_convert_fuzzer/int-max new file mode 100644 index 0000000000000000000000000000000000000000..21e6b06899faa8a47252b2486e728cd1f31ba27b GIT binary patch literal 28 jcmcCCGBP$cH!?6bvoJL=H#fB~FpsHcWME)qWMlyVQC|ei literal 0 HcmV?d00001 diff --git a/fuzzing/corpus/bt_convert_fuzzer/int-underflow b/fuzzing/corpus/bt_convert_fuzzer/int-underflow new file mode 100644 index 0000000000000000000000000000000000000000..461be1bad879ec7f4802540f04b55c7768bca1de GIT binary patch literal 29 kcmdP3G%_+aHa9XbHnT7_F*i50FtCiNXJBGrVq{1n&R< literal 0 HcmV?d00001 diff --git a/fuzzing/corpus/bt_convert_fuzzer/queue-int b/fuzzing/corpus/bt_convert_fuzzer/queue-int new file mode 100644 index 0000000000000000000000000000000000000000..9c0cbaac68f815659eb983739ce62cd9e5dbe129 GIT binary patch literal 14 TcmXrFwy?I0sb^$h1R`Mo6w3k; literal 0 HcmV?d00001 diff --git a/fuzzing/corpus/bt_convert_fuzzer/uint-max b/fuzzing/corpus/bt_convert_fuzzer/uint-max new file mode 100644 index 0000000000000000000000000000000000000000..cf87620535767ea2e7c7f6ed12ebe4069ec53411 GIT binary patch literal 29 kcmXrCFflPRH!(3VH#Rr0G&MCeGc=8f+h^c2{WME)q5&!@<%>(2B literal 0 HcmV?d00001 diff --git a/fuzzing/corpus/bt_convert_fuzzer/vec-int b/fuzzing/corpus/bt_convert_fuzzer/vec-int new file mode 100644 index 0000000000000000000000000000000000000000..81af62b80b99795037c0e76b9014e8d8ed810991 GIT binary patch literal 17 YcmXrCHnKLh)-{Q#XJlYtWMbq803Ff-m;e9( literal 0 HcmV?d00001 diff --git a/fuzzing/corpus/bt_convert_fuzzer/vec-str b/fuzzing/corpus/bt_convert_fuzzer/vec-str new file mode 100644 index 0000000000000000000000000000000000000000..ce9ab17fb7632ba54777e868111497d692505b4d GIT binary patch literal 14 VcmYeaPO?spsb^qfU|?nt0stDd0*?Ry literal 0 HcmV?d00001 diff --git a/fuzzing/dictionaries/bt_convert_fuzzer.dict b/fuzzing/dictionaries/bt_convert_fuzzer.dict new file mode 100644 index 000000000..4abcc6d60 --- /dev/null +++ b/fuzzing/dictionaries/bt_convert_fuzzer.dict @@ -0,0 +1,50 @@ +# Dictionary for bt_convert_fuzzer: numeric forms, enum names, separators. +"9223372036854775807" +"-9223372036854775808" +"18446744073709551615" +"0" +"-1" +"+1" +"007" +"0x1F" +"0b101" +"1e10" +"1E-10" +"1e999" +"-1e999" +"nan" +"inf" +"-inf" +"3.14159" +".5" +"5." +"1.0e-400" +"true" +"false" +"TRUE" +"False" +"1;2;3" +"-4;5;6" +"1.5;2.5" +"a;b;c" +"true;false" +"SUCCESS" +"FAILURE" +"RUNNING" +"SKIPPED" +"IDLE" +"Action" +"Condition" +"Control" +"Decorator" +"SubTree" +"UNDEFINED" +"INPUT" +"OUTPUT" +"BIDIRECTIONAL" +" " +" 42 " +";;" +"--7" +"12abc" +"1;2;3;" From 849d49480c4af382cbd800a42a09657c3ec465aa Mon Sep 17 00:00:00 2001 From: Arthur Chan Date: Tue, 3 Nov 2026 09:54:13 +0000 Subject: [PATCH 2/2] Fix formatting Signed-off-by: Arthur Chan --- fuzzing/bt_convert_fuzzer.cpp | 354 ++++++++++++++++------------------ 1 file changed, 171 insertions(+), 183 deletions(-) diff --git a/fuzzing/bt_convert_fuzzer.cpp b/fuzzing/bt_convert_fuzzer.cpp index 7bfd8b142..e5dfea93d 100644 --- a/fuzzing/bt_convert_fuzzer.cpp +++ b/fuzzing/bt_convert_fuzzer.cpp @@ -34,12 +34,12 @@ #include "behaviortree_cpp/utils/polymorphic_cast_registry.hpp" #include "behaviortree_cpp/utils/safe_any.hpp" -#include - #include #include #include +#include + namespace { @@ -169,217 +169,206 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) // APIs are contained per-iteration and never escape LLVMFuzzerTestOneInput. try { - // ---- Stage 2: Any storage + cross-type cast matrix - { - BT::Any any; - switch(fdp.ConsumeIntegralInRange(0, 2)) - { - case 0: - any = BT::Any(text); - break; - case 1: - any = BT::Any(static_cast(text.size()) * 1000000); - break; - default: - any = BT::Any(static_cast(text.size()) * 1.5); - break; - } - - (void)any.isNumber(); - (void)any.isIntegral(); - (void)any.isString(); - - switch(fdp.ConsumeIntegralInRange(0, 5)) + // ---- Stage 2: Any storage + cross-type cast matrix { - case 0: + BT::Any any; + switch(fdp.ConsumeIntegralInRange(0, 2)) { - auto res = any.tryCast(); - (void)res.has_value(); - break; - } - case 1: - { - auto res = any.tryCast(); - (void)res.has_value(); - break; - } - case 2: - { - auto res = any.tryCast(); - (void)res.has_value(); - break; + case 0: + any = BT::Any(text); + break; + case 1: + any = BT::Any(static_cast(text.size()) * 1000000); + break; + default: + any = BT::Any(static_cast(text.size()) * 1.5); + break; } - case 3: + + (void)any.isNumber(); + (void)any.isIntegral(); + (void)any.isString(); + + switch(fdp.ConsumeIntegralInRange(0, 5)) { - auto res = any.tryCast(); - (void)res.has_value(); - break; + case 0: { + auto res = any.tryCast(); + (void)res.has_value(); + break; + } + case 1: { + auto res = any.tryCast(); + (void)res.has_value(); + break; + } + case 2: { + auto res = any.tryCast(); + (void)res.has_value(); + break; + } + case 3: { + auto res = any.tryCast(); + (void)res.has_value(); + break; + } + case 4: { + auto res = any.tryCast(); + (void)res.has_value(); + break; + } + default: { + // cast() throws RuntimeError on a lossy/invalid cast + try + { + (void)any.cast(); + } + catch(const std::exception&) + { + // expected on invalid casts + } + break; + } } - case 4: + } + + // ---- Stage 3: polymorphic cast registry + { + BT::PolymorphicCastRegistry registry; + registry.registerCast(); + registry.registerCast(); + registry.registerCast(); + + BT::Any animal_any; + switch(fdp.ConsumeIntegralInRange(0, 3)) { - auto res = any.tryCast(); - (void)res.has_value(); - break; + case 0: + animal_any = BT::Any(AnimalPtr(std::make_shared())); + break; + case 1: + animal_any = BT::Any(CatPtr(std::make_shared())); + break; + case 2: + animal_any = BT::Any(DogPtr(std::make_shared())); + break; + default: + animal_any = BT::Any(SphynxPtr(std::make_shared())); + break; } - default: + + switch(fdp.ConsumeIntegralInRange(0, 4)) { - // cast() throws RuntimeError on a lossy/invalid cast - try - { - (void)any.cast(); + case 0: { + auto res = animal_any.tryCastWithRegistry(registry); + (void)res.has_value(); + break; } - catch(const std::exception&) - { - // expected on invalid casts + case 1: { + auto res = animal_any.tryCastWithRegistry(registry); + (void)res.has_value(); + break; + } + case 2: { + auto res = animal_any.tryCastWithRegistry(registry); + (void)res.has_value(); + break; + } + case 3: { + auto res = animal_any.tryCastWithRegistry(registry); + (void)res.has_value(); + break; + } + default: { + try + { + auto p = animal_any.cast(); + (void)p; + } + catch(const std::exception&) + { + // expected when the stored type is not Cat-compatible + } + break; } - break; } - } - } - - // ---- Stage 3: polymorphic cast registry - { - BT::PolymorphicCastRegistry registry; - registry.registerCast(); - registry.registerCast(); - registry.registerCast(); - - BT::Any animal_any; - switch(fdp.ConsumeIntegralInRange(0, 3)) - { - case 0: - animal_any = BT::Any(AnimalPtr(std::make_shared())); - break; - case 1: - animal_any = BT::Any(CatPtr(std::make_shared())); - break; - case 2: - animal_any = BT::Any(DogPtr(std::make_shared())); - break; - default: - animal_any = BT::Any(SphynxPtr(std::make_shared())); - break; - } - switch(fdp.ConsumeIntegralInRange(0, 4)) - { - case 0: + // Blackboard with an attached registry: typed set/get through the + // polymorphic path (Issue #943 usage pattern). + auto reg2 = std::make_shared(); + reg2->registerCast(); + reg2->registerCast(); + auto bb_poly = BT::Blackboard::create(); + bb_poly->setPolymorphicCastRegistry(reg2); + bb_poly->set("obj", CatPtr(std::make_shared())); + try { - auto res = animal_any.tryCastWithRegistry(registry); - (void)res.has_value(); - break; + auto up = bb_poly->get("obj"); + (void)up; } - case 1: + catch(const std::exception&) { - auto res = animal_any.tryCastWithRegistry(registry); - (void)res.has_value(); - break; + // expected when the registry cannot satisfy the cast } - case 2: + try { - auto res = animal_any.tryCastWithRegistry(registry); - (void)res.has_value(); - break; + auto down = bb_poly->get("obj"); + (void)down; } - case 3: + catch(const std::exception&) { - auto res = animal_any.tryCastWithRegistry(registry); - (void)res.has_value(); - break; + // expected on unsupported downcasts } - default: + } + + // ---- Stage 4: blackboard typed set/get mismatch matrix + { + auto bb = BT::Blackboard::create(); + switch(fdp.ConsumeIntegralInRange(0, 3)) { + case 0: + bb->set("k", static_cast(text.size())); + break; + case 1: + bb->set("k", static_cast(text.size())); + break; + case 2: + bb->set("k", text); + break; + default: + bb->set("k", !text.empty()); + break; + } + const auto fetch = [&bb](auto type_tag) { + using T = decltype(type_tag); try { - auto p = animal_any.cast(); - (void)p; + // typed get: mismatched entry type raises RuntimeError (expected) + (void)bb->get("k"); } catch(const std::exception&) { - // expected when the stored type is not Cat-compatible + // expected: type mismatch or missing key } - break; - } - } - - // Blackboard with an attached registry: typed set/get through the - // polymorphic path (Issue #943 usage pattern). - auto reg2 = std::make_shared(); - reg2->registerCast(); - reg2->registerCast(); - auto bb_poly = BT::Blackboard::create(); - bb_poly->setPolymorphicCastRegistry(reg2); - bb_poly->set("obj", CatPtr(std::make_shared())); - try - { - auto up = bb_poly->get("obj"); - (void)up; - } - catch(const std::exception&) - { - // expected when the registry cannot satisfy the cast - } - try - { - auto down = bb_poly->get("obj"); - (void)down; - } - catch(const std::exception&) - { - // expected on unsupported downcasts - } - } - - // ---- Stage 4: blackboard typed set/get mismatch matrix - { - auto bb = BT::Blackboard::create(); - switch(fdp.ConsumeIntegralInRange(0, 3)) - { - case 0: - bb->set("k", static_cast(text.size())); - break; - case 1: - bb->set("k", static_cast(text.size())); - break; - case 2: - bb->set("k", text); - break; - default: - bb->set("k", !text.empty()); - break; - } - const auto fetch = [&bb](auto type_tag) { - using T = decltype(type_tag); - try + }; + switch(fdp.ConsumeIntegralInRange(0, 4)) { - // typed get: mismatched entry type raises RuntimeError (expected) - (void)bb->get("k"); + case 0: + fetch(int64_t{}); + break; + case 1: + fetch(double{}); + break; + case 2: + fetch(std::string{}); + break; + case 3: + fetch(bool{}); + break; + default: + fetch(int64_t{}); // on a missing key: RuntimeError path + break; } - catch(const std::exception&) - { - // expected: type mismatch or missing key - } - }; - switch(fdp.ConsumeIntegralInRange(0, 4)) - { - case 0: - fetch(int64_t{}); - break; - case 1: - fetch(double{}); - break; - case 2: - fetch(std::string{}); - break; - case 3: - fetch(bool{}); - break; - default: - fetch(int64_t{}); // on a missing key: RuntimeError path - break; } } - } catch(const std::exception&) { // expected: Any/registry/blackboard APIs raise RuntimeError on @@ -388,4 +377,3 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) return 0; } -