Click here to Skip to main content
15,867,568 members
Articles / Desktop Programming / MFC

LitmusV - Validity Verification for Variables

Rate me:
Please Sign up or sign in to vote.
4.33/5 (3 votes)
8 Jul 2011CPOL11 min read 17.3K   315   8  
A simple and easy way to check validity of variables in native C++ language
Screen shot of test program on LitmusV

Introduction

One of the most common errors in multi-thread programming is accessing invalid data in different threads. Although this is obviously the programmer’s fault, this kind of error is too common in reality to leave it as it is.

In this article, a simple remedy for this issue is presented. The presented solution is a set of supplementary classes. These classes serve a way to tell whether or not a variable is valid which does not exist in the C++ language originally.

Before you read this article, there are some terminologies you should check:

  • LitmusV means this set of classes or this system.
  • LitmusV variable means a variable which is managed by LitmusV. Therefore, the variable can be verified by using LitmusV methods.
  • LitmusV class means a class which is managed by LitmusV.
  • LitmusV object means an object which is managed by LitmusV.

Motivation: Why Do I Need It?

The following code is a common erroneous thread use:

C++
ULONG __stdcall CThreadTestDlg::ThreadFunc(LPVOID lParam)
{
    CThreadTestDlg* pThis = (CThreadTestDlg*)lParam;

    //
    // Do something accessing pThis in order to communicate to the caller.
    //

    return 0;
}

void CThreadTestDlg::OnBnClickedOk()
{
    DWORD tid;
    ::CreateThread( NULL, NULL, ThreadFunc, (LPVOID)this, 0, &tid );
}

Although it looks logically right, this code can cause runtime errors when an instance of CThreadTestDlg is released before the thread ends.

There are two possible approaches to avoid that error. One method is delaying the variables’ destruction process until threads end or terminating threads before variables are released. Another method is tracing validity of variables and checking its validity before it is accessed. In other words, the latter solution does not touch threads and selectively allows to access variables which are not yet released.

The first approach is commonly used in multi-threaded programming and well known solution. However, the application can be seen as an unresponsive program when you use the delaying destruction process method. Additionally, if the application terminates when it is releasing, the thread would encounter unstable state which can increase a number of unreleased objects. Also, forced terminating thread can cause additional variable dependency problem if the thread has variables that are shared with another thread.

Therefore, the need of the latter method emerged. However, tracing validity of variables including objects is needed to implements the latter method. Additionally, additional functions that check the validity of variables must be offered. Moreover, the offered features must have lock and unlock features to avoid runtime exceptions that come from multi-thread environment.

The LitmusV gives simple solutions for all the challenges mentioned above.

Background

To use it, basic knowledge of C++ language is enough.
To understand it, stack management of C/C++ language and basic knowledge of template class are required.

Caution: Things You Must Know Before You Use It

The LitmusV is not a panacea for accessing invalid variables in threads.
Additionally, it might have logical flaws in some situations. Although that situation is very unrealistic, I cannot say it never happens.

Therefore, if you are in trouble with this class, I strongly recommend you read this article fully and think about your problem is the case which this article describes.
Additionally, it does not contain a solution for pointer variables. This is because dealing pointer variables are usually easier than local variables and member variables which are allocated on the stack. Therefore, the solution for pointer variables will be covered later if it is possible and I have time to deal with it.

Usages: How To Use It?

By following the below instructions, you can use LitmusV properly. There are two methods to make a LitmusV variable: Using macro functions for local variables and using inheritance class.

Method I: Using Macro Functions for Local Variables

1.The first thing you should do is include “LitmusV.h” file.
2.CLitmusVManager::Init() and CLitmusVManager::Deinit() should be called at the beginning of and at the end of the program. If you are writing a MFC application, you can call these functions at the very beginning and end of CxxApp::InitInstance().
3.To declare variables, _LV_DECL_LOCAL macro is used. The macro has two parameters: variable type and variable name. For example, if you want to declare like “int temp;”, you can declare like “_LV_DECL_LOCAL( int, temp );”. You can declare not only local variables but also class member variables in this way.
4.To use variable, _LV macro must be used since the type of variables declared with _LV_DECL_LOCAL differs from the original type that is the first parameter of _LV_DECL_LOCAL.
Also, _LV macro must be used in CreateThread or AfxBeginThread function to pass variable to the new thread.
5.To access variable in the threads, _LV_EXTRACT is used. If you receive parameter in thread like “int* pVarFromCaller = (int*)lParam;”, then you can use _LV_EXTRACT like “_LV_EXTRACT(int*, pVarFromCaller, lParam, uVarID, bVarError);”. The first three parameters are similar to the original code you might use. uVarID has variant ID which is used to check the validity of variable. bVarError stores the result of _LV_EXTRACT macro. If the variable was already released before _LV_EXTRACT is executed, bVarError is FALSE.
6.In the thread, you can specify a block to ensure the validity of variable by using _LV_USEHERE. It checks the validity of variable and if the variable is valid, it locks the variable until the block ends. Therefore, the validity of variable is ensured in the specified block. If the variable was already released, the second parameter, which is bError, is set to FALSE to choose alternative path (usually exit the thread safely).

The following is an example code for a simple thread task:

C++
UINT CThreadTestDlg::ThreadFunc(LPVOID lParam)
{	
	// [org] int* pVarFromCaller = (int*)lParam;
	_LV_EXTRACT(int*, pVarFromCaller, lParam, uVarID, bVarError);
	if( bVarError ) {
		OutputDebugString(_T("Error occurs."));
		return 0;
	}

	CString s;	
	while(1) {
		{ // By using block, the area affected by synchronizing 
		  // processes is minimized.
			_LV_USEHERE( uVarID, bError );
			if( bError ) {
				OutputDebugString(_T("variable IS NOT VALID"));
			} else {				
				s.Format(_T("variable = %d, (ID = %u)"), 
					(*pVarFromCaller)++, uVarID);
				OutputDebugString(s);
			}			
		}		

		Sleep(1000);
	}

	return 0;
}

class CTestObj {
public:
	_LV_DECL_LOCAL( int, temp ); // [org] int temp;
};

void CThreadTestDlg::OnBnClickedButton1()
{
	// Declare a local LitmusV variable
	_LV_DECL_LOCAL( int, temp ); // [org] int temp;
	AfxBeginThread(ThreadFunc, 
	    (LPVOID)&_LV(temp)); // [org] AfxBeginThread(ThreadFunc, (LPVOID)&temp);

	// Declare an object which has a LitmusV variable.
	CTestObj obj;
	_LV(obj.temp) = 1024; // [org] obj.temp = 1024;
	AfxBeginThread(ThreadFunc, 
	 (LPVOID)&_LV(obj.temp)); // [org] AfxBeginThread(ThreadFunc, (LPVOID)&obj.temp);

	AfxMessageBox(_T("Temp will not be released until this message closed."));
}

This method is simple and easy to use. Moreover, you can use it to make LitmusV variable even in the class. Therefore, you can use this method everywhere you want to declare variable.
However, using macros every time you access the variable is somewhat annoying. Therefore, method II is suggested to minimize use of macros.

Method II: Using Inheritance Class

The above example is somewhat complex since macros are needed every time you access variables.
LitmusV offers a class to simplify this annoying task. The below instructions are what you should do in order to make a LitmusV variable.

1._LV_INHERIT_HDR” has to be added to the class declaration in order to inherit it.
2._LV_INHERIT_SRC” has to be added on the constructor in order to initialize the class.
3.You can use “this pointer of the object” without any modification of source code.
4.To use this pointer in threads, _LV_EXTRACT must be used like “_LV_EXTRACT(CPopupDlg*, pThis, lParam, uThisID, bError);”. Since method II is only for a class, another expression of _LV_EXTRACT may not be used frequently.
5.This step is exactly the same as step six of method I.
- In the thread, you can specify a block to ensure the validity of variable by using _LV_USEHERE. It checks the validity of variable and if the variable is valid, it locks the variable until the block ends. Therefore, the validity of variable is ensured in the specified block. If the variable was already released, the second parameter, which is bError, is set to FALSE to choose alternative path (usually exit the thread safely).

The following is an example code for a simple thread task. The code given below is from the header file:

C++
#pragma once

#include "LitmusV.h"

// CPopupDlg dialog

class CPopupDlg : public CDialogEx, _LV_INHERIT_HDR
{
	DECLARE_DYNAMIC(CPopupDlg)

public:
	CPopupDlg(CWnd* pParent = NULL);   // standard constructor

.
.
.

private:
	static UINT ThreadFunc(LPVOID lParam);
public:
	CListBox m_lstInfo;
	virtual BOOL OnInitDialog();
};

The below code is from the source file:

C++
// PopupDlg.cpp : implementation file
//

#include "stdafx.h"
.
.
.

#include "LitmusV.h"


// CPopupDlg dialog

IMPLEMENT_DYNAMIC(CPopupDlg, CDialogEx)

CPopupDlg::CPopupDlg(CWnd* pParent /*=NULL*/)
	: CDialogEx(CPopupDlg::IDD, pParent), _LV_INHERIT_SRC
{
}

.
.
.


// CPopupDlg message handlers

void CPopupDlg::OnBnClickedOk()
{
	AfxBeginThread(ThreadFunc, (LPVOID)this);
}

UINT CPopupDlg::ThreadFunc(LPVOID lParam)
{
	/***********************************************************/
	/* Initializing LitmusV - begin - */
	// [org] CPopupDlg* pThis = (CPopupDlg*)lParam;
	_LV_EXTRACT(CPopupDlg*, pThis, lParam, uThisID, bError);	
	if( bError ) {
		OutputDebugString(_T("Error occurs during the extraction process."));
		return 0;
	}
	/* Initializing LitmusV - end - */
	/***********************************************************/
	
	int nCount = 0;
	CString szText;

	while(1) {
		/***********************************************************/
		/* Using LitmusV - begin - */			
		{ // By using block, the area affected by 
		//synchronizing processes is minimized.
			_LV_USEHERE( uThisID, bError );
			if( bError ) {
				OutputDebugString(_T("this pointer IS NOT VALID"));
			} else {

				szText.Format(_T("Count : %d [this = %X]"), 
						++nCount, pThis);
				pThis->m_lstInfo.AddString(szText);
			}
		}
		/* Using LitmusV - end - */
		/***********************************************************/
		Sleep(1000);
	}

	return 0;
}

Method II is easy and simple. However, this can be applied to only classes, not the primitive type variables such as int, float, char, etc.
Therefore, I recommend you to use method I for primitive type variables and to use method II for classes.

Implementation

LitmusV

The LitmusV consists of three parts: LitmusV Support Classes, LitmusV Manager and its macro functions.

LitmusV Support Classes

To trace variables’ life cycle, detecting releasing of variables must be implemented. LitmusV Support Classes do this by using template classes. Basically, it manipulates the fact that C++ compiler calls destructor during the object is releasing. Additionally, the template class stores the Variable ID.

Although using Variable ID to identify variable is annoying, identifying variable by its address is practically impossible. This is because sometimes different variables can be assigned at the same address.

For example, the address of tempA and the address of tempB are the same because the stack pointer of caller is the same during the loop. However, tempA and tempB are definitely different variables and must be treated as different variables.

C++
void funcA()
{
     int tempA = 1024;
     
     printf("The address of tempA(int) : %X\n", &tempA);
}

void funcB()
{
     float tempB = 4096.1024;

     printf("The address of tempB(float) : %X\n", &tempB);
}     

int main(int argc, char *argv[])
{
    for(int i = 0; i < 2; i++ ) {
        if(i) funcA();
        else funcB();
    } // for ( 0 to 2 )
}

The following output is the result of the above code:

  • The address of tempB(float): 28FF14
  • The address of tempA(int): 28FF14

Image 2

By adding Variable ID to the original variable, now you do not have to pass Variable ID since the ID can be extracted by reading the previous 4 bytes of an original variable.

Although variable IDs are attached to the original value, the problem is that variable ID can also be overwritten if a new variable is allocated at the same address. To avoid this problem, it extracts variable ID at the very beginning of a thread. However, extracting variable ID on the thread can theoretically introduce a misidentifying problem. For example, it would be possible that a variable was released and new variable allocated during the creation of the thread. In other words, since there is small time gap between the execution of CreateThread or AfxBeginThread function and the actual beginning of thread function, it would be possible that a variable passed to CreateThread or AfxBeginThread can be replaced with a new one. Therefore, variable ID extracted in the thread can differ from passed variable’s ID, resulting in logical errors in runtime. This situation is shown in the following code and picture in detail:

C++
void CThreadTestDlg::OnBnClickedButton2()
{
	{
		_VL_DECL_LOCAL( int, tempA );
		_VL(tempA) = 100;

		AfxBeginThread(ThreadFunc, (LPVOID)&(_VL(tempA)));
	}

	{
		_VL_DECL_LOCAL( int, tempB );
		_VL(tempB) = 1000;

		AfxMessageBox(_T("tempB will not be released until 
					this message is closed."));
	}
}

Image 3

The above topics are related to apply LitmusV on local variables. In case of the objects, not primitive type variables such as int, float, char, etc., LitmusV serves a different way to apply it. By adding "_LV_INHERIT_HDR” on the class definition and “_LV_INHERIT_SRC” on the class constructor, the class can be traceable and verifiable. Although declaring a LitmusV class is more complex (it requires 2 additional expressions), the object can be accessed without “_LV” macro which means that it does not require any additional changes on the legacy code. The following code is an example for declaring a LitmusV class.

C++
//
// In the header file
//
class CPopupDlg : public CDialogEx, _LV_INHERIT_HDR
{
.
.
.
};

//
// In the source file
//
CPopupDlg::CPopupDlg(CWnd* pParent /*=NULL*/)
	: CDialogEx(CPopupDlg::IDD, pParent), _LV_INHERIT_SRC
{
.
.
.
}

Similar to the case of local variables, _LV_INHERIT_xxx macros create a class inherited from CLitmusVObject class. However, you do not have to use _LV macro to access a LitmusV object.

CLitmutVLock serves lock and unlock functions. It locks the given variable when it is declared. Then, it unlocks the locked variable when the block ends.

Macro Functions

To make this class accessible, LitmusV offers several macros:

  • _LV_DECL_LOCAL: It declares a local variable as a LitmusV variable.
  • _LV_INHERIT_HDR and _LV_INHERIT_SRC: It makes a LitmusV class.
  • _LV: It allows a programmer to access LitmusV variable.
  • _LV_ISVALID: It just checks the validity of variable.
  • _LV_EXTRACT: It is used to extract variable ID and information from the variable in threads.
  • _LV_USEHERE: It checks the validity of variable and if the variable is valid, it locks the variable.

All macros are defined at LitmusV.h. Therefore, if you are curious about the above macros, please refer to the “LitmusV.h” file.

LitmusV Manager

To trace the validity of variables, storing variables information such as an address of a variable is needed. The LitmusV Manager(LVM) is the object for storing and managing variables’ information.

C++
class CLitmusVManager
{
public:
	static unsigned int GetVarID(void* ptr);
	static void Init();	
	static void Deinit();
	static BOOL IsAlive(unsigned int uVarID);
	static unsigned int Register(void* ptr, void* pLitmusObj, LV_TYPE vlType);
	static void Unregister(unsigned int uVarID);

	static BOOL Lock(unsigned int uVarID);
	static void Unlock(unsigned int uVarID);

private:
	static CPtrArray m_aryVariable;
	static CPtrArray m_aryVariableLitmusObj;
	static CDWordArray m_aryVariableID;
	static CWordArray m_aryVariableType;
	static CPtrArray m_aryVariableCriticalSection;

	static BOOL	m_bInited;
	static CRITICAL_SECTION m_csAryVariable;

	static unsigned int m_uVarID;
};

It has five lists to store variables’ information.

1.m_aryVariable saves addresses of variables.
2.m_aryVariableID saves identification numbers of variables which are related to each address of variable.
3.m_aryVariableCriticalSection holds CRITICAL_SECTION object for each variable in order to serve lock and unlock features.
4.m_aryVariableType is a storage for variable type. A variable type can be “LV_TYPE_LOCAL” or “LV_TYPE_OBJECT”. LV_TYPE_LOCAL indicates that the variable is declared with “_LV_DECL_LOCAL” and LV_TYPE_OBJECT means that the variable is declared with _LV_INHERIT_xxx.
5.m_aryVariableLitmusObj stores addresses of “CLitmusVObject” objects. As some people might know, an inherited object has different “this” pointers for each inherited class.

Member functions are really simple. All functions except GetVarID and Register, have the Variable ID parameter.

Once the variable is registered by using Register function, the registered variable must be accessed by using Variable ID(uVarID). The Variable ID(m_uVarID) is an unsigned integer number that starts from 1 (the initial value is 1).

IsAlive” just checks validity of the given variable. Although this is the core function of LitmusV, this is not usually used since “Lock” function does the same work with a lock feature.

Lock” and “Unlock” functions allow programs to access variables exclusively.

Register” and “Unregister” functions manage what variables are under the control of LitmusV Manager.

Init” and “Deinit” functions must be called at the very beginning of the program and at the very end of the program to initialize and de-initialize the LitmusV class. “Init” function does not have to be called since all LitmusV methods try to initialize LitmusV class if it is not yet initialized.

History

  • 2011-07-07: Initial release

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United States United States
I started to write software since 1999 and have developed various products including security solutions and system utilities.

Microsoft Visual C++ MVP
Assistant Professor at the University of Virginia
Website: http://yongkwon.info

Comments and Discussions

 
-- There are no messages in this forum --