Fixed a bunch of memory leaks in tests, including:

- String traversal test data (now in a zone)
 - Debug message thread (now joined on exit)
 - Threading test threads (now joined on exit)
 - Changed message tests framework to cope with valgrind
Also, fixed a bug where we'd try to delete stack-allocated objects
when tearing down v8.  Good times.


git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@1622 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
This commit is contained in:
christian.plesner.hansen@gmail.com 2009-03-27 00:24:49 +00:00
parent d970e04e04
commit 9142c42df2
17 changed files with 109 additions and 31 deletions

View File

@ -2033,6 +2033,17 @@ class V8EXPORT V8 {
*/ */
static void ResumeProfiler(); static void ResumeProfiler();
/**
* Releases any resources used by v8 and stops any utility threads
* that may be running. Note that disposing v8 is permanent, it
* cannot be reinitialized.
*
* It should generally not be necessary to dispose v8 before exiting
* a process, this should happen automatically. It is only necessary
* to use if the process needs the resources taken up by v8.
*/
static bool Dispose();
private: private:
V8(); V8();

View File

@ -45,7 +45,7 @@ v8::Handle<v8::String> ReadFile(const char* name);
void ReportException(v8::TryCatch* handler); void ReportException(v8::TryCatch* handler);
int main(int argc, char* argv[]) { int RunMain(int argc, char* argv[]) {
v8::V8::SetFlagsFromCommandLine(&argc, argv, true); v8::V8::SetFlagsFromCommandLine(&argc, argv, true);
v8::HandleScope handle_scope; v8::HandleScope handle_scope;
// Create a template for the global object. // Create a template for the global object.
@ -95,11 +95,17 @@ int main(int argc, char* argv[]) {
return 1; return 1;
} }
} }
if (run_shell) RunShell(context);
return 0; return 0;
} }
int main(int argc, char* argv[]) {
int result = RunMain(argc, argv);
v8::V8::Dispose();
return result;
}
// Extracts a C string from a V8 Utf8Value. // Extracts a C string from a V8 Utf8Value.
const char* ToCString(const v8::String::Utf8Value& value) { const char* ToCString(const v8::String::Utf8Value& value) {
return *value ? *value : "<string conversion failed>"; return *value ? *value : "<string conversion failed>";

View File

@ -2272,6 +2272,12 @@ bool v8::V8::Initialize() {
} }
bool v8::V8::Dispose() {
i::V8::TearDown();
return true;
}
const char* v8::V8::GetVersion() { const char* v8::V8::GetVersion() {
return "1.1.4 (candidate)"; return "1.1.4 (candidate)";
} }

View File

@ -1737,6 +1737,15 @@ void Debugger::SetEventListener(Handle<Object> callback,
} }
void Debugger::TearDown() {
if (message_thread_ != NULL) {
message_thread_->Stop();
delete message_thread_;
message_thread_ = NULL;
}
}
void Debugger::SetMessageHandler(v8::DebugMessageHandler handler, void* data) { void Debugger::SetMessageHandler(v8::DebugMessageHandler handler, void* data) {
message_handler_ = handler; message_handler_ = handler;
message_handler_data_ = data; message_handler_data_ = data;
@ -1852,16 +1861,23 @@ void Debugger::StopAgent() {
DebugMessageThread::DebugMessageThread() DebugMessageThread::DebugMessageThread()
: host_running_(true), : host_running_(true),
command_queue_(kQueueInitialSize), command_queue_(kQueueInitialSize),
message_queue_(kQueueInitialSize) { message_queue_(kQueueInitialSize),
keep_running_(true) {
command_received_ = OS::CreateSemaphore(0); command_received_ = OS::CreateSemaphore(0);
message_received_ = OS::CreateSemaphore(0); message_received_ = OS::CreateSemaphore(0);
} }
// Does not free resources held by DebugMessageThread // Should only be done after the thread is done running.
// because this cannot be done thread-safely.
DebugMessageThread::~DebugMessageThread() { DebugMessageThread::~DebugMessageThread() {
delete command_received_;
delete message_received_;
} }
void DebugMessageThread::Stop() {
keep_running_ = false;
SendMessage(Vector<uint16_t>(NULL, 0));
Join();
}
// Puts an event coming from V8 on the queue. Creates // Puts an event coming from V8 on the queue. Creates
// a copy of the JSON formatted event string managed by the V8. // a copy of the JSON formatted event string managed by the V8.
@ -1909,7 +1925,7 @@ bool DebugMessageThread::SetEventJSONFromEvent(Handle<Object> event_data) {
void DebugMessageThread::Run() { void DebugMessageThread::Run() {
// Sends debug events to an installed debugger message callback. // Sends debug events to an installed debugger message callback.
while (true) { while (keep_running_) {
// Wait and Get are paired so that semaphore count equals queue length. // Wait and Get are paired so that semaphore count equals queue length.
message_received_->Wait(); message_received_->Wait();
Logger::DebugTag("Get message from event message_queue."); Logger::DebugTag("Get message from event message_queue.");

View File

@ -429,6 +429,7 @@ class Debugger {
bool auto_continue); bool auto_continue);
static void SetEventListener(Handle<Object> callback, Handle<Object> data); static void SetEventListener(Handle<Object> callback, Handle<Object> data);
static void SetMessageHandler(v8::DebugMessageHandler handler, void* data); static void SetMessageHandler(v8::DebugMessageHandler handler, void* data);
static void TearDown();
static void SetHostDispatchHandler(v8::DebugHostDispatchHandler handler, static void SetHostDispatchHandler(v8::DebugHostDispatchHandler handler,
void* data); void* data);
static void SendMessage(Vector<uint16_t> message); static void SendMessage(Vector<uint16_t> message);
@ -527,7 +528,7 @@ class LockingMessageQueue BASE_EMBEDDED {
class DebugMessageThread: public Thread { class DebugMessageThread: public Thread {
public: public:
DebugMessageThread(); // Called from API thread. DebugMessageThread(); // Called from API thread.
virtual ~DebugMessageThread(); // Never called. virtual ~DebugMessageThread();
// Called by V8 thread. Reports events from V8 VM. // Called by V8 thread. Reports events from V8 VM.
// Also handles command processing in stopped state of V8, // Also handles command processing in stopped state of V8,
// when host_running_ is false. // when host_running_ is false.
@ -554,6 +555,7 @@ class DebugMessageThread: public Thread {
// Check whether there are commands in the queue. // Check whether there are commands in the queue.
bool HasCommands() { return !command_queue_.IsEmpty(); } bool HasCommands() { return !command_queue_.IsEmpty(); }
void Stop();
bool host_running_; // Is the debugging host running or stopped? bool host_running_; // Is the debugging host running or stopped?
Semaphore* command_received_; // Non-zero when command queue is non-empty. Semaphore* command_received_; // Non-zero when command queue is non-empty.
@ -564,6 +566,7 @@ class DebugMessageThread: public Thread {
static const int kQueueInitialSize = 4; static const int kQueueInitialSize = 4;
LockingMessageQueue command_queue_; LockingMessageQueue command_queue_;
LockingMessageQueue message_queue_; LockingMessageQueue message_queue_;
bool keep_running_;
DISALLOW_COPY_AND_ASSIGN(DebugMessageThread); DISALLOW_COPY_AND_ASSIGN(DebugMessageThread);
}; };

View File

@ -62,7 +62,7 @@
// printing / etc in the flag parser code. We only do this for writable flags. // printing / etc in the flag parser code. We only do this for writable flags.
#elif defined(FLAG_MODE_META) #elif defined(FLAG_MODE_META)
#define FLAG_FULL(ftype, ctype, nam, def, cmt) \ #define FLAG_FULL(ftype, ctype, nam, def, cmt) \
{ Flag::TYPE_##ftype, #nam, &FLAG_##nam, &FLAGDEFAULT_##nam, cmt }, { Flag::TYPE_##ftype, #nam, &FLAG_##nam, &FLAGDEFAULT_##nam, cmt, false },
#define FLAG_READONLY(ftype, ctype, nam, def, cmt) #define FLAG_READONLY(ftype, ctype, nam, def, cmt)
#else #else

View File

@ -58,6 +58,7 @@ struct Flag {
void* valptr_; // Pointer to the global flag variable. void* valptr_; // Pointer to the global flag variable.
const void* defptr_; // Pointer to the default value. const void* defptr_; // Pointer to the default value.
const char* cmt_; // A comment about the flags purpose. const char* cmt_; // A comment about the flags purpose.
bool owns_ptr_; // Does the flag own its string value?
FlagType type() const { return type_; } FlagType type() const { return type_; }
@ -80,9 +81,17 @@ struct Flag {
return reinterpret_cast<double*>(valptr_); return reinterpret_cast<double*>(valptr_);
} }
const char** string_variable() const { const char* string_value() const {
ASSERT(type_ == TYPE_STRING); ASSERT(type_ == TYPE_STRING);
return reinterpret_cast<const char**>(valptr_); return *reinterpret_cast<const char**>(valptr_);
}
void set_string_value(const char *value, bool owns_ptr) {
ASSERT(type_ == TYPE_STRING);
const char **ptr = reinterpret_cast<const char **>(valptr_);
if (owns_ptr_ && *ptr != NULL) DeleteArray(*ptr);
*ptr = value;
owns_ptr_ = owns_ptr;
} }
JSArguments* args_variable() const { JSArguments* args_variable() const {
@ -125,7 +134,7 @@ struct Flag {
case TYPE_FLOAT: case TYPE_FLOAT:
return *float_variable() == float_default(); return *float_variable() == float_default();
case TYPE_STRING: { case TYPE_STRING: {
const char* str1 = *string_variable(); const char* str1 = string_value();
const char* str2 = string_default(); const char* str2 = string_default();
if (str2 == NULL) return str1 == NULL; if (str2 == NULL) return str1 == NULL;
if (str1 == NULL) return str2 == NULL; if (str1 == NULL) return str2 == NULL;
@ -151,7 +160,7 @@ struct Flag {
*float_variable() = float_default(); *float_variable() = float_default();
break; break;
case TYPE_STRING: case TYPE_STRING:
*string_variable() = string_default(); set_string_value(string_default(), false);
break; break;
case TYPE_ARGS: case TYPE_ARGS:
*args_variable() = args_default(); *args_variable() = args_default();
@ -197,7 +206,7 @@ static SmartPointer<const char> ToString(Flag* flag) {
buffer.Add("%f", FmtElm(*flag->float_variable())); buffer.Add("%f", FmtElm(*flag->float_variable()));
break; break;
case Flag::TYPE_STRING: { case Flag::TYPE_STRING: {
const char* str = *flag->string_variable(); const char* str = flag->string_value();
buffer.Add("%s", str ? str : "NULL"); buffer.Add("%s", str ? str : "NULL");
break; break;
} }
@ -389,17 +398,17 @@ int FlagList::SetFlagsFromCommandLine(int* argc,
*flag->float_variable() = strtod(value, &endp); *flag->float_variable() = strtod(value, &endp);
break; break;
case Flag::TYPE_STRING: case Flag::TYPE_STRING:
*flag->string_variable() = value; flag->set_string_value(value ? StrDup(value) : NULL, true);
break; break;
case Flag::TYPE_ARGS: { case Flag::TYPE_ARGS: {
int start_pos = (value == NULL) ? i : i - 1; int start_pos = (value == NULL) ? i : i - 1;
int js_argc = *argc - start_pos; int js_argc = *argc - start_pos;
const char** js_argv = NewArray<const char*>(js_argc); const char** js_argv = NewArray<const char*>(js_argc);
if (value != NULL) { if (value != NULL) {
js_argv[0] = value; js_argv[0] = StrDup(value);
} }
for (int k = i; k < *argc; k++) { for (int k = i; k < *argc; k++) {
js_argv[k - start_pos] = argv[k]; js_argv[k - start_pos] = StrDup(argv[k]);
} }
*flag->args_variable() = JSArguments(js_argc, js_argv); *flag->args_variable() = JSArguments(js_argc, js_argv);
i = *argc; // Consume all arguments i = *argc; // Consume all arguments
@ -491,11 +500,7 @@ int FlagList::SetFlagsFromString(const char* str, int len) {
// cleanup // cleanup
DeleteArray(argv); DeleteArray(argv);
// don't delete copy0 since the substrings DeleteArray(copy0);
// may be pointed to by FLAG variables!
// (this is a memory leak, but it's minor since this
// code is only used for debugging, or perhaps once
// during initialization).
return result; return result;
} }

View File

@ -407,6 +407,7 @@ FILE* Logger::logfile_ = NULL;
Profiler* Logger::profiler_ = NULL; Profiler* Logger::profiler_ = NULL;
Mutex* Logger::mutex_ = NULL; Mutex* Logger::mutex_ = NULL;
VMState* Logger::current_state_ = NULL; VMState* Logger::current_state_ = NULL;
VMState Logger::bottom_state_(OTHER);
SlidingStateWindow* Logger::sliding_state_window_ = NULL; SlidingStateWindow* Logger::sliding_state_window_ = NULL;
#endif // ENABLE_LOGGING_AND_PROFILING #endif // ENABLE_LOGGING_AND_PROFILING
@ -1017,7 +1018,7 @@ bool Logger::Setup() {
mutex_ = OS::CreateMutex(); mutex_ = OS::CreateMutex();
} }
current_state_ = new VMState(OTHER); current_state_ = &bottom_state_;
// as log is initialized early with V8, we can assume that JS execution // as log is initialized early with V8, we can assume that JS execution
// frames can never reach this point on stack // frames can never reach this point on stack
@ -1052,8 +1053,6 @@ void Logger::TearDown() {
profiler_ = NULL; profiler_ = NULL;
} }
// Deleting the current_state_ has the side effect of assigning to it(!).
while (current_state_) delete current_state_;
delete sliding_state_window_; delete sliding_state_window_;
delete ticker_; delete ticker_;

View File

@ -83,7 +83,7 @@ class LogMessageBuilder;
#endif #endif
class VMState { class VMState BASE_EMBEDDED {
#ifdef ENABLE_LOGGING_AND_PROFILING #ifdef ENABLE_LOGGING_AND_PROFILING
public: public:
explicit VMState(StateTag state); explicit VMState(StateTag state);
@ -252,6 +252,9 @@ class Logger {
// A stack of VM states. // A stack of VM states.
static VMState* current_state_; static VMState* current_state_;
// Singleton bottom or default vm state.
static VMState bottom_state_;
// SlidingStateWindow instance keeping a sliding window of the most // SlidingStateWindow instance keeping a sliding window of the most
// recent VM states. // recent VM states.
static SlidingStateWindow* sliding_state_window_; static SlidingStateWindow* sliding_state_window_;

View File

@ -417,6 +417,7 @@ class ThreadHandle::PlatformData : public Malloced {
case ThreadHandle::INVALID: thread_ = kNoThread; break; case ThreadHandle::INVALID: thread_ = kNoThread; break;
} }
} }
pthread_t thread_; // Thread handle for pthread. pthread_t thread_; // Thread handle for pthread.
}; };

View File

@ -111,6 +111,8 @@ void V8::TearDown() {
Heap::TearDown(); Heap::TearDown();
Logger::TearDown(); Logger::TearDown();
Debugger::TearDown();
has_been_setup_ = false; has_been_setup_ = false;
has_been_disposed_ = true; has_been_disposed_ = true;
} }

View File

@ -50,6 +50,12 @@ inline void* Zone::New(int size) {
} }
template <typename T>
T* Zone::NewArray(int length) {
return static_cast<T*>(Zone::New(length * sizeof(T)));
}
bool Zone::excess_allocation() { bool Zone::excess_allocation() {
return segment_bytes_allocated_ > zone_excess_limit_; return segment_bytes_allocated_ > zone_excess_limit_;
} }

View File

@ -58,6 +58,9 @@ class Zone {
// allocating new segments of memory on demand using malloc(). // allocating new segments of memory on demand using malloc().
static inline void* New(int size); static inline void* New(int size);
template <typename T>
static inline T* NewArray(int length);
// Delete all objects and free all memory allocated in the Zone. // Delete all objects and free all memory allocated in the Zone.
static void DeleteAll(); static void DeleteAll();

View File

@ -30,6 +30,7 @@
#include <cstring> #include <cstring>
#include <cstdio> #include <cstdio>
#include "cctest.h" #include "cctest.h"
#include "debug.h"
CcTest* CcTest::last_ = NULL; CcTest* CcTest::last_ = NULL;
@ -120,5 +121,6 @@ int main(int argc, char* argv[]) {
} }
if (print_run_count && tests_run != 1) if (print_run_count && tests_run != 1)
printf("Ran %i tests.\n", tests_run); printf("Ran %i tests.\n", tests_run);
v8::V8::Dispose();
return 0; return 0;
} }

View File

@ -154,7 +154,7 @@ class ApiTestFuzzer: public v8::internal::Thread {
class RegisterThreadedTest { class RegisterThreadedTest {
public: public:
explicit RegisterThreadedTest(CcTest::TestFunction* callback) explicit RegisterThreadedTest(CcTest::TestFunction* callback)
: callback_(callback) { : fuzzer_(NULL), callback_(callback) {
prev_ = first_; prev_ = first_;
first_ = this; first_ = this;
count_++; count_++;
@ -5098,6 +5098,10 @@ void ApiTestFuzzer::ContextSwitch() {
void ApiTestFuzzer::TearDown() { void ApiTestFuzzer::TearDown() {
fuzzing_ = false; fuzzing_ = false;
for (int i = 0; i < RegisterThreadedTest::count(); i++) {
ApiTestFuzzer *fuzzer = RegisterThreadedTest::nth(i)->fuzzer_;
if (fuzzer != NULL) fuzzer->Join();
}
} }

View File

@ -11,6 +11,7 @@
#include "factory.h" #include "factory.h"
#include "cctest.h" #include "cctest.h"
#include "zone-inl.h"
unsigned int seed = 123; unsigned int seed = 123;
@ -48,6 +49,8 @@ static const int SUPER_DEEP_DEPTH = 80 * 1024;
static void InitializeBuildingBlocks( static void InitializeBuildingBlocks(
Handle<String> building_blocks[NUMBER_OF_BUILDING_BLOCKS]) { Handle<String> building_blocks[NUMBER_OF_BUILDING_BLOCKS]) {
// A list of pointers that we don't have any interest in cleaning up.
// If they are reachable from a root then leak detection won't complain.
for (int i = 0; i < NUMBER_OF_BUILDING_BLOCKS; i++) { for (int i = 0; i < NUMBER_OF_BUILDING_BLOCKS; i++) {
int len = gen() % 16; int len = gen() % 16;
if (len > 14) { if (len > 14) {
@ -80,7 +83,7 @@ static void InitializeBuildingBlocks(
} }
case 2: { case 2: {
class Resource: public v8::String::ExternalStringResource, class Resource: public v8::String::ExternalStringResource,
public Malloced { public ZoneObject {
public: public:
explicit Resource(Vector<const uc16> string): data_(string.start()) { explicit Resource(Vector<const uc16> string): data_(string.start()) {
length_ = string.length(); length_ = string.length();
@ -92,7 +95,7 @@ static void InitializeBuildingBlocks(
const uc16* data_; const uc16* data_;
size_t length_; size_t length_;
}; };
uc16* buf = NewArray<uc16>(len); uc16* buf = Zone::NewArray<uc16>(len);
for (int j = 0; j < len; j++) { for (int j = 0; j < len; j++) {
buf[j] = gen() % 65536; buf[j] = gen() % 65536;
} }
@ -212,6 +215,7 @@ TEST(Traverse) {
InitializeVM(); InitializeVM();
v8::HandleScope scope; v8::HandleScope scope;
Handle<String> building_blocks[NUMBER_OF_BUILDING_BLOCKS]; Handle<String> building_blocks[NUMBER_OF_BUILDING_BLOCKS];
ZoneScope zone(DELETE_ON_EXIT);
InitializeBuildingBlocks(building_blocks); InitializeBuildingBlocks(building_blocks);
Handle<String> flat = ConstructBalanced(building_blocks); Handle<String> flat = ConstructBalanced(building_blocks);
FlattenString(flat); FlattenString(flat);
@ -303,6 +307,7 @@ TEST(Slice) {
InitializeVM(); InitializeVM();
v8::HandleScope scope; v8::HandleScope scope;
Handle<String> building_blocks[NUMBER_OF_BUILDING_BLOCKS]; Handle<String> building_blocks[NUMBER_OF_BUILDING_BLOCKS];
ZoneScope zone(DELETE_ON_EXIT);
InitializeBuildingBlocks(building_blocks); InitializeBuildingBlocks(building_blocks);
seed = 42; seed = 42;

View File

@ -41,6 +41,11 @@ class MessageTestCase(test.TestCase):
self.config = config self.config = config
self.mode = mode self.mode = mode
def IgnoreLine(self, str):
"""Ignore empty lines and valgrind output."""
if not str: return True
else: return str.startswith('==') or str.startswith('**')
def IsFailureOutput(self, output): def IsFailureOutput(self, output):
f = file(self.expected) f = file(self.expected)
# Skip initial '#' comment and spaces # Skip initial '#' comment and spaces
@ -58,7 +63,8 @@ class MessageTestCase(test.TestCase):
pattern = '^%s$' % pattern pattern = '^%s$' % pattern
patterns.append(pattern) patterns.append(pattern)
# Compare actual output with the expected # Compare actual output with the expected
outlines = [ s for s in output.stdout.split('\n') if s ] raw_lines = output.stdout.split('\n')
outlines = [ s for s in raw_lines if not self.IgnoreLine(s) ]
if len(outlines) != len(patterns): if len(outlines) != len(patterns):
return True return True
for i in xrange(len(patterns)): for i in xrange(len(patterns)):