This section summarizes best practices for using PREfast and annotations.
Consider the following general best practices for integrating PREfast into your development cycle:
Consider these ideas for integrating PREfast into your team's development practices:
Start running PREfast as soon as you get the first clean compile of each source file.
Establish a policy for your development team about which PREfast warnings must be fixed, which warnings can be ignored, and which warnings the developer might choose to fix or not to fix.
Establish a policy and comment conventions to record why particular warnings, such as false positives, were not fixed.
Try these guidelines for which warnings to hide:
At a minimum, fix all warnings that pass the drivers-all filter. These are particularly serious, low-noise warnings about errors that can affect system security.
Filter PREfast results by hiding messages when the development team considers the risk that is associated with the warning to be acceptably low, when the noise associated with it is high, or when the product ship cycle permits fixing only the most critical errors.
Some guidelines are related to overall practices for coding and commenting:
Make minor changes to your code to reduce false positives that are caused by coding style.
Instead of using inline assembler, use the utility functions that are provided with newer compilers.
If you cannot avoid using inline assembler, consider placing the code in an __inline function, so that PREfast can analyze the code more effectively. Use annotations on these functions.
Wherever possible, initialize variables when you declare them.
Identify programming assumptions in comments, and make these assumptions explicit with assertions.
Use parentheses and other syntax to enforce order of evaluation, rather than relying on the precedence rules of C when those rules are not completely intuitive-that is, in the cases that PREfast warns about.
Use the NT_SUCCESS macro instead of explicit tests for STATUS_SUCCESS.
Start using annotations to provide PREfast with more specific information about your source code.
Continue to use other driver testing and validation tools, such as Static Driver Verifier and Driver Verifier, in addition to PREfast.
The following guidelines represent best practices for applying annotations:
The "right" approach to using annotations is the one that works best for your project:
For some development projects, it might make sense to exercise PREfast capabilities to the fullest, by proactively examining every function in the program and applying the appropriate annotations and other recommended changes.
This takes some effort but helps to assure that every bug that PREfast could find is found and identified. Done correctly, this minimizes false positives as well.
If time and resources are limited, it might make sense to take a reactive approach by running PREfast on existing code and applying whatever annotations are needed to suppress false positives.
This does not necessarily find all of the problems that the proactive approach might find, but is a valid way to start using PREfast.
Rather than postpone using PREfast until there is "time to do it right," it is better to follow a reactive model-or even to apply no annotations and ignore the noise. Typically, the time that is saved when PREfast finds even a few problems is enough to compensate for the effort of applying annotations, compared to the time that is required to debug those problems by using conventional methods. Over time, transitioning to a more proactive approach is a good idea, and the value of being more proactive should become obvious.
Annotations should reflect a successful call to the function. PREfast should catch inherently ill-formed or unsuccessful calls, not just calls that cause the system to crash. Annotations should reflect the intent of the function as reflected by its interface, not the actual implementation of the function.
A common example is optional parameters. A lot of code is written that checks to see if a parameter is NULL. But is a NULL parameter genuinely optional? For example:
If the function ignores the NULL parameter and proceeds to do something useful, then the parameter is optional.
If the function returns an error when it is called with a NULL parameter, the parameter is not optional. Instead, the function defends itself from bad usage.
If PREfast can tell the caller that a potentially NULL parameter will cause the function to fail, then PREfast can find the potential bug rather than having the bug surface at runtime in some obscure and hard-to-test-for circumstance. Having a function defend itself against bad parameters is good practice, but the fact that it does so does not make the parameter "optional" in this sense.
When annotating a function, it is important to consider how the function might evolve. The annotation should reflect the intention of the function designer, not necessarily the current implementation of the function.
For example, a function as implemented might work correctly with parameter values that are different from those that the designer intended. Although it is tempting to annotate the function based upon what the code actually does, the designer might be aware of future requirements, such as the need to maintain some restriction to support some future enhancement or pending system requirement.
Annotations can expose two kinds of "conflicts" between a function's implementation and its documentation:
The code and the documentation are factually inconsistent-one or the other must be fixed.
The documentation describes as required something that is true about the implementation but is not enforced-that is, an "incorrect" function call would succeed.
In this case, the function designer must decide whether to revise the documentation or use annotations to enforce the documented behavior of the function, to ensure that the function is used correctly as intended.
The compiler also detects a number of potential errors, not all of which are detected by PREfast. It is good practice to use the following compiler flags:
/W4 | Warn at level 4 |
/WX | Make warnings fatal |
/Wp64 | Warn on 64-bit portability issues |
Minimize the scope of any #pragma warning annotations that are used to suppress false-positive warnings from the compiler.
Annotations are implemented in different ways, depending on the exact version of the analysis tools you are using. Documentation and header file comments that mention __declspec or annotations that use a square-bracket notation similar to those used in C# can be ignored for the purposes of using PREfast. Only the annotations that are discussed in this chapter are officially supported for annotating driver code.
Note Annotations can also be provided through a model file. For various reasons, many Microsoft-provided system functions are annotated in the model file and not yet annotated in the source code. The model file is no more powerful than the annotations that are described here and is more difficult to use, so its use is neither documented nor recommended for new annotations.
Some annotations are currently only partially implemented. Some annotations might be sufficient to suppress a spurious warning in PREfast, but they do not enable the additional checks that the annotation name might imply. In other cases, they are simply structured comments. These annotations are being considered for future versions of PREfast.