BDE 4.14.0 Production release
|
Macros | |
#define | BSLS_REVIEW_NO_REVIEW_MACROS_DEFINED 1 |
#define | BSLS_REVIEW_REVIEW_COUNT_IMP |
#define | BSLS_REVIEW_REVIEW_IMP(X, LVL) |
#define | BSLS_REVIEW_DISABLED_IMP(X, LVL) |
#define | BSLS_REVIEW_INVOKE(X) |
#define | BSLS_REVIEW_SAFE(X) |
#define | BSLS_REVIEW_IS_ACTIVE |
#define | BSLS_REVIEW_IS_USED |
#define | BSLS_REVIEW(X) |
#define | BSLS_REVIEW_OPT_IS_ACTIVE |
#define | BSLS_REVIEW_OPT_IS_USED |
#define | BSLS_REVIEW_OPT(X) |
#define | BSLS_REVIEW_RECURSIVELY_INCLUDED_TESTDRIVER_GUARD |
Provide assertion macros to safely identify contract violations.
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.
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:
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.
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:
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:
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):
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:
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.
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.
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 conditionally 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.
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.
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.
bsls_review is not appropriate for "new" code that has not yet been deployed to a production environment; use bsls_assert instead.
The introduction of a new assertion using bsls_review should follow these steps:
BSLS_REVIEW
with BSLS_ASSERT
and redeploy.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.
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.Therefore, the "safest" way to increase an assertion level is to follow these steps:
BSLS_ASSERT
such as this: .. BSLS_ASSERT(some_test()); .. You can duplicate the test as a BSLS_REVIEW_OPT
, if the test is very negligible, like this: .. BSLS_ASSERT(some_test()); BSLS_REVIEW_OPT(some_test()); .. If the duplicated check is unacceptably expensive (which should make you question making this assertion a BSLS_ASSERT_OPT
in the first place), then you can opt for the more cumbersome: .. #ifdef BSLS_ASSERT_IS_ACTIVE BSLS_ASSERT(some_test()); #else BSLS_REVIEW_OPT(some_test()); #endif ..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:
BSLS_REVIEW_LEVEL_REVIEW_OPT
, BSLS_REVIEW_LEVEL_REVIEW
, or BSLS_REVIEW_LEVEL_REVIEW_SAFE
.BSLS_REVIEW_LEVEL
from your build and change to set an explicit BSLS_ASSERT_LEVEL
at your new level.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:
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.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.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:
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.
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:
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::Review
s default review failure handler and will be similar to:
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):
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":
At this point, any contract violations in the use of myFunc
in new code will be caught immediately (i.e., in appropriate build modes).
#define BSLS_REVIEW | ( | X | ) |
#define BSLS_REVIEW_DISABLED_IMP | ( | X, | |
LVL | |||
) |
#define BSLS_REVIEW_INVOKE | ( | X | ) |
#define BSLS_REVIEW_IS_ACTIVE |
#define BSLS_REVIEW_IS_USED |
#define BSLS_REVIEW_NO_REVIEW_MACROS_DEFINED 1 |
#define BSLS_REVIEW_OPT | ( | X | ) |
#define BSLS_REVIEW_OPT_IS_ACTIVE |
#define BSLS_REVIEW_OPT_IS_USED |
#define BSLS_REVIEW_RECURSIVELY_INCLUDED_TESTDRIVER_GUARD |
#define BSLS_REVIEW_REVIEW_COUNT_IMP |
#define BSLS_REVIEW_REVIEW_IMP | ( | X, | |
LVL | |||
) |
#define BSLS_REVIEW_SAFE | ( | X | ) |