Miranda NG Project to Get the "Wild 
Pointers" Award (Part 1) 
Author: Andrey Karpov 
Date: 25.11.2014 
I have recently got to the Miranda NG project and checked it with the PVS-Studio code analyzer. And I'm 
afraid this is the worst project in regard to memory and pointers handling issues I've ever seen. 
Although I didn't study the analysis results too thoroughly, there still were so many errors that I had to 
split the material into 2 articles. The first of them is devoted to pointers and the second to all the rest 
stuff. Enjoy reading and don't forget your popcorn. 
Checking Miranda NG 
The Miranda NG project is a successor of the multi-protocol IM-client for Windows, Miranda IM. 
Well, I didn't actually plan to check Miranda NG at first. It's just that we need a few actively developing 
projects to test one PVS-Studio's new feature on. It is about using a special database storing all the 
information about messages that shouldn't be displayed. To learn more about it, see this article. In brief, 
the idea behind it is the following. It is sometimes difficult to integrate static analysis into a large project 
because the analyzer generates too many warnings and one has a hard time trying to sort it all out while 
still wishing to start seeing the benefit right away. That's why you can hide all the warnings and check 
only fresh ones generated while writing new code or doing refactoring. And then, if you really feel like 
that, you can start gradually fixing errors in the old code. 
Miranda NG appeared to be one of the actively developing projects. But when I saw the analysis results 
generated by PVS-Studio after the first launch, I knew for sure I had got enough material for a new 
article. 
So, let's see what the PVS-Studio static code analyzer has found in Miranda NG's source codes. 
To do this check, we took the Trunk from the repository. Please keep in mind that I was just scanning 
through the analysis report and may have well missed much. I only checked the general diagnostics of 
the 1-st and 2-nd severity levels and didn't even bother to take a look at the 3-rd level. You see, the first 
two were just more than enough. 
Part 1. Pointers and memory handling 
Null pointer dereferencing 
void CMLan::OnRecvPacket(u_char* mes, int len, in_addr from) 
{ 
.... 
TContact* cont = m_pRootContact;
.... 
if (!cont) 
RequestStatus(true, cont->m_addr.S_un.S_addr); 
.... 
} 
PVS-Studio's diagnostic message: V522 Dereferencing of the null pointer 'cont' might take place. 
EmLanProto mlan.cpp 342 
It's all simple here. Since the pointer equals NULL, then let's dereference it and see if anything funny 
comes out of it. 
First using the pointer, then checking it 
There are numbers and numbers of errors of this kind in Miranda NG, just like in any other application. 
Such code usually works well because the function receives a non-null pointer. But if it is null, functions 
aren't ready to handle it. 
Here's a typical example: 
void TSAPI BB_InitDlgButtons(TWindowData *dat) 
{ 
.... 
HWND hdlg = dat->hwnd; 
.... 
if (dat == 0 || hdlg == 0) { return; } 
.... 
} 
PVS-Studio's diagnostic message: V595 The 'dat' pointer was utilized before it was verified against 
nullptr. Check lines: 428, 430. TabSRMM buttonsbar.cpp 428 
If you pass NULL into the BB_InitDlgButtons() function, the check will be done too late. The analyzer 
generated 164 more messages like this on Miranda NG's code. Citing them all in this article won't make 
any sense, so here they are all in a file: MirandaNG-595.txt. 
Potentially uninitialized pointer 
BSTR IEView::getHrefFromAnchor(IHTMLElement *element) 
{ 
.... 
if (SUCCEEDED(....) { 
VARIANT variant; 
BSTR url; 
if (SUCCEEDED(element->getAttribute(L"href", 2, &variant) && 
variant.vt == VT_BSTR))
{ 
url = mir_tstrdup(variant.bstrVal); 
SysFreeString(variant.bstrVal); 
} 
pAnchor->Release(); 
return url; 
} 
.... 
} 
PVS-Studio's diagnostic message: V614 Potentially uninitialized pointer 'url' used. IEView ieview.cpp 
1117 
If the if (SUCCEEDED(....)) condition is wrong, the 'url' variable will remain uninitialized and the function 
will have to return god knows what. The situation is much trickier though. The code contains another 
error: a closing parenthesis is put in a wrong place. It will result in the SUCCEEDED macro being applied 
only to the expression of the 'bool' type, which doesn't make any sense. 
The second bug makes up for the first. Let's see what the SUCCEEDED macro really is in itself: 
#define SUCCEEDED(hr) (((HRESULT)(hr)) >= 0) 
An expression of the 'bool' type evaluates to 0 or 1. In its turn, 0 or 1 are always >= 0. So it turns out that 
the SUCCEEDED macro will always return the truth value thus enabling the 'url' variable to be initialized 
all the time. 
So now we've just seen a very nice example of how one bug makes up for another. If we fix the 
condition, the bug with the uninitialized variable will show up. 
If we fix both, the code will look like this: 
BSTR url = NULL; 
if (SUCCEEDED(element->getAttribute(L"href", 2, &variant)) && 
variant.vt == VT_BSTR) 
The analyzer suspects something to be wrong in 20 more fragments. Here they are: MirandaNG-614.txt. 
Array size and item number mixed up 
The number of items in an array and the array size in bytes are two different entities. However, if you 
are not careful enough, you may easily mix them up. The Miranda NG project offers a handful of various 
ways to do that. 
Most harmful of all was the SIZEOF macro: 
#define SIZEOF(X) (sizeof(X)/sizeof(X[0])) 
This macro calculates the number of items in an array. But the programmer seems to treat it as a fellow 
of the sizeof() operator. I don't know, though, why use a macro instead of the standard sizeof() then, so I 
have another version - the programmer doesn't know how to use the memcpy() function. 
Here is a typical example:
int CheckForDuplicate(MCONTACT contact_list[], MCONTACT lparam) 
{ 
MCONTACT s_list[255] = { 0 }; 
memcpy(s_list, contact_list, SIZEOF(s_list)); 
for (int i = 0;; i++) { 
if (s_list[i] == lparam) 
return i; 
if (s_list[i] == 0) 
return -1; 
} 
return 0; 
} 
PVS-Studio's diagnostic message: V512 A call of the 'memcpy' function will lead to underflow of the 
buffer 's_list'. Sessions utils.cpp 288 
The memcpy() function will copy only part of the array as the third argument specifies the array size in 
bytes. 
In the same incorrect way, the SIZEOF() macro is used in 8 more places: MirandaNG-512-1.txt. 
The next trouble. Programmers often forget to fix memset()/memcpy() calls when using Unicode in their 
code: 
void checkthread(void*) 
{ 
.... 
WCHAR msgFrom[512]; 
WCHAR msgSubject[512]; 
ZeroMemory(msgFrom,512); 
ZeroMemory(msgSubject,512); 
.... 
} 
PVS-Studio's diagnostic messages: 
• V512 A call of the 'memset' function will lead to underflow of the buffer 'msgFrom'. LotusNotify 
lotusnotify.cpp 760 
• V512 A call of the 'memset' function will lead to underflow of the buffer 'msgSubject'. 
LotusNotify lotusnotify.cpp 761 
The ZeroMemoty() function will clear only half of the buffer as characters of the WCHAR type occupy 2 
bytes. 
And here is an example of partial string copying:
INT_PTR CALLBACK DlgProcMessage(....) 
{ 
.... 
CopyMemory(tr.lpstrText, _T("mailto:"), 7); 
.... 
} 
PVS-Studio's diagnostic message: V512 A call of the 'memcpy' function will lead to underflow of the 
buffer 'L"mailto:"'. TabSRMM msgdialog.cpp 2085 
Only part of the string will be copied. Each string character occupies 2 bytes, so 14 bytes instead of 7 
should have been copied. 
Other similar issues: 
• userdetails.cpp 206 
• weather_conv.cpp 476 
• dirent.c 138 
The next mistake was made due to mere inattentiveness: 
#define MSGDLGFONTCOUNT 22 
LOGFONTA logfonts[MSGDLGFONTCOUNT + 2]; 
void TSAPI CacheLogFonts() 
{ 
int i; 
HDC hdc = GetDC(NULL); 
logPixelSY = GetDeviceCaps(hdc, LOGPIXELSY); 
ReleaseDC(NULL, hdc); 
ZeroMemory(logfonts, sizeof(LOGFONTA) * MSGDLGFONTCOUNT + 2); 
.... 
} 
PVS-Studio's diagnostic message: V512 A call of the 'memset' function will lead to underflow of the 
buffer 'logfonts'. TabSRMM msglog.cpp 134 
The programmer must have been in a hurry, for he mixed up the object size and number of objects. 2 
should be added before the multiplication. Here's the fixed code: 
ZeroMemory(logfonts, sizeof(LOGFONTA) * (MSGDLGFONTCOUNT + 2)); 
In the next sample, the programmer tried his best to make it all work right using sizeof() but eventually 
ended up mixing sizes up again. The resulting value is larger than needed.
BOOL HandleLinkClick(....) 
{ 
.... 
MoveMemory(tr.lpstrText + sizeof(TCHAR)* 7, 
tr.lpstrText, 
sizeof(TCHAR)*(tr.chrg.cpMax - tr.chrg.cpMin + 1)); 
.... 
} 
PVS-Studio's diagnostic message: V620 It's unusual that the expression of sizeof(T)*N kind is being 
summed with the pointer to T type. Scriver input.cpp 387 
The 'tr.lpstrText' variable points to a string consisting of characters of the wchat_t type. If you want to 
skip 7 characters, you just need to add 7; no need to multiply it by sizeof(wchar_t). 
Another similar error: ctrl_edit.cpp 351 
It's not over, I'm afraid. What about one more way of making a mistake: 
INT_PTR CALLBACK DlgProcThemeOptions(....) 
{ 
.... 
str = (TCHAR *)malloc(MAX_PATH+1); 
.... 
} 
PVS-Studio's diagnostic message: V641 The size of the allocated memory buffer is not a multiple of the 
element size. KeyboardNotify options.cpp 718 
Multiplication by sizeof(TCHAR) is missing. There are 2 more errors in the same file, lines 819 and 1076. 
And finally the last code fragment with an error related to the number of items: 
void createProcessList(void) 
{ 
.... 
ProcessList.szFileName[i] = 
(TCHAR *)malloc(wcslen(dbv.ptszVal) + 1); 
if (ProcessList.szFileName[i]) 
wcscpy(ProcessList.szFileName[i], dbv.ptszVal); 
.... 
}
PVS-Studio's diagnostic messages: V635 Consider inspecting the expression. The length should probably 
be multiplied by the sizeof(wchar_t). KeyboardNotify main.cpp 543 
Missing multiplication by sizeof(TCHAR) can also be found in the following fragments: options.cpp 1177, 
options.cpp 1204. 
Now we're done with sizes, let's pass on to other methods of shooting yourself in the foot with a 
pointer. 
Array index out of bounds 
INT_PTR CALLBACK DlgProcFiles(....) 
{ 
.... 
char fn[6], tmp[MAX_PATH]; 
.... 
SetDlgItemTextA(hwnd, IDC_WWW_TIMER, 
_itoa(db_get_w(NULL, MODNAME, strcat(fn, "_timer"), 60), 
tmp, 10)); 
.... 
} 
V512 A call of the 'strcat' function will lead to overflow of the buffer 'fn'. NimContact files.cpp 290 
The "_timer" string doesn't fit into the 'fn' array. Although it consists of 6 characters only, mind the 
terminal null character (NUL). Theoretically, we've got undefined behavior here. In practice, it appears 
that the 'tmp' array will be affected: '0' will be written into the null element of the 'tmp' array. 
The next example is even worse. In the code below, HANDLE of some icon will be spoiled: 
typedef struct 
{ 
int cbSize; 
char caps[0x10]; 
HANDLE hIcon; 
char name[MAX_CAPNAME]; 
} ICQ_CUSTOMCAP; 
void InitCheck() 
{ 
.... 
strcpy(cap.caps, "GPG AutoExchange"); 
....
} 
PVS-Studio's diagnostic message: V512 A call of the 'strcpy' function will lead to overflow of the buffer 
'cap.caps'. New_GPG main.cpp 2246 
The end-of-string character is again not taken into account. I guess it would be better to use the 
memcpy() function here. 
Other similar issues: 
• main.cpp 2261 
• messages.cpp 541 
• messages.cpp 849 
• utilities.cpp 547 
The Great and Powerful strncat() function 
Many heard about the danger of using the strcat() function and therefore prefer to use a seemingly 
safer strncat() function instead. But few can really handle it right. This function is much more dangerous 
than you might think. You see, the third argument specifies the amount of free space left in the buffer, 
not the buffer's maximum length. 
The following code is totally incorrect: 
BOOL ExportSettings(....) 
{ 
.... 
char header[512], buff[1024], abuff[1024]; 
.... 
strncat(buff, abuff, SIZEOF(buff)); 
.... 
} 
PVS-Studio's diagnostic message: V645 The 'strncat' function call could lead to the 'buff' buffer overflow. 
The bounds should not contain the size of the buffer, but a number of characters it can hold. Miranda 
fontoptions.cpp 162 
If only half of 'buff' is occupied, the code will show no care about it and allow adding 1000 more 
characters thus causing an array overrun - and quite a large one indeed. After all, the programmer could 
simply use strcat() to get the same result. 
Well, to be exact, the statement strncat(...., ...., SIZEOF(X)) is fundamentally incorrect. It implies that the 
array ALWAYS has some free space left. 
There are 48 more fragments in Miranda NG where the strncat() function is misused. Here they are: 
MirandaNG-645-1.txt. 
By the way, such issues in the code can well be treated as potential vulnerabilities. 
In defense of Miranda NG programmers, I should note that some of them did read the description of the 
strncat() function. These guys write their code in the following way: 
void __cdecl GGPROTO::dccmainthread(void*)
{ 
.... 
strncat(filename, (char*)local_dcc->file_info.filename, 
sizeof(filename) - strlen(filename)); 
.... 
} 
PVS-Studio's diagnostic message: V645 The 'strncat' function call could lead to the 'filename' buffer 
overflow. The bounds should not contain the size of the buffer, but a number of characters it can hold. 
GG filetransfer.cpp 273 
Unfortunately, it's wrong again. At least, there is a risk of spoiling 1 byte outside the array. And I think 
you have already guessed that the reason is that very ill-fated end-of-string character that wasn't taken 
into account. 
Let me explain this error by a simple example: 
char buf[5] = "ABCD"; 
strncat(buf, "E", 5 - strlen(buf)); 
The buffer doesn't have any more space left for new characters. It is keeping 4 characters and an end-of-string 
character. The "5 - strlen(buf)" expression evaluates to 1. The strncpy() function will copy the "E" 
character into the last item of the 'buf' array and the end-of-string character will be written outside the 
buffer bounds. 
Other 34 issues are collected in this file: MirandaNG-645-2.txt. 
Erotica with new[] and delete 
Someone of the Miranda NG team keeps constantly forgetting to write square brackets for the delete 
operator: 
extern "C" int __declspec(dllexport) Load(void) 
{ 
int wdsize = GetCurrentDirectory(0, NULL); 
TCHAR *workingDir = new TCHAR[wdsize]; 
GetCurrentDirectory(wdsize, workingDir); 
Utils::convertPath(workingDir); 
workingDirUtf8 = mir_utf8encodeT(workingDir); 
delete workingDir; 
.... 
} 
PVS-Studio's diagnostic message: V611 The memory was allocated using 'new T[]' operator but was 
released using the 'delete' operator. Consider inspecting this code. It's probably better to use 'delete [] 
workingDir;'. IEView ieview_main.cpp 68 
Here are 20 more issues of the kind: MirandaNG-611-1.txt.
Well, errors like that don't usually have any serious effects though. That's why I put them into the 
"erotica" category. More hard-core things are shown further. 
Perverted new, malloc, delete and free 
The programmer mixed up methods of memory allocation and freeing: 
void CLCDLabel::UpdateCutOffIndex() 
{ 
.... 
int *piWidths = new int[(*--m_vLines.end()).length()]; 
.... 
free(piWidths); 
.... 
} 
PVS-Studio's diagnostic message: V611 The memory was allocated using 'new' operator but was 
released using the 'free' function. Consider inspecting operation logics behind the 'piWidths' variable. 
MirandaG15 clcdlabel.cpp 209 
11 more Kama Sutra positions can be studied here: MirandaNG-611-2.txt. 
Meaningless checks 
In case of a memory shortage issue, the ordinary 'new' operator throws an exception. That's why it 
doesn't make sense checking a pointer returned by 'new' for being null. 
Such an excessive check is usually harmless. However, you may sometimes come across code fragments 
like the following one: 
int CIcqProto::GetAvatarData(....) 
{ 
.... 
ar = new avatars_request(ART_GET); // get avatar 
if (!ar) { // out of memory, go away 
m_avatarsMutex->Leave(); 
return 0; 
} 
.... 
} 
PVS-Studio's diagnostic message: V668 There is no sense in testing the 'ar' pointer against null, as the 
memory was allocated using the 'new' operator. The exception will be generated in the case of memory 
allocation error. ICQ icq_avatar.cpp 608 
If the error occurs, the mutex should be freed. But it won't happen. If an object can't be created, things 
will go quite a different way than the programmer expects.
I suggest checking the rest 83 analyzer's warnings of this kind: MirandaNG-668.txt. 
SIZEOF() and _tcslen() mixed up 
#define SIZEOF(X) (sizeof(X)/sizeof(X[0])) 
.... 
TCHAR *ptszVal; 
.... 
int OnButtonPressed(WPARAM wParam, LPARAM lParam) 
{ 
.... 
int FinalLen = slen + SIZEOF(dbv.ptszVal) + 1; 
.... 
} 
PVS-Studio's diagnostic message: V514 Dividing sizeof a pointer 'sizeof (dbv.ptszVal)' by another value. 
There is a probability of logical error presence. TranslitSwitcher layoutproc.cpp 827 
Something strange is written here. The SIZEOF() macro is applied to a pointer, which makes no sense at 
all. I suspect that the programmer really wanted to calculate the string length. Then he should have used 
the _tcslen() function. 
Other similar fragments: 
• layoutproc.cpp 876 
• layoutproc.cpp 924 
• main.cpp 1300 
vptr spoiled 
class CBaseCtrl 
{ 
.... 
virtual void Release() { } 
virtual BOOL OnInfoChanged(MCONTACT hContact, LPCSTR pszProto); 
.... 
}; 
CBaseCtrl::CBaseCtrl() 
{ 
ZeroMemory(this, sizeof(*this)); 
_cbSize = sizeof(CBaseCtrl); 
}
PVS-Studio's diagnostic message: V598 The 'memset' function is used to nullify the fields of 'CBaseCtrl' 
class. Virtual method table will be damaged by this. UInfoEx ctrl_base.cpp 77 
The programmer was too lazy and settled for the ZeroMemory() function to zero the class fields. He 
didn't take into account, however, that the class contains a pointer to a virtual method table. In the base 
class, many methods are declared as virtual. Spoiling a pointer to a virtual method table will lead to 
undefined behavior when handling an object initialized in such a crude manner. 
Other similar issues: 
• ctrl_base.cpp 87 
• ctrl_base.cpp 103. 
Object lifetime 
static INT_PTR CALLBACK DlgProcFindAdd(....) 
{ 
.... 
case IDC_ADD: 
{ 
ADDCONTACTSTRUCT acs = {0}; 
if (ListView_GetSelectedCount(hwndList) == 1) { 
.... 
} 
else { 
.... 
PROTOSEARCHRESULT psr = { 0 }; <<<--- 
psr.cbSize = sizeof(psr); 
psr.flags = PSR_TCHAR; 
psr.id = str; 
acs.psr = &psr; <<<--- 
acs.szProto = (char*)SendDlgItemMessage(....); 
} 
acs.handleType = HANDLE_SEARCHRESULT; 
CallService(MS_ADDCONTACT_SHOW, 
(WPARAM)hwndDlg, (LPARAM)&acs); 
} 
break;
.... 
} 
PVS-Studio's diagnostic message: V506 Pointer to local variable 'psr' is stored outside the scope of this 
variable. Such a pointer will become invalid. Miranda findadd.cpp 777 
The 'psr' object will cease to exist when the program leaves the else branch. However, the pointer to 
this object will have been already saved by the time and will be used further in the program. This is an 
example of a genuine "wild pointer". The results of handling it cannot be predicted. 
Another similar example: 
HMENU BuildRecursiveMenu(....) 
{ 
.... 
if (GetKeyState(VK_CONTROL) & 0x8000) { 
TCHAR str[256]; 
mir_sntprintf(str, SIZEOF(str), 
_T("%s (%d, id %x)"), mi->pszName, 
mi->position, mii.dwItemData); 
mii.dwTypeData = str; 
} 
.... 
} 
PVS-Studio's diagnostic message: V507 Pointer to local array 'str' is stored outside the scope of this 
array. Such a pointer will become invalid. Miranda genmenu.cpp 973 
The text is printed into a temporary array which is destroyed right after. But the pointer to this array will 
be used in some other part of the program. 
I wonder how programs like this work at all! Check other 9 fragments inhabited by wild pointers: 
MirandaNG-506-507.txt. 
Torments of 64-bit pointers 
I didn't examine the 64-bit diagnostics. I look only to V220 warnings. Almost each of them indicates a 
genuine bug. 
Here's an example of incorrect code from the viewpoint of the 64-bit mode: 
typedef LONG_PTR LPARAM; 
LRESULT 
WINAPI 
SendMessageA(
__in HWND hWnd, 
__in UINT Msg, 
__in WPARAM wParam, 
__in LPARAM lParam); 
static INT_PTR CALLBACK DlgProcOpts(....) 
{ 
.... 
SendMessageA(hwndCombo, CB_ADDSTRING, 0, (LONG)acc[i].name); 
.... 
} 
PVS-Studio's diagnostic message: V220 Suspicious sequence of types castings: memsize -> 32-bit integer 
-> memsize. The value being casted: 'acc[i].name'. GmailNotifier options.cpp 55 
A 64-bit pointer is to be passed somewhere. To do this, it must be cast to the LPARAM type. But instead, 
this pointer is forced to turn into the 32-bit LONG type and only after that automatically expanded to 
LONG_PTR. This error dates back to the times of 32 bits when the LONG and LPARAM types' sizes 
coincided. Nowadays they no longer do. The most significant 32 bits will be spoiled in the 64-bit pointer. 
What is especially unpleasant about bugs like this is that they do not eagerly reveal themselves. You will 
be lucky while memory is allocated within the low addresses. 
Here are 20 more fragments where 64-bit pointers get spoiled: MirandaNG-220.txt. 
Non-erased private data 
void CAST256::Base::UncheckedSetKey(....) 
{ 
AssertValidKeyLength(keylength); 
word32 kappa[8]; 
.... 
memset(kappa, 0, sizeof(kappa)); 
} 
PVS-Studio's diagnostic message: V597 The compiler could delete the 'memset' function call, which is 
used to flush 'kappa' buffer. The RtlSecureZeroMemory() function should be used to erase the private 
data. Cryptlib cast.cpp 293 
The compiler will delete the call of the memset() function in the release version. To find out why, see 
the diagnostic description. 
There are 6 more fragments where private data won't be erased: MirandaNG-597.txt. 
Miscellaneous 
There are another couple of analyzer's warnings which I'd like to discuss together.
void LoadStationData(...., WIDATA *Data) 
{ 
.... 
ZeroMemory(Data, sizeof(Data)); 
.... 
} 
PVS-Studio's diagnostic message: V512 A call of the 'memset' function will lead to underflow of the 
buffer 'Data'. Weather weather_ini.cpp 250 
What the 'sizeof(Data)' expression returns is the size of the pointer, not WIDATA. Only part of the object 
will be zeroed. A correct way to write this code is as follows: sizeof(*Data). 
void CSametimeProto::CancelFileTransfer(HANDLE hFt) 
{ 
.... 
FileTransferClientData* ftcd = ....; 
if (ftcd) { 
while (mwFileTransfer_isDone(ftcd->ft) && ftcd) 
ftcd = ftcd->next; 
.... 
} 
PVS-Studio's diagnostic message: V713 The pointer ftcd was utilized in the logical expression before it 
was verified against nullptr in the same logical expression. Sametime files.cpp 423 
In the loop condition, the 'ftcd' pointer is first dereferenced and only then checked. I guess the 
expression should be rewritten in the following way: 
while (ftcd && mwFileTransfer_isDone(ftcd->ft)) 
Conclusion 
Handling pointers and memory is not the only aspect of C++ programs. In the next article, we'll discuss 
other types of bugs found in Miranda NG. There are not as many of them, but still quite a lot.

Miranda NG Project to Get the "Wild Pointers" Award (Part 1)

  • 1.
    Miranda NG Projectto Get the "Wild Pointers" Award (Part 1) Author: Andrey Karpov Date: 25.11.2014 I have recently got to the Miranda NG project and checked it with the PVS-Studio code analyzer. And I'm afraid this is the worst project in regard to memory and pointers handling issues I've ever seen. Although I didn't study the analysis results too thoroughly, there still were so many errors that I had to split the material into 2 articles. The first of them is devoted to pointers and the second to all the rest stuff. Enjoy reading and don't forget your popcorn. Checking Miranda NG The Miranda NG project is a successor of the multi-protocol IM-client for Windows, Miranda IM. Well, I didn't actually plan to check Miranda NG at first. It's just that we need a few actively developing projects to test one PVS-Studio's new feature on. It is about using a special database storing all the information about messages that shouldn't be displayed. To learn more about it, see this article. In brief, the idea behind it is the following. It is sometimes difficult to integrate static analysis into a large project because the analyzer generates too many warnings and one has a hard time trying to sort it all out while still wishing to start seeing the benefit right away. That's why you can hide all the warnings and check only fresh ones generated while writing new code or doing refactoring. And then, if you really feel like that, you can start gradually fixing errors in the old code. Miranda NG appeared to be one of the actively developing projects. But when I saw the analysis results generated by PVS-Studio after the first launch, I knew for sure I had got enough material for a new article. So, let's see what the PVS-Studio static code analyzer has found in Miranda NG's source codes. To do this check, we took the Trunk from the repository. Please keep in mind that I was just scanning through the analysis report and may have well missed much. I only checked the general diagnostics of the 1-st and 2-nd severity levels and didn't even bother to take a look at the 3-rd level. You see, the first two were just more than enough. Part 1. Pointers and memory handling Null pointer dereferencing void CMLan::OnRecvPacket(u_char* mes, int len, in_addr from) { .... TContact* cont = m_pRootContact;
  • 2.
    .... if (!cont) RequestStatus(true, cont->m_addr.S_un.S_addr); .... } PVS-Studio's diagnostic message: V522 Dereferencing of the null pointer 'cont' might take place. EmLanProto mlan.cpp 342 It's all simple here. Since the pointer equals NULL, then let's dereference it and see if anything funny comes out of it. First using the pointer, then checking it There are numbers and numbers of errors of this kind in Miranda NG, just like in any other application. Such code usually works well because the function receives a non-null pointer. But if it is null, functions aren't ready to handle it. Here's a typical example: void TSAPI BB_InitDlgButtons(TWindowData *dat) { .... HWND hdlg = dat->hwnd; .... if (dat == 0 || hdlg == 0) { return; } .... } PVS-Studio's diagnostic message: V595 The 'dat' pointer was utilized before it was verified against nullptr. Check lines: 428, 430. TabSRMM buttonsbar.cpp 428 If you pass NULL into the BB_InitDlgButtons() function, the check will be done too late. The analyzer generated 164 more messages like this on Miranda NG's code. Citing them all in this article won't make any sense, so here they are all in a file: MirandaNG-595.txt. Potentially uninitialized pointer BSTR IEView::getHrefFromAnchor(IHTMLElement *element) { .... if (SUCCEEDED(....) { VARIANT variant; BSTR url; if (SUCCEEDED(element->getAttribute(L"href", 2, &variant) && variant.vt == VT_BSTR))
  • 3.
    { url =mir_tstrdup(variant.bstrVal); SysFreeString(variant.bstrVal); } pAnchor->Release(); return url; } .... } PVS-Studio's diagnostic message: V614 Potentially uninitialized pointer 'url' used. IEView ieview.cpp 1117 If the if (SUCCEEDED(....)) condition is wrong, the 'url' variable will remain uninitialized and the function will have to return god knows what. The situation is much trickier though. The code contains another error: a closing parenthesis is put in a wrong place. It will result in the SUCCEEDED macro being applied only to the expression of the 'bool' type, which doesn't make any sense. The second bug makes up for the first. Let's see what the SUCCEEDED macro really is in itself: #define SUCCEEDED(hr) (((HRESULT)(hr)) >= 0) An expression of the 'bool' type evaluates to 0 or 1. In its turn, 0 or 1 are always >= 0. So it turns out that the SUCCEEDED macro will always return the truth value thus enabling the 'url' variable to be initialized all the time. So now we've just seen a very nice example of how one bug makes up for another. If we fix the condition, the bug with the uninitialized variable will show up. If we fix both, the code will look like this: BSTR url = NULL; if (SUCCEEDED(element->getAttribute(L"href", 2, &variant)) && variant.vt == VT_BSTR) The analyzer suspects something to be wrong in 20 more fragments. Here they are: MirandaNG-614.txt. Array size and item number mixed up The number of items in an array and the array size in bytes are two different entities. However, if you are not careful enough, you may easily mix them up. The Miranda NG project offers a handful of various ways to do that. Most harmful of all was the SIZEOF macro: #define SIZEOF(X) (sizeof(X)/sizeof(X[0])) This macro calculates the number of items in an array. But the programmer seems to treat it as a fellow of the sizeof() operator. I don't know, though, why use a macro instead of the standard sizeof() then, so I have another version - the programmer doesn't know how to use the memcpy() function. Here is a typical example:
  • 4.
    int CheckForDuplicate(MCONTACT contact_list[],MCONTACT lparam) { MCONTACT s_list[255] = { 0 }; memcpy(s_list, contact_list, SIZEOF(s_list)); for (int i = 0;; i++) { if (s_list[i] == lparam) return i; if (s_list[i] == 0) return -1; } return 0; } PVS-Studio's diagnostic message: V512 A call of the 'memcpy' function will lead to underflow of the buffer 's_list'. Sessions utils.cpp 288 The memcpy() function will copy only part of the array as the third argument specifies the array size in bytes. In the same incorrect way, the SIZEOF() macro is used in 8 more places: MirandaNG-512-1.txt. The next trouble. Programmers often forget to fix memset()/memcpy() calls when using Unicode in their code: void checkthread(void*) { .... WCHAR msgFrom[512]; WCHAR msgSubject[512]; ZeroMemory(msgFrom,512); ZeroMemory(msgSubject,512); .... } PVS-Studio's diagnostic messages: • V512 A call of the 'memset' function will lead to underflow of the buffer 'msgFrom'. LotusNotify lotusnotify.cpp 760 • V512 A call of the 'memset' function will lead to underflow of the buffer 'msgSubject'. LotusNotify lotusnotify.cpp 761 The ZeroMemoty() function will clear only half of the buffer as characters of the WCHAR type occupy 2 bytes. And here is an example of partial string copying:
  • 5.
    INT_PTR CALLBACK DlgProcMessage(....) { .... CopyMemory(tr.lpstrText, _T("mailto:"), 7); .... } PVS-Studio's diagnostic message: V512 A call of the 'memcpy' function will lead to underflow of the buffer 'L"mailto:"'. TabSRMM msgdialog.cpp 2085 Only part of the string will be copied. Each string character occupies 2 bytes, so 14 bytes instead of 7 should have been copied. Other similar issues: • userdetails.cpp 206 • weather_conv.cpp 476 • dirent.c 138 The next mistake was made due to mere inattentiveness: #define MSGDLGFONTCOUNT 22 LOGFONTA logfonts[MSGDLGFONTCOUNT + 2]; void TSAPI CacheLogFonts() { int i; HDC hdc = GetDC(NULL); logPixelSY = GetDeviceCaps(hdc, LOGPIXELSY); ReleaseDC(NULL, hdc); ZeroMemory(logfonts, sizeof(LOGFONTA) * MSGDLGFONTCOUNT + 2); .... } PVS-Studio's diagnostic message: V512 A call of the 'memset' function will lead to underflow of the buffer 'logfonts'. TabSRMM msglog.cpp 134 The programmer must have been in a hurry, for he mixed up the object size and number of objects. 2 should be added before the multiplication. Here's the fixed code: ZeroMemory(logfonts, sizeof(LOGFONTA) * (MSGDLGFONTCOUNT + 2)); In the next sample, the programmer tried his best to make it all work right using sizeof() but eventually ended up mixing sizes up again. The resulting value is larger than needed.
  • 6.
    BOOL HandleLinkClick(....) { .... MoveMemory(tr.lpstrText + sizeof(TCHAR)* 7, tr.lpstrText, sizeof(TCHAR)*(tr.chrg.cpMax - tr.chrg.cpMin + 1)); .... } PVS-Studio's diagnostic message: V620 It's unusual that the expression of sizeof(T)*N kind is being summed with the pointer to T type. Scriver input.cpp 387 The 'tr.lpstrText' variable points to a string consisting of characters of the wchat_t type. If you want to skip 7 characters, you just need to add 7; no need to multiply it by sizeof(wchar_t). Another similar error: ctrl_edit.cpp 351 It's not over, I'm afraid. What about one more way of making a mistake: INT_PTR CALLBACK DlgProcThemeOptions(....) { .... str = (TCHAR *)malloc(MAX_PATH+1); .... } PVS-Studio's diagnostic message: V641 The size of the allocated memory buffer is not a multiple of the element size. KeyboardNotify options.cpp 718 Multiplication by sizeof(TCHAR) is missing. There are 2 more errors in the same file, lines 819 and 1076. And finally the last code fragment with an error related to the number of items: void createProcessList(void) { .... ProcessList.szFileName[i] = (TCHAR *)malloc(wcslen(dbv.ptszVal) + 1); if (ProcessList.szFileName[i]) wcscpy(ProcessList.szFileName[i], dbv.ptszVal); .... }
  • 7.
    PVS-Studio's diagnostic messages:V635 Consider inspecting the expression. The length should probably be multiplied by the sizeof(wchar_t). KeyboardNotify main.cpp 543 Missing multiplication by sizeof(TCHAR) can also be found in the following fragments: options.cpp 1177, options.cpp 1204. Now we're done with sizes, let's pass on to other methods of shooting yourself in the foot with a pointer. Array index out of bounds INT_PTR CALLBACK DlgProcFiles(....) { .... char fn[6], tmp[MAX_PATH]; .... SetDlgItemTextA(hwnd, IDC_WWW_TIMER, _itoa(db_get_w(NULL, MODNAME, strcat(fn, "_timer"), 60), tmp, 10)); .... } V512 A call of the 'strcat' function will lead to overflow of the buffer 'fn'. NimContact files.cpp 290 The "_timer" string doesn't fit into the 'fn' array. Although it consists of 6 characters only, mind the terminal null character (NUL). Theoretically, we've got undefined behavior here. In practice, it appears that the 'tmp' array will be affected: '0' will be written into the null element of the 'tmp' array. The next example is even worse. In the code below, HANDLE of some icon will be spoiled: typedef struct { int cbSize; char caps[0x10]; HANDLE hIcon; char name[MAX_CAPNAME]; } ICQ_CUSTOMCAP; void InitCheck() { .... strcpy(cap.caps, "GPG AutoExchange"); ....
  • 8.
    } PVS-Studio's diagnosticmessage: V512 A call of the 'strcpy' function will lead to overflow of the buffer 'cap.caps'. New_GPG main.cpp 2246 The end-of-string character is again not taken into account. I guess it would be better to use the memcpy() function here. Other similar issues: • main.cpp 2261 • messages.cpp 541 • messages.cpp 849 • utilities.cpp 547 The Great and Powerful strncat() function Many heard about the danger of using the strcat() function and therefore prefer to use a seemingly safer strncat() function instead. But few can really handle it right. This function is much more dangerous than you might think. You see, the third argument specifies the amount of free space left in the buffer, not the buffer's maximum length. The following code is totally incorrect: BOOL ExportSettings(....) { .... char header[512], buff[1024], abuff[1024]; .... strncat(buff, abuff, SIZEOF(buff)); .... } PVS-Studio's diagnostic message: V645 The 'strncat' function call could lead to the 'buff' buffer overflow. The bounds should not contain the size of the buffer, but a number of characters it can hold. Miranda fontoptions.cpp 162 If only half of 'buff' is occupied, the code will show no care about it and allow adding 1000 more characters thus causing an array overrun - and quite a large one indeed. After all, the programmer could simply use strcat() to get the same result. Well, to be exact, the statement strncat(...., ...., SIZEOF(X)) is fundamentally incorrect. It implies that the array ALWAYS has some free space left. There are 48 more fragments in Miranda NG where the strncat() function is misused. Here they are: MirandaNG-645-1.txt. By the way, such issues in the code can well be treated as potential vulnerabilities. In defense of Miranda NG programmers, I should note that some of them did read the description of the strncat() function. These guys write their code in the following way: void __cdecl GGPROTO::dccmainthread(void*)
  • 9.
    { .... strncat(filename,(char*)local_dcc->file_info.filename, sizeof(filename) - strlen(filename)); .... } PVS-Studio's diagnostic message: V645 The 'strncat' function call could lead to the 'filename' buffer overflow. The bounds should not contain the size of the buffer, but a number of characters it can hold. GG filetransfer.cpp 273 Unfortunately, it's wrong again. At least, there is a risk of spoiling 1 byte outside the array. And I think you have already guessed that the reason is that very ill-fated end-of-string character that wasn't taken into account. Let me explain this error by a simple example: char buf[5] = "ABCD"; strncat(buf, "E", 5 - strlen(buf)); The buffer doesn't have any more space left for new characters. It is keeping 4 characters and an end-of-string character. The "5 - strlen(buf)" expression evaluates to 1. The strncpy() function will copy the "E" character into the last item of the 'buf' array and the end-of-string character will be written outside the buffer bounds. Other 34 issues are collected in this file: MirandaNG-645-2.txt. Erotica with new[] and delete Someone of the Miranda NG team keeps constantly forgetting to write square brackets for the delete operator: extern "C" int __declspec(dllexport) Load(void) { int wdsize = GetCurrentDirectory(0, NULL); TCHAR *workingDir = new TCHAR[wdsize]; GetCurrentDirectory(wdsize, workingDir); Utils::convertPath(workingDir); workingDirUtf8 = mir_utf8encodeT(workingDir); delete workingDir; .... } PVS-Studio's diagnostic message: V611 The memory was allocated using 'new T[]' operator but was released using the 'delete' operator. Consider inspecting this code. It's probably better to use 'delete [] workingDir;'. IEView ieview_main.cpp 68 Here are 20 more issues of the kind: MirandaNG-611-1.txt.
  • 10.
    Well, errors likethat don't usually have any serious effects though. That's why I put them into the "erotica" category. More hard-core things are shown further. Perverted new, malloc, delete and free The programmer mixed up methods of memory allocation and freeing: void CLCDLabel::UpdateCutOffIndex() { .... int *piWidths = new int[(*--m_vLines.end()).length()]; .... free(piWidths); .... } PVS-Studio's diagnostic message: V611 The memory was allocated using 'new' operator but was released using the 'free' function. Consider inspecting operation logics behind the 'piWidths' variable. MirandaG15 clcdlabel.cpp 209 11 more Kama Sutra positions can be studied here: MirandaNG-611-2.txt. Meaningless checks In case of a memory shortage issue, the ordinary 'new' operator throws an exception. That's why it doesn't make sense checking a pointer returned by 'new' for being null. Such an excessive check is usually harmless. However, you may sometimes come across code fragments like the following one: int CIcqProto::GetAvatarData(....) { .... ar = new avatars_request(ART_GET); // get avatar if (!ar) { // out of memory, go away m_avatarsMutex->Leave(); return 0; } .... } PVS-Studio's diagnostic message: V668 There is no sense in testing the 'ar' pointer against null, as the memory was allocated using the 'new' operator. The exception will be generated in the case of memory allocation error. ICQ icq_avatar.cpp 608 If the error occurs, the mutex should be freed. But it won't happen. If an object can't be created, things will go quite a different way than the programmer expects.
  • 11.
    I suggest checkingthe rest 83 analyzer's warnings of this kind: MirandaNG-668.txt. SIZEOF() and _tcslen() mixed up #define SIZEOF(X) (sizeof(X)/sizeof(X[0])) .... TCHAR *ptszVal; .... int OnButtonPressed(WPARAM wParam, LPARAM lParam) { .... int FinalLen = slen + SIZEOF(dbv.ptszVal) + 1; .... } PVS-Studio's diagnostic message: V514 Dividing sizeof a pointer 'sizeof (dbv.ptszVal)' by another value. There is a probability of logical error presence. TranslitSwitcher layoutproc.cpp 827 Something strange is written here. The SIZEOF() macro is applied to a pointer, which makes no sense at all. I suspect that the programmer really wanted to calculate the string length. Then he should have used the _tcslen() function. Other similar fragments: • layoutproc.cpp 876 • layoutproc.cpp 924 • main.cpp 1300 vptr spoiled class CBaseCtrl { .... virtual void Release() { } virtual BOOL OnInfoChanged(MCONTACT hContact, LPCSTR pszProto); .... }; CBaseCtrl::CBaseCtrl() { ZeroMemory(this, sizeof(*this)); _cbSize = sizeof(CBaseCtrl); }
  • 12.
    PVS-Studio's diagnostic message:V598 The 'memset' function is used to nullify the fields of 'CBaseCtrl' class. Virtual method table will be damaged by this. UInfoEx ctrl_base.cpp 77 The programmer was too lazy and settled for the ZeroMemory() function to zero the class fields. He didn't take into account, however, that the class contains a pointer to a virtual method table. In the base class, many methods are declared as virtual. Spoiling a pointer to a virtual method table will lead to undefined behavior when handling an object initialized in such a crude manner. Other similar issues: • ctrl_base.cpp 87 • ctrl_base.cpp 103. Object lifetime static INT_PTR CALLBACK DlgProcFindAdd(....) { .... case IDC_ADD: { ADDCONTACTSTRUCT acs = {0}; if (ListView_GetSelectedCount(hwndList) == 1) { .... } else { .... PROTOSEARCHRESULT psr = { 0 }; <<<--- psr.cbSize = sizeof(psr); psr.flags = PSR_TCHAR; psr.id = str; acs.psr = &psr; <<<--- acs.szProto = (char*)SendDlgItemMessage(....); } acs.handleType = HANDLE_SEARCHRESULT; CallService(MS_ADDCONTACT_SHOW, (WPARAM)hwndDlg, (LPARAM)&acs); } break;
  • 13.
    .... } PVS-Studio'sdiagnostic message: V506 Pointer to local variable 'psr' is stored outside the scope of this variable. Such a pointer will become invalid. Miranda findadd.cpp 777 The 'psr' object will cease to exist when the program leaves the else branch. However, the pointer to this object will have been already saved by the time and will be used further in the program. This is an example of a genuine "wild pointer". The results of handling it cannot be predicted. Another similar example: HMENU BuildRecursiveMenu(....) { .... if (GetKeyState(VK_CONTROL) & 0x8000) { TCHAR str[256]; mir_sntprintf(str, SIZEOF(str), _T("%s (%d, id %x)"), mi->pszName, mi->position, mii.dwItemData); mii.dwTypeData = str; } .... } PVS-Studio's diagnostic message: V507 Pointer to local array 'str' is stored outside the scope of this array. Such a pointer will become invalid. Miranda genmenu.cpp 973 The text is printed into a temporary array which is destroyed right after. But the pointer to this array will be used in some other part of the program. I wonder how programs like this work at all! Check other 9 fragments inhabited by wild pointers: MirandaNG-506-507.txt. Torments of 64-bit pointers I didn't examine the 64-bit diagnostics. I look only to V220 warnings. Almost each of them indicates a genuine bug. Here's an example of incorrect code from the viewpoint of the 64-bit mode: typedef LONG_PTR LPARAM; LRESULT WINAPI SendMessageA(
  • 14.
    __in HWND hWnd, __in UINT Msg, __in WPARAM wParam, __in LPARAM lParam); static INT_PTR CALLBACK DlgProcOpts(....) { .... SendMessageA(hwndCombo, CB_ADDSTRING, 0, (LONG)acc[i].name); .... } PVS-Studio's diagnostic message: V220 Suspicious sequence of types castings: memsize -> 32-bit integer -> memsize. The value being casted: 'acc[i].name'. GmailNotifier options.cpp 55 A 64-bit pointer is to be passed somewhere. To do this, it must be cast to the LPARAM type. But instead, this pointer is forced to turn into the 32-bit LONG type and only after that automatically expanded to LONG_PTR. This error dates back to the times of 32 bits when the LONG and LPARAM types' sizes coincided. Nowadays they no longer do. The most significant 32 bits will be spoiled in the 64-bit pointer. What is especially unpleasant about bugs like this is that they do not eagerly reveal themselves. You will be lucky while memory is allocated within the low addresses. Here are 20 more fragments where 64-bit pointers get spoiled: MirandaNG-220.txt. Non-erased private data void CAST256::Base::UncheckedSetKey(....) { AssertValidKeyLength(keylength); word32 kappa[8]; .... memset(kappa, 0, sizeof(kappa)); } PVS-Studio's diagnostic message: V597 The compiler could delete the 'memset' function call, which is used to flush 'kappa' buffer. The RtlSecureZeroMemory() function should be used to erase the private data. Cryptlib cast.cpp 293 The compiler will delete the call of the memset() function in the release version. To find out why, see the diagnostic description. There are 6 more fragments where private data won't be erased: MirandaNG-597.txt. Miscellaneous There are another couple of analyzer's warnings which I'd like to discuss together.
  • 15.
    void LoadStationData(...., WIDATA*Data) { .... ZeroMemory(Data, sizeof(Data)); .... } PVS-Studio's diagnostic message: V512 A call of the 'memset' function will lead to underflow of the buffer 'Data'. Weather weather_ini.cpp 250 What the 'sizeof(Data)' expression returns is the size of the pointer, not WIDATA. Only part of the object will be zeroed. A correct way to write this code is as follows: sizeof(*Data). void CSametimeProto::CancelFileTransfer(HANDLE hFt) { .... FileTransferClientData* ftcd = ....; if (ftcd) { while (mwFileTransfer_isDone(ftcd->ft) && ftcd) ftcd = ftcd->next; .... } PVS-Studio's diagnostic message: V713 The pointer ftcd was utilized in the logical expression before it was verified against nullptr in the same logical expression. Sametime files.cpp 423 In the loop condition, the 'ftcd' pointer is first dereferenced and only then checked. I guess the expression should be rewritten in the following way: while (ftcd && mwFileTransfer_isDone(ftcd->ft)) Conclusion Handling pointers and memory is not the only aspect of C++ programs. In the next article, we'll discuss other types of bugs found in Miranda NG. There are not as many of them, but still quite a lot.