Walk-through 3: Tuning with knowledge base records

Klocwork 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:

  • Example 1: Reducing false positives with ALLOC ignore
  • Example 2: Tuning memory leak issues with XMRF
  • Example 3: Tuning the NPD checker with NORET
  • Example 4: Tuning memory leaks with ALLOC
  • Implementing additions to the knowledge base

For details of knowledge base record syntax, see C/C++ knowledge base reference.

Example 1: Reducing false positives with ALLOC ignore

There 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 XMRF

By 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 NORET

The 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 ALLOC

The 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:

  • allocates new memory under conditions affected by its parameters (preconditions)
  • stores the allocated address in a particular parameter or result (returned address)
  • returns particular values if memory allocation was successful (postconditions)

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:

  • the allocation belongs to the 'stdc' group, and can be expected to behave like 'malloc' and related functions in terms of being a natural source for 'free'
  • there is no precondition (1), the allocation is always attempted
  • when the allocation is successful, the pointer to the newly allocated memory is stored in the address pointed to by the first parameter (*$1)
  • the allocation is successful only if the return code is 0 ($$ EQ 0)

Implementing additions to the knowledge base

To 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:

  1. Create a new text file with the extension .kb.
  2. Using the editor of your choice, add records as required, with each record occupying one line of the file.
  3. Make sure you're overriding the generated knowledge base exactly as you intend. Your new .kb file records will override all the records in the generated .kb with the same record name for that particular function, and only those records. For example, the CONDNORET record shown in the following sample file will override any and every CONDNORET record in the generated .kb file for the error_msg function, but won't affect any NORET, BO, CHECKRET, or any other generated record.

Here is an example of a .kb file containing the records from the four examples in this article:

    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)

If you're not sure of the effect your new record will have, copy all the records of the same name for your function in the generated file that you don't want overridden into your new .kb file.

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.