3.2.2 Static Analysis.
Apart from the required code instrumentation, Clang produces compilation error and warning messages and utilizes its static analyzer to simplify the input program (e.g., calculating the sizes of expressions, evaluating static asserts, and performing constants propagation) [
51].
Furthermore, the Clang static code analyzer produces the
Consumed Input Size, which represents the minimum number of bytes in the input stream required for fuzzing. This information plays an important role in enhancing the fuzzing process (see Section
3.4.1).
Another function of static analysis is to identify the ranges of the input variables. This is performed by collecting branch conditions that match a pattern \((x \circ val)\) , where x is the name of the variable, val is a numeric value, and \(\circ \in \lbrace \gt , \ge , \lt , \le , =, \not=\rbrace\) . Both fuzzers use this information to generate inputs only from the identified ranges.
Finally,
FuSeBMC analyzes the instrumented code and ranks the injected goal labels. Each goal label is attributed with its origin information (i.e., if statement, while loop, end of function) and its depth in the instrumented program. Then
FuSeBMC sorts all goals using one of the two strategies (line 3 of Algorithm
1): (1) based on their depth (i.e., depth-first search), or (2) based on their
rank scores calculated as a product of a goal’s depth and its
power score—a value between 1 and 5 describing the goal’s branching power. Each power score was decided upon via experimental analysis. The
if statement goals are assigned a score of 5 (goals 4, 8, and 10 in Figure
3(b)), the
function goals—4 (goals 1 and 2 in Figure
3(b), note that main function goals are scored differently), the
loop goals—3 (goal 6 in Figure
3(b)) and the
else goals—2 (goal 5 in Figure
3(b)). All remaining types of goals (i.e.,
end-of-main (goal 3 in Figure
3(b)),
empty-else (goals 9, 11 in Figure
3(b)), and
after-loop goals (goal 7 in Figure
3(b)) are assigned a value of 1.
In general, the goal sorting improves overall FuSeBMC performance. Using the depth-first strategy, FuSeBMC attempts to cover the deeper goals first. This is beneficial since all preceding goals on the path to a deep goal can be ignored during subsequent fuzzing as the same test-case covers them. On the other hand, the ranking strategy allows for the prioritization of conditional branches as they may lead to multiple goals that increase potential code coverage.
Figure
3(c) features the resulting goals tree for the instrumented code from Figure
3(b) (with
GOAL_0 representing the entry point of the program, i.e., the
main function). Note that
FuSeBMC builds it based on the original Clang AST without analyzing the code for trivially unreachable goals. For example, labels
GOAL_7 and
GOAL_3 can never be reached during the program’s execution. However, this will not be reflected in the goals tree.
The goal’s depth value is assigned at its highest depth. Therefore, labels GOAL_1, GOAL_7, and GOAL_3 are assigned depth values of 5, 7, and 8, respectively. When the first ranking strategy is applied, two goals at the same depth are ordered in the ascending order of their label names. Using the second-ranking strategy, two goals with the same rank value are processed in the “power score first” manner. For example, GOAL_8 will be placed in front of GOAL_1 and GOAL_2 since it has a higher power score (5 vs. 4). Hence, FuSeBMC will process the goal labels in the following orders {3,7,10,11,1,2,8,9,6,4,5}, {10,8,1,2,4,6,3,7,11,5,9} using the first and second sorting strategies, respectively. Finally, the list of goals is stored in the shared memory as the Goal Queue. This queue can be modified by the BMC and Tracer engines during the consecutive stages to remove the goal labels that have been covered.