I’m sure you have experienced the ever-green art of steganography : a bug hiding in plain sight in your code. Yet, this is an invisible bug. The code compiles, IDE reports nothing wrong, unit tests are a failing everywhere, and you keep reading the sources without finding anything. And suddenly, it strikes you : YES, there ! One hour lost due to a stupid typo.
Even with experience dating back from the last millenium, I run occasionally into those. So, here is a list of five of those horrendous errors that you may encounter in your code. I collected them as a reference : the list often sparks inspiration and help me target my diagnostic efforts to the right place in the code. May it help you like it helped me.
- The variable inside a string
- The cutting semi-colon
- The hidden iffectation
- The nested comparison
- The last line effect
The variable inside a string
<?php $x = '<a href= "$url">$title</a>' ; ?>
Note that the contrary may very well occur too : the variable is shortened in the string. Here, the variable was longer ($urls[‘site’][‘http’]), and pushing it into a double-quoted string makes it ignore the second index. PHP only access one level arrays inside a double-quoted string. The same is possible with the object notation and the properties.
<?php $x = " <a href= \"$urls[site][http] \">$urls[site][name]</a> " ; $x = " <a href= \"$urls->site->http \">$urls->site->name</a> " ; ?>
This case is related to refactor from concatenation to double-quote strings or HEREDOC strings.
Static analysis is able to spot errors like that in the code. PHP variables are easy to identify, with their leading $. But this may easily collide with real-world notation, like countries that use $CAD as a currently indicator.
The cutting semi-colon
A sneaky semi-colon after a conditional statement is always hard to find :
<?php for($i = 0 ; $i < 10 ; ++$i) ; $p += $i ; ?>
Note the hidden semi-colon, after the for : it actually void the loop, and the next instruction is only executed once. Always using curly brackets may help preventing this problem, though it is still possible :
<?php for($i = 0 ; $i < 10 ; ++$i) ; { $p += $i ; } ?>
Finally, this may affect a lot of conditional structures : for, foreach, if/then/elseif.
Static analysis is a good help here : empty blocks are rarely useful, and may be easily reported to manual inspection.
The hidden iffectation
PHP allows for iffectation : the ability to assign a value, and use it for comparison. For example :
<?php $dir = opendir($path); while ($file = readdir($dir)) { doSomething($file) ; } ?>
$file receives the value read from the directory $dir. The same value is used directly by while() to keep on running the loop, until readdir() return null or false, which reports an error or the end of the file list.
Problems arise when the same ability is used with a constant, like the example below. The comparison is now constant too, and is always true (or false, depending on the constant), leading to a useless condition, and a modified variable.
<?php if ($variable = 3) { doSomething() ; } ?>
This may be avoided by using the Yoda comparison : set the constant on the left, so that no assignation may ever occur. In fact, in case of typo, PHP will not lint the code, and respond with a fatal error.
The nested comparison
Another vicious error is the comparison that goes inside. As brought to my attention by Vladimir Reznichenko, a misplaced parenthesis may very well lead to valid PHP code :
<?php if (trim($str == '')) { doSomething() ; } ?>
Here, the comparison should have been done with the result of trim(), not as an argument of trim, which only accepts strings (actually, boolean are cast to string first). Again, this leads to an strange behavior.
One aspect that makes this error hard to find is that using a comparison as a parameter is valid in many situations :
<?php // Set the HTTPS only configuration for the cookie setcookie("TestCookie", $value, time()+3600, "/~rasmus/", "example.com", 1, $_SERVER['HTTPS'] != ''); // Set the HTTPS only configuration for the cookie // Checks that the cookie could be send setcookie("TestCookie", $value, time()+3600, "/~rasmus/", "example.com", 1, $_SERVER['HTTPS'] )!= ''; ?>
Those errors may be reported when the last argument used in the function is not supposed to be a boolean : for this, typehint may help.
The last line effect
The last line effect is a micro-cloning problem : copy-paste is not your friend. It is easy to understand on the example below :
<?php $x = $point->x; $y = $point->y; $z = $point->y; ?>
Obviously, the three lines are the same, and the two last are probably build by copy/paste/adaptation. Except that the alteration was not complete, and the last assignation is actually a bug : $z should get the z coordinate, instead of the mistaken y. Anytime there is a micro-clone, you may encounter those mistake. They are even harder to spot, as our brain is wired to skim over those similar looking blocks of code, and skip a full check.
Coding conventions help with those. Static analysis is trying to find algorithms to spot the problem, which is quite spread : Read ‘the last line effect explained’ from Moritz Beller.
Don’t let the code source bug bite !
This list of bugs may very well be committed in the repositories. They may make their way past unit tests, though quite rarely. They may linger in the code for long times before being found by code review or static analysis.
Indeed, all those errors are now treated by static analysis. The automated code review, like the exakat engine, signals suspicious code, and target developper attention right in the files, on the line, where suspicious code is. It helps having a disciplined helper that can read code without getting used to read it again and again.
Do you more than those five invisible bugs? Ping me on Twitter.