GH-293: Protect copy() against reference cycles.

Reference cycles shouldn't occur but there's nothing really preventing
people from creating them, so may just as well be safe and deal with
them when cloning values. While the code is a bit more cumbersome this
way, it could actually be bit faster as well as it no longer caches
non-mutable values. (I measured it with the test suite: That's about
the same in execution time, maybe tiny little bit faster now;
definitly not slower).
This commit is contained in:
Robin Sommer 2019-06-03 15:20:30 +00:00
parent 1e488d7ebe
commit 0767598771
6 changed files with 52 additions and 12 deletions

View file

@ -97,7 +97,7 @@ Val* MD5Val::DoClone(CloneState* state)
EVP_MD_CTX_copy_ex(out->ctx, ctx); EVP_MD_CTX_copy_ex(out->ctx, ctx);
} }
return out; return state->NewClone(this, out);
} }
void MD5Val::digest(val_list& vlist, u_char result[MD5_DIGEST_LENGTH]) void MD5Val::digest(val_list& vlist, u_char result[MD5_DIGEST_LENGTH])
@ -241,7 +241,7 @@ Val* SHA1Val::DoClone(CloneState* state)
EVP_MD_CTX_copy_ex(out->ctx, ctx); EVP_MD_CTX_copy_ex(out->ctx, ctx);
} }
return out; return state->NewClone(this, out);
} }
void SHA1Val::digest(val_list& vlist, u_char result[SHA_DIGEST_LENGTH]) void SHA1Val::digest(val_list& vlist, u_char result[SHA_DIGEST_LENGTH])
@ -376,7 +376,7 @@ Val* SHA256Val::DoClone(CloneState* state)
EVP_MD_CTX_copy_ex(out->ctx, ctx); EVP_MD_CTX_copy_ex(out->ctx, ctx);
} }
return out; return state->NewClone(this, out);
} }
void SHA256Val::digest(val_list& vlist, u_char result[SHA256_DIGEST_LENGTH]) void SHA256Val::digest(val_list& vlist, u_char result[SHA256_DIGEST_LENGTH])
@ -517,7 +517,7 @@ Val* EntropyVal::DoClone(CloneState* state)
uinfo.cache = false; uinfo.cache = false;
Val* clone = Unserialize(&uinfo, type); Val* clone = Unserialize(&uinfo, type);
free(data); free(data);
return clone; return state->NewClone(this, clone);
} }
bool EntropyVal::Feed(const void* data, size_t size) bool EntropyVal::Feed(const void* data, size_t size)
@ -639,10 +639,10 @@ Val* BloomFilterVal::DoClone(CloneState* state)
{ {
auto bf = new BloomFilterVal(bloom_filter->Clone()); auto bf = new BloomFilterVal(bloom_filter->Clone());
bf->Typify(type); bf->Typify(type);
return bf; return state->NewClone(this, bf);
} }
return new BloomFilterVal(); return state->NewClone(this, new BloomFilterVal());
} }
bool BloomFilterVal::Typify(BroType* arg_type) bool BloomFilterVal::Typify(BroType* arg_type)
@ -801,7 +801,8 @@ CardinalityVal::~CardinalityVal()
Val* CardinalityVal::DoClone(CloneState* state) Val* CardinalityVal::DoClone(CloneState* state)
{ {
return new CardinalityVal(new probabilistic::CardinalityCounter(*c)); return state->NewClone(this,
new CardinalityVal(new probabilistic::CardinalityCounter(*c)));
} }
IMPLEMENT_SERIAL(CardinalityVal, SER_CARDINALITY_VAL); IMPLEMENT_SERIAL(CardinalityVal, SER_CARDINALITY_VAL);

View file

@ -1155,8 +1155,8 @@ unsigned int StringVal::MemoryAllocation() const
Val* StringVal::DoClone(CloneState* state) Val* StringVal::DoClone(CloneState* state)
{ {
return new StringVal(new BroString((u_char*) val.string_val->Bytes(), return state->NewClone(this, new StringVal(new BroString((u_char*) val.string_val->Bytes(),
val.string_val->Len(), 1)); val.string_val->Len(), 1)));
} }
IMPLEMENT_SERIAL(StringVal, SER_STRING_VAL); IMPLEMENT_SERIAL(StringVal, SER_STRING_VAL);
@ -1226,7 +1226,7 @@ Val* PatternVal::DoClone(CloneState* state)
auto re = new RE_Matcher(val.re_val->PatternText(), auto re = new RE_Matcher(val.re_val->PatternText(),
val.re_val->AnywherePatternText()); val.re_val->AnywherePatternText());
re->Compile(); re->Compile();
return new PatternVal(re); return state->NewClone(this, new PatternVal(re));
} }
IMPLEMENT_SERIAL(PatternVal, SER_PATTERN_VAL); IMPLEMENT_SERIAL(PatternVal, SER_PATTERN_VAL);
@ -1331,6 +1331,7 @@ Val* ListVal::DoClone(CloneState* state)
{ {
auto lv = new ListVal(tag); auto lv = new ListVal(tag);
lv->vals.resize(vals.length()); lv->vals.resize(vals.length());
state->NewClone(this, lv);
loop_over_list(vals, i) loop_over_list(vals, i)
lv->Append(vals[i]->Clone(state)); lv->Append(vals[i]->Clone(state));
@ -2541,6 +2542,7 @@ void TableVal::ReadOperation(Val* index, TableEntryVal* v)
Val* TableVal::DoClone(CloneState* state) Val* TableVal::DoClone(CloneState* state)
{ {
auto tv = new TableVal(table_type); auto tv = new TableVal(table_type);
state->NewClone(this, tv);
const PDict(TableEntryVal)* tbl = AsTable(); const PDict(TableEntryVal)* tbl = AsTable();
IterCookie* cookie = tbl->InitForIteration(); IterCookie* cookie = tbl->InitForIteration();
@ -3158,6 +3160,7 @@ Val* RecordVal::DoClone(CloneState* state)
// we don't touch it. // we don't touch it.
auto rv = new RecordVal(Type()->AsRecordType(), false); auto rv = new RecordVal(Type()->AsRecordType(), false);
rv->origin = nullptr; rv->origin = nullptr;
state->NewClone(this, rv);
loop_over_list(*val.val_list_val, i) loop_over_list(*val.val_list_val, i)
{ {
@ -3454,6 +3457,7 @@ Val* VectorVal::DoClone(CloneState* state)
{ {
auto vv = new VectorVal(vector_type); auto vv = new VectorVal(vector_type);
vv->val.vector_val->reserve(val.vector_val->size()); vv->val.vector_val->reserve(val.vector_val->size());
state->NewClone(this, vv);
for ( unsigned int i = 0; i < val.vector_val->size(); ++i ) for ( unsigned int i = 0; i < val.vector_val->size(); ++i )
{ {

View file

@ -424,7 +424,16 @@ protected:
// For internal use by the Val::Clone() methods. // For internal use by the Val::Clone() methods.
struct CloneState { struct CloneState {
std::unordered_map<const Val*, Val*> clones; // Caches a cloned value for later reuse during the same
// cloning operation. For recursive types, call this *before*
// descending down.
Val* NewClone(Val *src, Val* dst)
{
clones.insert(std::make_pair(src, dst));
return dst;
}
std::unordered_map<Val*, Val*> clones;
}; };
Val* Clone(CloneState* state); Val* Clone(CloneState* state);

View file

@ -187,7 +187,7 @@ Val* TopkVal::DoClone(CloneState* state)
{ {
auto clone = new TopkVal(size); auto clone = new TopkVal(size);
clone->Merge(this); clone->Merge(this);
return clone; return state->NewClone(this, clone);
} }
bool TopkVal::DoSerialize(SerialInfo* info) const bool TopkVal::DoSerialize(SerialInfo* info) const

View file

@ -0,0 +1,3 @@
F (expected: F)
T (expected: T)
T (expected: T)

View file

@ -0,0 +1,23 @@
# @TEST-EXEC: zeek -b %INPUT >out
# @TEST-EXEC: btest-diff out
type B: record {
x: any &optional;
};
type A: record {
x: any &optional;
y: B;
};
event zeek_init()
{
local x: A;
x$x = x;
x$y$x = x;
local y = copy(x);
print fmt("%s (expected: F)", same_object(x, y));
print fmt("%s (expected: T)", same_object(y, y$x));
print fmt("%s (expected: T)", same_object(y, y$y$x));
}