diff --git a/NEWS b/NEWS index b0e99cb9c3..3ee3e8bd19 100644 --- a/NEWS +++ b/NEWS @@ -16,6 +16,10 @@ New Functionality Zeek now raises a warning when a script declares these events while this option is set to true. +- Type expressions were added to Zeek script. Within scripts, a type may be + assigned to a local or global then later used as a type. The type cannot be + redefined. This allows types to be directly passed into BIFs without aliasing. + Changed Functionality --------------------- diff --git a/src/Expr.cc b/src/Expr.cc index 88c2bbd759..884719ebfd 100644 --- a/src/Expr.cc +++ b/src/Expr.cc @@ -4764,6 +4764,26 @@ void IsExpr::ExprDescribe(ODesc* d) const { t->Describe(d); } +TypeExpr::TypeExpr(TypePtr t) : Expr(EXPR_TYPE), ty(std::move(t)) { + SetType(make_intrusive(ty)); + SetLocationInfo(ty->GetLocationInfo()); +} + +ValPtr TypeExpr::Eval(Frame* /* f */) const { return make_intrusive(ty, true); } + +TraversalCode TypeExpr::Traverse(TraversalCallback* cb) const { + TraversalCode tc = cb->PreExpr(this); + HANDLE_TC_EXPR_PRE(tc); + + tc = ty->Traverse(cb); + HANDLE_TC_EXPR_PRE(tc); + + tc = cb->PostExpr(this); + HANDLE_TC_EXPR_POST(tc); +} + +void TypeExpr::ExprDescribe(ODesc* d) const { ty->Describe(d); } + ExprPtr get_assign_expr(ExprPtr op1, ExprPtr op2, bool is_init) { ExprPtr e; diff --git a/src/Expr.h b/src/Expr.h index 78576758a9..01c497978d 100644 --- a/src/Expr.h +++ b/src/Expr.h @@ -94,6 +94,7 @@ enum ExprTag : int { EXPR_CAST, EXPR_IS, EXPR_INDEX_SLICE_ASSIGN, + EXPR_TYPE, // The following types of expressions are only created for ASTs // transformed to reduced form; they aren't germane for ASTs produced @@ -1672,6 +1673,25 @@ protected: ExprPtr Duplicate() override; }; +/** + * An expression representing a type without a bound identifier. This is necessary + * in order to use many primitives directly as values into BIFs without aliases. + */ +class TypeExpr final : public Expr { +public: + TypeExpr(TypePtr t); + + ValPtr Eval(Frame* f) const override; + TraversalCode Traverse(TraversalCallback* cb) const override; + ExprPtr Duplicate() override; + +protected: + void ExprDescribe(ODesc* d) const override; + +private: + TypePtr ty; +}; + // Assigns v1[v2] = v3. Returns an error message, or nullptr on success. // Factored out so that compiled code can call it as well as the interpreter. extern const char* assign_to_index(ValPtr v1, ValPtr v2, ValPtr v3, bool& iterators_invalidated); diff --git a/src/parse.y b/src/parse.y index eac4d95c4e..51fcd382d2 100644 --- a/src/parse.y +++ b/src/parse.y @@ -64,7 +64,7 @@ %type expr opt_expr rhs opt_init anonymous_function lambda_body index_slice opt_deprecated when_condition %type event %type stmt stmt_list func_body for_head -%type type opt_type enum_body +%type simple_type type opt_type enum_body %type func_hdr func_params %type type_list %type type_decl formal_args_decl @@ -766,6 +766,11 @@ expr: " in arbitrary expression contexts, only" " as a statement"); + // Type types cannot be reassigned because that could require parse-time + // control flow to resolve a variable's type + if ( $1->Tag() == EXPR_NAME && $1->GetType()->Tag() == TYPE_TYPE ) + $1->Error("type expression variables cannot be assigned to"); + $$ = get_assign_expr({AdoptRef{}, $1}, {AdoptRef{}, $4}, in_init).release(); } @@ -1025,6 +1030,11 @@ expr: set_location(@1); $$ = new ConstExpr({AdoptRef{}, $1}); } + | simple_type + { + set_location(@1); + $$ = new TypeExpr({AdoptRef{}, $1}); + } | '/' { begin_RE(); } TOK_PATTERN_TEXT TOK_PATTERN_END { @@ -1153,63 +1163,75 @@ enum_body_elem: } ; -type: - TOK_BOOL { +simple_type: + TOK_BOOL + { set_location(@1); $$ = base_type(TYPE_BOOL)->Ref(); } - | TOK_INT { + | TOK_INT + { set_location(@1); $$ = base_type(TYPE_INT)->Ref(); } - | TOK_COUNT { + | TOK_COUNT + { set_location(@1); $$ = base_type(TYPE_COUNT)->Ref(); } - | TOK_DOUBLE { + | TOK_DOUBLE + { set_location(@1); $$ = base_type(TYPE_DOUBLE)->Ref(); } - | TOK_TIME { + | TOK_TIME + { set_location(@1); $$ = base_type(TYPE_TIME)->Ref(); } - | TOK_INTERVAL { + | TOK_INTERVAL + { set_location(@1); $$ = base_type(TYPE_INTERVAL)->Ref(); } - | TOK_STRING { + | TOK_STRING + { set_location(@1); $$ = base_type(TYPE_STRING)->Ref(); } - | TOK_PATTERN { + | TOK_PATTERN + { set_location(@1); $$ = base_type(TYPE_PATTERN)->Ref(); } - | TOK_PORT { + | TOK_PORT + { set_location(@1); $$ = base_type(TYPE_PORT)->Ref(); } - | TOK_ADDR { + | TOK_ADDR + { set_location(@1); $$ = base_type(TYPE_ADDR)->Ref(); } - | TOK_SUBNET { + | TOK_SUBNET + { set_location(@1); $$ = base_type(TYPE_SUBNET)->Ref(); } - | TOK_ANY { + | TOK_ANY + { set_location(@1); $$ = base_type(TYPE_ANY)->Ref(); } @@ -1265,24 +1287,6 @@ type: $$ = new VectorType({AdoptRef{}, $3}); } - | TOK_FUNCTION func_params - { - set_location(@1, @2); - $$ = $2; - } - - | TOK_EVENT '(' formal_args ')' - { - set_location(@1, @3); - $$ = new FuncType({AdoptRef{}, $3}, nullptr, FUNC_FLAVOR_EVENT); - } - - | TOK_HOOK '(' formal_args ')' - { - set_location(@1, @3); - $$ = new FuncType({AdoptRef{}, $3}, base_type(TYPE_BOOL), FUNC_FLAVOR_HOOK); - } - | TOK_FILE TOK_OF type { set_location(@1, @3); @@ -1301,9 +1305,31 @@ type: $$ = new OpaqueType($3); } +type: + simple_type + | TOK_FUNCTION func_params + { + set_location(@1, @2); + $$ = $2; + } + + | TOK_HOOK '(' formal_args ')' + { + set_location(@1, @3); + $$ = new FuncType({AdoptRef{}, $3}, base_type(TYPE_BOOL), FUNC_FLAVOR_HOOK); + } + + | TOK_EVENT '(' formal_args ')' + { + set_location(@1, @3); + $$ = new FuncType({AdoptRef{}, $3}, nullptr, FUNC_FLAVOR_EVENT); + } + | resolve_id { - if ( ! $1 || ! ($$ = $1->IsType() ? $1->GetType().get() : nullptr) ) + if ( $1 && $1->GetType()->Tag() == TYPE_TYPE ) + $$ = $1->GetType()->AsTypeType()->GetType()->Ref(); + else if ( ! $1 || ! ($$ = $1->IsType() ? $1->GetType().get() : nullptr) ) { NullStmt here; if ( $1 ) diff --git a/src/script_opt/Expr.cc b/src/script_opt/Expr.cc index 2f471d3ef5..3355265423 100644 --- a/src/script_opt/Expr.cc +++ b/src/script_opt/Expr.cc @@ -3393,6 +3393,8 @@ TraversalCode NopExpr::Traverse(TraversalCallback* cb) const { HANDLE_TC_EXPR_POST(tc); } +ExprPtr TypeExpr::Duplicate() { return SetSucc(new TypeExpr(ty)); } + static bool same_singletons(ExprPtr e1, ExprPtr e2) { auto e1t = e1->Tag(); auto e2t = e2->Tag(); diff --git a/src/script_opt/ScriptOpt.cc b/src/script_opt/ScriptOpt.cc index f160d1e963..acb750b3e3 100644 --- a/src/script_opt/ScriptOpt.cc +++ b/src/script_opt/ScriptOpt.cc @@ -793,6 +793,7 @@ bool has_AST_node_unknown_to_script_opt(const ProfileFunc* prof, bool /* is_ZAM EXPR_CAST, EXPR_IS, // EXPR_INDEX_SLICE_ASSIGN, + // EXPR_TYPE, EXPR_INLINE, // EXPR_APPEND_TO, // EXPR_INDEX_ASSIGN, @@ -804,7 +805,7 @@ bool has_AST_node_unknown_to_script_opt(const ProfileFunc* prof, bool /* is_ZAM // EXPR_ANY_INDEX, // EXPR_SCRIPT_OPT_BUILTIN, // EXPR_NOP, -#define SCRIPT_OPT_NUM_EXPRS 70 +#define SCRIPT_OPT_NUM_EXPRS 71 }; // clang-format on diff --git a/testing/btest/Baseline/language.type-expr-assignment/.stderr b/testing/btest/Baseline/language.type-expr-assignment/.stderr new file mode 100644 index 0000000000..3c2633ee6d --- /dev/null +++ b/testing/btest/Baseline/language.type-expr-assignment/.stderr @@ -0,0 +1,2 @@ +### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. +error in <...>/type-expr-assignment.zeek, line 8: type expression variables cannot be assigned to (str) diff --git a/testing/btest/Baseline/language.type-expr/.stdout b/testing/btest/Baseline/language.type-expr/.stdout new file mode 100644 index 0000000000..08b4f709b2 --- /dev/null +++ b/testing/btest/Baseline/language.type-expr/.stdout @@ -0,0 +1,6 @@ +### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. +hi there :) +hey!! +42 +[v=aoeu, valid=T] +type diff --git a/testing/btest/language/type-expr-assignment.zeek b/testing/btest/language/type-expr-assignment.zeek new file mode 100644 index 0000000000..c8b27e8f88 --- /dev/null +++ b/testing/btest/language/type-expr-assignment.zeek @@ -0,0 +1,10 @@ +# @TEST-DOC: Ensure redefining a type expression ID is an error +# @TEST-EXEC-FAIL: zeek -b %INPUT +# @TEST-EXEC: TEST_DIFF_CANONIFIER=$SCRIPTS/diff-remove-abspath btest-diff .stderr + +event zeek_init() + { + local str = string; + str = count; + local my_string: str; # This will still be a string + } diff --git a/testing/btest/language/type-expr.zeek b/testing/btest/language/type-expr.zeek new file mode 100644 index 0000000000..fe64754a74 --- /dev/null +++ b/testing/btest/language/type-expr.zeek @@ -0,0 +1,23 @@ +# @TEST-DOC: Test valid use of type expressions in scripts +# @TEST-EXEC: zeek -b %INPUT +# @TEST-EXEC: TEST_DIFF_CANONIFIER= btest-diff .stdout + +global global_str = string; +global my_global_string: global_str = "hey!!"; + +event zeek_init() + { + local str = string; + local my_string: str = "hi there :)"; + print my_string; + print my_global_string; + + local integer = int; + local my_int: integer = 41; + my_int += 1; + print my_int; + + # Try a couple of functions that take types + print from_json("\"aoeu\"", string); + print type_name(string); + }