diff --git a/src/Expr.cc b/src/Expr.cc index b3e8d5572d..4d237ffdd9 100644 --- a/src/Expr.cc +++ b/src/Expr.cc @@ -3187,7 +3187,7 @@ HasFieldExpr::HasFieldExpr(ExprPtr arg_op, const char* arg_field_name) HasFieldExpr::~HasFieldExpr() { - delete field_name; + delete[] field_name; } ValPtr HasFieldExpr::Fold(Val* v) const diff --git a/src/Var.cc b/src/Var.cc index d23edc0df8..72d942f43f 100644 --- a/src/Var.cc +++ b/src/Var.cc @@ -6,6 +6,7 @@ #include +#include "zeek/ActivationManager.h" #include "zeek/Desc.h" #include "zeek/EventRegistry.h" #include "zeek/Expr.h" @@ -234,7 +235,7 @@ static void make_var(const IDPtr& id, TypePtr t, InitClass c, ExprPtr init, { if ( IsFunc(id->GetType()->Tag()) ) add_prototype(id, t.get(), attr.get(), init); - else + else if ( activation_mgr->IsActivated() ) id->Error("already defined", init.get()); return; @@ -405,7 +406,18 @@ static void make_var(const IDPtr& id, TypePtr t, InitClass c, ExprPtr init, void add_global(const IDPtr& id, TypePtr t, InitClass c, ExprPtr init, std::unique_ptr> attr, DeclType dt) { - make_var(id, std::move(t), c, std::move(init), std::move(attr), dt, true); + bool do_init = activation_mgr->IsActivated(); + + if ( dt == VAR_REDEF ) + { + activation_mgr->AddingRedef(id, c, init, attr); + + if ( ! do_init ) + // Don't actually change the attributes. + attr = nullptr; + } + + make_var(id, std::move(t), c, std::move(init), std::move(attr), dt, do_init); } StmtPtr add_local(IDPtr id, TypePtr t, InitClass c, ExprPtr init, @@ -711,6 +723,8 @@ void begin_func(IDPtr id, const char* module_name, FunctionFlavor flavor, bool i else if ( is_redef ) id->Error("redef of not-previously-declared value"); + bool is_activated = activation_mgr->IsActivated(); + if ( id->HasVal() ) { FunctionFlavor id_flavor = id->GetVal()->AsFunc()->Flavor(); @@ -724,12 +738,17 @@ void begin_func(IDPtr id, const char* module_name, FunctionFlavor flavor, bool i case FUNC_FLAVOR_EVENT: case FUNC_FLAVOR_HOOK: if ( is_redef ) - // Clear out value so it will be replaced. - id->SetVal(nullptr); + { + activation_mgr->RedefingHandler(id); + + if ( ! is_activated ) + // Clear out value so it will be replaced. + id->SetVal(nullptr); + } break; case FUNC_FLAVOR_FUNCTION: - if ( ! id->IsRedefinable() ) + if ( ! id->IsRedefinable() && is_activated ) id->Error("already defined", t.get()); break; @@ -753,12 +772,15 @@ void begin_func(IDPtr id, const char* module_name, FunctionFlavor flavor, bool i if ( ! check_params(i, prototype, args, canon_args, module_name) ) break; - if ( Attr* depr_attr = find_attr(current_scope()->Attrs().get(), ATTR_DEPRECATED) ) - current_scope()->GetID()->MakeDeprecated(depr_attr->GetExpr()); + if ( is_activated ) + { + if ( Attr* depr_attr = find_attr(current_scope()->Attrs().get(), ATTR_DEPRECATED) ) + current_scope()->GetID()->MakeDeprecated(depr_attr->GetExpr()); - // Reset the AST node statistics to track afresh for this function. - Stmt::ResetNumStmts(); - Expr::ResetNumExprs(); + // Reset the AST node statistics to track afresh for this function. + Stmt::ResetNumStmts(); + Expr::ResetNumExprs(); + } } class OuterIDBindingFinder : public TraversalCallback @@ -846,7 +868,7 @@ void end_func(StmtPtr body, const char* module_name, bool free_of_conditionals) oi->num_stmts = Stmt::GetNumStmts(); oi->num_exprs = Expr::GetNumExprs(); - auto ingredients = std::make_unique(pop_scope(), std::move(body), + auto ingredients = std::make_shared(pop_scope(), std::move(body), module_name); auto id = ingredients->GetID(); if ( ! id->HasVal() ) @@ -854,27 +876,27 @@ void end_func(StmtPtr body, const char* module_name, bool free_of_conditionals) auto f = make_intrusive(id); id->SetVal(make_intrusive(std::move(f))); id->SetConst(); + activation_mgr->AddingGlobalVal(id); } - id->GetVal()->AsFunc()->AddBody(ingredients->Body(), ingredients->Inits(), - ingredients->FrameSize(), ingredients->Priority(), - ingredients->Groups()); - script_coverage_mgr.AddFunction(id, ingredients->Body()); auto func_ptr = cast_intrusive(id->GetVal())->AsFuncPtr(); auto func = cast_intrusive(func_ptr); func->SetScope(ingredients->Scope()); - for ( const auto& group : ingredients->Groups() ) - group->AddFunc(func); + activation_mgr->AddingBody(id, ingredients); - analyze_func(std::move(func)); + if ( activation_mgr->IsActivated() ) + { + func->AddBody(ingredients->Body(), ingredients->Inits(), ingredients->FrameSize(), + ingredients->Priority(), ingredients->Groups()); - // Note: ideally, something would take ownership of this memory until the - // end of script execution, but that's essentially the same as the - // lifetime of the process at the moment, so ok to "leak" it. - ingredients.release(); + for ( const auto& group : ingredients->Groups() ) + group->AddFunc(func); + + analyze_func(std::move(func)); + } } IDPList gather_outer_ids(ScopePtr scope, StmtPtr body) diff --git a/src/input.h b/src/input.h index 205ffe734d..249a8ec781 100644 --- a/src/input.h +++ b/src/input.h @@ -23,7 +23,7 @@ extern void add_to_name_list(char* s, char delim, zeek::name_list& nl); extern void begin_RE(); -extern void do_atif(zeek::detail::Expr* expr); +extern void do_atif(zeek::detail::Expr* expr, bool is_activate); extern void do_atifdef(const char* id); extern void do_atifndef(const char* id); extern void do_atelse(); diff --git a/src/parse.y b/src/parse.y index 4db15571ab..e61360d468 100644 --- a/src/parse.y +++ b/src/parse.y @@ -5,10 +5,10 @@ // Switching parser table type fixes ambiguity problems. %define lr.type ielr -%expect 211 +%expect 212 %token TOK_ADD TOK_ADD_TO TOK_ADDR TOK_ANY -%token TOK_ATENDIF TOK_ATELSE TOK_ATIF TOK_ATIFDEF TOK_ATIFNDEF +%token TOK_ATENDIF TOK_ATELSE TOK_ATIF TOK_ATACTIVATEIF TOK_ATIFDEF TOK_ATIFNDEF %token TOK_BOOL TOK_BREAK TOK_CASE TOK_OPTION TOK_CONST %token TOK_CONSTANT TOK_COPY TOK_COUNT TOK_DEFAULT TOK_DELETE %token TOK_DOUBLE TOK_ELSE TOK_ENUM TOK_EVENT TOK_EXPORT TOK_FALLTHROUGH @@ -31,6 +31,18 @@ %token TOK_ATTR_TYPE_COLUMN TOK_ATTR_DEPRECATED %token TOK_ATTR_IS_ASSIGNED TOK_ATTR_IS_USED TOK_ATTR_ORDERED +// Heads-up, this one is a weirdo. It combines both the attribute and +// a leading ')' before it (the two can be separated by spaces/tabs, but +// no newlines). This is necessary because if we use the more natural +// +// TOK_ATIF '(' expr ')' TOK_ATTR_ANALYZE +// +// then the parser needs to look ahead past the ')' to see if the attribute +// is there. If it *isn't*, then the scanner will return the first token +// of the conditional block for the look-ahead ... which will break the parse +// if that block should in fact have been skipped. +%token TOK_ATTR_ANALYZE + %token TOK_DEBUG %token TOK_NO_TEST @@ -98,6 +110,7 @@ #include "zeek/RE.h" #include "zeek/Scope.h" #include "zeek/Reporter.h" +#include "zeek/ActivationManager.h" #include "zeek/ScriptCoverageManager.h" #include "zeek/ScriptValidation.h" #include "zeek/zeekygen/Manager.h" @@ -139,16 +152,19 @@ extern const char* g_curr_debug_error; extern int in_when_cond; static int in_hook = 0; -int in_init = 0; -int in_record = 0; +static int in_init = 0; +static int in_body = 0; +static int in_global_stmts = 0; +static int in_record = 0; static int in_record_redef = 0; static int in_enum_redef = 0; -bool resolving_global_ID = false; -bool defining_global_ID = false; -std::vector saved_in_init; +static bool resolving_global_ID = false; +static bool defining_global_ID = false; +static bool is_activated = true; +static std::vector saved_in_init; static int expr_list_has_opt_comma = 0; -std::vector> locals_at_this_scope; +static std::vector> locals_at_this_scope; static std::unordered_set out_of_scope_locals; static Location func_hdr_location; @@ -321,6 +337,9 @@ static void build_global(ID* id, Type* t, InitClass ic, Expr* e, add_global(id_ptr, std::move(t_ptr), ic, e_ptr, std::move(attrs_ptr), dt); + if ( ! activation_mgr->IsActivated() ) + return; + if ( dt == VAR_REDEF ) zeekygen_mgr->Redef(id, ::filename, ic, std::move(e_ptr)); else @@ -392,9 +411,13 @@ zeek: auto loc = zeek::detail::GetCurrentLocation(); if ( loc.filename ) set_location(loc); + + ++in_global_stmts; } stmt_list { + --in_global_stmts; + if ( stmts ) stmts->AsStmtList()->Stmts().push_back($3); else @@ -1400,29 +1423,49 @@ decl: } | TOK_REDEF TOK_ENUM global_id TOK_ADD_TO '{' - { ++in_enum_redef; parse_redef_enum($3); zeekygen_mgr->Redef($3, ::filename); } + { + ++in_enum_redef; + parse_redef_enum($3); + zeekygen_mgr->Redef($3, ::filename); + } enum_body '}' ';' { + if ( activation_mgr->InsideConditional() ) + reporter->Error("enum redef cannot appear inside @if &analyze"); --in_enum_redef; // Zeekygen already grabbed new enum IDs as the type created them. } | TOK_REDEF TOK_RECORD global_id '$' TOK_ID - { cur_decl_type_id = $3; zeekygen_mgr->Redef($3, ::filename, INIT_EXTRA); } + { + cur_decl_type_id = $3; + zeekygen_mgr->Redef($3, ::filename, INIT_EXTRA); + } TOK_ADD_TO '{' attr_list '}' ';' { + if ( activation_mgr->InsideConditional() ) + reporter->Error("record redef cannot appear inside @if &analyze"); cur_decl_type_id = 0; parse_redef_record_field($3, $5, INIT_EXTRA, std::unique_ptr>($9)); } + | TOK_REDEF TOK_RECORD global_id '$' TOK_ID - { cur_decl_type_id = $3; zeekygen_mgr->Redef($3, ::filename, INIT_REMOVE); } + { + cur_decl_type_id = $3; + zeekygen_mgr->Redef($3, ::filename, INIT_REMOVE); + } TOK_REMOVE_FROM '{' attr_list '}' ';' { + if ( activation_mgr->InsideConditional() ) + reporter->Error("record redef cannot appear inside @if &analyze"); cur_decl_type_id = 0; parse_redef_record_field($3, $5, INIT_REMOVE, std::unique_ptr>($9)); } | TOK_REDEF TOK_RECORD global_id - { cur_decl_type_id = $3; zeekygen_mgr->Redef($3, ::filename); } + { + cur_decl_type_id = $3; + zeekygen_mgr->Redef($3, ::filename); + } TOK_ADD_TO '{' { ++in_record; ++in_record_redef; } type_decl_list @@ -1433,6 +1476,8 @@ decl: if ( ! $3->GetType() ) $3->Error("unknown identifier"); + else if ( activation_mgr->InsideConditional() ) + reporter->Error("record redef cannot appear inside @if &analyze"); else extend_record($3, std::unique_ptr($8), std::unique_ptr>($11)); @@ -1465,7 +1510,19 @@ conditional_list: conditional: TOK_ATIF '(' expr ')' - { do_atif($3); } + { do_atif($3, false); } + | TOK_ATIF '(' expr TOK_ATTR_ANALYZE + { + if ( in_body ) + reporter->Error("@if &analyze cannot appear inside a function body"); + do_atif($3, true); + } + | TOK_ATACTIVATEIF '(' expr ')' + { + if ( in_body ) + reporter->Error("@if &analyze cannot appear inside a function body"); + do_atif($3, true); + } | TOK_ATIFDEF '(' TOK_ID ')' { do_atifdef($3); } | TOK_ATIFNDEF '(' TOK_ID ')' @@ -1516,6 +1573,7 @@ func_body: { saved_in_init.push_back(in_init); in_init = 0; + ++in_body; locals_at_this_scope.clear(); out_of_scope_locals.clear(); @@ -1525,6 +1583,7 @@ func_body: { in_init = saved_in_init.back(); saved_in_init.pop_back(); + --in_body; } '}' @@ -1545,12 +1604,14 @@ lambda_body: { saved_in_init.push_back(in_init); in_init = 0; + ++in_body; } stmt_list { in_init = saved_in_init.back(); saved_in_init.pop_back(); + --in_body; } '}' @@ -1963,11 +2024,21 @@ stmt: ; stmt_list: - stmt_list stmt + stmt_list { is_activated = activation_mgr->IsActivated(); } stmt { - set_location(@1, @2); - $1->AsStmtList()->Stmts().push_back($2); - $1->UpdateLocationEndInfo(@2); + set_location(@1, @3); + + // We can't simply test activation_mgr->IsActivated() + // here because the parser can wind up looking ahead + // to the @endif token and restoring activation that + // in fact was off for the statement. So we capture + // the activation state prior to parsing the statement + // in "is_activated" and test that instead. + if ( ! in_global_stmts || is_activated ) + { + $1->AsStmtList()->Stmts().push_back($3); + $1->UpdateLocationEndInfo(@3); + } } | { $$ = new StmtList(); } @@ -2207,8 +2278,10 @@ global_or_event_id: resolving_global_ID ? current_module.c_str() : 0; - $$ = install_ID($1, module_name, - true, is_export).release(); + auto gid = install_ID($1, module_name, + true, is_export); + activation_mgr->CreatingGlobalID(gid); + $$ = gid.release(); } } ; diff --git a/src/scan.l b/src/scan.l index c961b928e2..39976af9c2 100644 --- a/src/scan.l +++ b/src/scan.l @@ -29,8 +29,6 @@ #include "zeek/DNS_Mgr.h" #include "zeek/Expr.h" #include "zeek/Func.h" -#include "zeek/Stmt.h" -#include "zeek/IntrusivePtr.h" #include "zeek/Val.h" #include "zeek/Var.h" #include "zeek/Debug.h" @@ -41,6 +39,7 @@ #include "zeek/Traverse.h" #include "zeek/module_util.h" #include "zeek/ScannedFile.h" +#include "zeek/ActivationManager.h" #include "zeek/analyzer/Analyzer.h" #include "zeek/zeekygen/Manager.h" @@ -55,9 +54,10 @@ extern YYLTYPE yylloc; // holds start line and column of token extern zeek::EnumType* cur_enum_type; // Track the @if... depth. -static std::intptr_t conditional_depth = 0; +static int conditional_depth = 0; zeek::detail::int_list entry_cond_depth; // @if depth upon starting file +zeek::detail::int_list entry_act_depth; // @if &analyze depth upon starting file zeek::detail::int_list entry_pragma_stack_depth; // @pragma push depth upon starting file static std::vector pragma_stack; // stack of @pragma pushes @@ -127,6 +127,9 @@ static std::string find_relative_script_file(const std::string& filename) static void start_conditional() { + if ( activation_mgr->InsideConditional() ) + zeek::reporter->Warning("@conditional inside @if &analyze"); + ++conditional_depth; ++conditional_epoch; @@ -155,17 +158,18 @@ static void do_pragma(const std::string& pragma) if ( parts[0] == "push" ) { - if ( parts.size() < 2 ) - { - zeek::reporter->FatalError("@pragma push without value"); - return; - } + if ( parts.size() < 2 ) + { + zeek::reporter->FatalError("@pragma push without value"); + return; + } - if ( known_stack_pragmas.count(parts[1]) == 0 ) - zeek::reporter->Warning("pushing unknown @pragma value '%s'", parts[1].c_str()); + if ( known_stack_pragmas.count(parts[1]) == 0 ) + zeek::reporter->Warning("pushing unknown @pragma value '%s'", parts[1].c_str()); - pragma_stack.push_back(parts[1]); + pragma_stack.push_back(parts[1]); } + else if ( parts[0] == "pop" ) { if ( pragma_stack.empty() || pragma_stack.size() == entry_pragma_stack_depth.back() ) @@ -176,10 +180,11 @@ static void do_pragma(const std::string& pragma) // Popping with a value: Verify it's popping the right thing. Not providing // a pop value itself is valid. Don't return, probably blows up below anyway. - if ( parts.size() > 1 && pragma_stack.back() != parts[1] ) { + if ( parts.size() > 1 && pragma_stack.back() != parts[1] ) + { zeek::reporter->Error("@pragma pop with unexpected '%s', expected '%s'", parts[1].c_str(), pragma_stack.back().c_str()); - } + } // Just pop anything pragma_stack.pop_back(); @@ -397,6 +402,8 @@ when return TOK_WHEN; &backend return TOK_ATTR_BACKEND; &ordered return TOK_ATTR_ORDERED; +")"{OWS}&analyze return TOK_ATTR_ANALYZE; // see parse.y for discussion of weirdness + @deprecated.* { auto num_files = file_stack.length(); auto comment = zeek::util::skip_whitespace(yytext + 11); @@ -534,11 +541,12 @@ when return TOK_WHEN; @endif do_atendif(); @if start_conditional(); +&analyze zeek::reporter->Warning("@if &analyze inside regular @if"); @ifdef start_conditional(); @ifndef start_conditional(); @else return TOK_ATELSE; @endif return TOK_ATENDIF; -[^@\r\n]+ /* eat */ +[^@&\r\n]+ /* eat */ . /* eat */ T RET_CONST(zeek::val_mgr->True()->Ref()) @@ -802,6 +810,7 @@ static int load_files(const char* orig_file) current_file_has_conditionals = files_with_conditionals.count(filename) > 0; entry_cond_depth.push_back(conditional_depth); + entry_act_depth.push_back(activation_mgr->ActivationDepth()); entry_pragma_stack_depth.push_back(pragma_stack.size()); return 1; @@ -846,10 +855,8 @@ static void resume_processing() BEGIN(INITIAL); } -void do_atif(zeek::detail::Expr* expr) +void do_atif(zeek::detail::Expr* expr, bool is_activate) { - start_conditional(); - LocalNameFinder cb; expr->Traverse(&cb); zeek::ValPtr val; @@ -865,11 +872,28 @@ void do_atif(zeek::detail::Expr* expr) if ( ! val ) { expr->Error("invalid expression in @if"); + + if ( ! is_activate ) + start_conditional(); + return; } - if ( ! val->AsBool() ) - begin_ignoring(); + bool is_true = val->AsBool(); + + if ( is_activate ) + { + if ( conditional_depth > 0 ) + zeek::reporter->Warning("@if &analyze inside conditional"); + activation_mgr->Start({zeek::NewRef{}, expr}, is_true, conditional_depth); + } + else + { + start_conditional(); + + if ( ! is_true ) + begin_ignoring(); + } } void do_atifdef(const char* id) @@ -904,6 +928,12 @@ void do_atifndef(const char *id) void do_atelse() { + if ( activation_mgr->InsideConditional(conditional_depth) ) + { // This is the @else corresponding to an @if &analyze. + activation_mgr->SwitchToElse(); + return; + } + if ( conditional_depth == 0 ) zeek::reporter->Error("@else without @if..."); @@ -918,6 +948,15 @@ void do_atelse() void do_atendif() { + if ( activation_mgr->InsideConditional(conditional_depth) ) + { // We're ending an @if &analyze. + if ( activation_mgr->ActivationDepth() <= entry_act_depth.back() ) + zeek::reporter->Error("unbalanced @if &analyze... @endif"); + else + activation_mgr->End(); + return; + } + if ( conditional_depth <= entry_cond_depth.back() ) zeek::reporter->Error("unbalanced @if... @endif"); @@ -1000,6 +1039,13 @@ int yywrap() entry_cond_depth.pop_back(); } + if ( entry_act_depth.size() > 0 ) + { + if ( activation_mgr->ActivationDepth() > entry_act_depth.back() ) + zeek::reporter->FatalError("unbalanced @if &analyze... @endif"); + entry_act_depth.pop_back(); + } + if ( entry_pragma_stack_depth.size() > 0 ) { if ( pragma_stack.size() > entry_pragma_stack_depth.back() ) diff --git a/src/zeek-setup.cc b/src/zeek-setup.cc index 46d5eacef0..5cc5963d64 100644 --- a/src/zeek-setup.cc +++ b/src/zeek-setup.cc @@ -23,6 +23,7 @@ #define DOCTEST_CONFIG_IMPLEMENT #include "zeek/3rdparty/doctest.h" +#include "zeek/ActivationManager.h" #include "zeek/Anon.h" #include "zeek/DFA.h" #include "zeek/DNS_Mgr.h" @@ -183,6 +184,7 @@ zeek::plugin::Manager* zeek::plugin_mgr = nullptr; zeek::detail::RuleMatcher* zeek::detail::rule_matcher = nullptr; zeek::detail::DNS_Mgr* zeek::detail::dns_mgr = nullptr; zeek::detail::TimerMgr* zeek::detail::timer_mgr = nullptr; +zeek::detail::ActivationManager* zeek::detail::activation_mgr; zeek::logging::Manager* zeek::log_mgr = nullptr; zeek::threading::Manager* zeek::thread_mgr = nullptr; @@ -443,6 +445,7 @@ static void terminate_zeek() delete session_mgr; delete fragment_mgr; delete telemetry_mgr; + delete activation_mgr; #ifdef HAVE_SPICY delete spicy_mgr; #endif @@ -673,6 +676,7 @@ SetupResult setup(int argc, char** argv, Options* zopts) auto zeekygen_cfg = options.zeekygen_config_file.value_or(""); zeekygen_mgr = new zeekygen::detail::Manager(zeekygen_cfg, zeek_argv[0]); + activation_mgr = new ActivationManager(); add_essential_input_file("base/init-bare.zeek"); add_essential_input_file("builtin-plugins/__preload__.zeek");