RADE |
C++ Source Checker |
mkCheckSource AddRef Release ChecksTesting source code in the CAA V5 environment |
Technical Article |
AbstractThis article describes the checks relative to AddRef / Release. These checks are identified by AWRS, AWR2, PNSS. |
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]
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.
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 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.
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; } |
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 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.
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 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.
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]
For a more complete description of Setting Files, see [3].
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):
Be careful:
The behaviour of a method declared in a base class is inherited to 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.
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.
This section is located in: SettingsSet > OptionLists > MemoryManagement_OptionLists
This is an internal section. Do not use it.
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:
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]
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:
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.
/** * ... * @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); |
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.
/** * ... * @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.
/** * ... * @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]
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]
[1] | CAA V5 C++ Coding Rules |
[2] | Using Components |
[3] | Setting Files |
[4] | CAA V5 C++ Documentation Rules |
[Top] |
Version: 1 [May 2001] | Document created |
[Top] |
Copyright © 2000, Dassault Systèmes. All rights reserved.