diff --git a/src/ActivationManager.cc b/src/ActivationManager.cc new file mode 100644 index 0000000000..104b6cdd21 --- /dev/null +++ b/src/ActivationManager.cc @@ -0,0 +1,199 @@ +#include "zeek/ActivationManager.h" + +#include "zeek/Desc.h" + +using namespace std; + +namespace zeek::detail + { + +void ActivationEvent::Dump(int indent_level) const + { + Indent(indent_level); + + switch ( et ) + { + case ActivationEvent::COND: + printf("Cond"); + break; + case ActivationEvent::CREATE_GLOBAL: + printf("Create Global"); + break; + case ActivationEvent::ADDING_GLOBAL_VAL: + printf("Add Global Val"); + break; + case ActivationEvent::REDEF: + printf("Redef"); + break; + case ActivationEvent::HANDLER_REDEF: + printf("Handler Redef"); + break; + case ActivationEvent::BODY: + printf("Body"); + break; + } + + if ( id ) + printf(" ID=%s", obj_desc(id.get()).c_str()); + + if ( expr ) + printf(" expr=%s", obj_desc(expr.get()).c_str()); + + printf("\n"); + + if ( et != ActivationEvent::COND ) + return; + + Indent(indent_level); + printf("TRUE:\n"); + for ( auto& s : T_sub_events ) + s->Dump(indent_level + 1); + + if ( ! in_true_branch ) + { + Indent(indent_level); + printf("FALSE:\n"); + for ( auto& s : F_sub_events ) + s->Dump(indent_level + 1); + } + + Indent(indent_level); + printf("END\n"); + } + +void ActivationEvent::Indent(int indent_level) const + { + while ( indent_level-- > 0 ) + printf("\t"); + } + +Activation::Activation(ExprPtr cond, bool _is_activated, bool _parent_activated, int _cond_depth) + { + is_activated = _is_activated; + parent_activated = _parent_activated; + cond_depth = _cond_depth; + + cond_event = std::make_shared(ActivationEvent::COND); + cond_event->AddExpr(cond); + } + +Activation::~Activation() + { + ResetGlobals(); + } + +void Activation::ResetGlobals() + { + if ( ! is_activated ) + { // undo changes we temporarily introduced + for ( auto& gv : global_vals ) + gv->SetVal(nullptr); + + auto gs = global_scope(); + + if ( gs ) + for ( auto& gid : global_IDs ) + gs->RemoveGlobal(gid->Name(), gid); + } + + global_vals.clear(); + global_IDs.clear(); + } + +ActivationManager::~ActivationManager() + { +#if 0 + for ( auto& ae : activation_events ) + ae->Dump(0); +#endif + } + +void ActivationManager::Start(ExprPtr cond, bool activate, int cond_depth) + { + activate = activate && IsActivated(); + auto a = std::make_unique(cond, activate, IsActivated(), cond_depth); + + auto ce = a->CondEvent(); + + if ( activation_stack.empty() ) + activation_events.push_back(ce); + else + activation_stack.back()->CondEvent()->AddSubEvent(std::move(ce)); + + activation_stack.push_back(std::move(a)); + } + +void ActivationManager::SwitchToElse() + { + ASSERT(! activation_stack.empty()); + activation_stack.back()->SwitchToElse(); + } + +void ActivationManager::End() + { + ASSERT(! activation_stack.empty()); + activation_stack.pop_back(); + } + +void ActivationManager::CreatingGlobalID(IDPtr gid) + { + if ( activation_stack.empty() ) + return; + + auto cg = std::make_shared(ActivationEvent::CREATE_GLOBAL); + cg->AddID(gid); + + activation_stack.back()->CondEvent()->AddSubEvent(std::move(cg)); + activation_stack.back()->AddGlobalID(std::move(gid)); + } + +void ActivationManager::AddingGlobalVal(IDPtr gid) + { + if ( activation_stack.empty() ) + return; + + auto gv = std::make_shared(ActivationEvent::ADDING_GLOBAL_VAL); + gv->AddID(gid); + + activation_stack.back()->CondEvent()->AddSubEvent(std::move(gv)); + activation_stack.back()->AddGlobalVal(std::move(gid)); + } + +void ActivationManager::AddingRedef(const IDPtr& id, InitClass c, ExprPtr init, AttrVec& attrs) + { + if ( activation_stack.empty() ) + return; + + auto r = std::make_shared(ActivationEvent::REDEF); + r->AddID(id); + r->AddInitClass(c); + r->AddExpr(init); + r->AddAttrs(attrs); + + activation_stack.back()->CondEvent()->AddSubEvent(std::move(r)); + } + +void ActivationManager::RedefingHandler(const IDPtr& id) + { + if ( activation_stack.empty() ) + return; + + auto hr = std::make_shared(ActivationEvent::HANDLER_REDEF); + hr->AddID(id); + + activation_stack.back()->CondEvent()->AddSubEvent(std::move(hr)); + } + +void ActivationManager::AddingBody(IDPtr func, std::shared_ptr ingr) + { + if ( activation_stack.empty() ) + return; + + auto b = std::make_shared(ActivationEvent::BODY); + b->AddID(func); + b->AddIngredients(std::move(ingr)); + + activation_stack.back()->CondEvent()->AddSubEvent(std::move(b)); + } + + } // namespace zeek::detail diff --git a/src/ActivationManager.h b/src/ActivationManager.h new file mode 100644 index 0000000000..09d6afd282 --- /dev/null +++ b/src/ActivationManager.h @@ -0,0 +1,311 @@ +// See the file "COPYING" in the main distribution directory for copyright. + +// Classes for supporting @if &analyze constructs. + +#pragma once + +#include "zeek/Expr.h" +#include "zeek/Func.h" + +namespace zeek::detail + { + +using ScriptFuncPtr = IntrusivePtr; +using AttrVec = std::unique_ptr>; + +class ActivationManager; + +/** + * Expresses an event (with one of the above types) occurring during an + * @if &analyze. Events reflect what *could* have happened rather than + * what *did* happen, so events will be present even for @if &analyze blocks + * that were skipped due to their condition evaluating to false. + * + * The current use for these events is to enable the CPP script compiler + * to generate run-time execution of @if &analyze conditionals. To do that, + * it needs a record of their associated effects. + * + * We use a "flat" class that encompasses all of the possibilities, rather + * than subclassing on the different types. This is because ActivationEvent's + * are a record for *reading* rather than conducting further processing + * directly; hence there aren't apt active methods to virtualize. If we + * used subclasses, we'd wind up having to cast to get to the specifics + * elements of a given event, which is clunky enough that it's not clear + * we gain anything useful. + */ +class ActivationEvent + { +public: + /** + * The different types of ActivationEvent's. + */ + enum AE_Type + { + // Represents an @if &analyze conditional, with a corresponding + // condition expression, and sub-events (i.e., other + // ActivationEvent's) for what occurs in the "true" and + // "false" branches. + COND, + + // Represents the introduction of a new global identifier. + CREATE_GLOBAL, + + // Represents adding a value (in particular, a ScriptFunc) to + // a particular global. + ADDING_GLOBAL_VAL, + + // Represents a global having its initialization value and/or + // attributes redef'd. + REDEF, + + // Represents an event handler being redef'd (which discards + // its current value). + HANDLER_REDEF, + + // Represents adding a body to a function/hook/event handler. + BODY, + }; + + // ActivationEvent's always have a type. All the other fields + // are optional, and are populated depending on the type. + ActivationEvent(AE_Type _et) : et(_et) { } + + // Type of the activation event. + AE_Type Type() const { return et; } + + // An associated expression associated. + void AddExpr(ExprPtr _expr) { expr = std::move(_expr); } + ExprPtr GetExpr() const { return expr; } + + // An associated identifier. + void AddID(IDPtr _id) { id = std::move(_id); } + IDPtr GetID() const { return id; } + + // An associated initialization class (equivalent to =/+=/-=). + void AddInitClass(InitClass _c) { c = _c; } + InitClass GetInitClass() const { return c; } + + // A set of associated attributes, or none if a nil pointer. + void AddAttrs(AttrVec& _attrs) + { + // It's a pity that the code base has settled on unique_ptr's + // for collections of attributes rather than shared_ptr's ... + if ( _attrs ) + { + attrs = std::make_unique>(); + *attrs = *_attrs; + } + } + const auto& GetAttrs() const { return attrs; } + + // A set of associated "ingredients" for building a function. + void AddIngredients(std::shared_ptr _ingr) { ingr = std::move(_ingr); } + const auto& GetIngredients() const { return ingr; } + + // Adds a "subevent" to this event, only valid for events that are + // themselves conditionals. Note that the subevent might itself + // be a (nested) conditional. + void AddSubEvent(std::shared_ptr ae) + { + ASSERT(et == COND); + CurrSubEvents().push_back(std::move(ae)); + } + + // Changes this events accrural of subevents to correspond to its + // "else" branch rather than its main/true branch. + void SwitchToElse() + { + ASSERT(et == COND); + ASSERT(in_true_branch); + in_true_branch = false; + } + + // Prints out the event (and any subevents) for debug purposes. + void Dump(int indent_level) const; + +private: + // Manages indentation when dumping events. + void Indent(int indent_level) const; + + using SubEvents = std::vector>; + + SubEvents& CurrSubEvents() { return in_true_branch ? T_sub_events : F_sub_events; } + + AE_Type et; + ExprPtr expr; + IDPtr id; + InitClass c = INIT_NONE; + AttrVec attrs; + std::shared_ptr ingr; + + // For events corresponding to conditionals, we track two sets + // of sub_events, one for the main (true) branch of the conditional, + // and one for the else (false) branch, if any, with in_true_branch + // reflecting which one we're working on (per CurrSubEvents()). + bool in_true_branch = true; // tells us which one to use + SubEvents T_sub_events; + SubEvents F_sub_events; + }; + +/** + * An "Activation" object tracks the status of a current @if &analyze + * conditional as it's being parsed. Its role is to keep track of what's + * up with the conditional for the live parsing, as opposed to what *could* + * have happened (which is instead reflected in a set of ActivationEvent's). + */ +class Activation + { +public: + Activation(ExprPtr cond, bool _is_activated, bool _parent_activated, int _cond_depth); + ~Activation(); + + // True if we're in the part of the @activiate-if conditional for + // which we should be incorporating statements (making changes to + // globals, adding function bodies, etc.). + bool IsActivated() const { return is_activated; } + + // Returns the @if (not @if &analyze) conditional depth associated + // with this activation. Used to tell whether a given @else or @endif + // corresponds to this @if &analyze, or something nested within it. + int CondDepth() const { return cond_depth; } + + // Returns the ActivationEvent associated with this @if &analyze. + auto CondEvent() const { return cond_event; } + + // Tells the Activation to switch from its main (conditinal-is-true) + // processing to its "else" (conditional-is-false) processing. + void SwitchToElse() + { + // We're done tracking globals for the current body. + ResetGlobals(); + + // Toggle our activation status *unless* our parent (another + // @if &analyze) was itself not active, in which case we + // stay inactive. + if ( parent_activated ) + is_activated = ! is_activated; + + // Keep the ActivationEvent in synch. + cond_event->SwitchToElse(); + } + + void AddGlobalID(IDPtr gid) { global_IDs.push_back(std::move(gid)); } + void AddGlobalVal(IDPtr gid) { global_vals.push_back(std::move(gid)); } + +private: + // If we weren't active, then undo the effects that the parser had + // to make (installing new globals and event handlers) in order + // to assess the correctness of the code within the block. + void ResetGlobals(); + + std::shared_ptr cond_event; + + // True if our parent was itself activated. Always true if there + // wasn't an outer @if &analyze. + bool parent_activated; + + // Whether we're currently activated. + bool is_activated; + + // Depth of @if conditionals when this activation began. + int cond_depth; + + // Set of globals that were added during processing of the current body. + std::vector global_IDs; + + // Similar: set of globals for which we added values. We track + // this only for function/hook/event handler bodies. + std::vector global_vals; + }; + +/** + * Class for managing the processing of @if &analyze's. Deals with + * potential nesting, and with constructing a trace of the associated + * ActivationEvents. + */ +class ActivationManager + { +public: + ActivationManager() = default; + ~ActivationManager(); + + // True if we are currently inside an @if &analyze. + bool InsideConditional() const { return ! activation_stack.empty(); } + + // True if the current @if &analyze corresponds to the given + // @if conditional-depth. Needed to disambiguate @else and @endif + // tokens in the presence of possible inter-nesting of @if and + // @if &analyze constructs. + bool InsideConditional(int cond_depth) const + { + if ( activation_stack.empty() ) + return false; + + return activation_stack.back()->CondDepth() == cond_depth; + } + + // True if processing of the current script is "activated". This + // is the usual state of things, other than when inside the branch + // of an @if &analyze that doesn't correspond to its condition. + bool IsActivated() const + { + return activation_stack.empty() || activation_stack.back()->IsActivated(); + } + + // Returns the nesting level of @if &analyze's. Used by the scanner + // to find dangling conditionals at the end of files. Typed as an + // integer to match similar other structures in the scanner. + int ActivationDepth() const { return static_cast(activation_stack.size()); } + + // Tells the manager to being a new @if &analyze conditional. + // "cond" is the associated condition, "activate" reflects whether + // the condition is true, and "cond_depth" is the depth of any + // parent @if constructs. + void Start(ExprPtr cond, bool activate, int cond_depth); + + // Tells the manager that an @else has been seen for its innermost + // @if &analyze. + void SwitchToElse(); + + // Tells the manager that an @endif has been seen for its innermost + // @if &analyze. + void End(); + + // Tells the manager that the parser is creating a new global + // identifier. + void CreatingGlobalID(IDPtr gid); + + // Tells the manager that the parser is adding a value to a global. + void AddingGlobalVal(IDPtr gid); + + // Tells the manager that the parser is redef'ing an identifier using + // the associated values. + void AddingRedef(const IDPtr& id, InitClass c, ExprPtr init, AttrVec& attrs); + + // Tells the manger that the given identifier's event handler is + // being redef'd. + void RedefingHandler(const IDPtr& id); + + // Tells the manager that the parser is adding a function body to + // the given function. + void AddingBody(IDPtr func, std::shared_ptr ingr); + + // Returns a list of top-level ActivationEvent's. These will all + // be COND events, which in general should be traversed recursively + // to extract what happened inside the conditionals, including + // additional conditionals potentially nested inside. + const auto& ActivationEvents() const { return activation_events; } + +private: + // Currently live @if &analyze information. + std::vector> activation_stack; + + // A trace of all of the top-level @if &analyze events. Any + // nested @if &analyze's are available from the top-level events + // as sub-events. + std::vector> activation_events; + }; + +extern ActivationManager* activation_mgr; + + } // namespace zeek::detail diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6baf3fa3dc..80777f55ce 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -282,6 +282,7 @@ set(MAIN_SRCS module_util.cc zeek-affinity.cc zeek-setup.cc + ActivationManager.cc Anon.cc Attr.cc Base64.cc