Quick Links:

bal | bbl | bdl | bsl

Namespaces

Component bsls_review
[Package bsls]

Provide assertion macros to safely identify contract violations. More...

Namespaces

namespace  bsls

Detailed Description

Outline
Purpose:
Provide assertion macros to safely identify contract violations.
Classes:
bsls::Review namespace for "review" management functions
bsls::ReviewFailureHandlerGuard scoped guard for changing handlers safely
bsls::ReviewViolation attributes describing a failed review check
Macros:
BSLS_REVIEW runtime check typically enabled in all non-opt build modes
BSLS_REVIEW_SAFE runtime check typically only enabled in safe build modes
BSLS_REVIEW_OPT runtime check typically enabled in all build modes
BSLS_REVIEW_INVOKE immediately invoke the current review handler
See also:
Component bsls_assert
Description:
This component provides three "assert-like" macros, BSLS_REVIEW, BSLS_REVIEW_SAFE, and BSLS_REVIEW_OPT, that can be used to enable optional redundant runtime checks in corresponding build modes that are designed to log their failures so production systems can be safely monitored for contract violations.
This component is designed to allow apparently working production software, which nonetheless may harbor contract violations, to increase the number of precondition checks used to catch such bugs without negatively impacting the existing behavior of the software. The assumption is that any contract violations uncovered through these checks in stable software is (at least for the moment) benign. This component should not be used for assertions added to new code; new code should rely on bsls_assert, as any contract violations discovered in new code may not be benign and the resulting behavior may be much worse than the task terminating (as would happen using bsls_assert).
If the argument of a review macro evaluates to 0, a runtime-configurable "handler" function is invoked with a bsls::ReviewViolation, a value-semantic class that encapsulates the current filename, line number, level of failed check, (0-valued expression) argument text, and a count of how many times that check has already failed. The default handler logs that a failure has occurred and then allows processing to continue, thus not adversely impacting the running program. The class bsls::Review provides functions for manipulating the globally configured "handler". A scoped guard for setting and restoring the review handler is provided by bsls::ReviewFailureHandlerGuard.
An additional macro, BSLS_REVIEW_INVOKE, is included for directly invoking the current review handler as if an assertion had failed on the current line.
Detailed Behavior:
If a review fires (i.e., due to a 0-valued expression argument in a review macro that is enabled), there is a violation of the contract that the review is checking. For such a failing review, the program is in an undefined state but it is deemed inadvisable to immediately abort without further analysis. It is the goal of the review to be sure to log that a contract was violated so the issue (in either the calling code or the contract) can be addressed.
Reviews are enabled or disabled at compile time based on the review level, assertion level, and build mode flags that have been defined. It is also possible that assert macros (see bsls_assert) can be configured in review mode, and so they may behave exactly as would review macros with appropriate build flags.
When enabled, the review macros will all do essentially the same thing: Each macro tests the predicate expression X, and if !(X) is true, tracks a count of how many times this particular review has failed and invokes the currently installed review handler. An instance of bsls::ReviewViolation will be created and populated with a textual rendering of the predicate (#X), the current __FILE__, the current __LINE__, a string representing which particular type of review or assertion has failed, and the current count of how many times this predicate has failed (used primarily to throttle repeated logging of violations from the same location). This violation is then passed to the currently installed review failure handler, a function pointer with the type bsls::Review::ViolationHandler having the signature:
  void(const bsls::ReviewViolation&)
Review Levels and Build Modes:
There are a few macros available to control which of the review macros are actually enabled. These macros are for the compilation and build environment to provide and are not themselves defined by BDE code -- e.g., by supplying one or more of these macros with -D options on the compiler command line. In general, these macros are used to determine a REVIEW_LEVEL that can be NONE, REVIEW_OPT, REVIEW, or REVIEW_SAFE. Depending on the review level, different review macros will be enabled. For "safer" review configurations, more macros are enabled and all lower-level (and presumably lower cost) macros are kept enabled. The NONE level is primarily provided for testing to ensure that the REVIEW_OPT level doesn't actually introduce any side-effects or incur measurable overhead when enabled, though in practice it should not be deployed.
   ==================================================
   "REVIEW" Macro Instantiation Based on Review Level
   ==================================================
  BSLS_REVIEW_LEVEL  BSLS_REVIEW_OPT BSLS_REVIEW BSLS_REVIEW_SAFE
  -----------------  --------------- ----------- ----------------
  NONE
  REVIEW_OPT         ENABLED
  REVIEW             ENABLED         ENABLED
  REVIEW_SAFE        ENABLED         ENABLED     ENABLED
The logic for the determination of the review level checks a few different macros. The first check is for one of the 4 mutually exclusive BSLS_REVIEW_LEVEL macros that can explicitly set the review level:
  MACRO                         BSLS_REVIEW_LEVEL
  -----                         ----------------
  BSLS_REVIEW_LEVEL_NONE        NONE
  BSLS_REVIEW_LEVEL_REVIEW_OPT  REVIEW_OPT
  BSLS_REVIEW_LEVEL_REVIEW      REVIEW
  BSLS_REVIEW_LEVEL_REVIEW_SAFE REVIEW_SAFE
If none of those macros are defined, the review level is implemented to be exactly the same as the assertion level (see bsls_assert). This is so that any introduced review macro will still be enabled (and become an assertion) when it is textually replaced with a BSLS_ASSERT. This means that first one of the 7 mutually exclusive BSLS_ASSERT_LEVEL macros are checked to determine the review level:
  MACRO                           BSLS_REVIEW_LEVEL
  -----                           ----------------
  BSLS_ASSERT_LEVEL_ASSUME_SAFE   NONE
  BSLS_ASSERT_LEVEL_ASSUME_ASSERT NONE
  BSLS_ASSERT_LEVEL_ASSUME_OPT    NONE
  BSLS_ASSERT_LEVEL_NONE          NONE
  BSLS_ASSERT_LEVEL_ASSERT_OPT    REVIEW_OPT
  BSLS_ASSERT_LEVEL_ASSERT        REVIEW
  BSLS_ASSERT_LEVEL_ASSERT_SAFE   REVIEW_SAFE
Finally, the default review (and assert) level, if none of the overriding review or assert level macros above are defined, is determined by the build mode. With "safer" build modes we incorporate higher-level defensive program checks. A particular build mode is implied by the relevant (BDE) build targets that are defined at compilation (preprocessing) time. The following table shows the three (BDE) build targets that can affect the assertion and review levels (note that BDE_BUILD_TARGET_DBG plays no role):
        (BDE) Build Targets
      -----------------------
  (A) BDE_BUILD_TARGET_SAFE_2
  (B) BDE_BUILD_TARGET_SAFE
  (C) BDE_BUILD_TARGET_OPT
Any of the 8 possible combinations of the three build targets is valid; e.g., BDE_BUILD_TARGET_OPT and BDE_BUILD_TARGET_SAFE_2 may both be defined. The following table shows the review level that is set depending on which combination of build target macros have been set:
   =========================================================
   "REVIEW" Level Set With no Level-Overriding Flags defined
   =========================================================
  --- BDE_BUILD_TARGET ----   BSLS_REVIEW_LEVEL
  _SAFE_2   _SAFE    _OPT
  -------  -------  -------   -----------------
                              REVIEW
                    DEFINED   REVIEW_OPT
           DEFINED            REVIEW_SAFE
           DEFINED  DEFINED   REVIEW_SAFE
  DEFINED                     REVIEW_SAFE
  DEFINED           DEFINED   REVIEW_SAFE
  DEFINED  DEFINED            REVIEW_SAFE
  DEFINED  DEFINED  DEFINED   REVIEW_SAFE
As the table above illustrates, with no build target explicitly defined the review level defaults to REVIEW. If only BDE_BUILD_TARGET_OPT is defined, the review level will be set to REVIEW_OPT. If either BDE_BUILD_TARGET_SAFE or BDE_BUILD_TARGET_SAFE_2 is defined then the review level is set to REVIEW_SAFE and ALL review macros will be enabled.
Runtime-Configurable Review-Failure Behavior:
In addition to the three (BSLS) "REVIEW" macros, BSLS_REVIEW, BSLS_REVIEW_SAFE, and BSLS_REVIEW_OPT, and the immediate invocation macro BSLS_REVIEW_INVOKE, this component provides (1) an invokeHandler method used (primarily) to implement these "REVIEW" macros and enable their runtime configuration, (2) administrative methods to configure, at runtime, the behavior resulting from a review failure (i.e., by installing an appropriate review-failure handler function), and (3) a suite of standard ("off-the-shelf") review-failure handler functions, to be installed via the methods (if desired), and invoked by the invokeHandler method of a review failure.
When an enabled review fails, the currently installed failure handler ("callback") function is invoked. The default handler is the (static) bsls::Review::failByLog method, which will log the failure and the current callstack when invoked for the first time, and exponentially less frequently as additional failures of the same review site occur. A user may replace this default handler by using the (static) bsls::Review::setViolationHandler administrative method and passing it (the address of) a function whose signature conforms to the bsls::Review::ViolationHandler typedef. This handler may be one of the other handler methods provided in bsls::Review, or a "custom" function written by the user.
One additional provided class, bsls::ReviewFailureHandlerGuard, can be used to override the review handler within a specific block of code. Note that this is primarily intended for testing and should be instantiated at a non-granular level, as the setting and resetting of the handler done by this RAII class has no provisions in it to handle being used concurrently by multiple threads.
The primary uses for setting the review handler to a non-default handler are to get all reviews to behave like some kind of an assertion, or to get reviews to throw exceptions to facilitate testing. Both of these scenarios are primarily encountered in test drivers, and the general workflows for reviews and assertions depend on a policy where deployed production tasks use the default handlers.
Conditional Compilation:
To recap, there are three (mutually compatible) general build targets:
  • BDE_BUILD_TARGET_OPT
  • BDE_BUILD_TARGET_SAFE
  • BDE_BUILD_TARGET_SAFE_2
seven (mutually exclusive) component-specific assertion levels:
  • BSLS_ASSERT_LEVEL_ASSERT_SAFE
  • BSLS_ASSERT_LEVEL_ASSERT
  • BSLS_ASSERT_LEVEL_ASSERT_OPT
  • BSLS_ASSERT_LEVEL_NONE
  • BSLS_ASSERT_LEVEL_ASSUME_OPT
  • BSLS_ASSERT_LEVEL_ASSUME_ASSERT
  • BSLS_ASSERT_LEVEL_ASSUME_SAFE
and four (mutually exclusive) component-specific review levels:
  • BSLS_REVIEW_LEVEL_REVIEW_SAFE
  • BSLS_REVIEW_LEVEL_REVIEW
  • BSLS_REVIEW_LEVEL_REVIEW_OPT
  • BSLS_REVIEW_LEVEL_NONE
The above macros can be defined (externally) by the build environment to affect which of the three review macros:
  • BSLS_REVIEW_SAFE(boolean-valued expression)
  • BSLS_REVIEW(boolean-valued expression)
  • BSLS_REVIEW_OPT(boolean-valued expression)
will be enabled (i.e., instantiated).
The public interface of this component also provides some additional intermediate macros to identify how the various BSLS_REVIEW macros have been instantiated. These exist for each level and have the following suffixes and meanings:
  • IS_ACTIVE: Defined if the corresponding level is enabled.
  • IS_USED: Defined if the expressions for the corresponding level need to be valid (i.e., if they are"'ODR-used").
Putting that together, these 3 macros are defined if the corresponding macro is enabled:
  • BSLS_REVIEW_SAFE_IS_ACTIVE
  • BSLS_REVIEW_IS_ACTIVE
  • BSLS_REVIEW_OPT_IS_ACTIVE
Finally, three more macros with the IS_USED suffix are defined when the expression for the corresponding macro is going to be compiled. This will be true if the macro is enabled or if BSLS_REVIEW_VALIDATE_DISABLED_MACROS has been defined.
  • BSLS_REVIEW_SAFE_IS_USED
  • BSLS_REVIEW_IS_USED
  • BSLS_REVIEW_OPT_IS_USED
All of these additional "predicate" macros can be used directly by clients of this component to conditioanlly compile code other than just (BSLS) reviews, but that should be done with care to be sure code compiles and is compatible across all build modes.
Validating Disabled Macro Expressions:
An additional external macro, BSLS_REVIEW_VALIDATE_DISABLED_MACROS, can be defined to control the compile time behavior of bsls_review. Enabling this macro configures all disabled review macros to still instantiate their predicates (in a non-evaluated context) to be sure that the predicate is still syntactically valid. This can be used to ensure reviews that are rarely enabled have valid expressions.
Uses for bsls_review:
bsls_review exists primarily as a step towards adding or modifying instances of bsls_assert macros in code that are already running in production. New bsls_assert macros are risky to add, and enabled bsls_assert macros that were not previously enabled is equally risky.
Given code that is already running and deployed, but not "breaking" in obvious ways, the review process provides a way to see if contracts are being met in those running processes without risking unexpected aborts happening in those systems. The contracts that might be getting violated in already deployed applications need to be fixed and avoided, BUT there exists no evidence that these violations are currently crashing the system.
In general, bsls_review can also be considered as a way to identify if deployed code is violating function contracts in some way. Once calling applications and library code have been altered so all contracts are being met, uses of bsls_review macros are then changed into the corresponding bsls_assert macros, which enforce the contracts instead of simply monitoring them. In some cases, the bsls_review macros can also be replaced by new behavior, e.g., widening narrow contracts without risking breaking the expectations of existing applications.
Adding New bsls_assert Checks To New Code:
bsls_review is not appropriate for "new" code that has not yet been deployed to a production environment; use bsls_assert instead.
Adding Assertions With bsls_review:
The introduction of a new assertion using bsls_review should follow these steps:
  1. Add the appropriate bsls_review macros to your code.
  2. Commit these changes and make sure all processes using your code get rebuilt and deployed to production.
  3. Monitor relevant log files for "BSLS_REVIEW failure" messages citing the location of these reviews.
  4. If any failures occurred, fix the calling code, or widen the contract being violated (which should be rare).
  5. Wait for sufficient time to pass to be confident that the contract is not being violated by the current version of the software in production.
  6. Replace BSLS_REVIEW with BSLS_ASSERT and redeploy.
Reducing The Assertion Level For Existing bsls_assert Macros:
In order to reduce the level of an existing assertion, such as changing a BSLS_ASSERT_SAFE into a BSLS_ASSERT, or a BSLS_ASSERT into a BSLS_ASSERT_OPT, you should follow a very similar process to the process for adding a new assertion of that level. There are two possible approaches that largely depend on how widely deployed the code is that is already built with the old assertions enabled.
  1. If the existing assertion is not enabled in deployed production code, such as a BSLS_ASSERT_SAFE where client code rarely or never deploys safe builds, then simply remove the old assertion and start the process described above of adding in the higher level assertion as if it was a newly added assertion. Here changing a BSLS_ASSERT_SAFE to a BSLS_ASSERT would then begin with changing the BSLS_ASSERT_SAFE into a BSLS_REVIEW and continuing as above.
  2. If the existing assertion is depended on in released production code then the process involved needs to maintain the assertion at the existing level while adding in the review at the higher level, eventually including only the assert at the higher level when the process is complete.
Therefore, the "safest" way to increase an assertion level is to follow these steps:
  1. Given an existing BSLS_ASSERT such as this:
Increasing Deployed Assertion Levels With BSLS_REVIEW_LEVEL_*:
A common situation in deployed tasks is that they are built with only BSLS_ASSERT_OPT enabled. To improve the robustness of these applications, there is then a desire to run them with BSLS_ASSERT enabled, or even BSLS_ASSERT_SAFE. Enabling these assertions, however, brings in the same risks of hard failures for contract violations that adding new assertions does. BSLS_REVIEW provides a process for making this change without risking aborting processes that were previously "running just fine".
Any explicit setting of the BSLS_REVIEW_LEVEL ("review level") to a level higher than the BSLS_ASSERT_LEVEL ("assert level") will not only enable the BSLS_REVIEW macros at that level, but it will turn all BSLS_ASSERT macros at that level into reviews as well, and disable any assumption of BSLS_ASSERT assertions. Given a task that is built with an assertion level of OPT, if you set the review level to REVIEW you will then get logs and notifications of any failed BSLS_ASSERT checks, but those failing checks will not immediately abort your application.
So the process for deploying an application with a higher assertion level is to follow these steps:
  1. Rebuild the task with the review level set to the desired assertion level, using BSLS_REVIEW_LEVEL_REVIEW_OPT, BSLS_REVIEW_LEVEL_REVIEW, or BSLS_REVIEW_LEVEL_REVIEW_SAFE.
  2. Deploy the task.
  3. Monitor relevant log files for "BSLS_REVIEW failure" messages citing the location of these reviews.
  4. Once all review failures have been addressed and no failures are logged for a "sufficient" time, remove the explicitly set BSLS_REVIEW_LEVEL from your build and change to set an explicit BSLS_ASSERT_LEVEL at your new level.
  5. Deploy your newly built application with increased enabled defensive checks.
  6. Revel in the comfort of taking advantage of the additional defensive checks now enabled in your code.
Checking Library Usage Before Changing it With bsls_review:
Occasionally, given code that is in use in production, you may come across a case that makes no sense but that you want to reimplement with altered behavior. The previous behavior might have been out of contract or just seemingly nonsensical, but you are not confident that no one is actually running code that executes that behavior and relies on it.
bsls_review here provides a way to insert a review that checks if the code in question is ever being invoked. To do that, follow these steps:
  1. Depending on the structure of the code you want to monitor, add a BSLS_REVIEW_INVOKE or a BSLS_REVIEW_OPT with a check that will fail on the condition you want to monitor. Use these macros as they will be enabled in almost all build modes.
  2. Commit your changes and get the applications using your code deployed.
  3. If any code hits your review, assess it for why and fix the calling code or re-assess the behavior you want to change.
  4. Once "sufficient" time has passed with no review failures, remove the bsls_review checks entirely.
  5. Make changes to your code's behavior that impact only those states where previously your review would have failed. Check in and deploy those changes.
  6. Revel in safely having deployed a Liskov-substitutable version of your library with exciting and new behavior.
Concerns When Adding Reviews or Assertions:
  1. Performance: In general, a new bsls_review check will perform exactly the same as a bsls_assert with the same predicate. One subtle difference is that apparently working software can contain failing bsls_review checks logging failures that may be ignored (whereas bsls_assert failures are designed to be hard to ignore). Handling these failures requires maintaining the failure count using atomic operations that may negatively impact performance if checks are failing (and ignored) in performance-sensitive code. Failing checks should always be addressed as promptly as possible.
  2. Downstream Linking: For a library developer, part of the review rollout process will rely heavily on users of your library relinking multiple times during the process of adding reviews. One might assume that this means the verification that a review is not failing can extend an indefinite amount of time. The better overall policy is to realize that, just like library users can opt-in to rebuilding their tasks more often, the stability benefits of being involved in the review process by relinking and rolling out more often are opt-in as well. For tasks that are rebuilt very infrequently, they will simply have to accept that library misuse on their end might result in crashes due to asserts that were introduced between their own too-infrequent releases.
  3. Unit Testing with Reviews: In general, unit test log files are rarely monitored or read, so a failing BSLS_REVIEW is unlikely to be caught during unit tests. Test drivers should almost always set the review handler to the abort handler so any failed reviews are caught immediately as test failures.
  4. Sufficient Time: All of the review-related workflows mention running reviewed code for "sufficient" time to know the check is not failing. "Sufficient" time will vary by application.
Usage:
Example 1: Adding BSLS_ASSERT To An Existing Function:
Suppose you have an existing function, already deployed to production, that was not written with defensive programming in mind. In order to increase robustness, you would like to add BSLS_ASSERT macros to this function that match the contract originally written when this function was initially released.
For example, consider the function myFunc in the class FunctionsV1 that was implemented like this:
 my_functions.h
 ...

  class FunctionsV1 {
      // ...
    public:
      // ...

      static int myFunc(int x, int y);
          // Do something with the specified positive integers 'x' and 'y'.
  };

  inline int FunctionsV1::myFunc(int x, int y)
  {
      int output = 0;
      // ... do stuff with 'x' and 'y'.
      return output;
  }
Notice that there are no checks on x and y within myFunc and no assertions to detect use of myFunc outside of its contract. On the other hand, myFunc is part of legacy code that has been in use extensively for years or decades, so clearly this is not causing a problem (yet).
Upon reviewing this class you realize that myFunc produces random results for values of x or y less than 0. You, however, do not have enough information to conclude that no one is calling it with negative values and just using the bad results unknowingly. There are a number of possibilities for how the result of this undefined behavior might be going unnoticed.
  • The invalid value might be discarded by a bounds check later in the process.
  • The invalid value may only result in a small glitch the users have not noticed or ignored.
  • The resulting value may actually be valid, but allowing negative input for x and y may preclude potential future development in ways we do not want to allow.
All of these are bad, but adding in checks with BSLS_ASSERT that would replace these bad behaviors by process termination would turn silent errors into loud errors (potentially worse). On the other hand, by not adding BSLS_ASSERT checks we permit future misuses of this function, which may not be innocuous, to potentially reach production systems. BSLS_REVIEW here serves as a bridge, from the current state of myFunc (entirely unchecked) to the ideal state of myFunc (where misuse is caught loudly and immediately through BSLS_ASSERT), following a path that doesn't risk turning an un-noticed or irrelevant error into one that will significantly hinder ongoing business.
The solution to this is to initially reimplement myFunc using BSLS_REVIEW like this:
 my_functions.h
 ...
  #include <bsls_review.h>
 ...

  class FunctionsV2 {
      // ...
    public:
      // ...

      static int myFunc(int x, int y);
          // Do something with the specified 'x' and 'y'.  The behavior is
          // undefined unless 'x > 0' and 'y > 0'.
  };

  inline int FunctionsV2::myFunc(int x, int y)
  {
      BSLS_REVIEW(x > 0);
      BSLS_REVIEW(y > 0);
      int output = 0;
      // ... do stuff with 'x' and 'y'.
      return output;
  }
Now you can deploy this code to production and then begin reviewing logs. The log messages you should look for are those produced by bsls::Reviews default review failure handler and will be similar to:
  ERROR myfunction.h::17 BSLS_REVIEW failure (level:R-DBG): 'x > 0'
                                     Please run "/bb/bin/showfunc.tsk ...
showfunc.tsk is a Bloomberg application that can be used (along with the task binary) to convert the reported stack addresses to a more traditional stack trace with a function call stack.
It is important to note that BSLS_REVIEW is purely informative, and adding a review will not adversely affect behavior, and may in fact alert the library author to common client misconceptions about the intended behavior.
For example, let's say actual usage makes it clear that users expect 0 to be valid values for the arguments to myFunc, and nothing in the implementation prevents us from accepting 0 as input and producing the answer clients expect. Instead of changing all the clients, we may instead choose to change the function contract (and implemented checks):
 my_functions.h
 ...
  #include <bsls_review.h>
 ...

  class FunctionsV3 {
      // ...
    public:
      // ...

      static int myFunc(int x, int y);
          // Do something with the specified 'x' and 'y'.  The behavior is
          // undefined unless 'x >= 0' and 'y >= 0'.
  };

  inline int FunctionsV3::myFunc(int x, int y)
  {
      BSLS_REVIEW(x >= 0);
      BSLS_REVIEW(y >= 0);
      int output = 0;
      // ... do stuff with 'x' and 'y'.
      return output;
  }
Finally, at some point, the implementation of myFunc using BSLS_REVIEW has been running a suitable amount of time that you are comfortable transitioning the use of bsls_review to bsls_assert. We now use our favorite text editor or script to replace "BSLS_REVIEW" with "BSLS_ASSERT":
 my_functions.h
 ...
  #include <bsls_assert.h>
 ...

  class FunctionsV4 {
      // ...
    public:
      // ...

      static int myFunc(int x, int y);
          // Do something with the specified 'x' and 'y'.  The behavior is
          // undefined unless 'x >= 0' and 'y >= 0'.
  };

  inline int FunctionsV4::myFunc(int x, int y)
  {
      BSLS_ASSERT(x >= 0);
      BSLS_ASSERT(y >= 0);
      int output = 0;
      // ... do stuff with 'x' and 'y'.
      return output;
  }
At this point, any contract violations in the use of myFunc in new code will be caught immediately (i.e., in appropriate build modes).