Refactorizations in checkmemoryleak.cpp:

- Use symbolDatabase more often to increase performance and accuracy.
- Replaced indendation counter
- Replaced custom stringify implementation

Benchmark results (sqlite checking):
4% complete, 7% on "Memory leaks (function variables)", 9% on "Memory leaks (address not taken)" and 82% on "Memory leaks (struct members)"
This commit is contained in:
PKEuS 2012-05-03 10:43:47 +02:00
parent f01c244212
commit 04704ac5fa
3 changed files with 103 additions and 195 deletions

View File

@ -492,63 +492,42 @@ CheckMemoryLeak::AllocType CheckMemoryLeak::functionReturnType(const Token *tok,
} }
const char *CheckMemoryLeak::functionArgAlloc(const Token *tok, unsigned int targetpar, AllocType &allocType) const const char *CheckMemoryLeak::functionArgAlloc(const Function *func, unsigned int targetpar, AllocType &allocType) const
{ {
// Find the varid of targetpar, then locate the start of the function..
unsigned int varid = 0;
allocType = No; allocType = No;
// Locate start of arguments if (!func || !func->start)
tok = Token::findsimplematch(tok, "(");
if (!tok)
return ""; return "";
// Is this the start of a function? std::list<Variable>::const_iterator arg = func->argumentList.begin();
const Token* const fstart = tok->link(); for (; arg != func->argumentList.end(); ++arg) {
if (!Token::Match(fstart, ") const| {")) if (arg->index() == targetpar-1)
return "";
// Locate targetpar
tok = tok->next();
for (unsigned int par = 1; par < targetpar; par++) {
tok = tok->nextArgument();
if (tok == 0)
return "";
}
while (tok) {
if (tok->str() == "(")
tok = tok->link();
else if (tok->str() == ",")
break;
if (Token::Match(tok, "%type% * * %var%")) {
varid = tok->tokAt(3)->varId();
break; break;
} }
if (arg == func->argumentList.end())
tok = tok->next();
}
if (varid == 0)
return ""; return "";
// continue in function body // Is **
tok = fstart; if (!arg->isPointer())
while (tok->str() != "{") return "";
tok = tok->next(); const Token* tok = arg->typeEndToken();
if (tok->str() == "const")
tok = tok->previous();
tok = tok->previous();
if (tok->str() != "*")
return "";
// Check if pointer is allocated. // Check if pointer is allocated.
int realloc = 0; int realloc = 0;
for (const Token* const end = tok->link(); tok && tok != end; tok = tok->next()) { for (const Token* tok = func->start; tok && tok != func->start->link(); tok = tok->next()) {
if (tok->varId() == varid) { if (tok->varId() == arg->varId()) {
if (Token::Match(tok->tokAt(-3), "free ( * %varid% )", varid)) { if (Token::Match(tok->tokAt(-3), "free ( * %var% )")) {
realloc = 1; realloc = 1;
allocType = No; allocType = No;
} else if (Token::Match(tok->previous(), "* %varid% =", varid)) { } else if (Token::Match(tok->previous(), "* %var% =")) {
allocType = getAllocationType(tok->tokAt(2), varid); allocType = getAllocationType(tok->tokAt(2), arg->varId());
if (allocType == No) { if (allocType == No) {
allocType = getReallocationType(tok->tokAt(2), varid); allocType = getReallocationType(tok->tokAt(2), arg->varId());
} }
if (allocType != No) { if (allocType != No) {
if (realloc) if (realloc)
@ -652,7 +631,7 @@ const char * CheckMemoryLeakInFunction::call_func(const Token *tok, std::list<co
else if (tok2->strAt(1) == "=") else if (tok2->strAt(1) == "=")
return "assign"; return "assign";
else else
return"use_"; return "use_";
} }
} }
@ -668,7 +647,7 @@ const char * CheckMemoryLeakInFunction::call_func(const Token *tok, std::list<co
if (callstack.size() > 2) if (callstack.size() > 2)
return "dealloc_"; return "dealloc_";
const std::string funcname(tok->str()); const std::string& funcname(tok->str());
for (std::list<const Token *>::const_iterator it = callstack.begin(); it != callstack.end(); ++it) { for (std::list<const Token *>::const_iterator it = callstack.begin(); it != callstack.end(); ++it) {
if ((*it) && (*it)->str() == funcname) if ((*it) && (*it)->str() == funcname)
return "recursive"; return "recursive";
@ -705,7 +684,7 @@ const char * CheckMemoryLeakInFunction::call_func(const Token *tok, std::list<co
if (notnoreturn.find(funcname) != notnoreturn.end()) if (notnoreturn.find(funcname) != notnoreturn.end())
return NULL; return NULL;
return (tok->previous()->str() != "=") ? "callfunc" : NULL; return "callfunc";
} }
unsigned int par = 0; unsigned int par = 0;
@ -731,24 +710,17 @@ const char * CheckMemoryLeakInFunction::call_func(const Token *tok, std::list<co
if (numpar != countParameters(ftok)) if (numpar != countParameters(ftok))
return "recursive"; return "recursive";
const char *parname = Tokenizer::getParameterName(ftok, par); const Function* function = _tokenizer->getSymbolDatabase()->findFunctionByToken(ftok);
if (! parname) if (!function)
return "recursive"; return "recursive";
unsigned int parameterVarid = 0;
{ const Variable* param = function->getArgumentVar(par-1);
const Token *partok = Token::findmatch(ftok, parname); if (!param || !param->nameToken())
if (partok) return "use";
parameterVarid = partok->varId(); if (!function->start)
} return "use";
if (parameterVarid == 0) Token *func = getcode(function->start->next(), callstack, param->varId(), alloctype, dealloctype, false, sz);
return "recursive"; //simplifycode(func);
// Check if the function deallocates the variable..
while (ftok && (ftok->str() != "{"))
ftok = ftok->next();
if (!ftok)
return 0;
Token *func = getcode(ftok->next(), callstack, parameterVarid, alloctype, dealloctype, false, sz);
//simplifycode(func, all);
const Token *func_ = func; const Token *func_ = func;
while (func_ && func_->str() == ";") while (func_ && func_->str() == ";")
func_ = func_->next(); func_ = func_->next();
@ -767,8 +739,11 @@ const char * CheckMemoryLeakInFunction::call_func(const Token *tok, std::list<co
} }
if (varid > 0 && Token::Match(tok, "& %varid% [,()]", varid)) { if (varid > 0 && Token::Match(tok, "& %varid% [,()]", varid)) {
const Token *ftok = _tokenizer->getFunctionTokenByName(funcname.c_str()); const Token *ftok = _tokenizer->getFunctionTokenByName(funcname.c_str());
if (ftok == 0)
continue;
const Function* func = _tokenizer->getSymbolDatabase()->findFunctionByToken(ftok);
AllocType a; AllocType a;
const char *ret = functionArgAlloc(ftok, par, a); const char *ret = functionArgAlloc(func, par, a);
if (a != No) { if (a != No) {
if (alloctype == No) if (alloctype == No)
@ -1079,31 +1054,21 @@ Token *CheckMemoryLeakInFunction::getcode(const Token *tok, std::list<const Toke
} else { } else {
// Check if the condition depends on var or extravar somehow.. // Check if the condition depends on var or extravar somehow..
bool dep = false; bool dep = false;
int innerParlevel = 0; const Token* const end = tok->linkAt(1);
for (const Token *tok2 = tok->next(); tok2; tok2 = tok2->next()) { for (const Token *tok2 = tok->next(); tok2 != end; tok2 = tok2->next()) {
if (tok2->str() == "(") if (Token::Match(tok2, "close|pclose|fclose|closedir ( %varid% )", varid)) {
++innerParlevel;
else if (tok2->str() == ")") {
--innerParlevel;
if (innerParlevel <= 0)
break;
} else if (Token::Match(tok2, "close|pclose|fclose|closedir ( %varid% )", varid)) {
addtoken(&rettail, tok, "dealloc"); addtoken(&rettail, tok, "dealloc");
addtoken(&rettail, tok, ";"); addtoken(&rettail, tok, ";");
dep = true; dep = true;
break; break;
} else if (alloctype == Fd && Token::Match(tok2, "%varid% !=|>=", varid)) { } else if (alloctype == Fd && Token::Match(tok2, "%varid% !=|>=", varid)) {
dep = true; dep = true;
} else if (innerParlevel > 0 && Token::Match(tok2, "! %varid%", varid)) { } else if (Token::Match(tok2, "! %varid%", varid)) {
dep = true; dep = true;
} else if (innerParlevel > 0 && Token::Match(tok2, "%var% (") && !test_white_list(tok2->str())) { } else if (Token::Match(tok2, "%var% (") && !test_white_list(tok2->str())) {
bool use = false; bool use = false;
for (const Token *tok3 = tok2->tokAt(2); tok3; tok3 = tok3->next()) { for (const Token *tok3 = tok2->tokAt(2); tok3; tok3 = tok3->nextArgument()) {
if (tok3->str() == "(") if (Token::Match(tok3->previous(), "(|, &| %varid% ,|)", varid)) {
tok3 = tok3->link();
else if (tok3->str() == ")")
break;
else if (Token::Match(tok3->previous(), "(|, &| %varid% ,|)", varid)) {
use = true; use = true;
break; break;
} }
@ -2159,8 +2124,7 @@ void CheckMemoryLeakInFunction::checkScope(const Token *Tok1, const std::string
if (! noerr) { if (! noerr) {
std::ostringstream errmsg; std::ostringstream errmsg;
errmsg << "inconclusive leak of " << varname << ": "; errmsg << "inconclusive leak of " << varname << ": ";
for (const Token *tok2 = tok; tok2; tok2 = tok2->next()) errmsg << tok->stringifyList(false, false, false, false, false, 0, 0);
errmsg << " " << tok2->str();
reportError(first, Severity::debug, "debug", errmsg.str()); reportError(first, Severity::debug, "debug", errmsg.str());
} }
} }
@ -2201,8 +2165,8 @@ void CheckMemoryLeakInFunction::checkReallocUsage()
tok->varId() == tok->tokAt(4)->varId() && tok->varId() == tok->tokAt(4)->varId() &&
isNoArgument(symbolDatabase, tok->varId())) { isNoArgument(symbolDatabase, tok->varId())) {
// Check that another copy of the pointer wasn't saved earlier in the function // Check that another copy of the pointer wasn't saved earlier in the function
if (Token::findmatch(scope->classStart, "%var% = %varid% ;", tok->varId()) || if (Token::findmatch(scope->classStart, "%var% = %varid% ;", tok, tok->varId()) ||
Token::findmatch(scope->classStart, "[{};] %varid% = %var% [;=]", tok->varId())) Token::findmatch(scope->classStart, "[{};] %varid% = %var% [;=]", tok, tok->varId()))
continue; continue;
const Token* tokEndRealloc = tok->linkAt(3); const Token* tokEndRealloc = tok->linkAt(3);
@ -2220,8 +2184,8 @@ void CheckMemoryLeakInFunction::checkReallocUsage()
tok->next()->varId() == tok->tokAt(6)->varId()) && tok->next()->varId() == tok->tokAt(6)->varId()) &&
isNoArgument(symbolDatabase, tok->next()->varId())) { isNoArgument(symbolDatabase, tok->next()->varId())) {
// Check that another copy of the pointer wasn't saved earlier in the function // Check that another copy of the pointer wasn't saved earlier in the function
if (Token::findmatch(scope->classStart, "%var% = * %varid% ;", tok->next()->varId()) || if (Token::findmatch(scope->classStart, "%var% = * %varid% ;", tok, tok->next()->varId()) ||
Token::findmatch(scope->classStart, "[{};] * %varid% = %var% [;=]", tok->next()->varId())) Token::findmatch(scope->classStart, "[{};] * %varid% = %var% [;=]", tok, tok->next()->varId()))
continue; continue;
const Token* tokEndRealloc = tok->linkAt(4); const Token* tokEndRealloc = tok->linkAt(4);
@ -2248,62 +2212,12 @@ void CheckMemoryLeakInFunction::checkReallocUsage()
// Checks for memory leaks inside function.. // Checks for memory leaks inside function..
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
void CheckMemoryLeakInFunction::parseFunctionScope(const Token *body, const Token *arg, const bool classmember) static bool isInMemberFunc(const Scope* scope)
{ {
// Check locking/unlocking of global resources.. while (scope->nestedIn && !scope->functionOf)
checkScope(body->next(), "", 0, classmember, 1); scope = scope->nestedIn;
// Locate parameters and check their usage.. return (scope->functionOf != 0);
for (const Token *tok2 = arg->next(); tok2; tok2 = tok2->nextArgument()) {
if (Token::Match(tok2, "%type% * %var% [,)]") && tok2->isStandardType()) {
const std::string varname(tok2->strAt(2));
const unsigned int varid = tok2->tokAt(2)->varId();
const unsigned int sz = _tokenizer->sizeOfType(tok2);
checkScope(body->next(), varname, varid, classmember, sz);
}
}
// Locate variable declarations and check their usage..
for (const Token* tok = body; tok && tok != body->link(); tok = tok->next()) {
// Skip these weird blocks... "( { ... } )"
if (Token::simpleMatch(tok, "( {")) {
tok = tok->link();
if (!tok)
break;
continue;
}
if (!Token::Match(tok, "[{};:] %type%"))
continue;
tok = tok->next();
// Don't check static/extern variables
if (Token::Match(tok, "static|extern"))
continue;
// return/else is not part of a variable declaration..
if (Token::Match(tok, "return|else"))
continue;
unsigned int sz = _tokenizer->sizeOfType(tok);
if (sz < 1)
sz = 1;
if (Token::Match(tok, "%type% * const| %var% [;=]")) {
const Token *vartok = tok->tokAt(tok->strAt(2) != "const" ? 2 : 3);
checkScope(tok, vartok->str(), vartok->varId(), classmember, sz);
}
else if (Token::Match(tok, "%type% %type% * const| %var% [;=]")) {
const Token *vartok = tok->tokAt(tok->strAt(3) != "const" ? 3 : 4);
checkScope(tok, vartok->str(), vartok->varId(), classmember, sz);
}
else if (Token::Match(tok, "int %var% [;=]")) {
const Token *vartok = tok->next();
checkScope(tok, vartok->str(), vartok->varId(), classmember, sz);
}
}
} }
void CheckMemoryLeakInFunction::check() void CheckMemoryLeakInFunction::check()
@ -2311,15 +2225,32 @@ void CheckMemoryLeakInFunction::check()
// fill the "noreturn" // fill the "noreturn"
parse_noreturn(); parse_noreturn();
// Check locking/unlocking of global resources..
for (std::list<Scope>::const_iterator scope = symbolDatabase->scopeList.begin(); scope != symbolDatabase->scopeList.end(); ++scope) { for (std::list<Scope>::const_iterator scope = symbolDatabase->scopeList.begin(); scope != symbolDatabase->scopeList.end(); ++scope) {
// only check functions // only check functions
if (scope->type != Scope::eFunction) if (scope->type != Scope::eFunction)
continue; continue;
const Token *body = scope->classStart; checkScope(scope->classStart->next(), "", 0, scope->functionOf != NULL, 1);
const Token *arg = scope->classDef->next(); }
bool classmember = scope->functionOf != NULL;
parseFunctionScope(body, arg, classmember); // Check variables..
for (unsigned int i = 0; i < symbolDatabase->getVariableListSize(); i++) {
const Variable* var = symbolDatabase->getVariableFromVarId(i);
if (!var || (!var->isLocal() && !var->isArgument()) || var->isStatic() || !var->scope())
continue;
if (!var->isPointer() && var->typeStartToken()->str() != "int")
continue;
unsigned int sz = _tokenizer->sizeOfType(var->typeStartToken());
if (sz < 1)
sz = 1;
if (var->isArgument())
checkScope(var->scope()->classStart->next(), var->name(), i, isInMemberFunc(var->scope()), sz);
else
checkScope(var->nameToken(), var->name(), i, isInMemberFunc(var->scope()), sz);
} }
} }
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
@ -2558,38 +2489,23 @@ void CheckMemoryLeakInClass::publicAllocationError(const Token *tok, const std::
void CheckMemoryLeakStructMember::check() void CheckMemoryLeakStructMember::check()
{ {
unsigned int indentlevel1 = 0; const SymbolDatabase* symbolDatabase = _tokenizer->getSymbolDatabase();
for (const Token *tok = _tokenizer->tokens(); tok; tok = tok->next()) { for (unsigned int i = 0; i < symbolDatabase->getVariableListSize(); i++) {
if (tok->str() == "{") const Variable* var = symbolDatabase->getVariableFromVarId(i);
++indentlevel1; if (!var || !var->isLocal() || var->isStatic())
else if (tok->str() == "}")
--indentlevel1;
if (indentlevel1 == 0)
continue; continue;
if (var->typeEndToken()->isStandardType())
// check struct variables.. continue;
if (Token::Match(tok, "struct|;|{|} %type% * %var% [=;]")) { checkStructVariable(var);
checkStructVariable(tok->tokAt(3));
} else if (Token::Match(tok, "struct|;|{|} %type% %var% ;")) {
checkStructVariable(tok->tokAt(2));
}
} }
} }
bool CheckMemoryLeakStructMember::isMalloc(const Token *vartok) bool CheckMemoryLeakStructMember::isMalloc(const Variable *variable)
{ {
const unsigned int varid(vartok->varId()); const unsigned int varid(variable->varId());
bool alloc = false; bool alloc = false;
unsigned int indentlevel2 = 0; for (const Token *tok2 = variable->nameToken(); tok2 != variable->scope()->classEnd; tok2 = tok2->next()) {
for (const Token *tok2 = vartok; tok2; tok2 = tok2->next()) { if (Token::Match(tok2, "= %varid% [;=]", varid)) {
if (tok2->str() == "{")
++indentlevel2;
else if (tok2->str() == "}") {
if (indentlevel2 == 0)
break;
--indentlevel2;
} else if (Token::Match(tok2, "= %varid% [;=]", varid)) {
return false; return false;
} else if (Token::Match(tok2, "%varid% = malloc|kmalloc (", varid)) { } else if (Token::Match(tok2, "%varid% = malloc|kmalloc (", varid)) {
alloc = true; alloc = true;
@ -2599,7 +2515,7 @@ bool CheckMemoryLeakStructMember::isMalloc(const Token *vartok)
} }
void CheckMemoryLeakStructMember::checkStructVariable(const Token * const vartok) void CheckMemoryLeakStructMember::checkStructVariable(const Variable * const variable)
{ {
// This should be in the CheckMemoryLeak base class // This should be in the CheckMemoryLeak base class
static std::set<std::string> ignoredFunctions; static std::set<std::string> ignoredFunctions;
@ -2610,13 +2526,10 @@ void CheckMemoryLeakStructMember::checkStructVariable(const Token * const vartok
ignoredFunctions.insert("malloc"); ignoredFunctions.insert("malloc");
} }
if (vartok->varId() == 0)
return;
// Is struct variable a pointer? // Is struct variable a pointer?
if (vartok->strAt(-1) == "*") { if (variable->isPointer()) {
// Check that variable is allocated with malloc // Check that variable is allocated with malloc
if (!isMalloc(vartok)) if (!isMalloc(variable))
return; return;
} else if (!_tokenizer->isC()) { } else if (!_tokenizer->isC()) {
// For non-C code a destructor might cleanup members // For non-C code a destructor might cleanup members
@ -2625,7 +2538,7 @@ void CheckMemoryLeakStructMember::checkStructVariable(const Token * const vartok
// Check struct.. // Check struct..
unsigned int indentlevel2 = 0; unsigned int indentlevel2 = 0;
for (const Token *tok2 = vartok; tok2; tok2 = tok2->next()) { for (const Token *tok2 = variable->nameToken(); tok2 != variable->scope()->classEnd; tok2 = tok2->next()) {
if (tok2->str() == "{") if (tok2->str() == "{")
++indentlevel2; ++indentlevel2;
@ -2637,12 +2550,12 @@ void CheckMemoryLeakStructMember::checkStructVariable(const Token * const vartok
// Unknown usage of struct // Unknown usage of struct
/** @todo Check how the struct is used. Only bail out if necessary */ /** @todo Check how the struct is used. Only bail out if necessary */
else if (Token::Match(tok2, "[(,] %varid% [,)]", vartok->varId())) else if (Token::Match(tok2, "[(,] %varid% [,)]", variable->varId()))
break; break;
// Struct member is allocated => check if it is also properly deallocated.. // Struct member is allocated => check if it is also properly deallocated..
else if (Token::Match(tok2->previous(), "[;{}] %varid% . %var% = malloc|strdup|kmalloc (", vartok->varId())) { else if (Token::Match(tok2->previous(), "[;{}] %varid% . %var% = malloc|strdup|kmalloc (", variable->varId())) {
const unsigned int structid(vartok->varId()); const unsigned int structid(variable->varId());
const unsigned int structmemberid(tok2->tokAt(2)->varId()); const unsigned int structmemberid(tok2->tokAt(2)->varId());
// This struct member is allocated.. check that it is deallocated // This struct member is allocated.. check that it is deallocated
@ -2653,7 +2566,7 @@ void CheckMemoryLeakStructMember::checkStructVariable(const Token * const vartok
else if (tok3->str() == "}") { else if (tok3->str() == "}") {
if (indentlevel3 == 0) { if (indentlevel3 == 0) {
memoryLeak(tok3, vartok->str() + "." + tok2->strAt(2), Malloc); memoryLeak(tok3, variable->name() + "." + tok2->strAt(2), Malloc);
break; break;
} }
--indentlevel3; --indentlevel3;
@ -2683,7 +2596,7 @@ void CheckMemoryLeakStructMember::checkStructVariable(const Token * const vartok
// Deallocating the struct.. // Deallocating the struct..
else if (indentlevel2 == 0 && Token::Match(tok3, "free|kfree ( %varid% )", structid)) { else if (indentlevel2 == 0 && Token::Match(tok3, "free|kfree ( %varid% )", structid)) {
memoryLeak(tok3, vartok->str() + "." + tok2->strAt(2), Malloc); memoryLeak(tok3, variable->name() + "." + tok2->strAt(2), Malloc);
break; break;
} }
@ -2729,7 +2642,7 @@ void CheckMemoryLeakStructMember::checkStructVariable(const Token * const vartok
// Returning from function without deallocating struct member? // Returning from function without deallocating struct member?
if (!Token::Match(tok3, "return %varid% ;", structid) && if (!Token::Match(tok3, "return %varid% ;", structid) &&
!Token::Match(tok3, "return & %varid% .", structid)) { !Token::Match(tok3, "return & %varid% .", structid)) {
memoryLeak(tok3, vartok->str() + "." + tok2->strAt(2), Malloc); memoryLeak(tok3, variable->name() + "." + tok2->strAt(2), Malloc);
} }
break; break;
} }

View File

@ -41,6 +41,8 @@
class Token; class Token;
class Scope; class Scope;
class Function;
class Variable;
/// @addtogroup Core /// @addtogroup Core
/// @{ /// @{
@ -158,7 +160,7 @@ public:
AllocType functionReturnType(const Token *tok, std::list<const Token *> *callstack = NULL) const; AllocType functionReturnType(const Token *tok, std::list<const Token *> *callstack = NULL) const;
/** Function allocates pointed-to argument (a la asprintf)? */ /** Function allocates pointed-to argument (a la asprintf)? */
const char *functionArgAlloc(const Token *tok, unsigned int targetpar, AllocType &allocType) const; const char *functionArgAlloc(const Function *func, unsigned int targetpar, AllocType &allocType) const;
}; };
/// @} /// @}
@ -217,14 +219,6 @@ public:
*/ */
void checkReallocUsage(); void checkReallocUsage();
/**
* @brief %Check all variables in function scope
* @param tok The first '{' token of the function body
* @param tok1 The '(' token in the function declaration
* @param classmember Is this function a class member?
*/
void parseFunctionScope(const Token *tok, const Token *tok1, const bool classmember);
/** /**
* @brief %Check if there is a "!var" match inside a condition * @brief %Check if there is a "!var" match inside a condition
* @param tok first token to match * @param tok first token to match
@ -419,9 +413,9 @@ public:
private: private:
/** Is local variable allocated with malloc? */ /** Is local variable allocated with malloc? */
static bool isMalloc(const Token *vartok); static bool isMalloc(const Variable *variable);
void checkStructVariable(const Token * const vartok); void checkStructVariable(const Variable * const variable);
void getErrorMessages(ErrorLogger * /*errorLogger*/, const Settings * /*settings*/) const void getErrorMessages(ErrorLogger * /*errorLogger*/, const Settings * /*settings*/) const
{ } { }

View File

@ -2751,7 +2751,8 @@ private:
" return false;\n" " return false;\n"
" return true;\n" " return true;\n"
"}\n", false); "}\n", false);
ASSERT_EQUALS("[test.cpp:3]: (error) Common realloc mistake: \'m_options\' nulled but not freed upon failure\n", errout.str()); ASSERT_EQUALS("[test.cpp:3]: (error) Common realloc mistake: \'m_options\' nulled but not freed upon failure\n"
"[test.cpp:6]: (error) Memory leak: m_options\n", errout.str());
} }
void assign1() { void assign1() {