Webinar: C++ semantics - 06.11
Operating systems are among the largest and most complicated software projects, and that means they perfectly suit the purpose of demonstrating the capabilities of static code analysis. After the successful analysis of the Linux Kernel, I felt inspired to try analyzing other open-source operating systems as well.
Haiku is a free and open-source operating system for PC, designed to be binary compatible with the BeOS operating system, and embodying the basic ideas of BeOS. It's a modular system with the hybrid-kernel architecture - microkernel architecture capable of dynamical module linking.
The idea to check this project was suggested by one PVS-Studio user familiar with our work on open-source software analysis. With the experience of the relatively recent Linux Kernel analysis, I could predict what troubles I would face checking the Haiku project, and told that guy about them in a reply email. Unexpectedly, I was offered assistance in building the operating system and analyzer integration. Also, there was highly extensive documentation available at the official site, so I decided to give it a try.
It was some time before I managed to get the long-awaited analysis log, and after studying it, I decided to write one large article in two parts, describing code fragments I found to be the most suspicious. This article is the first part.
In the first article, I'm discussing the analyzer's warnings on conditional operators, for errors in conditions can be treated as execution logic errors, can't they?
V501 There are identical sub-expressions to the left and to the right of the '<' operator: lJack->m_jackType < lJack->m_jackType MediaJack.cpp 783
int __CORTEX_NAMESPACE__ compareTypeAndID(....)
{
int retValue = 0;
....
if (lJack && rJack)
{
if (lJack->m_jackType < lJack->m_jackType) // <=
{
return -1;
}
if (lJack->m_jackType == lJack->m_jackType) // <=
{
if (lJack->m_index < rJack->m_index)
{
return -1;
}
else
{
return 1;
}
}
else if (lJack->m_jackType > rJack->m_jackType)
{
retValue = 1;
}
}
return retValue;
}
This function triggered two warnings at once. In both cases, there is a typo you can clearly see (of course, not before the analyzer has "pointed a finger" at it) in the names lJack and rjack.
V575 The 'strchr' function processes value '2112800'. Inspect the second argument. CommandActuators.cpp 1517
extern char *strchr(const char *string, int character);
SendMessageCommandActuator::
SendMessageCommandActuator(int32 argc, char** argv)
:
CommandActuator(argc, argv),
fSignature((argc > 1) ? argv[1] : "")
{
....
const char* arg = argv[i];
BString argString(arg);
const char* equals = strchr(arg, ' = '); // <=
....
}
The return result of the strchr() function is the pointer to the first occurrence of the specified character in the specified string. The character is cast to int, and in this case ' = ' will be presented as the number 2112800. The programmer most likely intended to search for a single '=' character, and its code is 61.
If the programmer wanted to find the " = " substring, the function used in the code is obviously the wrong choice, and should be replaced with the strstr() call.
V502 Perhaps the '?:' operator works in a different way than it was expected. The '?:' operator has a lower priority than the '-' operator. AbstractLayout.cpp 244
bool IsVisible(bool ancestorsVisible) const
{
int16 showLevel = BView::Private(view).ShowLevel();
return (showLevel - (ancestorsVisible) ? 0 : 1) <= 0;
}
Unfortunately, enclosing the 'ancestorsVisible' variable in parentheses won't affect the expression evaluation order in this case. Therefore, according to the operation precedence hierarchy, the first operation to be executed is subtraction (bool is subtracted from int16), and only then the ternary operator '?:' will be executed.
This is what the correct version of this code should look like:
bool IsVisible(bool ancestorsVisible) const
{
int16 showLevel = BView::Private(view).ShowLevel();
return (showLevel - (ancestorsVisible ? 0 : 1) ) <= 0;
}
V502 Perhaps the '?:' operator works in a different way than it was expected. The '?:' operator has a lower priority than the '&&' operator. fnmatch.c 58
#define FOLD(c) \
((flags & FNM_CASEFOLD) && ISUPPER ((unsigned char) (c)) \
? tolower ((unsigned char) (c)) \
: (c))
I also recommend that the authors check the operation execution order in this macro, and add parentheses wherever necessary to make it more transparent.
V562 It's odd to compare 0 or 1 with a value of 0. cmp.c 300
#ifndef same_file
# define same_file(s, t) \
((((s)->st_ino == (t)->st_ino) \
&& ((s)->st_dev == (t)->st_dev)) \
|| same_special_file (s, t))
#endif
int
main (int argc, char **argv)
{
....
if (0 < same_file (&stat_buf[0], &stat_buf[1]) // <=
&& same_file_attributes (&stat_buf[0], &stat_buf[1])
&& file_position (0) == file_position (1))
return EXIT_SUCCESS;
....
}
This is an ordinary condition at first sight, but actually "same_file" is a macro converted into a logical expression, just like 'same_file_attributes', so what we get is a strange comparison "0 < value_of_boolean_type". There is no 'bool' type in the C language. The expression to the right will have the 'int' type, but in fact it is always either "true" or "false", that's why it is called 'boolean'. So the "0 < boolean_expr" comparison does look pretty suspicious.
A similar issue with a macro:
V562 It's odd to compare a bool type value with a value of 18: 0x12 == IsProfessionalSpdif(). CEchoGals_mixer.cpp 533
#define ECHOSTATUS_DSP_DEAD 0x12 // <=
virtual BOOL IsProfessionalSpdif() // <=
{
....
return( (....) ? TRUE : FALSE );
}
ECHOSTATUS CEchoGals::ProcessMixerFunction
(
PMIXER_FUNCTION pMixerFunction,
INT32 & iRtnDataSz
)
{
....
case MXF_GET_PROF_SPDIF :
if ( ECHOSTATUS_DSP_DEAD == IsProfessionalSpdif() ) // <=
{
Status = ECHOSTATUS_DSP_DEAD;
}
else
{
pMixerFunction->Data.bProfSpdif = IsProfessionalSpdif();
}
....
}
Another incorrect comparison of macros. The IsProfessionalSpdif() function returns TRUE or FALSE, while the returned result is compared to the number 0x12.
V583 The '?:' operator, regardless of its conditional expression, always returns one and the same value. impactv.c 520
void Radeon_CalcImpacTVRegisters(....)
{
....
values->tv_hstart =
internal_encoder ?
values->tv_hdisp + 1 - params->mode888 + 12 :
values->tv_hdisp + 1 - params->mode888 + 12;
....
}
Regardless of the value of the 'internal_encoder' variable, the ternary operator returns identical values. This code needs to be examined for typos.
V523 The 'then' statement is equivalent to the 'else' statement. mkntfs.c 1132
static int insert_positioned_attr_in_mft_record(....)
{
....
if (flags & ATTR_COMPRESSION_MASK) {
hdr_size = 72;
/* FIXME: This compression stuff is all wrong. .... */
/* now. (AIA) */
if (val_len)
mpa_size = 0; /* get_size_for_compressed_....; */
else
mpa_size = 0;
} else {
....
}
....
}
The analyzer reminds us that suspended code fragments should be fixed.
Another issue of this kind:
V503 This is a nonsensical comparison: pointer <= 0. Header.cpp 900
extern
char *strstr(const char *string, const char *searchString);
void
TTextControl::MessageReceived(BMessage *msg)
{
....
while (node.GetNextAttrName(buffer) == B_OK) {
if (strstr(buffer, "email") <= 0)
continue;
....
}
The strstr() function returns the pointer to the first occurrence of the "email" string in the 'buffer' string. If no such correspondence is found, NULL is returned. Therefore, it is NULL that it should be compared to.
A possible solution:
void
TTextControl::MessageReceived(BMessage *msg)
{
....
while (node.GetNextAttrName(buffer) == B_OK) {
if (strstr(buffer, "email") == NULL)
continue;
....
}
V512 A call of the 'memcmp' function will lead to underflow of the buffer '"Private-key-format: v"'. dst_api.c 858
dst_s_read_private_key_file(....)
{
....
if (memcmp(in_buff, "Private-key-format: v", 20) != 0)
goto fail;
....
}
The length of the string being compared doesn't coincide with the specified number of characters to be compared. The "Private-key-format: v" string contains 21 characters.
V547 Expression '* r && * r == ' ' && * r == '\t'' is always false. Probably the '||' operator should be used here. selection.c 546
static int
selection_rel(....)
{
char *r, *rname;
....
while (*r && *r == ' ' && *r == '\t')
r++;
....
}
I'm almost sure there is an error here. The programmer intended to skip all the space characters and tabs in the loop, but one and the same character can't be both at a time.
The possible correct version of this code is as follows:
static int
selection_rel(....)
{
char *r, *rname;
....
while (*r == ' ' || *r == '\t')
r++;
....
}
V590 Consider inspecting the 'path[i] == '/' && path[i] != '\0'' expression. The expression is excessive or contains a misprint. storage_support.cpp 309
status_t
parse_first_path_component(const char *path, int32& length,
int32& nextComponent)
{
....
for (; path[i] == '/' && path[i] != '\0'; i++); // <=
if (path[i] == '\0') // this covers "" as well
nextComponent = 0;
else
nextComponent = i;
....
}
Everything is OK in this code, but one check is excessive and should be removed. Without affecting the code logic, we can simply leave the following: "for (; path[i] == '/'; i++);".
Other similar fragments:
V547 Expression is always true. Probably the '&&' operator should be used here. StatusView.cpp 1397
void
TDragRegion::Draw(BRect)
{
....
if (fDragLocation != kDontDrawDragRegion ||
fDragLocation != kNoDragRegion)
DrawDragRegion();
}
In this function, something is constantly getting drawn. If we build the truth table for the logical expression in the condition, we will find that it is always true. Perhaps the '&&' operator is missing here.
V547 Expression 'reservedBase < 0' is always false. Unsigned type value is never < 0. agp_gart.cpp 1172
/* address types */
typedef unsigned long int __haiku_addr_t; // <=
typedef __haiku_addr_t addr_t; // <=
static status_t
bind_aperture(...., addr_t reservedBase, ....)
{
....
if (status < B_OK) {
if (reservedBase < 0) // <=
aperture->DeleteMemory(memory);
return status;
}
....
}
In a comparison against an unsigned type like this, the condition is always false, and somewhere memory fails to be cleared. It's sad to say, but there are about two hundred similar checks involving unsigned types in the Haiku project. It wouldn't make sense to talk about all these instances, or even part of them in this article, for they all are very alike and not very interesting. However, we will send a complete log to the developers, so that they can examine all those suspicious fragments.
V713 The pointer lp was utilized in the logical expression before it was verified against nullptr in the same logical expression. util.c 311
char *
bittok2str(register const struct tok *lp, ....)
{
....
while (lp->s != NULL && lp != NULL) {
....
}
....
}
The pointer check order in the loop condition is incorrect. The pointer is first dereferenced and only then checked for being null. The correct code:
while (lp != NULL && lp->s != NULL) {
V593 Consider reviewing the expression of the 'A = B != C' kind. The expression is calculated as following: 'A = (B != C)'. VideoProducer.cpp 766
int32
VideoProducer::_FrameGeneratorThread()
{
....
err = B_OK;
// Send the buffer on down to the consumer
if (wasCached || (err = SendBuffer(buffer, fOutput.source,
fOutput.destination) != B_OK)) {
....
}
....
}
The analyzer has detected a potential error in an expression that is very likely to work differently to how the programmer wanted. What was intended is to execute the "err = SendBuffer()" assignment, and compare the result to the 'B_OK' constant, but the '!=' operator's precedence is higher than that of '=', so the 'err' variable will be used to store the result of the logical operation.
Other similar fragments:
V547 Expression 'nogscale >= 0' is always true. Unsigned type value is always >= 0. tvp3026.c 212
status_t mil2_dac_init (void)
{
uint32 rfhcnt, nogscale, memconfig;
....
for (nogscale = 1; nogscale >= 0; nogscale--) { // <=
int max = -1 + 33.2 * mclk / (nogscale? 1: 4);
for (rfhcnt = 15; rfhcnt > 0; rfhcnt--) {
int value = (rfhcnt & 0x0e) * 256 + (rfhcnt & 0x01) * 64;
LOG(2,("mil2_dac_init factor %d, rfhcnt %2d: %d ?<= %d\n",
nogscale, rfhcnt, value, max));
if (value <= max) goto rfhcnt_found;
}
}
....
}
The 'goto' operator was probably the reason the programmer never noticed that one of the loops was infinite, since an unsigned variable can be endlessly decremented in a check like "nogscale >= 0".
V621 Consider inspecting the 'for' operator. It's possible that the loop will be executed incorrectly or won't be executed at all. if_ae.c 1670
#define AE_IDLE_TIMEOUT 100
static void
ae_stop_rxmac(ae_softc_t *sc)
{
....
/*
* Wait for IDLE state.
*/
for (i = 0; i < AE_IDLE_TIMEOUT; i--) {
val = AE_READ_4(sc, AE_IDLE_REG);
if ((val & (AE_IDLE_RXMAC | AE_IDLE_DMAWRITE)) == 0)
break;
DELAY(100);
}
....
}
For some reason, the counter in this loop runs in the opposite direction: it would make more sense to increment the 'i' variable so that the program would have to wait for 100 iterations at most, instead of millions of times more.
Another similar issue:
V646 Consider inspecting the application's logic. It's possible that 'else' keyword is missing. Filter.cpp 760
uchar
Scaler::Limit(intType value)
{
if (value < 0) {
value = 0;
} if (value > 255) {
value = 255;
}
return value;
}
There is no serious error in this function, but the code is badly formatted. The key word 'else' should be added, or the conditions should be aligned at one level.
V640 The code's operational logic does not correspond with its formatting. The second statement will always be executed. It is possible that curly brackets are missing. strftime.c 1263
#define DO_NUMBER(d, v) \
digits = width == -1 ? d : width; \
number_value = v; goto do_number
size_t
my_strftime (s, maxsize, format, tp extra_args)
{
....
if (modifier == L_('O'))
goto bad_format;
else
DO_NUMBER (1, tp->tm_year + TM_YEAR_BASE);
....
}
Macros are always a headache in debugging, but moreover, they are often sources of the following bugs: the 'DO_NUMBER' macro is expanded into several lines, but only the first of them will be part of the conditional operator, while all the next operators will be executed independently of the condition.
Here's another code fragment where a macro is used in a similarly incorrect way:
Thanks to the help of a few guys interested in setting up the build of the Haiku operating system and analyzer integration, we managed to get everything necessary for analysis ready quickly. This is in fact an ideal scenario of open-source software analysis, because we often have to deal with completely unfamiliar projects which often have their own build systems.
In the next article, we discussed the remaining warnings I have picked for you. They are grouped into several categories according to their types.
0