tc39 / proposal-function-implementation-hiding

JavaScript language proposal: function implementation hiding
https://ci.tc39.es/preview/tc39/ecma262/pull/1739
MIT License
98 stars 7 forks source link

Examples of stack position information restricting refactoring #40

Open misterdjules opened 4 years ago

misterdjules commented 4 years ago

This proposal mentions:

Additionally, Error.prototype.stack reveals position (line/column number) information and file attribution information that restricts refactoring in a similar way to Function.prototype.toString's exposure of the function source text.

Do you mind sharing concrete examples of the refactoring efforts that are limited by this type of information?

michaelficarra commented 4 years ago
  1. Stack entries change (additional entries) when you pull out a helper function:
```js function f(userCode) { // ... let result = userCode(); // ... } f(function() { console.log((new Error).stack); }); ``` ```js function f(userCode) { // ... g(userCode); } function g(userCode) { let result = userCode(); // ... } f(function() { console.log((new Error).stack); }); ```
  1. Stack traces change (positions) when you add/remove lines of code:
```js function f(userCode) { // ... let result = userCode(); // ... } f(function() { console.log((new Error).stack); }); ``` ```js function f(userCode) { // ... // ... more lines (even just whitespace) ... let result = userCode(); // ... } f(function() { console.log((new Error).stack); }); ```
  1. Stack traces change (file name / URL) when you rename files. Source code examples not applicable here.
misterdjules commented 4 years ago

Right, I understand how position information in stack traces can be changed. I think my question was poorly worded. What I meant to ask was: what use cases rely on position information not changing when performing those types of refactoring?

bathos commented 4 years ago

I don’t know of any. The example in question specifically mentions it in the context of refactoring hazards, which does seem like a stretch even though it isn’t impossible.

For contexts where untrusted code is given access to select trusted objects, I think no stretch is needed. It provides information/fingerprinting and one may need to have tight control over not just what capabilities but also what information is available to the untrusted code. For example, a script could leverage callsite position information to determine what version of its hosting code is running, and this might in turn reveal whether or not some known exploit could be performed without detection. Maybe this consideration should be reframed?

jorendorff commented 4 years ago

I think the hazard is when you're the author of a widely used open source JS package.

Your users can do arbitrarily silly things, like relying on individual function names in err.stack. If a significant downstream user does this, you then have to weigh the possible breakage when refactoring.

Ridiculous user code really does block upstream improvements from time to time. Microsoft Dynamics CRM 2011 blocked Array.prototype.values for a while. MooTools made it impossible to add Array.prototype.flatten, forcing the standard committee to rename it to .flat.

Of course, these are web browser examples, not cases where "hide source" is relevant. The big difference, the reason web browsers won't break compatibility now matter how stinky the code, is that the browsers compete on web sites working. If a web site doesn't work in Firefox, users don't care that the JS on that site is horrrific. They'll just switch to Chrome. Open source libraries are not in that kind of fight, right?

So I guess I agree: to me this doesn't seem like a strong motivating use case. I don't know that open source maintainers actually want this. It might not see much use even if we implemented it, since it might trade off with bug report quality and developer experience. I could be wrong.

ljharb commented 4 years ago

I am a relatively prolific open source maintainer (200+ packages on npm, in various "top N" lists for downloads, etc) and I desperately want this so that I can prevent refactorings from being observable by consumers of the package.

misterdjules commented 4 years ago

@ljharb Do you have concrete examples (e.g. a link to a GH repo, or to a Website's source code) of some code that relies on position/file information from stack frames that prevents/prevented you from refactoring code in libraries you maintain?

To be clear: this is not a rhetorical question, I'm not necessarily disagreeing this would be useful, but without concrete non-contrived examples that demonstrate the use case, it's difficult to make a case for it and convince others this is actually needed.

ljharb commented 4 years ago

No, I do not have concrete examples; semver operates on theoretical breakage not actual breakage, so I want things that can eliminate theoretical concerns as well as concrete ones.

michaelficarra commented 4 years ago

@misterdjules Library authors not making changes because they are living with this spectre of observability is a consequence.

jorendorff commented 4 years ago

Has this spectre of observability in fact caused a library author not to make a change, that we know of?

jorendorff commented 4 years ago

I am a relatively prolific open source maintainer (200+ packages on npm, in various "top N" lists for downloads, etc) and I desperately want this so that I can prevent refactorings from being observable by consumers of the package.

@ljharb Fantastic! Can you say more about this from a library maintainer's perspective? What will you do, and how will you decide which directive to use for what?

What if your users want the kind of error logging described in #27?

[...]semver operates on theoretical breakage not actual breakage, so I want things that can eliminate theoretical concerns as well as concrete ones.

Well, semver explicitly lets packages say what the public API is, and defines compatibility in terms of that declared public API.

And generally semver is just not all that precise. I mean, define "fixes incorrect behavior"...

All of which is to say, I really think we have to justify changes using concrete concerns.

ljharb commented 4 years ago

I would hide whatever can be hidden, in an effort to be as conservative as possible.

I am not convinced by runtime observability concerns, since all of those use cases involve deployed apps with a source rewriting tool pipeline available, which thus are trivially positioned to delete any pragmas the app finds inconvenient.

jorendorff commented 4 years ago

OK, I think I have to disagree, but I see where you're coming from. Thanks for talking this out. This was really helpful.