parse.y: Traverse AST post parsing to detect break/next usage issues

Seemed easiest to do it via the traversal infrastructure as we do not
otherwise track enough context/scope when instantiating break or next
statements.

Might be worth moving this out of src/parse.y, but didn't exactly know
where. Or maybe we wait until there's more such trivial validations
popping up

Fixes #2440
This commit is contained in:
Arne Welzel 2022-10-26 13:47:54 +02:00
parent 2ed42ef771
commit 850aaaa5a8
15 changed files with 227 additions and 2 deletions

97
src/ScriptValidation.cc Normal file
View file

@ -0,0 +1,97 @@
#include "zeek/ScriptValidation.h"
#include "zeek/Func.h"
#include "zeek/Reporter.h"
#include "zeek/Stmt.h"
#include "zeek/Traverse.h"
namespace zeek::detail
{
// Validate context of break and next statement usage.
class BreakNextScriptValidation : public TraversalCallback
{
public:
TraversalCode PreStmt(const Stmt* stmt)
{
if ( ! StmtIsRelevant(stmt) )
return TC_CONTINUE;
stmt_depths[stmt->Tag()] += 1;
if ( stmt->Tag() == STMT_BREAK && ! BreakStmtIsValid() )
{
zeek::reporter->PushLocation(stmt->GetLocationInfo());
zeek::reporter->Error("break statement used outside of for, while or "
"switch statement and not within a hook");
zeek::reporter->PopLocation();
}
if ( stmt->Tag() == STMT_NEXT && ! NextStmtIsValid() )
{
zeek::reporter->PushLocation(stmt->GetLocationInfo());
zeek::reporter->Error("next statement used outside of for or while statement");
zeek::reporter->PopLocation();
}
return TC_CONTINUE;
}
TraversalCode PostStmt(const Stmt* stmt)
{
if ( ! StmtIsRelevant(stmt) )
return TC_CONTINUE;
--stmt_depths[stmt->Tag()];
assert(stmt_depths[stmt->Tag()] >= 0);
return TC_CONTINUE;
}
TraversalCode PreFunction(const zeek::Func* func)
{
if ( func->Flavor() == zeek::FUNC_FLAVOR_HOOK )
++hook_depth;
assert(hook_depth <= 1);
return TC_CONTINUE;
}
TraversalCode PostFunction(const zeek::Func* func)
{
if ( func->Flavor() == zeek::FUNC_FLAVOR_HOOK )
--hook_depth;
assert(hook_depth >= 0);
return TC_CONTINUE;
}
private:
bool StmtIsRelevant(const Stmt* stmt)
{
StmtTag tag = stmt->Tag();
return tag == STMT_FOR || tag == STMT_WHILE || tag == STMT_SWITCH || tag == STMT_BREAK ||
tag == STMT_NEXT;
}
bool BreakStmtIsValid()
{
return hook_depth > 0 || stmt_depths[STMT_FOR] > 0 || stmt_depths[STMT_WHILE] > 0 ||
stmt_depths[STMT_SWITCH] > 0;
}
bool NextStmtIsValid() { return stmt_depths[STMT_FOR] > 0 || stmt_depths[STMT_WHILE] > 0; }
std::unordered_map<const StmtTag, int> stmt_depths;
int hook_depth = 0;
};
void script_validation()
{
zeek::detail::BreakNextScriptValidation bn_cb;
zeek::detail::traverse_all(&bn_cb);
}
}