zeek/src/Trigger.cc
Jon Siwek f7440375f1 Interpreter exceptions occurring in "when" blocks are now handled.
The scripting error that caused the exception is still reported, but
it no longer causes Bro to terminate.  Addresses #779
2012-12-04 12:38:09 -06:00

438 lines
8.3 KiB
C++

#include <algorithm>
#include "Trigger.h"
#include "Traverse.h"
// Callback class to traverse an expression, registering all relevant IDs and
// Vals for change notifications.
class TriggerTraversalCallback : public TraversalCallback {
public:
TriggerTraversalCallback(Trigger *arg_trigger)
{ Ref(arg_trigger); trigger = arg_trigger; }
~TriggerTraversalCallback()
{ Unref(trigger); }
virtual TraversalCode PreExpr(const Expr*);
private:
Trigger* trigger;
};
TraversalCode TriggerTraversalCallback::PreExpr(const Expr* expr)
{
// We catch all expressions here which in some way reference global
// state.
switch ( expr->Tag() ) {
case EXPR_NAME:
{
const NameExpr* e = static_cast<const NameExpr*>(expr);
if ( e->Id()->IsGlobal() )
trigger->Register(e->Id());
Val* v = e->Id()->ID_Val();
if ( v && v->IsMutableVal() )
trigger->Register(v);
break;
};
case EXPR_INDEX:
{
const IndexExpr* e = static_cast<const IndexExpr*>(expr);
BroObj::SuppressErrors no_errors;
Val* v = e->Eval(trigger->frame);
if ( v )
trigger->Register(v);
break;
}
default:
// All others are uninteresting.
break;
}
return TC_CONTINUE;
}
class TriggerTimer : public Timer {
public:
TriggerTimer(double arg_timeout, Trigger* arg_trigger)
: Timer(network_time + arg_timeout, TIMER_TRIGGER)
{
Ref(arg_trigger);
trigger = arg_trigger;
timeout = arg_timeout;
time = network_time;
}
~TriggerTimer()
{ Unref(trigger); }
void Dispatch(double t, int is_expire)
{
// The network_time may still have been zero when the
// timer was instantiated. In this case, it fires
// immediately and we simply restart it.
if ( time )
trigger->Timeout();
else
{
TriggerTimer* timer = new TriggerTimer(timeout, trigger);
timer_mgr->Add(timer);
trigger->timer = timer;
}
}
protected:
Trigger* trigger;
double timeout;
double time;
};
Trigger::Trigger(Expr* arg_cond, Stmt* arg_body, Stmt* arg_timeout_stmts,
Expr* arg_timeout, Frame* arg_frame,
bool arg_is_return, const Location* arg_location)
{
if ( ! pending )
pending = new list<Trigger*>;
cond = arg_cond;
body = arg_body;
timeout_stmts = arg_timeout_stmts;
timeout = arg_timeout;
frame = arg_frame->Clone();
timer = 0;
delayed = false;
disabled = false;
attached = 0;
is_return = arg_is_return;
location = arg_location;
++total_triggers;
DBG_LOG(DBG_NOTIFIERS, "%s: instantiating", Name());
if ( is_return )
{
Trigger* parent = frame->GetTrigger();
if ( ! parent )
{
reporter->Error("return trigger in context which does not allow delaying result");
Unref(this);
return;
}
parent->Attach(this);
arg_frame->SetDelayed();
}
Val* timeout = arg_timeout ? arg_timeout->ExprVal() : 0;
// Make sure we don't get deleted if somebody calls a method like
// Timeout() while evaluating the trigger.
Ref(this);
if ( ! Eval() && timeout )
{
timer = new TriggerTimer(timeout->AsInterval(), this);
timer_mgr->Add(timer);
}
Unref(this);
}
Trigger::~Trigger()
{
DBG_LOG(DBG_NOTIFIERS, "%s: deleting", Name());
for ( ValCache::iterator i = cache.begin(); i != cache.end(); ++i )
Unref(i->second);
Unref(frame);
UnregisterAll();
Unref(attached);
// Due to ref'counting, "this" cannot be part of pending at this
// point.
}
void Trigger::Init()
{
assert(! disabled);
UnregisterAll();
TriggerTraversalCallback cb(this);
cond->Traverse(&cb);
}
Trigger::TriggerList* Trigger::pending = 0;
unsigned long Trigger::total_triggers = 0;
bool Trigger::Eval()
{
if ( disabled )
return true;
DBG_LOG(DBG_NOTIFIERS, "%s: evaluating", Name());
if ( delayed )
{
DBG_LOG(DBG_NOTIFIERS, "%s: skipping eval due to delayed call",
Name());
return false;
}
// It's unfortunate that we have to copy the frame again here but
// otherwise changes to any of the locals would propagate to later
// evaluations.
//
// An alternative approach to copying the frame would be to deep-copy
// the expression itself, replacing all references to locals with
// constants.
Frame* f = frame->Clone();
f->SetTrigger(this);
Val* v = cond->Eval(f);
f->ClearTrigger();
if ( f->HasDelayed() )
{
DBG_LOG(DBG_NOTIFIERS, "%s: eval has delayed", Name());
assert(!v);
Unref(f);
return false;
}
if ( v->IsZero() )
{
// Not true. Perhaps next time...
DBG_LOG(DBG_NOTIFIERS, "%s: trigger condition is false", Name());
Unref(v);
Unref(f);
Init();
return false;
}
DBG_LOG(DBG_NOTIFIERS, "%s: trigger condition is true, executing",
Name());
Unref(v);
v = 0;
stmt_flow_type flow;
try
{
v = body->Exec(f, flow);
}
catch ( InterpreterException& e )
{ /* Already reported. */ }
if ( is_return )
{
Trigger* trigger = frame->GetTrigger();
assert(trigger);
assert(frame->GetCall());
assert(trigger->attached == this);
#ifdef DEBUG
const char* pname = copy_string(trigger->Name());
DBG_LOG(DBG_NOTIFIERS, "%s: trigger has parent %s, caching result", Name(), pname);
delete [] pname;
#endif
trigger->Cache(frame->GetCall(), v);
trigger->Release();
}
Unref(v);
Unref(f);
if ( timer )
timer_mgr->Cancel(timer);
Disable();
Unref(this);
return true;
}
void Trigger::QueueTrigger(Trigger* trigger)
{
assert(! trigger->disabled);
assert(pending);
if ( std::find(pending->begin(), pending->end(), trigger) == pending->end() )
{
Ref(trigger);
pending->push_back(trigger);
}
}
void Trigger::EvaluatePending()
{
DBG_LOG(DBG_NOTIFIERS, "evaluating all pending triggers");
if ( ! pending )
return;
// While we iterate over the list, executing statements, we may
// in fact trigger new triggers and thereby modify the list.
// Therefore, we create a new temporary list which will receive
// triggers triggered during this time.
TriggerList* orig = pending;
TriggerList tmp;
pending = &tmp;
for ( TriggerList::iterator i = orig->begin(); i != orig->end(); ++i )
{
Trigger* t = *i;
(*i)->Eval();
Unref(t);
}
pending = orig;
orig->clear();
// Sigh... Is this really better than a for-loop?
std::copy(tmp.begin(), tmp.end(),
insert_iterator<TriggerList>(*pending, pending->begin()));
}
void Trigger::Timeout()
{
if ( disabled )
return;
DBG_LOG(DBG_NOTIFIERS, "%s: timeout", Name());
if ( timeout_stmts )
{
stmt_flow_type flow;
Frame* f = frame->Clone();
Val* v = 0;
try
{
v = timeout_stmts->Exec(f, flow);
}
catch ( InterpreterException& e )
{ /* Already reported. */ }
if ( is_return )
{
Trigger* trigger = frame->GetTrigger();
assert(trigger);
assert(frame->GetCall());
assert(trigger->attached == this);
#ifdef DEBUG
const char* pname = copy_string(trigger->Name());
DBG_LOG(DBG_NOTIFIERS, "%s: trigger has parent %s, caching timeout result", Name(), pname);
delete [] pname;
#endif
trigger->Cache(frame->GetCall(), v);
trigger->Release();
}
Unref(v);
Unref(f);
}
Disable();
Unref(this);
}
void Trigger::Register(ID* id)
{
assert(! disabled);
notifiers.Register(id, this);
Ref(id);
ids.insert(id);
}
void Trigger::Register(Val* val)
{
assert(! disabled);
notifiers.Register(val, this);
Ref(val);
vals.insert(val);
}
void Trigger::UnregisterAll()
{
loop_over_list(ids, i)
{
notifiers.Unregister(ids[i], this);
Unref(ids[i]);
}
ids.clear();
loop_over_list(vals, j)
{
notifiers.Unregister(vals[j], this);
Unref(vals[j]);
}
vals.clear();
}
void Trigger::Attach(Trigger *trigger)
{
assert(! disabled);
assert(! trigger->disabled);
assert(! trigger->delayed);
#ifdef DEBUG
const char* pname = copy_string(trigger->Name());
DBG_LOG(DBG_NOTIFIERS, "%s: attaching to %s", Name(), pname);
delete [] pname;
#endif
Ref(trigger);
attached = trigger;
Hold();
}
void Trigger::Cache(const CallExpr* expr, Val* v)
{
if ( disabled || ! v )
return;
ValCache::iterator i = cache.find(expr);
if ( i != cache.end() )
{
Unref(i->second);
i->second = v;
}
else
cache.insert(ValCache::value_type(expr, v));
Ref(v);
QueueTrigger(this);
}
Val* Trigger::Lookup(const CallExpr* expr)
{
assert(! disabled);
ValCache::iterator i = cache.find(expr);
return (i != cache.end()) ? i->second : 0;
}
const char* Trigger::Name() const
{
assert(location);
return fmt("%s:%d-%d", location->filename,
location->first_line, location->last_line);
}
void Trigger::GetStats(Stats* stats)
{
stats->total = total_triggers;
stats->pending = pending ? pending->size() : 0;
}