diff --git a/aux/bifcl b/aux/bifcl index 69d7443794..14f2e1ddf9 160000 --- a/aux/bifcl +++ b/aux/bifcl @@ -1 +1 @@ -Subproject commit 69d7443794e8712344561a49517340aab3e58ff4 +Subproject commit 14f2e1ddf994ffdaae0b863d94e53d6f73ce5671 diff --git a/doc b/doc index fbbcdd345d..9e77250acc 160000 --- a/doc +++ b/doc @@ -1 +1 @@ -Subproject commit fbbcdd345d275036384f20e2f11f7a9d6d26f9d2 +Subproject commit 9e77250acc4b5eaf03c0535e8ea00a0a4d19cae0 diff --git a/src/ID.cc b/src/ID.cc index e0caea3b2d..1c1530fb49 100644 --- a/src/ID.cc +++ b/src/ID.cc @@ -431,8 +431,42 @@ void ID::DescribeReST(ODesc* d, bool roles_only) const d->Add("`"); } else + { 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(); } diff --git a/src/Type.cc b/src/Type.cc index baa1b6552a..ef4e4b4fad 100644 --- a/src/Type.cc +++ b/src/Type.cc @@ -432,6 +432,7 @@ FuncType::FuncType(IntrusivePtr arg_args, flavor = arg_flavor; bool has_default_arg = false; + std::map offsets; for ( int i = 0; i < args->NumFields(); ++i ) { @@ -448,7 +449,10 @@ FuncType::FuncType(IntrusivePtr arg_args, } arg_types->Append({NewRef{}, args->FieldType(i)}); + offsets[i] = i; } + + prototypes.emplace_back(Prototype{false, args, std::move(offsets)}); } FuncType* FuncType::ShallowClone() @@ -458,6 +462,7 @@ FuncType* FuncType::ShallowClone() f->arg_types = {NewRef{}, arg_types->AsTypeList()}; f->yield = yield; f->flavor = flavor; + f->prototypes = prototypes; 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::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 t, const char* i, attr_list* arg_attrs, bool in_record) : type(std::move(t)), attrs(arg_attrs ? make_intrusive(arg_attrs, type, in_record, false) : nullptr), diff --git a/src/Type.h b/src/Type.h index 9686bcdf9f..129ed9fcd9 100644 --- a/src/Type.h +++ b/src/Type.h @@ -12,6 +12,7 @@ #include #include #include +#include // BRO types. @@ -439,6 +440,17 @@ protected: class FuncType : public BroType { 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 args; + std::map offsets; + }; + FuncType(IntrusivePtr args, IntrusivePtr yield, function_flavor f); FuncType* ShallowClone() override; @@ -464,12 +476,29 @@ public: void Describe(ODesc* d) 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 FindPrototype(const RecordType& args) const; + + /** + * Returns all allowed function prototypes. + */ + const std::vector& Prototypes() const + { return prototypes; } + protected: FuncType() : BroType(TYPE_FUNC) { flavor = FUNC_FLAVOR_FUNCTION; } IntrusivePtr args; IntrusivePtr arg_types; IntrusivePtr yield; function_flavor flavor; + std::vector prototypes; }; class TypeType : public BroType { diff --git a/src/Var.cc b/src/Var.cc index 406e8ab54b..061fb9c099 100644 --- a/src/Var.cc +++ b/src/Var.cc @@ -29,13 +29,83 @@ static IntrusivePtr init_val(Expr* init, const BroType* t, } } +static bool add_prototype(ID* id, BroType* t, attr_list* attrs, + const IntrusivePtr& 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 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 t, init_class c, IntrusivePtr 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 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 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 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 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) ) diff --git a/testing/btest/Baseline/language.alternate-event-hook-prototypes-invalid-arg/out b/testing/btest/Baseline/language.alternate-event-hook-prototypes-invalid-arg/out new file mode 100644 index 0000000000..eee2a611e1 --- /dev/null +++ b/testing/btest/Baseline/language.alternate-event-hook-prototypes-invalid-arg/out @@ -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) diff --git a/testing/btest/Baseline/language.alternate-event-hook-prototypes/out b/testing/btest/Baseline/language.alternate-event-hook-prototypes/out new file mode 100644 index 0000000000..8aad989b3d --- /dev/null +++ b/testing/btest/Baseline/language.alternate-event-hook-prototypes/out @@ -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 diff --git a/testing/btest/Baseline/language.undeclared-alternate-event-hook-prototype/out b/testing/btest/Baseline/language.undeclared-alternate-event-hook-prototype/out new file mode 100644 index 0000000000..0e2d386615 --- /dev/null +++ b/testing/btest/Baseline/language.undeclared-alternate-event-hook-prototype/out @@ -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) diff --git a/testing/btest/language/alternate-event-hook-prototypes-invalid-arg.zeek b/testing/btest/language/alternate-event-hook-prototypes-invalid-arg.zeek new file mode 100644 index 0000000000..61bcc79b66 --- /dev/null +++ b/testing/btest/language/alternate-event-hook-prototypes-invalid-arg.zeek @@ -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); + } diff --git a/testing/btest/language/alternate-event-hook-prototypes.zeek b/testing/btest/language/alternate-event-hook-prototypes.zeek new file mode 100644 index 0000000000..e58c542b99 --- /dev/null +++ b/testing/btest/language/alternate-event-hook-prototypes.zeek @@ -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); + } diff --git a/testing/btest/language/undeclared-alternate-event-hook-prototype.zeek b/testing/btest/language/undeclared-alternate-event-hook-prototype.zeek new file mode 100644 index 0000000000..1522a91bc7 --- /dev/null +++ b/testing/btest/language/undeclared-alternate-event-hook-prototype.zeek @@ -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); + }