diff --git a/CMakeLists.txt b/CMakeLists.txt index 3910a28..c2d269c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -61,3 +61,4 @@ foreach(FILE ${ALL_FILES}) endforeach() set_property(TEST three_regions.vpy PROPERTY WILL_FAIL true) +set_property(TEST leak_with_global.vpy PROPERTY WILL_FAIL true) diff --git a/src/lang/interpreter.cc b/src/lang/interpreter.cc index 631f3c7..894cbd4 100644 --- a/src/lang/interpreter.cc +++ b/src/lang/interpreter.cc @@ -125,8 +125,8 @@ namespace verona::interpreter std::cout << node->location().view() << std::endl << std::endl; // Mermaid output - std::vector edges{{nullptr, "?", frame()}}; - ui->output(edges, std::string(node->location().view())); + std::vector roots{frame()}; + ui->output(roots, std::string(node->location().view())); // Continue return ExecNext{}; @@ -464,12 +464,13 @@ namespace verona::interpreter out.open(path); } - void output(std::vector& edges, std::string message) + void + output(std::vector& roots, std::string message) { out << "```" << std::endl; out << message << std::endl; out << "```" << std::endl; - rt::ui::mermaid(edges, out); + rt::ui::mermaid(roots, out); if (interactive) { out.close(); diff --git a/src/rt/core.h b/src/rt/core.h index d3c86e5..e4acfc0 100644 --- a/src/rt/core.h +++ b/src/rt/core.h @@ -3,14 +3,13 @@ namespace rt::core { - class PrototypeObject : public objects::DynObject { std::string name; public: PrototypeObject(std::string name_, objects::DynObject* prototype = nullptr) - : objects::DynObject(prototype, true), name(name_) + : objects::DynObject(prototype), name(name_) {} std::string get_name() @@ -21,15 +20,19 @@ namespace rt::core } }; - PrototypeObject framePrototypeObject{"Frame"}; + inline PrototypeObject* framePrototypeObject() + { + static PrototypeObject* proto = new PrototypeObject("Frame"); + return proto; + } class FrameObject : public objects::DynObject { - FrameObject() : objects::DynObject(&framePrototypeObject, true) {} + FrameObject() : objects::DynObject(framePrototypeObject(), true) {} public: FrameObject(objects::DynObject* parent_frame) - : objects::DynObject(&framePrototypeObject) + : objects::DynObject(framePrototypeObject()) { if (parent_frame) { @@ -45,9 +48,17 @@ namespace rt::core } }; - PrototypeObject funcPrototypeObject{"Function"}; - PrototypeObject bytecodeFuncPrototypeObject{ - "BytecodeFunction", &funcPrototypeObject}; + inline PrototypeObject* funcPrototypeObject() + { + static PrototypeObject* proto = new PrototypeObject("Function"); + return proto; + } + inline PrototypeObject* bytecodeFuncPrototypeObject() + { + static PrototypeObject* proto = + new PrototypeObject("BytecodeFunction", funcPrototypeObject()); + return proto; + } class FuncObject : public objects::DynObject { @@ -63,7 +74,7 @@ namespace rt::core public: BytecodeFuncObject(verona::interpreter::Bytecode* body_) - : FuncObject(&bytecodeFuncPrototypeObject), body(body_) + : FuncObject(bytecodeFuncPrototypeObject()), body(body_) {} ~BytecodeFuncObject() { @@ -77,15 +88,19 @@ namespace rt::core } }; - PrototypeObject stringPrototypeObject{"String"}; + inline PrototypeObject* stringPrototypeObject() + { + static PrototypeObject* proto = new PrototypeObject("String"); + return proto; + } class StringObject : public objects::DynObject { std::string value; public: - StringObject(std::string value_, bool global = false) - : objects::DynObject(&stringPrototypeObject, global), value(value_) + StringObject(std::string value_) + : objects::DynObject(stringPrototypeObject()), value(value_) {} std::string get_name() @@ -106,12 +121,23 @@ namespace rt::core } }; - StringObject TrueObject{"True", true}; - StringObject FalseObject{"False", true}; + inline StringObject* trueObject() + { + static StringObject* val = new StringObject("True"); + return val; + } + inline StringObject* falseObject() + { + static StringObject* val = new StringObject("False"); + return val; + } // The prototype object for iterators - // TODO put some stuff in here? - PrototypeObject keyIterPrototypeObject{"KeyIterator"}; + inline PrototypeObject* keyIterPrototypeObject() + { + static PrototypeObject* proto = new PrototypeObject("KeyIterator"); + return proto; + } class KeyIterObject : public objects::DynObject { @@ -120,7 +146,7 @@ namespace rt::core public: KeyIterObject(std::map& fields) - : objects::DynObject(&keyIterPrototypeObject), + : objects::DynObject(keyIterPrototypeObject()), iter(fields.begin()), iter_end(fields.end()) {} @@ -147,4 +173,19 @@ namespace rt::core return this; } }; + + inline std::set* globals() + { + static std::set* globals = + new std::set{ + framePrototypeObject(), + funcPrototypeObject(), + bytecodeFuncPrototypeObject(), + stringPrototypeObject(), + keyIterPrototypeObject(), + trueObject(), + falseObject(), + }; + return globals; + } } // namespace rt::core diff --git a/src/rt/objects/dyn_object.h b/src/rt/objects/dyn_object.h index 5306e53..ac6e2b5 100644 --- a/src/rt/objects/dyn_object.h +++ b/src/rt/objects/dyn_object.h @@ -28,7 +28,7 @@ namespace rt::objects friend class Reference; friend objects::DynObject* rt::make_iter(objects::DynObject* obj); friend void - rt::ui::mermaid(std::vector& roots, std::ostream& out); + rt::ui::mermaid(std::vector& roots, std::ostream& out); friend void destruct(DynObject* obj); friend void dealloc(DynObject* obj); template @@ -170,10 +170,10 @@ namespace rt::objects public: // prototype is borrowed, the caller does not need to provide an RC. - DynObject(DynObject* prototype_ = nullptr, bool global = false) + DynObject(DynObject* prototype_ = nullptr, bool first_frame = false) : prototype(prototype_) { - if (!global) + if (!first_frame) { count++; all_objects.insert(this); @@ -181,9 +181,10 @@ namespace rt::objects region = local_region; local_region->objects.insert(this); } + if (prototype != nullptr) prototype->change_rc(1); - std::cout << "Allocate: " << get_name() << std::endl; + std::cout << "Allocate: " << this << std::endl; } // TODO This should use prototype lookup for the destructor. @@ -198,7 +199,10 @@ namespace rt::objects // RC is zero. if (change_rc(0) != 0 && matched != 0) { - ui::error("Object still has references"); + std::stringstream stream; + stream << this; + stream << " still has references"; + ui::error(stream.str()); } auto r = get_region(this); diff --git a/src/rt/rt.cc b/src/rt/rt.cc index 439e87a..6c46386 100644 --- a/src/rt/rt.cc +++ b/src/rt/rt.cc @@ -3,7 +3,9 @@ #include "core.h" #include "objects/dyn_object.h" +#include #include +#include #include #include @@ -54,7 +56,7 @@ namespace rt { // TODO Add some checking. This is need to lookup the correct function in // the prototype chain. - if (key->get_prototype() != &core::stringPrototypeObject) + if (key->get_prototype() != core::stringPrototypeObject()) { ui::error("Key must be a string."); } @@ -96,11 +98,11 @@ namespace rt objects::DynObject* get_true() { - return &core::TrueObject; + return core::trueObject(); } objects::DynObject* get_false() { - return &core::FalseObject; + return core::falseObject(); } void add_reference(objects::DynObject* src, objects::DynObject* target) @@ -123,6 +125,9 @@ namespace rt size_t pre_run() { + std::cout << "Initilizing global objects" << std::endl; + core::globals(); + std::cout << "Running test..." << std::endl; return objects::DynObject::get_count(); } @@ -131,17 +136,27 @@ namespace rt { std::cout << "Test complete - checking for cycles in local region..." << std::endl; + auto globals = core::globals(); if (objects::DynObject::get_count() != initial_count) { std::cout << "Cycles detected in local region." << std::endl; - auto objs = objects::DynObject::get_local_region()->get_objects(); - std::vector edges; - for (auto obj : objs) - { - edges.push_back({nullptr, "?", obj}); - } - ui.output(edges, "Cycles detected in local region."); + auto roots = objects::DynObject::get_local_region()->get_objects(); + roots.erase( + std::remove_if( + roots.begin(), + roots.end(), + [&globals](auto x) { return globals->contains(x); }), + roots.end()); + ui.output(roots, "Cycles detected in local region."); } + + // Freeze global objects, to low the termination of the local region + std::cout << "Freezing global objects" << std::endl; + for (auto obj : *globals) + { + obj->freeze(); + } + objects::DynObject::get_local_region()->terminate_region(); if (objects::DynObject::get_count() != initial_count) { @@ -150,12 +165,12 @@ namespace rt std::cout << "Final count: " << objects::DynObject::get_count() << std::endl; - std::vector edges; + std::vector roots; for (auto obj : objects::DynObject::get_objects()) { - edges.push_back({nullptr, "?", obj}); + roots.push_back(obj); } - ui.output(edges, "Memory leak detected!"); + ui.output(roots, "Memory leak detected!"); std::exit(1); } @@ -168,7 +183,7 @@ namespace rt objects::DynObject* iter_next(objects::DynObject* iter) { assert(!iter->is_immutable()); - if (iter->get_prototype() != &core::keyIterPrototypeObject) + if (iter->get_prototype() != core::keyIterPrototypeObject()) { ui::error("Object is not an iterator."); } @@ -178,7 +193,7 @@ namespace rt verona::interpreter::Bytecode* get_bytecode(objects::DynObject* func) { - if (func->get_prototype() == &core::bytecodeFuncPrototypeObject) + if (func->get_prototype() == core::bytecodeFuncPrototypeObject()) { return reinterpret_cast(func)->get_bytecode(); } diff --git a/src/rt/ui.h b/src/rt/ui.h index cb81967..18a824e 100644 --- a/src/rt/ui.h +++ b/src/rt/ui.h @@ -12,10 +12,10 @@ namespace rt::ui class UI { public: - virtual void output(std::vector&, std::string) {} + virtual void output(std::vector&, std::string) {} }; - void mermaid(std::vector& roots, std::ostream& out); + void mermaid(std::vector& roots, std::ostream& out); [[noreturn]] inline void error(const std::string& msg) { diff --git a/src/rt/ui/mermaid.cc b/src/rt/ui/mermaid.cc index 809b20c..29a432b 100644 --- a/src/rt/ui/mermaid.cc +++ b/src/rt/ui/mermaid.cc @@ -1,3 +1,4 @@ +#include "../core.h" #include "../objects/dyn_object.h" #include "../ui.h" @@ -30,7 +31,7 @@ namespace rt::ui return text; } - void mermaid(std::vector& roots, std::ostream& out) + void mermaid(std::vector& roots, std::ostream& out) { // Give a nice id to each object. std::map visited; @@ -56,6 +57,10 @@ namespace rt::ui objects::DynObject* dst = e.target; std::string key = e.key; objects::DynObject* src = e.src; + if (unreachable && core::globals()->contains(dst)) + { + return false; + } if (src != nullptr) { out << " id" << visited[src] << " -->|" << escape(key) << "| "; @@ -85,10 +90,10 @@ namespace rt::ui } return true; }; - // Output all reachable edges + // Output all reachable nodes for (auto& root : roots) { - objects::visit({root.src, root.key, root.target}, explore); + objects::visit(root, explore); } // Output the unreachable parts of the graph diff --git a/tests/leak_with_global.vpy b/tests/leak_with_global.vpy new file mode 100644 index 0000000..74d6d14 --- /dev/null +++ b/tests/leak_with_global.vpy @@ -0,0 +1,3 @@ +str = "example" + +str["__proto__"].dummy = {}