PREfast logs every error that it can find in your source code. If the code does not give PREfast any assurance that the code is safe, PREfast behaves as if the code is unsafe.
For example, suppose PREfast encounters a code path that dereferences a pointer. Could the pointer ever be NULL? If there is some reason to suspect that it could be-for example, if earlier code tests for NULL but subsequent code accesses the pointer in an unsafe manner, PREfast issues a warning about dereferencing a NULL pointer. If there is no reason to suspect that the pointer could ever be NULL, then PREfast does not issue a warning.
PREfast can miss some potential bugs due to faulty assumptions-these are often referred to as false negatives. Other PREfast warnings might not represent actual errors in code-these are often referred to as false positives or "noise."
Do not dismiss false positives in PREfast results. They often flag assumptions about how the code will actually execute. For example, if a variable is initialized inside a loop, you might know that the loop could not be executed zero times or that the variable is safely initialized by some other function. However, the PREfast warning that this variable might be uninitialized identifies the assumption that the variable will always be safely initialized, which is valuable information.
Some noise is unavoidable and you might simply need to ignore it or suppress it. PREfast analyzes code on a function-by-function basis, so it has no information about global state or work that is performed outside the current function that might affect a given code path. For this reason, PREfast often reports false positives related to the following:
Members of structures or other objects that are not simple variables.
These can mislead the more accurate tests for flow of control.
Wrapper functions.
These can cause false positives for many kinds of warnings, such as memory leaks, resource leaks, NULL pointer dereferencing, uninitialized memory access, and incorrect argument types. Many of the problems with wrapper functions can be addressed with source code annotations.
This section describes techniques you can use to reduce noise and improve PREfast results.
Minor changes to your code can help reduce noise caused by coding style. Although these changes might seem trivial, they can both suppress noise and help to make the code easier for other developers to maintain. In addition, PREfast often reports the same error repeatedly in slightly different contexts. Thus, a single code change can eliminate a number of warnings.
The following kinds of PREfast warnings often identify common causes of noise in PREfast results:
Warnings about uninitialized variables
Initialize variables when you declare them, whenever you can.
Warnings triggered by an explicit test for STATUS_SUCCESS
Replace explicit tests for STATUS_SUCCESS with the NT_SUCCESS macro, as shown in the following code fragment:
status = IoAttachDevice( ); if (!NT_SUCCESS(status)) { //handle error }
Multiple warnings that are triggered by a single error, such as multiple uses of the same NULL pointer
Fix the underlying error and then rerun PREfast to produce a shorter message list relatively quickly.
Warnings that identify assumptions
Make assumptions in your code explicit by using assertions such as an ASSERT macro or an __analysis_assume (expression) source code annotation. For example, if PREfast detects potential use of an uninitialized variable that you know is initialized safely elsewhere, add an assertion to confirm that the path in which the variable is left uninitialized is impossible and take advantage of the checked build's notification if the assertion happens to fail.
Info See "How to: Specify Additional Code Information" on MSDN for details about __analysis_assume-online at http://go.microsoft.com/fwlink/?LinkId=80906.
Warnings that identify errors in the use of parentheses or other syntactic misuses
Add parentheses or otherwise modify the code to make your intentions explicit. Without these modifications, the code might not behave as you intend because of the precedence rules of C.
Warnings that a slight rearrangement of code can eliminate
For example, if a variable is initialized inside a loop that might be executed zero times, thus leaving the variable uninitialized, consider rewriting the code to initialize the variable outside the loop or restructuring the loop so that it is always executed at least once.
Warnings about potentially incorrect use of function pointers
Use function typedef declarations to identify system callback types. PREfast can take advantage of these declarations to check that function pointers are being used correctly, which both reduces noise and improves the accuracy of the analysis. See "Example 5: Function Type Class Mismatch" earlier in this chapter for an example.
PREfast simply ignores inline assembler, so the use of inline assembler in your code can prevent PREfast from fully analyzing a code path and can cause both false positives and false negatives in PREfast results. The use of inline assembler also makes your code less portable to newer architectures that Windows supports.
To reduce the effects of inline assembler on PREfast results:
Use the utility functions provided by newer compilers. For example, use __debugbreak instead of __asm int 3. See your compiler documentation for details.
If you cannot avoid using inline assembler, place it in an __inline or __forceinline function so that PREfast can analyze the rest of the function more effectively.
If you determine that a PREfast warning is a false positive or simply noise that does not need to be fixed, you can use a #pragma warning directive to suppress the PREfast warning. Unlike a filter that temporarily changes the results that appear in the PREfast defect log, a #pragma warning directive affects PREfast analysis of your code.
In the #pragma warning directive, use the PREfast warning number to identify the warning to suppress. You can use the (push) and (pop) statements to confine the effect of the directive to the line of code that is producing the false positive, as shown in the following example:
#pragma warning (push) #pragma warning( disable:6001 ) // FLAG is always present in arr arr[i+1] = 0; #pragma warning (pop) j++; // Warning 6001: Actual error
As an alternative to push and pop, you can use the suppress specifier to suppress the warning for the line of code-and only that line of code-that immediately follows the #pragma warning statement, as shown in the following example:
#pragma warning( suppress : 6001 ) arr[i+1] = 0; // Warning 6001 is suppressed j++; // Warning 6001 is reported
Remember that #pragma warning is a drastic measure because it changes your source code to prevent PREfast from reporting an error. Consider whether to simply ignore or filter the PREfast warning until a future version of PREfast can produce more accurate results.
If you do use #pragma warning to suppress a PREfast warning, be sure to add a comment in your code to explain why the warning is suppressed. When you install a new version of PREfast, disable all #pragma warning directives and run the new version on your code, to see if it fixes the problem.
Tip See "Using a Pragma Warning Directive" in the WDK-online at http://go.microsoft.com/fwlink/?LinkId=80908.
Annotations can provide PREfast with information about global state or work performed outside the function being analyzed that might affect a given code path. With more specific information about the intended use of an annotated function, PREfast can better determine whether a particular bug exists. Annotations can greatly reduce the incidence of false positives and false negatives in PREfast results.
For example, you can use the _bcount(size) partial annotation to express the size of a buffer in bytes. The size parameter can be a number, but it is usually the name of some parameter in the function that is being annotated. The memset function provides a good example of this, as shown in the following example:
void * memset( __out_bcount(s) char *p, __in int v, __in size_t s );
In this example, __out_bcount(s) specifies that content of the memory at the p output parameter is set by the function and that the value of s is the number of bytes to be set. The information that this provides is something that "everyone" knows, but the compiler does not. Nothing in the C source code tells the compiler that p and s are related in this way. Only the annotation provides this information.
With this additional information, PREfast can check the implementation of memset to be sure it never accesses past the end of the buffer-that is, it never accesses more than s bytes into the buffer. PREfast also can often check that the value of p+s is within the declared bounds of the array when the function is called. In this case, the size is expressed in bytes because that is what memset expects.
Inside Out | PREfast supports both the general-purpose annotations defined in %wdk%\inc\api\Specstrings.h, which can be applied to both drivers and general kernel-mode and user-mode code, and the driver-specific annotations defined in %wdk%\inc\ddk\Driverspecs.h, which are specifically designed for use in kernel-mode drivers. The rest of this chapter discusses these annotations in detail. |