Skip to content
This repository has been archived by the owner on Dec 11, 2019. It is now read-only.

Commit

Permalink
Merge pull request #13240 from brave/fix/log-state-null-key-paths
Browse files Browse the repository at this point in the history
Handle failure in creating a state diff by logging (and crash-reporting) the full path of any keys that are null
  • Loading branch information
bsclifton authored Feb 23, 2018
2 parents 3b509f4 + 4321c87 commit a4a04cc
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 3 deletions.
16 changes: 16 additions & 0 deletions app/common/state/immutableUtil.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,22 @@ const api = {
return defaultValue
}
return api.isImmutable(obj) ? obj.toJS() : obj
},

findNullKeyPaths (state, pathToState = []) {
let nullKeys = [ ]
if (!Immutable.Map.isMap(state) && !Immutable.List.isList(state)) {
return nullKeys
}
for (const key of state.keySeq()) {
const keyPath = [...pathToState, key]
if (key === null) {
nullKeys.push(keyPath)
}
// recursive, to find deep keys
nullKeys.push(...api.findNullKeyPaths(state.get(key), keyPath))
}
return nullKeys
}
}

Expand Down
25 changes: 22 additions & 3 deletions js/stores/appStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ const urlUtil = require('../lib/urlutil')
const buildConfig = require('../constants/buildConfig')

// state helpers
const {makeImmutable} = require('../../app/common/state/immutableUtil')
const {makeImmutable, findNullKeyPaths} = require('../../app/common/state/immutableUtil')
const basicAuthState = require('../../app/common/state/basicAuthState')
const extensionState = require('../../app/common/state/extensionState')
const aboutNewTabState = require('../../app/common/state/aboutNewTabState')
Expand Down Expand Up @@ -80,8 +80,27 @@ class AppStore extends EventEmitter {

emitChanges () {
if (this.lastEmittedState && this.lastEmittedState !== appState) {
const d = diff(this.lastEmittedState, appState)
if (!d.isEmpty()) {
let d
try {
d = diff(this.lastEmittedState, appState)
} catch (e) {
console.error('Error getting a diff from latest state.')
// one possible reason immutablediff can throw an error
// is due to null keys, so let's log any that we find
const nullKeyPaths = findNullKeyPaths(appState)
const error = (typeof e === 'object')
? e
: (typeof e === 'string')
? new Error(e)
: new Error()
for (let keyPath of nullKeyPaths) {
keyPath = keyPath.map(key => key === null ? 'null' : key)
const message = ` State path had null entry! Path was: [${keyPath.join(', ')}].`
error.message += message
}
throw error
}
if (d && !d.isEmpty()) {
BrowserWindow.getAllWindows().forEach((wnd) => {
if (wnd.webContents && !wnd.webContents.isDestroyed()) {
wnd.webContents.send(messages.APP_STATE_CHANGE, { stateDiff: d.toJS() })
Expand Down
28 changes: 28 additions & 0 deletions test/unit/app/common/state/immutableUtilTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -148,4 +148,32 @@ describe('immutableUtil unit test', function () {
assert.deepEqual(immutableUtil.deleteImmutablePaths(data, [['a', 'a1'], 'c']).toJS(), {a: {a2: 8}, d: 5})
})
})
describe('findNullKeyPaths', function () {
it('finds maps which have a null key', function () {
const data = Immutable.fromJS({
normal: {
key1: 'value'
},
bad: { },
anotherBad: {
deeper: { }
},
null: {
badParent: 'value4'
},
nullValue: null
})
.setIn(['bad', null], 'value2')
.setIn(['anotherBad', 'deeper', null], 'value3')
.set(null, Immutable.fromJS({badParent: 'value4'}))

const expectedNullPaths = [
['bad', null],
['anotherBad', 'deeper', null],
[null]
]
const actualNullPaths = immutableUtil.findNullKeyPaths(data)
assert.deepEqual(actualNullPaths, expectedNullPaths)
})
})
})

0 comments on commit a4a04cc

Please sign in to comment.