Instrumented Code
While our intention is that you should not have to look at or understand instrumented code, scribble
is still beta software and bugs will happen. In such cases it may be useful for you to understand how Scribble generates instrumented code. For this we will look at two examples.
Function Annotations
To understand function instrumentation lets look at the following (slightly contrived) example:
The instrumented code generated would look like this:
There are several important things to note:
Scribble interposes on functions by renaming them, making them private, and adding a new stub with the same signature as the original function. For example, note that the original code from the
add
function now lives in the new private function_original_Foo_add
(lines 20-23).In the generated wrapper
add()
we invoke the renamed original function (line 13), and check theif_succeeds
annotation after the call (lines 14-17).The values of
old
expressions are evaluated before the call (line 12) and require a temporary variable. However, instead of just adding multiple temporary variables, we group all of the temporary variables needed inside of a single struct (vars0
) defined on lines 4-6. This is done to reduce the extra stack space used by instrumented code.Which custom property failed is signaled by emitting the
AssertionFailed
event (line 15).
Property Maps
When the above property is violated, a message with the string 0: test
(line 15) is emitted. The test
part is the original user-readable label in the annotation. 0
is an internally assigned id. Scribble assigns such an id for each annotation we write. It is possible to hint Scribble to also output a metadata with map from ids to the original property. See more about the metadata format in section JSON output specification.
There are two ways to get the instrumentation metadata with Scribble:
Using
--output-mode json
(or-m json
) command line option as part of JSON output ininstrumentationMetadata
key.Using
--instrumentation-metadata-file path/to/meta-file.json
command line option to get it in a standalone JSON file at specified path.
Contract Annotations
For contract invariants there are several additional changes. Let's consider this slightly different example:
Now the emitted code is quite a bit more involved:
Again there are several changes:
A new helper contract -
__scribble_ReentrancyUtils
(lines 2-4) was added. This contract was also added as a base contract ofFoo
. (line 6).__scribble_ReentrancyUtils
contains a single boolean property that is used for tracking whether the current stack frame is the root frame of an external call. This is useful as public functions can be called both internally and externally, but we only want to check contract invariants in them when they are called externally.A new function
__scribble_Foo_check_state_invariants_internal
was added (lines 36-41) which actually checks whether the invariant is true. It is only called indirectly, by__scribble_check_state_invariants
which is itself called by the wrapped functions.The public
add
function (lines 22-33) was wrapped similarly to functions in the previous section. The wrapper (lines 22-29) invokes the__scribble_check_state_invariants
function at the end of execution (line 27) to check whether the invariant is preserved by the call toadd
.The check on line 27 is done conditionally. This is a twist to make sure we are not checking contract invariants inside of public functions when the public functions are called internally. If
add
was declaredexternal
then the call on line 27 would be unconditional.All external, public functions in the contract (and the constructor) receive additional code at their beginning and end that manipulates the
__scribble_out_of_contract
state variable defined in the contract. This is used to keep track of whether we are just entering the contract or not. The logic is particularly involved for public functions. Note that in the public functionadd
, whether we check the invariants at the end depends on the value of__scribble_out_of_contract
at the start of the function (which was stored in_v.__scrible_check_invs_at_end
on line 24). This reflects the intuition that we want to check contract invariants only at the end of external calls, not internal calls (for more details on contract invariants see Contract Invariants).Note that the contract invariants are also checked at the end of the constructor (line 18).
Last updated