ojj11/analyse-control

analyze-control crashes on analyzing classnames

Closed this issue · 4 comments

I tried to use this library to analyze a copy of the popular classnames library - I think this is classnames 2.2.6

// classnames.js
/*!
  Copyright (c) 2017 Jed Watson.
  Licensed under the MIT License (MIT), see
  http://jedwatson.github.io/classnames
*/
/* global define */
(function () {
    'use strict';
    var hasOwn = {}.hasOwnProperty;
    function classNames() {
        var classes = [];
        for (var i = 0; i < arguments.length; i++) {
            var arg = arguments[i];
            if (!arg)
                continue;
            var argType = typeof arg;
            if (argType === 'string' || argType === 'number') {
                classes.push(arg);
            }
            else if (Array.isArray(arg) && arg.length) {
                var inner = classNames.apply(null, arg);
                if (inner) {
                    classes.push(inner);
                }
            }
            else if (argType === 'object') {
                for (var key in arg) {
                    if (hasOwn.call(arg, key) && arg[key]) {
                        classes.push(key);
                    }
                }
            }
        }
        return classes.join(' ');
    }
    if (typeof module !== 'undefined' && module.exports) {
        classNames.default = classNames;
        module.exports = classNames;
    }
    else if (typeof define === 'function' && typeof define.amd === 'object' && define.amd) {
        // register as 'classnames', consistent with npm package name
        define('classnames', [], function () {
            return classNames;
        });
    }
    else {
        window.classNames = classNames;
    }
}());
//# sourceMappingURL=classnames.js.map

node ./node_modules/analyse-control/visualise.js classnames.js (with analyse-control 1.0.1 installed) results in

/home/dgoldstein/src/server/node_modules/analyse-control/basic_control.js:648
    throw new Error("No specific rules for given " + input.type);
    ^

Error: No specific rules for given VariableDeclarator
    at flowProgram.otherwise.then (/home/dgoldstein/src/server/node_modules/analyse-control/basic_control.js:648:11)
    at morphicFunction (/home/dgoldstein/src/server/node_modules/morphic/morphic.js:60:19)
    at nodeList.flatMap (/home/dgoldstein/src/server/node_modules/analyse-control/basic_control.js:653:37)
    at /home/dgoldstein/src/server/node_modules/immutable/dist/immutable.js:3433:45
    at /home/dgoldstein/src/server/node_modules/immutable/dist/immutable.js:3016:46
    at /home/dgoldstein/src/server/node_modules/immutable/dist/immutable.js:2867:56
    at List.__iterate (/home/dgoldstein/src/server/node_modules/immutable/dist/immutable.js:2206:13)
    at ToIndexedSequence.__iterate (/home/dgoldstein/src/server/node_modules/immutable/dist/immutable.js:2867:25)
    at IndexedIterable.mappedSequence.__iterateUncached (/home/dgoldstein/src/server/node_modules/immutable/dist/immutable.js:3015:23)
    at seqIterate (/home/dgoldstein/src/server/node_modules/immutable/dist/immutable.js:604:16)

I have no idea where to go from here as the error message is rather unhelpful and a quick glance at the code didn't help me undestand much of anything.

ojj11 commented

@dgoldstein0, I've proposed a fix #9.

You might still be underwhelmed by the output as method bodies are skipped - this library doesn't support inter-procedural control flow analysis. Whilst your example is simple, understanding how control flows in and out of methods gets very complicated, very quickly, for example:

global.difficultFlow = function difficultFlow() {
  const originalFlow = global.difficultFlow;
  global.difficultFlow = function evenMoreDifficultFlow() {
    originalFlow();
  }
}

global.difficultFlow();
// difficult flow is now evenMoreDifficultFlow { difficultFlow() }
global.difficultFlow();
// difficult flow is now evenMoreDifficultFlow { evenMoreDifficultFlow { difficultFlow() } }
global.difficultFlow();
// difficult flow is now evenMoreDifficultFlow { evenMoreDifficultFlow { evenMoreDifficultFlow { difficultFlow() } } }

Here, every time difficultFlow is called, it introduces a layer around difficultFlow. This is too complicated for this small library to navigate (and isn't needed for many tasks).

For the example you gave, you can run analyse on each nested method if you want to view these flows, (or copy and paste each method into a new file for the visualiser). The boilerplate would look something like this (although I haven't tested this):

// from https://www.npmjs.com/package/recursive-iterator :
const RecursiveIterator = require("recursive-iterator");
const acorn = require("acorn");
const analyse = require("analyse-control");

const ast = acorn.parse(program);

for(let {node} of new RecursiveIterator(ast)) {
  if (node.type == "FunctionExpression" || node.type == "FunctionDeclaration") {
    // analyse the control flow for this function:
    analyse(node.body);
    // do more interesting things here...
  }
}
ojj11 commented

This fix has been released in v1.0.3

cool, thanks for the fix.

For what it's worth I was evaluating this library for use in a tool to figure out if access to various global variables were guarded or not. I ended up solving the problem a different way - mainly by rolling my own code for it (looking for patterns like typeof x === "undefined" and a few useful variations of that with boolean operations and conditionals & ternaries), which ended up somewhat false-positive prone but covered ~2/3 of what I was aiming for. maybe someday I'll revisit it; if I do I might reevaluate this library.

and no, I didn't expect inter-procedural control flow analysis. a lot of my analysis was just trying to understand that UMD wrappers were testing the environment before using a global variable that wasn't defined. which is always in a single function in normal code