// balst_stacktracetestallocator.h -*-C++-*- // ---------------------------------------------------------------------------- // NOTICE // // This component is not up to date with current BDE coding standards, and // should not be used as an example for new development. // ---------------------------------------------------------------------------- #ifndef INCLUDED_BALST_STACKTRACETESTALLOCATOR #define INCLUDED_BALST_STACKTRACETESTALLOCATOR #include <bsls_ident.h> BSLS_IDENT("$Id: $") //@PURPOSE: Provide a test allocator that reports the call stack for leaks. // //@CLASSES: // balst::StackTraceTestAllocator: allocator that reports call stack for leaks // //@DESCRIPTION: This component provides an instrumented allocator, // 'balst::StackTraceTestAllocator', that implements the // 'bdlma::ManagedAllocator' protocol. An object of this type records the call // stack for each allocation performed, and can report, either using the // 'reportBlocksInUse' method or implicitly at destruction, the call stack // associated with every allocated block that has not (yet) been freed. It is // optionally supplied a 'bslma::Allocator' at construction that it uses to // allocate memory. //.. // ,------------------------------. // ( balst::StackTraceTestAllocator ) // `------------------------------' // | ctor/dtor // | numBlocksInUse // | reportBlocksInUse // | setFailureHandler // V // ,----------------------. // ( bdlma::ManagedAllocator) // `----------------------' // | release // V // ,----------------. // ( bslma::Allocator ) // `----------------' // allocate // deallocate //.. // Note that allocation using a 'balst::StackTraceTestAllocator' is // deliberately incompatible with the default global 'new', 'malloc', 'delete', // and 'free'. Using 'delete' or 'free' to free memory supplied by this // allocator will corrupt the dynamic memory manager and also cause a memory // leak (and will be reported by purify as freeing mismatched memory, freeing // unallocated memory, or as a memory leak). Using 'deallocate' to free memory // supplied by global 'new' or 'malloc' will immediately cause an error to be // reported to the associated 'ostream' and the abort handler (which can be // configured to be a no-op) called. // ///Overhead / Efficiency ///--------------------- // There is some overhead to using this allocator. It's is slower than // 'bslma::NewDeleteAllocator' and uses more memory. It is, however, // comparable in speed and memory usage to 'bslma::TestAllocator'. The stack // trace stored for each allocation is stack pointers only, which are compact // and quick to obtain. Actual resolving of the stack pointer to subroutine // names and, on some platforms, source file names and line numbers, is // expensive but doesn't happen during allocation or deallocation and is put // off until a memory leak report is being generated. // // Note that the overhead increases and efficiency decreases as the // 'numRecordedFrames' argument to the constructor is increased. // ///Failure Handler ///--------------- // An object of type 'balst::StackTraceTestAllocator' always has a failure // handler associated with it. This is a configurable 'bdef::Functton' object // that will be called if any error condition is detected, after the error // condition is reported. By default, it is set to // 'balst::StackTraceTestAllocator::failAbort' which calls 'abort', but it may // be set by 'setFailureHandler' to another routine. If the client does not // want a core dump to occur, it is recommended they do: //.. // stackTraceTestAllocator.setFailureHandler( // &balst::StackTraceTestAllocator::failNoop); //.. // The stack trace test allocator is prepared for the failure handler to // return, throw (provided the client will catch the exception) or longjmp // without undefined behavior. // // If a memory is found to be outstanding during destruction, that is // considered a memory leak and a report is written. After the report, the // failure handler is called, and if the failure handler returns, the leaked // memory is then released. This means that if the failure handler throws or // longjmps in this case, the leaked memory will not be freed, and there will // be no way to release it afterward since the allocator will no longer exist. // ///Usage ///----- // In this example, we will define a class 'ShipsCrew' that does something, // but leaks memory, and then we will demonstrate the use of the stack trace // test allocator to locate the leak. // // First, we define 'ShipsCrew', a class that will read the names of a ship's // crew from a file at construction, and make the results available through // accessors: //.. // struct ShipsCrew { // // This struct will, at construction, read and parse a file describing // // the names of the crew of a ship. // // private: // // PRIVATE TYPES // struct CharStrLess { // // Functor to compare two 'const char *'s in alphabetical order. // // bool operator()(const char *a, const char *b) const // { // return bsl::strcmp(a, b) < 0; // } // }; // // typedef bsl::set<const char *, CharStrLess> NameSet; // // // DATA // const char *d_captain; // const char *d_firstMate; // const char *d_cook; // NameSet d_sailors; // bslma::Allocator *d_allocator_p; // // private: // // PRIVATE MANIPULATORS // void addSailor(const bsl::string& name); // // Add the specified 'name' to the set of sailor's names. // // Redundant names are ignored. // // const char *copy(const bsl::string& str); // // Allocate memory for a copy of the specified 'str' as a char // // array, copy the contents of 'str' into it, and return a pointer // // to the new copy. // // void free(const char **str); // // If '0 != str', deallocate '*str' using the allocator associated // // with this object and set '*str' to 0, otherwise do nothing. The // // behavior is undefined if '0 == str'. // // void setCaptain(const bsl::string& name); // // Set the name of the ship's captain to the specified 'name'. // // void setCook(const bsl::string& name); // // Set the name of the ship's cook to the specified 'name'. // // void setFirstMate(const bsl::string& name); // // Set the name of the ship's first mate to the specified 'name'. // // public: // // CREATORS // explicit // ShipsCrew(const char *crewFileName, // bslma::Allocator *basicAllocator = 0); // // Read the names of the ship's crew in from the file with the // // specified name 'crewFileName'. // // ~ShipsCrew(); // // Destroy this object and free memory. // // // ACCESSORS // const char *captain(); // // Return the captain's name. // // const char *cook(); // // Return the cook's name. // // const char *firstMate(); // // Return the first mate's name. // // const char *firstSailor(); // // Return the name of the sailor whose name is alphabetically the // // first in the list. // // const char *nextSailor(const char *currentSailor); // // Return the next sailor alphabetically after the specified // // 'currentSailor', or 0 if 'currentSailor' is the last in the list // // or not found. The behavior is undefined if // // '0 == currentSailor'. // }; //.. // Then, we implement the private manipulators of the class: //.. // PRIVATE MANIPULATORS // void ShipsCrew::addSailor(const bsl::string& name) // { // if (!d_sailors.count(name.c_str())) { // d_sailors.insert(copy(name)); // } // } // // const char *ShipsCrew::copy(const bsl::string& str) // { // char *result = (char *) d_allocator_p->allocate(str.length() + 1); // bsl::strcpy(result, str.c_str()); // return result; // } // // void ShipsCrew::free(const char **str) // { // assert(str); // // if (*str) { // d_allocator_p->deallocate(const_cast<char *>(*str)); // *str = 0; // } // } // // void ShipsCrew::setCaptain(const bsl::string& name) // { // free(&d_captain); // // d_captain = copy(name); // } // // void ShipsCrew::setCook(const bsl::string& name) // { // free(&d_cook); // // d_cook = copy(name); // } // // void ShipsCrew::setFirstMate(const bsl::string& name) // { // free(&d_firstMate); // // d_firstMate = copy(name); // } //.. // Next, we implement the creators: //.. // // CREATORS // ShipsCrew::ShipsCrew(const char *crewFileName, // bslma::Allocator *basicAllocator) // : d_captain(0) // , d_firstMate(0) // , d_cook(0) // , d_sailors( bslma::Default::allocator(basicAllocator)) // , d_allocator_p(bslma::Default::allocator(basicAllocator)) // { // bsl::ifstream input(crewFileName); // BSLS_ASSERT(!input.eof() && !input.bad()); // // bsl::string line(d_allocator_p); // // while (bsl::getline(input, line), !line.empty()) { // bsl::size_t colon = line.find(':'); // if (bsl::string::npos != colon) { // const bsl::string& field = line.substr(0, colon); // const bsl::string& name = line.substr(colon + 1); // // if (0 == bdlb::String::lowerCaseCmp(field, "captain")) { // setCaptain(name); // } // else if (0 == bdlb::String::lowerCaseCmp(field, "first mate")){ // setFirstMate(name); // } // else if (0 == bdlb::String::lowerCaseCmp(field, "cook")) { // setCook(name); // } // else if (0 == bdlb::String::lowerCaseCmp(field, "sailor")) { // addSailor(name); // } // else { // cerr << "Unrecognized field '" << field << "' in line '" << // line << "'\n"; // } // } // else { // cerr << "Garbled line '" << line << "'\n"; // } // } // } // // ShipsCrew::~ShipsCrew() // { // free(&d_captain); // free(&d_firstMate); // // // Note that deallocating the strings will invalidate 'd_sailors' -- // // any manipulation of 'd_sailors' other than destruction after this // // would lead to undefined behavior. // // const NameSet::iterator end = d_sailors.end(); // for (NameSet::iterator it = d_sailors.begin(); end != it; ++it) { // d_allocator_p->deallocate(const_cast<char *>(*it)); // } // } //.. // Then, we implement the public accessors: //.. // ACCESSORS // const char *ShipsCrew::captain() // { // return d_captain ? d_captain : ""; // } // // const char *ShipsCrew::cook() // { // return d_cook ? d_cook : ""; // } // // const char *ShipsCrew::firstMate() // { // return d_firstMate ? d_firstMate : ""; // } // // const char *ShipsCrew::firstSailor() // { // NameSet::iterator it = d_sailors.begin(); // return d_sailors.end() == it ? 0 : *it; // } // // const char *ShipsCrew::nextSailor(const char *currentSailor) // { // assert(currentSailor); // NameSet::iterator it = d_sailors.find(currentSailor); // if (d_sailors.end() != it) { // ++it; // } // return d_sailors.end() == it ? 0 : *it; // } //.. // Next, in 'main', we create our file './shipscrew.txt' describing the crew of // the ship. Note that the order of crew members is not important: //.. // { // bsl::ofstream outFile("shipscrew.txt"); // // outFile << "sailor:Mitch Sandler\n" // << "sailor:Ben Lampert\n" // << "cook:Bob Jones\n" // << "captain:Steve Miller\n" // << "sailor:Daniel Smith\n" // << "first mate:Sally Chandler\n" // << "sailor:Joe Owens\n"; // } //.. // Then, we set up a test case to test our 'ShipsCrew' class. We do not use // the stack trace test allocator yet, we just use a 'bslma::TestAllocator' to // get memory usage statistics and determine whether any leakage occurred. //.. // { // bslma::TestAllocator ta("Bslma Test Allocator"); // bslma::TestAllocatorMonitor tam(&ta); // // { // ShipsCrew crew("shipscrew.txt", &ta); // assert(tam.isInUseUp()); // assert(tam.isTotalUp()); // // if (verbose) { // cout << "Captain: " << crew.captain() << // "\nFirst Mate: " << crew.firstMate() << // "\nCook: " << crew.cook() << endl; // for (const char *sailor = crew.firstSailor(); sailor; // sailor = crew.nextSailor(sailor)) { // cout << "Sailor: " << sailor << endl; // } // } // } // // int bytesLeaked = ta.numBytesInUse(); // if (bytesLeaked > 0) { // cout << bytesLeaked << " bytes of memory were leaked!\n"; // } // } //.. // The program generates the following output in non-verbose mode, telling us // that one segment of 10 bytes was leaked: //.. // 10 bytes of memory were leaked! // MEMORY_LEAK from Bslma Test Allocator: // Number of blocks in use = 1 // Number of bytes in use = 10 //.. // Next, we would like to use stack trace test allocator to tell us WHERE the // memory leak is, but we have a problem: our test case not only uses // 'bslma::TestAllocator', but it calls the 'numBytesInUse' accessor, which is // not available from stack trace test allocator. We are also using // 'bslma::TestAllocatorMonitor', which will only work with // 'bslma::TestAllocator'. So if we were to just substitute the stack trace // test allocator for the bslma test allocator, it would break our test case in // several ways. To instrument our test with a minimal change to the code, we // create a stack test test allocator and feed that allocator to the // constructor to bslma test allocator. The rest of our example will now work // without modification. (Note that it is important to call // 'ta.setNoAbort(true)' when we use this method, otherwise the bslma test // allocator will bomb out before the destructor for 'stta' is able to generate // its report). //.. // { // balst::StackTraceTestAllocator stta; // stta.setName("stta"); // stta.setFailureHandler(&stta.failNoop); // // bslma::TestAllocator ta("Bslma Test Allocator", &stta); // ta.setNoAbort(true); // // // the rest of the test case after this is totally unchanged // // bslma::TestAllocatorMonitor tam(&ta); // // { // ShipsCrew crew("shipscrew.txt", &ta); // assert(tam.isInUseUp()); // assert(tam.isTotalUp()); // // if (verbose) { // cout << "Captain: " << crew.captain() << // "\nFirst Mate: " << crew.firstMate() << // "\nCook: " << crew.cook() << endl; // for (const char *sailor = crew.firstSailor(); sailor; // sailor = crew.nextSailor(sailor)) { // cout << "Sailor: " << sailor << endl; // } // } // } // // int bytesLeaked = ta.numBytesInUse(); // if (bytesLeaked > 0) { // cout << bytesLeaked << " bytes of memory were leaked!\n"; // } // } //.. // Now, this generates the following report: //.. // 10 bytes of memory were leaked! // MEMORY_LEAK from Bslma Test Allocator: // Number of blocks in use = 1 // Number of bytes in use = 10 // =========================================================================== // Error: memory leaked: // 1 block(s) in allocator 'stta' in use. // Block(s) allocated from 1 trace(s). // --------------------------------------------------------------------------- // Allocation trace 1, 1 block(s) in use. // Stack trace at allocation time: // (0): BloombergLP::balst::StackTraceTestAllocator::allocate(int)+0x17d at 0x // 805e741 in balst_stacktracetestallocator.t.dbg_exc_mt // (1): BloombergLP::bslma::TestAllocator::allocate(int)+0x12c at 0x8077398 in // balst_stacktracetestallocator.t.dbg_exc_mt // (2): ShipsCrew::copy(bsl::basic_string<char, std::char_traits<char>, bsl::a // llocator<char> > const&)+0x31 at 0x804c3db in balst_stacktracetestallocator // .t.dbg_exc_mt // (3): ShipsCrew::setCook(bsl::basic_string<char, std::char_traits<char>, bsl // ::allocator<char> > const&)+0x2d at 0x804c4c1 in balst_stacktracetestalloca // tor.t.dbg_exc_mt // (4): ShipsCrew::ShipsCrew(char const*, BloombergLP::bslma::Allocator*)+0x23 // 4 at 0x804c738 in balst_stacktracetestallocator.t.dbg_exc_mt // (5): main+0x53c at 0x804d55e in balst_stacktracetestallocator.t.dbg_exc_mt // (6): __libc_start_main+0xdc at 0x182e9c in /lib/libc.so.6 // (7): --unknown-- at 0x804c1d1 in balst_stacktracetestallocator.t.dbg_exc_mt //.. // Finally, Inspection shows that frame (3) of the stack trace from the // allocation of the leaked segment was from 'ShipsCrew::setCook'. Inspection // of the code shows that we neglected to free 'd_cook' in the destructor and // we can now easily fix our leak. #include <balscm_version.h> #include <bslmt_mutex.h> #include <bdlma_managedallocator.h> #include <bslma_allocator.h> #include <bsls_atomic.h> #include <bsl_cstddef.h> #include <bsl_functional.h> #include <bsl_iosfwd.h> namespace BloombergLP { namespace balst { // ============================= // class StackTraceTestAllocator // ============================= class StackTraceTestAllocator : public bdlma::ManagedAllocator { // This class defines a concrete "test" allocator mechanism that implements // the 'bdlma::ManagedAllocator' protocol, and provides instrumentation to // track the set of all blocks allocated by this allocator that have yet to // be freed. At any time it can produce a report about such blocks, // listing for each place that any unfreed blocks were allocated //: o the number of unfreed blocks allocated at that place //: o the stack trace at that place // The allocator will also detect redundant frees of the same block, and // frees by the wrong allocator. The client can choose whether such // violations are handled by a core dump, or merely a report being written. // // Note that, unlike many other allocators, this allocator does DOES NOT // rely on the currently installed default allocator (see 'bslma_default') // at all, but instead -- by default -- uses 'MallocFreeAllocator' // singleton, which in turn calls the C Standard Library functions 'malloc' // and 'free' as needed. Clients may, however, override this allocator by // supplying (at construction) any other allocator implementing the // 'bslma::Allocator' protocol. public: // PUBLIC TYPES typedef bsl::function<void()> FailureHandler; // Type of functor called by this object to handle failures. // Note that this can be set and accessed using th // 'setFailureHandler' and 'failureHandler' methods // respectively. private: // PRIVATE TYPES enum AllocatorMagic { k_STACK_TRACE_TEST_ALLOCATOR_MAGIC = 1335775331 }; struct BlockHeader; // information stored in // each block (defined in // .cpp) // DATA AllocatorMagic d_magic; // magic # to identify type // of memory allocator bsls::AtomicInt d_numBlocksInUse; // number of allocated // blocks currently unfreed BlockHeader *d_blocks; // list of allocated, // unfreed blocks mutable bslmt::Mutex d_mutex; // mutex used to synchronize // access to this object const char *d_name; // name of this allocator FailureHandler d_failureHandler; // function we are to call // on errors. The default // handler will call // 'abort'. const int d_maxRecordedFrames; // max number of stack trace // frames to store in each // block; may be larger than // the number of frames // requested to the ctor due // to ignored frames const int d_traceBufferLength; // length, in pointers, of // area for storing stack // traces; may be larger // than // 'd_maxRecordedFrames' due // to alignment // considerations bsl::ostream *d_ostream; // stream to which // diagnostics are to be // written; held, not owned bool d_demangleFlag; // if 'true', demangling of // symbol names is attempted bslma::Allocator *d_allocator_p; // held, not owned private: // NOT IMPLEMENTED StackTraceTestAllocator(const StackTraceTestAllocator&); StackTraceTestAllocator& operator=( const StackTraceTestAllocator&); private: // PRIVATE ACCESSORS int checkBlockHeader(const BlockHeader *blockHdr) const; // Return 0 if the block specified by 'blockHdr' was allocated with // this allocator, is not corrupted, and has not yet been freed; report // diagnostics to '*d_ostream' and return a non-zero value otherwise. public: // CLASS METHODS static void failAbort(); // Calls 'bsl::abort()', 'd_failureHandler' is initialized to this // value by all constructors. Note that in ALL failure situations, // errors or warnings will be written to the 'ostream' associated with // this object prior to the failure handler call. static void failNoop(); // Does nothing. 'setFailureHandler' may be called with this function, // in which case this allocator object, when a failure occurs, will // recover rather than abort. Note that in ALL failure situations, // errors or warnings will be written to the 'ostream' associated with // this object prior to the failure handler call. // CREATORS explicit StackTraceTestAllocator(bslma::Allocator *basicAllocator = 0); explicit StackTraceTestAllocator(int numRecordedFrames, bslma::Allocator *basicAllocator = 0); // Create a test allocator. Optionally specify 'numRecordedFrames', // the number of stack trace frame pointers to be saved for every // allocation. Specifying a larger value of 'numRecordedFrames' means // that stack traces, when given, will be more complete, but will also // mean that both more CPU time and more memory per allocation will be // consumed. If 'numRecordedFrames' is not specified, a value of '12' // will be assumed. Optionally specify 'basicAllocator', the allocator // from which memory will be provided. If 'basicAllocator' is 0, the // 'MallocFreeAllocator' singleton is used. Associate 'bsl::cerr' with // this object for error diagnostic output, which may be changed by // calling the 'setOstream' manipulator. Set the // 'demanglingPreferringFlag' attribute to 'true', which may be changed // using the 'setDemanglingPreferredFlag' manipulator. The behavior is // undefined if 'numRecordedFrames < 2'. virtual ~StackTraceTestAllocator(); // Destroy this allocator. Report any memory leaks to the 'ostream' // that was supplied at construction. If no memory leaks are observed, // nothing is written to the output 'ostream'. Call the failure // handler if 'numBlocksInUse() > 0'. Note that a report of // outstanding memory blocks is written to 'ostream' before the failure // handler is called, and if the failure handler returns, all // outstanding memory blocks will be released. // MANIPULATORS virtual void *allocate(size_type size); // Return a newly allocated block of memory of the specified positive // 'size' (in bytes). If 'size' is 0, a null pointer is returned with // no other other effect. Otherwise, invoke the 'allocate' method of // the allocator supplied at construction and record the returned block // in order to be able to report leaked blocks upon destruction. virtual void deallocate(void *address); // Return the memory block at the specified 'address' back to this // allocator. If 'address' is 0, this function has no effect. // Otherwise, if the memory at 'address' is consistent with being // allocated from this test allocator, deallocate it using the // underlying allocator and delete it from the data structures keeping // track of blocks in use'. If 'address' is not zero and is not the // address of a block allocated with this allocator (or if it is being // deallocated a second time), write an error message and call the // failure handler. virtual void release(); // Deallocate all memory held by this allocator. void setDemanglingPreferredFlag(bool value); // Set the 'demanglingPreferredFlag' attribute, which is used to // determine whether demangling of symbols is to be attempted when // generating diagnostics, to the specified 'value'. The default value // of the flag is 'true'. However the flag is ignored on some // platforms; demangling never happens on some platforms and always // happens on others. void setFailureHandler(const FailureHandler& func); // Set the failure handler associated with this allocator object to the // specified 'func'. Upon construction, the function 'failAbort' is // associated with this object by default. Note that 'func' will be // called by this object's destructor if memory is leaked, so it is // important that it not throw. Note that in ALL failure situations, // errors or warnings will be written to the 'ostream' associated with // this object prior to the call to the failure handler. void setName(const char * name); // Set the name of this allocator to the specified 'name'. If // 'setName' is never called, the name of the allocator is "<unnamed>". // Note that the lifetime of 'name' must exceed the lifetime of this // object. void setOstream(bsl::ostream *ostream); // Set the stream to which diagnostics will be written to the specified // 'ostream'. If 'setOstream' is never called, diagnostics will be // written to 'bsl::cerr'. // ACCESSORS const FailureHandler& failureHandler() const; // Return a reference to the function that will be called when a // failure is observered. bsl::size_t numBlocksInUse() const; // Return the number of blocks currently allocated from this object. void reportBlocksInUse(bsl::ostream *ostream = 0) const; // Write a report to the specified 'ostream', reporting the unique // call-stacks for each block that has been allocated and has not yet // been freed. If 'ostream' is not specified, the value of 'ostream' // passed to the last call to 'setOstream' will be used. If // 'setOstream' was never called, 'bsl::cerr' will be used. }; // ----------------------------- // class StackTraceTestAllocator // ----------------------------- inline const StackTraceTestAllocator::FailureHandler& StackTraceTestAllocator::failureHandler() const { return d_failureHandler; } inline bsl::size_t StackTraceTestAllocator::numBlocksInUse() const { return d_numBlocksInUse; } } // close package namespace } // close enterprise namespace #endif // ---------------------------------------------------------------------------- // Copyright 2015 Bloomberg Finance L.P. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // ----------------------------- END-OF-FILE ----------------------------------