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

Function calls with side effects are removed (when they shouldn't be) #476

Closed
Pauan opened this issue Jan 28, 2016 · 6 comments
Closed

Function calls with side effects are removed (when they shouldn't be) #476

Pauan opened this issue Jan 28, 2016 · 6 comments

Comments

@Pauan
Copy link
Contributor

Pauan commented Jan 28, 2016

Here is a reduced test case:

http://rollupjs.org/#%7B%22options%22%3A%7B%22format%22%3A%22amd%22%2C%22moduleName%22%3A%22myBundle%22%2C%22globals%22%3A%7B%7D%2C%22moduleId%22%3A%22%22%7D%2C%22modules%22%3A%5B%7B%22name%22%3A%22main.js%22%2C%22code%22%3A%22const%20foo%20%3D%20%7B%7D%3B%5Cn%5Cnconst%20set%20%3D%20(key%2C%20value)%20%3D%3E%20%7B%5Cn%20%20foo%5Bkey%5D%20%3D%20value%3B%5Cn%7D%3B%5Cn%5Cnset(%5C%22foo%5C%22%2C%201)%3B%5Cnset(%5C%22bar%5C%22%2C%202)%3B%5Cnset(%5C%22qux%5C%22%2C%203)%3B%5Cn%5Cnconsole.log(foo)%3B%22%7D%5D%7D

As you can see, it completely removes the calls to set

In my real application, I am using an object to store configuration, and then I am using function calls to populate the object.

But rollup removes all of the function calls, so the configuration ends up being empty.

@Victorystick
Copy link
Contributor

@Pauan Thank you for reporting this issue.

For reference, here is the offending code:

const foo = {};

const set = (key, value) => {
  foo[key] = value;
};

set("foo", 1);
set("bar", 2);
set("qux", 3);

console.log(foo);

It's curious that the resulting core preserves the definition of set as it's never used in the output. Removing the console.log also results in the definition of set being removed.

Replacing the set arrow-function expression with a function declaration like so, set is completely removed from the output.

const foo = {};

function set( key, value ) {
  foo[key] = value;
}

set("foo", 1);
set("bar", 2);
set("qux", 3);

console.log(foo);

I suspect that Rollup preserves the definition of the arrow function above, as it believes that the definition itself somehow modifies foo; while the calls to set don't. I've no idea why every reference to set is removed in the second example.

@eventualbuddha
Copy link
Contributor

Interestingly, commenting out const foo = {}; also "fixes" it. I tried to go back in the rollup git history to see when this broke but I can't always get it to reliably build. Perhaps @Rich-Harris can explain why I'm getting missing modules (i.e. minimist, ansi-styles) with this script:

#!/usr/bin/env bash

set -e

cat <<EOS > file.js
const foo = {};

const set = (key, value) => {
  foo[key] = value;
};

set("foo", 1);
set("bar", 2);
set("qux", 3);

console.log(foo);
EOS

git clean -dfx test
npm install
npm run build
./bin/rollup file.js | grep qux
echo PASS

@kzc
Copy link
Contributor

kzc commented Jan 31, 2016

Rollup is not acknowledging the side effect in the MemberExpression of the left side of the AssignmentExpression in function set, which is why the calls to set are thought to be side-effect-free and excluded.

If the test case is modified as follows it works as one would hope:

const foo = {};
function set(obj, key, value) {
  obj[key] = value;
}
set(foo, "bar", 2);
set(foo, "qux", 3);

The test above was was constructed after observing that a side effect is generated from a modifier node that happens to be a parameter:

if ( declaration.isParam ) hasSideEffect = true;

I think a fix has to be introduced in the general vicinity of this code to get rollup to recognize the side effect in the foo AssignmentExpression MemberExpression which is in the same module but outside the block scope of the function.

@kzc
Copy link
Contributor

kzc commented Feb 4, 2016

The same reason why rollup presently (correctly) drops obj and obj.foo from this test case:

var obj = {};
obj.foo = function() { console.log('obj.foo() called'); }
function bar() { console.log('bar() called'); }
bar();

to produce:

function bar() { console.log('bar() called'); }
bar();

is also why rollup does not acknowledge a side effect in the set function in:

const foo = {};
function set(key, value) {
  foo[key] = value;
}
set("bar", 2);
set("qux", 3);
console.log(foo);

which ultimately causes the set() calls to be incorrectly dropped.

In both cases the condition below marked as (*) was false in run(), and the side effect not triggered:

        else if ( isModifierNode( node ) ) {
            let subject = node[ modifierNodes[ node.type ] ];
            while ( subject.type === 'MemberExpression' ) subject = subject.object;

            let declaration = scope.findDeclaration( subject.name );

            if ( declaration ) {
                if ( declaration.isParam ) hasSideEffect = true;
            } else {
                declaration = statement.module.trace( subject.name );

                if ( !declaration || declaration.isExternal || declaration.isUsed ) {
                    // (*) Not reached for member expression assignments
                    //     for both test cases.
                    hasSideEffect = true;
                }
            }
        }

rollup/src/utils/run.js

Lines 90 to 102 in 4b61455

else if ( isModifierNode( node ) ) {
let subject = node[ modifierNodes[ node.type ] ];
while ( subject.type === 'MemberExpression' ) subject = subject.object;
let declaration = scope.findDeclaration( subject.name );
if ( declaration ) {
if ( declaration.isParam ) hasSideEffect = true;
} else {
declaration = statement.module.trace( subject.name );
if ( !declaration || declaration.isExternal || declaration.isUsed ) {
hasSideEffect = true;

Some code has to crafted to differentiate whether the declaration in question is actually used by the final program.

@kzc
Copy link
Contributor

kzc commented Feb 12, 2016

Fix in PR #512

@Pauan
Copy link
Contributor Author

Pauan commented Feb 16, 2016

Awesome, thanks guys!

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

No branches or pull requests

4 participants