DebugAllocator.cpp
Go to the documentation of this file.
/* Frobby: Software for monomial ideal computations.
Copyright (C) 2007 Bjarke Hammersholt Roune (www.broune.com)
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see http://www.gnu.org/licenses/.
*/
#include "stdinc.h"
#include "DebugAllocator.h"
#include "main.h"
#include "error.h"
#include "Ideal.h"
#include "test/TestCase.h"
#include "IOHandler.h"
#include <limits>
// Must not include code below if not debugging. For one, then Frobby
// cannot be built as a library as this code calls main.
// The code only applies to debug builds anyway.
#ifdef DEBUG
#undef new
static const size_t AllocationLimitsTryFirst = 500;
static const size_t AllocationLimitsTryLast = 200;
static const size_t AllocationLimitsStepRatio = 1000;
DebugAllocator& DebugAllocator::getSingleton() {
static DebugAllocator singleton;
return singleton;
}
void DebugAllocator::rewindInput() {
if (_inputFile != "")
if (!freopen(_inputFile.c_str(), "r", stdin))
reportError("Could not open file \"" + _inputFile + "\" for input.");
}
int DebugAllocator::runDebugMain(int argc, const char** argv) {
processDebugOptions(argc, argv);
// Run all the way through once to count the number of allocations
// and to produce the correct output. Note that the number of
// allocations on subsequent runs can be a bit less due to static
// caches.
rewindInput();
_allocationCount = 0;
int exitCode = frobbyMain(argc, argv);
size_t maxAllocations = _allocationCount;
if (_actionIsTest || !_debugAllocation)
return exitCode;
fclose(stdout); // Output has already been produced.
fputs("DEBUG: Now debugging out-of-memory conditions.\n", stderr);
fprintf(stderr, "DEBUG: There are %i allocations in total on this input.\n",
(int)maxAllocations);
// If maxAllocations is small then just try every possible limit.
if (maxAllocations < AllocationLimitsTryFirst + AllocationLimitsTryLast) {
fputs("DEBUG: This is few, so am now running through "
"every possible condition.\n", stderr);
for (size_t limit = 0; limit < maxAllocations; ++limit)
runWithLimit(argc, argv, limit);
return ExitCodeSuccess;
}
fprintf(stderr, "DEBUG: Trying the first %i allocations.\n",
(int)AllocationLimitsTryFirst);
for (size_t limit = 0; limit < AllocationLimitsTryFirst; ++limit)
runWithLimit(argc, argv, limit);
fprintf(stderr, "DEBUG: Trying the last %i allocations.\n",
(int)AllocationLimitsTryLast);
for (size_t limit = 0; limit < AllocationLimitsTryLast; ++limit)
runWithLimit(argc, argv, maxAllocations - limit);
size_t limitsLeft =
maxAllocations - AllocationLimitsTryFirst - AllocationLimitsTryLast;
size_t stepSize = limitsLeft / AllocationLimitsStepRatio + 1;
fprintf(stderr,
"DEBUG: Going through the %i remaining allocations "
"with steps of size %i.\n",
(int)limitsLeft, (int)stepSize);
for (size_t limit = AllocationLimitsTryFirst;
limit < maxAllocations - AllocationLimitsTryLast; limit += stepSize)
runWithLimit(argc, argv, limit);
return ExitCodeSuccess;
}
void DebugAllocator::runWithLimit(int argc, const char** argv, size_t limit) {
if (_detailAllocation)
fprintf(stderr, "DEBUG: Trying allocation limit of %i\n", (int)limit);
// To make each run more similar.
Ideal::clearStaticCache();
_limitAllocation = true;
_allocationLimit = limit;
_allocationCount = 0;
// We use this to distinguish genuinely running out of memory from
// our artificial limit-induced throwing of bad_alloc.
_expectBadAllocException = false;
rewindInput();
try {
frobbyMain(argc, argv);
} catch (const bad_alloc&) {
if (!_expectBadAllocException)
throw;
}
_limitAllocation = false;
}
void DebugAllocator::processDebugOptions(int& argc, const char**& argv) {
const char* originalArgvZero = argv[0];
while (true) {
if (argc <= 1 || argv[1][0] != '_')
break;
string option(argv[1] + 1);
++argv;
--argc;
if (option == "debugAlloc")
_debugAllocation = true;
else if (option == "detailAlloc")
_detailAllocation = true;
else if (option == "input") {
if (argc <= 1)
reportError("Debug option _input requries an argument.");
_inputFile = argv[1];
++argv;
--argc;
} else
reportError("Unknown debug option \"" + option + "\".");
}
if (argc >= 2 && string(argv[1]) == "test")
_actionIsTest = true;
argv[0] = originalArgvZero;
}
void DebugAllocator::runTest(TestCase& test, const string& name) {
test.run(name.c_str(), true);
if (!_debugAllocation)
return;
_limitAllocation = true;
for (_allocationLimit = 0; _allocationLimit < numeric_limits<size_t>::max();
++_allocationLimit) {
if (_detailAllocation)
fprintf(stderr, "DEBUG: Trying test allocation limit of %i\n",
(int)_allocationLimit);
// To make each run more similar.
Ideal::clearStaticCache();
_allocationCount = 0;
_expectBadAllocException = false;
rewindInput();
try {
test.run(name.c_str(), false);
// At this point test has completed within allocation limit.
break;
} catch (const bad_alloc&) {
if (!_expectBadAllocException)
throw;
// Continue and try next limit.
}
}
_limitAllocation = false;
}
void* DebugAllocator::allocate(size_t size) {
return allocate(size, 0, 0);
}
void* DebugAllocator::allocate(size_t size,
const char* file,
size_t lineNumber) {
if (_detailAllocation) {
if (file != 0)
fprintf(stderr, "DEBUG: Allocating %i bytes at %s:%i.\n",
(int)size, file, (int)lineNumber);
if (file == 0)
fprintf(stderr, "DEBUG: Allocating %i bytes at an unknown point.\n",
(int)size);
}
if (_limitAllocation && _allocationCount >= _allocationLimit) {
if (_detailAllocation)
fputs("DEBUG: Throwing bad_alloc due to artifically imposed limit.\n",
stderr);
_expectBadAllocException = true;
throw bad_alloc();
}
++_allocationCount;
void* p = malloc(size);
if (p == 0)
throw bad_alloc();
return p;
}
DebugAllocator::DebugAllocator():
_debugAllocation(false),
_detailAllocation(false),
_limitAllocation(false),
_expectBadAllocException(false),
_actionIsTest(false),
_allocationCount(0),
_allocationLimit(0) {
}
void* operator new(size_t size, const char* file, size_t lineNumber)
throw(bad_alloc) {
return DebugAllocator::getSingleton().allocate(size, file, lineNumber);
}
void* operator new[](size_t size, const char* file, size_t lineNumber)
throw(bad_alloc) {
return DebugAllocator::getSingleton().allocate(size, file, lineNumber);
}
void* operator new(size_t size) throw (bad_alloc) {
return DebugAllocator::getSingleton().allocate(size);
}
void* operator new[](size_t size) throw (bad_alloc) {
return DebugAllocator::getSingleton().allocate(size);
}
void operator delete(void* buffer) throw() {
free(buffer);
}
void operator delete[](void* buffer) throw() {
free(buffer);
}
void operator delete(void* buffer, const char* file, size_t line) {
free(buffer);
}
void operator delete[](void* buffer, const char* file, size_t line) {
free(buffer);
}
#endif
DebugAllocator.h
IOHandler.h
Ideal.h
TestCase.h
Ideal::clearStaticCache
static void clearStaticCache()
Ideal caches memory allocated with new internally and reuses it to avoid calling new all the time.
Definition Ideal.cpp:810
TestCase
Represents a test case, which is usually created through a macro that defines a subclass.
Definition TestCase.h:29
TestCase::run
virtual void run(const char *nameOfTest, bool printDots)=0
Run the test and record the name of the test as __nameOfTest.
reportError
void reportError(const string &errorMsg)
Definition error.cpp:23
error.h
frobbyMain
int frobbyMain(int argc, const char **argv)
This function runs the Frobby console interface.
Definition main.cpp:35
main.h
ExitCodeSuccess
static const int ExitCodeSuccess
Definition main.h:33
stdinc.h
This header file includes common definitions and is included as the first line of code in every imple...