From f94fe9e3dee9e3745a1837310efe75b924dc9a35 Mon Sep 17 00:00:00 2001 From: xFrednet Date: Fri, 18 Oct 2024 11:33:00 +0200 Subject: [PATCH 1/8] Mermaid: Add tainting logic --- src/rt/objects/dyn_object.h | 2 +- src/rt/ui.h | 2 +- src/rt/ui/mermaid.cc | 32 ++++++++++++++++++++++++++++++-- 3 files changed, 32 insertions(+), 4 deletions(-) diff --git a/src/rt/objects/dyn_object.h b/src/rt/objects/dyn_object.h index ac6e2b5..b12ee5d 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, std::vector* taint); friend void destruct(DynObject* obj); friend void dealloc(DynObject* obj); template diff --git a/src/rt/ui.h b/src/rt/ui.h index 18a824e..344cc28 100644 --- a/src/rt/ui.h +++ b/src/rt/ui.h @@ -15,7 +15,7 @@ namespace rt::ui virtual void output(std::vector&, std::string) {} }; - void mermaid(std::vector& roots, std::ostream& out); + void mermaid(std::vector& roots, std::ostream& out, std::vector* taint = nullptr); [[noreturn]] inline void error(const std::string& msg) { diff --git a/src/rt/ui/mermaid.cc b/src/rt/ui/mermaid.cc index 29a432b..f3c142d 100644 --- a/src/rt/ui/mermaid.cc +++ b/src/rt/ui/mermaid.cc @@ -31,7 +31,10 @@ namespace rt::ui return text; } - void mermaid(std::vector& roots, std::ostream& out) + void mermaid( + std::vector& roots, + std::ostream& out, + std::vector* taint) { // Give a nice id to each object. std::map visited; @@ -149,7 +152,32 @@ namespace rt::ui // Output object count as very useful. out << "subgraph Count " << objects::DynObject::get_count() << std::endl; out << "end" << std::endl; - out << "classDef unreachable stroke:red,stroke-width:2px" << std::endl; + out << "classDef unreachable stroke:red,stroke-width:2px;" << std::endl; + + // Taint nodes on request + if (taint) + { + out << "classDef tainted fill:#43a;" << std::endl; + std::set tainted; + + auto mark_tained = [&](objects::Edge e) { + objects::DynObject* dst = e.target; + if (tainted.contains(dst)) + { + return false; + } + out << "class id" << visited[dst] << " tainted;" << std::endl; + tainted.insert(dst); + + return true; + }; + + for (auto root : *taint) + { + objects::visit(root, mark_tained); + } + } + // Footer (end of mermaid graph) out << "```" << std::endl; } From 74a06dabd3c79d307a6ab3ebce9990586b61f430 Mon Sep 17 00:00:00 2001 From: xFrednet Date: Fri, 18 Oct 2024 11:44:02 +0200 Subject: [PATCH 2/8] Test: Cleanup `three_regions.vpy` --- tests/three_regions.vpy | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/tests/three_regions.vpy b/tests/three_regions.vpy index cbb7426..6b2874e 100644 --- a/tests/three_regions.vpy +++ b/tests/three_regions.vpy @@ -5,25 +5,24 @@ b = {} a["b"] = b region a + c = {} c["self"] = c -d = {} -c["d"] = d +c["d"] = {} region c e = {} e["self"] = e -f = {} -e["f"] = f -region e +e["f"] = {} +region e # connect first region to second in two ways -a["c"] = c -b["d"] = d +a.c = c +a.b.d = c.d # Connect first region to third with single entry point -b["e"] = f +a.b.e = e.f # Now freeze part of the first region, and the reachable parts of the # second and third regions @@ -32,8 +31,6 @@ freeze b # Drop all the references drop a -drop d -drop f -drop b +drop b drop c drop e From 074457bc90585ce6e0d9896903d994f36f649b21 Mon Sep 17 00:00:00 2001 From: xFrednet Date: Fri, 18 Oct 2024 12:11:28 +0200 Subject: [PATCH 3/8] Passes: Support tainting --- src/lang/bytecode.h | 1 + src/lang/interpreter.cc | 23 ++++++++++++++++++----- src/lang/lang.h | 13 +++++++------ src/lang/passes/bytecode.cc | 5 +++++ src/lang/passes/call_stmts.cc | 4 ++-- src/lang/passes/flatten.cc | 12 ++++++------ src/lang/passes/grouping.cc | 6 ++++++ src/lang/passes/parse.cc | 3 ++- src/rt/ui.h | 11 +++++++++-- src/rt/ui/mermaid.cc | 2 +- tests/three_regions.vpy | 3 +++ 11 files changed, 60 insertions(+), 23 deletions(-) diff --git a/src/lang/bytecode.h b/src/lang/bytecode.h index 4faa24f..4fab1e3 100644 --- a/src/lang/bytecode.h +++ b/src/lang/bytecode.h @@ -43,4 +43,5 @@ inline const trieste::TokenDef Jump{"jump", trieste::flag::print}; /// Jump if the current stack frame is `False` inline const trieste::TokenDef JumpFalse{"jump_false", trieste::flag::print}; inline const trieste::TokenDef Print("print", trieste::flag::print); +inline const trieste::TokenDef Taint{"display_taint"}; inline const trieste::TokenDef IterNext("iter_next"); diff --git a/src/lang/interpreter.cc b/src/lang/interpreter.cc index 894cbd4..9195c08 100644 --- a/src/lang/interpreter.cc +++ b/src/lang/interpreter.cc @@ -119,14 +119,25 @@ namespace verona::interpreter // ========================================== // Operators that shouldn't be printed // ========================================== - if (node == Print) + if (node == Print || node == Taint) { // Console output std::cout << node->location().view() << std::endl << std::endl; + std::vector taint = {}; + if (node == Taint) + { + auto v = pop("taint source"); + taint.push_back(v); + // Removing the reference here is a bit early, but should be safe + // since it comes from an ident. Removing it later would require an + // additional if clause + rt::remove_reference(frame(), v); + } + // Mermaid output std::vector roots{frame()}; - ui->output(roots, std::string(node->location().view())); + ui->output(roots, std::string(node->location().view()), &taint); // Continue return ExecNext{}; @@ -464,13 +475,15 @@ namespace verona::interpreter out.open(path); } - void - output(std::vector& roots, std::string message) + void output( + std::vector& roots, + std::string message, + std::vector* taint = nullptr) { out << "```" << std::endl; out << message << std::endl; out << "```" << std::endl; - rt::ui::mermaid(roots, out); + rt::ui::mermaid(roots, out, taint); if (interactive) { out.close(); diff --git a/src/lang/lang.h b/src/lang/lang.h index 8d6cee6..d8eea35 100644 --- a/src/lang/lang.h +++ b/src/lang/lang.h @@ -40,11 +40,12 @@ namespace verona::wf inline const auto grouping = (Top <<= File) | (File <<= Body) | (Body <<= Block) | (Block <<= - (Freeze | Region | Assign | If | For | Func | Return | ReturnValue | Call | - Method)++) | + (Freeze | Taint | Region | Assign | If | For | Func | Return | + ReturnValue | Call | Method)++) | (Assign <<= (Lhs >>= lv) * (Rhs >>= rv)) | (Lookup <<= (Op >>= operand) * (Rhs >>= key)) | (Region <<= Ident) | - (Freeze <<= Ident) | (Create <<= Ident) | (If <<= Eq * Block * Block) | + (Freeze <<= Ident) | (Taint <<= Ident) | (Create <<= Ident) | + (If <<= Eq * Block * Block) | (For <<= (Key >>= Ident) * (Value >>= Ident) * (Op >>= lv) * Block) | (Eq <<= (Lhs >>= cmp_values) * (Rhs >>= cmp_values)) | (Func <<= Ident * Params * Body) | (Call <<= Ident * List) | @@ -54,9 +55,9 @@ namespace verona::wf inline const trieste::wf::Wellformed bytecode = (Top <<= Body) | (Body <<= (LoadFrame | StoreFrame | LoadField | StoreField | Drop | Null | - CreateObject | CreateRegion | FreezeObject | IterNext | Print | Eq | Neq | - Jump | JumpFalse | Label | Call | Return | ReturnValue | ClearStack | - Dup)++) | + CreateObject | CreateRegion | FreezeObject | Taint | IterNext | Print | + Eq | Neq | Jump | JumpFalse | Label | Call | Return | ReturnValue | + ClearStack | Dup)++) | (CreateObject <<= (Dictionary | String | KeyIter | Proto | Func)) | (Func <<= Body) | (Label <<= Ident)[Ident]; } diff --git a/src/lang/passes/bytecode.cc b/src/lang/passes/bytecode.cc index 0c3dbbc..4b06a31 100644 --- a/src/lang/passes/bytecode.cc +++ b/src/lang/passes/bytecode.cc @@ -74,6 +74,11 @@ PassDef bytecode() << create_print(_(Op)); }, + T(Compile) << (T(Taint)[Op] << T(Ident)[Ident]) >> + [](auto& _) { + return Seq << (Compile << _[Ident]) << create_from(Taint, _(Op)); + }, + T(Compile) << (T(Create)[Op] << T(Ident)[Ident]) >> [](auto& _) { return Seq << (Compile << _[Ident]) << (CreateObject << Proto); diff --git a/src/lang/passes/call_stmts.cc b/src/lang/passes/call_stmts.cc index 6eb6ce4..029ab8b 100644 --- a/src/lang/passes/call_stmts.cc +++ b/src/lang/passes/call_stmts.cc @@ -6,8 +6,8 @@ namespace verona::wf inline const auto call_stmts = grouping | (Block <<= - (Freeze | Region | Assign | If | For | Func | Return | ReturnValue | Call | - Method | ClearStack | Print)++); + (Freeze | Taint | Region | Assign | If | For | Func | Return | + ReturnValue | Call | Method | ClearStack | Print)++); } PassDef call_stmts() diff --git a/src/lang/passes/flatten.cc b/src/lang/passes/flatten.cc index 3fcbf4a..9809b80 100644 --- a/src/lang/passes/flatten.cc +++ b/src/lang/passes/flatten.cc @@ -6,16 +6,16 @@ namespace verona::wf inline const trieste::wf::Wellformed flatten = (Top <<= File) | (File <<= Body) | (Body <<= - (Freeze | Region | Assign | Eq | Neq | Label | Jump | JumpFalse | Print | - StoreFrame | LoadFrame | CreateObject | Ident | IterNext | Create | - StoreField | Lookup | String | Call | Method | Return | ReturnValue | - ClearStack)++) | + (Freeze | Taint | Region | Assign | Eq | Neq | Label | Jump | JumpFalse | + Print | StoreFrame | LoadFrame | CreateObject | Ident | IterNext | + Create | StoreField | Lookup | String | Call | Method | Return | + ReturnValue | ClearStack)++) | (CreateObject <<= (KeyIter | String | Dictionary | Func)) | (Func <<= Compile) | (Compile <<= Body) | (Create <<= Ident) | (Assign <<= (Lhs >>= lv) * (Rhs >>= rv)) | (Lookup <<= (Op >>= operand) * (Rhs >>= key)) | (Region <<= Ident) | - (Freeze <<= Ident) | (Call <<= Ident * List) | (Method <<= Lookup * List) | - (List <<= rv++) | (Params <<= Ident++) | + (Freeze <<= Ident) | (Taint <<= Ident) | (Call <<= Ident * List) | + (Method <<= Lookup * List) | (List <<= rv++) | (Params <<= Ident++) | (Eq <<= (Lhs >>= cmp_values) * (Rhs >>= cmp_values)) | (Neq <<= (Lhs >>= cmp_values) * (Rhs >>= cmp_values)) | (Label <<= Ident)[Ident]; diff --git a/src/lang/passes/grouping.cc b/src/lang/passes/grouping.cc index 2218863..738779d 100644 --- a/src/lang/passes/grouping.cc +++ b/src/lang/passes/grouping.cc @@ -28,6 +28,12 @@ PassDef grouping() return _(Freeze) << _(Ident); }, + T(Group) << ((T(Taint)[Taint] << End) * T(Ident)[Ident] * End) >> + [](auto& _) { + _(Taint)->extend(_(Ident)->location()); + return _(Taint) << _(Ident); + }, + T(Group) << ((T(Drop)[Drop] << End) * LV[Lhs] * End) >> [](auto& _) { return Assign << _(Lhs) << Null; }, // function(arg, arg) diff --git a/src/lang/passes/parse.cc b/src/lang/passes/parse.cc index 3cc5090..dfabfff 100644 --- a/src/lang/passes/parse.cc +++ b/src/lang/passes/parse.cc @@ -5,7 +5,7 @@ namespace verona::wf using namespace trieste::wf; inline const auto parse_tokens = Region | Ident | Lookup | Empty | Freeze | - Drop | Null | String | Create | Parens; + Drop | Taint | Null | String | Create | Parens; inline const auto parse_groups = Group | Assign | If | Else | Block | For | Func | List | Return; @@ -155,6 +155,7 @@ trieste::Parse parser() "create" >> [](auto& m) { m.add(Create); }, "freeze" >> [](auto& m) { m.add(Freeze); }, "region" >> [](auto& m) { m.add(Region); }, + "taint" >> [](auto& m) { m.add(Taint); }, "None" >> [](auto& m) { m.add(Null); }, "[0-9A-Za-z_]+" >> [](auto& m) { m.add(Ident); }, "\\[" >> [](auto& m) { m.push(Lookup); }, diff --git a/src/rt/ui.h b/src/rt/ui.h index 344cc28..818af4b 100644 --- a/src/rt/ui.h +++ b/src/rt/ui.h @@ -12,10 +12,17 @@ namespace rt::ui class UI { public: - virtual void output(std::vector&, std::string) {} + virtual void output( + std::vector&, + std::string, + std::vector* = nullptr) + {} }; - void mermaid(std::vector& roots, std::ostream& out, std::vector* taint = nullptr); + void mermaid( + std::vector& roots, + std::ostream& out, + std::vector* taint = nullptr); [[noreturn]] inline void error(const std::string& msg) { diff --git a/src/rt/ui/mermaid.cc b/src/rt/ui/mermaid.cc index f3c142d..7e38e91 100644 --- a/src/rt/ui/mermaid.cc +++ b/src/rt/ui/mermaid.cc @@ -155,7 +155,7 @@ namespace rt::ui out << "classDef unreachable stroke:red,stroke-width:2px;" << std::endl; // Taint nodes on request - if (taint) + if (taint && !taint->empty()) { out << "classDef tainted fill:#43a;" << std::endl; std::set tainted; diff --git a/tests/three_regions.vpy b/tests/three_regions.vpy index 6b2874e..a7d94ec 100644 --- a/tests/three_regions.vpy +++ b/tests/three_regions.vpy @@ -17,6 +17,7 @@ e["self"] = e e["f"] = {} region e +taint a # connect first region to second in two ways a.c = c @@ -24,6 +25,8 @@ a.b.d = c.d # Connect first region to third with single entry point a.b.e = e.f +taint a + # Now freeze part of the first region, and the reachable parts of the # second and third regions freeze b From 450dda40a8defbb8516ac97b6836125cba34122e Mon Sep 17 00:00:00 2001 From: xFrednet Date: Fri, 18 Oct 2024 14:37:24 +0200 Subject: [PATCH 4/8] Cown: Reifing cown objects --- src/rt/core.h | 49 +++++++++++++++++++++++++++++++++++++ src/rt/objects/dyn_object.h | 18 +++++++++++--- src/rt/rt.cc | 27 ++++++++++++++++++++ src/rt/rt.h | 1 + 4 files changed, 91 insertions(+), 4 deletions(-) diff --git a/src/rt/core.h b/src/rt/core.h index e4acfc0..c4f1e52 100644 --- a/src/rt/core.h +++ b/src/rt/core.h @@ -174,6 +174,54 @@ namespace rt::core } }; + // The prototype object for cown + inline PrototypeObject* cownPrototypeObject() + { + static PrototypeObject* proto = new PrototypeObject("Cown"); + return proto; + } + + class CownObject : public objects::DynObject + { + // For now always false, but might be needed later if we want to simulate + // concurrency. + bool aquired = false; + + public: + CownObject(objects::DynObject* region) + : objects::DynObject(cownPrototypeObject()), + { + // FIXME: Add once regions are reified + // assert( + // region->get_prototype() == regionPrototype() && + // "Cowns can only store regions"); + // + // FIXME: Also check that the region has a LRC == 1, with 1 + // being the reference passed into this constructor + this->set("region", region); + } + + bool is_aquired() + { + return aquired; + } + + std::string get_name() + { + return ""; + } + + objects::DynObject* is_primitive() + { + return this; + } + + bool is_cown() override + { + return false; + } + }; + inline std::set* globals() { static std::set* globals = @@ -185,6 +233,7 @@ namespace rt::core keyIterPrototypeObject(), trueObject(), falseObject(), + cownPrototypeObject(), }; return globals; } diff --git a/src/rt/objects/dyn_object.h b/src/rt/objects/dyn_object.h index b12ee5d..a5285e9 100644 --- a/src/rt/objects/dyn_object.h +++ b/src/rt/objects/dyn_object.h @@ -27,8 +27,10 @@ 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, std::vector* taint); + friend void rt::ui::mermaid( + std::vector& roots, + std::ostream& out, + std::vector* taint); friend void destruct(DynObject* obj); friend void dealloc(DynObject* obj); template @@ -241,7 +243,8 @@ namespace rt::objects get_region(obj)->objects.erase(obj); } obj->region.set_tag(ImmutableTag); - return true; + + return !obj->is_cown(); }); } @@ -250,6 +253,11 @@ namespace rt::objects return region.get_tag() == ImmutableTag; } + virtual bool is_cown() + { + return false; + } + [[nodiscard]] DynObject* get(std::string name) { auto result = fields.find(name); @@ -270,7 +278,7 @@ namespace rt::objects [[nodiscard]] DynObject* set(std::string name, DynObject* value) { - if (is_immutable()) + if (is_immutable() && this->is_cown()) { ui::error("Cannot mutate immutable object"); } @@ -282,6 +290,7 @@ namespace rt::objects // The caller must provide an rc for value. [[nodiscard]] DynObject* set_prototype(DynObject* value) { + // No need to check for a cown, since cowns already have a set prototype if (is_immutable()) { ui::error("Cannot mutate immutable object"); @@ -331,6 +340,7 @@ namespace rt::objects static void move_reference(DynObject* src, DynObject* dst, DynObject* target) { + // An immutable cown can't be moved to another region if (target == nullptr || target->is_immutable()) return; diff --git a/src/rt/rt.cc b/src/rt/rt.cc index 6c46386..a24ee65 100644 --- a/src/rt/rt.cc +++ b/src/rt/rt.cc @@ -34,11 +34,18 @@ namespace rt return new core::FrameObject(parent); } + objects::DynObject* make_cown(objects::DynObject* region) + { + return new core::CownObject(region); + } + thread_local objects::RegionPointer objects::DynObject::local_region = new Region(); void freeze(objects::DynObject* obj) { + // Cown specific handling of the freeze operation is handled by the + // `freeze()` implementation of the object obj->freeze(); } @@ -49,6 +56,15 @@ namespace rt objects::DynObject* get(objects::DynObject* obj, std::string key) { + if (obj->get_prototype() == core::cownPrototypeObject()) + { + core::CownObject* cown = reinterpret_cast(obj); + if (!cown->is_aquired()) + { + ui::error( + "the cown needs to be aquired, before its data can be accessed"); + } + } return obj->get(key); } @@ -72,6 +88,17 @@ namespace rt objects::DynObject* set(objects::DynObject* obj, std::string key, objects::DynObject* value) { + if (obj->get_prototype() == core::cownPrototypeObject()) + { + core::CownObject* cown = reinterpret_cast(obj); + if (!cown->is_aquired()) + { + // Overwriting data can change the RC and then call destructors of the + // type this action therefore requires the cown to be aquired + ui::error("the cown needs to be aquired, before its data can modified"); + } + } + return obj->set(key, value); } diff --git a/src/rt/rt.h b/src/rt/rt.h index 4fc2046..9eea970 100644 --- a/src/rt/rt.h +++ b/src/rt/rt.h @@ -15,6 +15,7 @@ namespace rt objects::DynObject* make_str(std::string str_value); objects::DynObject* make_object(); objects::DynObject* make_frame(objects::DynObject* parent); + objects::DynObject* make_cown(objects::DynObject* region); void freeze(objects::DynObject* obj); void create_region(objects::DynObject* objects); From b69a27feab9cdadd045b41ec081887d5dfcd3078 Mon Sep 17 00:00:00 2001 From: xFrednet Date: Fri, 18 Oct 2024 16:59:36 +0200 Subject: [PATCH 5/8] Passes: Support cown creation --- src/lang/bytecode.h | 1 + src/lang/interpreter.cc | 6 ++++++ src/lang/lang.h | 11 ++++++----- src/lang/passes/bytecode.cc | 14 ++++++++++++++ src/lang/passes/flatten.cc | 5 +++-- src/lang/passes/grouping.cc | 20 +++++--------------- src/lang/passes/parse.cc | 5 +++-- src/rt/core.h | 4 ++-- src/rt/objects/dyn_object.h | 12 ++++++++---- tests/valid_cowns.vpy | 21 +++++++++++++++++++++ 10 files changed, 69 insertions(+), 30 deletions(-) create mode 100644 tests/valid_cowns.vpy diff --git a/src/lang/bytecode.h b/src/lang/bytecode.h index 4fab1e3..67eacfe 100644 --- a/src/lang/bytecode.h +++ b/src/lang/bytecode.h @@ -9,6 +9,7 @@ inline const trieste::TokenDef StoreField{"store_field"}; inline const trieste::TokenDef CreateObject{"create_object"}; inline const trieste::TokenDef Proto{"prototype"}; inline const trieste::TokenDef Dictionary{"dictionary"}; +inline const trieste::TokenDef Cown{"cown"}; inline const trieste::TokenDef String{"string", trieste::flag::print}; inline const trieste::TokenDef KeyIter{"key_iter"}; inline const trieste::TokenDef Func{"func"}; diff --git a/src/lang/interpreter.cc b/src/lang/interpreter.cc index 9195c08..ed02d0d 100644 --- a/src/lang/interpreter.cc +++ b/src/lang/interpreter.cc @@ -185,6 +185,12 @@ namespace verona::interpreter "CreateObject: A bytecode function requires a body node"); obj = rt::make_func(new Bytecode{payload->at(0)}); } + else if (payload == Cown) + { + auto v = pop("cown reagion"); + obj = rt::make_cown(v); + rt::move_reference(frame(), obj, v); + } else { assert(false && "CreateObject has to specify a value"); diff --git a/src/lang/lang.h b/src/lang/lang.h index d8eea35..20711f4 100644 --- a/src/lang/lang.h +++ b/src/lang/lang.h @@ -32,7 +32,8 @@ inline const TokenDef Compile{"compile"}; namespace verona::wf { inline const auto lv = Ident | Lookup; - inline const auto rv = lv | Empty | Null | String | Create | Call | Method; + inline const auto rv = + lv | Empty | Null | String | Create | Call | Method | Cown; inline const auto cmp_values = Ident | Lookup | Null; inline const auto key = Ident | Lookup | String; inline const auto operand = Lookup | Call | Method | Ident; @@ -44,8 +45,8 @@ namespace verona::wf ReturnValue | Call | Method)++) | (Assign <<= (Lhs >>= lv) * (Rhs >>= rv)) | (Lookup <<= (Op >>= operand) * (Rhs >>= key)) | (Region <<= Ident) | - (Freeze <<= Ident) | (Taint <<= Ident) | (Create <<= Ident) | - (If <<= Eq * Block * Block) | + (Freeze <<= Ident) | (Taint <<= Ident) | (Cown <<= Ident) | + (Create <<= Ident) | (If <<= Eq * Block * Block) | (For <<= (Key >>= Ident) * (Value >>= Ident) * (Op >>= lv) * Block) | (Eq <<= (Lhs >>= cmp_values) * (Rhs >>= cmp_values)) | (Func <<= Ident * Params * Body) | (Call <<= Ident * List) | @@ -58,13 +59,13 @@ namespace verona::wf CreateObject | CreateRegion | FreezeObject | Taint | IterNext | Print | Eq | Neq | Jump | JumpFalse | Label | Call | Return | ReturnValue | ClearStack | Dup)++) | - (CreateObject <<= (Dictionary | String | KeyIter | Proto | Func)) | + (CreateObject <<= (Dictionary | String | KeyIter | Proto | Func | Cown)) | (Func <<= Body) | (Label <<= Ident)[Ident]; } inline const auto LV = T(Ident, Lookup); inline const auto RV = - T(Empty, Ident, Lookup, Null, String, Create, Call, Method); + T(Empty, Ident, Lookup, Null, String, Create, Call, Method, Cown); inline const auto CMP_V = T(Ident, Lookup, Null); inline const auto KEY = T(Ident, Lookup, String); inline const auto OPERAND = T(Lookup, Call, Method, Ident); diff --git a/src/lang/passes/bytecode.cc b/src/lang/passes/bytecode.cc index 4b06a31..94c452c 100644 --- a/src/lang/passes/bytecode.cc +++ b/src/lang/passes/bytecode.cc @@ -1,5 +1,7 @@ #include "../lang.h" +inline const trieste::TokenDef DestructiveRead{"destructive_read"}; + PassDef bytecode() { PassDef p{ @@ -79,6 +81,18 @@ PassDef bytecode() return Seq << (Compile << _[Ident]) << create_from(Taint, _(Op)); }, + T(Compile) << (T(DestructiveRead) << T(Ident)[Ident]) >> + [](auto& _) { + // Read the value from the frame and set the frame value to null + return Seq << (Compile << _(Ident)) << Null + << create_from(StoreFrame, _(Ident)); + }, + T(Compile) << (T(Cown)[Op] << T(Ident)[Ident]) >> + [](auto& _) { + return Seq << (Compile << (DestructiveRead << _(Ident))) + << (CreateObject << Cown); + }, + T(Compile) << (T(Create)[Op] << T(Ident)[Ident]) >> [](auto& _) { return Seq << (Compile << _[Ident]) << (CreateObject << Proto); diff --git a/src/lang/passes/flatten.cc b/src/lang/passes/flatten.cc index 9809b80..2d89994 100644 --- a/src/lang/passes/flatten.cc +++ b/src/lang/passes/flatten.cc @@ -14,8 +14,9 @@ namespace verona::wf (Func <<= Compile) | (Compile <<= Body) | (Create <<= Ident) | (Assign <<= (Lhs >>= lv) * (Rhs >>= rv)) | (Lookup <<= (Op >>= operand) * (Rhs >>= key)) | (Region <<= Ident) | - (Freeze <<= Ident) | (Taint <<= Ident) | (Call <<= Ident * List) | - (Method <<= Lookup * List) | (List <<= rv++) | (Params <<= Ident++) | + (Freeze <<= Ident) | (Taint <<= Ident) | (Cown <<= Ident) | + (Call <<= Ident * List) | (Method <<= Lookup * List) | (List <<= rv++) | + (Params <<= Ident++) | (Eq <<= (Lhs >>= cmp_values) * (Rhs >>= cmp_values)) | (Neq <<= (Lhs >>= cmp_values) * (Rhs >>= cmp_values)) | (Label <<= Ident)[Ident]; diff --git a/src/lang/passes/grouping.cc b/src/lang/passes/grouping.cc index 738779d..3301af8 100644 --- a/src/lang/passes/grouping.cc +++ b/src/lang/passes/grouping.cc @@ -16,22 +16,12 @@ PassDef grouping() In(Group) * OPERAND[Op] * (T(Lookup)[Lookup] << (T(Group) << KEY[Rhs])) >> [](auto& _) { return Lookup << _(Op) << _(Rhs); }, - T(Group) << ((T(Region)[Region] << End) * T(Ident)[Ident] * End) >> - [](auto& _) { - _(Region)->extend(_(Ident)->location()); - return _(Region) << _(Ident); - }, - - T(Group) << ((T(Freeze)[Freeze] << End) * T(Ident)[Ident] * End) >> - [](auto& _) { - _(Freeze)->extend(_(Ident)->location()); - return _(Freeze) << _(Ident); - }, - - T(Group) << ((T(Taint)[Taint] << End) * T(Ident)[Ident] * End) >> + T(Group) + << ((T(Freeze, Taint, Cown, Region)[Op] << End) * T(Ident)[Ident] * + End) >> [](auto& _) { - _(Taint)->extend(_(Ident)->location()); - return _(Taint) << _(Ident); + _(Op)->extend(_(Ident)->location()); + return _(Op) << _(Ident); }, T(Group) << ((T(Drop)[Drop] << End) * LV[Lhs] * End) >> diff --git a/src/lang/passes/parse.cc b/src/lang/passes/parse.cc index dfabfff..8e21dab 100644 --- a/src/lang/passes/parse.cc +++ b/src/lang/passes/parse.cc @@ -5,7 +5,7 @@ namespace verona::wf using namespace trieste::wf; inline const auto parse_tokens = Region | Ident | Lookup | Empty | Freeze | - Drop | Taint | Null | String | Create | Parens; + Taint | Cown | Drop | Null | String | Create | Parens; inline const auto parse_groups = Group | Assign | If | Else | Block | For | Func | List | Return; @@ -154,8 +154,9 @@ trieste::Parse parser() "drop" >> [](auto& m) { m.add(Drop); }, "create" >> [](auto& m) { m.add(Create); }, "freeze" >> [](auto& m) { m.add(Freeze); }, - "region" >> [](auto& m) { m.add(Region); }, "taint" >> [](auto& m) { m.add(Taint); }, + "cown" >> [](auto& m) { m.add(Cown); }, + "region" >> [](auto& m) { m.add(Region); }, "None" >> [](auto& m) { m.add(Null); }, "[0-9A-Za-z_]+" >> [](auto& m) { m.add(Ident); }, "\\[" >> [](auto& m) { m.push(Lookup); }, diff --git a/src/rt/core.h b/src/rt/core.h index c4f1e52..0ede3cb 100644 --- a/src/rt/core.h +++ b/src/rt/core.h @@ -189,7 +189,7 @@ namespace rt::core public: CownObject(objects::DynObject* region) - : objects::DynObject(cownPrototypeObject()), + : objects::DynObject(cownPrototypeObject()) { // FIXME: Add once regions are reified // assert( @@ -218,7 +218,7 @@ namespace rt::core bool is_cown() override { - return false; + return true; } }; diff --git a/src/rt/objects/dyn_object.h b/src/rt/objects/dyn_object.h index a5285e9..973e312 100644 --- a/src/rt/objects/dyn_object.h +++ b/src/rt/objects/dyn_object.h @@ -79,8 +79,11 @@ namespace rt::objects return; } - assert(target->parent == src); - Region::dec_prc(target); + if (src) + { + assert(target->parent == src); + Region::dec_prc(target); + } return; } @@ -234,7 +237,7 @@ namespace rt::objects // TODO SCC algorithm visit(this, [](Edge e) { auto obj = e.target; - if (obj->is_immutable()) + if (!obj || obj->is_immutable()) return false; auto r = get_region(obj); @@ -244,7 +247,8 @@ namespace rt::objects } obj->region.set_tag(ImmutableTag); - return !obj->is_cown(); + auto cown = obj->is_cown(); + return !cown; }); } diff --git a/tests/valid_cowns.vpy b/tests/valid_cowns.vpy new file mode 100644 index 0000000..56093a7 --- /dev/null +++ b/tests/valid_cowns.vpy @@ -0,0 +1,21 @@ +global = {} + +# A simple cown +a = {} +a.b = {} +region a +c01 = cown a + +# Store the cown in a global +global.cown = c01 +taint global + +# Freeze global with a cown +freeze global + +# FIXME: traint with cowns is wrong +# FIXME: Show local region address +# FIXME: Cowns should have special rendering and out edges + +drop c01 +drop global From e7b2d8009d4b6339116d566ef811f45a9811697a Mon Sep 17 00:00:00 2001 From: xFrednet Date: Fri, 18 Oct 2024 18:17:03 +0200 Subject: [PATCH 6/8] Mermaid: Make mermaid aware of cowns --- src/rt/core.h | 13 +++++++------ src/rt/objects/dyn_object.h | 8 ++++++++ src/rt/rt.cc | 21 ++++++--------------- src/rt/ui/mermaid.cc | 22 ++++++++++++++++++---- 4 files changed, 39 insertions(+), 25 deletions(-) diff --git a/src/rt/core.h b/src/rt/core.h index 0ede3cb..a36db45 100644 --- a/src/rt/core.h +++ b/src/rt/core.h @@ -198,12 +198,8 @@ namespace rt::core // // FIXME: Also check that the region has a LRC == 1, with 1 // being the reference passed into this constructor - this->set("region", region); - } - - bool is_aquired() - { - return aquired; + auto old = this->set("region", region); + assert(old == nullptr); } std::string get_name() @@ -220,6 +216,11 @@ namespace rt::core { return true; } + + bool is_cown_aquired() override + { + return aquired; + } }; inline std::set* globals() diff --git a/src/rt/objects/dyn_object.h b/src/rt/objects/dyn_object.h index 973e312..e4332c3 100644 --- a/src/rt/objects/dyn_object.h +++ b/src/rt/objects/dyn_object.h @@ -261,6 +261,14 @@ namespace rt::objects { return false; } + virtual bool is_cown_aquired() + { + assert(false && "should only be called on cowns"); + } + bool is_opaque() + { + return this->is_cown() && !this->is_cown_aquired(); + } [[nodiscard]] DynObject* get(std::string name) { diff --git a/src/rt/rt.cc b/src/rt/rt.cc index a24ee65..a7f2b08 100644 --- a/src/rt/rt.cc +++ b/src/rt/rt.cc @@ -56,14 +56,9 @@ namespace rt objects::DynObject* get(objects::DynObject* obj, std::string key) { - if (obj->get_prototype() == core::cownPrototypeObject()) + if (obj->is_opaque()) { - core::CownObject* cown = reinterpret_cast(obj); - if (!cown->is_aquired()) - { - ui::error( - "the cown needs to be aquired, before its data can be accessed"); - } + ui::error("opaque objects can't be accessed"); } return obj->get(key); } @@ -88,15 +83,11 @@ namespace rt objects::DynObject* set(objects::DynObject* obj, std::string key, objects::DynObject* value) { - if (obj->get_prototype() == core::cownPrototypeObject()) + if (obj->is_opaque()) { - core::CownObject* cown = reinterpret_cast(obj); - if (!cown->is_aquired()) - { - // Overwriting data can change the RC and then call destructors of the - // type this action therefore requires the cown to be aquired - ui::error("the cown needs to be aquired, before its data can modified"); - } + // Overwriting data can change the RC and then call destructors of the + // type this action therefore requires the cown to be aquired + ui::error("opaque objects can't be modified"); } return obj->set(key, value); diff --git a/src/rt/ui/mermaid.cc b/src/rt/ui/mermaid.cc index 7e38e91..2e78bec 100644 --- a/src/rt/ui/mermaid.cc +++ b/src/rt/ui/mermaid.cc @@ -10,6 +10,15 @@ namespace rt::ui { + struct MermaidDecoration + { + const char* start; + const char* end; + const char* out; + }; + const inline auto NORMAL = MermaidDecoration{"[", "]", "-->"}; + const inline auto COWN = MermaidDecoration{"[[", "]]", "-.->"}; + void replace(std::string& text, std::string from, std::string replace) { size_t pos = 0; @@ -64,9 +73,13 @@ namespace rt::ui { return false; } + auto src_deco = ((src && src->is_cown()) ? &COWN : &NORMAL); + auto dst_deco = ((dst && dst->is_cown()) ? &COWN : &NORMAL); + if (src != nullptr) { - out << " id" << visited[src] << " -->|" << escape(key) << "| "; + out << " id" << visited[src] << " " << src_deco->out << "|" + << escape(key) << "| "; } if (visited.find(dst) != visited.end()) { @@ -75,11 +88,12 @@ namespace rt::ui } auto curr_id = id++; visited[dst] = curr_id; - out << "id" << curr_id << "[ "; + out << "id" << curr_id << dst_deco->start << " "; out << escape(dst->get_name()); out << "
rc=" << dst->rc; - out << " ]" << (unreachable ? ":::unreachable" : "") << std::endl; + out << " " << dst_deco->end << (unreachable ? ":::unreachable" : "") + << std::endl; auto region = objects::DynObject::get_region(dst); if (region != nullptr) @@ -169,7 +183,7 @@ namespace rt::ui out << "class id" << visited[dst] << " tainted;" << std::endl; tainted.insert(dst); - return true; + return dst->is_opaque(); }; for (auto root : *taint) From e9a0a8b698c2f523e2dfc81f18397fa079a30e82 Mon Sep 17 00:00:00 2001 From: xFrednet Date: Fri, 18 Oct 2024 18:33:11 +0200 Subject: [PATCH 7/8] Test: Add tests for invalid cown usage --- CMakeLists.txt | 4 +++- tests/invalid_cowns/modify_cown.vpy | 7 +++++++ tests/invalid_cowns/read_region.vpy | 7 +++++++ 3 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 tests/invalid_cowns/modify_cown.vpy create mode 100644 tests/invalid_cowns/read_region.vpy diff --git a/CMakeLists.txt b/CMakeLists.txt index c2d269c..ac83280 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -48,7 +48,7 @@ clangformat_targets() message(STATUS "Adding tests") -FILE(GLOB ALL_FILES +FILE(GLOB_RECURSE ALL_FILES CONFIGURE_DEPENDS tests/* ) @@ -62,3 +62,5 @@ endforeach() set_property(TEST three_regions.vpy PROPERTY WILL_FAIL true) set_property(TEST leak_with_global.vpy PROPERTY WILL_FAIL true) +set_property(TEST modify_cown.vpy PROPERTY WILL_FAIL true) +set_property(TEST read_region.vpy PROPERTY WILL_FAIL true) diff --git a/tests/invalid_cowns/modify_cown.vpy b/tests/invalid_cowns/modify_cown.vpy new file mode 100644 index 0000000..c7f043b --- /dev/null +++ b/tests/invalid_cowns/modify_cown.vpy @@ -0,0 +1,7 @@ + +# A simple cown +a = {} +region a +co = cown a + +co.other = {} diff --git a/tests/invalid_cowns/read_region.vpy b/tests/invalid_cowns/read_region.vpy new file mode 100644 index 0000000..dfc7958 --- /dev/null +++ b/tests/invalid_cowns/read_region.vpy @@ -0,0 +1,7 @@ + +# A simple cown +a = {} +region a +co = cown a + +dummy = co.region From f312966fd171bf9f181570b6810aa4e4e4da9d4e Mon Sep 17 00:00:00 2001 From: xFrednet Date: Fri, 18 Oct 2024 18:43:32 +0200 Subject: [PATCH 8/8] PR: Cleanup --- src/lang/passes/bytecode.cc | 3 ++- src/rt/core.h | 6 +++--- src/rt/objects/dyn_object.h | 10 +++++----- src/rt/rt.cc | 2 +- src/rt/ui/mermaid.cc | 2 +- tests/valid_cowns.vpy | 4 ---- 6 files changed, 12 insertions(+), 15 deletions(-) diff --git a/src/lang/passes/bytecode.cc b/src/lang/passes/bytecode.cc index 94c452c..77b5878 100644 --- a/src/lang/passes/bytecode.cc +++ b/src/lang/passes/bytecode.cc @@ -78,7 +78,8 @@ PassDef bytecode() T(Compile) << (T(Taint)[Op] << T(Ident)[Ident]) >> [](auto& _) { - return Seq << (Compile << _[Ident]) << create_from(Taint, _(Op)); + auto print_str = std::string(create_print(_(Op))->location().view()); + return Seq << (Compile << _[Ident]) << (Taint ^ print_str); }, T(Compile) << (T(DestructiveRead) << T(Ident)[Ident]) >> diff --git a/src/rt/core.h b/src/rt/core.h index a36db45..c1d45f3 100644 --- a/src/rt/core.h +++ b/src/rt/core.h @@ -185,7 +185,7 @@ namespace rt::core { // For now always false, but might be needed later if we want to simulate // concurrency. - bool aquired = false; + bool acquired = false; public: CownObject(objects::DynObject* region) @@ -217,9 +217,9 @@ namespace rt::core return true; } - bool is_cown_aquired() override + bool is_cown_acquired() override { - return aquired; + return acquired; } }; diff --git a/src/rt/objects/dyn_object.h b/src/rt/objects/dyn_object.h index e4332c3..3e4ccac 100644 --- a/src/rt/objects/dyn_object.h +++ b/src/rt/objects/dyn_object.h @@ -247,8 +247,7 @@ namespace rt::objects } obj->region.set_tag(ImmutableTag); - auto cown = obj->is_cown(); - return !cown; + return !obj->is_cown(); }); } @@ -261,13 +260,14 @@ namespace rt::objects { return false; } - virtual bool is_cown_aquired() + virtual bool is_cown_acquired() { - assert(false && "should only be called on cowns"); + ui::error("is_cown_acquired() should only be called on cowns"); + return false; } bool is_opaque() { - return this->is_cown() && !this->is_cown_aquired(); + return this->is_cown() && !this->is_cown_acquired(); } [[nodiscard]] DynObject* get(std::string name) diff --git a/src/rt/rt.cc b/src/rt/rt.cc index a7f2b08..0bd667a 100644 --- a/src/rt/rt.cc +++ b/src/rt/rt.cc @@ -86,7 +86,7 @@ namespace rt if (obj->is_opaque()) { // Overwriting data can change the RC and then call destructors of the - // type this action therefore requires the cown to be aquired + // type this action therefore requires the cown to be acquired ui::error("opaque objects can't be modified"); } diff --git a/src/rt/ui/mermaid.cc b/src/rt/ui/mermaid.cc index 2e78bec..17d59a5 100644 --- a/src/rt/ui/mermaid.cc +++ b/src/rt/ui/mermaid.cc @@ -183,7 +183,7 @@ namespace rt::ui out << "class id" << visited[dst] << " tainted;" << std::endl; tainted.insert(dst); - return dst->is_opaque(); + return !dst->is_opaque(); }; for (auto root : *taint) diff --git a/tests/valid_cowns.vpy b/tests/valid_cowns.vpy index 56093a7..28f7b55 100644 --- a/tests/valid_cowns.vpy +++ b/tests/valid_cowns.vpy @@ -13,9 +13,5 @@ taint global # Freeze global with a cown freeze global -# FIXME: traint with cowns is wrong -# FIXME: Show local region address -# FIXME: Cowns should have special rendering and out edges - drop c01 drop global