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

When would these directive get used? #48

Closed theScottyJam closed 3 years ago

theScottyJam commented 3 years ago

I'm trying to figure out who would actually use these directives.

A user hiding a functions implementation details, before providing it to a library?

Angular V1 was cited as an example where a function's parameter names became implementation details. The angular authors later removed this because of this confusing behavior, and the issues it created for minifiers, etc. I think this example shows a concrete example of why it's bad to depend upon implementation details, but what does this proposal have to do with this example? If I were to hide the implementation details of my function before giving it to angular, I would just cause angular to malfunction and my program would break - which doesn't solve anything. The proposal itself also suggests that most users shouldn't need to use this directive in the "Will everyone just end up using this everywhere?" section, which makes it seem that the average programmer wouldn't want to be sprinkling this directive everywhere in fear of the framework they're using abusing their source.

A library author hiding implementation details from its users?

One of the main selling points for this new directive seems to be the fact that library authors can prevent the end-users from inspecting the source code and making decisions based on the source code's content, but there's still a number of other ways to retrieve this information. (EDIT: As a clarification, all of these points are about a program making decisions on the source code, not a user trying to look at the source)

A library author hiding implementation details from plugins?

Library plugins are a different story because they don't have full control over the running code and might not know where provided libraries come from. It might not be feasible for a plugin to use any of the tricks described above to retrieve a function's original source.

But, a workaround already exists in javascript for the few library authors that care enough to want to hide a function's implementation details from plugins. It works like this:

const hideSource = fn => (...args) => fn(...args)
const myLibraryFn = hideSource(() => 2 + 2)
console.log(myLibraryFn.toString()) // "(...args) => fn(...args)"

Polyfills hiding the fact that they are polyfills?

This is the one scenario where there really isn't a good alternative out there. But, are we really going to add this new directive just for polyfill authors? I'm worried the directive will be too easy to use, and people will start using it left and right under the false illusion that they're somehow writing better code by encapsulating implementation details when they're really just adding a small roadblock. I'm especially worried about this happening with the "sensitive" directive - that people will actually think they can throw a sensitive API key into a function marked as sensitive, publicly host the source, and assume that it's magically hidden from the outside world.

In the very, very few scenarios where it's actually important to hide the number of calls to a recursive function from a stack trace, the "trampoline" recursion alternative can be used. Similar tricks can be done for non-recursive functions.

Summary

Given the potential use cases above and my thoughts around them, I'm not sure I really see the point of this proposal. I agree it's weird behavior for a function to have a .toString() method that shows its original source. but I'm skeptical of this solution.

However, I might just be missing the vision of it, so please feel free to fill me in on other use cases, or rebuttals to my above arguments.

ljharb commented 3 years ago

As a library maintainer, I would use this on all of my libraries - polyfills and non-polyfills alike.

The goal isn't to keep the source code secret from humans - they can do whatever they want with that. The goal is to prevent "changing the source code" from being part of the observable API of my library. If they're doing anything with the source text itself besides evaluating it, there are no warranties and no need for me to support that.

Humans who want the original source are just going to go look at the JS code directly; they don't need runtime access for that.

theScottyJam commented 3 years ago

Maybe I wasn't very clear when I wrote it, but I was mostly referring to the fact that a program still has other ways to get a function's definition, and make programmatic choices depending on it's content - they're just more difficult to do - but if a user is already going to do some hacky code to make decisions based off of a function's source because they ran out of other options, I wouldn't put it past them to also use one of these other hacks to get around this directive.

Which brings me to another question - why would anyone make decisions based off of a library function's source, besides maybe trying to detect the version of that code? I'm sure it happens occasionally, but I can't think of any concrete examples where this would actually be a useful thing to do.

Also, what would be a concrete use case for the "sensitive" directive - I'm at an even bigger loss with that one.

ljharb commented 3 years ago

The crux is whether you have to reach outside the program (ie, the filesystem or network) to do it. If so, all bets are off. This directive handles the "if not" cases.

Tons of people have done that. Angular 1, for example, read functions' toString to try to determine automatic dependency injection. If people can do it, they will do it, and so it is absolutely critical to ensure there's ways to prevent people from doing things like this. "Occasionally" is still nonzero, which is the same as "every time" from the perspective of a library author.

theScottyJam commented 3 years ago

But ... This directive doesn't fix the angular case. Someone could still release a framework that does dependency injection that way, and in the docs they just tell you not to use this directive. Can you think of other examples for both of these directives - i.e. when might a novice or expert javascript programmer write something that relies on the function's source? Or, when might someone want to hide call stack frames. (I honestly just can't think of good ones, but maybe if I had a couple, I could better appreciate the goal).

ljharb commented 3 years ago

It fixes this case by not unknowingly allowing someone to depend on implementation details of a function. When I write my function, I don't just want to "not care" about someone doing this, i want to actively prevent them from being capable of doing so, so that the issue never comes up in the first place.

theScottyJam commented 3 years ago

So - are you recommending using this directive both as a library author (to prevent users from depending on implementation details) and as a library consumer (to prevent the libray from depending on your implementation details without you knowing, like with angular)?

Effectively, we ought to use both "use strict" and "hide source" everywhere? That's different then what the readme suggested ...

ljharb commented 3 years ago

I don't know about "ought". Using these directives harms debugging. I would certainly use it for everything I wrote, but i'd probably use a build process to insert it in prod, and not have it present in dev.

What the readme suggests, what you choose, and what others choose may be different, but it seems like your original question is answered?

theScottyJam commented 3 years ago

I guess so - at least for the "hide source" directive. But what about the "sensitive" one.

I presume when you say you would put the directive everywhere in your own source, you're referring to the "hide source" one. When would you personally choose to use the "sensitive" directive?

ljharb commented 3 years ago

If i was writing code that needed to be robust against malware, since the stack frames of an error thrown from my code could also reveal implementation details of my code. It’s not my personal use case, but some companies, for example, write software that runs on bank websites to protect them against malicious code attempting to do fraudulent things, and having the ability to fingerprint the detection code could allow bad actors to bypass it.

theScottyJam commented 3 years ago

Huh, I didn't know code like that gets written - that is an interesting use case.

Well, thanks for spending time to help me see your point of view on this.