How can you detect elusive 64-bit portability issues?

I found a fragment similar to this in some (C ++) code that I am preparing for a 64-bit port.

int n; size_t pos, npos; /* ... initialization ... */ while((pos = find(ch, start)) != npos) { /* ... advance start position ... */ n++; // this will overflow if the loop iterates too many times } 

While I seriously doubt that this will actually cause a problem even in applications with intensive memory, it is worth taking a look from a theoretical point of view, since such errors can occur, which will cause problems. (Change n to short in the example above, and even small files can overflow the counter.)

Static analysis tools are useful, but they cannot detect such an error directly. (Not yet.) The counter n does not participate in the while statement at all, therefore it is not as simple as other loops (where type conversion errors lead to an error). Any tool will have to determine that the cycle will execute more than 2 31 times, but this means that it should be able to evaluate how many times the expression (pos = find(ch, start)) != npos will be evaluated as true - not a small feat ! Even if the tool can determine that the loop can execute more than 2 31 times (say, because it recognizes that the find function is working on the string), how could it know that the loop won’t execute more than 2 64 times, overflowing the size_t value too ?

It seems obvious that the human eye is required for the final identification and correction of this kind of error, but are there schemes that give such an error, so it can be checked manually? What kind of errors do I have to keep track of?

EDIT 1: Since the short , int and long types are intrinsically problematic, such an error can be found by examining each instance of these types. However, given their ubiquity in legacy C ++ code, I'm not sure if this is practical for most software. What else gives this error? It seems that every while may exhibit some error? ( for loops, of course, are not protected from this!) How bad is such an error if we are not dealing with 16-bit types such as short ?

EDIT 2: Here is another example showing how this error appears in a for loop.

 int i = 0; for (iter = c.begin(); iter != c.end(); iter++, i++) { /* ... */ } 

This is essentially the same problem: loops rely on some variable that never interacts directly with a wider type. The variable may still overflow, but the compiler or tool does not detect a casting error. (Strictly speaking, they are not.)

EDIT 3: The code I'm working with is very large. (10-15 million lines of code for C ++ alone.) It is impossible to verify all this, so I am interested in identifying this problem (even if it leads to a high false positive rate) automatically.

+4
source share
3 answers

Review codes. Get a bunch of smart people looking at the code.

Using short , int or long is a warning sign, since the range of these types is not defined in the standard. Most of the usage should be changed to the new int_fastN_t types in <stdint.h> , using serialization on intN_t . Well, actually these <stdint.h> types should be used for the typedef new application types.

This example should really be:

 typedef int_fast32_t linecount_appt; linecount_appt n; 

This suggests that the linecount fits in 32 bits, and also makes it easier to fix code when changing design requirements.

+1
source

Its clear what you need is an intelligent range analyzer tool to determine which range of values ​​is calculated compared to the type in which these values ​​are stored. (Your main objection is that the intelligent range analyzer is human). You may need some additional code annotations (manually well placed typedefs or statements that provide explicit range limitations) to provide good analysis and otherwise handle arbitrarily large user input.

You will need special checks to handle the place where C / C ++ says that arithmetic is legal but dumb (for example, the assumption that you do not want overflow [twos supplement]). For your example, n ++ (equivalent to n_after = n_before + 1) n_before may be 2 ^ 31-1 (due to your observations about strings), so n_before + 1 may be 2 ^ 32, which overflows. (I think the standard C / C ++ semantics say that overflowing to -0 without complaint is okay).

Our DMS Software Reengineering Toolkit actually has a built-in range analyzer ... but it is not connected to the CMS DMS interface; we can only trade so fast: - {[We used it in COBOL programs for different tasks with ranges].

In the absence of such range analysis, you can probably find existing loops with such dependent threads; the value of n clearly depends on the number of cycles. I suspect this will give you every loop in a program that has a side effect, which might not be so much.

Another poster offers to somehow update all int-type ads that are similar to specific applications (for example, * linecount_appt *), and then print those that will evaluate this work for your application. To do this, I would have thought that you would have to classify each int type declaration as a category (for example, "these declarations are all * linecount_appt *"). Doing this by manually inspecting the 10M SLOC seems rather complicated and very error prone. Finding all the ads that receive (on assignment) values ​​from the β€œsame” value sources can be a way to get hints about where these types of applications are. You want to be able to mechanically find such ad groups, and then have some tool that automatically replaces the actual declarations with the designated application type (for example, * linecount_appt *). This is probably somewhat simpler than analyzing the exact range.

+1
source

There are tools that help find such problems. I will not give any links here because the ones that I know are commercial, but they are quite easy to find.

0
source

Source: https://habr.com/ru/post/891145/


All Articles