For example, the following function that uses the Windows API to load a file into memory and calculate its CRC32 checksum with a polynomial defined elsewhere as POLYNOMIAL
:
#include <Windows.h>
UINT32 WINAPI GetFileCRC32(WCHAR *wszFileName, LPDWORD lpdwError)
{
HANDLE hHeap = GetProcessHeap();
HANDLE hFile = INVALID_HANDLE_VALUE;
LARGE_INTEGER liSize;
DWORD dwError, dwRead;
SIZE_T cbAlloc, i, i2;
BYTE *bBuffer = NULL;
UINT32 uCRC = 0xFFFFFFFFU;
hFile = CreateFileW(wszFileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (INVALID_HANDLE_VALUE == hFile)
{
dwError = GetLastError();
goto cleanup;
}
GetFileSizeEx(hFile, &liSize);
do
{
cbAlloc = min(liSize.QuadPart, ALLOC_MAX);
bBuffer = (BYTE *) HeapAlloc(hHeap, HEAP_ZERO_MEMORY, cbAlloc);
if (NULL == bBuffer)
{
dwError = ERROR_OUTOFMEMORY;
goto cleanup;
}
if (!ReadFile(hFile, bBuffer, cbAlloc, &dwRead, NULL))
{
dwError = GetLastError();
goto cleanup;
}
for(i = 0; i < cbAlloc; i++)
{
uCRC ^= bBuffer[i];
for (i2 = 0; i2 < 8; i2++)
{
uCRC = (uCRC >> 1) ^ ((uCRC & 1) ? POLYNOMIAL : 0);
}
}
HeapFree(hHeap, 0, bBuffer);
bBuffer = NULL;
liSize.QuadPart -= cbAlloc;
}
while(liSize.QuadPart);
dwError = ERROR_SUCCESS;
cleanup:
if (hFile != INVALID_HANDLE_VALUE)
{
CloseHandle(hFile);
hFile = INVALID_HANDLE_VALUE;
}
if (bBuffer != NULL)
{
HeapFree(hHeap, 0, bBuffer);
bBuffer = NULL;
}
*lpdwError = dwError;
return uCRC;
}
Clang-Tidy, by default, does not detect the bBuffer = NULL
line as a dead store, I assume because it recognizes the practice of setting a pointer to NULL
as a defensive programming practice to prevent double-free bugs. However, I also set the value of hFile
back to INVALID_HANDLE_VALUE
after closing it, as part of the same pattern.
However, clang-tidy does complain about the assignment of INVALID_HANDLE_VALUE
(which is the numeric value -1
coerced into a HANDLE
) at the end of the function as a "dead store".
How can I configure clang-tidy analysis to accept values other than NULL
as exempt from the dead store analysis?
No, it is not possible to configure the clang-tidy
deadcode.DeadStores
checker to ignore assignments of values other than nullptr
.
The code that ignores nullptr
assignments is at
DeadStoresChecker.cpp:340
:
if (DeclRefExpr *DR = dyn_cast<DeclRefExpr>(B->getLHS()))
if (VarDecl *VD = dyn_cast<VarDecl>(DR->getDecl())) {
// Special case: check for assigning null to a pointer.
// This is a common form of defensive programming.
const Expr *RHS =
LookThroughTransitiveAssignmentsAndCommaOperators(B->getRHS());
QualType T = VD->getType();
if (T.isVolatileQualified())
return;
if (T->isPointerType() || T->isObjCObjectPointerType()) {
===> if (RHS->isNullPointerConstant(Ctx, Expr::NPC_ValueDependentIsNull))
return;
}
// Special case: self-assignments. These are often used to shut up
// "unused variable" compiler warnings.
if (const DeclRefExpr *RhsDR = dyn_cast<DeclRefExpr>(RHS))
if (VD == dyn_cast<VarDecl>(RhsDR->getDecl()))
return;
// Otherwise, issue a warning.
DeadStoreKind dsk = Parents.isConsumedExpr(B)
? Enclosing
: (isIncrement(VD,B) ? DeadIncrement : Standard);
CheckVarDecl(VD, DR, B->getRHS(), dsk, Live);
}
The indicated line calls
Expr::isNullPointerConstant
to check for nullptr
, but there isn't any flexibility here to allow
other values.
At the end of the quoted snippet there is a call to CheckVarDecl
,
which does a little more filtering based on the type of the variable
(ignoring references, for example) before issuing the report. It
accepts the RHS expression but only uses it to get the source location
range; there is no filtering there based on the assigned value.
The only semi-feasible workaround I see is to wrap the assignment in a
call to an identity macro (#define ID(x) x
), as the checker refuses to
report code that is inside a macro expansion.