From e0fb9eb2b2a0f731244b0f104123e0c96821fb3a Mon Sep 17 00:00:00 2001 From: Jon Siwek Date: Thu, 15 Nov 2012 13:45:13 -0600 Subject: [PATCH 1/9] Add new function flavor called a "hook". This new flavor of function behaves like a "synchronous event". See documentation for more details on usage. --- doc/scripts/builtins.rst | 72 +++++++++++++++++-- src/Attr.cc | 8 ++- src/BroDoc.cc | 1 + src/BroDoc.h | 26 +++++++ src/Expr.cc | 27 ++++++- src/Expr.h | 2 +- src/Func.cc | 14 ++-- src/Func.h | 2 +- src/ID.cc | 5 +- src/SerialTypes.h | 1 + src/Serializer.cc | 15 ++-- src/Serializer.h | 2 +- src/Stmt.cc | 49 ++++++++++++- src/Stmt.h | 18 +++++ src/StmtEnums.h | 2 +- src/Type.cc | 70 +++++++++++++++--- src/Type.h | 22 +++--- src/Var.cc | 35 +++++---- src/input/Manager.cc | 4 +- src/logging/Manager.cc | 2 +- src/parse.y | 62 +++++++++++++--- src/scan.l | 1 + .../doc.autogen-reST-example/example.rst | 6 +- .../autogen-reST-func-params.rst | 6 +- testing/btest/Baseline/language.hook/out | 7 ++ .../btest/Baseline/language.invalid_hook/out | 1 + testing/btest/istate/events-ssl.bro | 4 +- testing/btest/istate/events.bro | 4 +- testing/btest/language/hook.bro | 71 ++++++++++++++++++ testing/btest/language/invalid_hook.bro | 16 +++++ 30 files changed, 476 insertions(+), 79 deletions(-) create mode 100644 testing/btest/Baseline/language.hook/out create mode 100644 testing/btest/Baseline/language.invalid_hook/out create mode 100644 testing/btest/language/hook.bro create mode 100644 testing/btest/language/invalid_hook.bro 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..0ef9e2ad94 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,29 @@ 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..a2dbb2ddc2 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 a70aa3fd0e..5239c3bb35 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; default: 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..8a43874342 100644 --- a/src/Serializer.cc +++ b/src/Serializer.cc @@ -389,23 +389,30 @@ 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); } - else + break; + 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 414c07d3d7..df2b7b0a3d 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,21 @@ 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 +713,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 +727,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 +738,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 +770,26 @@ 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 +806,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) @@ -1595,7 +1647,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() ) @@ -1882,7 +1934,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 efe15e6188..53250183a1 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..7dd5d14820 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,26 @@ 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 c1f6ddd96e..e1ce5f3d57 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; @@ -859,7 +861,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 @@ -991,12 +999,23 @@ 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 @@ -1175,6 +1194,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(), @@ -1209,9 +1237,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: @@ -1331,6 +1359,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); @@ -1484,6 +1520,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/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"); + } From c8d64b50289132d4f455c991ecaae62ad4e1424e Mon Sep 17 00:00:00 2001 From: Jon Siwek Date: Thu, 15 Nov 2012 14:02:24 -0600 Subject: [PATCH 2/9] Add memory leak unit test for "hook" function flavor. --- testing/btest/core/leaks/hook.bro | 78 +++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 testing/btest/core/leaks/hook.bro 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"]); + } From e0805498c6fd491bf56d51a4b3533c51c607d060 Mon Sep 17 00:00:00 2001 From: Jon Siwek Date: Thu, 15 Nov 2012 16:40:18 -0600 Subject: [PATCH 3/9] Fix some warnings from sphinx when building docs. --- scripts/base/frameworks/intel/main.bro | 4 ++-- scripts/policy/integration/collective-intel/README | 10 ++++------ scripts/policy/protocols/modbus/track-memmap.bro | 3 ++- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/scripts/base/frameworks/intel/main.bro b/scripts/base/frameworks/intel/main.bro index d66990e611..aeb7bf4bfc 100644 --- a/scripts/base/frameworks/intel/main.bro +++ b/scripts/base/frameworks/intel/main.bro @@ -59,7 +59,7 @@ export { }; ## Enum to represent where data came from when it was discovered. - ## The convenction is to prefix the name with "IN_". + ## The convention is to prefix the name with ``IN_``. type Where: enum { ## A catchall value to represent data of unknown provenance. IN_ANYWHERE, @@ -342,4 +342,4 @@ function insert(item: Item) else event Intel::new_item(item); } - \ No newline at end of file + diff --git a/scripts/policy/integration/collective-intel/README b/scripts/policy/integration/collective-intel/README index 550eb96962..17d534c8dd 100644 --- a/scripts/policy/integration/collective-intel/README +++ b/scripts/policy/integration/collective-intel/README @@ -1,6 +1,4 @@ -Collective Intelligence Framework Integration -============================================= - -The scripts in this module are for deeper integration with the Collective Intelligence -Framework (CIF) since Bro's Intel framework doesn't natively behave the same as CIF nor -does it store and maintain the same data in all cases. \ No newline at end of file +The scripts in this module are for deeper integration with the +Collective Intelligence Framework (CIF) since Bro's Intel framework +doesn't natively behave the same as CIF nor does it store and maintain +the same data in all cases. diff --git a/scripts/policy/protocols/modbus/track-memmap.bro b/scripts/policy/protocols/modbus/track-memmap.bro index fc02d9b274..e2001e66bf 100644 --- a/scripts/policy/protocols/modbus/track-memmap.bro +++ b/scripts/policy/protocols/modbus/track-memmap.bro @@ -2,11 +2,12 @@ ##! changes as they are discovered. ##! ##! .. todo: Not all register reads and write functions are being supported yet. -module Modbus; @load base/protocols/modbus @load base/utils/directions-and-hosts +module Modbus; + export { redef enum Log::ID += { Modbus::REGISTER_CHANGE_LOG }; From 9e49703087dfc0c69e9362144fc91bcf4d2cc7c1 Mon Sep 17 00:00:00 2001 From: Jon Siwek Date: Thu, 15 Nov 2012 16:54:33 -0600 Subject: [PATCH 4/9] Improve auto-generated enum documentation. The names of enum types are tracked so that variables holding a value of a given enum type can generate a reference to it instead of just listing the type as a generic "enum". --- src/ID.cc | 8 ++++++++ src/Type.cc | 10 +++++++++- src/Type.h | 12 ++++++++++-- src/parse.y | 13 ++++++++----- .../autogen-reST-enums.rst | 16 ++++++++++++++++ testing/btest/doc/autogen-reST-enums.bro | 3 +++ 6 files changed, 54 insertions(+), 8 deletions(-) diff --git a/src/ID.cc b/src/ID.cc index a70aa3fd0e..dcf163650c 100644 --- a/src/ID.cc +++ b/src/ID.cc @@ -660,8 +660,16 @@ void ID::DescribeReSTShort(ODesc* d) const d->Add(type->AsFuncType()->IsEvent() ? "event" : type_name(t)); break; + case TYPE_ENUM: + if ( is_type ) + d->Add(type_name(t)); + else + d->Add(type->AsEnumType()->Name().c_str()); + break; + default: d->Add(type_name(t)); + break; } } diff --git a/src/Type.cc b/src/Type.cc index 414c07d3d7..e1a9271aa3 100644 --- a/src/Type.cc +++ b/src/Type.cc @@ -1202,9 +1202,10 @@ bool FileType::DoUnserialize(UnserialInfo* info) return yield != 0; } -EnumType::EnumType() +EnumType::EnumType(const string& arg_name) : BroType(TYPE_ENUM) { + name = arg_name; counter = 0; } @@ -1327,6 +1328,13 @@ const char* EnumType::Lookup(bro_int_t value) return 0; } +void EnumType::DescribeReST(ODesc* d) const + { + d->Add(":bro:type:`"); + d->Add(name.c_str()); + d->Add("`"); + } + void CommentedEnumType::DescribeReST(ODesc* d) const { // create temporary, reverse name map so that enums can be documented diff --git a/src/Type.h b/src/Type.h index efe15e6188..3a0b349f19 100644 --- a/src/Type.h +++ b/src/Type.h @@ -497,7 +497,7 @@ protected: class EnumType : public BroType { public: - EnumType(); + EnumType(const string& arg_name); ~EnumType(); // The value of this name is next internal counter value, starting @@ -513,7 +513,12 @@ public: bro_int_t Lookup(const string& module_name, const char* name); const char* Lookup(bro_int_t value); // Returns 0 if not found + string Name() const { return name; } + + void DescribeReST(ODesc* d) const; + protected: + EnumType() { counter = 0; } DECLARE_SERIAL(EnumType) virtual void AddNameInternal(const string& module_name, @@ -529,11 +534,14 @@ protected: // as a flag to prevent mixing of auto-increment and explicit // enumerator specifications. bro_int_t counter; + + // The name of the enum type is stored for documentation purposes. + string name; }; class CommentedEnumType: public EnumType { public: - CommentedEnumType() {} + CommentedEnumType(const string& arg_name) : EnumType(arg_name) {} ~CommentedEnumType(); void DescribeReST(ODesc* d) const; diff --git a/src/parse.y b/src/parse.y index c1f6ddd96e..035ec03f90 100644 --- a/src/parse.y +++ b/src/parse.y @@ -131,16 +131,18 @@ const char* cur_enum_elem_id = 0; type_decl_list* fake_type_decl_list = 0; TypeDecl* last_fake_type_decl = 0; +static ID* cur_decl_type_id = 0; + static void parser_new_enum (void) { /* Starting a new enum definition. */ assert(cur_enum_type == NULL); - cur_enum_type = new EnumType(); + cur_enum_type = new EnumType(cur_decl_type_id->Name()); // For documentation purposes, a separate type object is created // in order to avoid overlap that can be caused by redefs. if ( generate_documentation ) - cur_enum_type_doc = new CommentedEnumType(); + cur_enum_type_doc = new CommentedEnumType(cur_decl_type_id->Name()); } static void parser_redef_enum (ID *id) @@ -158,7 +160,7 @@ static void parser_redef_enum (ID *id) } if ( generate_documentation ) - cur_enum_type_doc = new CommentedEnumType(); + cur_enum_type_doc = new CommentedEnumType(id->Name()); } static void add_enum_comment (std::list* comments) @@ -1098,9 +1100,10 @@ decl: } } - | TOK_TYPE global_id ':' type opt_attr ';' + | TOK_TYPE global_id ':' { cur_decl_type_id = $2; } type opt_attr ';' { - add_type($2, $4, $5, 0); + cur_decl_type_id = 0; + add_type($2, $5, $6, 0); if ( generate_documentation ) { diff --git a/testing/btest/Baseline/doc.autogen-reST-enums/autogen-reST-enums.rst b/testing/btest/Baseline/doc.autogen-reST-enums/autogen-reST-enums.rst index 8bd6286c24..7ee7d86e66 100644 --- a/testing/btest/Baseline/doc.autogen-reST-enums/autogen-reST-enums.rst +++ b/testing/btest/Baseline/doc.autogen-reST-enums/autogen-reST-enums.rst @@ -12,6 +12,12 @@ autogen-reST-enums.bro Summary ~~~~~~~ +Options +####### +==================================================================== ====================================================================== +:bro:id:`test_enum_option`: :bro:type:`TestEnum1` :bro:attr:`&redef` this should reference the TestEnum1 type and not a generic "enum" type +==================================================================== ====================================================================== + Types ##### ======================================= ====================================== @@ -30,6 +36,16 @@ Redefinitions Detailed Interface ~~~~~~~~~~~~~~~~~~ +Options +####### +.. bro:id:: test_enum_option + + :Type: :bro:type:`TestEnum1` + :Attributes: :bro:attr:`&redef` + :Default: ``ONE`` + + this should reference the TestEnum1 type and not a generic "enum" type + Types ##### .. bro:type:: TestEnum1 diff --git a/testing/btest/doc/autogen-reST-enums.bro b/testing/btest/doc/autogen-reST-enums.bro index fc01bed243..e3cceba22e 100644 --- a/testing/btest/doc/autogen-reST-enums.bro +++ b/testing/btest/doc/autogen-reST-enums.bro @@ -34,3 +34,6 @@ redef enum TestEnum1 += { ## adding another FIVE, ##< value }; + +## this should reference the TestEnum1 type and not a generic "enum" type +const test_enum_option = ONE &redef; From 56e359ca9d7e54b61ed8806324f6b0bb89e96315 Mon Sep 17 00:00:00 2001 From: Jon Siwek Date: Fri, 16 Nov 2012 12:43:39 -0600 Subject: [PATCH 5/9] Fix ambiguity between composite table index and record ctor expressions. For tables of a composite index type with the first type being a record, membership checks with an inline index key could be misinterpreted as a record constructor instead of an expression list. E.g, if the table type is "global t = table[conn_id, bool] of count", then checking membership like "[c$id, is_orig] in t" now works. Addresses #80. --- src/parse.y | 25 +++++++++++++++-------- testing/btest/Baseline/language.table/out | 6 ++++++ testing/btest/language/table.bro | 16 ++++++++++++++- 3 files changed, 37 insertions(+), 10 deletions(-) diff --git a/src/parse.y b/src/parse.y index c1f6ddd96e..6cafa94021 100644 --- a/src/parse.y +++ b/src/parse.y @@ -456,17 +456,24 @@ expr: | '[' expr_list ']' { - // A little crufty: we peek at the type of - // the first expression in the list. If it's - // a record or a field assignment, then this - // is a record constructor. If not, then this - // is a list used for an initializer. - set_location(@1, @3); - Expr* e0 = $2->Exprs()[0]; - if ( e0->Tag() == EXPR_FIELD_ASSIGN || - e0->Type()->Tag() == TYPE_RECORD ) + bool is_record_ctor = true; + + // If every expression in the list is a field assignment, + // then treat it as a record constructor, else as a list + // used for an initializer. + + for ( int i = 0; i < $2->Exprs().length(); ++i ) + { + if ( $2->Exprs()[i]->Tag() != EXPR_FIELD_ASSIGN ) + { + is_record_ctor = false; + break; + } + } + + if ( is_record_ctor ) $$ = new RecordConstructorExpr($2); else $$ = $2; diff --git a/testing/btest/Baseline/language.table/out b/testing/btest/Baseline/language.table/out index 514cb6b02d..0d746ded2b 100644 --- a/testing/btest/Baseline/language.table/out +++ b/testing/btest/Baseline/language.table/out @@ -12,6 +12,7 @@ cardinality (PASS) cardinality (PASS) cardinality (PASS) cardinality (PASS) +cardinality (PASS) iterate over table (PASS) iterate over table (PASS) iterate over table (PASS) @@ -30,6 +31,11 @@ add element (PASS) in operator (PASS) add element (PASS) in operator (PASS) +composite index add element (PASS) +composite index in operator (PASS) +composite index in operator (PASS) +remove element (PASS) +!in operator (PASS) remove element (PASS) !in operator (PASS) remove element (PASS) diff --git a/testing/btest/language/table.bro b/testing/btest/language/table.bro index d1b0751970..3c8e8db280 100644 --- a/testing/btest/language/table.bro +++ b/testing/btest/language/table.bro @@ -1,4 +1,4 @@ -# @TEST-EXEC: bro %INPUT >out +# @TEST-EXEC: bro -b %INPUT >out # @TEST-EXEC: btest-diff out function test_case(msg: string, expect: bool) @@ -26,6 +26,9 @@ event bro_init() local t10: table[port, string, bool] of string = { [10/udp, "curly", F] = "first", [11/udp, "braces", T] = "second" }; + local t11: table[conn_id, bool] of count = { + [ [$orig_h=1.1.1.1, $orig_p=1234/tcp, + $resp_h=2.2.2.2, $resp_p=4321/tcp], T ] = 42 }; # Type inference tests @@ -45,6 +48,7 @@ event bro_init() test_case( "cardinality", |t8| == 0 ); test_case( "cardinality", |t9| == 1 ); test_case( "cardinality", |t10| == 2 ); + test_case( "cardinality", |t11| == 1 ); test_case( "cardinality", |tg1| == 3 ); # Test iterating over each table @@ -121,6 +125,13 @@ event bro_init() test_case( "add element", |t5| == 3 ); test_case( "in operator", 10 in t5 ); + local cid = [$orig_h=1.1.1.1, $orig_p=1234/tcp, + $resp_h=2.2.2.2, $resp_p=4321/tcp]; + t11[[$orig_h=[::1], $orig_p=3/tcp, $resp_h=[::2], $resp_p=3/tcp], F] = 3; + test_case( "composite index add element", |t11| == 2 ); + test_case( "composite index in operator", [cid, T] in t11 ); + test_case( "composite index in operator", [[$orig_h=[::1], $orig_p=3/tcp, $resp_h=[::2], $resp_p=3/tcp], F] in t11 ); + # Test removing elements from each table (Note: cannot remove elements # from tables of multiple types) @@ -145,5 +156,8 @@ event bro_init() test_case( "remove element", |t5| == 2 ); test_case( "!in operator", 1 !in t5 ); + delete t11[cid, T]; + test_case( "remove element", |t11| == 1 ); + test_case( "!in operator", [cid, T] !in t11 ); } From 6c2ee1ef54fda85142b8e017f5cce77f0afb9955 Mon Sep 17 00:00:00 2001 From: Robin Sommer Date: Fri, 23 Nov 2012 18:46:57 -0800 Subject: [PATCH 6/9] Removing in_hook variable in parser. I believe that's unnecessary and tests indeed pass just fine without it. --- src/parse.y | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/parse.y b/src/parse.y index 9a66973d6b..b4eee1a56c 100644 --- a/src/parse.y +++ b/src/parse.y @@ -119,7 +119,6 @@ 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; @@ -1373,11 +1372,11 @@ stmt: brofiler.AddStmt($$); } - | TOK_HOOK { in_hook = true; } hook { in_hook = false; } ';' opt_no_test + | TOK_HOOK hook ';' opt_no_test { - set_location(@1, @5); - $$ = new HookStmt($3); - if ( ! $6 ) + set_location(@1, @4); + $$ = new HookStmt($2); + if ( ! $4 ) brofiler.AddStmt($$); } @@ -1538,7 +1537,7 @@ hook: expr '(' opt_expr_list ')' { set_location(@1, @4); - $$ = new CallExpr($1, $3, in_hook); + $$ = new CallExpr($1, $3, true); } ; From a5e237f50c963bfc815cff6c9c9a457189a7ec68 Mon Sep 17 00:00:00 2001 From: Robin Sommer Date: Fri, 23 Nov 2012 19:33:28 -0800 Subject: [PATCH 7/9] The ASCII writer now supports a filter config option 'only_single_header_row' that turns the output into CSV format. In that mode all meta data is skipped except for a single header line with the fields names. Example: local my_filter: Log::Filter = [$name = "my-filter", $writer = Log::WRITER_ASCII, $config = table(["only_single_header_row"] = "T")]; Contributed by Carsten Langer. --- CHANGES | 15 ++++ VERSION | 2 +- .../base/frameworks/logging/writers/ascii.bro | 8 +++ src/logging/writers/Ascii.cc | 69 +++++++++++++------ src/logging/writers/Ascii.h | 1 + .../ssh-filtered.log | 6 ++ .../base/frameworks/logging/ascii-csv.bro | 37 ++++++++++ 7 files changed, 117 insertions(+), 21 deletions(-) create mode 100644 testing/btest/Baseline/scripts.base.frameworks.logging.ascii-csv/ssh-filtered.log create mode 100644 testing/btest/scripts/base/frameworks/logging/ascii-csv.bro diff --git a/CHANGES b/CHANGES index 5a04012955..cc42fa701b 100644 --- a/CHANGES +++ b/CHANGES @@ -1,4 +1,19 @@ +2.1-178 | 2012-11-23 19:35:32 -0800 + + * The ASCII writer now supports a new filter config option + "only_single_header_row" that turns the output into CSV format + when set to "T". (Carsten Langer) + + * Add new function flavor called a "hook". This new flavor of + function behaves like a "synchronous event". See + doc/scripts/builtins.rst more details on usage. (Jon Siwek) + + * Improve auto-generated enum documentation. The names of enum types + are tracked so that variables holding a value of a given enum type + can generate a reference to it instead of just listing the type as + a generic "enum". (Jon Siwek) + 2.1-171 | 2012-11-23 18:24:15 -0800 * Fix ambiguity between composite table index and record ctor diff --git a/VERSION b/VERSION index 7a44c3a19f..fc5f5588a7 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.1-171 +2.1-178 diff --git a/scripts/base/frameworks/logging/writers/ascii.bro b/scripts/base/frameworks/logging/writers/ascii.bro index bacb0996d0..800af51502 100644 --- a/scripts/base/frameworks/logging/writers/ascii.bro +++ b/scripts/base/frameworks/logging/writers/ascii.bro @@ -1,5 +1,13 @@ ##! Interface for the ASCII log writer. Redefinable options are available ##! to tweak the output format of ASCII logs. +##! +##! The ASCII writer supports currently one writer-specific filter option via +##! ``config``: setting ``only_single_header_row`` to ``T`` turns the output into +##! into CSV mode where only a single header row with the column names is printed +##! out as meta information. Example filter using this:: +##! +##! local my_filter: Log::Filter = [$name = "my-filter", $writer = Log::WRITER_ASCII, $config = table(["only_single_header_row"] = "T")]; +##! module LogAscii; diff --git a/src/logging/writers/Ascii.cc b/src/logging/writers/Ascii.cc index 11b322f5a3..c65b0701c3 100644 --- a/src/logging/writers/Ascii.cc +++ b/src/logging/writers/Ascii.cc @@ -19,6 +19,7 @@ Ascii::Ascii(WriterFrontend* frontend) : WriterBackend(frontend) { fd = 0; ascii_done = false; + only_single_header_row = false; output_to_stdout = BifConst::LogAscii::output_to_stdout; include_meta = BifConst::LogAscii::include_meta; @@ -80,7 +81,7 @@ void Ascii::CloseFile(double t) if ( ! fd ) return; - if ( include_meta ) + if ( include_meta && ! only_single_header_row ) WriteHeaderField("close", Timestamp(0)); safe_close(fd); @@ -108,29 +109,29 @@ bool Ascii::DoInit(const WriterInfo& info, int num_fields, const Field* const * return false; } + for ( WriterInfo::config_map::const_iterator i = info.config.begin(); i != info.config.end(); i++ ) + { + if ( strcmp(i->first, "only_single_header_row") == 0 ) + { + if ( strcmp(i->second, "T") == 0 ) + only_single_header_row = true; + + else if ( strcmp(i->second, "F") == 0 ) + only_single_header_row = false; + + else + { + Error("invalid value for 'only_single_header_row', must be boolean (T/F)"); + return false; + } + } + } + if ( include_meta ) { string names; string types; - string str = string(meta_prefix, meta_prefix_len) - + "separator " // Always use space as separator here. - + get_escaped_string(string(separator, separator_len), false) - + "\n"; - - if ( ! safe_write(fd, str.c_str(), str.length()) ) - goto write_error; - - if ( ! (WriteHeaderField("set_separator", get_escaped_string( - string(set_separator, set_separator_len), false)) && - WriteHeaderField("empty_field", get_escaped_string( - string(empty_field, empty_field_len), false)) && - WriteHeaderField("unset_field", get_escaped_string( - string(unset_field, unset_field_len), false)) && - WriteHeaderField("path", get_escaped_string(path, false)) && - WriteHeaderField("open", Timestamp(0))) ) - goto write_error; - for ( int i = 0; i < num_fields; ++i ) { if ( i > 0 ) @@ -143,11 +144,39 @@ bool Ascii::DoInit(const WriterInfo& info, int num_fields, const Field* const * types += fields[i]->TypeName().c_str(); } + if ( only_single_header_row ) + { + // A single CSV-style line is all we need. + string str = names + "\n"; + if ( ! safe_write(fd, str.c_str(), str.length()) ) + goto write_error; + + return true; + } + + string str = string(meta_prefix, meta_prefix_len) + + "separator " // Always use space as separator here. + + get_escaped_string(string(separator, separator_len), false) + + "\n"; + + if ( ! safe_write(fd, str.c_str(), str.length()) ) + goto write_error; + + if ( ! (WriteHeaderField("set_separator", get_escaped_string( + string(set_separator, set_separator_len), false)) && + WriteHeaderField("empty_field", get_escaped_string( + string(empty_field, empty_field_len), false)) && + WriteHeaderField("unset_field", get_escaped_string( + string(unset_field, unset_field_len), false)) && + WriteHeaderField("path", get_escaped_string(path, false)) && + WriteHeaderField("open", Timestamp(0))) ) + goto write_error; + if ( ! (WriteHeaderField("fields", names) && WriteHeaderField("types", types)) ) goto write_error; } - + return true; write_error: diff --git a/src/logging/writers/Ascii.h b/src/logging/writers/Ascii.h index cf0190aa80..37ec19aba6 100644 --- a/src/logging/writers/Ascii.h +++ b/src/logging/writers/Ascii.h @@ -45,6 +45,7 @@ private: // Options set from the script-level. bool output_to_stdout; bool include_meta; + bool only_single_header_row; char* separator; int separator_len; diff --git a/testing/btest/Baseline/scripts.base.frameworks.logging.ascii-csv/ssh-filtered.log b/testing/btest/Baseline/scripts.base.frameworks.logging.ascii-csv/ssh-filtered.log new file mode 100644 index 0000000000..f59c7c8f54 --- /dev/null +++ b/testing/btest/Baseline/scripts.base.frameworks.logging.ascii-csv/ssh-filtered.log @@ -0,0 +1,6 @@ +t id.orig_h id.orig_p id.resp_h id.resp_p status country b +1353727995.082217 1.2.3.4 1234 2.3.4.5 80 success unknown - +1353727995.082217 1.2.3.4 1234 2.3.4.5 80 - US - +1353727995.082217 1.2.3.4 1234 2.3.4.5 80 failure UK - +1353727995.082217 1.2.3.4 1234 2.3.4.5 80 - BR - +1353727995.082217 1.2.3.4 1234 2.3.4.5 80 failure (empty) T diff --git a/testing/btest/scripts/base/frameworks/logging/ascii-csv.bro b/testing/btest/scripts/base/frameworks/logging/ascii-csv.bro new file mode 100644 index 0000000000..1c10f5fc6b --- /dev/null +++ b/testing/btest/scripts/base/frameworks/logging/ascii-csv.bro @@ -0,0 +1,37 @@ +# +# @TEST-EXEC: bro -b %INPUT +# @TEST-EXEC: cat ssh.log | grep -v PREFIX.*20..- >ssh-filtered.log +# @TEST-EXEC: btest-diff ssh-filtered.log + +module SSH; + +export { + redef enum Log::ID += { LOG }; + + type Log: record { + t: time; + id: conn_id; # Will be rolled out into individual columns. + status: string &optional; + country: string &default="unknown"; + b: bool &optional; + } &log; +} + +event bro_init() +{ + Log::create_stream(SSH::LOG, [$columns=Log]); + + local filter = Log::get_filter(SSH::LOG, "default"); + filter$config = table(["only_single_header_row"] = "T"); + Log::add_filter(SSH::LOG, filter); + + local cid = [$orig_h=1.2.3.4, $orig_p=1234/tcp, $resp_h=2.3.4.5, $resp_p=80/tcp]; + + Log::write(SSH::LOG, [$t=network_time(), $id=cid, $status="success"]); + Log::write(SSH::LOG, [$t=network_time(), $id=cid, $country="US"]); + Log::write(SSH::LOG, [$t=network_time(), $id=cid, $status="failure", $country="UK"]); + Log::write(SSH::LOG, [$t=network_time(), $id=cid, $country="BR"]); + Log::write(SSH::LOG, [$t=network_time(), $id=cid, $b=T, $status="failure", $country=""]); + +} + From 7245aa597768aa335cd597ba7798da3dab992346 Mon Sep 17 00:00:00 2001 From: Robin Sommer Date: Fri, 23 Nov 2012 19:39:27 -0800 Subject: [PATCH 8/9] Adding NEWS placeholder for hooks and CSV mode. --- NEWS | 5 +++++ VERSION | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index 26dcf0e8cd..63c4d5d6f7 100644 --- a/NEWS +++ b/NEWS @@ -17,6 +17,11 @@ New Functionality - ssl.log now also records the subject client and issuer certificates. +- Hooks: TODO: Briefly summarize the documention from + doc/scripts/builtins.rst here. + +- The ASCII writer can now output CSV files on a per filter basis. + Changed Functionality ~~~~~~~~~~~~~~~~~~~~~ diff --git a/VERSION b/VERSION index fc5f5588a7..6463a80a16 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.1-178 +2.1-179 From e2fdf16e0c87b914cf271e03766010f24d1b41ca Mon Sep 17 00:00:00 2001 From: Robin Sommer Date: Fri, 23 Nov 2012 19:51:42 -0800 Subject: [PATCH 9/9] Updating submodule(s). [nomail] --- aux/broccoli | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aux/broccoli b/aux/broccoli index 907210ce14..a8846fc5b0 160000 --- a/aux/broccoli +++ b/aux/broccoli @@ -1 +1 @@ -Subproject commit 907210ce1470724fb386f939cc1b10a4caa2ae39 +Subproject commit a8846fc5b004ffe4e3d00e826d0077ba19518192