Tuning C/C++ analysisTuning C/C++ analysisTuning allows you to increase the accuracy of the Klocwork analysis by finding more real defects, or reducing the number of false positives and uninteresting issues. There are two ways of looking at tuning: changing the code that Klocwork sees during analysis, or changing Klocwork's understanding of the code. How do you tune Klocwork?This article describes three common methods of tuning Klocwork analysis:
The walk-through examples in this article describe typical scenarios that show how to use these tuning methods. Walk-through 1: Handling NPD false positives with the __KLOCWORK__ macroA typical use of the __KLOCWORK__ macro is in handling NPD false positives caused by custom asserts.
AssertionsAssertions are widely used in C/C++ code to implement validity checks and the pre- and post-conditions of a function. For example, if a function expects its argument to be a non-NULL pointer, the code typically looks like this: 1 void my_function(my_data_t *ptr) 2 { 3 MY_ASSERT(ptr != NULL); 4 char *name = ptr->name; 5 } It's important for a static analysis tool to recognize assertions in the code and handle them properly, as a condition check. For example, if MY_ASSERT in the above code sample is not recognized as "check ptr for NULL", Klocwork would report a NULL pointer dereference issue at line 4 (a false positive). Although the standard C/C++ library provides a default implementation of assert() in assert.h (typically a macro that checks the condition and aborts on failure), it is common practice to define a custom ASSERT macro, to provide:
Using the __KLOCWORK__ macroAdding conditionally compiled code to your source is a very familiar way of dealing with differing environments. The __KLOCWORK__ macro, which is always defined by the Klocwork compiler, allows certain code to be active only when Klocwork is working and is otherwise ignored. Adding an alternative definition of an assert-type macro in a __KLOCWORK__ block can be non-intrusive and simple to manage. For example, assume we have the following code: 1 MY_ASSERT(a!=NULL); 2 use(a->mm); If Klocwork incorrectly processes your production definition of MY_ASSERT so that it doesn't understand the abort condition, it may report a false positive NPD on line 2. You can reduce the probability of this happening by providing Klocwork with a simpler, more standard, definition of your custom assert: 1 #ifdef __KLOCWORK__ 2 # define MY_ASSERT(x) do { if (!(x)) abort(); } while (0) 3 #else 4 # define MY_ASSERT(x) ... production, advanced definition of assert ... 5 #endif Walk-through 2: Handling NPD false positives with #kw_overrideThe __KLOCWORK__ macro supports generic code replacement using standard preprocessor mechanisms, and is very powerful, but it still means that you have to modify your source code specifically for Klocwork. If the modification you need to make to your code is localized to one or more macro definitions, you can use an override file and avoid source modification. An override file is a header file that you import into your Klocwork project. The Klocwork compiler automatically includes and processes your override file with every source module built for the project. In the file, the Klocwork-specific preprocessor directive, #kw_override, is used to change the definition of macros in your source. Assume that in your production source you have this macro definition: # define MY_ASSERT(x) (void) ((x) __handle_failed_assert(#x, __FILE__, __LINE__, __FUNCTION__)) If you want the compiler to see a simpler, more direct assertion, you can create an override file containing the following definition: # kw_override MY_ASSERT(x) do { if( !(x) ) abort(); } while(0) When you import this override file into your Klocwork project and build the project, a source function such as: 1 void myFunction(char *aString) 2 { 3 MY_ASSERT(aString); 4 } is expanded by the Klocwork compiler to the Klocwork-specific definition of MY_ASSERT: 1 void myFunction(char* aString) 2 { 3 do { if( (!aString) ) abort(); } while(0); 4 } while your production compiler continues to expand the macro using the original definition. The #kw_override directive affects only macros, and only those macros that are defined in your production source. To apply the override file to the integration build analysis, import it into a project or the projects_root directory. To apply the override file to your standalone desktop, use kwcheck import. Walk-through 3: Tuning with knowledge base recordsKlocwork automatically generates a knowledge base during the first analysis of your source code, and updates this information with every new analysis. Native system function behavior is specified in the default knowledge bases. To handle false positives and negatives, you can fine-tune your results by creating your own knowledge base records to change the way Klocwork understands your code. In this walk-through, we show four examples of tuning with knowledge base records, and explain how to implement the addition of knowledge base records:
For details of knowledge base record syntax, see C/C++ knowledge base reference.
Example 1: Reducing false positives with ALLOC ignoreThere are times when you may want to avoid issue reports on possible memory allocation functions. For example, in the following snippet, the Klocwork memory leak checker, MLK, would normally report a memory leak for 'p' at line 9. 1 void* C::custom_alloc(unsigned int n) 2 { 3 return malloc(n); 4 } 5 6 void C::foo() 7 { 8 void* p = custom_alloc(10); 9 return; 10 } You can suppress the finding by adding an ALLOC ignore knowledge base record to the analysis: C::custom_alloc - ALLOC ignore This record specifies a fully-qualified function name, the function key (always a hyphen, '-'), the record kind (ALLOC in this case), and the specification for this knowledge base record, 'ignore'. As a result of the new record, Klocwork assumes that custom_alloc isn't a memory-allocating function, and no report is issued.
Example 2: Tuning memory leak issues with XMRFBy default, the memory leak checker assumes that if a pointer to allocated memory is passed to an unknown function, that pointer will probably be modified in some way (aliased, stored in a shared pool, or released), and so stops tracking it. This behavior can be modified with an XMRF record, which you can use to specify whether the memory ownership will be retained. If memory ownership is retained, tracking continues, but if memory ownership is not retained, the engine assumes the called function is taking responsibility for the memory and stops tracking it. In the following snippet, Klocwork would normally stop tracking 'p' after line 8, and no report would occur at line 9: 1 // some library function 2 // does not take ownership or free memory pointed to by 'ptr' 3 void process_ptr(void*); 4 5 void foo() 6 { 7 void* p = malloc(100); 8 process_ptr(p); // Klocwork assumes that ownership of 'p' is not retained 9 } // FN: no memory leak reported here To avoid this false negative, you can add a memory retention (XMRF) record to the analysis: process_ptr - XMRF $1 : 1 This record informs the checker that the calling function retains ownership of the memory passed to 'process_ptr' as its first argument ($1). The retention flag can be either 0 to signal that the caller does not retain ownership, or 1 to show that the caller does retain ownership. When this record is applied to the analysis of the sample code snippet, the checker keeps tracking pointer 'p' after line 8, and correctly reports a memory leak at line 9. Conversely, when a function parameter is a qualified const, or 'this' for a non-static class method, the MLK checker continues to track a pointer to allocated memory even if it's passed to a function that the checker considers unknown. These situations can result in false positives, as shown by the following two snippets: 1 // Collect 'ptr' for pool management 2 extern int pool_ptr(const void* ptr); 3 4 int foo() 5 { 6 void* p = malloc(100); 7 int res = pool_ptr(p); //"const" heuristics: keep tracking 'p' 8 return res; // FP: memory leak reported 9 } 1 class C 2 { 3 public: 4 // Collect 'this' for pool management 5 int pool_self(); 6 }; 7 8 int bar() 9 { 10 C* c = new C(); 11 int res = c->pool_self(); // 'this' heuristics: keep tracking 'c' 12 return res; // FP: memory leak reported 13 } In these cases, the following XMRF records can be added to the analysis to avoid the false positive reports: pool_ptr - XMRF $1 : 0 C::pool_self - XMRF $0 : 0 These entries tell the checker that memory ownership is not retained by the caller and shouldn't be tracked afterwards.
Example 3: Tuning the NPD checker with NORETThe NULL pointer dereference checker, NPD, may produce false positives if it doesn't know that a function call never returns. For example, it is typical for programs to use error severity levels to determine a response in a reporting function, such as an abort or longjmp() away from the current context. Lacking that knowledge, the following code snippet would normally cause an NPD report detailing a check for NULL at line 15 followed by a dereference at line 17: 1 class C 2 { 3 public: 4 static C* findInstance(const char* name); 5 virtual void run(); 6 }; 7 8 // Error handling, abort() on ERROR or FATAL 9 enum { INFO, ERROR, FATAL }; 10 void error_msg(int level, const char *msg); 11 12 void foo() 13 { 14 C *ptr = C::findInstance("default"); 15 if( !ptr ) 16 error_msg(FATAL, "Can't find default object"); 17 ptr->run(); 18 } To avoid false positives when the error_msg function reports a FATAL error and then always exits, you could use the following NORET record: error_msg - NORET However, since error_msg in this code exits only when it is called with the first argument greater than 1 (ERROR or FATAL), NORET is too general for this situation and you should use CONDNORET instead: error_msg - CONDNORET $1 > 1
Example 4: Tuning memory leaks with ALLOCThe memory allocation record ALLOC allows you to specify how, and under what conditions, memory is allocated if you don't make use of typical system primitives. Specifically, ALLOC allows you to define that a function:
Depending on how much it is able to determine about 'get_buf' and the inter-relationship of the return code and a successful allocation, Klocwork might report a false positive memory leak at line 10 in the following code snippet: 1 // get_buf allocates memory and returns 0 on success 2 extern int get_buf(void** buf); 3 4 void process_data() 5 { 6 void* buffer; 7 if( !get_buf(&buffer) ) 8 free(buffer); 9 else 10 fprintf(stderr, "operation failed!\n"); 11 } To eliminate such a report, you can define an ALLOC knowledge base record: get_buf - ALLOC stdc : 1 : *$1 : $$ EQ(0) This record states that:
Implementing additions to the knowledge baseTo implement your knowledge base records, you need to create a knowledge base file and apply the file to the Klocwork analysis.
Creating a knowledge base file:To create a knowledge base file:
C::custom_alloc - ALLOC ignore process_ptr - XMRF $1 : 1 pool_ptr - XMRF $1 : 0 C::pool_self - XMRF $0 : 0 error_msg - CONDNORET $1 > 1 get_buf - ALLOC stdc: 1 : *$1 : $$EQ(0)
Applying the file to your analysis:To apply your new configuration file to the integration build analysis, import it into a project or the projects_root. For example: kwadmin import-config <projectname> <file> The file will be synchronized to connected desktops. To apply your knowledge base file to a standalone desktop project, see Customizing your desktop analysis. Run a full build analysisFor your tuning changes to take effect, you need to run a full build analysis. |