diff --git a/lang/c++/include/avro/GenericDatum.hh b/lang/c++/include/avro/GenericDatum.hh index 1b1c3d9af87..b18acd31e03 100644 --- a/lang/c++/include/avro/GenericDatum.hh +++ b/lang/c++/include/avro/GenericDatum.hh @@ -537,14 +537,26 @@ inline LogicalType GenericDatum::logicalType() const { template T &GenericDatum::value() { - return (type_ == AVRO_UNION) ? std::any_cast(&value_)->datum().value() - : *std::any_cast(&value_); + if (type_ == AVRO_UNION) { + return std::any_cast(&value_)->datum().value(); + } + T *ptr = std::any_cast(&value_); + if (ptr == nullptr) { + throw Exception("Invalid type. Requested C++ type does not match the datum type {}", toString(type_)); + } + return *ptr; } template const T &GenericDatum::value() const { - return (type_ == AVRO_UNION) ? std::any_cast(&value_)->datum().value() - : *std::any_cast(&value_); + if (type_ == AVRO_UNION) { + return std::any_cast(&value_)->datum().value(); + } + const T *ptr = std::any_cast(&value_); + if (ptr == nullptr) { + throw Exception("Invalid type. Requested C++ type does not match the datum type {}", toString(type_)); + } + return *ptr; } inline size_t GenericDatum::unionBranch() const { diff --git a/lang/c++/test/unittest.cc b/lang/c++/test/unittest.cc index b0cb44c5b9f..353b155eefe 100644 --- a/lang/c++/test/unittest.cc +++ b/lang/c++/test/unittest.cc @@ -24,6 +24,7 @@ #include "Compiler.hh" #include "Decoder.hh" #include "Encoder.hh" +#include "GenericDatum.hh" #include "Node.hh" #include "Parser.hh" #include "Schema.hh" @@ -1066,6 +1067,25 @@ void testNestedMapSchema() { BOOST_CHECK_EQUAL(expected, actual.str()); } +// Regression test for AVRO-3194: GenericDatum::value() with a mismatched +// C++ type used to dereference the null pointer returned by std::any_cast, +// causing a segmentation fault. It must now throw an avro::Exception instead. +static void testGenericDatumValueTypeMismatch() { + GenericDatum datum(std::string("hello")); + BOOST_CHECK_EQUAL(datum.type(), AVRO_STRING); + + // Correct type still works. + BOOST_CHECK_EQUAL(datum.value(), std::string("hello")); + + // Mismatched type must throw, not segfault. + BOOST_CHECK_THROW(datum.value(), avro::Exception); + BOOST_CHECK_THROW(datum.value>(), avro::Exception); + + // Same guarantee through the const overload. + const GenericDatum &constDatum = datum; + BOOST_CHECK_THROW(constDatum.value(), avro::Exception); +} + boost::unit_test::test_suite * init_unit_test_suite(int /*argc*/, char * /*argv*/[]) { using namespace boost::unit_test; @@ -1086,6 +1106,7 @@ init_unit_test_suite(int /*argc*/, char * /*argv*/[]) { boost::make_shared())); test->add(BOOST_TEST_CASE(&testNestedArraySchema)); test->add(BOOST_TEST_CASE(&testNestedMapSchema)); + test->add(BOOST_TEST_CASE(&testGenericDatumValueTypeMismatch)); return test; }