>
>
Additional configuration of C and C++ d…


Additional configuration of C and C++ diagnostic rules

User annotations are special comments that can be specified in the source code for additional diagnostic rules configuration. An annotation can be found at one of the following locations:

  • In the analyzed file (*.c, *.cpp, *.cxx, ....). The annotation will be applied only within the context of this file.
  • In the header file (*.h, *.hxx, ....). The annotation will be applied to all analyzed files that include this header file.
  • In the diagnostic rules configuration file (.pvsconfig). The annotation will be applied to all analyzed files of a project/solution.

User annotations that change the behavior of diagnostic rules are listed below. This functionality is available only for the C and C++ analyzer. User annotations given in this section are omitted when analyzing projects written in other programming languages.

Note. By default, user annotations are not applied to virtual functions. Here you can learn how to enable it.

Enforcing diagnostic rules on Unreal Engine projects

When you check the project based on Unreal Engine, the analyzer applies diagnostic rules (for example, V1100 and V1102) that detect errors typical for UE projects. PVS-Studio applies the diagnostic rules only when it detects header files from the directory containing UE source code.

If a project contains compilable files that do not have such header files, the diagnostic rules are not applied, even if they are enabled. This way, the analyzer avoids generating irrelevant warnings for projects that do not use UE.

If you want to enforce a set of diagnostic rules on an arbitrary compilable file or a group of files, add the following comment:

//V_TREAT_AS_UNREAL_ENGINE_FILE

A function can/cannot return null pointer

There are many system functions that can return a null pointer under certain conditions. Functions like 'malloc', 'realloc', and 'calloc' are good examples. These functions return 'NULL' when they fail to allocate a buffer of the specified size.

Sometimes you may want to change the analyzer's behavior and make it think, for example, that 'malloc' cannot return a null pointer. For example, this might be handy if the user employs system libraries that handle out-of-memory errors in a specific way.

An opposite scenario is also possible. The user may help the analyzer and specify that a certain system or user-declared function can return a null pointer.

With user annotations, you can specify whether a function can or cannot return a null pointer.

  • V_RET_NULL— the function can return a null pointer
  • V_RET_NOT_NULL — the function cannot return a null pointer

Annotation format:

//V_RET_[NOT]_NULL, function: [namespace::][class::]functionName
  • The 'function' key — after ':', insert the full name of the function. It consists of the namespace name, the class name, and the function name. Namespace and/or class are optional.

For example, the user wants to specify to the analyzer that the 'Foo' function of the 'Bar' class within the 'Space' namespace cannot return a null pointer. Then the annotation looks like this:

//V_RET_NOT_NULL, function: Space::Bar::Foo

User annotations support nested namespaces and nested classes. Suppose the 'Space2' namespace is within the 'Space1' namespace. The 'Bar1' class is within the 'Space2' namespace. The 'Bar2' class is within the 'Bar1' class. The 'Bar2' class has the 'Foo' member function, which can't return a null pointer. Then you can annotate this function the following way:

//V_RET_NOT_NULL, function: Space1::Space2::Bar1::Bar2::Foo

For system functions, the annotation can be located at the global header file (for example, at the precompiled header), or at the diagnostic rules configuration file.

For clarity, let's look at two examples of system function annotations.

The function does not return a null pointer:

//V_RET_NOT_NULL, function:malloc

Now the analyzer thinks that the 'malloc' function cannot return a null pointer and, therefore, will not issue the V522 warning for the following code:

int *p = (int *)malloc(sizeof(int) * 100);
p[0] = 12345; // ok

The function returns a pointer that can be null:

//V_RET_NULL, function: Memory::QuickAlloc

With this comment, the analyzer starts issuing a warning for the following code:

char *p = Memory::QuickAlloc(strlen(src) + 1);
strcpy(p, src); // Warning!

In projects with special quality requirements, you may need to find all functions that return a pointer. For this purpose, use the following comment:

//V_RET_NULL_ALL

We don't recommend using this annotation as it causes too many warnings to be issued. However, if you require this for your project, you can add the return pointer check for all such functions in your code using this specific comment.

Configuration of the assert() macro handling

By default, the analyzer equally checks the code where the 'assert' macro is present regardless of the project's configuration (Debug, Release, ...). So, the analyzer does not take into account that code execution is interrupted if there is a false condition.

To set another analyzer behavior, use the following comment:

//V_ASSERT_CONTRACT

Please note that in this mode, the analysis results may differ depending on the way the macro is expanded in the project configuration being checked.

Let's look at this example to make it clear:

MyClass *p = dynamic_cast<MyClass *>(x);
assert(p);
p->foo();

The 'dynamic_cast' operator can return the 'nullptr' value. Thus, in the standard mode, the analyzer issues a warning on the possible null pointer dereference when calling the 'foo' function.

But if you add the '//V_ASSERT_CONTRACT' comment, the warning will be gone.

You can also specify the name of the macro which the analyzer will handle in the same way it handles 'assert'. To do this, use the following annotation:

//V_ASSERT_CONTRACT, assertMacro:[MACRO_NAME]

The 'assetMacro' key is the name of the macro that the analyzer handles similarly to 'assert'. Instead of '[MACRO_NAME]', insert the name of the annotated macro.

Example:

//V_ASSERT_CONTRACT, assertMacro:MY_CUSTOM_MACRO_NAME

Now the analyzer processes the 'MY_CUSTOM_MACRO_NAME' macro as 'assert'.

If you need to specify multiple macro names, add the separate '//V_ASSERT_CONTRACT' directive for each of them.

An alias for a system function

Some projects use custom implementations of various system functions, such as 'memcpy', 'malloc', and so on. In this case, the analyzer is unaware that such functions behave similarly to their standard implementations. You can specify which custom functions correspond to system functions.

Annotation format:

//V_FUNC_ALIAS, implementation:imp, function:f, namespace:n, class:c
  • The 'implementation' key — a name of a system function for which an alias is specified.
  • The 'function' key – an alias name. The function specified in this option must have exactly the same signature as the one specified in the 'implementation' option.
  • The 'class' key – a class name. Optional.
  • The 'namespace' key – a namespace name. Optional.

Usage example:

//V_FUNC_ALIAS, implementation:memcpy, function:MyMemCpy

Now, the analyzer will process calls to the 'MyMemCpy' function in the same way it processes calls to 'memcpy'.

Custom formatted IO function

You can specify the names of your own functions that should be format validated. It's assumed that the principle of string formatting corresponds to the 'printf' function.

You will need the user annotation for this. Here is the usage example:

//V_FORMATTED_IO_FUNC,function:Log,format_arg:1,ellipsis_arg:2
void Log(const char *fmt, ...);

Log("%f", time(NULL)); // <= V576

Annotation format:

  • The 'function' key specifies the full name of the function, which consists of the namespace, the class name, and the function name. Nested namespaces and nested classes are supported.
  • The 'format_arg' key specifies the number of the function argument that contains the format string. Numbers start at 1 and should not exceed 14. This argument is required.
  • The 'ellipsis_arg' key specifies the number of the function argument with the ellipsis (three dots). The number also starts at 1 and should not exceed 14. Moreover, the 'ellipsis_arg' number should be greater than the 'format_arg' number (because ellipsis can only be the last argument). This argument is also required.

The most complete usage example:

namespace A
{
  class B
  {
    void C(int, const char *fmt, ...);
  };
}

//V_FORMATTED_IO_FUNC, function:A::B::C, format_arg:2, ellipsis_arg:3