mirror of
https://github.com/zeek/zeek.git
synced 2025-10-02 14:48:21 +00:00

with the field. This works now: type X: record { a: table[string] of bool &default=table( ["foo"] = T ); b: table[string] of bool &default=table(); c: set[string] &default=set("A", "B", "C"); d: set[string] &default=set(); }; I think previously the intend was to associate &default with the table/set (i.e., define the default value for non-existing indices). However, that was already not working: the error checking was reporting type mismatches. So, this shouldn't break anything and make things more consistent.
1017 lines
21 KiB
C++
1017 lines
21 KiB
C++
// $Id: StateAccess.cc 6888 2009-08-20 18:23:11Z vern $
|
|
|
|
#include "Val.h"
|
|
#include "StateAccess.h"
|
|
#include "Serializer.h"
|
|
#include "Event.h"
|
|
#include "NetVar.h"
|
|
#include "DebugLogger.h"
|
|
#include "RemoteSerializer.h"
|
|
#include "PersistenceSerializer.h"
|
|
|
|
int StateAccess::replaying = 0;
|
|
|
|
StateAccess::StateAccess(Opcode arg_opcode,
|
|
const MutableVal* arg_target, const Val* arg_op1,
|
|
const Val* arg_op2, const Val* arg_op3)
|
|
{
|
|
opcode = arg_opcode;
|
|
target.val = const_cast<MutableVal*>(arg_target);
|
|
target_type = TYPE_MVAL;
|
|
op1.val = const_cast<Val*>(arg_op1);
|
|
op1_type = TYPE_VAL;
|
|
op2 = const_cast<Val*>(arg_op2);
|
|
op3 = const_cast<Val*>(arg_op3);
|
|
delete_op1_key = false;
|
|
|
|
RefThem();
|
|
}
|
|
|
|
StateAccess::StateAccess(Opcode arg_opcode,
|
|
const ID* arg_target, const Val* arg_op1,
|
|
const Val* arg_op2, const Val* arg_op3)
|
|
{
|
|
opcode = arg_opcode;
|
|
target.id = const_cast<ID*>(arg_target);
|
|
target_type = TYPE_ID;
|
|
op1.val = const_cast<Val*>(arg_op1);
|
|
op1_type = TYPE_VAL;
|
|
op2 = const_cast<Val*>(arg_op2);
|
|
op3 = const_cast<Val*>(arg_op3);
|
|
delete_op1_key = false;
|
|
|
|
RefThem();
|
|
}
|
|
|
|
StateAccess::StateAccess(Opcode arg_opcode,
|
|
const ID* arg_target, const HashKey* arg_op1,
|
|
const Val* arg_op2, const Val* arg_op3)
|
|
{
|
|
opcode = arg_opcode;
|
|
target.id = const_cast<ID*>(arg_target);
|
|
target_type = TYPE_ID;
|
|
op1.key = new HashKey(arg_op1->Key(), arg_op1->Size(), arg_op1->Hash());
|
|
op1_type = TYPE_KEY;
|
|
op2 = const_cast<Val*>(arg_op2);
|
|
op3 = const_cast<Val*>(arg_op3);
|
|
delete_op1_key = true;
|
|
|
|
RefThem();
|
|
}
|
|
|
|
StateAccess::StateAccess(Opcode arg_opcode,
|
|
const MutableVal* arg_target, const HashKey* arg_op1,
|
|
const Val* arg_op2, const Val* arg_op3)
|
|
{
|
|
opcode = arg_opcode;
|
|
target.val = const_cast<MutableVal*>(arg_target);
|
|
target_type = TYPE_MVAL;
|
|
op1.key = new HashKey(arg_op1->Key(), arg_op1->Size(), arg_op1->Hash());
|
|
op1_type = TYPE_KEY;
|
|
op2 = const_cast<Val*>(arg_op2);
|
|
op3 = const_cast<Val*>(arg_op3);
|
|
delete_op1_key = true;
|
|
|
|
RefThem();
|
|
}
|
|
|
|
StateAccess::StateAccess(const StateAccess& sa)
|
|
: SerialObj()
|
|
{
|
|
opcode = sa.opcode;
|
|
target_type = sa.target_type;
|
|
op1_type = sa.op1_type;
|
|
delete_op1_key = false;
|
|
|
|
if ( target_type == TYPE_ID )
|
|
target.id = sa.target.id;
|
|
else
|
|
target.val = sa.target.val;
|
|
|
|
if ( op1_type == TYPE_VAL )
|
|
op1.val = sa.op1.val;
|
|
else
|
|
{
|
|
// We need to copy the key as the pointer may not be
|
|
// valid anymore later.
|
|
op1.key = new HashKey(sa.op1.key->Key(), sa.op1.key->Size(),
|
|
sa.op1.key->Hash());
|
|
delete_op1_key = true;
|
|
}
|
|
|
|
op2 = sa.op2;
|
|
op3 = sa.op3;
|
|
|
|
RefThem();
|
|
}
|
|
|
|
StateAccess::~StateAccess()
|
|
{
|
|
if ( target_type == TYPE_ID )
|
|
Unref(target.id);
|
|
else
|
|
Unref(target.val);
|
|
|
|
if ( op1_type == TYPE_VAL )
|
|
Unref(op1.val);
|
|
else if ( delete_op1_key )
|
|
delete op1.key;
|
|
|
|
Unref(op2);
|
|
Unref(op3);
|
|
}
|
|
|
|
void StateAccess::RefThem()
|
|
{
|
|
if ( target_type == TYPE_ID )
|
|
Ref(target.id);
|
|
else
|
|
Ref(target.val);
|
|
|
|
if ( op1_type == TYPE_VAL && op1.val )
|
|
Ref(op1.val);
|
|
|
|
if ( op2 )
|
|
Ref(op2);
|
|
if ( op3 )
|
|
Ref(op3);
|
|
}
|
|
|
|
bool StateAccess::CheckOld(const char* op, ID* id, Val* index,
|
|
Val* should, Val* is)
|
|
{
|
|
if ( ! remote_check_sync_consistency )
|
|
return true;
|
|
|
|
if ( ! should && ! is )
|
|
return true;
|
|
|
|
// 'should == index' means that 'is' should be non-nil.
|
|
if ( should == index && is )
|
|
return true;
|
|
|
|
if ( should && is )
|
|
{
|
|
// There's no general comparision for non-atomic vals currently.
|
|
if ( ! (is_atomic_val(is) && is_atomic_val(should)) )
|
|
return true;
|
|
|
|
if ( same_atomic_val(should, is) )
|
|
return true;
|
|
}
|
|
|
|
Val* arg1;
|
|
Val* arg2;
|
|
Val* arg3;
|
|
|
|
if ( index )
|
|
{
|
|
ODesc d;
|
|
d.SetShort();
|
|
index->Describe(&d);
|
|
arg1 = new StringVal(fmt("%s[%s]", id->Name(), d.Description()));
|
|
}
|
|
else
|
|
arg1 = new StringVal(id->Name());
|
|
|
|
if ( should )
|
|
{
|
|
ODesc d;
|
|
d.SetShort();
|
|
should->Describe(&d);
|
|
arg2 = new StringVal(d.Description());
|
|
}
|
|
else
|
|
arg2 = new StringVal("<none>");
|
|
|
|
if ( is )
|
|
{
|
|
ODesc d;
|
|
d.SetShort();
|
|
is->Describe(&d);
|
|
arg3 = new StringVal(d.Description());
|
|
}
|
|
else
|
|
arg3 = new StringVal("<none>");
|
|
|
|
val_list* args = new val_list;
|
|
args->append(new StringVal(op));
|
|
args->append(arg1);
|
|
args->append(arg2);
|
|
args->append(arg3);
|
|
mgr.QueueEvent(remote_state_inconsistency, args);
|
|
|
|
return false;
|
|
}
|
|
|
|
bool StateAccess::CheckOldSet(const char* op, ID* id, Val* index,
|
|
bool should, bool is)
|
|
{
|
|
if ( ! remote_check_sync_consistency )
|
|
return true;
|
|
|
|
if ( should == is )
|
|
return true;
|
|
|
|
ODesc d;
|
|
d.SetShort();
|
|
index->Describe(&d);
|
|
|
|
Val* arg1 = new StringVal(fmt("%s[%s]", id->Name(), d.Description()));
|
|
Val* arg2 = new StringVal(should ? "set" : "not set");
|
|
Val* arg3 = new StringVal(is ? "set" : "not set");
|
|
|
|
val_list* args = new val_list;
|
|
args->append(new StringVal(op));
|
|
args->append(arg1);
|
|
args->append(arg2);
|
|
args->append(arg3);
|
|
mgr.QueueEvent(remote_state_inconsistency, args);
|
|
|
|
return false;
|
|
}
|
|
|
|
bool StateAccess::MergeTables(TableVal* dst, Val* src)
|
|
{
|
|
if ( ! src->Type()->Tag() == TYPE_TABLE )
|
|
{
|
|
run_time("type mismatch while merging tables");
|
|
return false;
|
|
}
|
|
|
|
if ( ! src->AsTableVal()->FindAttr(ATTR_MERGEABLE) )
|
|
return false;
|
|
|
|
DBG_LOG(DBG_STATE, "merging tables %s += %s", dst->UniqueID()->Name(),
|
|
src->AsTableVal()->UniqueID()->Name());
|
|
|
|
src->AsTableVal()->AddTo(dst, 0);
|
|
|
|
// We need to make sure that the resulting table is accessible by
|
|
// the new name (while keeping the old as an alias).
|
|
dst->TransferUniqueID(src->AsMutableVal());
|
|
|
|
return true;
|
|
}
|
|
|
|
void StateAccess::Replay()
|
|
{
|
|
// For simplicity we assume that we only replay unserialized accesses.
|
|
assert(target_type == TYPE_ID && op1_type == TYPE_VAL);
|
|
|
|
if ( ! target.id )
|
|
return;
|
|
|
|
Val* v = target.id->ID_Val();
|
|
TypeTag t = v ? v->Type()->Tag() : TYPE_VOID;
|
|
|
|
if ( opcode != OP_ASSIGN && ! v )
|
|
{
|
|
// FIXME: I think this warrants an internal error,
|
|
// but let's check that first ...
|
|
// internal_error("replay id lacking a value");
|
|
run_time("replay id lacks a value");
|
|
return;
|
|
}
|
|
|
|
++replaying;
|
|
|
|
switch ( opcode ) {
|
|
case OP_ASSIGN:
|
|
assert(op1.val);
|
|
// There mustn't be a direct assignment to a unique ID.
|
|
assert(target.id->Name()[0] != '#');
|
|
CheckOld("assign", target.id, 0, op2, v);
|
|
|
|
if ( t == TYPE_TABLE && v &&
|
|
v->AsTableVal()->FindAttr(ATTR_MERGEABLE) )
|
|
if ( MergeTables(v->AsTableVal(), op1.val) )
|
|
break;
|
|
|
|
target.id->SetVal(op1.val->Ref());
|
|
break;
|
|
|
|
case OP_INCR:
|
|
if ( IsIntegral(t) )
|
|
{
|
|
assert(op1.val && op2);
|
|
// We derive the amount as difference between old
|
|
// and new value.
|
|
bro_int_t amount =
|
|
op1.val->CoerceToInt() - op2->CoerceToInt();
|
|
|
|
target.id->SetVal(new Val(v->CoerceToInt() + amount, t),
|
|
OP_INCR);
|
|
}
|
|
break;
|
|
|
|
case OP_ASSIGN_IDX:
|
|
assert(op1.val);
|
|
|
|
if ( t == TYPE_TABLE )
|
|
{
|
|
assert(op2);
|
|
|
|
BroType* yt = v->Type()->AsTableType()->YieldType();
|
|
|
|
if ( yt && yt->Tag() == TYPE_TABLE )
|
|
{
|
|
TableVal* tv = v->AsTableVal();
|
|
Val* w = tv->Lookup(op1.val);
|
|
if ( w && w->AsTableVal()->FindAttr(ATTR_MERGEABLE) )
|
|
if ( MergeTables(w->AsTableVal(), op2) )
|
|
break;
|
|
}
|
|
|
|
CheckOld("index assign", target.id, op1.val, op3,
|
|
v->AsTableVal()->Lookup(op1.val));
|
|
|
|
v->AsTableVal()->Assign(op1.val, op2 ? op2->Ref() : 0);
|
|
}
|
|
|
|
else if ( t == TYPE_RECORD )
|
|
{
|
|
const char* field = op1.val->AsString()->CheckString();
|
|
int idx = v->Type()->AsRecordType()->FieldOffset(field);
|
|
|
|
if ( idx >= 0 )
|
|
{
|
|
BroType* ft = v->Type()->AsRecordType()->FieldType(field);
|
|
|
|
if ( ft && ft->Tag() == TYPE_TABLE )
|
|
{
|
|
RecordVal* rv = v->AsRecordVal();
|
|
Val* w = rv->Lookup(idx);
|
|
if ( w && w->AsTableVal()->FindAttr(ATTR_MERGEABLE) )
|
|
if ( MergeTables(w->AsTableVal(), op2) )
|
|
break;
|
|
}
|
|
|
|
CheckOld("index assign", target.id, op1.val, op3,
|
|
v->AsRecordVal()->Lookup(idx));
|
|
v->AsRecordVal()->Assign(idx, op2 ? op2->Ref() : 0);
|
|
}
|
|
else
|
|
run_time(fmt("access replay: unknown record field %s for assign", field));
|
|
}
|
|
|
|
else if ( t == TYPE_VECTOR )
|
|
{
|
|
assert(op2);
|
|
bro_uint_t index = op1.val->AsCount();
|
|
|
|
BroType* yt = v->Type()->AsVectorType()->YieldType();
|
|
|
|
if ( yt && yt->Tag() == TYPE_TABLE )
|
|
{
|
|
VectorVal* vv = v->AsVectorVal();
|
|
Val* w = vv->Lookup(index);
|
|
if ( w && w->AsTableVal()->FindAttr(ATTR_MERGEABLE) )
|
|
if ( MergeTables(w->AsTableVal(), op2) )
|
|
break;
|
|
}
|
|
|
|
CheckOld("index assign", target.id, op1.val, op3,
|
|
v->AsVectorVal()->Lookup(index));
|
|
v->AsVectorVal()->Assign(index, op2 ? op2->Ref() : 0, 0);
|
|
}
|
|
|
|
else
|
|
internal_error("unknown type in replaying index assign");
|
|
|
|
break;
|
|
|
|
case OP_INCR_IDX:
|
|
{
|
|
assert(op1.val && op2 && op3);
|
|
|
|
// We derive the amount as the difference between old
|
|
// and new value.
|
|
bro_int_t amount = op2->CoerceToInt() - op3->CoerceToInt();
|
|
|
|
if ( t == TYPE_TABLE )
|
|
{
|
|
t = v->Type()->AsTableType()->YieldType()->Tag();
|
|
Val* lookup_op1 = v->AsTableVal()->Lookup(op1.val);
|
|
int delta = lookup_op1->CoerceToInt() + amount;
|
|
Val* new_val = new Val(delta, t);
|
|
v->AsTableVal()->Assign(op1.val, new_val, OP_INCR );
|
|
}
|
|
|
|
else if ( t == TYPE_RECORD )
|
|
{
|
|
const char* field = op1.val->AsString()->CheckString();
|
|
int idx = v->Type()->AsRecordType()->FieldOffset(field);
|
|
if ( idx >= 0 )
|
|
{
|
|
t = v->Type()->AsRecordType()->FieldType(idx)->Tag();
|
|
Val* lookup_field =
|
|
v->AsRecordVal()->Lookup(idx);
|
|
bro_int_t delta =
|
|
lookup_field->CoerceToInt() + amount;
|
|
Val* new_val = new Val(delta, t);
|
|
v->AsRecordVal()->Assign(idx, new_val, OP_INCR);
|
|
}
|
|
else
|
|
run_time(fmt("access replay: unknown record field %s for assign", field));
|
|
}
|
|
|
|
else if ( t == TYPE_VECTOR )
|
|
{
|
|
bro_uint_t index = op1.val->AsCount();
|
|
t = v->Type()->AsVectorType()->YieldType()->Tag();
|
|
Val* lookup_op1 = v->AsVectorVal()->Lookup(index);
|
|
int delta = lookup_op1->CoerceToInt() + amount;
|
|
Val* new_val = new Val(delta, t);
|
|
v->AsVectorVal()->Assign(index, new_val, 0);
|
|
}
|
|
|
|
else
|
|
internal_error("unknown type in replaying index increment");
|
|
|
|
break;
|
|
}
|
|
|
|
case OP_ADD:
|
|
assert(op1.val);
|
|
if ( t == TYPE_TABLE )
|
|
{
|
|
CheckOldSet("add", target.id, op1.val, op2 != 0,
|
|
v->AsTableVal()->Lookup(op1.val) != 0);
|
|
v->AsTableVal()->Assign(op1.val, 0);
|
|
}
|
|
break;
|
|
|
|
case OP_DEL:
|
|
assert(op1.val);
|
|
if ( t == TYPE_TABLE )
|
|
{
|
|
if ( v->Type()->AsTableType()->IsSet() )
|
|
CheckOldSet("delete", target.id, op1.val, op2 != 0,
|
|
v->AsTableVal()->Lookup(op1.val) != 0);
|
|
else
|
|
CheckOld("delete", target.id, op1.val, op2,
|
|
v->AsTableVal()->Lookup(op1.val));
|
|
|
|
Unref(v->AsTableVal()->Delete(op1.val));
|
|
}
|
|
break;
|
|
|
|
case OP_EXPIRE:
|
|
assert(op1.val);
|
|
if ( t == TYPE_TABLE )
|
|
{
|
|
// No old check for expire. It may have already
|
|
// been deleted by ourselves. Furthermore, we
|
|
// ignore the expire_func's return value.
|
|
TableVal* tv = v->AsTableVal();
|
|
if ( tv->Lookup(op1.val, false) )
|
|
{
|
|
// We want to propagate state updates which
|
|
// are performed in the expire_func.
|
|
StateAccess::ResumeReplay();
|
|
|
|
if ( remote_serializer )
|
|
remote_serializer->ResumeStateUpdates();
|
|
|
|
tv->CallExpireFunc(op1.val->Ref());
|
|
|
|
if ( remote_serializer )
|
|
remote_serializer->SuspendStateUpdates();
|
|
|
|
StateAccess::SuspendReplay();
|
|
|
|
Unref(tv->AsTableVal()->Delete(op1.val));
|
|
}
|
|
}
|
|
|
|
break;
|
|
|
|
case OP_PRINT:
|
|
assert(op1.val);
|
|
internal_error("access replay for print not implemented");
|
|
break;
|
|
|
|
case OP_READ_IDX:
|
|
if ( t == TYPE_TABLE )
|
|
{
|
|
assert(op1.val);
|
|
TableVal* tv = v->AsTableVal();
|
|
|
|
// Update the timestamp if we have a read_expire.
|
|
if ( tv->FindAttr(ATTR_EXPIRE_READ) )
|
|
{
|
|
if ( ! tv->UpdateTimestamp(op1.val) &&
|
|
remote_check_sync_consistency )
|
|
{
|
|
ODesc d;
|
|
d.SetShort();
|
|
op1.val->Describe(&d);
|
|
|
|
val_list* args = new val_list;
|
|
args->append(new StringVal("read"));
|
|
args->append(new StringVal(fmt("%s[%s]", target.id->Name(), d.Description())));
|
|
args->append(new StringVal("existent"));
|
|
args->append(new StringVal("not existent"));
|
|
mgr.QueueEvent(remote_state_inconsistency, args);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
run_time("read for non-table");
|
|
break;
|
|
|
|
default:
|
|
internal_error("access replay: unknown opcode for StateAccess");
|
|
break;
|
|
}
|
|
|
|
--replaying;
|
|
|
|
if ( remote_state_access_performed )
|
|
{
|
|
val_list* vl = new val_list;
|
|
vl->append(new StringVal(target.id->Name()));
|
|
vl->append(target.id->ID_Val()->Ref());
|
|
mgr.QueueEvent(remote_state_access_performed, vl);
|
|
}
|
|
}
|
|
|
|
ID* StateAccess::Target() const
|
|
{
|
|
return target_type == TYPE_ID ? target.id : target.val->UniqueID();
|
|
}
|
|
|
|
bool StateAccess::Serialize(SerialInfo* info) const
|
|
{
|
|
return SerialObj::Serialize(info);
|
|
}
|
|
|
|
StateAccess* StateAccess::Unserialize(UnserialInfo* info)
|
|
{
|
|
StateAccess* sa =
|
|
(StateAccess*) SerialObj::Unserialize(info, SER_STATE_ACCESS);
|
|
return sa;
|
|
}
|
|
|
|
IMPLEMENT_SERIAL(StateAccess, SER_STATE_ACCESS);
|
|
|
|
bool StateAccess::DoSerialize(SerialInfo* info) const
|
|
{
|
|
DO_SERIALIZE(SER_STATE_ACCESS, SerialObj);
|
|
|
|
if ( ! SERIALIZE(char(opcode)) )
|
|
return false;
|
|
|
|
const ID* id =
|
|
target_type == TYPE_ID ? target.id : target.val->UniqueID();
|
|
|
|
if ( ! SERIALIZE(id->Name()) )
|
|
return false;
|
|
|
|
if ( op1_type == TYPE_KEY )
|
|
{
|
|
Val* index =
|
|
id->ID_Val()->AsTableVal()->RecoverIndex(this->op1.key);
|
|
|
|
if ( ! index )
|
|
return false;
|
|
if ( ! index->Serialize(info) )
|
|
return false;
|
|
|
|
Unref(index);
|
|
}
|
|
|
|
else if ( ! op1.val->Serialize(info) )
|
|
return false;
|
|
|
|
// Don't send the "old" operand if we don't want consistency checks.
|
|
// Unfortunately, it depends on the opcode which operand that actually
|
|
// is.
|
|
|
|
const Val* null = 0;
|
|
|
|
if ( remote_check_sync_consistency )
|
|
{
|
|
SERIALIZE_OPTIONAL(op2);
|
|
SERIALIZE_OPTIONAL(op3);
|
|
}
|
|
|
|
else
|
|
{
|
|
switch ( opcode ) {
|
|
case OP_PRINT:
|
|
case OP_EXPIRE:
|
|
case OP_READ_IDX:
|
|
// No old.
|
|
SERIALIZE_OPTIONAL(null);
|
|
SERIALIZE_OPTIONAL(null);
|
|
break;
|
|
|
|
case OP_INCR:
|
|
case OP_INCR_IDX:
|
|
// Always need old.
|
|
SERIALIZE_OPTIONAL(op2);
|
|
SERIALIZE_OPTIONAL(op3);
|
|
break;
|
|
|
|
case OP_ASSIGN:
|
|
case OP_ADD:
|
|
case OP_DEL:
|
|
// Op2 is old.
|
|
SERIALIZE_OPTIONAL(null);
|
|
SERIALIZE_OPTIONAL(null);
|
|
break;
|
|
|
|
case OP_ASSIGN_IDX:
|
|
// Op3 is old.
|
|
SERIALIZE_OPTIONAL(op2);
|
|
SERIALIZE_OPTIONAL(null);
|
|
break;
|
|
|
|
default:
|
|
internal_error("StateAccess::DoSerialize: unknown opcode");
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool StateAccess::DoUnserialize(UnserialInfo* info)
|
|
{
|
|
DO_UNSERIALIZE(SerialObj);
|
|
|
|
char c;
|
|
if ( ! UNSERIALIZE(&c) )
|
|
return false;
|
|
|
|
opcode = Opcode(c);
|
|
|
|
const char* name;
|
|
if ( ! UNSERIALIZE_STR(&name, 0) )
|
|
return false;
|
|
|
|
target_type = TYPE_ID;
|
|
target.id = global_scope()->Lookup(name);
|
|
|
|
if ( target.id )
|
|
// Otherwise, we'll delete it below.
|
|
delete [] name;
|
|
|
|
op1_type = TYPE_VAL;
|
|
op1.val = Val::Unserialize(info);
|
|
if ( ! op1.val )
|
|
return false;
|
|
|
|
UNSERIALIZE_OPTIONAL(op2, Val::Unserialize(info));
|
|
UNSERIALIZE_OPTIONAL(op3, Val::Unserialize(info));
|
|
|
|
if ( target.id )
|
|
Ref(target.id);
|
|
else
|
|
{
|
|
// This may happen as long as we haven't agreed on the
|
|
// unique name for an ID during initial synchronization, or if
|
|
// the local peer has already deleted the ID.
|
|
DBG_LOG(DBG_STATE, "state access referenced unknown id %s", name);
|
|
|
|
if ( info->install_uniques )
|
|
{
|
|
target.id = new ID(name, SCOPE_GLOBAL, true);
|
|
Ref(target.id);
|
|
global_scope()->Insert(name, target.id);
|
|
#ifdef USE_PERFTOOLS
|
|
heap_checker->IgnoreObject(target.id);
|
|
#endif
|
|
}
|
|
|
|
delete [] name;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void StateAccess::Describe(ODesc* d) const
|
|
{
|
|
const ID* id;
|
|
const char* id_str = "";
|
|
const char* unique_str = "";
|
|
|
|
d->SetShort();
|
|
|
|
if ( target_type == TYPE_ID )
|
|
{
|
|
id = target.id;
|
|
|
|
if ( ! id )
|
|
{
|
|
d->Add("(unknown id)");
|
|
return;
|
|
}
|
|
|
|
id_str = id->Name();
|
|
|
|
if ( id->ID_Val() && id->ID_Val()->IsMutableVal() &&
|
|
id->Name()[0] != '#' )
|
|
unique_str = fmt(" [id] (%s)", id->ID_Val()->AsMutableVal()->UniqueID()->Name());
|
|
}
|
|
else
|
|
{
|
|
id = target.val->UniqueID();
|
|
|
|
#ifdef DEBUG
|
|
if ( target.val->GetID() )
|
|
{
|
|
id_str = target.val->GetID()->Name();
|
|
unique_str = fmt(" [val] (%s)", id->Name());
|
|
}
|
|
else
|
|
#endif
|
|
id_str = id->Name();
|
|
}
|
|
|
|
const Val* op1 = op1_type == TYPE_VAL ?
|
|
this->op1.val :
|
|
id->ID_Val()->AsTableVal()->RecoverIndex(this->op1.key);
|
|
|
|
switch ( opcode ) {
|
|
case OP_ASSIGN:
|
|
assert(op1);
|
|
d->Add(id_str);
|
|
d->Add(" = ");
|
|
op1->Describe(d);
|
|
if ( op2 )
|
|
{
|
|
d->Add(" (");
|
|
op2->Describe(d);
|
|
d->Add(")");
|
|
}
|
|
d->Add(unique_str);
|
|
break;
|
|
|
|
case OP_INCR:
|
|
assert(op1 && op2);
|
|
d->Add(id_str);
|
|
d->Add(" += ");
|
|
d->Add(op1->CoerceToInt() - op2->CoerceToInt());
|
|
d->Add(unique_str);
|
|
break;
|
|
|
|
case OP_ASSIGN_IDX:
|
|
assert(op1);
|
|
d->Add(id_str);
|
|
d->Add("[");
|
|
op1->Describe(d);
|
|
d->Add("]");
|
|
d->Add(" = ");
|
|
if ( op2 )
|
|
op2->Describe(d);
|
|
else
|
|
d->Add("(null)");
|
|
if ( op3 )
|
|
{
|
|
d->Add(" (");
|
|
op3->Describe(d);
|
|
d->Add(")");
|
|
}
|
|
d->Add(unique_str);
|
|
break;
|
|
|
|
case OP_INCR_IDX:
|
|
assert(op1 && op2 && op3);
|
|
d->Add(id_str);
|
|
d->Add("[");
|
|
op1->Describe(d);
|
|
d->Add("]");
|
|
d->Add(" += ");
|
|
d->Add(op2->CoerceToInt() - op3->CoerceToInt());
|
|
d->Add(unique_str);
|
|
break;
|
|
|
|
case OP_ADD:
|
|
assert(op1);
|
|
d->Add("add ");
|
|
d->Add(id_str);
|
|
d->Add("[");
|
|
op1->Describe(d);
|
|
d->Add("]");
|
|
if ( op2 )
|
|
{
|
|
d->Add(" (");
|
|
op2->Describe(d);
|
|
d->Add(")");
|
|
}
|
|
d->Add(unique_str);
|
|
break;
|
|
|
|
case OP_DEL:
|
|
assert(op1);
|
|
d->Add("del ");
|
|
d->Add(id_str);
|
|
d->Add("[");
|
|
op1->Describe(d);
|
|
d->Add("]");
|
|
if ( op2 )
|
|
{
|
|
d->Add(" (");
|
|
op2->Describe(d);
|
|
d->Add(")");
|
|
}
|
|
d->Add(unique_str);
|
|
break;
|
|
|
|
case OP_EXPIRE:
|
|
assert(op1);
|
|
d->Add("expire ");
|
|
d->Add(id_str);
|
|
d->Add("[");
|
|
op1->Describe(d);
|
|
d->Add("]");
|
|
if ( op2 )
|
|
{
|
|
d->Add(" (");
|
|
op2->Describe(d);
|
|
d->Add(")");
|
|
}
|
|
d->Add(unique_str);
|
|
break;
|
|
|
|
case OP_PRINT:
|
|
assert(op1);
|
|
d->Add("print ");
|
|
d->Add(id_str);
|
|
op1->Describe(d);
|
|
d->Add(unique_str);
|
|
break;
|
|
|
|
case OP_READ_IDX:
|
|
assert(op1);
|
|
d->Add("read ");
|
|
d->Add(id_str);
|
|
d->Add("[");
|
|
op1->Describe(d);
|
|
d->Add("]");
|
|
break;
|
|
|
|
default:
|
|
internal_error("unknown opcode for StateAccess");
|
|
break;
|
|
}
|
|
|
|
if ( op1_type != TYPE_VAL )
|
|
Unref(const_cast<Val*>(op1));
|
|
}
|
|
|
|
void StateAccess::Log(StateAccess* access)
|
|
{
|
|
bool synchronized = false;
|
|
bool persistent = false;
|
|
bool tracked = false;
|
|
|
|
if ( access->target_type == TYPE_ID )
|
|
{
|
|
if ( access->target.id->FindAttr(ATTR_SYNCHRONIZED) )
|
|
synchronized = true;
|
|
|
|
if ( access->target.id->FindAttr(ATTR_PERSISTENT) )
|
|
persistent = true;
|
|
|
|
if ( access->target.id->FindAttr(ATTR_TRACKED) )
|
|
tracked = true;
|
|
}
|
|
else
|
|
{
|
|
if ( access->target.val->GetProperties() & MutableVal::SYNCHRONIZED )
|
|
synchronized = true;
|
|
|
|
if ( access->target.val->GetProperties() & MutableVal::PERSISTENT )
|
|
persistent = true;
|
|
|
|
if ( access->target.val->GetProperties() & MutableVal::TRACKED )
|
|
tracked = true;
|
|
}
|
|
|
|
if ( synchronized )
|
|
{
|
|
if ( state_serializer )
|
|
{
|
|
SerialInfo info(state_serializer);
|
|
state_serializer->Serialize(&info, *access);
|
|
}
|
|
|
|
SerialInfo info(remote_serializer);
|
|
remote_serializer->SendAccess(&info, *access);
|
|
}
|
|
|
|
if ( persistent && persistence_serializer->IsSerializationRunning() )
|
|
persistence_serializer->LogAccess(*access);
|
|
|
|
if ( tracked )
|
|
notifiers.AccessPerformed(*access);
|
|
|
|
#ifdef DEBUG
|
|
ODesc desc;
|
|
access->Describe(&desc);
|
|
DBG_LOG(DBG_STATE, "operation: %s%s [%s%s]",
|
|
desc.Description(), replaying > 0 ? " (replay)" : "",
|
|
persistent ? "P" : "", synchronized ? "S" : "");
|
|
#endif
|
|
|
|
delete access;
|
|
|
|
}
|
|
|
|
NotifierRegistry notifiers;
|
|
|
|
void NotifierRegistry::Register(ID* id, NotifierRegistry::Notifier* notifier)
|
|
{
|
|
DBG_LOG(DBG_NOTIFIERS, "registering ID %s for notifier %s",
|
|
id->Name(), notifier->Name());
|
|
|
|
if ( id->Attrs() )
|
|
id->Attrs()->AddAttr(new Attr(ATTR_TRACKED));
|
|
else
|
|
{
|
|
attr_list* a = new attr_list;
|
|
Attr* attr = new Attr(ATTR_TRACKED);
|
|
a->append(attr);
|
|
id->SetAttrs(new Attributes(a, id->Type(), false));
|
|
Unref(attr);
|
|
}
|
|
|
|
NotifierMap::iterator i = ids.find(id->Name());
|
|
|
|
if ( i != ids.end() )
|
|
i->second->insert(notifier);
|
|
else
|
|
{
|
|
NotifierSet* s = new NotifierSet;
|
|
s->insert(notifier);
|
|
ids.insert(NotifierMap::value_type(id->Name(), s));
|
|
}
|
|
|
|
Ref(id);
|
|
}
|
|
|
|
void NotifierRegistry::Register(Val* val, NotifierRegistry::Notifier* notifier)
|
|
{
|
|
if ( val->IsMutableVal() )
|
|
Register(val->AsMutableVal()->UniqueID(), notifier);
|
|
}
|
|
|
|
void NotifierRegistry::Unregister(ID* id, NotifierRegistry::Notifier* notifier)
|
|
{
|
|
DBG_LOG(DBG_NOTIFIERS, "unregistering ID %s for notifier %s",
|
|
id->Name(), notifier->Name());
|
|
|
|
NotifierMap::iterator i = ids.find(id->Name());
|
|
|
|
if ( i == ids.end() )
|
|
return;
|
|
|
|
id->Attrs()->RemoveAttr(ATTR_TRACKED);
|
|
|
|
NotifierSet* s = i->second;
|
|
s->erase(notifier);
|
|
|
|
if ( s->size() == 0 )
|
|
{
|
|
delete s;
|
|
ids.erase(i);
|
|
}
|
|
|
|
Unref(id);
|
|
}
|
|
|
|
void NotifierRegistry::Unregister(Val* val, NotifierRegistry::Notifier* notifier)
|
|
{
|
|
if ( val->IsMutableVal() )
|
|
Unregister(val->AsMutableVal()->UniqueID(), notifier);
|
|
}
|
|
|
|
void NotifierRegistry::AccessPerformed(const StateAccess& sa)
|
|
{
|
|
ID* id = sa.Target();
|
|
|
|
NotifierMap::iterator i = ids.find(id->Name());
|
|
|
|
if ( i == ids.end() )
|
|
return;
|
|
|
|
DBG_LOG(DBG_NOTIFIERS, "modification to tracked ID %s", id->Name());
|
|
|
|
NotifierSet* s = i->second;
|
|
|
|
if ( id->IsInternalGlobal() )
|
|
for ( NotifierSet::iterator j = s->begin(); j != s->end(); j++ )
|
|
(*j)->Access(id->ID_Val(), sa);
|
|
else
|
|
for ( NotifierSet::iterator j = s->begin(); j != s->end(); j++ )
|
|
(*j)->Access(id, sa);
|
|
}
|
|
|
|
const char* NotifierRegistry::Notifier::Name() const
|
|
{
|
|
return fmt("%p", this);
|
|
}
|
|
|