Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prevent accessing property of undefined while retrocycling #10

Merged
merged 1 commit into from
Jan 16, 2017
Merged

Prevent accessing property of undefined while retrocycling #10

merged 1 commit into from
Jan 16, 2017

Conversation

zalmoxisus
Copy link
Collaborator

@kolodny, not sure you'll want to accept it, but I didn't want to use a fork only for this change.

For Redux DevTools we've implemented a hack to support ImmutableJS. The idea is pretty simple, we mark custom data in the stringify replacer, then convert it back in the parse reviver. The problem, however, is that the reviver alters the path for references used by jsan and parsing fails. The solution is to skip undefined part of the path. It shouldn't be a problem for other cases, as if we're not at the end of the path and got undefined, it will fail for sure.

@kolodny
Copy link
Owner

kolodny commented Jan 12, 2017

I didn't quite follow the issue with immutable. That said I'm fine with providing an escape hatch along the lines of:

function pathGetter(obj, path, immutableEscapeHatch) {
  if (path !== '$') {
    var paths = getPaths(path);
    for (var i = 0; i < paths.length; i++) {
      path = paths[i].toString().replace(/\\"/g, '"');
      if (immutableEscapeHatch && typeof obj[path] === 'undefined' && i !== paths.length - 1) continue;
      obj = obj[path];
    }
  }
  return obj;
}

and then just call jsan.parse with an options object {immutableEscapeHatch: true}

I would need more convincing to implement it without a hatch as the default

I can make those changes or you can change the PR. I'm also giving you collaborator status for jsan on npm

@zalmoxisus
Copy link
Collaborator Author

zalmoxisus commented Jan 12, 2017

@kolodny, sorry for not providing enough information, that's not an issue with immutable but with our implementation.

So, in jsan.stringify replacer we're converting immutable custom types like so:

function replacer(key, value) {
  if (Immutable.List.isList(value)) {
    return { data: value.toArray(), __serializedType__: 'ImmutableList' }; 
  }
  // .. other types
  return value;
}

So, instead of returning the original array (or object or other type) we're returning a new object which contains that array (more details here).

However, after parsing we restore the initial structure back inside the reviver, getting rid of that part of the subtree:

function reviver(key, value) {
  if (
    typeof value === 'object' && value !== null &&
    '__serializedType__' in value && typeof value.data === 'object'
  ) {
    const data = value.data;
    // ...
    return data;
  }
  return value;
}

In other words, if y is an Immutable Map:

{ x: { y: { z: 1 } } }

the object will become:

{ x: { data: { y: { z: 1 } }, __serializedType__: 'ImmutableMap' } }

Since, replacer adds a data key, all references to z will have as a path x.data.y.z instead of x.y.z. So, after we parse with our reviver, pathGetter will not find data key in x object and will throw as y is not a property of undefined.

This happens because we first parse the data, then call retrocycling, which has altered data by the reviver.

This PR just prevents accessing a property of undefined, which is not related to Immutable. We could call it undefinedEscapeHatch, but does it worth extending the API? Would checking if a property is undefined, affect the perf? It shouldn't have edge cases (except the fact that it will make an error not obvious), because if it has no children (i !== paths.length - 1) we return undefined.

@kolodny
Copy link
Owner

kolodny commented Jan 12, 2017

I think I understand, this change will allow things like the following:

var str = `
{
  "a": {
    "b": {}
  },
  "c": {"$jsan": "$.a.no.b"}
}`;

var parsed = jsan.parse(str);
console.log(
  parsed.a.b === parsed.c
);

Which should error. I'm OK with breaking (fixing?) people that are giving bad input to parse, I'm just wondering if there's a cleaner way

@kolodny
Copy link
Owner

kolodny commented Jan 16, 2017

Published to 3.1.6, thanks and sorry for the delay

@zalmoxisus
Copy link
Collaborator Author

@kolodny, no problem. Thanks for merging and all the investigation!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants