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

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,
IntrusivePtr<Expr> init, attr_list* attr, decl_type dt,
bool do_init)
{
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();
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 )
{
id->Error("already defined", init.get());
if ( IsFunc(id->Type()->Tag()) )
add_prototype(id, t.get(), attr, init);
else
id->Error("already defined", init.get());
return;
}
}
@ -338,6 +412,26 @@ static bool has_attr(const attr_list* al, attr_tag tag)
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,
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);
}
std::optional<FuncType::Prototype> prototype;
if ( id->Type() )
{
if ( ! same_type(id->Type(), t.get()) )
id->Type()->Error("incompatible types", t.get());
prototype = func_type_check(id->Type()->AsFuncType(), t.get());
else
if ( prototype )
{
// If a previous declaration of the function had &default params,
// automatically transfer any that are missing (convenience so that
// 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 )
@ -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->SetType(arg_i->type);
if ( prototype )
arg_id->SetOffset(prototype->offsets[i]);
}
if ( Attr* depr_attr = find_attr(attrs, ATTR_DEPRECATED) )