Deal with project which may contain undefined behaviour - c++

I would like advice how to proceed in such situation.
Imagine I have large C++ project which works well.
I have suspicion there might be some UB in this code (because in different project written by same author I found UB).
Now, say I need to add new features to this project.
I am afraid because:
if I recompile with new compiler this can increase risk of UB happening if in the code is UB already. (e.g. new compiler might not be OK with UB which the old compiler was fine with).
Is it realistic to eliminate all UB in this large project by eye inspection (before I move to adding new feature)??
If not, then I should at least compile with same version of compiler right? (to decrease chance of problems if there is UB).
Project is done in Visual Studio so I don't know if there are object files, in which case, I could leave object files same and only modify parts in files where I need to add something - thus again minimizing risk of UB.
What is the course of action in such situation? I think this could be pretty common scenario.
I like suggestion that I test the project using new compiler before adding new code, but even then - we know testing might not reveal UB, isn't it?

In order, I would:
Compile with -Wall (/W4 for you Windows folk) and fix errors.
Write tests if there aren't any already.
Use tools like valgrind to detect issues and fix them.
Study synchronization primitives if in use, and use modern paradigms where possible.
Document the code and adhere to a style guide.
I would not attempt to avoid problems by keeping object files around. That's a nightmarish maintenance problem.

Undefined Behavior = Bugs
It's impossible to prove that a project is bug-free. Even the best programmers do create bugs. Even the best code-review cannot eliminate all bugs in a project. No, it's not realistic to eliminate all UB in a project of some size by code inspection or by any other means. Your best option is to review the code and eliminate as many as possible.
Change your perception of UB (bugs): If you encounter a bug during your re-engineering efforts, it's a good thing! You are in the best position to remove one UB.
Don't keep the old compiler just because you are afraid of UB. Recompile the project with the latest and best compiler available. Compilers can also have bugs. Newer compilers will produce better, more robust code. Newer compilers will produce better warnings. Use all warnings possible -Wall.
Eliminate all the warnings that the compiler produces. Every single warning is there for a reason, it highlights a problem. The likelihood of a "false positive" is quite dim nowadays. This is even true for MSVC (I'm not talking about real old compilers like before VC 2005)
Use a static code checker (Cppcheck). It can point you to common problems with the code.
Use a custom rule set for your code checker. It will help you to get the code up to some standard.
If possible, compile the project with another compiler (GCC, Clang) just for the sake of getting the warnings of these compilers.
Don't link against old object files. This will create more problems than what you think it avoids

As others said: First and foremost, try to find the errors, not hide them.
The first and simplest measure is to set the warning level to /W4 (you can try Wall, but due to the large amount of noise this will produce (e.g. from standard headerfiles), it is usually only of help if you know you have an error in a certain part of your code)
Use static analyzers - you can start with the builtin Code Analysis tool and then go for external tools (which are usually much more difficult to set up correctly for a non-trivial project).
Write lots of tests and make sure, you are exercising edge cases - thats where UB usually lurks.
If possible, try to compile the project (or parts of it) under clang and activate the different sanitizers (in particular there is UndefinedBehaviorSanitizer) which will further instrument your code to check for UB (only helpfull if you have tests to exercise that UB though)
Test your code at different optimization levels and combination of flags (in VS, especially _ITERATOR_DEBUG_LEVEL can be helpfull to find out-of-bounds errors)
I'd say any non-trivial code base potentially contains undefined behavior. What is special about that particular Programmer? If he/she is prone to a special kind of UB, then you can focus your efforts on this.

Related

Can merely using (stable) third party library render my code not working

Say I have C++ project which has been working for years well.
Say also this project might (need to verify) contain undefined behaviour.
So maybe compiler was kind to us and doesn't make program misbehave even though there is UB.
Now imagine I want to add some features to the project. e.g. add Crypto ++ library to it.
But the actual code I add to it say from Crypto++ is legitimate.
Here I read:
Your code, if part of a larger project, could conditionally call some
3rd party code (say, a shell extension that previews an image type in
a file open dialog) that changes the state of some flags (floating
point precision, locale, integer overflow flags, division by zero
behavior, etc). Your code, which worked fine before, now exhibits
completely different behavior.
But I can't gauge exactly what author means. Does he say even by adding say Crypto ++ library to my project, despite the code from Crypto++ I add is legitimate, my project can suddenly start working incorrectly?
Is this realistic?
Any links which can confirm this?
It is hard for me to explain to people involved that just adding library might increase risks. Maybe someone can help me formulate how to explain this?
When source code invokes undefined behaviour, it means that the standard gives no guarantee on what could happen. It can work perfectly in one compilation run, but simply compiling it again with a newer version of the compiler or of a library could make it break. Or changing the optimisation level on the compiler can have same effect.
A common example for that is reading one element past end of an array. Suppose you expect it to be null and by chance next memory location contains a 0 on normal conditions (say it is an error flag). It will work without problem. But suppose now that on another compilation run after changing something totally unrelated, the memory organization is slightly changed and next memory location after the array is no longer that flag (that kept a constant value) but a variable taking other values. You program will break and will be hard to debug, because if that variable is used as a pointer, you could overwrite memory on random places.
TL/DR: If one version works but you suspect UB in it, the only correct way is to consistently remove all possible UB from the code before any change. Alternatively, you can keep the working version untouched, but beware, you could have to change it later...
Over the years, C has mutated into a weird hybrid of a low-level language and a high-level language, where code provides a low-level description of a way of performing a task, and modern compilers then try to convert that into a high-level description of what the task is and then implement efficient code to perform that task (possibly in a way very different from what was specified). In order to facilitate the translation from the low-level sequence of steps into the higher-level description of the operations being performed, the compiler needs to make certain assumptions about the conditions under which those low-level steps will be performed. If those assumptions do not hold, the compiler may generate code which malfunctions in very weird and bizarre ways.
Complicating the situation is the fact that there are many common programming constructs which might be legal if certain parts of the rules were a little better thought-out, but which as the rules are written would authorize compilers to do anything they want. Identifying all the places where code does things which arguably should be legal, and which have historically worked correctly 99.999% of the time, but might break for arbitrary reasons can be very difficult.
Thus, one may wish for the addition of a new library not to break anything, and most of the time one's wish might come true, but unfortunately it's very difficult to know whether any code may have lurking time bombs within it.

Why #pragma optimize("", off)

I'm reviewing a C++ MFC project. At the beginning of some of the files there is this line:
#pragma optimize("", off)
I get that this turns optimization off for all following functions. But what would the motivation typically be for doing so?
I have used this exclusively to get better debug information in a particular set of code with the rest of the application is compiled with the optimization on. This is very useful when running with a full debug build is impossible due to the performance requirement of your application.
I've seen production code which is correct but so complicated that it confuses the optimiser into producing incorrect output. This could be the reason to turn optimisations off.
However, I'd consider it much more likely that the code is simply buggy, having Undefined Behaviour. The optimiser exposes that and leads to incorrect runtime behaviour or crashes. Without optimisations, the code happens to "work." And rather than find and remove the underlying problem, someone "fixed" it by disabling optimisations and leaving it at that.
Of course, this is about as fragile and workarounds can get. New hardware, new OS patch, new compiler patch, any of these can break such a "fix."
Even if the pragma is there for the first reason, it should be heavily documented.
Another alternative reason for these to be in a code base... Its an accident.
This is a very handy tool for turning off the optimizer on a specific file whilst debugging - as Ray mentioned above.
If changelists are not reviewed carefully before committing, it is very easy for these lines to make their way into codebases, simply because they were 'accidentally' still there when other changes were committed.
I know this is an old topic, but I would add that there is another reason to use this directive - though not relevant for most application developers.
When writing device drivers or other low-level code, the optimizer sometimes produces output that does not interact with the hardware correctly.
For example code that needs to read a memory-mapped register (but not use the value read) to clear an interrupt might be optimized out by the compiler, producing assembly code that does not work.
This might also illustrate why (as Angew notes) use of this directive should be clearly documented.
It allows you to debug in release mode.

What is the scope of optimizations in a compiler?

When compiler optimizes code, what is the scope of the optimizations? Can an optimization
a) span more than a set of nested braces?
b) span more than a function?
c) span more than a file?
We are chasing an obscure bug that seems to derive from optimization. Code crashes in release mode but not debug mode. And of course we are aware this could be heap corruption or other memory problem bur have been chasing this for a while now. One avenue we are considering is to selectively compile our files in debug mode until the problem goes away. In other words:
a) Start with all file compiled in release mode
b) Compile 1/2 the files in debug mode
if crash still seen, take half the release compiled files and compile in debug mode
if crash not seen, talk half the files compiled in debug mode and compile in release mode
repeat until we narrow in on suspect files
this is a binary search to narrow in on problem files
We are aware that if this is a memory issue, simple doing this mixed compilation may make the bug go away, but we are curous if we can narrow in on the problem files.
The outstanding question though is what is the scope of optimizations - can they span more than one file?
An optimization can do literally anything as long as it doesn't change the semantics of the behaviour defined by the language. That means the answers to your first questions (a), (b), and (c) are all yes. In practice, most compilers aren't that ambitious, but there are certainly some examples. Clang and LLVM have flags for link time optimization that allow optimizations to span pretty much the entire program. MSVC has a similar /GL flag that allows whole-program optimization.
Often the causes of these sorts of failures are uninitialized variables. Static analysis tools can be very helpful in finding problems like the one you're describing. I'm not sure your binary searching via optimizations is going to help you track down too much, though it is possible. Your bug could be the result of pretty complicated interactions between modules.
Good luck!
You can approximately identify problem files from call traces of crash in release mode. Then try to rebuild them without optimizations - this is much simpler than binary search.
To know about compiler optimization, one easy resourse could be wikipedia. It nicely explains in brief some important and wide-implemented optimizations by almost all modern compilers.
Maybe, you would like to read the wiki entry first, especially these sections:
Types of optimizations
Specific techniques
I think you are asking the wrong question.
It is unlikely (unless you are doing something special) that the optimizer is your problem.
The problem is most likely an uninitialized variable in your code.
When you compile in debug mode most compilers will initialize all memory to a specific pattern, so that during debugging you can see what has happened to the memory (was it allocated/ was it de-allcoated/ was it from the stack/ Was the stack popped back from etc).
Thus in debug mode any uninitialized memory has the a specific pattern that can be recognized by the debugger. The exact pattern will depend on the compiler. But this can make sloppy code work as the uninitialized pointers are NULL, integer counters start at 0 etc.
When you compile for release mode all the extra initialization is turned off. If you did not explicitly initialize the memory it is in a random state. This is what debug/release versions of an application behave differently.
The easy way to check for this is to check your compiler warnings.
Make sure that all variables are initialized before use (it will be in the warnings).

Good approaches to enforce building with increased warning level for new C++ code

I've inherited a large C++ codebase for several Windows applications that successfully is in use by many customers.
The codebase is large, >1mill LOC.
The codebase has a history of 15+ years.
The codebase is in some areas dominated by C programming style and/or not very modern C++ style, e.g. not using Standard C++ collections and algorithms.
The codebase has unfortunately only been compiled with warning level 2 (/W2 in Visual C++). I would like to increase to level 3 (/W3) to increase security and prepare for 64-bit.
The most problematic point in the increase to warning level 3 are the many warnings received involving signed/unsigned mismatches and I recognize that it will be a very large task to resolve all those for the existing codebase.
What would be a good approach to ensure and enforce that new code committed to the codebase are compiled with the increased warning level?
In more general terms the question could be rephrased to how you enforce increased programming quality into new committed code. If you don't do anything, new code has, in my experience, a tendency to be affected and styled similar to existing code, rather than being improved to more modern standards.
I would even go as far as going to warning level 4 (/W4).
Since you're using Visual Studio, it's quite easy to suppress bothersome warnings like signed vs unsigned comparision:
#pragma warning(disable:NNNN)
Where NNNN is the number of your warning. Now put all those disabled warnings in a header file (say, "tedious_warnings.h") and force-include that header file everywhere - Project Properties -> C/C++ -> Advanced -> Forced Include File.
Later on, or better, ASAP, remove the force include and work your way through the warnings, since most of them are quite easy to fix (size_t instead if int, etc).
Perhaps you could create new code in separate DLLs or Libraries. That way you can enforce your higher warning level (and I would say go for /W4 and be prepared to turn off a few of MS's dafter warnings rather than settle for /W3) without having to wade through 1000s of warnings from the old code.
Then you can start working on cleaning up the old code, a bit at a time, when there is time -- making sure you have suitable unit tests in place to avoid breaking it by accident of course.
you may not like the answer...
remove the warnings by correcting the issues.
i'm very picky about warning levels; even i ignore warnings which i don't need to correct, especially when the warning level is high and build times are high. meanwhile, new ones slip in (in large codebases). removing them incrementally doesn't work very well, in my experience -- they tend to get ignored if the noise is too high, or it is not enforced.
you need to reduce the warning noise so people can see the warnings they add (at the warning level you desire).
to reach the compliance level you want/need, make it a priority.
if you don't know whether the conversions/comparisons are valid, you can always use a template function with an error action (assert, throw, log) to perform the logic when in doubt.
it can be a slow/tedious process, but it's also a good way to learn the codebase.
i typically start at the libraries highest in the tree, or those which are reused most often. once a library meets a standard, maintain that standard.
If you are going to make code modifications as a result of the new stricter warning level, write adequate tests that protect against introducing new problems/bugs. Write the tests using the new warning level. Do this before you start to change the codebase and verify correct functionality. Then you can rerun the updated code against the same test case.
I would use an incremental approach.
The first step would be to modify the old files and add the required pragma action to deactivate the warning in the code.
The second step is to build a commit-hook that will refuse any committed file that contain the specific pragma pattern that discards all those "old" warnings.
This means that any modified file should be warning free.
However, let us be frank, developers always find ways to game the system.
My approach has been to go with the highest warning level you can and fix all the warnings that come up - you may even find some bugs in the process.
You should set this up using vsprops files so that all projects are compiled with the same warning level and any changes you make to these settings change in all projects.
A more incremental approach is to go with the highest warning level you can and then disable almost all warnings, leaving you with only a small number of warnings to consider at once - fix those and then switch on another warning, and so on until you are free of warnings.

How to spot undefined behavior

Is there any way to know if you program has undefined behavior in C++ (or even C), short of memorizing the entire spec?
The reason I ask is that I've noticed a lot of cases of programs working in debug but not release being due to undefined behavior. It would be nice if there were a tool to at least help spot UB, so we know there's the potential for problems.
Good coding standards. Protect you from yourself. Here are some ideas:
The code must compile at the highest warning level... without warnings. (In other words, your code must not set off any warnings at all when set to the highest level.) Turn on the error on warning flag for all projects.
This does mean some extra work when you use other peoples' libraries since they may not have done this. You will also find there are some warnings which are pointless... turn those off individually as your team decides.
Always use RAII.
Never use C style casts! Never! - I think there's like a couple rare cases when you have to break this but you will probably never find them.
If you must reinterpret_cast or cast to void then use a wrapper to make sure you're always casting to/from the same type. In other words, wrap your pointer/object in a boost::any and cast a pointer to it into whatever you need and on the other side do the same. Why? Because you will always know what type to reinterpret_cast from and the boost::any will enforce that you've cast to the correct type after that. It's the safest you can get.
Always initialize your variables at the point of declaration (or in constructor initializers when in a class).
There are more but those are some very important ones to start with.
Nobody can memorize the standard. What we intermediate to advanced C++ programmers do is use constructs we know are safe and protect ourselves from our human nature... and we don't use constructs that are not safe unless we have to and then we take extra care to make sure the danger is all wrapped up in a nice safe interface that is tested to hell and back.
One important thing to remember which is universal across all languages is to:
make your constructs easy to use correctly and difficult to use incorrectly
It's not possible to detect undefined behavior in all cases. For example, consider x = x++ + 1;. If you're familiar with the language, you know it's UB. Now, *p = (*p)++ + 1; is obviously also UB, but what about *q = (*p)++ + 1;? That's UB if q == p, but other than that it's defined (if awkward-looking). In a given program, it might well be possible to prove that p and q will never be equal when reaching that line, but that can't be done in general.
To help spot UB, use all of the tools you've got. Good compilers will warn for at least the more obvious cases, although you may have to use some compiler options for best coverage. If you have further static analysis tools, use them.
Code reviews are also very good for spotting such problems. Use them, if you've got more than one developer available.
Static code analysis tools such as PC-Lint can help a lot here
Well, this article covers most aspects..
I think you can use one tool from coverity to spot bugs which are going to lead to undefined behavior.
I guess you could use theorem provers (i only know Coq) to be sure your program does what you want.
clang tries hard to produce warnings when undefined behavior is encountered.
I'm not aware of any software tool to detect all forms of UB. Obviously using your compiler's warnings and possibly lint or another static code checker can help a lot.
The other thing that helps a lot is simply experience: The more you program the language, the more you'll see constructs that appear suspect and be able to catch them earlier in the process.
Unfortunately, there is no way way to detect all UB. You'd have to solve the Halting Problem to do that.
The best you can do is to know as many of the rules as possible, look it up when you're in doubt, and check with other programmers (through pair programming, code reviews or just SO questions)
Compiling with as many warnings as possible, and under multiple compilers can help. And running the code through static analysis tools such as Valgrind can detect many issues.
But ultimately, no tool can detect it all.
An additional problem is that many programs actually have to rely on UB. Some API's require it, and just assume that "it works on all sane compilers". OpenGL does that in one or two cases. The Win32 API won't even compile under a standards compliant compiler.
So even if you had a magic UB-detecting tool, it would still be tripped up by the cases that aren't under your control.
Simple: Don't do things that you don't know that you can do.
When you are unsure or have a fishy feeling, check the reference
A good compiler, such as the Intel C++ compiler, should be able to spot 99% of cases of undefined behaviour. You'll need to investigate the flags and switches to use. As ever, read the manual.