Add type expressions to Zeek script

Closes #4173

This is not particularly useful for Zeek script, since they just act as
aliases. The main utility is to easily pass primitive types into BIFs.
This commit is contained in:
Evan Typanski 2025-01-22 14:01:08 -05:00
parent cb44a6ca53
commit f28a50717b
10 changed files with 148 additions and 34 deletions

4
NEWS
View file

@ -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
---------------------

View file

@ -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<TypeType>(ty));
SetLocationInfo(ty->GetLocationInfo());
}
ValPtr TypeExpr::Eval(Frame* /* f */) const { return make_intrusive<TypeVal>(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;

View file

@ -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);

View file

@ -64,7 +64,7 @@
%type <expr> expr opt_expr rhs opt_init anonymous_function lambda_body index_slice opt_deprecated when_condition
%type <event_expr> event
%type <stmt> stmt stmt_list func_body for_head
%type <type> type opt_type enum_body
%type <type> simple_type type opt_type enum_body
%type <func_type> func_hdr func_params
%type <type_l> type_list
%type <type_decl> 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 )

View file

@ -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();

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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
}

View file

@ -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);
}