Allow alternate event/hook prototype declarations

The alternates must be some subset of the canonical prototype (the one
that's first declared) and allows users to define handlers for any
such prototype.  Example:

    # Prototype declarations
    global my_event: event(s: string, c: count);
    global my_event: event(c: count);
    global my_event: event();

    # Handler definitions
    event my_event(s: string, c: count) { print s, c; }
    event my_event(c: count) { print c; }
    event my_event() { }

This allows handlers to consume a subset of the arguments or even
re-order them.  This makes it easier to either extend an existing
event/hook's arguments and/or deprecate usages of certain prototypes.
This commit is contained in:
Jon Siwek 2020-03-31 18:06:05 -07:00
parent eefafdc1e1
commit 8c0e8ecd28
12 changed files with 399 additions and 8 deletions

@ -1 +1 @@
Subproject commit 69d7443794e8712344561a49517340aab3e58ff4 Subproject commit 14f2e1ddf994ffdaae0b863d94e53d6f73ce5671

2
doc

@ -1 +1 @@
Subproject commit fbbcdd345d275036384f20e2f11f7a9d6d26f9d2 Subproject commit 9e77250acc4b5eaf03c0535e8ea00a0a4d19cae0

View file

@ -431,8 +431,42 @@ void ID::DescribeReST(ODesc* d, bool roles_only) const
d->Add("`"); d->Add("`");
} }
else else
{
type->DescribeReST(d, roles_only); type->DescribeReST(d, roles_only);
if ( IsFunc(type->Tag()) )
{
auto ft = type->AsFuncType();
if ( ft->Flavor() == FUNC_FLAVOR_EVENT ||
ft->Flavor() == FUNC_FLAVOR_HOOK )
{
const auto& protos = ft->Prototypes();
if ( protos.size() > 1 )
{
auto first = true;
for ( const auto& proto : protos )
{
if ( first )
{
first = false;
continue;
}
d->NL();
d->Add(":Type: :zeek:type:`");
d->Add(ft->FlavorString());
d->Add("` (");
proto.args->DescribeFieldsReST(d, true);
d->Add(")");
}
}
}
}
}
d->NL(); d->NL();
} }

View file

@ -432,6 +432,7 @@ FuncType::FuncType(IntrusivePtr<RecordType> arg_args,
flavor = arg_flavor; flavor = arg_flavor;
bool has_default_arg = false; bool has_default_arg = false;
std::map<int, int> offsets;
for ( int i = 0; i < args->NumFields(); ++i ) for ( int i = 0; i < args->NumFields(); ++i )
{ {
@ -448,7 +449,10 @@ FuncType::FuncType(IntrusivePtr<RecordType> arg_args,
} }
arg_types->Append({NewRef{}, args->FieldType(i)}); arg_types->Append({NewRef{}, args->FieldType(i)});
offsets[i] = i;
} }
prototypes.emplace_back(Prototype{false, args, std::move(offsets)});
} }
FuncType* FuncType::ShallowClone() FuncType* FuncType::ShallowClone()
@ -458,6 +462,7 @@ FuncType* FuncType::ShallowClone()
f->arg_types = {NewRef{}, arg_types->AsTypeList()}; f->arg_types = {NewRef{}, arg_types->AsTypeList()};
f->yield = yield; f->yield = yield;
f->flavor = flavor; f->flavor = flavor;
f->prototypes = prototypes;
return f; return f;
} }
@ -572,6 +577,47 @@ void FuncType::DescribeReST(ODesc* d, bool roles_only) const
} }
} }
void FuncType::AddPrototype(Prototype p)
{
prototypes.emplace_back(std::move(p));
}
std::optional<FuncType::Prototype> FuncType::FindPrototype(const RecordType& args) const
{
for ( const auto& p : prototypes )
{
if ( args.NumFields() != p.args->NumFields() )
continue;
if ( args.NumFields() == 0 )
{
if ( p.args->NumFields() == 0 )
return p;
continue;
}
bool matched = true;
for ( auto i = 0; i < args.NumFields(); ++i )
{
auto ptype = p.args->FieldType(i);
auto desired_type = args.FieldType(i);
if ( same_type(ptype, desired_type) )
continue;
matched = false;
break;
}
if ( matched )
return p;
}
return {};
}
TypeDecl::TypeDecl(IntrusivePtr<BroType> t, const char* i, attr_list* arg_attrs, bool in_record) TypeDecl::TypeDecl(IntrusivePtr<BroType> t, const char* i, attr_list* arg_attrs, bool in_record)
: type(std::move(t)), : type(std::move(t)),
attrs(arg_attrs ? make_intrusive<Attributes>(arg_attrs, type, in_record, false) : nullptr), attrs(arg_attrs ? make_intrusive<Attributes>(arg_attrs, type, in_record, false) : nullptr),

View file

@ -12,6 +12,7 @@
#include <unordered_map> #include <unordered_map>
#include <map> #include <map>
#include <list> #include <list>
#include <optional>
// BRO types. // BRO types.
@ -439,6 +440,17 @@ protected:
class FuncType : public BroType { class FuncType : public BroType {
public: public:
/**
* Prototype is only currently used for events and hooks which declare
* multiple signature prototypes that allow users to have handlers
* with various argument permutations.
*/
struct Prototype {
bool deprecated;
IntrusivePtr<RecordType> args;
std::map<int, int> offsets;
};
FuncType(IntrusivePtr<RecordType> args, IntrusivePtr<BroType> yield, FuncType(IntrusivePtr<RecordType> args, IntrusivePtr<BroType> yield,
function_flavor f); function_flavor f);
FuncType* ShallowClone() override; FuncType* ShallowClone() override;
@ -464,12 +476,29 @@ public:
void Describe(ODesc* d) const override; void Describe(ODesc* d) const override;
void DescribeReST(ODesc* d, bool roles_only = false) const override; void DescribeReST(ODesc* d, bool roles_only = false) const override;
/**
* Adds a new event/hook signature allowed for use in handlers.
*/
void AddPrototype(Prototype s);
/**
* Returns a prototype signature that matches the desired argument types.
*/
std::optional<Prototype> FindPrototype(const RecordType& args) const;
/**
* Returns all allowed function prototypes.
*/
const std::vector<Prototype>& Prototypes() const
{ return prototypes; }
protected: protected:
FuncType() : BroType(TYPE_FUNC) { flavor = FUNC_FLAVOR_FUNCTION; } FuncType() : BroType(TYPE_FUNC) { flavor = FUNC_FLAVOR_FUNCTION; }
IntrusivePtr<RecordType> args; IntrusivePtr<RecordType> args;
IntrusivePtr<TypeList> arg_types; IntrusivePtr<TypeList> arg_types;
IntrusivePtr<BroType> yield; IntrusivePtr<BroType> yield;
function_flavor flavor; function_flavor flavor;
std::vector<Prototype> prototypes;
}; };
class TypeType : public BroType { class TypeType : public BroType {

View file

@ -29,13 +29,83 @@ static IntrusivePtr<Val> init_val(Expr* init, const BroType* t,
} }
} }
static bool add_prototype(ID* id, BroType* t, attr_list* attrs,
const IntrusivePtr<Expr>& init)
{
if ( ! IsFunc(id->Type()->Tag()) )
return false;
if ( ! IsFunc(t->Tag()) )
{
t->Error("type incompatible with previous definition", id);
return false;
}
auto canon_ft = id->Type()->AsFuncType();
auto alt_ft = t->AsFuncType();
if ( canon_ft->Flavor() != alt_ft->Flavor() )
{
alt_ft->Error("incompatible function flavor", canon_ft);
return false;
}
if ( canon_ft->Flavor() == FUNC_FLAVOR_FUNCTION )
{
alt_ft->Error("redeclaration of function", canon_ft);
return false;
}
if ( init )
{
init->Error("initialization not allowed during event/hook alternate prototype declaration");
return false;
}
auto canon_args = canon_ft->Args();
auto alt_args = alt_ft->Args();
if ( auto p = canon_ft->FindPrototype(*alt_args); p )
{
alt_ft->Error("alternate function prototype already exists", p->args.get());
return false;
}
std::map<int, int> offsets;
for ( auto i = 0; i < alt_args->NumFields(); ++i )
{
auto field = alt_args->FieldName(i);
auto o = canon_args->FieldOffset(field);
if ( o < 0 )
{
alt_ft->Error(fmt("alternate function prototype arg '%s' not found in canonical prototype", field), canon_ft);
return false;
}
offsets[i] = o;
}
auto deprecated = false;
if ( attrs )
for ( const auto& a : *attrs )
if ( a->Tag() == ATTR_DEPRECATED )
deprecated = true;
FuncType::Prototype p{deprecated, {NewRef{}, alt_args}, std::move(offsets)};
canon_ft->AddPrototype(std::move(p));
return true;
}
static void make_var(ID* id, IntrusivePtr<BroType> t, init_class c, static void make_var(ID* id, IntrusivePtr<BroType> t, init_class c,
IntrusivePtr<Expr> init, attr_list* attr, decl_type dt, IntrusivePtr<Expr> init, attr_list* attr, decl_type dt,
bool do_init) bool do_init)
{ {
if ( id->Type() ) if ( id->Type() )
{ {
if ( id->IsRedefinable() || (! init && attr) ) if ( id->IsRedefinable() || (! init && attr && ! IsFunc(id->Type()->Tag())) )
{ {
BroObj* redef_obj = init ? (BroObj*) init.get() : (BroObj*) t.get(); BroObj* redef_obj = init ? (BroObj*) init.get() : (BroObj*) t.get();
if ( dt != VAR_REDEF ) if ( dt != VAR_REDEF )
@ -44,7 +114,11 @@ static void make_var(ID* id, IntrusivePtr<BroType> t, init_class c,
else if ( dt != VAR_REDEF || init || ! attr ) else if ( dt != VAR_REDEF || init || ! attr )
{ {
if ( IsFunc(id->Type()->Tag()) )
add_prototype(id, t.get(), attr, init);
else
id->Error("already defined", init.get()); id->Error("already defined", init.get());
return; return;
} }
} }
@ -338,6 +412,26 @@ static bool has_attr(const attr_list* al, attr_tag tag)
return find_attr(al, tag) != nullptr; return find_attr(al, tag) != nullptr;
} }
static std::optional<FuncType::Prototype> func_type_check(const FuncType* decl, const FuncType* impl)
{
if ( decl->Flavor() != impl->Flavor() )
{
impl->Error("incompatible function flavor", decl);
return {};
}
if ( impl->Flavor() == FUNC_FLAVOR_FUNCTION )
{
if ( same_type(decl, impl) )
return decl->Prototypes()[0];
impl->Error("incompatible function types", decl);
return {};
}
return decl->FindPrototype(*impl->Args());
}
void begin_func(ID* id, const char* module_name, function_flavor flavor, void begin_func(ID* id, const char* module_name, function_flavor flavor,
bool is_redef, IntrusivePtr<FuncType> t, attr_list* attrs) bool is_redef, IntrusivePtr<FuncType> t, attr_list* attrs)
{ {
@ -351,16 +445,24 @@ void begin_func(ID* id, const char* module_name, function_flavor flavor,
t->ClearYieldType(flavor); t->ClearYieldType(flavor);
} }
std::optional<FuncType::Prototype> prototype;
if ( id->Type() ) if ( id->Type() )
{ {
if ( ! same_type(id->Type(), t.get()) ) prototype = func_type_check(id->Type()->AsFuncType(), t.get());
id->Type()->Error("incompatible types", t.get());
else if ( prototype )
{
// If a previous declaration of the function had &default params, // If a previous declaration of the function had &default params,
// automatically transfer any that are missing (convenience so that // automatically transfer any that are missing (convenience so that
// implementations don't need to specify the &default expression again). // implementations don't need to specify the &default expression again).
transfer_arg_defaults(id->Type()->AsFuncType()->Args(), t->Args()); transfer_arg_defaults(prototype->args.get(), t->Args());
if ( prototype->deprecated )
t->Warn("use of deprecated prototype", id);
}
else
t->Error("use of undeclared alternate prototype", id);
} }
else if ( is_redef ) else if ( is_redef )
@ -410,6 +512,9 @@ void begin_func(ID* id, const char* module_name, function_flavor flavor,
arg_id = install_ID(arg_i->id, module_name, false, false); arg_id = install_ID(arg_i->id, module_name, false, false);
arg_id->SetType(arg_i->type); arg_id->SetType(arg_i->type);
if ( prototype )
arg_id->SetOffset(prototype->offsets[i]);
} }
if ( Attr* depr_attr = find_attr(attrs, ATTR_DEPRECATED) ) if ( Attr* depr_attr = find_attr(attrs, ATTR_DEPRECATED) )

View file

@ -0,0 +1,2 @@
error in /home/jon/pro/zeek/zeek/testing/btest/.tmp/language.alternate-event-hook-prototypes-invalid-arg/alternate-event-hook-prototypes-invalid-arg.zeek, line 5 and /home/jon/pro/zeek/zeek/testing/btest/.tmp/language.alternate-event-hook-prototypes-invalid-arg/alternate-event-hook-prototypes-invalid-arg.zeek, line 4: alternate function prototype already exists (event(nope:string; cantdothis:count;) and record { s:string; c:count; })
error in /home/jon/pro/zeek/zeek/testing/btest/.tmp/language.alternate-event-hook-prototypes-invalid-arg/alternate-event-hook-prototypes-invalid-arg.zeek, line 8 and /home/jon/pro/zeek/zeek/testing/btest/.tmp/language.alternate-event-hook-prototypes-invalid-arg/alternate-event-hook-prototypes-invalid-arg.zeek, line 7: alternate function prototype arg 'andnotthiseither' not found in canonical prototype (hook(andnotthiseither:addr;) : bool and hook(s:string; c:count;) : bool)

View file

@ -0,0 +1,11 @@
warning in /home/jon/pro/zeek/zeek/testing/btest/.tmp/language.alternate-event-hook-prototypes/alternate-event-hook-prototypes.zeek, line 56 and /home/jon/pro/zeek/zeek/testing/btest/.tmp/language.alternate-event-hook-prototypes/alternate-event-hook-prototypes.zeek, line 10: use of deprecated prototype (hook(c:count;) : bool and my_hook)
my_hook, infinite, 13
my_hook, 13, infinite
my_hook, infinite
my_hook, 13
my_hook
my_event, enantiodromia, 42
my_event, 42, enantiodromia
my_event, enantiodromia
my_event, 42
my_event

View file

@ -0,0 +1,8 @@
error in /home/jon/pro/zeek/zeek/testing/btest/.tmp/language.undeclared-alternate-event-hook-prototype/undeclared-alternate-event-hook-prototype.zeek, line 13 and /home/jon/pro/zeek/zeek/testing/btest/.tmp/language.undeclared-alternate-event-hook-prototype/undeclared-alternate-event-hook-prototype.zeek, line 4: use of undeclared alternate prototype (event(c:count; s:string;) and my_event)
error in /home/jon/pro/zeek/zeek/testing/btest/.tmp/language.undeclared-alternate-event-hook-prototype/undeclared-alternate-event-hook-prototype.zeek, line 18 and /home/jon/pro/zeek/zeek/testing/btest/.tmp/language.undeclared-alternate-event-hook-prototype/undeclared-alternate-event-hook-prototype.zeek, line 4: use of undeclared alternate prototype (event(s:string;) and my_event)
error in /home/jon/pro/zeek/zeek/testing/btest/.tmp/language.undeclared-alternate-event-hook-prototype/undeclared-alternate-event-hook-prototype.zeek, line 23 and /home/jon/pro/zeek/zeek/testing/btest/.tmp/language.undeclared-alternate-event-hook-prototype/undeclared-alternate-event-hook-prototype.zeek, line 4: use of undeclared alternate prototype (event(c:count;) and my_event)
error in /home/jon/pro/zeek/zeek/testing/btest/.tmp/language.undeclared-alternate-event-hook-prototype/undeclared-alternate-event-hook-prototype.zeek, line 28 and /home/jon/pro/zeek/zeek/testing/btest/.tmp/language.undeclared-alternate-event-hook-prototype/undeclared-alternate-event-hook-prototype.zeek, line 4: use of undeclared alternate prototype (event() and my_event)
error in /home/jon/pro/zeek/zeek/testing/btest/.tmp/language.undeclared-alternate-event-hook-prototype/undeclared-alternate-event-hook-prototype.zeek, line 38 and /home/jon/pro/zeek/zeek/testing/btest/.tmp/language.undeclared-alternate-event-hook-prototype/undeclared-alternate-event-hook-prototype.zeek, line 6: use of undeclared alternate prototype (hook(c:count; s:string;) : bool and my_hook)
error in /home/jon/pro/zeek/zeek/testing/btest/.tmp/language.undeclared-alternate-event-hook-prototype/undeclared-alternate-event-hook-prototype.zeek, line 43 and /home/jon/pro/zeek/zeek/testing/btest/.tmp/language.undeclared-alternate-event-hook-prototype/undeclared-alternate-event-hook-prototype.zeek, line 6: use of undeclared alternate prototype (hook(s:string;) : bool and my_hook)
error in /home/jon/pro/zeek/zeek/testing/btest/.tmp/language.undeclared-alternate-event-hook-prototype/undeclared-alternate-event-hook-prototype.zeek, line 48 and /home/jon/pro/zeek/zeek/testing/btest/.tmp/language.undeclared-alternate-event-hook-prototype/undeclared-alternate-event-hook-prototype.zeek, line 6: use of undeclared alternate prototype (hook(c:count;) : bool and my_hook)
error in /home/jon/pro/zeek/zeek/testing/btest/.tmp/language.undeclared-alternate-event-hook-prototype/undeclared-alternate-event-hook-prototype.zeek, line 53 and /home/jon/pro/zeek/zeek/testing/btest/.tmp/language.undeclared-alternate-event-hook-prototype/undeclared-alternate-event-hook-prototype.zeek, line 6: use of undeclared alternate prototype (hook() : bool and my_hook)

View file

@ -0,0 +1,24 @@
# @TEST-EXEC-FAIL: zeek -b %INPUT >out 2>&1
# @TEST-EXEC: TEST_DIFF_CANONIFIER=$SCRIPTS/diff-remove-abspath btest-diff out
global my_event: event(s: string, c: count);
global my_event: event(nope: string, cantdothis: count);
global my_hook: hook(s: string, c: count);
global my_hook: hook(andnotthiseither: addr);
event my_event(s: string, c: count)
{
print "my_event", s, c;
}
hook my_hook(s: string, c: count)
{
print "my_hook", s, c;
}
event zeek_init()
{
hook my_hook("infinite", 13);
event my_event("enantiodromia", 42);
}

View file

@ -0,0 +1,70 @@
# @TEST-EXEC: zeek -b %INPUT >out 2>&1
# @TEST-EXEC: TEST_DIFF_CANONIFIER=$SCRIPTS/diff-remove-abspath btest-diff out
global my_event: event(s: string, c: count);
global my_event: event(c: count, s: string);
global my_event: event(s: string);
global my_event: event(c: count);
global my_event: event();
global my_hook: hook(s: string, c: count);
global my_hook: hook(c: count, s: string);
global my_hook: hook(s: string);
global my_hook: hook(c: count) &deprecated;
global my_hook: hook();
event my_event(s: string, c: count)
{
print "my_event", s, c;
}
event my_event(c: count, s: string)
{
print "my_event", c, s;
}
event my_event(s: string)
{
print "my_event", s;
}
event my_event(c: count)
{
print "my_event", c;
}
event my_event()
{
print "my_event";
}
hook my_hook(s: string, c: count)
{
print "my_hook", s, c;
}
hook my_hook(c: count, s: string)
{
print "my_hook", c, s;
}
hook my_hook(s: string)
{
print "my_hook", s;
}
hook my_hook(c: count)
{
print "my_hook", c;
}
hook my_hook()
{
print "my_hook";
}
event zeek_init()
{
hook my_hook("infinite", 13);
event my_event("enantiodromia", 42);
}

View file

@ -0,0 +1,62 @@
# @TEST-EXEC-FAIL: zeek -b %INPUT >out 2>&1
# @TEST-EXEC: TEST_DIFF_CANONIFIER=$SCRIPTS/diff-remove-abspath btest-diff out
global my_event: event(s: string, c: count);
global my_hook: hook(s: string, c: count);
event my_event(s: string, c: count)
{
print "my_event", s, c;
}
event my_event(c: count, s: string)
{
print "my_event", c, s;
}
event my_event(s: string)
{
print "my_event", s;
}
event my_event(c: count)
{
print "my_event", c;
}
event my_event()
{
print "my_event";
}
hook my_hook(s: string, c: count)
{
print "my_hook", s, c;
}
hook my_hook(c: count, s: string)
{
print "my_hook", c, s;
}
hook my_hook(s: string)
{
print "my_hook", s;
}
hook my_hook(c: count)
{
print "my_hook", c;
}
hook my_hook()
{
print "my_hook";
}
event zeek_init()
{
hook my_hook("infinite", 13);
event my_event("enantiodromia", 42);
}