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

Assignments to side-effecting getters in class prototypes can be incorrectly tree-shaken #4016

Closed
marijnh opened this issue Mar 25, 2021 · 3 comments · Fixed by #4018
Closed

Comments

@marijnh
Copy link
Contributor

marijnh commented Mar 25, 2021

  • Rollup Version: 2.42.4
  • Operating System (or Browser): Any
  • Node Version (if applicable): Any
  • Link to reproduction (IMPORTANT, read below): link

Expected Behavior

The code should exhibit a side effect (running console.log).

Actual Behavior

Rollup removes the entire program and emits an empty chunk.

This is caused by ClassNode.hasEffectsWhenAccessedAtPath being too liberal, but I expect just tightening that up the obvious way will cause significant regressions in tree-shaking on some code. We may have to start tracking class instance properties to properly fix this without regressing.

@marijnh
Copy link
Contributor Author

marijnh commented Mar 25, 2021

See eb6ce17, #3598, and #3587

@marijnh
Copy link
Contributor Author

marijnh commented Mar 25, 2021

(I have a fix for this in my upcoming patch, waiting on #4011 to merge because it depends on the new method added by that.)

@kzc
Copy link
Contributor

kzc commented Mar 25, 2021

Known issue: #2219

Workaround: #3985

marijnh added a commit to marijnh/rollup that referenced this issue Mar 26, 2021
This aims to improve tree-shaking of code that uses static class
properties (rollup#3989) and to improve detection of side effects through
class getters/setters (rollup#4016).

The first part works by keeping a map of positively known static
properties (methods and simple getters) in
`ClassNode.staticPropertyMap`, along with a flag
(`ClassNode.deoptimizedStatic`) that indicates that something happened
that removed our confidence that we know anything about the class
object.

Access and calls to these known static properties are handled by
routing the calls to `getLiteralValueAtPath`,
`getReturnExpressionWhenCalledAtPath`, and
`hasEffectsWhenCalledAtPath` to the known values in the properties. In
contrast to `ObjectExpression`, this class does not try to keep track
of multiple expressions associated with a property, since that doesn't
come up a lot on classes.

The handling of side effect detection through getters and setters is
done by, _if_ the entire class object (or its prototype in case of
access to the prototype) hasn't been deoptimized, scanning through the
directly defined getters and setters to see if one exists (calling
through to superclasses as appropriate). I believe that this is solid
because any code that would be able to change the set of getters and
setters on a class would cause the entire object to be deoptimized.
@marijnh marijnh mentioned this issue Mar 26, 2021
9 tasks
marijnh added a commit to marijnh/rollup that referenced this issue Mar 26, 2021
This aims to improve tree-shaking of code that uses static class
properties (rollup#3989) and to improve detection of side effects through
class getters/setters (rollup#4016).

The first part works by keeping a map of positively known static
properties (methods and simple getters) in
`ClassNode.staticPropertyMap`, along with a flag
(`ClassNode.deoptimizedStatic`) that indicates that something happened
that removed our confidence that we know anything about the class
object.

Access and calls to these known static properties are handled by
routing the calls to `getLiteralValueAtPath`,
`getReturnExpressionWhenCalledAtPath`, and
`hasEffectsWhenCalledAtPath` to the known values in the properties. In
contrast to `ObjectExpression`, this class does not try to keep track
of multiple expressions associated with a property, since that doesn't
come up a lot on classes.

The handling of side effect detection through getters and setters is
done by, _if_ the entire class object (or its prototype in case of
access to the prototype) hasn't been deoptimized, scanning through the
directly defined getters and setters to see if one exists (calling
through to superclasses as appropriate). I believe that this is solid
because any code that would be able to change the set of getters and
setters on a class would cause the entire object to be deoptimized.
marijnh added a commit to marijnh/rollup that referenced this issue Mar 28, 2021
This aims to improve tree-shaking of code that uses static class
properties (rollup#3989) and to improve detection of side effects through
class getters/setters (rollup#4016).

The first part works by keeping a map of positively known static
properties (methods and simple getters) in
`ClassNode.staticPropertyMap`, along with a flag
(`ClassNode.deoptimizedStatic`) that indicates that something happened
that removed our confidence that we know anything about the class
object.

Access and calls to these known static properties are handled by
routing the calls to `getLiteralValueAtPath`,
`getReturnExpressionWhenCalledAtPath`, and
`hasEffectsWhenCalledAtPath` to the known values in the properties. In
contrast to `ObjectExpression`, this class does not try to keep track
of multiple expressions associated with a property, since that doesn't
come up a lot on classes.

The handling of side effect detection through getters and setters is
done by, _if_ the entire class object (or its prototype in case of
access to the prototype) hasn't been deoptimized, scanning through the
directly defined getters and setters to see if one exists (calling
through to superclasses as appropriate). I believe that this is solid
because any code that would be able to change the set of getters and
setters on a class would cause the entire object to be deoptimized.
lukastaegert added a commit that referenced this issue May 23, 2021
* Move logic from ClassBody into ClassNode

So that it sits in one place and is easier to extend.

* Track static class fields and improve handling of class getters/setters

This aims to improve tree-shaking of code that uses static class
properties (#3989) and to improve detection of side effects through
class getters/setters (#4016).

The first part works by keeping a map of positively known static
properties (methods and simple getters) in
`ClassNode.staticPropertyMap`, along with a flag
(`ClassNode.deoptimizedStatic`) that indicates that something happened
that removed our confidence that we know anything about the class
object.

Access and calls to these known static properties are handled by
routing the calls to `getLiteralValueAtPath`,
`getReturnExpressionWhenCalledAtPath`, and
`hasEffectsWhenCalledAtPath` to the known values in the properties. In
contrast to `ObjectExpression`, this class does not try to keep track
of multiple expressions associated with a property, since that doesn't
come up a lot on classes.

The handling of side effect detection through getters and setters is
done by, _if_ the entire class object (or its prototype in case of
access to the prototype) hasn't been deoptimized, scanning through the
directly defined getters and setters to see if one exists (calling
through to superclasses as appropriate). I believe that this is solid
because any code that would be able to change the set of getters and
setters on a class would cause the entire object to be deoptimized.

* Remove ClassNode.deoptimizeCache

* Keep a table for class property effects

* Add comment explaining property map

* Fix types

* Make getReturnExpression and getLiteralValue more similar for objects

* Use common logic for return expression and literal value

* Use common logic for return access and call effects

* Extract shared logic from ObjectExpression

* Use an object for better performance

* Simplify handling for setters and other properties

* Small simplification

* Work towards better class handling

* merge ObjectPathHandler into ObjectEntity

* Slightly refactor default values

* Separate unknown nodes from other Nodes to avoid future circular dependencies

* Introduce new prototype tracking

* Improve coverage

* Fix class deoptimization in arrow functions via this/super

* Simplify and merge property and method definition

* Improve coverage

* Replace deoptimizeProperties by deoptimizing a double unknown path

* Assume functions can add getters to parameters

* Improve class.prototype handling

* Assume created instance getters have side-effects

* Base all expressions on a base class

* Only deoptimize necessary paths when deoptimizing "this"

* Handle deoptimizing "this" in getters

* Handle deoptimizing "this" in setters

* Unify this deoptimization

* Simplify recursion tracking

* Get rid of deoptimizations during bind phase

* Get rid of unneeded double-binding checks

* Inline deoptimizations into NodeBase for simple cases

* Add more efficient way to create object entities

* Add thisParameter to CallOptions

* Move NodeEvents to separate file

* Track array elements

* Simplify namespace handling

* Use Object.values and Object.entries instead of Object.keys where useful

* Improve code and simplify literal handling

* Improve coverage

* Improve coverage

* Improve coverage in conditional and logical expressions

* Improve coverage

* 2.49.0-0

* Fix test to support pre-release versions

* Fix failed deoptimization of array props

* 2.49.0-1

Co-authored-by: Lukas Taegert-Atkinson <lukas.taegert-atkinson@tngtech.com>
Co-authored-by: Lukas Taegert-Atkinson <lukastaegert@users.noreply.github.com>
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 a pull request may close this issue.

2 participants