Detect 64-Bit Portability Issues

The PVS-Studio product contains a set of static analysis rules intended to detect 64-bit errors in code of C/C++/C++11 applications.

This set of rules was shipped earlier as a separate solution called Viva64. Now the Viva64 static analyzer is included into the PVS-Studio software product while the name "Viva64" is associated with the corresponding set of rules.

The set of specialized rules of Viva64 lets you detect defects occurring when developing new 64-bit applications or when porting 32-bit code to 64-bit systems. Following the analyzer's recommendations will also help you optimize 64-bit code and make it more efficient.

Detecting 64-bit errors is detecting code fragments which work correctly in 32-bit versions of software and fail in 64-bit programs. The failure in a 64-bit program might look as a hang, crash, slow-down or unpredictable behavior. Let's look at some examples of 64-bit defects detected by PVS-Studio. To learn about the samples in detail, please read the article "A Collection of Examples of 64-bit Errors in Real Programs".

A sample of a 64-bit error when working with Windows API

HANDLE hFileMapping = CreateFileMapping(
  (HANDLE) 0xFFFFFFFF,
  NULL, PAGE_READWRITE, dwMaximumSizeHigh,
  dwMaximumSizeLow, name);

It is the magic number 0xFFFFFFFF which is used instead of the correct reserved constant INVALID_HANDLE_VALUE. According to C/C++ rules, value 0xFFFFFFFF has the "unsigned int" type since it cannot be represented by the "int" type. Therefore, value 0xFFFFFFFFu will turn into 0x00000000FFFFFFFFu when extending to the 64-bit type. This is incorrect in a Win64-program where the INVALID_HANDLE_VALUE constant has value 0xFFFFFFFFFFFFFFFF.

A sample of a 64-bit error leading to an access violation error

int A = -2;
unsigned B = 1;
int array[5] = { 1, 2, 3, 4, 5 };
int *ptr = array + 3;
ptr = ptr + (A + B);
printf("%in", *ptr); // Access violation

According to C++ rules, the A variable in the "A+B" expression is cast to the unsigned type. A and B are summed up. The result is value 0xFFFFFFFF of the unsigned type. The "ptr + 0xFFFFFFFFu" expression is calculated. The result depends on the capacity of the pointer on the particular platform. In a 32-bit program, the expression will equal "ptr - 1" and number 3 will be successfully printed. In a 64-bit program, value 0xFFFFFFFFu will be added to the pointer, which will cause the pointer to get far outside the array's bounds.

A sample of a 64-bit error causing a hang

size_t Count = BigValue;
for (unsigned Index = 0; Index != Count; Index++)
{ ... }  

This loop will never stop if Count > UINT_MAX. Suppose this code worked with the number of iterations less than UINT_MAX on a 32-bit system. But the 64-bit version of the program can process more data and it may need more iterations. Since the Index variable's values lie within the range [0..UINT_MAX], the condition "Index != Count" will never be fulfilled and this will cause an eternal loop.

Errors of mixed use of 32-bit and 64-bit types

One might easily forget that although a whole expression has a 64-bit type, its parts might have 32-bit types and overflows might occur inside them. Let's consider various expressions and results of their execution:

int x = 100000;
int y = 100000;
int z = 100000;
intptr_t size = 1;                  // Result:
intptr_t v1 = x * y * z;            // -1530494976
intptr_t v2 = intptr_t(x) * y * z;  // 1000000000000000
intptr_t v3 = x * y * intptr_t(z);  // 141006540800000
intptr_t v4 = size * x * y * z;     // 1000000000000000
intptr_t v5 = x * y * z * size;     // -1530494976
intptr_t v6 = size * (x * y * z);   // -1530494976
intptr_t v7 = size * (x * y) * z;   // 141006540800000
intptr_t v8 = ((size * x) * y) * z; // 1000000000000000
intptr_t v9 = size * (x * (y * z)); // -1530494976

From the viewpoint of compilers, such expressions are safe because they only have conversions of 32-bit types to 64-bit types. However, such constructs may contain subtle errors that occur at particular sets of input data. The analyzer allows you to detect and examine such potentially dangerous expressions.

A sample of a 64-bit error when working with virtual functions

One of the nice examples is using incorrect types of arguments in definitions of virtual functions. It is often nobody's inaccuracy but just an "accident" no one is guilty of, but it causes an error. Consider the following case.

In old versions of the MFC library, there is the CwinApp class that has the WinHelp function:

class CWinApp {
  ...
  virtual void WinHelp(DWORD dwData, UINT nCmd);
};

To enable a user application to show its own help, you had to overlap this function:

class CSampleApp : public CWinApp {
  ...
  virtual void WinHelp(DWORD dwData, UINT nCmd);
};

Then 64-bit systems appeared. The MFC developers had to change the interface of the WinHelp function (and some other functions as well) in the following way:

class CWinApp {
  ...
  virtual void WinHelp(DWORD_PTR dwData, UINT nCmd);
};

The types DWORD_PTR and DWORD coincided in the 32-bit mode but they do not coincide in the 64-bit mode. Certainly, the developers of the user application must also change their type to DWORD_PTR but they need to know about it to do this. As a result, an error occurs in the 64-bit program since the WinHelp function is not called in the user class.

A sample of unreasonable memory consumption

struct MyStruct
{
  bool m_bool;
  char *m_pointer;
  int m_int;
};

Creation of such structures is not an error. But if there are too many of such structures, it may cause a significant increase of consumed memory and slow-down of the program. This structure occupies 24 bytes in a 64-bit program, but we may reduce this size to 16 bytes through mere rearrangement of fields:

struct MyStructOpt
{
  char *m_pointer;
  int m_int;
  bool m_bool;
};

We listed only some kinds of 64-bit errors here. To learn more about them, please see articles on our site and lessons on 64-bit software development.

Advantages of using static analysis to detect 64-bit defects

Development of contemporary programs requires that programmers know patterns of errors occurring when writing 64-bit source code. Many of 64-bit errors are unobvious and require much skill and attention. The Viva64's rule set implemented in PVS-Studio lets you diagnose these types of errors, thereby performing two functions: it eliminates defects in a program and teaches the programmer how to write correct code taking into account the specifics of 64-bit systems.

Using PVS-Studio reduces the risk adoption of 64-bit platforms involves and lets you be more confident when estimating terms of implementing 64-bit projects. The process of estimate is described in detail in the article "Estimating the cost of 64-bit migration of C/C++ applications".

Developers can release 64-bit applications 3-4 times quicker with the PVS-Studio tool than without it.

The static code analysis methodology we use has significant advantages over other types of analysis because it allows you to cover the whole program code. The procedure of code check cannot damage the code itself in any way. The analysis process if completely controlled by person and it is the programmer who decides if it needs modification. With the help of PVS-Studio you will be able to detect more than 90% of errors already at the stage of developing (working with the source code) 64-bit programs.

The PVS-Studio tool is shipped together with a large knowledge base on 64-bit code development (help system, articles, samples) that will improve programmers' knowledge.

Additional Information