// bsls_fuzztest.h                                                    -*-C++-*-
#ifndef INCLUDED_BSLS_FUZZTEST
#define INCLUDED_BSLS_FUZZTEST

#include <bsls_ident.h>
BSLS_IDENT("$Id: $")

//@PURPOSE: Provide macros for use in fuzz testing narrow-contract functions.
//
//@CLASSES:
//  bsls::FuzzTestPreconditionTracker: utility for tracking assert violations
//  bsls::FuzzTestHandlerGuard: guard for fuzz testing assert-handler
//
//@MACROS:
//  BSLS_FUZZTEST_EVALUATE(EXPRESSION): wrapper for narrow contract function
//  BSLS_FUZZTEST_EVALUATE_RAW(EXPRESSION): wrapper with no origination check
//
//@SEE_ALSO: bsls_preconditions
//
//@DESCRIPTION: This component provides two macros, 'BSLS_FUZZTEST_EVALUATE'
// and 'BSLS_FUZZTEST_EVALUATE_RAW', that can be used in fuzz testing narrow
// contract functions.  They are intended to be used in conjunction with
// 'bsls::FuzzTestHandlerGuard' as well as 'BSLS_PRECONDITIONS_BEGIN' and
// 'BSLS_PRECONDITIONS_END'.
//
// When fuzzing narrow contract functions, if we do not wish to "massage" the
// data we pass to the function (as this may be error-prone and might introduce
// bias into the tested input) we must address the issue that we will often
// invoke the function out of contract, and this will cause the function to
// assert, and the test to end prematurely.  The macros defined in this
// component solve this issue by detecting the location of precondition
// violations.  Functions with narrow contracts that are to be tested must be
// decorated with the 'BSLS_PRECONDITIONS_BEGIN' and 'BSLS_PRECONDITIONS_END'
// macros.  These macros must be placed just before and after the function
// preconditions are checked.
//
// All these macros are intended to be used in fuzzing builds in which
// 'BDE_ACTIVATE_FUZZ_TESTING' is defined.  For our purposes, those
// preconditions that fail in the function under test (i.e., the one invoked
// by 'BSLS_FUZZTEST_EVALUATE') are treated differently from all other
// precondition failures.  We refer to these preconditions as "top-level"
// preconditions.  If a top-level precondition fails -- and the assertion is
// not from another component -- the execution will continue: we do not wish to
// stop the fuzz test if we simply invoked the narrow contract function under
// test out of contract.  We wish to detect only subsequent assertions (i.e.,
// not in the top-level), or assertions from other components.
//
// The 'BSLS_FUZZTEST_EVALUATE_RAW' macro does not check if the assertion
// originates from another component, though, like the non-'RAW' version, it
// ignores only top-level assertions.  This behavior is desirable in cases in
// which a function delegates its implementation and associated precondition
// checks to a different component.  In such cases, a precondition failure
// ought not cause the fuzz test to end.
//
///Usage
///-----
// This section illustrates intended use of this component.
//
///Example: Basic Usage of Macros
///- - - - - - - - - - - - - - - -
// The macros in this component rely upon the presence of related macros from
// 'bsls_preconditions'.  The fuzzing macros are typically used in a fuzzing
// build, in which case the entry point is 'LLVMFuzzerTestOneInput'.
//
// In this example, we illustrate the intended usage of two macros:
// 'BSLS_FUZZTEST_EVALUATE' and 'BSLS_FUZZTEST_EVALUATE_RAW'.
//
// First, in order to illustrate the use of 'BSLS_FUZZTEST_EVALUATE', we
// define two functions that implement the 'sqrt' function, both decorated
// with the precondition 'BEGIN' and 'END' macros.  'mySqrt' forwards its
// argument to 'newtonsSqrt', which has a slightly more restrictive
// precondition: 'mySqrt' accepts 0, while 'newtonsSqrt' does not.
//..
//  double newtonsSqrt(double x)
//      // Return the square root of the specified 'x' according to Newton's
//      // method.  The behavior is undefined unless 'x > 0'.
//  {
//      BSLS_PRECONDITIONS_BEGIN();
//      BSLS_ASSERT(x > 0);
//      BSLS_PRECONDITIONS_END();
//
//      double guess = 1.0;
//      for (int ii = 0; ii < 100; ++ii) {
//          guess = (guess + x / guess) / 2;
//      }
//      return guess;
//  }
//
//  double mySqrt(double x)
//      // Return the square root of the specified 'x'.  The behavior is
//      // undefined unless 'x >= 0'.
//  {
//      BSLS_PRECONDITIONS_BEGIN();
//      BSLS_ASSERT(x >= 0);
//      BSLS_PRECONDITIONS_END();
//      return newtonsSqrt(x);
//  }
//..
// Then, for the illustration of 'BSLS_FUZZTEST_EVALUATE_RAW', we define a
// class, 'Timer', containing a 'start' function that uses in its
// implementation a narrow contract function, 'setInterval', from another
// component, 'bsls::TimeInterval'.  This function, 'setInterval', has
// precondition checks that are surrounded by 'BEGIN' and 'END'.
//..
//  class Timer
//      // This class implements a simple interval timer.
//  {
//    private:
//      // DATA
//      bsls::TimeInterval d_timeout;  // timeout seconds and nanoseconds
//
//    public:
//      // MANIPULATORS
//      void start(bsls::Types::Int64 seconds, int nanoseconds)
//          // Start the countdown with a timer having the value given by the
//          // sum of the specified integral number of 'seconds' and
//          // 'nanoseconds'.  The behavior is undefined unless the total
//          // number of seconds in the resulting time interval can be
//          // represented with a 64-bit signed integer (see
//          // 'TimeInterval::isValid').
//      {
//          d_timeout.setInterval(seconds, nanoseconds);
//          //...
//      }
//  };
//..
// Next, implement 'LLVMFuzzerTestOneInput'.  We first select the test case
// number based on the supplied fuzz data.
//..
//  extern "C"
//  int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
//      // Use the specified 'data' array of 'size' bytes as input to methods
//      // of this component and return zero.
//  {
//      int         test;
//      if (data && size) {
//          test = static_cast<unsigned char>(*data) % 100;
//          ++data;
//          --size;
//      }
//      else {
//          test = 0;
//      }
//
//      switch (test) { case 0:  // Zero is always the leading case.
//..
// Then, we implement the test case to illustrate the use of
// 'BSLS_FUZZTEST_EVALUATE'.
//..
//        case 2: {
//          // ----------------------------------------------------------------
//          // 'mySqrt'
//          //
//          // Concerns:
//          //: 1. That 'mySqrt' does not invoke the original assertion handler
//          //:    for any 'input' value.
//          //
//          // Testing: double mySqrt(double x);
//          // ----------------------------------------------------------------
//          if (size < sizeof(double)) {
//              return 0;                                             // RETURN
//          }
//          double input;
//          memcpy(&input, data, sizeof(double));
//..
// Next, we set up the handler guard that installs the precondition handlers.
//..
//          bsls::FuzzTestHandlerGuard hg;
//..
// Now, we invoke the function under test (i.e., 'mySqrt') with the
// 'BSLS_FUZZTEST_EVALUATE' macro.
//..
//          BSLS_FUZZTEST_EVALUATE(mySqrt(input));
//..
// If the 'input' value obtained from the fuzz data is positive (e.g., 4.0),
// the 'mySqrt' implementation generates correct results without any errors.
// For negative inputs (e.g., -4.0), because the precondition violation occurs
// in the top level, execution of the test does not halt.  If 0 is passed as
// the input, 'mySqrt' forwards it to 'newtonsSqrt' where a second-level
// assertion occurs and execution halts, indicating a defect in the
// implementation of 'mySqrt'.
//..
//        } break;
//..
// Next, we implement the test case to illustrate the use of
// 'BSLS_FUZZTEST_EVALUATE_RAW'.
//..
//        case 1: {
//          // ----------------------------------------------------------------
//          // 'Timer::start'
//          //
//          // Concerns:
//          //: 1 That 'start', when invoked with the 'RAW' macro, does not
//          //:   invoke the original assertion handler.
//          //
//          // Testing:
//          //   void Timer::start(Int64 seconds, int nanoseconds);
//          // ----------------------------------------------------------------
//
//          if (size < sizeof(bsls::Types::Int64) + sizeof(int)) {
//              return 0;                                             // RETURN
//          }
//          bsls::Types::Int64 seconds;
//          int                nanoseconds;
//          memcpy(&seconds, data, sizeof(bsls::Types::Int64));
//          memcpy(&nanoseconds,
//                 data + sizeof(bsls::Types::Int64),
//                 sizeof(int));
//..
// Now, we set up the handler guard that installs the precondition handlers.
//..
//          bsls::FuzzTestHandlerGuard hg;
//..
// Finally, we invoke the function under test with the
// 'BSLS_FUZZTEST_EVALUATE_RAW' macro.
//..
//          Timer t;
//          BSLS_FUZZTEST_EVALUATE_RAW(t.start(seconds, nanoseconds));
//..
// If the total number of seconds resulting from the sum of 'seconds' and
// 'nanoseconds' cannot be represented with a 64-bit signed integer, a
// top-level assertion failure from a different component will occur.  Because
// we have invoked 'start' with the 'RAW' macro, a component name check will
// not be performed, and execution will continue.
//..
//        } break;
//        default: {
//        } break;
//      }
//
//      if (testStatus > 0) {
//          BSLS_ASSERT_INVOKE("FUZZ TEST FAILURES");
//      }
//
//      return 0;
//  }
//..
// Note that the use of 'bslim::FuzzUtil' and 'bslim::FuzzDataView' can
// simplify the consumption of fuzz data.

#include <bsls_assert.h>
#include <bsls_fuzztestpreconditionexception.h>
#include <bsls_preconditions.h>

                             // =================
                             // Macro Definitions
                             // =================

#if defined(BDE_BUILD_TARGET_EXC)
#define BSLS_FUZZTEST_EVALUATE_IMP(X) do {                                    \
        try {                                                                 \
            BloombergLP::bsls::FuzzTestPreconditionTracker::initStaticState(  \
                                                                   __FILE__); \
            X;                                                                \
        }                                                                     \
        catch (BloombergLP::bsls::FuzzTestPreconditionException& ftpe) {      \
            BloombergLP::bsls::FuzzTestPreconditionTracker::handleException(  \
                                                                       ftpe); \
        }                                                                     \
    } while (false)

#define BSLS_FUZZTEST_EVALUATE_RAW_IMP(X) do {                                \
        try {                                                                 \
            BloombergLP::bsls::FuzzTestPreconditionTracker::initStaticState(  \
                                                                   __FILE__); \
            X;                                                                \
        }                                                                     \
        catch (BloombergLP::bsls::FuzzTestPreconditionException& ftpe) {      \
        }                                                                     \
    } while (false)

#else
#define BSLS_FUZZTEST_EVALUATE_IMP(X) do {                                    \
        X;                                                                    \
    } while (false)
#define BSLS_FUZZTEST_EVALUATE_RAW_IMP(X) do {                                \
        X;                                                                    \
    } while (false)
#endif  // defined(BDE_BUILD_TARGET_EXC)

#ifdef BDE_ACTIVATE_FUZZ_TESTING

#define BSLS_FUZZTEST_EVALUATE(X) BSLS_FUZZTEST_EVALUATE_IMP(X)

#define BSLS_FUZZTEST_EVALUATE_RAW(X) BSLS_FUZZTEST_EVALUATE_RAW_IMP(X)

#else
#define BSLS_FUZZTEST_EVALUATE(X) do {                                        \
        X;                                                                    \
    } while (false)

#define BSLS_FUZZTEST_EVALUATE_RAW(X) do {                                    \
        X;                                                                    \
    } while (false)

#endif  // defined(BDE_ACTIVATE_FUZZ_TESTING)
namespace BloombergLP {
namespace bsls {

                         // =================================
                         // class FuzzTestPreconditionTracker
                         // =================================

struct FuzzTestPreconditionTracker {
    // This utility class is used by the preprocessor macros to appropriately
    // handle precondition violations that occur in different levels and
    // components.

  private:
    // CLASS DATA
    static const char *s_file_p;        // filename passed to 'initStaticState'

    static bool
        s_isInFirstPreconditionBlock;   // flag indicating whether the first
                                        // 'BEGIN/END' block has been closed

    static int         s_level;         // nesting level of 'BEGIN'/'END' call

  public:
    // CLASS METHODS
    static void handleException(
                               const FuzzTestPreconditionException& exception);
        // Invoke the assertion handler returned by
        // 'FuzzTestHandlerGuard::getOriginalAssertionHandler' if the assertion
        // violation wrapped by the specified 'exception' was encountered in a
        // component different from one supplied to 'initStaticState', and do
        // nothing otherwise.

    static void handlePreconditionViolation(const AssertViolation& violation);
        // Throw a 'FuzzTestPreconditionException' constructed from the
        // specified 'violation' if the assertion violation occurred after the
        // first invocation of 'handlePreconditionsBegin' but before the first
        // invocation of 'handlePreconditionsEnd', and invoke the assertion
        // handler returned by
        // 'FuzzTestHandlerGuard::getOriginalAssertionHandler' otherwise.

    static void handlePreconditionsBegin();
        // Increment the assertion block depth level counter.

    static void handlePreconditionsEnd();
        // Decrement the assertion block depth level counter and record that
        // the first precondition block has ended if the depth level changed to
        // 0.  The behavior is undefined unless the depth level is positive.

    static void initStaticState(const char *fileName);
        // Store the specified 'fileName' from the caller that invokes the
        // top-level function under test (via 'BSLS_FUZZTEST_EVALUATE(X)'), and
        // set the state to reflect that any precondition begin macro
        // encountered will be the first.
};

                         // ==========================
                         // class FuzzTestHandlerGuard
                         // ==========================

class FuzzTestHandlerGuard {
    // This class provides a guard that will install and uninstall three
    // handlers, one for assertion failure, one for 'BSLS_PRECONDITIONS_BEGIN',
    // and one for 'BSLS_PRECONDITIONS_END', within the protected scope.

  private:
    // CLASS DATA
    static Assert::ViolationHandler
        s_originalAssertionHandler;  // original assertion handler

  public:
    // CREATORS
    FuzzTestHandlerGuard();
        // Create a guard object, installing
        // 'FuzzTestPreconditionTracker::handlePreconditionViolation' as well
        // as the 'BEGIN/END' handler.  The behavior is undefined if the
        // current assertion handler is
        // 'FuzzTestPreconditionTracker::handlePreconditionViolation'.

    ~FuzzTestHandlerGuard();
        // Restore the failure handler that was in place when this object was
        // created, reset the precondition 'BEGIN/END' handlers to no-op, and
        // destroy this guard.

    // CLASS METHODS
    static Assert::ViolationHandler getOriginalAssertionHandler();
        // Return the original assertion handler.
};

// ============================================================================
//                            INLINE DEFINITIONS
// ============================================================================

inline
FuzzTestHandlerGuard::FuzzTestHandlerGuard()
{
    BSLS_ASSERT(&FuzzTestPreconditionTracker::handlePreconditionViolation !=
                bsls::Assert::violationHandler());

    PreconditionsHandler::installHandlers(
        &FuzzTestPreconditionTracker::handlePreconditionsBegin,
        &FuzzTestPreconditionTracker::handlePreconditionsEnd);

    s_originalAssertionHandler = bsls::Assert::violationHandler();

    bsls::Assert::setViolationHandler(
        &FuzzTestPreconditionTracker::handlePreconditionViolation);
}

inline
FuzzTestHandlerGuard::~FuzzTestHandlerGuard()
{
    PreconditionsHandler::installHandlers(&PreconditionsHandler::noOpHandler,
                                          &PreconditionsHandler::noOpHandler);
    bsls::Assert::setViolationHandler(s_originalAssertionHandler);
}

inline
Assert::ViolationHandler FuzzTestHandlerGuard::getOriginalAssertionHandler()
{
    return s_originalAssertionHandler;
}

}  // 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 ----------------------------------