// bslim_fuzzutil.h -*-C++-*- #ifndef INCLUDED_BSLIM_FUZZUTIL #define INCLUDED_BSLIM_FUZZUTIL #include <bsls_ident.h> BSLS_IDENT("$Id: $") //@PURPOSE: Provide fuzz test utilities for basic types. // //@CLASSES: // bslim::FuzzUtil: functions to create basic types from fuzz data // //@SEE_ALSO: bslim_fuzzdataview // //@DESCRIPTION: This component provides a namespace, 'bslim::FuzzUtil', // containing functions that create fundamental and standard library types from // fuzz data provided by a fuzz harness (e.g., 'libFuzzer'). // // See {http://bburl/BDEFuzzTesting} for details on how to build and run with // fuzz testing enabled. // ///Usage ///----- // This section illustrates intended use of this component. // ///Example 1: Consuming Integers in a Range to Pass to an Interface /// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Suppose we wish to fuzz test a function with preconditions. // // First, we define the 'TradingInterfaceUnderTest' 'struct': //.. // struct TradingInterfaceUnderTest { // // This utility class provides sample functionality to demonstrate how // // fuzz data might be used. // // // CLASS METHODS // static int numEarningsAnnouncements(int year, int month) // // Return a value containing the number of earnings announcements // // in the specified 'year' and 'month'. The behavior is undefined // // unless '1950 < year < 2030' and 'month' is in '[1 .. 12]'. Note // // that the values here are arbitrary, and in the real-world this // // data would be obtained from a database or an API. // { // BSLS_ASSERT(1950 < year && year < 2030); // BSLS_ASSERT( 1 <= month && month <= 12); // // if (2020 < year && 6 < month) { // return 11; // RETURN // } // return 6; // } // }; //.. // Then, we need a block of raw bytes. This would normally come from a fuzz // harness (e.g., the 'LLVMFuzzerTestOneInput' entry point function from // 'libFuzzer'). Since 'libFuzzer' is not available here, we initialize a // 'myFuzzData' array that we will use instead. //.. // const bsl::uint8_t myFuzzData[] = {0x43, 0x19, 0x0D, 0x44, 0x37, 0x0D, // 0x38, 0x5E, 0x9B, 0xAA, 0xF3, 0xDA}; //.. // Next, we create a 'FuzzDataView' to wrap the raw bytes. //.. // bslim::FuzzDataView fdv(myFuzzData, sizeof myFuzzData); //.. // Now, we pass this 'FuzzDataView' to 'FuzzUtil' to generate values within the // permissible range of the function under test: //.. // int month = bslim::FuzzUtil::consumeNumberInRange<int>(&fdv, 1, 12); // int year = bslim::FuzzUtil::consumeNumberInRange<int>(&fdv, 1951, 2029); // assert( 1 <= month && month <= 12); // assert(1951 <= year && year <= 2029); //.. // Finally, we can use these 'int' values to pass to a function that returns // the number of earnings announcements scheduled in a given month. //.. // int numEarnings = // TradingInterfaceUnderTest::numEarningsAnnouncements(year, month); // (void) numEarnings; //.. #include <bslscm_version.h> #include <bslim_fuzzdataview.h> #include <bslmf_assert.h> #include <bsls_assert.h> #include <bsls_libraryfeatures.h> #include <bsls_types.h> // 'bsls::Types::Uint64' #include <bsl_cmath.h> // 'bsl::isfinite' #include <bsl_cstdint.h> // 'bsl::uint8_t' #include <bsl_limits.h> // 'bsl::numeric_limits' #include <bsl_string.h> #include <bsl_type_traits.h> // 'bsl::is_same' #include <bsl_vector.h> #ifdef BSLS_LIBRARYFEATURES_HAS_CPP17_PMR #include <memory_resource> #endif #include <string> #include <vector> namespace BloombergLP { namespace bslim { // =============== // struct FuzzUtil // =============== struct FuzzUtil { // This utility 'struct' provides a namespace for a suite of functions // operating on objects of type 'FuzzDataView'and providing the consumption // of fuzz data bytes into fundamental and standard library types. // CLASS METHODS static bool consumeBool(FuzzDataView *fuzzDataView); // Return a 'bool' value based upon consuming a single byte from the // specified 'fuzzDataView'. If 'fuzzDataView->length()' is 0, return // 'false'. template <class TYPE> static typename bsl::enable_if<bsl::is_integral<TYPE>::value, TYPE>::type consumeNumber(FuzzDataView *fuzzDataView); template <class TYPE> static typename bsl::enable_if<bsl::is_floating_point<TYPE>::value, TYPE>::type consumeNumber(FuzzDataView *fuzzDataView); // Return a value of (template parameter) 'TYPE' in the range // [min .. max] -- where 'min' and 'max' are the minimum and maximum // values representable by the 'TYPE' -- based on at most the next // 'sizeof(TYPE) + 1' bytes from the specified 'fuzzDataView', and // update 'fuzzDataView' to reflect the bytes consumed. If // '0 == fuzzDataView->length()', return the minimum value of 'TYPE'. // This function does not participate in overload resolution unless // either 'bsl::is_integral<TYPE>::value' or // 'bsl::is_floating_point<TYPE>::value' is 'true'. The behavior is // undefined if 'bsl::is_same<TYPE, bool>::value' or // 'bsl::is_same<TYPE, long double>' is 'true'. template <class TYPE> static typename bsl::enable_if<bsl::is_integral<TYPE>::value, TYPE>::type consumeNumberInRange(FuzzDataView *fuzzDataView, TYPE min, TYPE max); template <class TYPE> static typename bsl::enable_if<bsl::is_floating_point<TYPE>::value, TYPE>::type consumeNumberInRange(FuzzDataView *fuzzDataView, TYPE min, TYPE max); // Return a value of (template parameter) 'TYPE' in the specified range // [min .. max] based on at most the next 'sizeof(TYPE) + 1' bytes from // the specified 'fuzzDataView', and update 'fuzzDataView' to reflect // the bytes consumed. If '0 == fuzzDataView->length()', return the // specified 'min'. This function does not participate in overload // resolution unless either 'bsl::is_integral<TYPE>::value' or // 'bsl::is_floating_point<TYPE>::value' is 'true'. The behavior is // undefined if 'min > max', 'min' or 'max' is not finite, or either // 'bsl::is_same<TYPE, bool>::value' or // 'bsl::is_same<TYPE, long double>' is 'true'. static void consumeRandomLengthChars(bsl::vector<char> *output, FuzzDataView *fuzzDataView, bsl::size_t maxLength); static void consumeRandomLengthChars(std::vector<char> *output, FuzzDataView *fuzzDataView, bsl::size_t maxLength); #ifdef BSLS_LIBRARYFEATURES_HAS_CPP17_PMR static void consumeRandomLengthChars(std::pmr::vector<char> *output, FuzzDataView *fuzzDataView, bsl::size_t maxLength); #endif // Load into the specified 'output' a sequence of characters of length from // 0 to the specified 'maxLength'. If the specified 'fuzzDataView' has // fewer bytes than 'maxLength', load at most 'fuzzDataView->length()' // bytes into 'output'. If the buffer in 'fuzzDataView' contains two // successive backslash characters, then in 'output' they will be converted // to a single backslash ('\\') character; if a single backslash character // is encountered, the consumption of bytes is terminated. Note that // because double backslashes are mapped to single backslashes, more than // 'maxLength' bytes may be consumed from the buffer to produce the // 'output'. Also note that the purpose of this function is to enable the // creation of a non-zero-terminated 'string_view', which is not possible // with the 'string' counterpart. static void consumeRandomLengthString(bsl::string *output, FuzzDataView *fuzzDataView, bsl::size_t maxLength); static void consumeRandomLengthString(std::string *output, FuzzDataView *fuzzDataView, bsl::size_t maxLength); #ifdef BSLS_LIBRARYFEATURES_HAS_CPP17_PMR static void consumeRandomLengthString(std::pmr::string *output, FuzzDataView *fuzzDataView, bsl::size_t maxLength); #endif // Load into the specified 'output' a string of length from 0 to the // specified 'maxLength'. If the specified 'fuzzDataView' has fewer // bytes than 'maxLength', load at most 'fuzzDataView->length()' bytes // into 'output'. If the buffer in 'fuzzDataView' contains two // successive backslash characters, then in 'output' they will be // converted to a single backslash ('\\') character; if a single // backslash character is encountered, the consumption of bytes is // terminated. Note that because double backslashes are mapped to // single backslashes, more than 'maxLength' bytes may be consumed // from the buffer to produce the 'output'. }; // ============================================================================ // INLINE DEFINITIONS // ============================================================================ // --------------- // struct FuzzUtil // --------------- // CLASS METHODS inline bool FuzzUtil::consumeBool(FuzzDataView *fuzzDataView) { return 1 & consumeNumber<bsl::uint8_t>(fuzzDataView); } template <class TYPE> typename bsl::enable_if<bsl::is_integral<TYPE>::value, TYPE>::type FuzzUtil::consumeNumber(FuzzDataView *fuzzDataView) { return consumeNumberInRange(fuzzDataView, bsl::numeric_limits<TYPE>::min(), bsl::numeric_limits<TYPE>::max()); } template <class TYPE> typename bsl::enable_if<bsl::is_floating_point<TYPE>::value, TYPE>::type FuzzUtil::consumeNumber(FuzzDataView *fuzzDataView) { return consumeNumberInRange(fuzzDataView, -bsl::numeric_limits<TYPE>::max(), bsl::numeric_limits<TYPE>::max()); } template <class TYPE> typename bsl::enable_if<bsl::is_integral<TYPE>::value, TYPE>::type FuzzUtil::consumeNumberInRange(FuzzDataView *fuzzDataView, TYPE min, TYPE max) { BSLMF_ASSERT(bsl::is_integral<TYPE>::value); BSLMF_ASSERT((!bsl::is_same<TYPE, bool>::value)); BSLMF_ASSERT(sizeof(TYPE) <= sizeof(bsls::Types::Uint64)); BSLS_ASSERT(min <= max); bsls::Types::Uint64 range = static_cast<bsls::Types::Uint64>(max) - min; int numBytes = 0; for (bsls::Types::Uint64 rangeCpy = range; 0 != rangeCpy; rangeCpy >>= 8, ++numBytes) { } bsls::Types::Uint64 addend = 0; FuzzDataView prefix = fuzzDataView->removePrefix(numBytes); for (const bsl::uint8_t *it = prefix.begin(); it != prefix.end(); it++) { addend = (addend << 8) | *it; } if (bsl::numeric_limits<bsls::Types::Uint64>::max() != range) { addend %= (range + 1); } return static_cast<TYPE>(min + addend); } template <class TYPE> typename bsl::enable_if<bsl::is_floating_point<TYPE>::value, TYPE>::type FuzzUtil::consumeNumberInRange(FuzzDataView *fuzzDataView, TYPE min, TYPE max) { BSLMF_ASSERT((!bsl::is_same<TYPE, long double>::value)); BSLMF_ASSERT(bsl::numeric_limits<TYPE>::has_infinity); BSLS_ASSERT(min <= max); BSLS_ASSERT(min == min && max == max); BSLS_ASSERT(bsl::numeric_limits<TYPE>::infinity() != max && -bsl::numeric_limits<TYPE>::infinity() != min); TYPE addend = min; TYPE range = 0; const TYPE k_HALF = 0.5; if (max > min + bsl::numeric_limits<TYPE>::max()) { range = max * k_HALF - min * k_HALF; if (consumeBool(fuzzDataView)) { addend = min + range; } } else { range = max - min; } typedef typename bsl::conditional<(sizeof(TYPE) <= sizeof(bsl::uint32_t)), bsl::uint32_t, bsls::Types::Uint64>::type IntegralType; TYPE factor = static_cast<TYPE>(consumeNumber<IntegralType>(fuzzDataView)) / static_cast<TYPE>( bsl::numeric_limits<IntegralType>::max()); // between 0-1 return addend + range * factor; } } // close package namespace } // close enterprise namespace #endif // ---------------------------------------------------------------------------- // Copyright 2021 Bloomberg Finance L.P. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // ----------------------------- END-OF-FILE ----------------------------------