RADE

C++ Source Checker

mkCheckSource AddRef Release Checks

Testing source code in the CAA V5 environment
Technical Article

Abstract

This article describes the checks relative to AddRef / Release. These checks are identified by AWRS, AWR2, PNSS.


Problematic

Managing component lifecycle in client applications can be a quite difficult job. The lifecycle of the component is managed by a reference count, this count represents the number of concurrent users of the component. The reference count management is under  the responsibility of the client application. This reference count is modified by both methods AddRef() (to increase it) or Release() (to decrease it). When the reference count is null (after the last call to Release()), the component deletes itself.

If the client application does not manage this reference count properly, the reference count will be de-correlated from the effective number of users, thus leading to potential bugs. For example, if a client application forgets to call Release() after an AddRef(), the component will nether be destroyed, thus leading to a memory leak.

Some methods (the most known of them being QueryInterface) call AddRef() internally on the component. A call to one of these methods requires a call to Release() afterwards on the pointer which has been AddRef'ed, to decrease the reference count.

For more explanations on Components and Interfaces Lifecycle management, see [1] and [2].

CSC helps debugging these memory leaks, by pointing some of the non-Released components.

[Top]

Checks

Principle

CSC knows a list of AddReffing methods (these methods are declared in the settings file or they are tagged in source code), the most known of them being QueryInterface. When one of these methods is called, the AddRef'ed pointer is followed by CSC to see if it is further Released (or stored, or duplicated, or returned). If this pointer is neither Released, nor stored, nor duplicated, nor returned, an error will be reported.

Limitations

If there are different overloading methods with the same number of arguments inside a class, they are supposed to have the same behaviour. For example, if one of them is declared as returning an AddRef'ed pointer, they all will be regarded as doing the same.

PNSS

PNSS stands for Pointer Not Stored in Source.

An error of type PNSS is reported when:

As the returned pointer is not stored, it can not be released, thus leading to a memory leak.

Example PNSS-1

The returned pointer is not stored.

Let's assume that CATPathElement::FindElement returns an AddRef'ed Pointer:

CATBaseUnknown * CATPathElement::FindElement( const IID &iIid )
{
  ...
  pElt->AddRef();
  return pElt;
}

The following code will report a PNSS error:

CATPathElement* pathElement= ...;
const IDD iid= ...;

if( pathElement-> FindElement(iid) ) 
{
  ...
}

The correct code is:

CATPathElement* pathElement= ...;
const IDD iid= ...;

CATBaseUnknown* foundElement= NULL;
if( foundElement= pathElement-> FindElement(iid) ) 
{
  ...
  foundElement->Release();
  foundElement= NULL;
}

Example PNSS-2

The returned pointer is directly used.

Let's assume that CATPathElement::FindElement returns an AddRef'ed pointer:

CATBaseUnknown * CATPathElement::FindElement( const IID &iIid )
{
  ...
  pElt->AddRef();
  return pElt;
}

The following code will report a PNSS error:

CATPathElement* pathElement= ...;
const IDD iid= ...;

if( pathElement-> FindElement(iid)->IsAKindOf( "CATPathElement" ) ) 
{
  ...
}

The correct code is:

CATPathElement* pathElement= ...;
const IDD iid= ...;

CATBaseUnknown* foundElement= NULL;
foundElement= pathElement-> FindElement(iid); 
if( foundElement ) 
{
  if( foundElement->IsAKindOf( "CATPathElement" ) ) 
  {
    ...
  }
  foundElement->Release();
  foundElement= NULL;
}

AWRS

AWRS stands for AddRef Without Release in Source.

An error of type AWRS is reported when:

As the returned pointer is not released, a memory leak is generated.

Example AWRS-1

The returned pointer is not Released.

The second argument of CATBaseUnknown::QueryInterface(..., ...) is an AddRef'ed pointer.

The following code will report an AWRS error:

CATBaseUnknown *entityToBeLinked= ...;  // Retrieve entityToBeLinked
if( entityToBeLinked )
{
  CATIElecConnector *pInputCtr = NULL;
  HRESULT rc = entityToBeLinked-> QueryInterface(CATIElecConnector::ClassId(), (void **)&pInputCtr);
  if( SUCCEEDED( rc ) )
  {
    // use of pInputCtr
    ..
  }
}

The correct code is:

CATBaseUnknown *entityToBeLinked= ...;  // Retrieve entityToBeLinked
if( entityToBeLinked )
{
  CATIElecConnector *pInputCtr = NULL;
  HRESULT rc = entityToBeLinked-> QueryInterface(CATIElecConnector::ClassId(), (void **)&pInputCtr);
  if( SUCCEEDED( rc ) )
  {
    // use of pInputCtr
    ..
    pInputCtr->Release();
    pInputCtr= NULL;
  }
}

AWR2

AWR2 stands for AddRef Without Release type 2.

An error of type AWR2 is reported when:

In the instruction "final= initial;", let's call final the "duplicating object" and initial the "duplicated object".

As the returned pointer can is not released, a memory leak is generated.

Example AWR2-1

The returned pointer is duplicated and not released.

The return of CATISpecAttribute::GetSpecObject() is a AddRef'ed pointer.

The following code will report an AWR2 error:

CATISpecAttribute* pIFaceConnectorAttr = ...;  // Retrieving the CATISpecAttribute pointer
CATISpecObject* pISpecDrwConnector= NULL;
if( pIFaceConnectorAttr != NULL ) 
{ 
  pISpecDrwConnector = pIFaceConnectorAttr-> GetSpecObject(); // Retrieving the AddRef'ed pointer
} 

CATIConnector_var spIConnector = pISpecDrwConnector;   // Duplicating the pointer
if( spIConnector != NULL_var) 
{
  // Using spIConnector
  ...
}
// Neither the initial pointer, nor the duplicated handler are released

The correct code is:

CATISpecAttribute* pIFaceConnectorAttr = ...;  // Retrieving the CATISpecAttribute pointer
CATISpecObject* pISpecDrwConnector= NULL;
if( pIFaceConnectorAttr != NULL ) 
{ 
  pISpecDrwConnector = pIFaceConnectorAttr-> GetSpecObject(); // Retrieving the AddRef'ed pointer
} 

CATIConnector_var spIConnector = pISpecDrwConnector;   // Duplicating the pointer
if( pISpecDrwConnector )
{
  pISpecDrwConnector->Release();  // Releasing the AddRef'ed pointer (spIConnector is still valid, as it is a handler)
  pISpecDrwConnector= NULL;
}
if( spIConnector != NULL_var ) 
{
  // Using spIConnector
  ...
}

[Top]

Relative sections in setting files

For a more complete description of Setting Files, see [3].

MemoryManagement_AddRefMethods

This section is located in: SettingsSet > OptionLists > MemoryManagement_OptionLists

This section contains a list of CallWithSpecificArg. Each CallWithSpecificArg describes a method, a function or a macro returning an AddRef'ed pointer (directly or via a returned argument), which will have to be released (or return or stored):

ClassName
The name of the class (no ClassName means that it is a function)
MethodOrFunctionSign
The signature of the method or function AddReffing the pointer
SpecificArg
The number of the AddRef'ed argument (0 refers to the method's result)

Be careful:

Example 1:

SpecificArg          2
ClassName            IUnknown
MethodOrFunctionSign QueryInterface(?,void**)

This means that the method QueryInterface with 2 arguments (the second being a void**) of the class IUnknown (or any inheriting class, like CATBaseUnknown) makes an AddRef on its second argument.

Example 2:

SpecificArg          0
ClassName            CATIGraphNode
MethodOrFunctionSign GetGraphNode

This means that the method GetGraphNode (no matter the number of arguments) of the class CATIGraphNode (or any inheriting class) makes an AddRef on its returned pointer.

MemoryManagement_NeutralMethods

This section is located in: SettingsSet > OptionLists > MemoryManagement_OptionLists

This is an internal section. Do not use it.

MemoryManagement_StorageMethods

This section is located in: SettingsSet > OptionLists > MemoryManagement_OptionLists

This section contains a list of CallWithSpecificArg. Each CallWithSpecificArg describes a method, a function or a macro storing or releasing a pointer:

ClassName
The name of the class (no ClassName means that it is a function or a macro)
MethodOrFunctionSign
The signature of the method or function or macro storing or releasing the pointer
SpecificArg
The number of the  released argument (0 refers to the method's result)

If the argument is being released, it does not need to be released directly a second time. If the argument is being stored, it can be released later.
So, if an AddRef'ed pointer is passed to a method registered in this section, and that this pointer is not released, no error of type AWRS or AWR2 will be reported.

Example 1:

SpecificArg          2
ClassName            CATIUnknownList
MethodOrFunctionSign Add(?,?)

This means that the method Add with 2 arguments of the class CATIUnknownList stores its second argument.

Example 2:

SpecificArg          1
ClassName            <none>
MethodOrFunctionSign CATShmRelease

This means that the macro (no matter the number of arguments) CATShmRelease Releases its first argument.

[Top]

Dynamic AddReff'ing method recognition

Target

Settings files provide CSC with information about the code it cannot obtain from the implementation (see [3] for description of settings files). But this mechanism can lead to de-correlation between information data in the settings file and the current implementation, which can lead to invalid errors. Then, CSC tool provides a means to declare for each method, in the header file in which the method is declared, the returned pointers to release after a call to the method.

This mechanism has other advantages compared to the use of a settings file:

Syntax

The mechanism leans on the specific comments inserted in a header file enclosing information used to document the declared entities, classes, methods and global functions...

CSC tool parses documentation comment preceding each method and function declaration and detects the use of @param and @return mkman tags. These tags are used to describe global function or method parameters and returned value. See [4] for a general description on how to use these tags.

Among other information, a deallocation method or function can be specified for the parameter or the return value, if needed. Any caller of the method has to use the specified method to "deallocate" the parameter or return variable after use. Here is a part of the specification (using BNF notation) for @param and @return tags:

<param> ::= ' * @param' <identifier> [<direction> [',' <dealloc>]]
            ' *' <comment>
	   ...
<identifier> ::= <dir> <upperalphabetic> { <limitedalphabetic> }
<dir>        ::= 'i'    | 'o'     | 'io'
<direction>  ::= '[in]' | '[out]' | '[inout]'
<dealloc>    ::= <class-name>     "#" <memberfunction-name> |
                 <interface-name> "#" <memberfunction-name> |
                 <memberfunction-name> | 'delete'
<return-sequence> ::= <return>
<return> ::= '  * @return' [<hresult> | <dealloc> ]
             '  *' <comment>
             ...
<dealloc>    ::= <class-name>     "#" <memberfunction-name> |
                 <interface-name> "#" <memberfunction-name> |
                 <memberfunction-name> | 'delete'

Multiple @param tags are allowed in the same comment. Multiple @return tags are not allowed in the same comment

If the dealloc method specified for a parameter in a @param section is <class>#Release (eg. CATBaseUnknown#Release or IUnknown#Release), CSC will recognize the method as returning an AddReff'ed pointer via the parameter, and will check that this latter is released (or returned or stored) after use.
If the dealloc method specified in the @return section is <class>#Release (eg. CATBaseUnknown#Release or IUnknown#Release), CSC will recognize the method as returning an AddReff'ed pointer and will check that this latter is released (or returned or stored) after use.

In all other cases (delete, CATRep#Destroy...), CSC will not consider the dealloc method information.

Remarks

  1. If Release() is the dealloc method, the recommendation is to use the appropriate type, consistent with the method argument signature type, that is CATBaseUnknown#Release for any object modeler class or interface whenever Release applies (Dialog objects should use RequestDelayedDestruction, Visualization repositories request Destroy, etc), and IUnknown#Release for any interface deriving from IUnknown but not from CATBaseUnknown, even if the TIE recalls CATBaseUnknown#Release onto the implementation.
  2. As for a method declared in the settings file, the behaviour of a method declared in a base class is inherited in child classes. For example, if the method declared in the base class is declared as returning an AddRef'ed pointer, the overloading methods defined in the child classes will be regarded as doing the same.
  3. On the other hand, the behaviour is not retrieved via the adhesion: the declaration of an AddReff'ing method should be tagged in the interface and in the implementation header file.

Examples of use

Example @param-1

/**
 * ...
 * @param iRow [in]
 *  The table row
 * @param iCol [in]
 *  The table column
 * @param opCell [out, IUnknown#Release]
 *  The object in the cell ...
 */
virtual HRESULT getCellContent(int iRow, int iCol, IUnknown*& opCell);
The comment informs that this method AddReff'ed parameter opCell. The pointer must be released (or stored or return) by any caller of the method:
pTable->getCellContent(row, col, pCell);
// use of pCell
// ...
if (pCell) {
    pCell->Release();
    pCell = NULL;
}

Otherwise, an error of type AWRS or AWR2 will be generated by CSC.

Example @param-2

/**
 * ...
 * @param  oProduct  [out,  CATBaseUnknown#Release]
 * @param  oSupport [out,  CATBaseUnknown#Release]
 * ...
 */
vvirtual HRESULT getSupport(CATIProduct*& oProduct, CATILinkableObject*& oSupport, int iNumber = 1);

In this case, oProduct and oSupport pointer must be released (or stored or return) by the caller:

pObject->getSupport(pProduct, pSupport);
// ...
if (pProduct) {
    pProduct->Release();
    pProduct = NULL;
}
if (pSupport) {
    pSupport->Release();
    pSupport = NULL;
}

 Otherwise, an error of type AWRS or AWR2 will be generated by CSC.

Example @return-1

/**
 * ...
 * @return CATBaseUnknown#Release
 *   Free text
 * ...
 */
virtual CATPathElementAgent* createPathElementAgent();

The comment informs that this method get returns an AddReff'ed pointer. The pointer must be released (or stored or return) by any caller of the method:

CATPathElementAgent* pAgent = createPathElementAgent();
// ...
if (pAgent){
   pAgent->Release();
   pAgent = NULL;
}

Otherwise, an error of type AWRS, AWR2 or PNSS will be generated by CSC.

[Top]


In Short

CSC helps debugging missing Releases on pointers, via a list of known AddRef methods, declared in the setting file or tagged in source files. You can tag your own AddReffing methods to detect missing Releases on your methods. You can also add your methods using your own settings file.

[Top]


References

[1] CAA V5 C++ Coding Rules
[2] Using Components
[3] Setting Files
[4] CAA V5 C++ Documentation Rules
[Top]

History

Version: 1 [May 2001] Document created
[Top]

Copyright © 2000, Dassault Systèmes. All rights reserved.