diff --git a/src/spicy/runtime-support.cc b/src/spicy/runtime-support.cc index edbb2b1614..ec9d133a9d 100644 --- a/src/spicy/runtime-support.cc +++ b/src/spicy/runtime-support.cc @@ -1071,3 +1071,422 @@ hilti::rt::Time rt::network_time() { auto _ = hilti::rt::profiler::start("zeek/rt/network_time"); return hilti::rt::Time(run_state::network_time, hilti::rt::Time::SecondTag()); } + +static ValPtr convertSignedInteger(int64_t i, std::string_view have_type, const TypePtr& target) { + if ( target->Tag() == TYPE_INT ) + return val_mgr->Int(i); + + if ( target->Tag() == TYPE_COUNT ) { + if ( i >= 0 ) + return val_mgr->Count(i); + else + throw rt::ParameterMismatch(hilti::rt::fmt("negative %s", have_type), target); + } + + throw rt::ParameterMismatch(have_type, target); +} + +static ValPtr convertUnsignedInteger(uint64_t i, std::string_view have_type, const TypePtr& target) { + if ( target->Tag() == TYPE_COUNT ) + return val_mgr->Count(i); + + if ( target->Tag() == TYPE_INT ) { + if ( i < static_cast(std::numeric_limits::max()) ) + return val_mgr->Int(static_cast(i)); + else + throw rt::ParameterMismatch(hilti::rt::fmt("%s too large", have_type), target); + } + + throw rt::ParameterMismatch(have_type, target); +} + +inline void setRecordField(RecordVal* rval, const IntrusivePtr& rtype, int idx, + const hilti::rt::type_info::Value& v) { + using namespace hilti::rt; + + const auto& type = v.type(); + + switch ( type.tag ) { + case TypeInfo::Bool: rval->Assign(idx, type.bool_->get(v)); return; + case TypeInfo::Bytes: rval->Assign(idx, type.bytes->get(v).str()); return; + case TypeInfo::Interval: rval->AssignInterval(idx, type.interval->get(v).seconds()); return; + case TypeInfo::Optional: + if ( const auto& x = type.optional->value(v) ) + setRecordField(rval, rtype, idx, x); + return; + + case TypeInfo::Null: return; + case TypeInfo::Real: rval->Assign(idx, type.real->get(v)); return; + case TypeInfo::SignedInteger_int8: rval->Assign(idx, type.signed_integer_int8->get(v)); return; + case TypeInfo::SignedInteger_int16: rval->Assign(idx, type.signed_integer_int16->get(v)); return; + case TypeInfo::SignedInteger_int32: rval->Assign(idx, type.signed_integer_int32->get(v)); return; + case TypeInfo::SignedInteger_int64: rval->Assign(idx, type.signed_integer_int64->get(v)); return; + case TypeInfo::String: rval->Assign(idx, type.string->get(v)); return; + case TypeInfo::Time: rval->AssignTime(idx, type.time->get(v).seconds()); return; + case TypeInfo::UnsignedInteger_uint8: rval->Assign(idx, type.unsigned_integer_uint8->get(v)); return; + case TypeInfo::UnsignedInteger_uint16: rval->Assign(idx, type.unsigned_integer_uint16->get(v)); return; + case TypeInfo::UnsignedInteger_uint32: rval->Assign(idx, type.unsigned_integer_uint32->get(v)); return; + case TypeInfo::UnsignedInteger_uint64: rval->Assign(idx, type.unsigned_integer_uint64->get(v)); return; + case TypeInfo::StrongReference: + if ( const auto& x = type.strong_reference->value(v) ) + setRecordField(rval, rtype, idx, x); + return; + + case TypeInfo::ValueReference: + if ( const auto& x = type.value_reference->value(v) ) + setRecordField(rval, rtype, idx, x); + return; + + case TypeInfo::WeakReference: + if ( const auto& x = type.weak_reference->value(v) ) + setRecordField(rval, rtype, idx, x); + return; + + case TypeInfo::Address: + case TypeInfo::Bitfield: + case TypeInfo::Enum: + case TypeInfo::Map: + case TypeInfo::Port: + case TypeInfo::Set: + case TypeInfo::Struct: + case TypeInfo::Tuple: + case TypeInfo::Vector: { + // This may return a nullptr in cases where the field is to be left unset. + ValPtr zval = rt::detail::to_val(v, rtype->GetFieldType(idx)); + + if ( v ) + rval->Assign(idx, zval); + else { + // Field must be &optional or &default. + if ( auto attrs = rtype->FieldDecl(idx)->attrs; + ! attrs || + ! (attrs->Find(zeek::detail::ATTR_DEFAULT) || attrs->Find(zeek::detail::ATTR_OPTIONAL)) ) + throw rt::ParameterMismatch( + hilti::rt::fmt("missing initialization for field '%s'", rtype->FieldName(idx))); + } + + return; + } + + default: throw zeek::spicy::rt::InvalidValue("unsupported type for record field"); + } + + hilti::rt::cannot_be_reached(); +} + +ValPtr rt::detail::to_val(const hilti::rt::type_info::Value& value, const TypePtr& target) { + using namespace hilti::rt; + + const auto& type = value.type(); + + switch ( type.tag ) { + case TypeInfo::Address: { + if ( target->Tag() != TYPE_ADDR ) + throw ParameterMismatch(type, target); + + auto in_addr = type.address->get(value).asInAddr(); + if ( auto v4 = std::get_if(&in_addr) ) + return make_intrusive(IPAddr(*v4)); + else { + auto v6 = std::get(in_addr); + return make_intrusive(IPAddr(v6)); + } + } + + case TypeInfo::Bitfield: { + if ( target->Tag() != TYPE_RECORD ) + throw ParameterMismatch(type, target); + + auto rtype = cast_intrusive(target); + + if ( type.bitfield->bits().size() != static_cast(rtype->NumFields()) ) + throw ParameterMismatch(type, target); + + auto rval = make_intrusive(rtype); + + int idx = 0; + for ( const auto& [bits, bvalue] : type.bitfield->iterate(value) ) + setRecordField(rval.get(), rtype, idx++, bvalue); + + return std::move(rval); + } + + case TypeInfo::Bool: { + if ( target->Tag() != TYPE_BOOL ) + throw ParameterMismatch(type, target); + + return val_mgr->Bool(type.bool_->get(value)); + } + + case TypeInfo::Bytes: { + if ( target->Tag() != TYPE_STRING ) + throw ParameterMismatch(type, target); + + const auto& b = type.bytes->get(value); + return make_intrusive(b.str()); + } + + case TypeInfo::Enum: { + if ( target->Tag() != TYPE_ENUM ) + throw ParameterMismatch(type, target); + + auto i = type.enum_->get(value); + + if ( target->GetName() == "transport_proto" ) { + // Special case: map Spicy's `Protocol` to Zeek's `transport_proto`. + if ( auto ty = std::string_view(type.display); ty != "hilti::Protocol" && ty != "spicy::Protocol" ) + throw ParameterMismatch(type.display, target); + + switch ( i.value ) { + case hilti::rt::Protocol::TCP: + return id::transport_proto->GetEnumVal(::TransportProto::TRANSPORT_TCP); + + case hilti::rt::Protocol::UDP: + return id::transport_proto->GetEnumVal(::TransportProto::TRANSPORT_UDP); + + case hilti::rt::Protocol::ICMP: + return id::transport_proto->GetEnumVal(::TransportProto::TRANSPORT_ICMP); + + case hilti::rt::Protocol::Undef: [[fallthrough]]; // just for readability, make Undef explicit + default: return id::transport_proto->GetEnumVal(::TransportProto::TRANSPORT_UNKNOWN); + } + + hilti::rt::cannot_be_reached(); + } + + // Zeek's enum can't be negative, so we swap in max_int for our Undef (-1). + if ( i.value == std::numeric_limits::max() ) + // Can't allow this ... + throw InvalidValue("enum values with value max_int not supported by Zeek integration"); + + zeek_int_t zi = (i.value >= 0 ? i.value : std::numeric_limits<::zeek_int_t>::max()); + return target->AsEnumType()->GetEnumVal(zi); + } + + case TypeInfo::Interval: { + if ( target->Tag() != TYPE_INTERVAL ) + throw ParameterMismatch(type, target); + + return make_intrusive(type.interval->get(value).seconds()); + } + + case TypeInfo::Map: { + if ( target->Tag() != TYPE_TABLE ) + throw ParameterMismatch(type, target); + + if ( type.map->keyType()->tag == TypeInfo::Tuple ) + throw ParameterMismatch("internal error: maps with tuples not yet supported in to_val()"); + + auto tt = cast_intrusive(target); + if ( tt->IsSet() ) + throw ParameterMismatch(type, target); + + if ( tt->GetIndexTypes().size() != 1 ) + throw ParameterMismatch(type, target); + + auto zv = make_intrusive(tt); + + for ( const auto& i : type.map->iterate(value) ) { + auto k = to_val(i.first, tt->GetIndexTypes()[0]); + auto v = to_val(i.second, tt->Yield()); + zv->Assign(std::move(k), std::move(v)); + } + + return std::move(zv); + } + + case TypeInfo::Optional: { + const auto& x = type.optional->value(value); + return x ? detail::to_val(x, target) : nullptr; + } + + case TypeInfo::Port: { + if ( target->Tag() != TYPE_PORT ) + throw ParameterMismatch(type, target); + + auto p = type.port->get(value); + switch ( p.protocol().value() ) { + case hilti::rt::Protocol::TCP: return val_mgr->Port(p.port(), ::TransportProto::TRANSPORT_TCP); + case hilti::rt::Protocol::UDP: return val_mgr->Port(p.port(), ::TransportProto::TRANSPORT_UDP); + case hilti::rt::Protocol::ICMP: return val_mgr->Port(p.port(), ::TransportProto::TRANSPORT_ICMP); + default: throw InvalidValue("port value with undefined protocol"); + } + } + + case TypeInfo::SignedInteger_int8: + return convertSignedInteger(type.signed_integer_int8->get(value), "int8", target); + + case TypeInfo::SignedInteger_int16: + return convertSignedInteger(type.signed_integer_int16->get(value), "int16", target); + + case TypeInfo::SignedInteger_int32: + return convertSignedInteger(type.signed_integer_int32->get(value), "int32", target); + + case TypeInfo::SignedInteger_int64: + return convertSignedInteger(type.signed_integer_int64->get(value), "int64", target); + + case TypeInfo::Time: { + if ( target->Tag() != TYPE_TIME ) + throw ParameterMismatch(type, target); + + return make_intrusive(type.time->get(value).seconds()); + } + + case TypeInfo::Real: { + if ( target->Tag() != TYPE_DOUBLE ) + throw ParameterMismatch(type, target); + + return make_intrusive(type.real->get(value)); + } + + case TypeInfo::Set: { + if ( target->Tag() != TYPE_TABLE ) + throw ParameterMismatch(type, target); + + if ( type.set->dereferencedType()->tag == TypeInfo::Tuple ) + throw ParameterMismatch("internal error: sets with tuples not yet supported in to_val()"); + + auto tt = cast_intrusive(target); + if ( ! tt->IsSet() ) + throw ParameterMismatch(type, target); + + auto zv = make_intrusive(tt); + + for ( const auto& i : type.set->iterate(value) ) { + if ( tt->GetIndexTypes().size() != 1 ) + throw ParameterMismatch(type, target); + + auto idx = to_val(i, tt->GetIndexTypes()[0]); + zv->Assign(std::move(idx), nullptr); + } + + return std::move(zv); + } + + case TypeInfo::String: { + if ( target->Tag() != TYPE_STRING ) + throw ParameterMismatch(type, target); + + const auto& s = type.string->get(value); + return make_intrusive(s); + } + + case TypeInfo::StrongReference: { + const auto& x = type.strong_reference->value(value); + return x ? detail::to_val(x, target) : nullptr; + } + + case TypeInfo::Struct: { + if ( target->Tag() != TYPE_RECORD ) + throw ParameterMismatch(type, target); + + auto rtype = cast_intrusive(target); + + auto rval = make_intrusive(rtype); + auto num_fields = rtype->NumFields(); + + int idx = 0; + for ( const auto& [field, fvalue] : type.struct_->iterate(value) ) { + if ( idx >= num_fields ) + throw ParameterMismatch(hilti::rt::fmt("no matching record field for field '%s'", field.name)); + + // Special-case: Lift up anonymous bitfields. + if ( field.name == "_anon" ) { + if ( field.type->tag == TypeInfo::Bitfield ) { + size_t j = 0; + for ( const auto& x : field.type->bitfield->iterate(fvalue) ) + setRecordField(rval.get(), rtype, idx++, x.second); + + continue; + } + + // There can't be any other anonymous fields. + auto msg = hilti::rt::fmt("unexpected anonymous field: %s", field.name); + reporter->InternalError("%s", msg.c_str()); + } + else { + auto* field_name = rtype->FieldName(idx); + + if ( field_name != field.name ) + throw ParameterMismatch(hilti::rt::fmt("mismatch in field name: expected '%s', found '%s'", + field.name, field_name)); + + if ( fvalue ) + setRecordField(rval.get(), rtype, idx, fvalue); + + idx++; + } + } + + // We already check above that all Spicy-side fields are mapped so we + // can only hit this if there are uninitialized Zeek-side fields left. + if ( idx != num_fields ) + throw ParameterMismatch(hilti::rt::fmt("missing initialization for field '%s'", rtype->FieldName(idx))); + + return std::move(rval); + } + + case TypeInfo::Tuple: { + if ( target->Tag() != TYPE_RECORD ) + throw ParameterMismatch(type, target); + + auto rtype = cast_intrusive(target); + + if ( type.tuple->elements().size() != static_cast(rtype->NumFields()) ) + throw ParameterMismatch(type, target); + + auto rval = make_intrusive(rtype); + + int idx = 0; + for ( const auto& x : type.tuple->iterate(value) ) { + if ( auto fval = x.second.type().optional->value(x.second) ) { + if ( fval ) + setRecordField(rval.get(), rtype, idx, fval); + } + + idx++; + } + + return rval; + } + + case TypeInfo::ValueReference: { + const auto& x = type.value_reference->value(value); + return x ? detail::to_val(x, target) : nullptr; + } + + case TypeInfo::Vector: { + if ( target->Tag() != TYPE_VECTOR && target->Tag() != TYPE_LIST ) + throw ParameterMismatch(type, target); + + auto vt = cast_intrusive(target); + auto zv = make_intrusive(vt); + + for ( const auto& i : type.vector->iterate(value) ) + zv->Assign(zv->Size(), to_val(i, vt->Yield())); + + return std::move(zv); + } + + case TypeInfo::UnsignedInteger_uint8: + return convertUnsignedInteger(type.unsigned_integer_uint8->get(value), "uint8", target); + + case TypeInfo::UnsignedInteger_uint16: + return convertUnsignedInteger(type.unsigned_integer_uint16->get(value), "uint16", target); + + case TypeInfo::UnsignedInteger_uint32: + return convertUnsignedInteger(type.unsigned_integer_uint32->get(value), "uint32", target); + + case TypeInfo::UnsignedInteger_uint64: + return convertUnsignedInteger(type.unsigned_integer_uint64->get(value), "uint64", target); + + case TypeInfo::WeakReference: { + const auto& x = type.weak_reference->value(value); + return x ? detail::to_val(x, target) : nullptr; + } + + default: throw InvalidValue(fmt("unexpected type for conversion to Zeek (%s)", type.display)); + } + + hilti::rt::cannot_be_reached(); +} diff --git a/src/spicy/runtime-support.h b/src/spicy/runtime-support.h index 0adcf8f885..33af128296 100644 --- a/src/spicy/runtime-support.h +++ b/src/spicy/runtime-support.h @@ -491,491 +491,15 @@ void forward_packet(const hilti::rt::integer::safe& identifier); /** Gets the network time from Zeek. */ hilti::rt::Time network_time(); -// Forward-declare to_val() functions. -template -ValPtr to_val(const hilti::rt::Tuple& t, const hilti::rt::TypeInfo* ti, const TypePtr& target); -template -inline ValPtr to_val(const hilti::rt::Bitfield& v, const hilti::rt::TypeInfo* ti, const TypePtr& target); -template::value>* = nullptr> -ValPtr to_val(const T& t, const hilti::rt::TypeInfo* ti, const TypePtr& target); -template::value>* = nullptr> -ValPtr to_val(const T& t, const hilti::rt::TypeInfo* ti, const TypePtr& target); -template::value>* = nullptr> -ValPtr to_val(const T& t, const hilti::rt::TypeInfo* ti, const TypePtr& target); -template -ValPtr to_val(const hilti::rt::Map& s, const hilti::rt::TypeInfo* ti, const TypePtr& target); -template -ValPtr to_val(const hilti::rt::Set& s, const hilti::rt::TypeInfo* ti, const TypePtr& target); -template -ValPtr to_val(const hilti::rt::Vector& v, const hilti::rt::TypeInfo* ti, const TypePtr& target); -template -ValPtr to_val(const std::optional& t, const hilti::rt::TypeInfo* ti, const TypePtr& target); -template -ValPtr to_val(hilti::rt::integer::safe i, const hilti::rt::TypeInfo* ti, const TypePtr& target); -template -ValPtr to_val(const hilti::rt::ValueReference& t, const hilti::rt::TypeInfo* ti, const TypePtr& target); - -inline ValPtr to_val(const hilti::rt::Bool& b, const hilti::rt::TypeInfo* ti, const TypePtr& target); -inline ValPtr to_val(const hilti::rt::Address& d, const hilti::rt::TypeInfo* ti, const TypePtr& target); -inline ValPtr to_val(const hilti::rt::Bytes& b, const hilti::rt::TypeInfo* ti, const TypePtr& target); -inline ValPtr to_val(const hilti::rt::Interval& t, const hilti::rt::TypeInfo* ti, const TypePtr& target); -inline ValPtr to_val(const hilti::rt::Port& d, const hilti::rt::TypeInfo* ti, const TypePtr& target); -inline ValPtr to_val(const hilti::rt::Time& t, const hilti::rt::TypeInfo* ti, const TypePtr& target); -inline ValPtr to_val(const std::string& s, const hilti::rt::TypeInfo* ti, const TypePtr& target); -inline ValPtr to_val(double r, const hilti::rt::TypeInfo* ti, const TypePtr& target); - -/** - * Converts a Spicy-side optional value to a Zeek value. This assumes the - * optional is set, and will throw an exception if not. The result is - * returned with ref count +1. - */ -template -inline ValPtr to_val(const std::optional& t, const hilti::rt::TypeInfo* ti, const TypePtr& target) { - if ( t.has_value() ) - return to_val(hilti::rt::optional::value(t), nullptr, target); - - return nullptr; -} - -/** - * Converts a Spicy-side string to a Zeek value. The result is returned with - * ref count +1. - */ -inline ValPtr to_val(const std::string& s, const hilti::rt::TypeInfo* ti, const TypePtr& target) { - if ( target->Tag() != TYPE_STRING ) - throw ParameterMismatch("string", target); - - return make_intrusive(s); -} - -/** - * Converts a Spicy-side bytes instance to a Zeek value. The result is returned with - * ref count +1. - */ -inline ValPtr to_val(const hilti::rt::Bytes& b, const hilti::rt::TypeInfo* ti, const TypePtr& target) { - if ( target->Tag() != TYPE_STRING ) - throw ParameterMismatch("string", target); - - return make_intrusive(b.str()); -} - -/** - * Converts a Spicy-side integer to a Zeek value. The result is - * returned with ref count +1. - */ -template -inline ValPtr to_val(hilti::rt::integer::safe i, const hilti::rt::TypeInfo* ti, const TypePtr& target) { - ValPtr v = nullptr; - if constexpr ( std::is_unsigned::value ) { - if ( target->Tag() == TYPE_COUNT ) - return val_mgr->Count(i); - - if ( target->Tag() == TYPE_INT ) - return val_mgr->Int(i); - - throw ParameterMismatch("uint64", target); - } - else { - if ( target->Tag() == TYPE_INT ) - return val_mgr->Int(i); - - if ( target->Tag() == TYPE_COUNT ) { - if ( i >= 0 ) - return val_mgr->Count(i); - else - throw ParameterMismatch("negative int64", target); - } - - throw ParameterMismatch("int64", target); - } +namespace detail { +extern ValPtr to_val(const hilti::rt::type_info::Value& value, const TypePtr& target); } template -ValPtr to_val(const hilti::rt::ValueReference& t, const hilti::rt::TypeInfo* ti, const TypePtr& target) { - if ( auto* x = t.get() ) - return to_val(*x, nullptr, target); - - return nullptr; +ValPtr to_val(const T& value, const hilti::rt::TypeInfo* type, const TypePtr& target) { + return detail::to_val(hilti::rt::type_info::Value(&value, type), target); } -/** - * Converts a Spicy-side signed bool to a Zeek value. The result is - * returned with ref count +1. - */ -inline ValPtr to_val(const hilti::rt::Bool& b, const hilti::rt::TypeInfo* ti, const TypePtr& target) { - if ( target->Tag() != TYPE_BOOL ) - throw ParameterMismatch("bool", target); - - return val_mgr->Bool(b); -} - -/** - * Converts a Spicy-side real to a Zeek value. The result is returned with - * ref count +1. - */ -inline ValPtr to_val(double r, const hilti::rt::TypeInfo* ti, const TypePtr& target) { - if ( target->Tag() != TYPE_DOUBLE ) - throw ParameterMismatch("double", target); - - return make_intrusive(r); -} - -/** - * Converts a Spicy-side address to a Zeek value. The result is returned with - * ref count +1. - */ -inline ValPtr to_val(const hilti::rt::Address& d, const hilti::rt::TypeInfo* ti, const TypePtr& target) { - if ( target->Tag() != TYPE_ADDR ) - throw ParameterMismatch("addr", target); - - auto in_addr = d.asInAddr(); - if ( auto v4 = std::get_if(&in_addr) ) - return make_intrusive(IPAddr(*v4)); - else { - auto v6 = std::get(in_addr); - return make_intrusive(IPAddr(v6)); - } -} - -/** - * Converts a Spicy-side address to a Zeek value. The result is returned with - * ref count +1. - */ -inline ValPtr to_val(const hilti::rt::Port& p, const hilti::rt::TypeInfo* ti, const TypePtr& target) { - if ( target->Tag() != TYPE_PORT ) - throw ParameterMismatch("port", target); - - switch ( p.protocol().value() ) { - case hilti::rt::Protocol::TCP: return val_mgr->Port(p.port(), ::TransportProto::TRANSPORT_TCP); - - case hilti::rt::Protocol::UDP: return val_mgr->Port(p.port(), ::TransportProto::TRANSPORT_UDP); - - case hilti::rt::Protocol::ICMP: return val_mgr->Port(p.port(), ::TransportProto::TRANSPORT_ICMP); - - default: throw InvalidValue("port value with undefined protocol"); - } -} - -/** - * Converts a Spicy-side time to a Zeek value. The result is returned with - * ref count +1. - */ -inline ValPtr to_val(const hilti::rt::Interval& i, const hilti::rt::TypeInfo* ti, const TypePtr& target) { - if ( target->Tag() != TYPE_INTERVAL ) - throw ParameterMismatch("interval", target); - - return make_intrusive(i.seconds()); -} - -/** - * Converts a Spicy-side time to a Zeek value. The result is returned with - * ref count +1. - */ -inline ValPtr to_val(const hilti::rt::Time& t, const hilti::rt::TypeInfo* ti, const TypePtr& target) { - if ( target->Tag() != TYPE_TIME ) - throw ParameterMismatch("time", target); - - return make_intrusive(t.seconds()); -} - -/** - * Converts a Spicy-side vector to a Zeek value. The result is returned with - * ref count +1. - */ -template -inline ValPtr to_val(const hilti::rt::Vector& v, const hilti::rt::TypeInfo* ti, const TypePtr& target) { - if ( target->Tag() != TYPE_VECTOR && target->Tag() != TYPE_LIST ) - throw ParameterMismatch("expected vector or list", target); - - auto vt = cast_intrusive(target); - auto zv = make_intrusive(vt); - for ( const auto& i : v ) - zv->Assign(zv->Size(), to_val(i, nullptr, vt->Yield())); - - return std::move(zv); -} - -/** - * Converts a Spicy-side map to a Zeek value. The result is returned with - * ref count +1. - */ -template -inline ValPtr to_val(const hilti::rt::Map& m, const hilti::rt::TypeInfo* ti, const TypePtr& target) { - if ( target->Tag() != TYPE_TABLE ) - throw ParameterMismatch("map", target); - - auto tt = cast_intrusive(target); - if ( tt->IsSet() ) - throw ParameterMismatch("map", target); - - if ( tt->GetIndexTypes().size() != 1 ) - throw ParameterMismatch("map with non-tuple elements", target); - - auto zv = make_intrusive(tt); - - for ( const auto& i : m ) { - auto k = to_val(i.first, nullptr, tt->GetIndexTypes()[0]); - auto v = to_val(i.second, nullptr, tt->Yield()); - zv->Assign(std::move(k), std::move(v)); - } - - return zv; -} // namespace spicy::rt - -/** - * Converts a Spicy-side set to a Zeek value. The result is returned with - * ref count +1. - */ -template -inline ValPtr to_val(const hilti::rt::Set& s, const hilti::rt::TypeInfo* ti, const TypePtr& target) { - if ( target->Tag() != TYPE_TABLE ) - throw ParameterMismatch("set", target); - - auto tt = cast_intrusive(target); - if ( ! tt->IsSet() ) - throw ParameterMismatch("set", target); - - auto zv = make_intrusive(tt); - - for ( const auto& i : s ) { - if ( tt->GetIndexTypes().size() != 1 ) - throw ParameterMismatch("set with non-tuple elements", target); - - auto idx = to_val(i, nullptr, tt->GetIndexTypes()[0]); - zv->Assign(std::move(idx), nullptr); - } - - return zv; -} - -namespace { -template typename> -struct is_instance_impl : std::false_type {}; - -template typename U, typename... Ts> -struct is_instance_impl, U> : std::true_type {}; -} // namespace - -template typename U> -using is_instance = is_instance_impl, U>; - -template -inline void set_record_field(RecordVal* rval, const IntrusivePtr& rtype, int idx, const T& x) { - using NoConversionNeeded = std::integral_constant< - bool, std::is_same_v || std::is_same_v || std::is_same_v || - std::is_same_v || std::is_same_v || std::is_same_v || - std::is_same_v || std::is_same_v || std::is_same_v || - std::is_same_v || std::is_same_v>; - - using IsSignedInteger = std::integral_constant> || - std::is_same_v> || - std::is_same_v> || - std::is_same_v>>; - - using IsUnsignedInteger = std::integral_constant> || - std::is_same_v> || - std::is_same_v> || - std::is_same_v>>; - - if constexpr ( NoConversionNeeded::value ) - rval->Assign(idx, x); - else if constexpr ( IsSignedInteger::value ) - rval->Assign(idx, static_cast(x.Ref())); - else if constexpr ( IsUnsignedInteger::value ) - rval->Assign(idx, static_cast(x.Ref())); - else if constexpr ( std::is_same_v ) - rval->Assign(idx, x.str()); - else if constexpr ( std::is_same_v ) - rval->Assign(idx, static_cast(x)); - else if constexpr ( std::is_same_v ) - rval->Assign(idx, x); - else if constexpr ( std::is_same_v ) - rval->AssignTime(idx, x.seconds()); - else if constexpr ( std::is_same_v ) - rval->AssignInterval(idx, x.seconds()); - else if constexpr ( std::is_same_v ) { - // "Null" turns into an unset optional record field. - } - else if constexpr ( is_instance::value ) { - if ( x.has_value() ) - set_record_field(rval, rtype, idx, *x); - } - else { - ValPtr v = nullptr; - - // This may return a nullptr in cases where the field is to be left unset. - v = to_val(x, nullptr, rtype->GetFieldType(idx)); - - if ( v ) - rval->Assign(idx, v); - else { - // Field must be &optional or &default. - if ( auto attrs = rtype->FieldDecl(idx)->attrs; - ! attrs || ! (attrs->Find(detail::ATTR_DEFAULT) || attrs->Find(detail::ATTR_OPTIONAL)) ) - throw ParameterMismatch(hilti::rt::fmt("missing initialization for field '%s'", rtype->FieldName(idx))); - } - } -} - -/** - * Converts a Spicy-side tuple to a Zeek record value. The result is returned - * with ref count +1. - */ -template -ValPtr to_val(const hilti::rt::Tuple& t, const hilti::rt::TypeInfo* ti, const TypePtr& target) { - if ( target->Tag() != TYPE_RECORD ) - throw ParameterMismatch("tuple", target); - - auto rtype = cast_intrusive(target); - - if ( sizeof...(Ts) != rtype->NumFields() ) - throw ParameterMismatch("tuple", target); - - auto rval = make_intrusive(rtype); - size_t idx = 0; - hilti::rt::tuple_for_each(t, [&](const auto& x) { set_record_field(rval.get(), rtype, idx++, x); }); - - return rval; -} - -/** - * Converts a Spicy-side bitfield to a Zeek record value. The result is returned - * with ref count +1. - */ -template -inline ValPtr to_val(const hilti::rt::Bitfield& v, const hilti::rt::TypeInfo* ti, const TypePtr& target) { - using Bitfield = hilti::rt::Bitfield; - - if ( target->Tag() != TYPE_RECORD ) - throw ParameterMismatch("bitfield", target); - - auto rtype = cast_intrusive(target); - - if ( sizeof...(Ts) - 1 != rtype->NumFields() ) - throw ParameterMismatch("bitfield", target); - - auto rval = make_intrusive(rtype); - size_t idx = 0; - hilti::rt::tuple_for_each(v.value, [&](const auto& x) { - if ( idx < sizeof...(Ts) - 1 ) // last element is original integer value, with no record equivalent - set_record_field(rval.get(), rtype, idx++, x); - }); - - return rval; -} - -template -constexpr bool is_optional_impl = false; -template -constexpr bool is_optional_impl> = true; -template -constexpr bool is_optional = is_optional_impl>>; - -/** - * Converts Spicy-side struct to a Zeek record value. The result is returned - * with a ref count +1. - */ -template::value>*> -inline ValPtr to_val(const T& t, const hilti::rt::TypeInfo* ti, const TypePtr& target) { - if ( target->Tag() != TYPE_RECORD ) - throw ParameterMismatch("struct", target); - - auto rtype = cast_intrusive(target); - - auto rval = make_intrusive(rtype); - int idx = 0; - - auto num_fields = rtype->NumFields(); - - t.__visit([&](std::string_view name, const auto& val) { - if ( idx >= num_fields ) - throw ParameterMismatch(hilti::rt::fmt("no matching record field for field '%s'", name)); - - // Special-case: Lift up anonymous bitfields (which always come as std::optionals). - if ( name == "" ) { - using X = typename std::decay::type; - if constexpr ( is_optional ) { - if constexpr ( std::is_base_of<::hilti::rt::trait::isBitfield, typename X::value_type>::value ) { - size_t j = 0; - hilti::rt::tuple_for_each(val->value, [&](const auto& x) { - if ( j++ < std::tuple_sizevalue)>() - - 1 ) // last element is original integer value, with no record equivalent - set_record_field(rval.get(), rtype, idx++, x); - }); - return; - } - } - - // There can't be any other anonymous fields. - auto msg = hilti::rt::fmt("unexpected anonymous field: %s", name); - reporter->InternalError("%s", msg.c_str()); - } - else { - auto field = rtype->GetFieldType(idx); - std::string field_name = rtype->FieldName(idx); - - if ( field_name != name ) - throw ParameterMismatch( - hilti::rt::fmt("mismatch in field name: expected '%s', found '%s'", name, field_name)); - - set_record_field(rval.get(), rtype, idx++, val); - } - }); - - // We already check above that all Spicy-side fields are mapped so we - // can only hit this if there are uninitialized Zeek-side fields left. - if ( idx != num_fields ) - throw ParameterMismatch(hilti::rt::fmt("missing initialization for field '%s'", rtype->FieldName(idx + 1))); - - return rval; -} - -/** Maps HILTI's `Protocol` enum to Zeek's `transport_proto` enum. */ -inline ValPtr to_val_for_transport_proto(int64_t val, const hilti::rt::TypeInfo* ti, const TypePtr& target) { - switch ( val ) { - case hilti::rt::Protocol::TCP: return id::transport_proto->GetEnumVal(::TransportProto::TRANSPORT_TCP); - case hilti::rt::Protocol::UDP: return id::transport_proto->GetEnumVal(::TransportProto::TRANSPORT_UDP); - case hilti::rt::Protocol::ICMP: return id::transport_proto->GetEnumVal(::TransportProto::TRANSPORT_ICMP); - case hilti::rt::Protocol::Undef: [[fallthrough]]; // just for readability, make Undef explicit - default: return id::transport_proto->GetEnumVal(::TransportProto::TRANSPORT_UNKNOWN); - } - - hilti::rt::cannot_be_reached(); -} - -/** - * Converts a Spicy-side enum to a Zeek enum value. The result is returned - * with ref count +1. - */ -template::value>*> -inline ValPtr to_val(const T& t, const hilti::rt::TypeInfo* ti, const TypePtr& target) { - if ( target->Tag() != TYPE_ENUM ) - throw ParameterMismatch("enum", target); - - // We'll usually be getting an int64_t for T, but allow other signed ints - // as well. - static_assert(std::is_signed>{}); - auto it = static_cast(t.value()); - - // Special case: map enum values to Zeek's semantics. - if ( target->GetName() == "transport_proto" ) { - if ( ! std::is_same_v ) - throw ParameterMismatch(hilti::rt::demangle(typeid(t).name()), target); - - return to_val_for_transport_proto(it, nullptr, target); - } - - // Zeek's enum can't be negative, so we swap in max_int for our Undef (-1). - if ( it == std::numeric_limits::max() ) - // can't allow this ... - throw InvalidValue("enum values with value max_int not supported by Zeek integration"); - - zeek_int_t bt = (it >= 0 ? it : std::numeric_limits<::zeek_int_t>::max()); - - return target->AsEnumType()->GetEnumVal(bt); -} - - /** * Returns the Zeek value associated with a global Zeek-side ID. Throws if the * ID does not exist. diff --git a/testing/btest/Baseline/spicy.event-args-mismatch/output b/testing/btest/Baseline/spicy.event-args-mismatch/output index cbedb262ac..9d1525161e 100644 --- a/testing/btest/Baseline/spicy.event-args-mismatch/output +++ b/testing/btest/Baseline/spicy.event-args-mismatch/output @@ -1,2 +1,2 @@ ### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. -XXXXXXXXXX.XXXXXX analyzer error in <...>/test.evt, line 5: Event parameter mismatch, cannot convert Spicy value of type 'string' to Zeek value of type 'count' +XXXXXXXXXX.XXXXXX analyzer error in <...>/test.evt, line 5: Event parameter mismatch, cannot convert Spicy value of type 'bytes' to Zeek value of type 'count'