zeek/src/zeekygen/Manager.cc

451 lines
12 KiB
C++

// See the file "COPYING" in the main distribution directory for copyright.
#include "zeek/zeekygen/Manager.h"
#include <utility>
#include <cstdlib>
#include "zeek/DebugLogger.h"
#include "zeek/util.h"
#include "zeek/zeekygen/utils.h"
#include "zeek/zeekygen/Info.h"
#include "zeek/zeekygen/PackageInfo.h"
#include "zeek/zeekygen/ScriptInfo.h"
#include "zeek/zeekygen/IdentifierInfo.h"
#include "zeek/Expr.h"
using namespace std;
namespace zeek::zeekygen::detail {
static void DbgAndWarn(const char* msg)
{
if ( reporter->Errors() )
// We've likely already reported to real source of the problem
// as an error, avoid adding an additional warning which may
// be confusing.
return;
reporter->Warning("%s", msg);
DBG_LOG(DBG_ZEEKYGEN, "%s", msg);
}
static void WarnMissingScript(const char* type, const zeek::detail::ID* id,
const string& script)
{
if ( script == "<command line>" )
return;
DbgAndWarn(util::fmt("Can't generate Zeekygen doumentation for %s %s, "
"lookup of %s failed",
type, id->Name(), script.c_str()));
}
static string RemoveLeadingSpace(const string& s)
{
if ( s.empty() || s[0] != ' ' )
return s;
// Treat "##Text" and "## Text" the same, so that a single space doesn't
// cause reST formatting to think the later is indented a level.
string rval = s;
rval.erase(0, 1);
return rval;
}
Manager::Manager(const string& arg_config, const string& bro_command)
: disabled(), comment_buffer(), comment_buffer_map(), packages(), scripts(),
identifiers(), all_info(), last_identifier_seen(), incomplete_type(),
enum_mappings(), config(arg_config), bro_mtime()
{
if ( getenv("ZEEK_DISABLE_ZEEKYGEN") )
disabled = true;
// If running bro without the "-X" option, then we don't need bro_mtime.
if ( disabled || arg_config.empty() )
return;
// Find the absolute or relative path to bro by checking each PATH
// component and also the current directory (so that this works if
// bro_command is a relative path).
const char* env_path = getenv("PATH");
string path = env_path ? string(env_path) + ":." : ".";
string path_to_bro = util::find_file(bro_command, path);
struct stat s;
// One way that find_file() could fail is when bro is located in
// a PATH component that starts with a tilde (such as "~/bin"). A simple
// workaround is to just run bro with a relative or absolute path.
if ( path_to_bro.empty() || stat(path_to_bro.c_str(), &s) < 0 )
reporter->InternalError(
"Zeekygen can't get mtime of zeek binary %s (try again by specifying the absolute or relative path to Zeek): %s",
path_to_bro.c_str(), strerror(errno));
// Internal error will abort above in the case that stat isn't initialized
// NOLINTNEXTLINE(clang-analyzer-core.uninitialized.Assign)
bro_mtime = s.st_mtime;
}
Manager::~Manager()
{
for ( size_t i = 0; i < all_info.size(); ++i )
delete all_info[i];
}
void Manager::InitPreScript()
{
if ( disabled )
return;
}
void Manager::InitPostScript()
{
if ( disabled )
return;
for ( size_t i = 0; i < all_info.size(); ++i )
all_info[i]->InitPostScript();
config.FindDependencies(all_info);
}
void Manager::GenerateDocs() const
{
if ( disabled )
return;
config.GenerateDocs();
}
void Manager::Script(const string& path)
{
if ( disabled )
return;
string name = normalize_script_path(path);
if ( scripts.GetInfo(name) )
{
DbgAndWarn(util::fmt("Duplicate Zeekygen script documentation: %s",
name.c_str()));
return;
}
ScriptInfo* info = new ScriptInfo(name, path);
scripts.map[name] = info;
all_info.push_back(info);
DBG_LOG(DBG_ZEEKYGEN, "Made ScriptInfo %s", name.c_str());
if ( ! info->IsPkgLoader() )
return;
name = util::SafeDirname(name).result;
if ( packages.GetInfo(name) )
{
DbgAndWarn(util::fmt("Duplicate Zeekygen package documentation: %s",
name.c_str()));
return;
}
PackageInfo* pkginfo = new PackageInfo(name);
packages.map[name] = pkginfo;
all_info.push_back(pkginfo);
DBG_LOG(DBG_ZEEKYGEN, "Made PackageInfo %s", name.c_str());
}
void Manager::ScriptDependency(const string& path, const string& dep)
{
if ( disabled )
return;
if ( dep.empty() )
{
DbgAndWarn(util::fmt("Empty Zeekygen script doc dependency: %s",
path.c_str()));
return;
}
string name = normalize_script_path(path);
string depname = normalize_script_path(dep);
ScriptInfo* script_info = scripts.GetInfo(name);
if ( ! script_info )
{
DbgAndWarn(util::fmt("Failed to add Zeekygen script doc dependency %s "
"for %s", depname.c_str(), name.c_str()));
return;
}
script_info->AddDependency(depname);
DBG_LOG(DBG_ZEEKYGEN, "Added script dependency %s for %s",
depname.c_str(), name.c_str());
for ( size_t i = 0; i < comment_buffer.size(); ++i )
DbgAndWarn(util::fmt("Discarded extraneous Zeekygen comment: %s",
comment_buffer[i].c_str()));
}
void Manager::ModuleUsage(const string& path, const string& module)
{
if ( disabled )
return;
string name = normalize_script_path(path);
ScriptInfo* script_info = scripts.GetInfo(name);
if ( ! script_info )
{
DbgAndWarn(util::fmt("Failed to add Zeekygen module usage %s in %s",
module.c_str(), name.c_str()));
return;
}
script_info->AddModule(module);
DBG_LOG(DBG_ZEEKYGEN, "Added module usage %s in %s",
module.c_str(), name.c_str());
}
IdentifierInfo* Manager::CreateIdentifierInfo(zeek::detail::IDPtr id, ScriptInfo* script, bool from_redef)
{
const auto& id_name = id->Name();
auto prev = identifiers.GetInfo(id_name);
IdentifierInfo* rval = prev ? prev : new IdentifierInfo(std::move(id), script,
from_redef);
rval->AddComments(comment_buffer);
comment_buffer.clear();
comment_buffer_map_t::iterator it = comment_buffer_map.find(id_name);
if ( it != comment_buffer_map.end() )
{
rval->AddComments(it->second);
comment_buffer_map.erase(it);
}
if ( ! prev )
{
all_info.push_back(rval);
identifiers.map[id_name] = rval;
}
last_identifier_seen = rval;
if ( script )
script->AddIdentifierInfo(rval);
return rval;
}
void Manager::StartType(zeek::detail::IDPtr id)
{
if ( disabled )
return;
if ( id->GetLocationInfo() == &zeek::detail::no_location )
{
DbgAndWarn(util::fmt("Can't generate zeekygen doumentation for %s, "
"no location available", id->Name()));
return;
}
string script = normalize_script_path(id->GetLocationInfo()->filename);
ScriptInfo* script_info = scripts.GetInfo(script);
if ( ! script_info )
{
WarnMissingScript("identifier", id.get(), script);
return;
}
DBG_LOG(DBG_ZEEKYGEN, "Making IdentifierInfo (incomplete) %s, in %s",
id->Name(), script.c_str());
incomplete_type = CreateIdentifierInfo(std::move(id), script_info);
}
static bool IsEnumType(zeek::detail::ID* id)
{
return id->IsType() ? id->GetType()->Tag() == TYPE_ENUM : false;
}
void Manager::Identifier(zeek::detail::IDPtr id, bool from_redef)
{
if ( disabled )
return;
if ( incomplete_type )
{
if ( incomplete_type->Name() == id->Name() )
{
DBG_LOG(DBG_ZEEKYGEN, "Finished document for type %s", id->Name());
incomplete_type->CompletedTypeDecl();
incomplete_type = nullptr;
return;
}
if ( IsEnumType(incomplete_type->GetID()) )
enum_mappings[id->Name()] = incomplete_type->GetID()->Name();
}
IdentifierInfo* id_info = identifiers.GetInfo(id->Name());
if ( id_info )
{
if ( IsFunc(id_info->GetID()->GetType()->Tag()) )
{
// Function may already been seen (declaration versus body).
id_info->AddComments(comment_buffer);
comment_buffer.clear();
return;
}
DbgAndWarn(util::fmt("Duplicate identifier documentation: %s", id->Name()));
return;
}
if ( id->GetLocationInfo() == &zeek::detail::no_location )
{
// Internally-created identifier (e.g. file/proto analyzer enum tags).
// Handled specially since they don't have a script location.
DBG_LOG(DBG_ZEEKYGEN, "Made internal IdentifierInfo %s",
id->Name());
CreateIdentifierInfo(id, nullptr, from_redef);
return;
}
string script = normalize_script_path(id->GetLocationInfo()->filename);
ScriptInfo* script_info = scripts.GetInfo(script);
if ( ! script_info )
{
WarnMissingScript("identifier", id.get(), script);
return;
}
DBG_LOG(DBG_ZEEKYGEN, "Making IdentifierInfo %s, in script %s",
id->Name(), script.c_str());
CreateIdentifierInfo(std::move(id), script_info, from_redef);
}
void Manager::RecordField(const zeek::detail::ID* id, const TypeDecl* field,
const string& path, bool from_redef)
{
if ( disabled )
return;
IdentifierInfo* idd = identifiers.GetInfo(id->Name());
if ( ! idd )
{
DbgAndWarn(util::fmt("Can't generate zeekygen doumentation for "
"record field %s, unknown record: %s",
field->id, id->Name()));
return;
}
string script = normalize_script_path(path);
idd->AddRecordField(field, script, comment_buffer, from_redef);
comment_buffer.clear();
DBG_LOG(DBG_ZEEKYGEN, "Document record field %s, identifier %s, script %s",
field->id, id->Name(), script.c_str());
}
void Manager::Redef(const zeek::detail::ID* id, const string& path,
zeek::detail::InitClass ic,
zeek::detail::ExprPtr init_expr)
{
if ( disabled )
return;
if ( path == "<params>" )
// This is a redef defined on the command line.
return;
IdentifierInfo* id_info = identifiers.GetInfo(id->Name());
if ( ! id_info )
{
DbgAndWarn(util::fmt("Can't generate zeekygen doumentation for "
"redef of %s, identifier lookup failed",
id->Name()));
return;
}
string from_script = normalize_script_path(path);
ScriptInfo* script_info = scripts.GetInfo(from_script);
if ( ! script_info )
{
WarnMissingScript("redef", id, from_script);
return;
}
id_info->AddRedef(from_script, ic, std::move(init_expr), comment_buffer);
script_info->AddRedef(id_info);
comment_buffer.clear();
last_identifier_seen = id_info;
DBG_LOG(DBG_ZEEKYGEN, "Added redef of %s from %s",
id->Name(), from_script.c_str());
}
void Manager::Redef(const zeek::detail::ID* id, const std::string& path,
zeek::detail::InitClass ic)
{
Redef(id, path, ic, nullptr);
}
void Manager::SummaryComment(const string& script, const string& comment)
{
if ( disabled )
return;
string name = normalize_script_path(script);
ScriptInfo* info = scripts.GetInfo(name);
if ( info )
info->AddComment(RemoveLeadingSpace(comment));
else
DbgAndWarn(util::fmt("Lookup of script %s failed for summary comment %s",
name.c_str(), comment.c_str()));
}
void Manager::PreComment(const string& comment)
{
if ( disabled )
return;
comment_buffer.push_back(RemoveLeadingSpace(comment));
}
void Manager::PostComment(const string& comment, const string& id_hint)
{
if ( disabled )
return;
if ( id_hint.empty() )
{
if ( last_identifier_seen )
last_identifier_seen->AddComment(RemoveLeadingSpace(comment));
else
DbgAndWarn(util::fmt("Discarded unassociated Zeekygen comment %s",
comment.c_str()));
return;
}
if ( last_identifier_seen &&
last_identifier_seen->Name() == id_hint )
last_identifier_seen->AddComment(RemoveLeadingSpace(comment));
else
// Assume identifier it's associated w/ is coming later.
comment_buffer_map[id_hint].push_back(RemoveLeadingSpace(comment));
}
string Manager::GetEnumTypeName(const string& id) const
{
map<string, string>::const_iterator it = enum_mappings.find(id);
return it == enum_mappings.end() ? "" : it->second;
}
} // namespace zeek::zeekygen::detail