// See the file "COPYING" in the main distribution directory for copyright. #include "zeek/ID.h" #include "zeek/Attr.h" #include "zeek/Desc.h" #include "zeek/Dict.h" #include "zeek/EventRegistry.h" #include "zeek/Expr.h" #include "zeek/File.h" #include "zeek/Func.h" #include "zeek/Scope.h" #include "zeek/Traverse.h" #include "zeek/Type.h" #include "zeek/Val.h" #include "zeek/module_util.h" #include "zeek/script_opt/IDOptInfo.h" #include "zeek/script_opt/UsageAnalyzer.h" #include "zeek/zeekygen/IdentifierInfo.h" #include "zeek/zeekygen/Manager.h" #include "zeek/zeekygen/ScriptInfo.h" #include "zeek/zeekygen/utils.h" namespace zeek { RecordTypePtr id::conn_id; RecordTypePtr id::conn_id_ctx; RecordTypePtr id::endpoint; RecordTypePtr id::connection; RecordTypePtr id::fa_file; RecordTypePtr id::fa_metadata; EnumTypePtr id::transport_proto; TableTypePtr id::string_set; TableTypePtr id::string_array; TableTypePtr id::count_set; VectorTypePtr id::string_vec; VectorTypePtr id::index_vec; const detail::IDPtr& id::find(std::string_view name) { return zeek::detail::global_scope()->Find(name); } const TypePtr& id::find_type(std::string_view name) { auto id = zeek::detail::global_scope()->Find(name); if ( ! id ) reporter->InternalError("Failed to find type named: %s", std::string(name).data()); return id->GetType(); } const ValPtr& id::find_val(std::string_view name) { auto id = zeek::detail::global_scope()->Find(name); if ( ! id ) reporter->InternalError("Failed to find variable named: %s", std::string(name).data()); return id->GetVal(); } const ValPtr& id::find_const(std::string_view name) { auto id = zeek::detail::global_scope()->Find(name); if ( ! id ) reporter->InternalError("Failed to find variable named: %s", std::string(name).data()); if ( ! id->IsConst() ) reporter->InternalError("Variable is not 'const', but expected to be: %s", std::string(name).data()); return id->GetVal(); } FuncPtr id::find_func(std::string_view name) { const auto& v = id::find_val(name); if ( ! v ) return nullptr; if ( ! IsFunc(v->GetType()->Tag()) ) reporter->InternalError("Expected variable '%s' to be a function", std::string(name).data()); return v.get()->As()->AsFuncPtr(); } void id::detail::init_types() { conn_id = id::find_type("conn_id"); conn_id_ctx = id::find_type("conn_id_ctx"); endpoint = id::find_type("endpoint"); connection = id::find_type("connection"); fa_file = id::find_type("fa_file"); fa_metadata = id::find_type("fa_metadata"); transport_proto = id::find_type("transport_proto"); string_set = id::find_type("string_set"); string_array = id::find_type("string_array"); count_set = id::find_type("count_set"); string_vec = id::find_type("string_vec"); index_vec = id::find_type("index_vec"); } namespace detail { ID::ID(const char* arg_name, IDScope arg_scope, bool arg_is_export) { name = util::copy_string(arg_name); scope = arg_scope; is_export = arg_is_export; offset = 0; if ( name && extract_var_name(name) == "_" ) { is_blank = true; SetType(base_type(TYPE_ANY)); } opt_info = new IDOptInfo(this); SetLocationInfo(&start_location, &end_location); } ID::~ID() { ClearOptInfo(); delete[] name; } std::string ID::ModuleName() const { return extract_module_name(name); } void ID::SetType(TypePtr t) { type = std::move(t); } void ID::ClearVal() { val = nullptr; } void ID::SetVal(ValPtr v) { val = std::move(v); Modified(); #ifdef DEBUG UpdateValID(); #endif if ( type && val && type->Tag() == TYPE_FUNC && type->AsFuncType()->Flavor() == FUNC_FLAVOR_EVENT ) { EventHandler* handler = event_registry->Lookup(name); auto func = val.get()->As()->AsFuncPtr(); if ( ! handler ) { handler = new EventHandler(name); handler->SetFunc(func); event_registry->Register(handler, true); if ( ! IsExport() ) register_new_event({NewRef{}, this}); } else { // Otherwise, internally defined events cannot // have local handler. handler->SetFunc(func); } } } void ID::SetVal(ValPtr v, InitClass c) { if ( c == INIT_NONE || c == INIT_FULL ) { SetVal(std::move(v)); return; } if ( type->Tag() != TYPE_TABLE && (type->Tag() != TYPE_PATTERN || c == INIT_REMOVE) && (type->Tag() != TYPE_VECTOR || c == INIT_REMOVE) ) { if ( c == INIT_EXTRA ) Error("+= initializer only applies to tables, sets, vectors and patterns", v.get()); else Error("-= initializer only applies to tables and sets", v.get()); } else { if ( c == INIT_EXTRA ) { if ( ! val ) { SetVal(std::move(v)); return; } else v->AddTo(val.get(), false); } else { if ( val ) v->RemoveFrom(val.get()); } } } void ID::SetVal(ExprPtr ev, InitClass c) { const auto& a = attrs->Find(c == INIT_EXTRA ? ATTR_ADD_FUNC : ATTR_DEL_FUNC); if ( ! a ) Internal("no add/delete function in ID::SetVal"); if ( ! val ) { Error(zeek::util::fmt("%s initializer applied to ID without value", c == INIT_EXTRA ? "+=" : "-="), this); return; } EvalFunc(a->GetExpr(), std::move(ev)); } bool ID::IsRedefinable() const { return GetAttr(ATTR_REDEF) != nullptr; } void ID::SetAttrs(AttributesPtr a) { attrs = nullptr; AddAttrs(std::move(a)); } void ID::UpdateValAttrs() { if ( ! attrs ) return; auto tag = GetType()->Tag(); if ( tag == TYPE_FUNC ) { const auto& attr = attrs->Find(ATTR_ERROR_HANDLER); if ( attr ) event_registry->SetErrorHandler(Name()); } if ( tag == TYPE_RECORD ) { const auto& attr = attrs->Find(ATTR_LOG); if ( attr ) { // Apply &log to all record fields. RecordType* rt = GetType()->AsRecordType(); for ( int i = 0; i < rt->NumFields(); ++i ) { TypeDecl* fd = rt->FieldDecl(i); if ( ! fd->attrs ) fd->attrs = make_intrusive(rt->GetFieldType(i), true, IsGlobal()); fd->attrs->AddAttr(make_intrusive(ATTR_LOG)); } } } if ( ! val ) return; auto vtag = val->GetType()->Tag(); if ( vtag == TYPE_TABLE ) val->AsTableVal()->SetAttrs(attrs); else if ( vtag == TYPE_FILE ) val->AsFile()->SetAttrs(attrs.get()); } const AttrPtr& ID::GetAttr(AttrTag t) const { return attrs ? attrs->Find(t) : Attr::nil; } bool ID::IsDeprecated() const { return GetAttr(ATTR_DEPRECATED) != nullptr; } void ID::MakeDeprecated(ExprPtr deprecation) { if ( IsDeprecated() ) return; AddAttr(make_intrusive(ATTR_DEPRECATED, std::move(deprecation))); } std::string ID::GetDeprecationWarning() const { std::string result; const auto& depr_attr = GetAttr(ATTR_DEPRECATED); if ( depr_attr ) result = depr_attr->DeprecationMessage(); if ( result.empty() ) return util::fmt("deprecated (%s)", Name()); else return util::fmt("deprecated (%s): %s", Name(), result.c_str()); } void ID::AddAttr(AttrPtr a, bool is_redef) { std::vector attrv{std::move(a)}; auto attrs = make_intrusive(std::move(attrv), GetType(), false, IsGlobal()); AddAttrs(std::move(attrs), is_redef); } void ID::AddAttrs(AttributesPtr a, bool is_redef) { if ( attrs ) attrs->AddAttrs(a, is_redef); else attrs = std::move(a); UpdateValAttrs(); } void ID::RemoveAttr(AttrTag a) { if ( attrs ) attrs->RemoveAttr(a); } void ID::SetOption() { if ( is_option ) return; is_option = true; // option implied redefinable if ( ! IsRedefinable() ) AddAttr(make_intrusive(ATTR_REDEF)); } void ID::EvalFunc(ExprPtr ef, ExprPtr ev) { auto arg1 = make_intrusive(val); auto args = make_intrusive(); args->Append(std::move(arg1)); args->Append(std::move(ev)); auto ce = make_intrusive(std::move(ef), std::move(args)); SetVal(ce->Eval(nullptr)); } TraversalCode ID::Traverse(TraversalCallback* cb) const { TraversalCode tc = cb->PreID(this); HANDLE_TC_STMT_PRE(tc); if ( is_type ) { tc = cb->PreTypedef(this); HANDLE_TC_STMT_PRE(tc); tc = cb->PostTypedef(this); HANDLE_TC_STMT_PRE(tc); } // FIXME: Perhaps we should be checking at other than global scope. else if ( val && IsFunc(val->GetType()->Tag()) && cb->current_scope == detail::global_scope() ) { tc = val->AsFunc()->Traverse(cb); HANDLE_TC_STMT_PRE(tc); } else if ( ! is_enum_const ) { tc = cb->PreDecl(this); HANDLE_TC_STMT_PRE(tc); tc = cb->PostDecl(this); HANDLE_TC_STMT_PRE(tc); } tc = cb->PostID(this); HANDLE_TC_EXPR_POST(tc); } void ID::Error(const char* msg, const Obj* o2) { Obj::Error(msg, o2, true); SetType(error_type()); } void ID::Describe(ODesc* d) const { d->Add(name); } void ID::DescribeExtended(ODesc* d) const { d->Add(name); if ( type ) { d->Add(" : "); type->Describe(d); } if ( val ) { d->Add(" = "); val->Describe(d); } if ( attrs ) { d->Add(" "); attrs->Describe(d); } } void ID::DescribeReSTShort(ODesc* d) const { if ( is_type ) d->Add(":zeek:type:`"); else d->Add(":zeek:id:`"); d->Add(name); d->Add("`"); if ( type ) { d->Add(": "); d->Add(":zeek:type:`"); if ( ! is_type && ! type->GetName().empty() ) d->Add(type->GetName().c_str()); else { TypeTag t = type->Tag(); switch ( t ) { case TYPE_TABLE: d->Add(type->IsSet() ? "set" : type_name(t)); break; case TYPE_FUNC: d->Add(type->AsFuncType()->FlavorString().c_str()); break; case TYPE_ENUM: if ( is_type ) d->Add(type_name(t)); else d->Add(zeekygen_mgr->GetEnumTypeName(Name()).c_str()); break; default: d->Add(type_name(t)); break; } } d->Add("`"); } if ( attrs ) { d->SP(); attrs->DescribeReST(d, true); } } void ID::DescribeReST(ODesc* d, bool roles_only) const { if ( roles_only ) { if ( is_type ) d->Add(":zeek:type:`"); else d->Add(":zeek:id:`"); d->Add(name); d->Add("`"); } else { if ( is_type ) d->Add(".. zeek:type:: "); else d->Add(".. zeek:id:: "); d->Add(name); if ( auto sc = zeek::zeekygen::detail::source_code_range(this) ) { d->PushIndent(); d->Add(util::fmt(":source-code: %s", sc->data())); d->PopIndentNoNL(); } } d->PushIndent(); d->NL(); if ( type ) { d->Add(":Type: "); if ( ! is_type && ! type->GetName().empty() ) { d->Add(":zeek:type:`"); d->Add(type->GetName()); 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(); } if ( attrs ) { d->Add(":Attributes: "); attrs->DescribeReST(d); d->NL(); } if ( val && type && type->Tag() != TYPE_FUNC && type->InternalType() != TYPE_INTERNAL_VOID && // Do not include a default value for enum const identifiers, // as their value can't be changed. ! IsEnumConst() && // Values within Version module are likely to include a // constantly-changing version number and be a frequent // source of error/desynchronization, so don't include them. ModuleName() != "Version" ) { d->Add(":Default:"); auto ii = zeekygen_mgr->GetIdentifierInfo(Name()); if ( ! ii ) return; auto redefs = ii->GetRedefs(); const auto& iv = ! redefs.empty() && ii->InitialVal() ? ii->InitialVal() : val; if ( type->InternalType() == TYPE_INTERNAL_OTHER ) { switch ( type->Tag() ) { case TYPE_TABLE: if ( iv->AsTable()->Length() == 0 ) { d->Add(" ``{}``"); d->NL(); break; } // Fall-through. default: d->NL(); d->PushIndent(); d->Add("::"); d->NL(); d->PushIndent(); iv->DescribeReST(d); d->PopIndent(); d->PopIndent(); } } else { d->SP(); iv->DescribeReST(d); d->NL(); } for ( auto& ir : redefs ) { if ( ! ir->init_expr ) continue; if ( ir->ic == INIT_NONE ) continue; std::string redef_str; ODesc expr_desc; ir->init_expr->Describe(&expr_desc); redef_str = expr_desc.Description(); redef_str = util::strreplace(redef_str, "\n", " "); d->Add(":Redefinition: "); d->Add(util::fmt("from :doc:`/scripts/%s`", ir->from_script.data())); d->NL(); d->PushIndent(); if ( ir->omit_value ) { d->Add("<< Value omitted due to ``@docs_omit_value`` annotation >>"); d->NL(); } else { if ( ir->ic == INIT_FULL ) d->Add("``=``"); else if ( ir->ic == INIT_EXTRA ) d->Add("``+=``"); else if ( ir->ic == INIT_REMOVE ) d->Add("``-=``"); else assert(false); d->Add("::"); d->NL(); d->PushIndent(); d->Add(redef_str.data()); d->PopIndent(); d->PopIndent(); } } } } #ifdef DEBUG void ID::UpdateValID() { if ( IsGlobal() && val && name && name[0] != '#' ) val->SetID(this); } #endif void ID::AddOptionHandler(FuncPtr callback, int priority) { option_handlers.emplace_front(priority, callback); option_handlers.sort( [](const OptionHandler& left, const OptionHandler& right) { return left.first < right.first; }); } std::vector ID::GetOptionHandlers() const { // It might be worth caching this if we expect it to be called a lot... std::vector v; for ( const auto& element : option_handlers ) v.push_back(element.second.get()); return v; } void ID::ClearOptInfo() { delete opt_info; opt_info = nullptr; } } // namespace detail } // namespace zeek