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

fix: ensure inputSourceMap is a plain object before instrumenting #576

Conversation

JLHwung
Copy link
Contributor

@JLHwung JLHwung commented Aug 12, 2020

This PR fixes a regression introduced in #484. When lib-instrument converts coverageData to AST nodes, babel will throw on the inputSourceMap because Babel don't know how to represent this object as literals since inputSourceMap is an instance of SourceMapGenerator.

In this PR I ensure inputSourceMap is a plain object and restore it after coverageData has been converted to AST. I am not familiar with the whole picture so please correct me if I overlooked any mistakes.

Context:
babel/babel#11614
jestjs/jest#10089

@JLHwung JLHwung force-pushed the remove-input-source-map-before-coverage-instrument branch from d7f44ca to 26c2e2a Compare August 12, 2020 19:13
const { inputSourceMap } = coverageData;
// ensure inputSourceMap is a plain object
if (inputSourceMap) {
coverageData.inputSourceMap = JSON.parse(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It could be a performance kill if the inputSourceMap is big. @coreyfarrell WDYT? Are we going to print inputSourceMap to the AST?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JLHwung sorry it's taken me so long to respond. We do print inputSourceMap to AST, this is used to remap coverage data to the original source. Source mapping is very difficult and we've had (continue to have) many issues with people running mismatched transpile settings so we save the input source map to the specific coverage data.

I'm a bit confused as coverageData.inputSourceMap should always be a plain object. Do you know how this is not the case? My preference would be to fix the bug which allows inputSourceMap to not be a plain object. In theory we could have inputSourceMap just be a string to reduce AST memory overhead. That would be a breaking change though, maybe something we could consider when babel 8 is available (the plan is for istanbuljs next majors upgrade to babel 8).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

coverageData.inputSourceMap should always be a plain object

I agree. But since inputSourceMap is in our API, I suggest we should validate if it is a plain object before it is consumed later, and finally thrown when instrumenting, which could be quite confusing: At that time we have lost the reason of why t.valueToNode is passed with a non-plain object.

vue-jest used to pass a SourceMapGenerator instance instead of a plain json object (fixed in vuejs/vue-jest#275). This PR was created then. I am good with closing this PR and instead we validate inputSourceMap in next major.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm unclear are you suggesting babel 8 will validate inputSourceMap or that istanbul-lib-instrument should validate it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be nice for istanbul-lib-instrument to validate it since Babel is not the only downstream users. But yeah Babel 8 could validate it, too.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for the slow response. I don't object to adding a check if I got a PR for it. I think we should probably just throw an error in that situation (this should be non-breaking since it will only effect code that is already broken?). I don't think istanbul-lib-instrument should be doing JSON.parse / JSON.stringify to fix broken input.

One thing I should mention my plan is for the next semver-major of babel-plugin-istanbul not use istanbul-lib-instrument, istanbul-lib-instrument will use babel-plugin-istanbul. The branch I'm working on is https://github.com/istanbuljs/babel-plugin-istanbul/tree/next. Still have to experiment with migrating istanbul-lib-instrument to use this updated branch, I'm sure I'll find some minor issues with the current babel plugin when I do that.

@JLHwung JLHwung changed the title fix: remove inputSourceMap before instrumenting fix: ensure inputSourceMap is a plain object before instrumenting Aug 12, 2020
@sargonpiraev
Copy link

Can we merge this?
I have checked that this work for me with the same problem

@coreyfarrell
Copy link
Member

Can we merge this?
I have checked that this work for me with the same problem

I don't think I'm considering merging this as is. In my opinion if something that is not a plain object is given to istanbul that is a bug from whatever created those invalid options. I'd be OK with validating and directly throwing if the wrong object is provided but I don't like the idea of performing heaving processing (JSON stringify + parse) to work around an external bug.

@JLHwung do you know if babel 8 is actually adding any validation of this option (rejection of invalid input)?

@marekdedic
Copy link
Contributor

Hi,
I just spent half a day debugging leading to this issue, so in case anyone finds this in the future, both babel-plugin-istanbul, coverage-istanbul-loader are susceptible to this issue - they pass the source map (which in my case was a SourceMap, not a POJO) straight into Instrumenter.instrument...

@bcoe
Copy link
Member

bcoe commented Dec 31, 2021

@JLHwung don't suppose you'd be up for dusting this off, could we use the approach @marekdedic has taken with object spread, rather than JSON.parse?

Another potential optimization would be for us to check if it's a POJO, and only perform the coercion if necessary, I think we could get away with something like?

if (Object.getPrototypeOf(arg) !== Object.prototype) = sourceMap = {...sourceMap}

@marekdedic
Copy link
Contributor

If you (the maintainers of istanbul) would consider merging this, I think I could update the PR pretty easily... However, as per previous comments by @coreyfarrell I though this needed to be done on the other side, so 🤷 ...

@bcoe
Copy link
Member

bcoe commented Jan 1, 2022

@marekdedic I'd be comfortable with a patch that:

  1. console.warns so that upstream implementers passing in the wrong object type correct the issue.
  2. checks the prototype, so that we're not always coercing large objects (for an edge case).

@bcoe
Copy link
Member

bcoe commented Oct 5, 2022

Thanks for the work on this @JLHwung, I've merged the alternate PR that adds tests.

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.

None yet

5 participants