diff --git a/doc/scripts/builtins.rst b/doc/scripts/builtins.rst index b9febb176c..06d61232ad 100644 --- a/doc/scripts/builtins.rst +++ b/doc/scripts/builtins.rst @@ -459,6 +459,31 @@ The Bro scripting language supports the following built-in types. print greeting("Dave"); + Function parameters may specify default values as long as they appear + last in the parameter list: + + .. code:: bro + + global foo: function(s: string, t: string &default="abc", u: count &default=0); + + If a function was previously declared with default parameters, the + default expressions can be omitted when implementing the function + body and they will still be used for function calls that lack those + arguments. + + .. code:: bro + + function foo(s: string, t: string, u: count) + { + print s, t, u; + } + + And calls to the function may omit the defaults from the argument list: + + .. code:: bro + + foo("test"); + .. bro:type:: event Event handlers are nearly identical in both syntax and semantics to @@ -597,10 +622,10 @@ scripting language supports the following built-in attributes. .. bro:attr:: &default - Uses a default value for a record field or container elements. For - example, ``table[int] of string &default="foo" }`` would create a - table that returns the :bro:type:`string` ``"foo"`` for any - non-existing index. + Uses a default value for a record field, a function/hook/event + parameter, or container elements. For example, ``table[int] of + string &default="foo" }`` would create a table that returns the + :bro:type:`string` ``"foo"`` for any non-existing index. .. bro:attr:: &redef diff --git a/src/Expr.cc b/src/Expr.cc index 37e1770673..12d3d72304 100644 --- a/src/Expr.cc +++ b/src/Expr.cc @@ -5478,6 +5478,50 @@ int check_and_promote_exprs(ListExpr*& elements, TypeList* types) return 1; } +int check_and_promote_args(ListExpr*& args, RecordType* types) + { + expr_list& el = args->Exprs(); + int ntypes = types->NumFields(); + + // give variadic BIFs automatic pass + if ( ntypes == 1 && types->FieldDecl(0)->type->Tag() == TYPE_ANY ) + return 1; + + if ( el.length() < ntypes ) + { + expr_list def_elements; + + // Start from rightmost parameter, work backward to fill in missing + // arguments using &default expressions. + for ( int i = ntypes - 1; i >= el.length(); --i ) + { + TypeDecl* td = types->FieldDecl(i); + Attr* def_attr = td->attrs ? td->attrs->FindAttr(ATTR_DEFAULT) : 0; + + if ( ! def_attr ) + { + types->Error("parameter mismatch", args); + return 0; + } + + def_elements.insert(def_attr->AttrExpr()); + } + + loop_over_list(def_elements, i) + el.append(def_elements[i]->Ref()); + } + + TypeList* tl = new TypeList(); + + for ( int i = 0; i < types->NumFields(); ++i ) + tl->Append(types->FieldType(i)->Ref()); + + int rval = check_and_promote_exprs(args, tl); + Unref(tl); + + return rval; + } + int check_and_promote_exprs_to_type(ListExpr*& elements, BroType* type) { expr_list& el = elements->Exprs(); diff --git a/src/Expr.h b/src/Expr.h index 1e07708d14..bb7526d502 100644 --- a/src/Expr.h +++ b/src/Expr.h @@ -1082,13 +1082,14 @@ Expr* get_assign_expr(Expr* op1, Expr* op2, int is_init); // match, promote it as necessary (modifying the ref parameter accordingly) // and return 1. // -// The second and third forms are for promoting a list of +// The second, third, and fourth forms are for promoting a list of // expressions (which is updated in place) to either match a list of // types or a single type. // // Note, the type is not "const" because it can be ref'd. extern int check_and_promote_expr(Expr*& e, BroType* t); extern int check_and_promote_exprs(ListExpr*& elements, TypeList* types); +extern int check_and_promote_args(ListExpr*& args, RecordType* types); extern int check_and_promote_exprs_to_type(ListExpr*& elements, BroType* type); // Returns a fully simplified form of the expression. Note that passed diff --git a/src/Type.cc b/src/Type.cc index db6e940e87..6461bf2560 100644 --- a/src/Type.cc +++ b/src/Type.cc @@ -671,8 +671,24 @@ FuncType::FuncType(RecordType* arg_args, BroType* arg_yield, function_flavor arg arg_types = new TypeList(); + bool has_default_arg = false; + for ( int i = 0; i < args->NumFields(); ++i ) + { + const TypeDecl* td = args->FieldDecl(i); + + if ( td->attrs && td->attrs->FindAttr(ATTR_DEFAULT) ) + has_default_arg = true; + + else if ( has_default_arg ) + { + const char* err_str = fmt("required parameter '%s' must precede " + "default parameters", td->id); + args->Error(err_str); + } + arg_types->Append(args->FieldType(i)->Ref()); + } } string FuncType::FlavorString() const @@ -708,7 +724,7 @@ BroType* FuncType::YieldType() int FuncType::MatchesIndex(ListExpr*& index) const { - return check_and_promote_exprs(index, arg_types) ? + return check_and_promote_args(index, args) ? MATCHES_INDEX_SCALAR : DOES_NOT_MATCH_INDEX; } diff --git a/src/Var.cc b/src/Var.cc index 0aadd93e92..cee231d26f 100644 --- a/src/Var.cc +++ b/src/Var.cc @@ -318,6 +318,29 @@ void add_type(ID* id, BroType* t, attr_list* attr, int /* is_event */) id->SetAttrs(new Attributes(attr, tnew, false)); } +static void transfer_arg_defaults(RecordType* args, RecordType* recv) + { + for ( int i = 0; i < args->NumFields(); ++i ) + { + TypeDecl* args_i = args->FieldDecl(i); + TypeDecl* recv_i = recv->FieldDecl(i); + + Attr* def = args_i->attrs ? args_i->attrs->FindAttr(ATTR_DEFAULT) : 0; + + if ( ! def ) continue; + + if ( ! recv_i->attrs ) + { + attr_list* a = new attr_list(); + a->append(def); + recv_i->attrs = new Attributes(a, recv_i->type, true); + } + + else if ( ! recv_i->attrs->FindAttr(ATTR_DEFAULT) ) + recv_i->attrs->AddAttr(def); + } + } + void begin_func(ID* id, const char* module_name, function_flavor flavor, int is_redef, FuncType* t) { @@ -335,6 +358,11 @@ void begin_func(ID* id, const char* module_name, function_flavor flavor, { if ( ! same_type(id->Type(), t) ) id->Type()->Error("incompatible types", t); + + // 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()); } else if ( is_redef ) diff --git a/src/bif_arg.cc b/src/bif_arg.cc index a4772e4d73..64b0cb131a 100644 --- a/src/bif_arg.cc +++ b/src/bif_arg.cc @@ -30,11 +30,13 @@ BuiltinFuncArg::BuiltinFuncArg(const char* arg_name, int arg_type) type_str = ""; } -BuiltinFuncArg::BuiltinFuncArg(const char* arg_name, const char* arg_type_str) +BuiltinFuncArg::BuiltinFuncArg(const char* arg_name, const char* arg_type_str, + const char* arg_attr_str) { name = arg_name; type = TYPE_OTHER; type_str = arg_type_str; + attr_str = arg_attr_str; for ( int i = 0; builtin_func_arg_type[i].bif_type[0] != '\0'; ++i ) if ( ! strcmp(builtin_func_arg_type[i].bif_type, arg_type_str) ) @@ -46,7 +48,8 @@ BuiltinFuncArg::BuiltinFuncArg(const char* arg_name, const char* arg_type_str) void BuiltinFuncArg::PrintBro(FILE* fp) { - fprintf(fp, "%s: %s%s", name, builtin_func_arg_type[type].bro_type, type_str); + fprintf(fp, "%s: %s%s %s", name, builtin_func_arg_type[type].bro_type, + type_str, attr_str); } void BuiltinFuncArg::PrintCDef(FILE* fp, int n) diff --git a/src/bif_arg.h b/src/bif_arg.h index 4ba6fa0c4f..1d8b565241 100644 --- a/src/bif_arg.h +++ b/src/bif_arg.h @@ -25,7 +25,10 @@ extern const char* builtin_func_arg_type_bro_name[]; class BuiltinFuncArg { public: BuiltinFuncArg(const char* arg_name, int arg_type); - BuiltinFuncArg(const char* arg_name, const char* arg_type_str); + BuiltinFuncArg(const char* arg_name, const char* arg_type_str, + const char* arg_attr_str = ""); + + void SetAttrStr(const char* arg_attr_str) { attr_str = arg_attr_str; }; const char* Name() const { return name; } int Type() const { return type; } @@ -39,6 +42,7 @@ protected: const char* name; int type; const char* type_str; + const char* attr_str; }; #endif diff --git a/src/bro.bif b/src/bro.bif index bea8e343c0..9f32892e99 100644 --- a/src/bro.bif +++ b/src/bro.bif @@ -2472,7 +2472,7 @@ function bytestring_to_double%(s: string%): double ## ## Returns: The value contained in *s*, or 0 if the conversion failed. ## -function bytestring_to_count%(s: string, is_le: bool%): count +function bytestring_to_count%(s: string, is_le: bool &default=F%): count %{ #ifdef HOST_BIGENDIAN static const bool host_bigendian = true; diff --git a/src/builtin-func.l b/src/builtin-func.l index 9baeb1a9f9..fa9915077f 100644 --- a/src/builtin-func.l +++ b/src/builtin-func.l @@ -26,6 +26,7 @@ int check_c_mode(int t) %} WS [ \t]+ +OWS [ \t]* /* Note, bifcl only accepts a single "::" in IDs while the policy layer acceptes multiple. (But the policy layer doesn't have a hierachy. */ @@ -101,7 +102,13 @@ HEX [0-9a-fA-F]+ return TOK_ID; } -&{ID} { + /* + Hacky way to pass along arbitrary attribute expressions since the BIF parser + has little understanding of valid Bro expressions. With this pattern, the + attribute expression should stop when it reaches another attribute, another + function argument, or the end of the function declaration. + */ +&{ID}({OWS}={OWS}[^&%;,]+)? { int t = check_c_mode(TOK_ATTR); if ( t == TOK_ATTR ) diff --git a/src/builtin-func.y b/src/builtin-func.y index 474f321ccd..54ec5a29f6 100644 --- a/src/builtin-func.y +++ b/src/builtin-func.y @@ -277,7 +277,7 @@ void print_event_c_body(FILE *fp) %left ',' ':' -%type TOK_C_TOKEN TOK_ID TOK_CSTR TOK_WS TOK_COMMENT TOK_ATTR TOK_INT opt_ws type +%type TOK_C_TOKEN TOK_ID TOK_CSTR TOK_WS TOK_COMMENT TOK_ATTR TOK_INT opt_ws type attr_list opt_attr_list %type TOK_ATOM TOK_BOOL %union { @@ -375,7 +375,8 @@ type_def_types: TOK_RECORD { set_definition_type(TYPE_DEF, "Table"); } ; -event_def: event_prefix opt_ws plain_head opt_attr end_of_head ';' +event_def: event_prefix opt_ws plain_head opt_attr_list + { fprintf(fp_bro_init, "%s", $4); } end_of_head ';' { print_event_c_prototype(fp_func_h, true); print_event_c_prototype(fp_func_def, false); @@ -458,20 +459,17 @@ const_def: TOK_CONST opt_ws TOK_ID opt_ws ':' opt_ws TOK_ID opt_ws ';' accessor); } - -/* Currently support only boolean and string values */ -opt_attr_init: /* nothing */ - | '=' opt_ws TOK_BOOL opt_ws - { - fprintf(fp_bro_init, "=%s%c%s", $2, ($3) ? 'T' : 'F', $4); - } - | '=' opt_ws TOK_CSTR opt_ws - { fprintf(fp_bro_init, "=%s%s%s", $2, $3, $4); } +attr_list: + attr_list TOK_ATTR + { $$ = concat($1, $2); } + | + TOK_ATTR ; -opt_attr: /* nothing */ - | opt_attr TOK_ATTR { fprintf(fp_bro_init, "%s", $2); } - opt_ws opt_attr_init +opt_attr_list: + attr_list + | /* nothing */ + { $$ = ""; } ; func_prefix: TOK_FUNCTION @@ -579,9 +577,10 @@ args: args_1 { /* empty, to avoid yacc complaint about type clash */ } ; -args_1: args_1 ',' opt_ws arg opt_ws - | opt_ws arg opt_ws - { /* empty */ } +args_1: args_1 ',' opt_ws arg opt_ws opt_attr_list + { if ( ! args.empty() ) args[args.size()-1]->SetAttrStr($6); } + | opt_ws arg opt_ws opt_attr_list + { if ( ! args.empty() ) args[args.size()-1]->SetAttrStr($4); } ; // TODO: Migrate all other compound types to this rule. Once the BiF language diff --git a/testing/btest/Baseline/bifs.bytestring_to_count/out b/testing/btest/Baseline/bifs.bytestring_to_count/out index e5f8c84f26..36e3526756 100644 --- a/testing/btest/Baseline/bifs.bytestring_to_count/out +++ b/testing/btest/Baseline/bifs.bytestring_to_count/out @@ -34,3 +34,4 @@ 65535 0 0 +65535 diff --git a/testing/btest/Baseline/language.default-params/out b/testing/btest/Baseline/language.default-params/out new file mode 100644 index 0000000000..0ae804cc6b --- /dev/null +++ b/testing/btest/Baseline/language.default-params/out @@ -0,0 +1,15 @@ +foo_func, test +foo_func, hello +bar_func, hmm, hi, 5 +bar_func, cool, beans, 5 +bar_func, cool, beans, 13 +foo_hook, test +foo_hook, hello +bar_hook, hmm, hi, 5 +bar_hook, cool, beans, 5 +bar_hook, cool, beans, 13 +foo_event, test +foo_event, hello +bar_event, hmm, hi, 5 +bar_event, cool, beans, 5 +bar_event, cool, beans, 13 diff --git a/testing/btest/bifs/bytestring_to_count.bro b/testing/btest/bifs/bytestring_to_count.bro index e26b201f26..db50929cb7 100644 --- a/testing/btest/bifs/bytestring_to_count.bro +++ b/testing/btest/bifs/bytestring_to_count.bro @@ -52,4 +52,7 @@ event bro_init() print bytestring_to_count("\x00\x00\x00\x00\x00\x00\x00\x00", T); # 0 print bytestring_to_count("\x00\x00\x00\x00\x00\x00\x00\x00", F); # 0 + # test the default endianness parameter + print bytestring_to_count("\x00\x00\x00\x00\x00\x00\xff\xff"); # 65535 + } diff --git a/testing/btest/language/default-params.bro b/testing/btest/language/default-params.bro new file mode 100644 index 0000000000..c11adbf3b5 --- /dev/null +++ b/testing/btest/language/default-params.bro @@ -0,0 +1,65 @@ +# @TEST-EXEC: bro -b %INPUT >out +# @TEST-EXEC: btest-diff out + +### functions + +global foo_func: function(a: string &default="hello"); + +# &defaults transfer from the declaration automatically +function foo_func(a: string) + { + print "foo_func", a; + } + +function bar_func(a: string, b: string &default="hi", c: count &default=5) + { + print "bar_func", a, b, c; + } + +### events + +global foo_event: event(a: string &default="hello"); + +event foo_event(a: string) + { + print "foo_event", a; + } + +event bar_event(a: string, b: string &default="hi", c: count &default=5) + { + print "bar_event", a, b, c; + } + +### hooks + +global foo_hook: hook(a: string &default="hello"); + +hook foo_hook(a: string) + { + print "foo_hook", a; + } + +hook bar_hook(a: string, b: string &default="hi", c: count &default=5) + { + print "bar_hook", a, b, c; + } + +{} + +foo_func("test"); +foo_func(); +bar_func("hmm"); +bar_func("cool", "beans"); +bar_func("cool", "beans", 13); + +event foo_event("test"); +event foo_event(); +event bar_event("hmm"); +event bar_event("cool", "beans"); +event bar_event("cool", "beans", 13); + +hook foo_hook("test"); +hook foo_hook(); +hook bar_hook("hmm"); +hook bar_hook("cool", "beans"); +hook bar_hook("cool", "beans", 13);