Background: because reasons, my code likes to return success/error-code values from its functions in the form of a (very lightweight) class-object. This works fine, however I'm having some difficulty getting Clang Static Analyzer to understand what's going on; its confusion causes it to emit spurious "uninitialized-value" warnings when it analyzes my program.
Here's some minimal example code to illustrate/reproduce the (mis)behavior:
// my_status_t_class.h
#ifndef MY_STATUS_T_CLASS_H
#define MY_STATUS_T_CLASS_H
class status_t
{
public:
status_t(int errorNumber) : _errorNumber(errorNumber) {/* empty */}
bool IsError() const {return (_errorNumber != 0);}
private:
int _errorNumber;
};
// A list of some common error codes
const status_t STATUS_NO_ERROR(0);
const status_t STATUS_DATA_NOT_FOUND(-1);
// [...]
#endif
// my_test_program.cpp
#include <stdio.h>
#include <stdlib.h>
#include "my_status_t_class.h"
status_t GetValue(int & ret)
{
if (rand()%2)
{
ret = 5;
return STATUS_NO_ERROR;
}
else return STATUS_DATA_NOT_FOUND;
}
int main(int argc, char ** argv)
{
int ret;
if (GetValue(ret).IsError()) ret = 5;
printf("ret=%i\n", ret);
return 0;
}
When I run scan-build on the above code, it gives this output:
$ scan-build g++ -std=c++17 my_test_program.cpp
scan-build: Using '/Users/jaf/llvm-project/build/bin/clang-16' for static analysis
testhashtable.cpp:20:8: warning: 2nd function call argument is an uninitialized value [core.CallAndMessage]
printf("ret=%i\n", ret);
^~~~~~~~~~~~~~~~~~~~~~~
1 warning generated.
.... and it generates a report about the error which can be viewed at this link.
As you can see from the report, ClangSA seems to think that it is possble for my GetValue() function to return STATUS_DATA_NOT_FOUND, but then have the IsError() method return false inside main(), leaving the ret local-variable undefined when it is used.
In actuality, that isn't possible -- IsError() will return true, so then main() will execute ret = 5; and so ret will always have a well-defined value when I pass it in to printf().
My question is, is this a bug in ClangSA? Or if not, is there a recommended way for me to get ClangSA to understand/follow the semantics of my status_t class here? (It seems like it should be able to, since the code is all defined in the my_status_t_class.h header file which ClangSA has direct access to).
I did notice that if I replace the constant-declarations in my_status_t_class.h with #define declarations instead, I get the behavior I want, e.g. if I do this:
// my_status_t_class.h
[...]
// A list of some common error codes
#define STATUS_NO_ERROR status_t(0)
#define STATUS_DATA_NOT_FOUND status_t(-1)
// [...]
... then ClangSA doesn't produce the spurious warning... but of course that is quite an ugly solution, and I'd like to avoid it if there is a better alternative.
I think the problem is that the analyzer is not able to see that the
STATUS_DATA_NOT_FOUNDobject is always an error, so it can't be sure thatretwill always be initialized.The reason it can't see that is because the
STATUS_DATA_NOT_FOUNDobject is not a constant expression, so the analyzer can't know its value at compile-time.One way to fix this would be to make the
STATUS_DATA_NOT_FOUNDobject a constant expression, by using aconstexprconstructor:With that change, the analyzer should be able to see that
STATUS_DATA_NOT_FOUNDis always an error, and so it will know thatretwill always be initialized.Alternatively, you could make the
STATUS_DATA_NOT_FOUNDobject a global variable instead of a constant object, and initialize it with a constant expression: