Merge remote-tracking branch 'origin/topic/jsiwek/hook'

* origin/topic/jsiwek/hook:
  Add memory leak unit test for "hook" function flavor.
  Add new function flavor called a "hook".
This commit is contained in:
Robin Sommer 2012-11-23 18:39:51 -08:00
commit d9bb9e0eb1
31 changed files with 578 additions and 79 deletions

View file

@ -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
----------

View file

@ -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;

View file

@ -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);
}

View file

@ -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;

View file

@ -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");
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());

View file

@ -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; }

View file

@ -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);

View file

@ -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;

View file

@ -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:

View file

@ -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)

View file

@ -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() : "<ignored>");
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);

View file

@ -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;

View file

@ -23,7 +23,7 @@ const char* stmt_name(BroStmtTag t)
"print", "event", "expr", "if", "when", "switch",
"for", "next", "break", "return", "add", "delete",
"list", "bodylist",
"<init>",
"<init>", "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;

View file

@ -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);

View file

@ -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;

View file

@ -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:

View file

@ -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 {

View file

@ -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,20 +315,28 @@ 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

View file

@ -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;

View file

@ -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;

View file

@ -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 <re> pattern
%type <expr> expr init anonymous_function
%type <event_expr> event
%type <call_expr> hook
%type <stmt> stmt stmt_list func_body for_head
%type <type> type opt_type enum_body
%type <func_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); }

View file

@ -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"! */

View file

@ -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
#############

View file

@ -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
~~~~~~~~~~~~~~~~~~

View file

@ -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]

View file

@ -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))

View file

@ -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"]);
}

View file

@ -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

View file

@ -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

View file

@ -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"]);
}

View file

@ -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");
}