Merge pull request #6977 from electron/ipc-cycle-detection

Improve cycle detection in IPC arguments
This commit is contained in:
Cheng Zhao 2016-08-26 20:26:00 +09:00 committed by GitHub
commit d35613b5ad
4 changed files with 86 additions and 17 deletions

View file

@ -49,33 +49,83 @@ class V8ValueConverter::FromV8ValueState {
// other handle B in the map points to the same object as A. Note that A can // other handle B in the map points to the same object as A. Note that A can
// be unique even if there already is another handle with the same identity // be unique even if there already is another handle with the same identity
// hash (key) in the map, because two objects can have the same hash. // hash (key) in the map, because two objects can have the same hash.
bool UpdateAndCheckUniqueness(v8::Local<v8::Object> handle) { bool AddToUniquenessCheck(v8::Local<v8::Object> handle) {
typedef HashToHandleMap::const_iterator Iterator; int hash;
int hash = handle->GetIdentityHash(); auto iter = GetIteratorInMap(handle, &hash);
// We only compare using == with handles to objects with the same identity if (iter != unique_map_.end())
// hash. Different hash obviously means different objects, but two objects return false;
// in a couple of thousands could have the same identity hash.
std::pair<Iterator, Iterator> range = unique_map_.equal_range(hash);
for (auto it = range.first; it != range.second; ++it) {
// Operator == for handles actually compares the underlying objects.
if (it->second == handle)
return false;
}
unique_map_.insert(std::make_pair(hash, handle)); unique_map_.insert(std::make_pair(hash, handle));
return true; return true;
} }
bool RemoveFromUniquenessCheck(v8::Local<v8::Object> handle) {
int unused_hash;
auto iter = GetIteratorInMap(handle, &unused_hash);
if (iter == unique_map_.end())
return false;
unique_map_.erase(iter);
return true;
}
bool HasReachedMaxRecursionDepth() { bool HasReachedMaxRecursionDepth() {
return max_recursion_depth_ < 0; return max_recursion_depth_ < 0;
} }
private: private:
typedef std::multimap<int, v8::Local<v8::Object> > HashToHandleMap; using HashToHandleMap = std::multimap<int, v8::Local<v8::Object>>;
using Iterator = HashToHandleMap::const_iterator;
Iterator GetIteratorInMap(v8::Local<v8::Object> handle, int* hash) {
*hash = handle->GetIdentityHash();
// We only compare using == with handles to objects with the same identity
// hash. Different hash obviously means different objects, but two objects
// in a couple of thousands could have the same identity hash.
std::pair<Iterator, Iterator> range = unique_map_.equal_range(*hash);
for (auto it = range.first; it != range.second; ++it) {
// Operator == for handles actually compares the underlying objects.
if (it->second == handle)
return it;
}
// Not found.
return unique_map_.end();
}
HashToHandleMap unique_map_; HashToHandleMap unique_map_;
int max_recursion_depth_; int max_recursion_depth_;
}; };
// A class to ensure that objects/arrays that are being converted by
// this V8ValueConverterImpl do not have cycles.
//
// An example of cycle: var v = {}; v = {key: v};
// Not an example of cycle: var v = {}; a = [v, v]; or w = {a: v, b: v};
class V8ValueConverter::ScopedUniquenessGuard {
public:
ScopedUniquenessGuard(V8ValueConverter::FromV8ValueState* state,
v8::Local<v8::Object> value)
: state_(state),
value_(value),
is_valid_(state_->AddToUniquenessCheck(value_)) {}
~ScopedUniquenessGuard() {
if (is_valid_) {
bool removed = state_->RemoveFromUniquenessCheck(value_);
DCHECK(removed);
}
}
bool is_valid() const { return is_valid_; }
private:
typedef std::multimap<int, v8::Local<v8::Object> > HashToHandleMap;
V8ValueConverter::FromV8ValueState* state_;
v8::Local<v8::Object> value_;
bool is_valid_;
DISALLOW_COPY_AND_ASSIGN(ScopedUniquenessGuard);
};
V8ValueConverter::V8ValueConverter() V8ValueConverter::V8ValueConverter()
: reg_exp_allowed_(false), : reg_exp_allowed_(false),
function_allowed_(false), function_allowed_(false),
@ -281,7 +331,8 @@ base::Value* V8ValueConverter::FromV8Array(
v8::Local<v8::Array> val, v8::Local<v8::Array> val,
FromV8ValueState* state, FromV8ValueState* state,
v8::Isolate* isolate) const { v8::Isolate* isolate) const {
if (!state->UpdateAndCheckUniqueness(val)) ScopedUniquenessGuard uniqueness_guard(state, val);
if (!uniqueness_guard.is_valid())
return base::Value::CreateNullValue().release(); return base::Value::CreateNullValue().release();
std::unique_ptr<v8::Context::Scope> scope; std::unique_ptr<v8::Context::Scope> scope;
@ -328,7 +379,8 @@ base::Value* V8ValueConverter::FromV8Object(
v8::Local<v8::Object> val, v8::Local<v8::Object> val,
FromV8ValueState* state, FromV8ValueState* state,
v8::Isolate* isolate) const { v8::Isolate* isolate) const {
if (!state->UpdateAndCheckUniqueness(val)) ScopedUniquenessGuard uniqueness_guard(state, val);
if (!uniqueness_guard.is_valid())
return base::Value::CreateNullValue().release(); return base::Value::CreateNullValue().release();
std::unique_ptr<v8::Context::Scope> scope; std::unique_ptr<v8::Context::Scope> scope;

View file

@ -32,6 +32,7 @@ class V8ValueConverter {
private: private:
class FromV8ValueState; class FromV8ValueState;
class ScopedUniquenessGuard;
v8::Local<v8::Value> ToV8ValueImpl(v8::Isolate* isolate, v8::Local<v8::Value> ToV8ValueImpl(v8::Isolate* isolate,
const base::Value* value) const; const base::Value* value) const;

View file

@ -325,6 +325,22 @@ describe('ipc module', function () {
}) })
ipcRenderer.send('message', document.location) ipcRenderer.send('message', document.location)
}) })
it('can send objects that both reference the same object', function (done) {
const child = {hello: 'world'}
const foo = {name: 'foo', child: child}
const bar = {name: 'bar', child: child}
const array = [foo, bar]
ipcRenderer.once('message', function (event, arrayValue, fooValue, barValue, childValue) {
assert.deepEqual(arrayValue, array)
assert.deepEqual(fooValue, foo)
assert.deepEqual(barValue, bar)
assert.deepEqual(childValue, child)
done()
})
ipcRenderer.send('message', array, foo, bar, child)
})
}) })
describe('ipc.sendSync', function () { describe('ipc.sendSync', function () {

View file

@ -35,8 +35,8 @@ process.stdout
// Access console to reproduce #3482. // Access console to reproduce #3482.
console console
ipcMain.on('message', function (event, arg) { ipcMain.on('message', function (event, ...args) {
event.sender.send('message', arg) event.sender.send('message', ...args)
}) })
// Write output to file if OUTPUT_TO_FILE is defined. // Write output to file if OUTPUT_TO_FILE is defined.