diff --git a/doc/scripts/builtins.rst b/doc/scripts/builtins.rst index d274de6b7b..ba786ba0d2 100644 --- a/doc/scripts/builtins.rst +++ b/doc/scripts/builtins.rst @@ -417,10 +417,6 @@ The Bro scripting language supports the following built-in types. Writing to files like this for logging usually isn't recommended, for better logging support see :doc:`/logging`. -.. bro:type:: func - - See :bro:type:`function`. - .. bro:type:: function Function types in Bro are declared using:: @@ -504,6 +500,74 @@ The Bro scripting language supports the following built-in types. identifier and the body of each will be executed in turn. Ordering of execution can be influenced with :bro:attr:`&priority`. +.. bro:type:: hook + + A hook is another flavor of function that shares characteristics of + both a :bro:type:`function` and a :bro:type:`event`. They are like + events in that many handler bodies can be defined for the same hook + identifier, they have no return vale, and the order of execution + can be enforced with :bro:attr:`&priority`. They are more like + functions in the way they are invoked/called, because, unlike + events, their execution is immediate and they do not get scheduled + through an event queue. Also, a unique feature of a hook is that + a given hook handler body can short-circuit the execution of + remaining hook handlers simply by exiting from the body as a result + of a ``break`` statement (as opposed to a ``return`` or just + reaching the end of the body). + + A hook type is declared like:: + + hook( argument* ) + + where *argument* is a (possibly empty) comma-separated list of + arguments. For example: + + .. code:: bro + + global myhook: hook(s: string) + + Here ``myhook`` is the hook type identifier and no hook handler + bodies have been defined for it yet. To define some hook handler + bodies the syntax looks like: + + .. code:: bro + + hook myhook(s: string) &priority=10 + { + print "priority 10 myhook handler", s; + s = "bye"; + } + + hook myhook(s: string) + { + print "break out of myhook handling", s; + break; + } + + hook myhook(s: string) &priority=-5 + { + print "not going to happen", s; + } + + Note that, although the first (forward) declaration of ``myhook`` as + a hook type isn't strictly required, when it is provided, the + argument types must match. + + To invoke immediate execution of all hook handler bodies, a ``hook`` + statement must be used: + + .. code:: bro + + hook myhook("hi"); + + And the output would like like:: + + priority 10 myhook handler, hi + break out of myhook handling, bye + + Note how the modification to arguments can be seen by remaining + hook handlers. + Attributes ---------- diff --git a/src/Attr.cc b/src/Attr.cc index bdf247b4f5..6e0769c7b0 100644 --- a/src/Attr.cc +++ b/src/Attr.cc @@ -71,7 +71,9 @@ void Attr::DescribeReST(ODesc* d) const else if ( expr->Type()->Tag() == TYPE_FUNC ) { - d->Add(":bro:type:`func`"); + d->Add(":bro:type:`"); + d->Add(expr->Type()->AsFuncType()->FlavorString()); + d->Add("`"); } else @@ -401,13 +403,13 @@ void Attributes::CheckAttr(Attr* a) case ATTR_GROUP: if ( type->Tag() != TYPE_FUNC || - ! type->AsFuncType()->IsEvent() ) + type->AsFuncType()->Flavor() != FUNC_FLAVOR_EVENT ) Error("&group only applicable to events"); break; case ATTR_ERROR_HANDLER: if ( type->Tag() != TYPE_FUNC || - ! type->AsFuncType()->IsEvent() ) + type->AsFuncType()->Flavor() != FUNC_FLAVOR_EVENT ) Error("&error_handler only applicable to events"); break; diff --git a/src/BroDoc.cc b/src/BroDoc.cc index 1e2d7d52ea..23b1f56aaa 100644 --- a/src/BroDoc.cc +++ b/src/BroDoc.cc @@ -271,6 +271,7 @@ void BroDoc::WriteInterface(const char* heading, char underline, WriteBroDocObjList(state_vars, isPublic, "State Variables", sub, isShort); WriteBroDocObjList(types, isPublic, "Types", sub, isShort); WriteBroDocObjList(events, isPublic, "Events", sub, isShort); + WriteBroDocObjList(hooks, isPublic, "Hooks", sub, isShort); WriteBroDocObjList(functions, isPublic, "Functions", sub, isShort); WriteBroDocObjList(redefs, isPublic, "Redefinitions", sub, isShort); } diff --git a/src/BroDoc.h b/src/BroDoc.h index ac6ff0a59b..79f02b7110 100644 --- a/src/BroDoc.h +++ b/src/BroDoc.h @@ -179,6 +179,30 @@ public: all.push_back(o); } + /** + * Schedules documentation of a hook declared by the script. + * @param o A pointer to a BroDocObj which contains the internal + * Bro language representation of the script hook and + * also any associated comments about it. + */ + void AddHook(const BroDocObj* o) + { + hooks.push_back(o); + all.push_back(o); + } + + /** + * Schedules documentation of a hook handler declared by the script. + * @param o A pointer to a BroDocObj which contains the internal + * Bro language representation of the script hook handler and + * also any associated comments about it. + */ + void AddHookHandler(const BroDocObj* o) + { + hook_handlers.push_back(o); + all.push_back(o); + } + /** * Schedules documentation of a function declared by the script. * @param o A pointer to a BroDocObj which contains the internal @@ -241,6 +265,8 @@ protected: BroDocObjList notices; BroDocObjList events; BroDocObjList event_handlers; + BroDocObjList hooks; + BroDocObjList hook_handlers; BroDocObjMap functions; BroDocObjList redefs; diff --git a/src/Expr.cc b/src/Expr.cc index e6936267d8..733e0fe9a5 100644 --- a/src/Expr.cc +++ b/src/Expr.cc @@ -4374,7 +4374,7 @@ bool InExpr::DoUnserialize(UnserialInfo* info) return true; } -CallExpr::CallExpr(Expr* arg_func, ListExpr* arg_args) +CallExpr::CallExpr(Expr* arg_func, ListExpr* arg_args, bool in_hook) : Expr(EXPR_CALL) { func = arg_func; @@ -4402,8 +4402,33 @@ CallExpr::CallExpr(Expr* arg_func, ListExpr* arg_args) if ( ! yield ) { - Error("event called in expression"); - SetError(); + switch ( func_type->AsFuncType()->Flavor() ) { + + case FUNC_FLAVOR_FUNCTION: + Error("function has no yield type"); + SetError(); + break; + + case FUNC_FLAVOR_EVENT: + Error("event called in expression, use event statement instead"); + SetError(); + break; + + case FUNC_FLAVOR_HOOK: + // It's fine to not have a yield if it's known that the call + // is being done from a hook statement. + if ( ! in_hook ) + { + Error("hook called in expression, use hook statement instead"); + SetError(); + } + break; + + default: + Error("invalid function flavor"); + SetError(); + break; + } } else SetType(yield->Ref()); diff --git a/src/Expr.h b/src/Expr.h index c16cf86612..bd4824f8ee 100644 --- a/src/Expr.h +++ b/src/Expr.h @@ -959,7 +959,7 @@ protected: class CallExpr : public Expr { public: - CallExpr(Expr* func, ListExpr* args); + CallExpr(Expr* func, ListExpr* args, bool in_hook = false); ~CallExpr(); Expr* Func() const { return func; } diff --git a/src/Func.cc b/src/Func.cc index 582de1d9bb..27acce4f04 100644 --- a/src/Func.cc +++ b/src/Func.cc @@ -284,8 +284,8 @@ Val* BroFunc::Call(val_list* args, Frame* parent) const #endif if ( ! bodies.size() ) { - // Can only happen for events. - assert(IsEvent()); + // Can only happen for events and hooks. + assert(Flavor() == FUNC_FLAVOR_EVENT || Flavor() == FUNC_FLAVOR_HOOK); loop_over_list(*args, i) Unref((*args)[i]); return 0 ; @@ -309,7 +309,7 @@ Val* BroFunc::Call(val_list* args, Frame* parent) const DescribeDebug(&d, args); g_trace_state.LogTrace("%s called: %s\n", - IsEvent() ? "event" : "function", d.Description()); + FType()->FlavorString().c_str(), d.Description()); } loop_over_list(*args, i) @@ -348,6 +348,12 @@ Val* BroFunc::Call(val_list* args, Frame* parent) const parent->SetDelayed(); break; } + + if ( flow == FLOW_BREAK && Flavor() == FUNC_FLAVOR_HOOK ) + { + // short-circuit execution of remaining hook handler bodies + break; + } } // Warn if the function returns something, but we returned from @@ -380,7 +386,7 @@ void BroFunc::AddBody(Stmt* new_body, id_list* new_inits, int new_frame_size, new_body = AddInits(new_body, new_inits); - if ( ! IsEvent() ) + if ( Flavor() == FUNC_FLAVOR_FUNCTION ) { // For functions, we replace the old body with the new one. assert(bodies.size() <= 1); diff --git a/src/Func.h b/src/Func.h index 909b2a9802..7f9627b66d 100644 --- a/src/Func.h +++ b/src/Func.h @@ -25,7 +25,7 @@ public: virtual ~Func(); virtual int IsPure() const = 0; - int IsEvent() const { return FType()->IsEvent(); } + function_flavor Flavor() const { return FType()->Flavor(); } struct Body { Stmt* stmts; diff --git a/src/ID.cc b/src/ID.cc index dcf163650c..959ad9b07d 100644 --- a/src/ID.cc +++ b/src/ID.cc @@ -107,7 +107,8 @@ void ID::SetVal(Val* v, Opcode op, bool arg_weak_ref) #endif if ( type && val && - type->Tag() == TYPE_FUNC && type->AsFuncType()->IsEvent() ) + type->Tag() == TYPE_FUNC && + type->AsFuncType()->Flavor() == FUNC_FLAVOR_EVENT ) { EventHandler* handler = event_registry->Lookup(name); if ( ! handler ) @@ -657,7 +658,7 @@ void ID::DescribeReSTShort(ODesc* d) const break; case TYPE_FUNC: - d->Add(type->AsFuncType()->IsEvent() ? "event" : type_name(t)); + d->Add(type->AsFuncType()->FlavorString()); break; case TYPE_ENUM: diff --git a/src/SerialTypes.h b/src/SerialTypes.h index c47ff19298..a18c9bcc65 100644 --- a/src/SerialTypes.h +++ b/src/SerialTypes.h @@ -165,6 +165,7 @@ SERIAL_STMT(EVENT_BODY_LIST, 16) SERIAL_STMT(INIT_STMT, 17) SERIAL_STMT(NULL_STMT, 18) SERIAL_STMT(WHEN_STMT, 19) +SERIAL_STMT(HOOK_STMT, 20) #define SERIAL_TYPE(name, val) SERIAL_CONST(name, val, BRO_TYPE) SERIAL_TYPE(BRO_TYPE, 1) diff --git a/src/Serializer.cc b/src/Serializer.cc index fc6d00d06c..3dea7ee9e4 100644 --- a/src/Serializer.cc +++ b/src/Serializer.cc @@ -389,23 +389,35 @@ bool Serializer::UnserializeCall(UnserialInfo* info) { if ( info->print ) fprintf(info->print, "%s [%.06f] %s(%s)\n", - functype->IsEvent() ? "Event" : "Function call", + functype->FlavorString().c_str(), time, name, types ? d.Description() : ""); - if ( functype->IsEvent() ) + switch ( functype->Flavor() ) { + + case FUNC_FLAVOR_EVENT: { EventHandler* handler = event_registry->Lookup(name); assert(handler); + if ( ! info->ignore_callbacks ) GotEvent(name, time, handler, args); + + break; } - else + + case FUNC_FLAVOR_FUNCTION: + case FUNC_FLAVOR_HOOK: if ( ! info->ignore_callbacks ) GotFunctionCall(name, time, id->ID_Val()->AsFunc(), args); + break; + + default: + reporter->InternalError("invalid function flavor"); + break; + } if ( info->ignore_callbacks ) delete_vals(args); - } else delete_vals(args); diff --git a/src/Serializer.h b/src/Serializer.h index e7396cb7f8..72e0723880 100644 --- a/src/Serializer.h +++ b/src/Serializer.h @@ -125,7 +125,7 @@ protected: // This will be increased whenever there is an incompatible change // in the data format. - static const uint32 DATA_FORMAT_VERSION = 22; + static const uint32 DATA_FORMAT_VERSION = 23; ChunkedIO* io; diff --git a/src/Stmt.cc b/src/Stmt.cc index 7d754d8e72..0a5ae16ef6 100644 --- a/src/Stmt.cc +++ b/src/Stmt.cc @@ -23,7 +23,7 @@ const char* stmt_name(BroStmtTag t) "print", "event", "expr", "if", "when", "switch", "for", "next", "break", "return", "add", "delete", "list", "bodylist", - "", + "", "hook", "null", }; @@ -933,6 +933,52 @@ bool EventStmt::DoUnserialize(UnserialInfo* info) return event_expr != 0; } +HookStmt::HookStmt(CallExpr* arg_e) : ExprStmt(STMT_HOOK, arg_e) + { + call_expr = arg_e; + } + +Val* HookStmt::Exec(Frame* f, stmt_flow_type& flow) const + { + RegisterAccess(); + + Val* ret = call_expr->Eval(f); + Unref(ret); + + flow = FLOW_NEXT; + + return 0; + } + +TraversalCode HookStmt::Traverse(TraversalCallback* cb) const + { + TraversalCode tc = cb->PreStmt(this); + HANDLE_TC_STMT_PRE(tc); + + // call expr is stored in base class's "e" field. + tc = e->Traverse(cb); + HANDLE_TC_STMT_PRE(tc); + + tc = cb->PostStmt(this); + HANDLE_TC_STMT_POST(tc); + } + +IMPLEMENT_SERIAL(HookStmt, SER_HOOK_STMT); + +bool HookStmt::DoSerialize(SerialInfo* info) const + { + DO_SERIALIZE(SER_HOOK_STMT, ExprStmt); + return call_expr->Serialize(info); + } + +bool HookStmt::DoUnserialize(UnserialInfo* info) + { + DO_UNSERIALIZE(ExprStmt); + + call_expr = (CallExpr*) Expr::Unserialize(info, EXPR_CALL); + return call_expr != 0; + } + ForStmt::ForStmt(id_list* arg_loop_vars, Expr* loop_expr) : ExprStmt(STMT_FOR, loop_expr) { @@ -1944,6 +1990,7 @@ int same_stmt(const Stmt* s1, const Stmt* s2) case STMT_RETURN: case STMT_EXPR: case STMT_EVENT: + case STMT_HOOK: { const ExprStmt* e1 = (const ExprStmt*) s1; const ExprStmt* e2 = (const ExprStmt*) s2; diff --git a/src/Stmt.h b/src/Stmt.h index 7c3b42609b..68bb8d6425 100644 --- a/src/Stmt.h +++ b/src/Stmt.h @@ -286,6 +286,24 @@ protected: EventExpr* event_expr; }; +class HookStmt : public ExprStmt { +public: + HookStmt(CallExpr* e); + + Val* Exec(Frame* f, stmt_flow_type& flow) const; + + TraversalCode Traverse(TraversalCallback* cb) const; + +protected: + friend class Stmt; + + HookStmt() { call_expr = 0; } + + DECLARE_SERIAL(HookStmt); + + CallExpr* call_expr; +}; + class ForStmt : public ExprStmt { public: ForStmt(id_list* loop_vars, Expr* loop_expr); diff --git a/src/StmtEnums.h b/src/StmtEnums.h index f431e3fea1..fa5b70389d 100644 --- a/src/StmtEnums.h +++ b/src/StmtEnums.h @@ -15,7 +15,7 @@ typedef enum { STMT_RETURN, STMT_ADD, STMT_DELETE, STMT_LIST, STMT_EVENT_BODY_LIST, - STMT_INIT, + STMT_INIT, STMT_HOOK, STMT_NULL #define NUM_STMTS (int(STMT_NULL) + 1) } BroStmtTag; diff --git a/src/Type.cc b/src/Type.cc index e1a9271aa3..e9b0949d13 100644 --- a/src/Type.cc +++ b/src/Type.cc @@ -651,12 +651,12 @@ bool SetType::DoUnserialize(UnserialInfo* info) return true; } -FuncType::FuncType(RecordType* arg_args, BroType* arg_yield, int arg_is_event) +FuncType::FuncType(RecordType* arg_args, BroType* arg_yield, function_flavor arg_flavor) : BroType(TYPE_FUNC) { args = arg_args; yield = arg_yield; - is_event = arg_is_event; + flavor = arg_flavor; arg_types = new TypeList(); @@ -664,6 +664,25 @@ FuncType::FuncType(RecordType* arg_args, BroType* arg_yield, int arg_is_event) arg_types->Append(args->FieldType(i)->Ref()); } +string FuncType::FlavorString() const + { + switch ( flavor ) { + + case FUNC_FLAVOR_FUNCTION: + return "function"; + + case FUNC_FLAVOR_EVENT: + return "event"; + + case FUNC_FLAVOR_HOOK: + return "hook"; + + default: + reporter->InternalError("Invalid function flavor"); + return "invalid_func_flavor"; + } + } + FuncType::~FuncType() { Unref(arg_types); @@ -698,7 +717,7 @@ void FuncType::Describe(ODesc* d) const { if ( d->IsReadable() ) { - d->Add(is_event ? "event" : "function"); + d->Add(FlavorString()); d->Add("("); args->DescribeFields(d); d->Add(")"); @@ -712,7 +731,7 @@ void FuncType::Describe(ODesc* d) const else { d->Add(int(Tag())); - d->Add(is_event); + d->Add(flavor); d->Add(yield != 0); args->DescribeFields(d); if ( yield ) @@ -723,7 +742,7 @@ void FuncType::Describe(ODesc* d) const void FuncType::DescribeReST(ODesc* d) const { d->Add(":bro:type:`"); - d->Add(is_event ? "event" : "function"); + d->Add(FlavorString()); d->Add("`"); d->Add(" ("); args->DescribeFieldsReST(d, true); @@ -755,9 +774,30 @@ bool FuncType::DoSerialize(SerialInfo* info) const SERIALIZE_OPTIONAL(yield); + int ser_flavor = 0; + + switch ( flavor ) { + + case FUNC_FLAVOR_FUNCTION: + ser_flavor = 0; + break; + + case FUNC_FLAVOR_EVENT: + ser_flavor = 1; + break; + + case FUNC_FLAVOR_HOOK: + ser_flavor = 2; + break; + + default: + reporter->InternalError("Invalid function flavor serialization"); + break; + } + return args->Serialize(info) && arg_types->Serialize(info) && - SERIALIZE(is_event); + SERIALIZE(ser_flavor); } bool FuncType::DoUnserialize(UnserialInfo* info) @@ -774,7 +814,27 @@ bool FuncType::DoUnserialize(UnserialInfo* info) if ( ! arg_types ) return false; - return UNSERIALIZE(&is_event); + int ser_flavor = 0; + + if ( ! UNSERIALIZE(&ser_flavor) ) + return false; + + switch ( ser_flavor ) { + case 0: + flavor = FUNC_FLAVOR_FUNCTION; + break; + case 1: + flavor = FUNC_FLAVOR_EVENT; + break; + case 2: + flavor = FUNC_FLAVOR_HOOK; + break; + default: + reporter->InternalError("Invalid function flavor unserialization"); + break; + } + + return true; } TypeDecl::TypeDecl(BroType* t, const char* i, attr_list* arg_attrs, bool in_record) @@ -1603,7 +1663,7 @@ int same_type(const BroType* t1, const BroType* t2, int is_init) const FuncType* ft1 = (const FuncType*) t1; const FuncType* ft2 = (const FuncType*) t2; - if ( ft1->IsEvent() != ft2->IsEvent() ) + if ( ft1->Flavor() != ft2->Flavor() ) return 0; if ( t1->YieldType() || t2->YieldType() ) @@ -1890,7 +1950,7 @@ BroType* merge_types(const BroType* t1, const BroType* t2) BroType* yield = t1->YieldType() ? merge_types(t1->YieldType(), t2->YieldType()) : 0; - return new FuncType(args->AsRecordType(), yield, ft1->IsEvent()); + return new FuncType(args->AsRecordType(), yield, ft1->Flavor()); } case TYPE_RECORD: diff --git a/src/Type.h b/src/Type.h index 3a0b349f19..8e2bb099d8 100644 --- a/src/Type.h +++ b/src/Type.h @@ -35,7 +35,11 @@ typedef enum { #define NUM_TYPES (int(TYPE_ERROR) + 1) } TypeTag; -typedef enum { FUNC_FLAVOR_FUNCTION, FUNC_FLAVOR_EVENT } function_flavor; +typedef enum { + FUNC_FLAVOR_FUNCTION, + FUNC_FLAVOR_EVENT, + FUNC_FLAVOR_HOOK +} function_flavor; typedef enum { TYPE_INTERNAL_VOID, @@ -350,18 +354,19 @@ protected: class FuncType : public BroType { public: - FuncType(RecordType* args, BroType* yield, int is_event); + FuncType(RecordType* args, BroType* yield, function_flavor f); ~FuncType(); RecordType* Args() const { return args; } BroType* YieldType(); void SetYieldType(BroType* arg_yield) { yield = arg_yield; } - int IsEvent() const { return is_event; } + function_flavor Flavor() const { return flavor; } + string FlavorString() const; - // Used to convert a function type to an event type. - void ClearYieldType() - { Unref(yield); yield = 0; is_event = 1; } + // Used to convert a function type to an event or hook type. + void ClearYieldType(function_flavor arg_flav) + { Unref(yield); yield = 0; flavor = arg_flav; } int MatchesIndex(ListExpr*& index) const; int CheckArgs(const type_list* args) const; @@ -374,14 +379,13 @@ public: void DescribeReST(ODesc* d) const; protected: - FuncType() { args = 0; arg_types = 0; yield = 0; return_value = 0; } + FuncType() { args = 0; arg_types = 0; yield = 0; flavor = FUNC_FLAVOR_FUNCTION; } DECLARE_SERIAL(FuncType) RecordType* args; TypeList* arg_types; BroType* yield; - int is_event; - ID* return_value; + function_flavor flavor; }; class TypeType : public BroType { diff --git a/src/Var.cc b/src/Var.cc index d54d94a078..9c4fb5b978 100644 --- a/src/Var.cc +++ b/src/Var.cc @@ -171,7 +171,9 @@ static void make_var(ID* id, BroType* t, init_class c, Expr* init, id->UpdateValAttrs(); - if ( t && t->Tag() == TYPE_FUNC && t->AsFuncType()->IsEvent() ) + if ( t && t->Tag() == TYPE_FUNC && + (t->AsFuncType()->Flavor() == FUNC_FLAVOR_EVENT || + t->AsFuncType()->Flavor() == FUNC_FLAVOR_HOOK) ) { // For events, add a function value (without any body) here so that // we can later access the ID even if no implementations have been @@ -258,7 +260,7 @@ void add_type(ID* id, BroType* t, attr_list* attr, int /* is_event */) case TYPE_FUNC: tnew = new FuncType(t->AsFuncType()->Args(), t->AsFuncType()->YieldType(), - t->AsFuncType()->IsEvent()); + t->AsFuncType()->Flavor()); break; default: SerializationFormat* form = new BinarySerializationFormat(); @@ -292,14 +294,14 @@ void add_type(ID* id, BroType* t, attr_list* attr, int /* is_event */) void begin_func(ID* id, const char* module_name, function_flavor flavor, int is_redef, FuncType* t) { - if ( flavor == FUNC_FLAVOR_EVENT ) + if ( flavor == FUNC_FLAVOR_EVENT || flavor == FUNC_FLAVOR_HOOK ) { const BroType* yt = t->YieldType(); if ( yt && yt->Tag() != TYPE_VOID ) - id->Error("event cannot yield a value", t); + id->Error("event/hook cannot yield a value", t); - t->ClearYieldType(); + t->ClearYieldType(flavor); } if ( id->Type() ) @@ -313,21 +315,29 @@ void begin_func(ID* id, const char* module_name, function_flavor flavor, if ( id->HasVal() ) { - int id_is_event = id->ID_Val()->AsFunc()->IsEvent(); + function_flavor id_flavor = id->ID_Val()->AsFunc()->Flavor(); - if ( id_is_event != (flavor == FUNC_FLAVOR_EVENT) ) - id->Error("inconsistency between event and function", t); - if ( id_is_event ) - { + if ( id_flavor != flavor ) + id->Error("inconsistent function flavor", t); + + switch ( id_flavor ) { + + case FUNC_FLAVOR_EVENT: + case FUNC_FLAVOR_HOOK: if ( is_redef ) // Clear out value so it will be replaced. id->SetVal(0); - } - else - { + break; + + case FUNC_FLAVOR_FUNCTION: if ( ! id->IsRedefinable() ) id->Error("already defined"); - } + break; + + default: + reporter->InternalError("invalid function flavor"); + break; + } } else id->SetType(t); diff --git a/src/input/Manager.cc b/src/input/Manager.cc index 43ac63200f..33f612cddd 100644 --- a/src/input/Manager.cc +++ b/src/input/Manager.cc @@ -393,7 +393,7 @@ bool Manager::CreateEventStream(RecordVal* fval) bool allow_file_func = false; - if ( ! etype->IsEvent() ) + if ( etype->Flavor() != FUNC_FLAVOR_EVENT ) { reporter->Error("stream event is a function, not an event"); return false; @@ -566,7 +566,7 @@ bool Manager::CreateTableStream(RecordVal* fval) { FuncType* etype = event->FType()->AsFuncType(); - if ( ! etype->IsEvent() ) + if ( etype->Flavor() != FUNC_FLAVOR_EVENT ) { reporter->Error("stream event is a function, not an event"); return false; diff --git a/src/logging/Manager.cc b/src/logging/Manager.cc index 4c6d2e92fd..f62cd1685d 100644 --- a/src/logging/Manager.cc +++ b/src/logging/Manager.cc @@ -327,7 +327,7 @@ bool Manager::CreateStream(EnumVal* id, RecordVal* sval) // Make sure the event is prototyped as expected. FuncType* etype = event->FType()->AsFuncType(); - if ( ! etype->IsEvent() ) + if ( etype->Flavor() != FUNC_FLAVOR_EVENT ) { reporter->Error("stream event is a function, not an event"); return false; diff --git a/src/parse.y b/src/parse.y index 9ef4525afb..9a66973d6b 100644 --- a/src/parse.y +++ b/src/parse.y @@ -2,14 +2,14 @@ // See the file "COPYING" in the main distribution directory for copyright. %} -%expect 87 +%expect 88 %token TOK_ADD TOK_ADD_TO TOK_ADDR TOK_ANY %token TOK_ATENDIF TOK_ATELSE TOK_ATIF TOK_ATIFDEF TOK_ATIFNDEF %token TOK_BOOL TOK_BREAK TOK_CASE TOK_CONST %token TOK_CONSTANT TOK_COPY TOK_COUNT TOK_COUNTER TOK_DEFAULT TOK_DELETE %token TOK_DOUBLE TOK_ELSE TOK_ENUM TOK_EVENT TOK_EXPORT TOK_FILE TOK_FOR -%token TOK_FUNCTION TOK_GLOBAL TOK_ID TOK_IF TOK_INT +%token TOK_FUNCTION TOK_GLOBAL TOK_HOOK TOK_ID TOK_IF TOK_INT %token TOK_INTERVAL TOK_LIST TOK_LOCAL TOK_MODULE %token TOK_NEXT TOK_OF TOK_PATTERN TOK_PATTERN_TEXT %token TOK_PORT TOK_PRINT TOK_RECORD TOK_REDEF @@ -56,6 +56,7 @@ %type pattern %type expr init anonymous_function %type event +%type hook %type stmt stmt_list func_body for_head %type type opt_type enum_body %type func_hdr func_params @@ -118,6 +119,7 @@ extern const char* g_curr_debug_error; #define YYLTYPE yyltype +static bool in_hook = false; int in_init = 0; int in_record = 0; bool resolving_global_ID = false; @@ -868,7 +870,13 @@ type: | TOK_EVENT '(' formal_args ')' { set_location(@1, @3); - $$ = new FuncType($3, 0, 1); + $$ = new FuncType($3, 0, FUNC_FLAVOR_EVENT); + } + + | TOK_HOOK '(' formal_args ')' + { + set_location(@1, @3); + $$ = new FuncType($3, 0, FUNC_FLAVOR_HOOK); } | TOK_FILE TOK_OF type @@ -1000,12 +1008,27 @@ decl: ID* id = $2; if ( id->Type()->Tag() == TYPE_FUNC ) { - if ( id->Type()->AsFuncType()->IsEvent() ) - current_reST_doc->AddEvent( - new BroDocObj(id, reST_doc_comments)); - else + switch ( id->Type()->AsFuncType()->Flavor() ) { + + case FUNC_FLAVOR_FUNCTION: current_reST_doc->AddFunction( new BroDocObj(id, reST_doc_comments)); + break; + + case FUNC_FLAVOR_EVENT: + current_reST_doc->AddEvent( + new BroDocObj(id, reST_doc_comments)); + break; + + case FUNC_FLAVOR_HOOK: + current_reST_doc->AddHook( + new BroDocObj(id, reST_doc_comments)); + break; + + default: + reporter->InternalError("invalid function flavor"); + break; + } } else @@ -1185,6 +1208,15 @@ func_hdr: current_reST_doc->AddEventHandler( new BroDocObj($2, reST_doc_comments)); } + | TOK_HOOK def_global_id func_params + { + begin_func($2, current_module.c_str(), + FUNC_FLAVOR_HOOK, 0, $3); + $$ = $3; + if ( generate_documentation ) + current_reST_doc->AddHookHandler( + new BroDocObj($2, reST_doc_comments)); + } | TOK_REDEF TOK_EVENT event_id func_params { begin_func($3, current_module.c_str(), @@ -1219,9 +1251,9 @@ begin_func: func_params: '(' formal_args ')' ':' type - { $$ = new FuncType($2, $5, 0); } + { $$ = new FuncType($2, $5, FUNC_FLAVOR_FUNCTION); } | '(' formal_args ')' - { $$ = new FuncType($2, base_type(TYPE_VOID), 0); } + { $$ = new FuncType($2, base_type(TYPE_VOID), FUNC_FLAVOR_FUNCTION); } ; opt_type: @@ -1341,6 +1373,14 @@ stmt: brofiler.AddStmt($$); } + | TOK_HOOK { in_hook = true; } hook { in_hook = false; } ';' opt_no_test + { + set_location(@1, @5); + $$ = new HookStmt($3); + if ( ! $6 ) + brofiler.AddStmt($$); + } + | TOK_IF '(' expr ')' stmt { set_location(@1, @4); @@ -1494,6 +1534,14 @@ event: } ; +hook: + expr '(' opt_expr_list ')' + { + set_location(@1, @4); + $$ = new CallExpr($1, $3, in_hook); + } + ; + case_list: case_list case { $1->append($2); } diff --git a/src/scan.l b/src/scan.l index 8ff33e7d24..4e1a66144e 100644 --- a/src/scan.l +++ b/src/scan.l @@ -287,6 +287,7 @@ for return TOK_FOR; function return TOK_FUNCTION; global return TOK_GLOBAL; "?$" return TOK_HAS_FIELD; +hook return TOK_HOOK; if return TOK_IF; in return TOK_IN; "!"{OWS}in/[^A-Za-z0-9] return TOK_NOT_IN; /* don't confuse w "! infoo"! */ diff --git a/testing/btest/Baseline/doc.autogen-reST-example/example.rst b/testing/btest/Baseline/doc.autogen-reST-example/example.rst index bee8658e14..1f60efe70b 100644 --- a/testing/btest/Baseline/doc.autogen-reST-example/example.rst +++ b/testing/btest/Baseline/doc.autogen-reST-example/example.rst @@ -73,9 +73,9 @@ Events Functions ######### -=============================================== ======================================= -:bro:id:`Example::a_function`: :bro:type:`func` Summarize purpose of "a_function" here. -=============================================== ======================================= +=================================================== ======================================= +:bro:id:`Example::a_function`: :bro:type:`function` Summarize purpose of "a_function" here. +=================================================== ======================================= Redefinitions ############# diff --git a/testing/btest/Baseline/doc.autogen-reST-func-params/autogen-reST-func-params.rst b/testing/btest/Baseline/doc.autogen-reST-func-params/autogen-reST-func-params.rst index 15526f12c7..9739d024e3 100644 --- a/testing/btest/Baseline/doc.autogen-reST-func-params/autogen-reST-func-params.rst +++ b/testing/btest/Baseline/doc.autogen-reST-func-params/autogen-reST-func-params.rst @@ -20,9 +20,9 @@ Types Functions ######### -===================================== ====================================== -:bro:id:`test_func`: :bro:type:`func` This is a global function declaration. -===================================== ====================================== +========================================= ====================================== +:bro:id:`test_func`: :bro:type:`function` This is a global function declaration. +========================================= ====================================== Detailed Interface ~~~~~~~~~~~~~~~~~~ diff --git a/testing/btest/Baseline/language.hook/out b/testing/btest/Baseline/language.hook/out new file mode 100644 index 0000000000..10688acc3b --- /dev/null +++ b/testing/btest/Baseline/language.hook/out @@ -0,0 +1,7 @@ +myhook, &priority=10, [a=1156, b=hello world] +myhook, &priority=5, [a=37, b=goobye world] +myhook3, 8 +myhook4, 2 +myhook4, 1 +myhook, &priority=10, [a=2, b=it works] +myhook, &priority=5, [a=37, b=goobye world] diff --git a/testing/btest/Baseline/language.invalid_hook/out b/testing/btest/Baseline/language.invalid_hook/out new file mode 100644 index 0000000000..167d62ccc8 --- /dev/null +++ b/testing/btest/Baseline/language.invalid_hook/out @@ -0,0 +1 @@ +error in /Users/jsiwek/Projects/bro/bro/testing/btest/.tmp/language.invalid_hook/invalid_hook.bro, line 15: hook called in expression, use hook statement instead (myhook(nope)) diff --git a/testing/btest/core/leaks/hook.bro b/testing/btest/core/leaks/hook.bro new file mode 100644 index 0000000000..eadb406e71 --- /dev/null +++ b/testing/btest/core/leaks/hook.bro @@ -0,0 +1,78 @@ +# Needs perftools support. +# +# @TEST-GROUP: leaks +# +# @TEST-REQUIRES: bro --help 2>&1 | grep -q mem-leaks +# +# @TEST-EXEC: HEAP_CHECK_DUMP_DIRECTORY=. HEAPCHECK=local bro -m -b -r $TRACES/wikipedia.trace %INPUT + +type rec: record { + a: count; + b: string; +}; + +global myhook: hook(r: rec); +global myhook2: hook(s: string); +# a hook doesn't have to take any arguments +global myhook4: hook(); + +hook myhook(r: rec) &priority=5 + { + print "myhook, &priority=5", r; + # break statement short-circuits the hook handling chain. + break; + print "ERROR: break statement should return from hook handler body"; + } + +hook myhook(r: rec) + { + # This handler shouldn't execute ever because of the handler at priority=5 + # exiting the body from a "break" statement. + print "myhook, &priority=0", rec; + } + +hook myhook(r: rec) &priority=10 + { + print "myhook, &priority=10", r; + # modifications to the record argument will be seen by remaining handlers. + r$a = 37; + r$b = "goobye world"; + # returning from the handler early, is fine, remaining handlers still run. + return; + print "ERROR: break statement should return from hook handler body"; + } + +# hook function doesn't need a declaration, we can go straight to defining +# a handler body. +hook myhook3(i: count) + { + print "myhook3", i; + } + +hook myhook4() &priority=1 + { + print "myhook4", 1; + } + +hook myhook4() &priority=2 + { + print "myhook4", 2; + } + +event new_connection(c: connection) + { + print "new_connection", c$id; + + hook myhook([$a=1156, $b="hello world"]); + + # A hook with no handlers is fine, it's just a no-op. + hook myhook2("nope"); + + hook myhook3(8); + hook myhook4(); + + # A hook can be treated like other data types and doesn't have to be + # invoked directly by name. + local h = myhook; + hook h([$a=2, $b="it works"]); + } diff --git a/testing/btest/istate/events-ssl.bro b/testing/btest/istate/events-ssl.bro index 1d285869b4..e4440834a7 100644 --- a/testing/btest/istate/events-ssl.bro +++ b/testing/btest/istate/events-ssl.bro @@ -11,8 +11,8 @@ # @TEST-EXEC: cat receiver/http.log | $SCRIPTS/diff-remove-timestamps >receiver.http.log # @TEST-EXEC: cmp sender.http.log receiver.http.log # -# @TEST-EXEC: bro -x sender/events.bst | sed 's/^Event \[[-0-9.]*\] //g' | grep '^http_' | grep -v http_stats | sed 's/(.*$//g' | $SCRIPTS/diff-remove-timestamps >events.snd.log -# @TEST-EXEC: bro -x receiver/events.bst | sed 's/^Event \[[-0-9.]*\] //g' | grep '^http_' | grep -v http_stats | sed 's/(.*$//g' | $SCRIPTS/diff-remove-timestamps >events.rec.log +# @TEST-EXEC: bro -x sender/events.bst | sed 's/^event \[[-0-9.]*\] //g' | grep '^http_' | grep -v http_stats | sed 's/(.*$//g' | $SCRIPTS/diff-remove-timestamps >events.snd.log +# @TEST-EXEC: bro -x receiver/events.bst | sed 's/^event \[[-0-9.]*\] //g' | grep '^http_' | grep -v http_stats | sed 's/(.*$//g' | $SCRIPTS/diff-remove-timestamps >events.rec.log # @TEST-EXEC: btest-diff events.rec.log # @TEST-EXEC: btest-diff events.snd.log # @TEST-EXEC: cmp events.rec.log events.snd.log diff --git a/testing/btest/istate/events.bro b/testing/btest/istate/events.bro index 590aabcd23..c292f77113 100644 --- a/testing/btest/istate/events.bro +++ b/testing/btest/istate/events.bro @@ -11,8 +11,8 @@ # @TEST-EXEC: cat receiver/http.log | $SCRIPTS/diff-remove-timestamps >receiver.http.log # @TEST-EXEC: cmp sender.http.log receiver.http.log # -# @TEST-EXEC: bro -x sender/events.bst | sed 's/^Event \[[-0-9.]*\] //g' | grep '^http_' | grep -v http_stats | sed 's/(.*$//g' | $SCRIPTS/diff-remove-timestamps >events.snd.log -# @TEST-EXEC: bro -x receiver/events.bst | sed 's/^Event \[[-0-9.]*\] //g' | grep '^http_' | grep -v http_stats | sed 's/(.*$//g' | $SCRIPTS/diff-remove-timestamps >events.rec.log +# @TEST-EXEC: bro -x sender/events.bst | sed 's/^event \[[-0-9.]*\] //g' | grep '^http_' | grep -v http_stats | sed 's/(.*$//g' | $SCRIPTS/diff-remove-timestamps >events.snd.log +# @TEST-EXEC: bro -x receiver/events.bst | sed 's/^event \[[-0-9.]*\] //g' | grep '^http_' | grep -v http_stats | sed 's/(.*$//g' | $SCRIPTS/diff-remove-timestamps >events.rec.log # @TEST-EXEC: btest-diff events.rec.log # @TEST-EXEC: btest-diff events.snd.log # @TEST-EXEC: cmp events.rec.log events.snd.log diff --git a/testing/btest/language/hook.bro b/testing/btest/language/hook.bro new file mode 100644 index 0000000000..8f7a85ce95 --- /dev/null +++ b/testing/btest/language/hook.bro @@ -0,0 +1,71 @@ +# @TEST-EXEC: bro -b %INPUT >out +# @TEST-EXEC: btest-diff out + +type rec: record { + a: count; + b: string; +}; + +global myhook: hook(r: rec); +global myhook2: hook(s: string); +# a hook doesn't have to take any arguments +global myhook4: hook(); + +hook myhook(r: rec) &priority=5 + { + print "myhook, &priority=5", r; + # break statement short-circuits the hook handling chain. + break; + print "ERROR: break statement should return from hook handler body"; + } + +hook myhook(r: rec) + { + # This handler shouldn't execute ever because of the handler at priority=5 + # exiting the body from a "break" statement. + print "myhook, &priority=0", rec; + } + +hook myhook(r: rec) &priority=10 + { + print "myhook, &priority=10", r; + # modifications to the record argument will be seen by remaining handlers. + r$a = 37; + r$b = "goobye world"; + # returning from the handler early, is fine, remaining handlers still run. + return; + print "ERROR: break statement should return from hook handler body"; + } + +# hook function doesn't need a declaration, we can go straight to defining +# a handler body. +hook myhook3(i: count) + { + print "myhook3", i; + } + +hook myhook4() &priority=1 + { + print "myhook4", 1; + } + +hook myhook4() &priority=2 + { + print "myhook4", 2; + } + +event bro_init() + { + hook myhook([$a=1156, $b="hello world"]); + + # A hook with no handlers is fine, it's just a no-op. + hook myhook2("nope"); + + hook myhook3(8); + hook myhook4(); + + # A hook can be treated like other data types and doesn't have to be + # invoked directly by name. + local h = myhook; + hook h([$a=2, $b="it works"]); + } diff --git a/testing/btest/language/invalid_hook.bro b/testing/btest/language/invalid_hook.bro new file mode 100644 index 0000000000..0dbbfd1b6f --- /dev/null +++ b/testing/btest/language/invalid_hook.bro @@ -0,0 +1,16 @@ +# @TEST-EXEC-FAIL: bro -b %INPUT >out 2>&1 +# @TEST-EXEC: TEST_DIFF_CANONIFIER=$SCRIPTS/diff-remove-abspath btest-diff out + +global myhook: hook(s: string); + +hook myhook(s: string) + { + print "myhook", s; + } + +event bro_init() + { + # hooks must be invoked with a "hook", statement. They have no return + # value and don't make sense to evaluate as arbitrary expressions. + local r = myhook("nope"); + }