├── .eslintrc ├── .gitignore ├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── index.js ├── input-scripts ├── coolclock │ ├── coolclock.js │ ├── excanvas.js │ └── moreskins.js ├── flotr │ ├── flotr-0.2.0-alpha.js │ ├── flotr.debug-0.2.0-alpha.js │ └── lib │ │ ├── base64.js │ │ ├── canvas2image.js │ │ ├── canvastext.js │ │ ├── excanvas.js │ │ └── prototype-1.6.0.2.js ├── fullcalendar │ ├── fullcalendar │ │ ├── fullcalendar.css │ │ ├── fullcalendar.js │ │ ├── fullcalendar.min.js │ │ ├── fullcalendar.print.css │ │ └── gcal.js │ └── lib │ │ └── jquery-2.1.0.js └── simple-scripts │ ├── function_function.js │ ├── functioncall-arithmetic.js │ ├── jQuery.js │ ├── or_function.js │ └── use_before_definition.js ├── js-callgraph.js ├── package-lock.json ├── package.json ├── saveSvgAsPng.js ├── scripts ├── install-hooks └── pre-commit ├── src ├── astutil.js ├── bindings.js ├── bitset.js ├── callbackCounter.js ├── callgraph.js ├── dftc.js ├── diagnostics.js ├── flowgraph.js ├── graph.js ├── harness.js ├── input-2.js ├── linkedList.js ├── module.js ├── natives.js ├── numset.js ├── olist.js ├── pessimistic.js ├── requireJsGraph.js ├── runner.js ├── semioptimistic.js ├── set.js ├── srcPreprocessor.js ├── symtab.js ├── tests.js ├── underscore.js └── utils.js └── tests ├── README.md ├── basics ├── arrow.js ├── arrow.truth ├── assignment.js ├── assignment.truth ├── global-as-prop.js ├── global-as-prop.truth ├── local-is-fine.js ├── local-is-fine.truth ├── method-def.js └── method-def.truth ├── callgraph.py ├── classes ├── anonymous-class.js ├── basic-class.js ├── basic-class.truth ├── basic-class2.js ├── basic-class2.truth ├── class-expression1.js ├── class-expression1.truth ├── class-expression2.js ├── class-expression2.truth ├── class.js ├── class.truth ├── export-class-expression1.js ├── export-class-expression2.js ├── import-anonymous-class.js ├── import-anonymous-class.truth ├── import-class-expression1.js ├── import-class-expression1.truth ├── import-class-expression2.js ├── import-class-expression2.truth ├── outer-fn.js └── outer-fn.truth ├── creating-vue-compiled.txt ├── es6 ├── array-pattern.js ├── array-pattern.truth ├── array-pattern2.js ├── array-pattern2.truth ├── array-pattern3.js ├── array-pattern3.truth ├── array-pattern4.js ├── array-pattern4.truth ├── binding-pattern-global.js ├── binding-pattern-global.truth ├── destructured-parameter.js ├── destructured-parameter.truth ├── destructured-parameter2.js ├── destructured-parameter2.truth ├── destructured-parameter3.js ├── destructured-parameter3.truth ├── lib.js ├── main.js ├── main.truth ├── object-pattern.js ├── object-pattern.truth ├── object-pattern2.js ├── object-pattern2.truth ├── object-pattern3.js └── object-pattern3.truth ├── ground_truths ├── create-component.txt ├── create-element.txt ├── ground-truth.txt ├── patch.txt └── vnode.txt ├── import-export ├── define │ ├── arrow-func-no-block-statement-module.js │ ├── arrow-func-no-block-statement-require.js │ ├── arrow-func-no-block-statement-require.truth │ ├── define-module.js │ ├── define-require.js │ └── define-require.truth ├── es6 │ ├── es6-cyclic-dependencies1.js │ ├── es6-cyclic-dependencies1.truth │ ├── es6-cyclic-dependencies2.js │ ├── es6-cyclic-dependencies2.truth │ ├── es6-export-default.js │ ├── es6-export-fns.js │ ├── es6-export-hasOwnProperty.js │ ├── es6-export-mixed.js │ ├── es6-import-default.js │ ├── es6-import-default.truth │ ├── es6-import-entire.js │ ├── es6-import-entire.truth │ ├── es6-import-hasOwnProperty.js │ ├── es6-import-hasOwnProperty.truth │ ├── es6-import-mixed.js │ ├── es6-import-mixed.truth │ ├── es6-import-named.js │ ├── es6-import-named.truth │ ├── es6-import-redirect.js │ ├── es6-import-redirect.truth │ └── redirect │ │ ├── auth.js │ │ ├── index.js │ │ ├── project.js │ │ └── search.js └── module.exports │ ├── module-exports-module.js │ ├── module-exports-module2.js │ ├── module-exports-require.js │ ├── module-exports-require.truth │ ├── module-exports-require2.js │ └── module-exports-require2.truth ├── jest ├── graph.test.js └── linkedList.test.js ├── limits ├── history.js ├── history.truth ├── overload.js └── overload.truth ├── process.py ├── required_files.py ├── test.py ├── typescript ├── simple.truth └── simple.ts ├── unexpected ├── stringiterator.js └── stringiterator.truth ├── unhandled ├── classes │ ├── class-compiled.js │ ├── class-compiled.truth │ ├── class-compiled2.js │ ├── class-compiled2.truth │ ├── class-getter.js │ └── class-getter.truth └── limits │ ├── history.js │ ├── history.truth │ ├── overload.js │ └── overload.truth └── vue ├── TodoList.truth ├── TodoList.vue └── example.vue /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "es6": true, 4 | "node": true, 5 | }, 6 | "rules": { 7 | "no-console": "off", 8 | "no-trailing-spaces": "error" 9 | }, 10 | "parserOptions": { 11 | "ecmaVersion": 2017, 12 | "sourceType": "module" 13 | }, 14 | "extends": "eslint:recommended" 15 | } 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .idea 3 | *.pickle 4 | 5 | # Byte-compiled / optimized / DLL files 6 | __pycache__/ 7 | *.py[cod] 8 | *$py.class 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | sudo: false 3 | node_js: 4 | - "node" 5 | before_install: 6 | - pyenv global 3.6 7 | install: 8 | - npm install 9 | script: 10 | - npm test 11 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | 2 | # How to contribute 3 | 4 | Welcome! We are super happy that you intend to contribute to the javascript-call-graph! This is a great place to start. 5 | 6 | #### If you find a bug 7 | 8 | * If you can fix it, submit a PR. 9 | * If not, open up an issue and include any information that could help others reproduce and fix. 10 | 11 | #### If you have a feature proposal or want to contribute 12 | 13 | * Open up an issue for discussion. 14 | * Contribute a PR. 15 | * Respond to code review 16 | * Watch the PR be merged, and bathe in a job well done :icecream: :+1: :v: :palm_tree:. 17 | 18 | ## How to create a Pull Request 19 | 20 | * First you should **fork** this repository (hit the fork button) 21 | * **Clone** your forked repository 22 | * **Create a branch** for you new feature 23 | * **Commit your changes** to your branch 24 | * **Keep your fork synced** with the original repository if needed. See [Keeping the fork up to date](#keeping-the-fork-up-to-date) section for further details. 25 | * When you finished your work: 26 | * **Open a Pull Request** in the original repository and set master as base and your newly created branch as head. Please fill the description field for letting the others know what the contribution is about. 27 | * If your changes seem good for the maintainers they will accept your pull request and merge your commits into the original repository 28 | 29 | ## Keeping the fork up to date 30 | 31 | It is an optional step. It is recommended when you are working on a larger feature or a complex bug (not a tiny quick fix). 32 | Following the steps below ensures to track the original fork which is usually called __upstream__. 33 | First add the original repository as a remote: 34 | 35 | ``` 36 | # Add 'upstream' repo to list of remotes 37 | git remote add upstream https://github.com/UPSTREAM-USER/ORIGINAL-PROJECT.git 38 | # Verify the new remote named 'upstream' 39 | git remote -v 40 | ``` 41 | 42 | To update your fork to the latest version you have to fetch the changes from the upstream: 43 | 44 | ``` 45 | # Fetch from upstream remote 46 | git fetch upstream 47 | # View all branches, including those from upstream 48 | git branch -va 49 | ``` 50 | 51 | Now, checkout your own master branch and merge the upstream repo's master branch: 52 | 53 | ``` 54 | # Checkout your master branch and merge upstream 55 | git checkout master 56 | git merge upstream/master 57 | ``` 58 | 59 | Now your master is up to date with the remote upstream repository. Your feature branch should be also updated. For this purpose you can use a rebase command: 60 | 61 | ``` 62 | git checkout newfeature 63 | # Updates origin/master 64 | git fetch origin 65 | # Rebases current branch onto origin/master 66 | git rebase origin/master 67 | ``` 68 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Field-based Call Graph Construction for JavaScript # 2 | 3 | 4 | [![Build Status](https://travis-ci.org/Persper/js-callgraph.svg?branch=master)](https://travis-ci.org/Persper/js-callgraph) 5 | [![NPM version](https://img.shields.io/badge/npm-v1.3.2-blue.svg)](https://www.npmjs.com/package/@persper/js-callgraph) 6 | [![License](https://img.shields.io/badge/license-EPL--2.0-green.svg)](https://www.eclipse.org/legal/epl-2.0/) 7 | 8 | This project implements a field-based call graph construction algorithm for JavaScript as described in 9 | 10 | > A. Feldthaus, M. Schäfer, M. Sridharan, J. Dolby, F. Tip. Efficient Construction of Approximate Call Graphs for JavaScript IDE Services. In *ICSE*, 2013. 11 | 12 | This repo builds upon [Max Schaefer](https://github.com/xiemaisi)'s original [acg.js](https://github.com/xiemaisi/acg.js) and adds 13 | 14 | * ES6 Support 15 | * Arrow functions 16 | * Destructuring assignments 17 | * Classes 18 | * Enhanced object literals 19 | * Rest/Spread operator 20 | * Module Support 21 | * ES6/CommonJS/AMD 22 | * More sophisticated scoping 23 | * Partial update to a large call graph 24 | * Unified JSON format representing a call graph 25 | * More flexible CLI 26 | * Take directory parameter 27 | * Support filtering files by regex 28 | * Vue.js support (.vue files) 29 | * More tests 30 | 31 | ## Get Started (CLI) 32 | ``` 33 | npm install -g @persper/js-callgraph 34 | js-callgraph -h # for a list of command line arguments 35 | 36 | # Running on simple input scripts 37 | js-callgraph --cg input-scripts/simple-scripts/functioncall-arithmetic.js 38 | 39 | # Running on a whole directory 40 | js-callgraph --cg input-scripts/fullcalendar/ 41 | 42 | # Running on mixed input 43 | js-callgraph --cg input-scripts/fullcalendar/fullcalendar/ input-scripts/fullcalendar/lib/jquery-2.1.0.js 44 | 45 | # Saving the result into a file 46 | js-callgraph --cg input-scripts/simple-scripts/functioncall-arithmetic.js --output filename.json 47 | 48 | # Running on a whole directory with filtering 49 | js-callgraph --cg input-scripts/fullcalendar/ --filter filename.filter 50 | ``` 51 | 52 | For an example of the output json, please see [here](#unified-json-format). 53 | For an example of filtering file, please see [here](#filter-file-format). 54 | 55 | ## Programming Interface 56 | 57 | ``` 58 | const JCG = require("./src/runner"); 59 | args = { ... }; 60 | JCG.setArgs(args); # Optional, specify a list of arguments 61 | JCG.setFiles(['file.js', 'directory/']); # List of files or directories to analyze 62 | JCG.setFilter(['-test[^\.]*.js', '+test576.js']); # Optional, please see "Filter file format" section for details 63 | JCG.setConsoleOutput(true); # Optional, the console output can be turned off. 64 | JCG.build(); # build returns the call graph as a JSON object, please see "Unified JSON Format" section 65 | ``` 66 | 67 | ## Running Tests 68 | To run the testing framework run: 69 | ``` 70 | npm test 71 | ``` 72 | To install the git hooks to run tests automatically pre-commit run: 73 | ``` 74 | scripts/install-hooks 75 | ``` 76 | ## Structure 77 | 78 | The call graph constructor can be run in two basic modes (selected using the `--strategy` flag to `javascript-call-graph`), *pessimistic* and *optimistic*, which differ in how interprocedural flows are handled. In the basic pessimistic approach (strategy `NONE`), interprocedural flow is not tracked at all; a slight refinement is strategy `ONESHOT`, where interprocedural flow is tracked only for one-shot closures that are invoked immediatel. The optimistic approach (strategy `DEMAND`) performs interprocedural propagation along edges that may ultimately end at a call site (and are thus interesting for call graph construction). Full interprocedural propagation (strategy `FULL`) is not implemented yet. 79 | 80 | All strategies use the same intraprocedural flow graph, in which properties are only identified by name; thus, like-named properties of different objects are conflated; this can lead to imprecise call graphs. Dynamic property reads and writes are ignored, as are reflective calls using `call` and `apply`; thus, the call graphs are intrinsically incomplete. 81 | 82 | Module `flowgraph.js` contains the code for extracting an intraprocedural flow graph from an [Esprima](esprima.org) AST annotated with name bindings for local variables (see `bindings.js`, which uses `symtab.js` and `astutil.js`). 83 | 84 | Modules `pessimistic.js` and `semioptimistic.js` implement the pessimistic and optimistic call graph builders, respectively. They both use `flowgraph.js` to build an intraprocedural flow graph, and then add some edges corresponding to interprocedural flow. Both use module `callgraph.js` for extracting a call graph from a given flow graph, by collecting, for every call site, all functions that can flow into the callee position. Both use module `natives.js` to add flow edges modelling well-known standard library functions. 85 | 86 | The remaining modules define key data structures, in several variants. 87 | 88 | Module `graph.js` implements graphs using adjacency sets, using sets of numbers as implemented by `numset.js`. The latter includes either `olist.js` to implement sets as ordered lists of numbers, or `bitset.js` to use bitsets (with disappointing performance, so we use ordered lists by default). 89 | 90 | Modules `dftc.js`, `heuristictc.js` and `nuutila.js` implement several transitive closure algorithms used by `callgraph.js`. By default, we use `dftc.js` which uses a simple, depth first-search based algorithm. `heuristictc.js` does something even simpler, which is very fast but unsound. Finally, `nuutila.js` implements Nuutila's algorithm for transitive closure, which for our graphs is usually slower than the depth first-based ones. 91 | 92 | ## Unified JSON Format 93 | 94 | ``` 95 | [ # The calls are represented with a list of objects. Each call is an object in this list. 96 | { 97 | "source": { # The source object represents the start point of a call (the caller node) 98 | "label": "global", 99 | "file": "...\\input-scripts\\simple-scripts\\functioncall-arithmetic.js", 100 | "start": { # The start point of the source with row-column based position. 101 | "row": 7, 102 | "column": 4 103 | }, 104 | "end": { # The end point of the source node with row-column based position. 105 | "row": 7, 106 | "column": 8 107 | }, 108 | "range": { # The position of the source node in index-based representation. 109 | "start": 59, 110 | "end": 63 111 | } 112 | }, 113 | "target": { # The target object represents the end point of a call (this node is called by the source) 114 | "label": "f", 115 | "file": "...\\input-scripts\\simple-scripts\\functioncall-arithmetic.js", 116 | "start": { # The start point of the target node with row-column based position.. 117 | "row": 3, 118 | "column": 0 119 | }, 120 | "end": { # The end point of the target node with row-column based position. 121 | "row": 5, 122 | "column": 1 123 | }, 124 | "range": { # The position of the target node in index-based representation. 125 | "start": 14, 126 | "end": 51 127 | } 128 | } 129 | } 130 | ] 131 | ``` 132 | 133 | ## Filter file format 134 | 135 | Any valid regular expression can be specified in the filter file. The order of the including and excluding lines are important since they are processed sequentially. 136 | 137 | The first character of each line represents the type of the filtering: 138 | ``` 139 | # Comment line 140 | - Exclude 141 | + Include 142 | ``` 143 | 144 | An example for filtering: 145 | 146 | ``` 147 | # Filter out all source files starting with "test": 148 | -test[^\.]*.js 149 | # But test576.js is needed: 150 | +test576.js 151 | # Furthermore, files beginning with "test1742" are also needed: 152 | +test1742[^\.]*.js 153 | # Finally, test1742_b.js is not needed: 154 | -test1742_b.js 155 | ``` 156 | 157 | ## List of arguments 158 | 159 | ``` 160 | -h : List of command line arguments 161 | --fg : print flow graph 162 | --cg : print call graph 163 | --time : print timings 164 | --strategy : interprocedural propagation strategy; one of NONE, ONESHOT (default), DEMAND, and FULL (not yet implemented) 165 | --countCB : Counts the number of callbacks. 166 | --reqJs : Make a RequireJS dependency graph. 167 | --output : The output file name into which the result JSON should be saved. (extension: .json) 168 | --filter : Path to the filter file. 169 | ``` 170 | 171 | # Contributing 172 | 173 | Looking to contribute something? [Here's how you can help](/CONTRIBUTING.md). 174 | 175 | # License # 176 | 177 | This code is licensed under the [Eclipse Public License (v2.0)](http://www.eclipse.org/legal/epl-2.0), a copy of which is included in this repository in file `LICENSE`. 178 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2018 Persper Foundation 3 | * 4 | * All rights reserved. This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License v2.0 6 | * which accompanies this distribution, and is available at 7 | * http://www.eclipse.org/legal/epl-2.0. 8 | *******************************************************************************/ 9 | const JCG = require("./src/runner"); 10 | 11 | exports.setArgs = JCG.setArgs; 12 | exports.setFiles = JCG.setFiles; 13 | exports.setFilter = JCG.setFilter; 14 | exports.setConsoleOutput = JCG.setConsoleOutput; 15 | exports.build = JCG.build; 16 | -------------------------------------------------------------------------------- /input-scripts/coolclock/coolclock.js: -------------------------------------------------------------------------------- 1 | /** 2 | * CoolClock 2.1.4 3 | * Copyright 2010, Simon Baird 4 | * Released under the BSD License. 5 | * 6 | * Display an analog clock using canvas. 7 | * http://randomibis.com/coolclock/ 8 | * 9 | */ 10 | 11 | // Constructor for CoolClock objects 12 | window.CoolClock = function(options) { 13 | return this.init(options); 14 | } 15 | 16 | // Config contains some defaults, and clock skins 17 | CoolClock.config = { 18 | tickDelay: 1000, 19 | longTickDelay: 15000, 20 | defaultRadius: 85, 21 | renderRadius: 100, 22 | defaultSkin: "chunkySwiss", 23 | // Should be in skin probably... 24 | // (TODO: allow skinning of digital display) 25 | showSecs: true, 26 | showAmPm: true, 27 | 28 | skins: { 29 | // There are more skins in moreskins.js 30 | // Try making your own skin by copy/pasting one of these and tweaking it 31 | swissRail: { 32 | outerBorder: { lineWidth: 2, radius:95, color: "black", alpha: 1 }, 33 | smallIndicator: { lineWidth: 2, startAt: 88, endAt: 92, color: "black", alpha: 1 }, 34 | largeIndicator: { lineWidth: 4, startAt: 79, endAt: 92, color: "black", alpha: 1 }, 35 | hourHand: { lineWidth: 8, startAt: -15, endAt: 50, color: "black", alpha: 1 }, 36 | minuteHand: { lineWidth: 7, startAt: -15, endAt: 75, color: "black", alpha: 1 }, 37 | secondHand: { lineWidth: 1, startAt: -20, endAt: 85, color: "red", alpha: 1 }, 38 | secondDecoration: { lineWidth: 1, startAt: 70, radius: 4, fillColor: "red", color: "red", alpha: 1 } 39 | }, 40 | chunkySwiss: { 41 | outerBorder: { lineWidth: 4, radius:97, color: "black", alpha: 1 }, 42 | smallIndicator: { lineWidth: 4, startAt: 89, endAt: 93, color: "black", alpha: 1 }, 43 | largeIndicator: { lineWidth: 8, startAt: 80, endAt: 93, color: "black", alpha: 1 }, 44 | hourHand: { lineWidth: 12, startAt: -15, endAt: 60, color: "black", alpha: 1 }, 45 | minuteHand: { lineWidth: 10, startAt: -15, endAt: 85, color: "black", alpha: 1 }, 46 | secondHand: { lineWidth: 4, startAt: -20, endAt: 85, color: "red", alpha: 1 }, 47 | secondDecoration: { lineWidth: 2, startAt: 70, radius: 8, fillColor: "red", color: "red", alpha: 1 } 48 | }, 49 | chunkySwissOnBlack: { 50 | outerBorder: { lineWidth: 4, radius:97, color: "white", alpha: 1 }, 51 | smallIndicator: { lineWidth: 4, startAt: 89, endAt: 93, color: "white", alpha: 1 }, 52 | largeIndicator: { lineWidth: 8, startAt: 80, endAt: 93, color: "white", alpha: 1 }, 53 | hourHand: { lineWidth: 12, startAt: -15, endAt: 60, color: "white", alpha: 1 }, 54 | minuteHand: { lineWidth: 10, startAt: -15, endAt: 85, color: "white", alpha: 1 }, 55 | secondHand: { lineWidth: 4, startAt: -20, endAt: 85, color: "red", alpha: 1 }, 56 | secondDecoration: { lineWidth: 2, startAt: 70, radius: 8, fillColor: "red", color: "red", alpha: 1 } 57 | } 58 | 59 | }, 60 | 61 | // Test for IE so we can nurse excanvas in a couple of places 62 | isIE: !!document.all, 63 | 64 | // Will store (a reference to) each clock here, indexed by the id of the canvas element 65 | clockTracker: {}, 66 | 67 | // For giving a unique id to coolclock canvases with no id 68 | noIdCount: 0 69 | }; 70 | 71 | // Define the CoolClock object's methods 72 | CoolClock.prototype = { 73 | 74 | // Initialise using the parameters parsed from the colon delimited class 75 | init: function(options) { 76 | // Parse and store the options 77 | this.canvasId = options.canvasId; 78 | this.skinId = options.skinId || CoolClock.config.defaultSkin; 79 | this.displayRadius = options.displayRadius || CoolClock.config.defaultRadius; 80 | this.showSecondHand = typeof options.showSecondHand == "boolean" ? options.showSecondHand : true; 81 | this.gmtOffset = (options.gmtOffset != null && options.gmtOffset != '') ? parseFloat(options.gmtOffset) : null; 82 | this.showDigital = typeof options.showDigital == "boolean" ? options.showDigital : false; 83 | this.logClock = typeof options.logClock == "boolean" ? options.logClock : false; 84 | this.logClockRev = typeof options.logClock == "boolean" ? options.logClockRev : false; 85 | 86 | this.tickDelay = CoolClock.config[ this.showSecondHand ? "tickDelay" : "longTickDelay" ]; 87 | 88 | // Get the canvas element 89 | this.canvas = document.getElementById(this.canvasId); 90 | 91 | // Make the canvas the requested size. It's always square. 92 | this.canvas.setAttribute("width",this.displayRadius*2); 93 | this.canvas.setAttribute("height",this.displayRadius*2); 94 | this.canvas.style.width = this.displayRadius*2 + "px"; 95 | this.canvas.style.height = this.displayRadius*2 + "px"; 96 | 97 | // Explain me please...? 98 | this.renderRadius = CoolClock.config.renderRadius; 99 | this.scale = this.displayRadius / this.renderRadius; 100 | 101 | // Initialise canvas context 102 | this.ctx = this.canvas.getContext("2d"); 103 | this.ctx.scale(this.scale,this.scale); 104 | 105 | // Keep track of this object 106 | CoolClock.config.clockTracker[this.canvasId] = this; 107 | 108 | // Start the clock going 109 | this.tick(); 110 | 111 | return this; 112 | }, 113 | 114 | // Draw a circle at point x,y with params as defined in skin 115 | fullCircleAt: function(x,y,skin) { 116 | this.ctx.save(); 117 | this.ctx.globalAlpha = skin.alpha; 118 | this.ctx.lineWidth = skin.lineWidth; 119 | 120 | if (!CoolClock.config.isIE) { 121 | this.ctx.beginPath(); 122 | } 123 | 124 | if (CoolClock.config.isIE) { 125 | // excanvas doesn't scale line width so we will do it here 126 | this.ctx.lineWidth = this.ctx.lineWidth * this.scale; 127 | } 128 | 129 | this.ctx.arc(x, y, skin.radius, 0, 2*Math.PI, false); 130 | 131 | if (CoolClock.config.isIE) { 132 | // excanvas doesn't close the circle so let's fill in the tiny gap 133 | this.ctx.arc(x, y, skin.radius, -0.1, 0.1, false); 134 | } 135 | 136 | if (skin.fillColor) { 137 | this.ctx.fillStyle = skin.fillColor 138 | this.ctx.fill(); 139 | } 140 | else { 141 | // XXX why not stroke and fill 142 | this.ctx.strokeStyle = skin.color; 143 | this.ctx.stroke(); 144 | } 145 | this.ctx.restore(); 146 | }, 147 | 148 | // Draw some text centered vertically and horizontally 149 | drawTextAt: function(theText,x,y) { 150 | this.ctx.save(); 151 | this.ctx.font = '15px sans-serif'; 152 | var tSize = this.ctx.measureText(theText); 153 | if (!tSize.height) tSize.height = 15; // no height in firefox.. :( 154 | this.ctx.fillText(theText,x - tSize.width/2,y - tSize.height/2); 155 | this.ctx.restore(); 156 | }, 157 | 158 | lpad2: function(num) { 159 | return (num < 10 ? '0' : '') + num; 160 | }, 161 | 162 | tickAngle: function(second) { 163 | // Log algorithm by David Bradshaw 164 | var tweak = 3; // If it's lower the one second mark looks wrong (?) 165 | if (this.logClock) { 166 | return second == 0 ? 0 : (Math.log(second*tweak) / Math.log(60*tweak)); 167 | } 168 | else if (this.logClockRev) { 169 | // Flip the seconds then flip the angle (trickiness) 170 | second = (60 - second) % 60; 171 | return 1.0 - (second == 0 ? 0 : (Math.log(second*tweak) / Math.log(60*tweak))); 172 | } 173 | else { 174 | return second/60.0; 175 | } 176 | }, 177 | 178 | timeText: function(hour,min,sec) { 179 | var c = CoolClock.config; 180 | return '' + 181 | (c.showAmPm ? ((hour%12)==0 ? 12 : (hour%12)) : hour) + ':' + 182 | this.lpad2(min) + 183 | (c.showSecs ? ':' + this.lpad2(sec) : '') + 184 | (c.showAmPm ? (hour < 12 ? ' am' : ' pm') : '') 185 | ; 186 | }, 187 | 188 | // Draw a radial line by rotating then drawing a straight line 189 | // Ha ha, I think I've accidentally used Taus, (see http://tauday.com/) 190 | radialLineAtAngle: function(angleFraction,skin) { 191 | this.ctx.save(); 192 | this.ctx.translate(this.renderRadius,this.renderRadius); 193 | this.ctx.rotate(Math.PI * (2.0 * angleFraction - 0.5)); 194 | this.ctx.globalAlpha = skin.alpha; 195 | this.ctx.strokeStyle = skin.color; 196 | this.ctx.lineWidth = skin.lineWidth; 197 | 198 | if (CoolClock.config.isIE) 199 | // excanvas doesn't scale line width so we will do it here 200 | this.ctx.lineWidth = this.ctx.lineWidth * this.scale; 201 | 202 | if (skin.radius) { 203 | this.fullCircleAt(skin.startAt,0,skin) 204 | } 205 | else { 206 | this.ctx.beginPath(); 207 | this.ctx.moveTo(skin.startAt,0) 208 | this.ctx.lineTo(skin.endAt,0); 209 | this.ctx.stroke(); 210 | } 211 | this.ctx.restore(); 212 | }, 213 | 214 | render: function(hour,min,sec) { 215 | // Get the skin 216 | var skin = CoolClock.config.skins[this.skinId]; 217 | if (!skin) skin = CoolClock.config.skins[CoolClock.config.defaultSkin]; 218 | 219 | // Clear 220 | this.ctx.clearRect(0,0,this.renderRadius*2,this.renderRadius*2); 221 | 222 | // Draw the outer edge of the clock 223 | if (skin.outerBorder) 224 | this.fullCircleAt(this.renderRadius,this.renderRadius,skin.outerBorder); 225 | 226 | // Draw the tick marks. Every 5th one is a big one 227 | for (var i=0;i<60;i++) { 228 | (i%5) && skin.smallIndicator && this.radialLineAtAngle(this.tickAngle(i),skin.smallIndicator); 229 | !(i%5) && skin.largeIndicator && this.radialLineAtAngle(this.tickAngle(i),skin.largeIndicator); 230 | } 231 | 232 | // Write the time 233 | if (this.showDigital) { 234 | this.drawTextAt( 235 | this.timeText(hour,min,sec), 236 | this.renderRadius, 237 | this.renderRadius+this.renderRadius/2 238 | ); 239 | } 240 | 241 | // Draw the hands 242 | if (skin.hourHand) 243 | this.radialLineAtAngle(this.tickAngle(((hour%12)*5 + min/12.0)),skin.hourHand); 244 | 245 | if (skin.minuteHand) 246 | this.radialLineAtAngle(this.tickAngle((min + sec/60.0)),skin.minuteHand); 247 | 248 | if (this.showSecondHand && skin.secondHand) 249 | this.radialLineAtAngle(this.tickAngle(sec),skin.secondHand); 250 | 251 | // Second hand decoration doesn't render right in IE so lets turn it off 252 | if (!CoolClock.config.isIE && this.showSecondHand && skin.secondDecoration) 253 | this.radialLineAtAngle(this.tickAngle(sec),skin.secondDecoration); 254 | }, 255 | 256 | // Check the time and display the clock 257 | refreshDisplay: function() { 258 | var now = new Date(); 259 | if (this.gmtOffset != null) { 260 | // Use GMT + gmtOffset 261 | var offsetNow = new Date(now.valueOf() + (this.gmtOffset * 1000 * 60 * 60)); 262 | this.render(offsetNow.getUTCHours(),offsetNow.getUTCMinutes(),offsetNow.getUTCSeconds()); 263 | } 264 | else { 265 | // Use local time 266 | this.render(now.getHours(),now.getMinutes(),now.getSeconds()); 267 | } 268 | }, 269 | 270 | // Set timeout to trigger a tick in the future 271 | nextTick: function() { 272 | setTimeout("CoolClock.config.clockTracker['"+this.canvasId+"'].tick()",this.tickDelay); 273 | }, 274 | 275 | // Check the canvas element hasn't been removed 276 | stillHere: function() { 277 | return document.getElementById(this.canvasId) != null; 278 | }, 279 | 280 | // Main tick handler. Refresh the clock then setup the next tick 281 | tick: function() { 282 | if (this.stillHere()) { 283 | this.refreshDisplay() 284 | this.nextTick(); 285 | } 286 | } 287 | }; 288 | 289 | // Find all canvas elements that have the CoolClock class and turns them into clocks 290 | CoolClock.findAndCreateClocks = function() { 291 | // (Let's not use a jQuery selector here so it's easier to use frameworks other than jQuery) 292 | var canvases = document.getElementsByTagName("canvas"); 293 | for (var i=0;i 317 | // If you do have jQuery and it's loaded already then we can do it right now 318 | if (window.jQuery) jQuery(document).ready(CoolClock.findAndCreateClocks); 319 | -------------------------------------------------------------------------------- /input-scripts/flotr/lib/base64.js: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 1999 Masanao Izumo 2 | * Version: 1.0 3 | * LastModified: Dec 25 1999 4 | * This library is free. You can redistribute it and/or modify it. 5 | */ 6 | 7 | /* 8 | * Interfaces: 9 | * b64 = base64encode(data); 10 | * data = base64decode(b64); 11 | */ 12 | 13 | (function() { 14 | 15 | var base64EncodeChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 16 | var base64DecodeChars = [ 17 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 18 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 19 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 20 | 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, 21 | -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 22 | 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, 23 | -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 24 | 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1]; 25 | 26 | function base64encode(str) { 27 | var out, i, len; 28 | var c1, c2, c3; 29 | 30 | len = str.length; 31 | i = 0; 32 | out = ""; 33 | while(i < len) { 34 | c1 = str.charCodeAt(i++) & 0xff; 35 | if(i == len) 36 | { 37 | out += base64EncodeChars.charAt(c1 >> 2); 38 | out += base64EncodeChars.charAt((c1 & 0x3) << 4); 39 | out += "=="; 40 | break; 41 | } 42 | c2 = str.charCodeAt(i++); 43 | if(i == len) 44 | { 45 | out += base64EncodeChars.charAt(c1 >> 2); 46 | out += base64EncodeChars.charAt(((c1 & 0x3)<< 4) | ((c2 & 0xF0) >> 4)); 47 | out += base64EncodeChars.charAt((c2 & 0xF) << 2); 48 | out += "="; 49 | break; 50 | } 51 | c3 = str.charCodeAt(i++); 52 | out += base64EncodeChars.charAt(c1 >> 2); 53 | out += base64EncodeChars.charAt(((c1 & 0x3)<< 4) | ((c2 & 0xF0) >> 4)); 54 | out += base64EncodeChars.charAt(((c2 & 0xF) << 2) | ((c3 & 0xC0) >>6)); 55 | out += base64EncodeChars.charAt(c3 & 0x3F); 56 | } 57 | return out; 58 | } 59 | 60 | function base64decode(str) { 61 | var c1, c2, c3, c4; 62 | var i, len, out; 63 | 64 | len = str.length; 65 | i = 0; 66 | out = ""; 67 | while(i < len) { 68 | /* c1 */ 69 | do { 70 | c1 = base64DecodeChars[str.charCodeAt(i++) & 0xff]; 71 | } while(i < len && c1 == -1); 72 | if(c1 == -1) 73 | break; 74 | 75 | /* c2 */ 76 | do { 77 | c2 = base64DecodeChars[str.charCodeAt(i++) & 0xff]; 78 | } while(i < len && c2 == -1); 79 | if(c2 == -1) 80 | break; 81 | 82 | out += String.fromCharCode((c1 << 2) | ((c2 & 0x30) >> 4)); 83 | 84 | /* c3 */ 85 | do { 86 | c3 = str.charCodeAt(i++) & 0xff; 87 | if(c3 == 61) 88 | return out; 89 | c3 = base64DecodeChars[c3]; 90 | } while(i < len && c3 == -1); 91 | if(c3 == -1) 92 | break; 93 | 94 | out += String.fromCharCode(((c2 & 0XF) << 4) | ((c3 & 0x3C) >> 2)); 95 | 96 | /* c4 */ 97 | do { 98 | c4 = str.charCodeAt(i++) & 0xff; 99 | if(c4 == 61) 100 | return out; 101 | c4 = base64DecodeChars[c4]; 102 | } while(i < len && c4 == -1); 103 | if(c4 == -1) 104 | break; 105 | out += String.fromCharCode(((c3 & 0x03) << 6) | c4); 106 | } 107 | return out; 108 | } 109 | 110 | if (!window.btoa) window.btoa = base64encode; 111 | if (!window.atob) window.atob = base64decode; 112 | 113 | })(); -------------------------------------------------------------------------------- /input-scripts/flotr/lib/canvas2image.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Canvas2Image v0.1 3 | * Copyright (c) 2008 Jacob Seidelin, cupboy@gmail.com 4 | * MIT License [http://www.opensource.org/licenses/mit-license.php] 5 | */ 6 | 7 | var Canvas2Image = (function() { 8 | // check if we have canvas support 9 | var oCanvas = document.createElement("canvas"); 10 | 11 | // no canvas, bail out. 12 | if (!oCanvas.getContext) { 13 | return { 14 | saveAsBMP : function(){}, 15 | saveAsPNG : function(){}, 16 | saveAsJPEG : function(){} 17 | } 18 | } 19 | 20 | var bHasImageData = !!(oCanvas.getContext("2d").getImageData); 21 | var bHasDataURL = !!(oCanvas.toDataURL); 22 | var bHasBase64 = !!(window.btoa); 23 | 24 | var strDownloadMime = "image/octet-stream"; 25 | 26 | // ok, we're good 27 | var readCanvasData = function(oCanvas) { 28 | var iWidth = parseInt(oCanvas.width); 29 | var iHeight = parseInt(oCanvas.height); 30 | return oCanvas.getContext("2d").getImageData(0,0,iWidth,iHeight); 31 | } 32 | 33 | // base64 encodes either a string or an array of charcodes 34 | var encodeData = function(data) { 35 | var strData = ""; 36 | if (typeof data == "string") { 37 | strData = data; 38 | } else { 39 | var aData = data; 40 | for (var i = 0; i < aData.length; i++) { 41 | strData += String.fromCharCode(aData[i]); 42 | } 43 | } 44 | return btoa(strData); 45 | } 46 | 47 | // creates a base64 encoded string containing BMP data 48 | // takes an imagedata object as argument 49 | var createBMP = function(oData) { 50 | var aHeader = []; 51 | 52 | var iWidth = oData.width; 53 | var iHeight = oData.height; 54 | 55 | aHeader.push(0x42); // magic 1 56 | aHeader.push(0x4D); 57 | 58 | var iFileSize = iWidth*iHeight*3 + 54; // total header size = 54 bytes 59 | aHeader.push(iFileSize % 256); iFileSize = Math.floor(iFileSize / 256); 60 | aHeader.push(iFileSize % 256); iFileSize = Math.floor(iFileSize / 256); 61 | aHeader.push(iFileSize % 256); iFileSize = Math.floor(iFileSize / 256); 62 | aHeader.push(iFileSize % 256); 63 | 64 | aHeader.push(0); // reserved 65 | aHeader.push(0); 66 | aHeader.push(0); // reserved 67 | aHeader.push(0); 68 | 69 | aHeader.push(54); // data offset 70 | aHeader.push(0); 71 | aHeader.push(0); 72 | aHeader.push(0); 73 | 74 | var aInfoHeader = []; 75 | aInfoHeader.push(40); // info header size 76 | aInfoHeader.push(0); 77 | aInfoHeader.push(0); 78 | aInfoHeader.push(0); 79 | 80 | var iImageWidth = iWidth; 81 | aInfoHeader.push(iImageWidth % 256); iImageWidth = Math.floor(iImageWidth / 256); 82 | aInfoHeader.push(iImageWidth % 256); iImageWidth = Math.floor(iImageWidth / 256); 83 | aInfoHeader.push(iImageWidth % 256); iImageWidth = Math.floor(iImageWidth / 256); 84 | aInfoHeader.push(iImageWidth % 256); 85 | 86 | var iImageHeight = iHeight; 87 | aInfoHeader.push(iImageHeight % 256); iImageHeight = Math.floor(iImageHeight / 256); 88 | aInfoHeader.push(iImageHeight % 256); iImageHeight = Math.floor(iImageHeight / 256); 89 | aInfoHeader.push(iImageHeight % 256); iImageHeight = Math.floor(iImageHeight / 256); 90 | aInfoHeader.push(iImageHeight % 256); 91 | 92 | aInfoHeader.push(1); // num of planes 93 | aInfoHeader.push(0); 94 | 95 | aInfoHeader.push(24); // num of bits per pixel 96 | aInfoHeader.push(0); 97 | 98 | aInfoHeader.push(0); // compression = none 99 | aInfoHeader.push(0); 100 | aInfoHeader.push(0); 101 | aInfoHeader.push(0); 102 | 103 | var iDataSize = iWidth*iHeight*3; 104 | aInfoHeader.push(iDataSize % 256); iDataSize = Math.floor(iDataSize / 256); 105 | aInfoHeader.push(iDataSize % 256); iDataSize = Math.floor(iDataSize / 256); 106 | aInfoHeader.push(iDataSize % 256); iDataSize = Math.floor(iDataSize / 256); 107 | aInfoHeader.push(iDataSize % 256); 108 | 109 | for (var i = 0; i < 16; i++) { 110 | aInfoHeader.push(0); // these bytes not used 111 | } 112 | 113 | var iPadding = (4 - ((iWidth * 3) % 4)) % 4; 114 | 115 | var aImgData = oData.data; 116 | 117 | var strPixelData = ""; 118 | var y = iHeight; 119 | do { 120 | var iOffsetY = iWidth*(y-1)*4; 121 | var strPixelRow = ""; 122 | for (var x=0;x object containing the imagedata 150 | var makeImageObject = function(strSource) { 151 | var oImgElement = document.createElement("img"); 152 | oImgElement.src = strSource; 153 | return oImgElement; 154 | } 155 | 156 | var scaleCanvas = function(oCanvas, iWidth, iHeight) { 157 | if (iWidth && iHeight) { 158 | var oSaveCanvas = document.createElement("canvas"); 159 | 160 | oSaveCanvas.width = iWidth; 161 | oSaveCanvas.height = iHeight; 162 | oSaveCanvas.style.width = iWidth+"px"; 163 | oSaveCanvas.style.height = iHeight+"px"; 164 | 165 | var oSaveCtx = oSaveCanvas.getContext("2d"); 166 | 167 | oSaveCtx.drawImage(oCanvas, 0, 0, oCanvas.width, oCanvas.height, 0, 0, iWidth, iWidth); 168 | 169 | return oSaveCanvas; 170 | } 171 | return oCanvas; 172 | } 173 | 174 | return { 175 | saveAsPNG : function(oCanvas, bReturnImg, iWidth, iHeight) { 176 | if (!bHasDataURL) { 177 | return false; 178 | } 179 | var oScaledCanvas = scaleCanvas(oCanvas, iWidth, iHeight); 180 | var strData = oScaledCanvas.toDataURL("image/png"); 181 | if (bReturnImg) { 182 | return makeImageObject(strData); 183 | } else { 184 | saveFile(strData.replace("image/png", strDownloadMime)); 185 | } 186 | return true; 187 | }, 188 | 189 | saveAsJPEG : function(oCanvas, bReturnImg, iWidth, iHeight) { 190 | if (!bHasDataURL) { 191 | return false; 192 | } 193 | 194 | var oScaledCanvas = scaleCanvas(oCanvas, iWidth, iHeight); 195 | var strMime = "image/jpeg"; 196 | var strData = oScaledCanvas.toDataURL(strMime); 197 | 198 | // check if browser actually supports jpeg by looking for the mime type in the data uri. 199 | // if not, return false 200 | if (strData.indexOf(strMime) != 5) { 201 | return false; 202 | } 203 | 204 | if (bReturnImg) { 205 | return makeImageObject(strData); 206 | } else { 207 | saveFile(strData.replace(strMime, strDownloadMime)); 208 | } 209 | return true; 210 | }, 211 | 212 | saveAsBMP : function(oCanvas, bReturnImg, iWidth, iHeight) { 213 | if (!(bHasImageData && bHasBase64)) { 214 | return false; 215 | } 216 | 217 | var oScaledCanvas = scaleCanvas(oCanvas, iWidth, iHeight); 218 | 219 | var oData = readCanvasData(oScaledCanvas); 220 | var strImgData = createBMP(oData); 221 | if (bReturnImg) { 222 | return makeImageObject(makeDataURI(strImgData, "image/bmp")); 223 | } else { 224 | saveFile(makeDataURI(strImgData, strDownloadMime)); 225 | } 226 | return true; 227 | } 228 | }; 229 | 230 | })(); -------------------------------------------------------------------------------- /input-scripts/flotr/lib/canvastext.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Persper/js-callgraph/7abc6bb04fed9d82640716cc410db21fe1e9eb00/input-scripts/flotr/lib/canvastext.js -------------------------------------------------------------------------------- /input-scripts/fullcalendar/fullcalendar/fullcalendar.print.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * FullCalendar v1.6.4 Print Stylesheet 3 | * Docs & License: http://arshaw.com/fullcalendar/ 4 | * (c) 2013 Adam Shaw 5 | */ 6 | 7 | /* 8 | * Include this stylesheet on your page to get a more printer-friendly calendar. 9 | * When including this stylesheet, use the media='print' attribute of the tag. 10 | * Make sure to include this stylesheet IN ADDITION to the regular fullcalendar.css. 11 | */ 12 | 13 | 14 | /* Events 15 | -----------------------------------------------------*/ 16 | 17 | .fc-event { 18 | background: #fff !important; 19 | color: #000 !important; 20 | } 21 | 22 | /* for vertical events */ 23 | 24 | .fc-event-bg { 25 | display: none !important; 26 | } 27 | 28 | .fc-event .ui-resizable-handle { 29 | display: none !important; 30 | } 31 | 32 | 33 | -------------------------------------------------------------------------------- /input-scripts/fullcalendar/fullcalendar/gcal.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * FullCalendar v1.6.4 Google Calendar Plugin 3 | * Docs & License: http://arshaw.com/fullcalendar/ 4 | * (c) 2013 Adam Shaw 5 | */ 6 | 7 | (function($) { 8 | 9 | 10 | var fc = $.fullCalendar; 11 | var formatDate = fc.formatDate; 12 | var parseISO8601 = fc.parseISO8601; 13 | var addDays = fc.addDays; 14 | var applyAll = fc.applyAll; 15 | 16 | 17 | fc.sourceNormalizers.push(function(sourceOptions) { 18 | if (sourceOptions.dataType == 'gcal' || 19 | sourceOptions.dataType === undefined && 20 | (sourceOptions.url || '').match(/^(http|https):\/\/www.google.com\/calendar\/feeds\//)) { 21 | sourceOptions.dataType = 'gcal'; 22 | if (sourceOptions.editable === undefined) { 23 | sourceOptions.editable = false; 24 | } 25 | } 26 | }); 27 | 28 | 29 | fc.sourceFetchers.push(function(sourceOptions, start, end) { 30 | if (sourceOptions.dataType == 'gcal') { 31 | return transformOptions(sourceOptions, start, end); 32 | } 33 | }); 34 | 35 | 36 | function transformOptions(sourceOptions, start, end) { 37 | 38 | var success = sourceOptions.success; 39 | var data = $.extend({}, sourceOptions.data || {}, { 40 | 'start-min': formatDate(start, 'u'), 41 | 'start-max': formatDate(end, 'u'), 42 | 'singleevents': true, 43 | 'max-results': 9999 44 | }); 45 | 46 | var ctz = sourceOptions.currentTimezone; 47 | if (ctz) { 48 | data.ctz = ctz = ctz.replace(' ', '_'); 49 | } 50 | 51 | return $.extend({}, sourceOptions, { 52 | url: sourceOptions.url.replace(/\/basic$/, '/full') + '?alt=json-in-script&callback=?', 53 | dataType: 'jsonp', 54 | data: data, 55 | startParam: false, 56 | endParam: false, 57 | success: function(data) { 58 | var events = []; 59 | if (data.feed.entry) { 60 | $.each(data.feed.entry, function(i, entry) { 61 | var startStr = entry['gd$when'][0]['startTime']; 62 | var start = parseISO8601(startStr, true); 63 | var end = parseISO8601(entry['gd$when'][0]['endTime'], true); 64 | var allDay = startStr.indexOf('T') == -1; 65 | var url; 66 | $.each(entry.link, function(i, link) { 67 | if (link.type == 'text/html') { 68 | url = link.href; 69 | if (ctz) { 70 | url += (url.indexOf('?') == -1 ? '?' : '&') + 'ctz=' + ctz; 71 | } 72 | } 73 | }); 74 | if (allDay) { 75 | addDays(end, -1); // make inclusive 76 | } 77 | events.push({ 78 | id: entry['gCal$uid']['value'], 79 | title: entry['title']['$t'], 80 | url: url, 81 | start: start, 82 | end: end, 83 | allDay: allDay, 84 | location: entry['gd$where'][0]['valueString'], 85 | description: entry['content']['$t'] 86 | }); 87 | }); 88 | } 89 | var args = [events].concat(Array.prototype.slice.call(arguments, 1)); 90 | var res = applyAll(success, this, args); 91 | if ($.isArray(res)) { 92 | return res; 93 | } 94 | return events; 95 | } 96 | }); 97 | 98 | } 99 | 100 | 101 | // legacy 102 | fc.gcalFeed = function(url, sourceOptions) { 103 | return $.extend({}, sourceOptions, { url: url, dataType: 'gcal' }); 104 | }; 105 | 106 | 107 | })(jQuery); 108 | -------------------------------------------------------------------------------- /input-scripts/simple-scripts/function_function.js: -------------------------------------------------------------------------------- 1 | var _createClass = function () { 2 | function defineProperties(target, props) { 3 | for (var i = 0; i < props.length; i++) { 4 | var descriptor = props[i]; 5 | descriptor.enumerable = descriptor.enumerable || false; 6 | descriptor.configurable = true; 7 | if ("value" in descriptor) 8 | descriptor.writable = true; 9 | Object.defineProperty(target, descriptor.key, descriptor); 10 | } 11 | } 12 | return function (Constructor, protoProps, staticProps) { 13 | if (protoProps) 14 | defineProperties(Constructor.prototype, protoProps); 15 | 16 | if (staticProps) 17 | defineProperties(Constructor, staticProps); 18 | 19 | return Constructor; 20 | }; 21 | }(); 22 | 23 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 24 | 25 | var Animal = function() { 26 | function Animal(name, color) { 27 | _classCallCheck(this, Animal); 28 | this.name = name 29 | this.color = color 30 | } 31 | 32 | _createClass(Animal, [{ 33 | key: 'fn', 34 | get: function get() { 35 | return function() {return this.color;}; 36 | } 37 | }]); 38 | }(); 39 | 40 | var a = new Animal(); 41 | a.fn(); 42 | -------------------------------------------------------------------------------- /input-scripts/simple-scripts/functioncall-arithmetic.js: -------------------------------------------------------------------------------- 1 | var x = 5; 2 | 3 | function f(a) { 4 | return 2 * a; 5 | } 6 | 7 | y = f(x); 8 | f(2); 9 | 10 | function j(b) { 11 | return f(b); 12 | } 13 | 14 | function g(h, y) { 15 | j(4); 16 | return h(y); 17 | } 18 | 19 | g(f, x + 8); 20 | -------------------------------------------------------------------------------- /input-scripts/simple-scripts/jQuery.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | function jQuery(n) { 3 | var res = Object.create(jQuery.fn); 4 | var elts = document.getElementsByTagName(n); 5 | for(var i=0;i> 5, 59 | word_idx = x % 32; 60 | 61 | if (word_off >= a.length) 62 | return false; 63 | 64 | return !!(a[word_off] & (1 << word_idx)); 65 | } 66 | 67 | function createSingletonBitset(x) { 68 | var word_off = x >> 5, 69 | word_idx = x % 32; 70 | var a = new Array(word_off + 1); 71 | a[word_off] = (1 << word_idx); 72 | return a; 73 | } 74 | 75 | /** 76 | * Add number x to set a, and return the possibly modified a. 77 | */ 78 | function add(a, x) { 79 | if (typeof a === 'undefined') 80 | return x; 81 | 82 | if (typeof a === 'number') 83 | a = createSingletonBitset(a); 84 | 85 | var word_off = x >> 5, 86 | word_idx = x % 32; 87 | a[word_off] = (a[word_off] || 0) | (1 << word_idx); 88 | return a; 89 | } 90 | 91 | /** 92 | * Add all elements in set b to set a, returning the resulting set. 93 | * While set a may be modified, set b never is. 94 | */ 95 | function addAll(a, b) { 96 | if (typeof a === 'undefined') 97 | return copy(b); 98 | 99 | if (typeof b === 'undefined') 100 | return a; 101 | 102 | if (typeof b === 'number') 103 | return add(a, b); 104 | 105 | if (typeof a === 'number') 106 | return add(copy(b), a); 107 | 108 | // both a and b must be bitsets 109 | for (var i = 0; i < b.length; ++i) 110 | if (b[i]) 111 | a[i] = (a[i] || 0) | b[i]; 112 | return a; 113 | } 114 | 115 | function remove(a, x) { 116 | if (typeof a === 'undefined') 117 | return a; 118 | 119 | if (typeof a === 'number') 120 | return a === x ? void(0) : a; 121 | 122 | var word_off = x >> 5, 123 | word_idx = x % 32; 124 | a[word_off] = (a[word_off] || 0) & ~(1 << word_idx); 125 | return a; 126 | } 127 | 128 | function removeAll(a, b) { 129 | if (typeof a === 'undefined' || typeof b === 'undefined') 130 | return a; 131 | 132 | if (typeof a === 'number') 133 | return contains(b, a) ? void(0) : a; 134 | 135 | if (typeof b === 'number') 136 | return remove(a, b); 137 | 138 | a.forEach(function (w, i) { 139 | if (b[i]) 140 | a[i] = a[i] & ~b[i]; 141 | }); 142 | return a; 143 | } 144 | 145 | function copy(a) { 146 | if (typeof a === 'undefined' || typeof a === 'number') 147 | return a; 148 | 149 | return a.slice(0); 150 | } 151 | 152 | function iter(a, cb) { 153 | if (a) { 154 | if (typeof a === 'number') 155 | cb(a); 156 | else 157 | a.forEach(function (w, i) { 158 | for (var j = 0; j < 32; ++j) 159 | if (w & (1 << j)) 160 | cb(32 * i + j); 161 | }); 162 | } 163 | } 164 | 165 | function map(a, f) { 166 | if (a) { 167 | if (typeof a === 'number') 168 | return [f(a)]; 169 | else { 170 | var res = []; 171 | iter(a, function (x) { 172 | res[res.length] = f(x); 173 | }) 174 | return res; 175 | } 176 | } else { 177 | return []; 178 | } 179 | } 180 | 181 | function some(a, f) { 182 | if (a) { 183 | if (typeof a === 'number') 184 | return f(a); 185 | else 186 | for (var i = 0; i < a.length; ++i) 187 | if (a[i]) 188 | for (var j = 0; j < 32; ++j) 189 | if (a[i] & (1 << j)) 190 | if (f(32 * i + j)) 191 | return true; 192 | } 193 | return false; 194 | } 195 | 196 | function all(a, f) { 197 | if (a) { 198 | if (typeof a === 'number') 199 | return f(a); 200 | else 201 | for (var i = 0; i < a.length; ++i) 202 | if (a[i]) 203 | for (var j = 0; j < 32; ++j) 204 | if (a[i] & (1 << j)) 205 | if (!f(32 * i + j)) 206 | return false; 207 | } 208 | return true; 209 | } 210 | 211 | function fromArray(ary) { 212 | var a; 213 | ary.forEach(function (x) { 214 | a = add(a, x); 215 | }); 216 | return a; 217 | } 218 | 219 | function toArray(a) { 220 | return map(a, function f(x) { 221 | return x; 222 | }); 223 | } 224 | 225 | exports.copy = copy; 226 | exports.size = size; 227 | exports.contains = contains; 228 | exports.add = add; 229 | exports.addAll = addAll; 230 | exports.remove = remove; 231 | exports.removeAll = removeAll; 232 | exports.iter = iter; 233 | exports.map = map; 234 | exports.some = some; 235 | exports.all = all; 236 | exports.fromArray = fromArray; 237 | exports.toArray = toArray; 238 | return exports; 239 | }); 240 | -------------------------------------------------------------------------------- /src/callbackCounter.js: -------------------------------------------------------------------------------- 1 | if (typeof define !== 'function') { 2 | var define = require('amdefine')(module); 3 | } 4 | 5 | define(function(require, exports) { 6 | var astutil = require('./astutil'), 7 | graph = require('./graph'); 8 | 9 | exports.countCallbacks = function(ast) { 10 | var callbacks = [], callbackUses = 0; 11 | var enclosingFunctionParameters = []; 12 | var functionDeclarationParameter = 0, functionExpressionParameter = 0; 13 | astutil.visit(ast, function(node) { 14 | switch (node.type) { 15 | case 'CallExpression' : 16 | //FIND ARGUMENT AS PARAMETER IN ENCLOSING FUNCTION. 17 | var callee = node.callee, functionName = callee.name; 18 | var enclosingFunctionParameter = findEnclosingFunctionParameter(callee, functionName); 19 | if (enclosingFunctionParameter !== null) { 20 | callbackUses++; 21 | if (enclosingFunctionParameters.indexOf(enclosingFunctionParameter) === -1) { 22 | callbacks.push(node); 23 | enclosingFunctionParameters.push(enclosingFunctionParameter); 24 | } 25 | } 26 | break; 27 | 28 | case 'FunctionDeclaration' : 29 | functionDeclarationParameter += node.params.length; 30 | break; 31 | 32 | case 'FunctionExpression' : 33 | functionExpressionParameter += node.params.length; 34 | break; 35 | } 36 | }); 37 | var totalParameters = functionDeclarationParameter + functionExpressionParameter; 38 | var callbackPercentage = callbacks.length / totalParameters * 100; 39 | console.log("I found " + callbacks.length + " callbacks and " + callbackUses + " call back uses. In total we have " + functionDeclarationParameter + " function declaration parameters and " + functionExpressionParameter + " function expression parameters."); 40 | console.log("This makes a total of " + totalParameters + " parameters. Which means that (counting each function once as a callback) " + callbackPercentage + " percent of parameters are callbacks."); 41 | }; 42 | 43 | function findEnclosingFunctionParameter(node, functionName) { 44 | var enclosingFunction = node.attr.enclosingFunction; 45 | if (!enclosingFunction) { 46 | return null; 47 | } 48 | 49 | var matchingParameter = findFirst(enclosingFunction.params, isParameterWithName(functionName)); 50 | if (matchingParameter !== null) { 51 | return matchingParameter; 52 | } 53 | 54 | return findEnclosingFunctionParameter(enclosingFunction, functionName); 55 | } 56 | 57 | function findFirst(array, predicate) { 58 | var soughtElement = null; 59 | array.forEach(function(element) { 60 | if (predicate(element) === true) { 61 | soughtElement = element; 62 | return false; 63 | } 64 | }); 65 | return soughtElement; 66 | } 67 | 68 | function isParameterWithName(functionName) { 69 | return function(parameter) { 70 | return parameter.name === functionName; 71 | }; 72 | } 73 | 74 | return exports; 75 | }); -------------------------------------------------------------------------------- /src/callgraph.js: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2013 Max Schaefer 3 | * Copyright (c) 2018 Persper Foundation 4 | * 5 | * All rights reserved. This program and the accompanying materials 6 | * are made available under the terms of the Eclipse Public License v2.0 7 | * which accompanies this distribution, and is available at 8 | * http://www.eclipse.org/legal/epl-2.0. 9 | *******************************************************************************/ 10 | 11 | /* Module for extracting a call graph from a flow graph. */ 12 | if (typeof define !== 'function') { 13 | var define = require('amdefine')(module); 14 | } 15 | 16 | define(function (require, exports) { 17 | var graph = require('./graph'), 18 | dftc = require('./dftc.js'), 19 | flowgraph = require('./flowgraph'); 20 | 21 | // extract a call graph from a flow graph by collecting all function vertices that are inversely reachable from a callee vertex 22 | function extractCG(ast, flow_graph) { 23 | var edges = new graph.Graph(), 24 | escaping = [], unknown = []; 25 | 26 | var reach = dftc.reachability(flow_graph, function (nd) { 27 | return nd.type !== 'UnknownVertex'; 28 | }); 29 | 30 | /* fn is a flow graph node of type 'FuncVertex' */ 31 | function processFuncVertex(fn) { 32 | var r = reach.getReachable(fn); 33 | r.forEach(function (nd) { 34 | if (nd.type === 'UnknownVertex') 35 | escaping[escaping.length] = fn; 36 | else if (nd.type === 'CalleeVertex') 37 | edges.addEdge(nd, fn); 38 | }); 39 | } 40 | 41 | /* 42 | ast.attr.functions.forEach(function (fn) { 43 | processFuncVertex(flowgraph.funcVertex(fn)); 44 | }); 45 | */ 46 | 47 | flow_graph.iterNodes(function (nd) { 48 | if (nd.type === 'FuncVertex'){ 49 | processFuncVertex(nd); 50 | } 51 | }); 52 | 53 | flowgraph.getNativeVertices().forEach(processFuncVertex); 54 | 55 | var unknown_r = reach.getReachable(flowgraph.unknownVertex()); 56 | unknown_r.forEach(function (nd) { 57 | if (nd.type === 'CalleeVertex') 58 | unknown[unknown.length] = nd; 59 | }); 60 | 61 | return { 62 | edges: edges, 63 | escaping: escaping, 64 | unknown: unknown, 65 | fg: flow_graph 66 | }; 67 | } 68 | 69 | exports.extractCG = extractCG; 70 | return exports; 71 | }); 72 | -------------------------------------------------------------------------------- /src/dftc.js: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2013 Max Schaefer 3 | * Copyright (c) 2018 Persper Foundation 4 | * 5 | * All rights reserved. This program and the accompanying materials 6 | * are made available under the terms of the Eclipse Public License v2.0 7 | * which accompanies this distribution, and is available at 8 | * http://www.eclipse.org/legal/epl-2.0. 9 | *******************************************************************************/ 10 | 11 | /** 12 | * This module implements the depth-first transitive closure algorithm of 13 | * Ioannidis and Ramakrishnan ("Efficient Transitive Closure Algorithms", VLDB '88). 14 | */ 15 | 16 | if (typeof define !== 'function') { 17 | var define = require('amdefine')(module); 18 | } 19 | 20 | define(function (require, exports) { 21 | const { nd2str } = require('./graph'); 22 | 23 | exports.reachability = function (graph, nodePred) { 24 | let enum_nodes = new Array(); 25 | 26 | let nodes = graph.getNodes(); 27 | 28 | let n = nodes.length; 29 | 30 | const str2rid = {}; 31 | 32 | for (let i = 0; i < n; i++) { 33 | enum_nodes[i] = nodes[i]; 34 | str2rid[nd2str(nodes[i])] = i; 35 | } 36 | 37 | let visited = new Array(n).fill(0), 38 | visited2 = new Array(n).fill(0), 39 | popped = new Array(n).fill(0), 40 | globol = new Set(), 41 | m = [], 42 | t = []; 43 | 44 | for (let i = 0; i < n; i++) { 45 | m.push(new Set()); 46 | t.push(new Set()); 47 | } 48 | 49 | function visit1(i) { 50 | visited[i] = 1; 51 | 52 | if (!nodePred || nodePred(enum_nodes[i])) { 53 | let succ = graph.succ(enum_nodes[i]) 54 | 55 | for (let j= 0; j < succ.length; j++) { 56 | let index = str2rid[nd2str(succ[j])]; 57 | if (nodePred && !nodePred(succ[j])) 58 | continue; 59 | if (m[i].has(index) || t[i].has(index)) 60 | continue; 61 | 62 | if (visited[index] == 0) 63 | visit1(index); 64 | 65 | if (popped[index] == 1) { 66 | m[i] = new Set(m[i]) 67 | for (let elem of m[index].values()) { 68 | m[i].add(elem); 69 | } 70 | m[i].add(index); 71 | t[i] = new Set(t[i]) 72 | for (let elem of t[index].values()) 73 | t[i].add(elem); 74 | for (let elem of m[i].values()) 75 | t[i].delete(elem); 76 | } else { 77 | t[i] = new Set(t[i].add(index)); 78 | } 79 | } 80 | } 81 | 82 | if(t[i].has(i)) { 83 | if (t[i].size === 1) { 84 | m[i].add(i); 85 | t[i] = new Set(); 86 | globol = new Set(m[i]); 87 | visit2(i); 88 | } else { 89 | t[i].delete(i); 90 | m[i].add(i); 91 | } 92 | } 93 | 94 | popped[i] = 1; 95 | } 96 | 97 | function visit2(i) { 98 | visited2[i] = 1; 99 | 100 | if (!nodePred || nodePred(enum_nodes[i])) { 101 | let succ = graph.succ(enum_nodes[i]) 102 | 103 | for (let j= 0; j < succ.length; j++) { 104 | let index = str2rid[nd2str(succ[j])]; 105 | if (nodePred && !nodePred(succ[j])) 106 | return; 107 | if (visited2[index] == 0 && t[index].size !== 0) 108 | visit2(index); 109 | } 110 | } 111 | 112 | m[i] = new Set(globol); 113 | t[i] = new Set(); 114 | } 115 | return { 116 | getReachable: function (src) { 117 | const nodeStr = nd2str(src); 118 | if (!(nodeStr in str2rid)) { 119 | enum_nodes.push(src); 120 | visited.push(0); 121 | visited2.push(0); 122 | popped.push(0); 123 | m.push(new Set()); 124 | t.push(new Set()); 125 | str2rid[nodeStr] = enum_nodes.length - 1; 126 | } 127 | const src_id = str2rid[nodeStr]; 128 | 129 | if (visited[src_id] == 0) 130 | visit1(src_id); 131 | 132 | var tc = new Set(m[src_id]); 133 | for (let elem of t[src_id].values()) 134 | tc.add(elem); 135 | 136 | let ret = new Array(); 137 | for (let elem of tc.values()) { 138 | ret.push(enum_nodes[elem]); 139 | } 140 | 141 | return ret; 142 | }, 143 | iterReachable: function (src, cb) { 144 | const nodeStr = nd2str(src); 145 | if (!(nodeStr in str2rid)) { 146 | enum_nodes.push(src); 147 | visited.push(0); 148 | visited2.push(0); 149 | popped.push(0); 150 | m.push(new Set()); 151 | t.push(new Set()); 152 | str2rid[nodeStr] = enum_nodes.length - 1; 153 | } 154 | const src_id = str2rid[nodeStr]; 155 | 156 | if (visited[src_id] == 0) 157 | visit1(src_id); 158 | 159 | var tc = new Set(m[src_id]); 160 | for (let elem of t[src_id].values()) 161 | tc.add(elem); 162 | 163 | for (let elem of tc.values()) 164 | cb(enum_nodes[elem]); 165 | }, 166 | reaches: function (src, dest) { 167 | const src_id = str2rid[nd2str(src)]; 168 | const dest_id = str2rid[nd2str(dest)]; 169 | 170 | if (visited[src_id] == 0) 171 | visit1(src_id); 172 | 173 | var tc = new Set(m[src_id]); 174 | for (let elem of t[src_id].values()) 175 | tc.add(elem); 176 | 177 | return tc.has(dest_id); 178 | } 179 | }; 180 | }; 181 | return exports; 182 | }); 183 | -------------------------------------------------------------------------------- /src/diagnostics.js: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2013 Max Schaefer 3 | * Copyright (c) 2018 Persper Foundation 4 | * 5 | * All rights reserved. This program and the accompanying materials 6 | * are made available under the terms of the Eclipse Public License v2.0 7 | * which accompanies this distribution, and is available at 8 | * http://www.eclipse.org/legal/epl-2.0. 9 | *******************************************************************************/ 10 | 11 | /* Module for converting a graph into DOT format. */ 12 | if (typeof define !== 'function') { 13 | var define = require('amdefine')(module); 14 | } 15 | 16 | define(function (require, exports) { 17 | var graph = require('./graph'), 18 | fs = require('fs'); 19 | 20 | graph.Graph.prototype.dotify = function () { 21 | var res = ""; 22 | res += "digraph FG {\n"; 23 | this.iter(function (from, to) { 24 | res += ' "' + from.attr.pp() + '" -> "' + to.attr.pp() + '";\n'; 25 | }); 26 | res += "}\n"; 27 | return res; 28 | }; 29 | 30 | graph.Graph.prototype.writeDOTFile = function (fn) { 31 | fs.writeFileSync(fn, this.dotify()); 32 | }; 33 | 34 | return exports; 35 | }); 36 | -------------------------------------------------------------------------------- /src/graph.js: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2013 Max Schaefer 3 | * Copyright (c) 2018 Persper Foundation 4 | * 5 | * All rights reserved. This program and the accompanying materials 6 | * are made available under the terms of the Eclipse Public License v2.0 7 | * which accompanies this distribution, and is available at 8 | * http://www.eclipse.org/legal/epl-2.0. 9 | *******************************************************************************/ 10 | 11 | /* Graphs represented using adjacency sets. */ 12 | if (typeof define !== 'function') { 13 | var define = require('amdefine')(module); 14 | } 15 | 16 | define(function (require, exports) { 17 | const { LinkedList } = require('./linkedList'); 18 | 19 | class BasicGraph { 20 | constructor() { 21 | this._pred = {}; 22 | this._succ = {}; 23 | } 24 | 25 | addNode(node) { 26 | this._pred[node] = this.pred(node); 27 | this._succ[node] = this.succ(node); 28 | } 29 | 30 | // Remove all associated edges 31 | // Does nothing if the node doesn not exist 32 | removeNode(node) { 33 | if (this._pred[node]) { 34 | for (let p of this._pred[node]) { 35 | this._succ[p].remove(node); 36 | } 37 | delete this._pred[node]; 38 | } 39 | if (this._succ[node]) { 40 | for (let s of this._succ[node]) { 41 | this._pred[s].remove(node); 42 | } 43 | delete this._succ[node]; 44 | } 45 | } 46 | 47 | pred(node) { 48 | return this._pred[node] || new LinkedList(); 49 | } 50 | 51 | succ(node) { 52 | return this._succ[node] || new LinkedList(); 53 | } 54 | 55 | addEdge(u, v) { 56 | this.addNode(u); 57 | this.addNode(v); 58 | this._succ[u].add(v); 59 | this._pred[v].add(u); 60 | } 61 | 62 | // Does not remove the nodes 63 | // Does nothing if the edge does not exist 64 | removeEdge(u, v) { 65 | if (this._succ[u]) { 66 | this._succ[u].remove(v); 67 | } 68 | if (this._pred[v]) { 69 | this._pred[v].remove(u); 70 | } 71 | } 72 | 73 | nodes() { 74 | return Object.keys(this._pred); 75 | } 76 | 77 | serialize() { 78 | let serialized = { 79 | nodes: this.nodes().map((id) => { 80 | return {id: id}; 81 | }), 82 | links: [] 83 | } 84 | 85 | serialized.nodes.forEach((node) => { 86 | let source = node.id; 87 | for (let target of this._succ[source]) { 88 | serialized.links.push({ 89 | source: source, 90 | target: target 91 | }); 92 | } 93 | }); 94 | return serialized; 95 | } 96 | } 97 | 98 | function nodeToString(nd) { 99 | return nd.attr.pp(); 100 | } 101 | 102 | var cf = nodeToString; 103 | 104 | function Graph() { 105 | this.graph = new BasicGraph(); 106 | this.node_pairings = {}; 107 | this.edge_annotations = {}; 108 | } 109 | 110 | /* Adds the node to the graph if not already there */ 111 | Graph.prototype.addNode = function (nd) { 112 | if (this.hasNode(nd)) 113 | return; 114 | 115 | this.node_pairings[cf(nd)] = nd; 116 | this.graph.addNode(cf(nd)); 117 | } 118 | 119 | Graph.prototype.addEdge = function (from, to, annote) { 120 | this.addNode(from); 121 | this.addNode(to); 122 | 123 | this.graph.addEdge(cf(from), cf(to)); 124 | 125 | if (annote !== undefined) 126 | this.edge_annotations[cf(from) + ' -> ' + cf(to)] = annote; 127 | }; 128 | 129 | Graph.prototype.addEdges = function (from, tos, annotations) { 130 | for (var i = 0; i < tos.length; ++i) 131 | if (annotations !== undefined) 132 | this.addEdge(from, tos[i], annotations[i]); 133 | else 134 | this.addEdge(from, tos[i]); 135 | }; 136 | 137 | Graph.prototype.iter = function (cb) { 138 | const nodes = this.graph.nodes(); 139 | for (let u of nodes) { 140 | for (let v of this.graph.succ(u)) { 141 | let u_nd = this.node_pairings[u]; 142 | let v_nd = this.node_pairings[v]; 143 | cb(u_nd, v_nd); 144 | } 145 | } 146 | }; 147 | 148 | Graph.prototype.hasEdge = function (from, to) { 149 | return this.graph.succ(cf(from)).has(cf(to)); 150 | }; 151 | 152 | Graph.prototype.succ = function (nd) { 153 | let succ = this.graph.succ(cf(nd)); 154 | let lst = []; 155 | for (let s of succ) 156 | lst.push(this.node_pairings[s]) 157 | return lst; 158 | } 159 | 160 | Graph.prototype.hasNode = function (nd) { 161 | return cf(nd) in this.node_pairings; 162 | } 163 | 164 | /* Remove (from , to), return false if edge doesn't exist */ 165 | Graph.prototype.removeEdge = function (from, to) { 166 | if (this.hasNode(from) && this.hasNode(to) && this.hasEdge(from, to)){ 167 | this.graph.removeEdge(cf(from), cf(to)) 168 | return true; 169 | } 170 | return false; 171 | }; 172 | 173 | /* Remove a node and all associated edges from graph */ 174 | Graph.prototype.removeNode = function (nd) { 175 | if (this.hasNode(nd)) { 176 | this.graph.removeNode(cf(nd)); 177 | delete this.node_pairings[cf(nd)]; 178 | return true; 179 | } 180 | return false; 181 | }; 182 | 183 | Graph.prototype.iterNodes = function (cb) { 184 | let nodes = this.graph.nodes(); 185 | for (let i = 0; i < nodes.length; i++) { 186 | let cfn = nodes[i]; 187 | let n = this.node_pairings[cfn]; 188 | cb(n); 189 | } 190 | }; 191 | 192 | Graph.prototype.getNodes = function() { 193 | let str_nodes = this.graph.nodes(); 194 | let nodes = []; 195 | for (let i = 0; i < str_nodes.length; i++) 196 | nodes.push(this.node_pairings[str_nodes[i]]); 197 | 198 | return nodes; 199 | }; 200 | 201 | /* Get enclosingFile of a node in flow graph by querying its associated AST node */ 202 | function getEnclosingFile (nd) { 203 | if (nd.hasOwnProperty('node')) { 204 | return nd.node.attr.enclosingFile; 205 | } else if (nd.hasOwnProperty('call')) { 206 | return nd.call.attr.enclosingFile; 207 | } else if (nd.hasOwnProperty('func')) { 208 | return nd.func.attr.enclosingFile; 209 | } else { 210 | // Native, Prop and Unknown vertices 211 | return null; 212 | } 213 | } 214 | 215 | class FlowGraph extends Graph { 216 | constructor() { 217 | super(); 218 | this._fileToNodes = {}; 219 | } 220 | 221 | addEdge(from, to, annote) { 222 | super.addEdge(from, to, annote); 223 | this._updateFileToNodes(from); 224 | this._updateFileToNodes(to); 225 | } 226 | 227 | _updateFileToNodes(fgNode) { 228 | const enclosingFile = getEnclosingFile(fgNode); 229 | if (enclosingFile === null) 230 | return; 231 | if (!(enclosingFile in this._fileToNodes)) 232 | this._fileToNodes[enclosingFile] = new Set(); 233 | this._fileToNodes[enclosingFile].add(fgNode); 234 | } 235 | 236 | removeNodesInFile(fileName) { 237 | if (fileName in this._fileToNodes) { 238 | for (let fgNode of this._fileToNodes[fileName]) { 239 | super.removeNode(fgNode); 240 | } 241 | } 242 | else { 243 | console.log("WARNING: fileName doesn't exist in _fileToNodes."); 244 | } 245 | } 246 | } 247 | 248 | 249 | exports.Graph = Graph; 250 | exports.FlowGraph = FlowGraph; 251 | exports.nd2str = nodeToString; 252 | return exports; 253 | }); 254 | -------------------------------------------------------------------------------- /src/input-2.js: -------------------------------------------------------------------------------- 1 | function aap() { 2 | noot(); 3 | } 4 | 5 | function noot() { 6 | var mies = wim; 7 | mies(); 8 | } 9 | 10 | function wim() { 11 | var output = zus(1, 2); 12 | } 13 | 14 | function zus(x, y) { 15 | x + y; 16 | jet(teun); 17 | (function(num) { 1 + num; })(1); 18 | } 19 | 20 | function teun() { 21 | 1 - 1; 22 | } 23 | 24 | function jet(func) { 25 | func(); 26 | } -------------------------------------------------------------------------------- /src/linkedList.js: -------------------------------------------------------------------------------- 1 | 2 | class ListNode { 3 | constructor(ele) { 4 | this._ele = ele; 5 | this._next = null; 6 | } 7 | 8 | set next(value) { 9 | this._next = value; 10 | } 11 | 12 | get next() { 13 | return this._next; 14 | } 15 | 16 | get element() { 17 | return this._ele; 18 | } 19 | } 20 | 21 | class LinkedList { 22 | constructor() { 23 | this._head = null; 24 | this._size = 0; 25 | } 26 | 27 | // simply adds ele to the head of the list 28 | add(ele) { 29 | let lnode = new ListNode(ele); 30 | lnode.next = this._head; 31 | this._head = lnode; 32 | this._size++; 33 | } 34 | 35 | // returns the removed element, or if its not found it returns -1 36 | remove(ele) { 37 | let current = this._head; 38 | let prev = null; 39 | while (current !== null) { 40 | if (current.element === ele) { 41 | if (prev === null) { 42 | this._head = current.next; 43 | } 44 | else { 45 | prev.next = current.next; 46 | } 47 | this._size--; 48 | return current.element; 49 | } 50 | prev = current; 51 | current = current.next; 52 | } 53 | return -1; 54 | } 55 | 56 | has(ele) { 57 | let current = this._head; 58 | while (current !== null) { 59 | if (current.element === ele) 60 | return true; 61 | current = current.next; 62 | } 63 | return false; 64 | } 65 | 66 | isEmpty() { 67 | return this._size === 0; 68 | } 69 | 70 | get size() { 71 | return this._size; 72 | } 73 | 74 | [Symbol.iterator]() { 75 | let value = null; 76 | let current = this._head; 77 | const iterator = { 78 | next: () => { 79 | if (current !== null) { 80 | value = current; 81 | current = current.next; 82 | return {value: value.element, done: false} 83 | } 84 | else { 85 | return {value: undefined, done: true}; 86 | } 87 | } 88 | } 89 | return iterator; 90 | } 91 | } 92 | 93 | module.exports.LinkedList = LinkedList; 94 | -------------------------------------------------------------------------------- /src/natives.js: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2013 Max Schaefer 3 | * Copyright (c) 2018 Persper Foundation 4 | * 5 | * All rights reserved. This program and the accompanying materials 6 | * are made available under the terms of the Eclipse Public License v2.0 7 | * which accompanies this distribution, and is available at 8 | * http://www.eclipse.org/legal/epl-2.0. 9 | *******************************************************************************/ 10 | 11 | /* Module for adding standard library/DOM modelling to flow graph. */ 12 | 13 | if (typeof define !== 'function') { 14 | var define = require('amdefine')(module); 15 | } 16 | 17 | define(function (require, exports) { 18 | var flowgraph = require('./flowgraph'), 19 | nativeFlows = require('./harness').nativeFlows; 20 | 21 | function addNativeFlowEdges(flow_graph) { 22 | for (var native in nativeFlows) { 23 | if (!nativeFlows.hasOwnProperty(native)) 24 | continue; 25 | var target = nativeFlows[native]; 26 | flow_graph.addEdge( 27 | flowgraph.nativeVertex(native), 28 | flowgraph.propVertex({ 29 | type: 'Identifier', 30 | name: target 31 | }) 32 | ); 33 | } 34 | return flow_graph; 35 | } 36 | 37 | exports.addNativeFlowEdges = addNativeFlowEdges; 38 | return exports; 39 | }); 40 | -------------------------------------------------------------------------------- /src/numset.js: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2013 Max Schaefer 3 | * Copyright (c) 2018 Persper Foundation 4 | * 5 | * All rights reserved. This program and the accompanying materials 6 | * are made available under the terms of the Eclipse Public License v2.0 7 | * which accompanies this distribution, and is available at 8 | * http://www.eclipse.org/legal/epl-2.0. 9 | *******************************************************************************/ 10 | 11 | /** 12 | * Facade module implementing sets of non-negative integers. 13 | * Allows to easily switch to a different implementation. 14 | */ 15 | 16 | if (typeof define !== 'function') { 17 | var define = require('amdefine')(module); 18 | } 19 | 20 | define(function (require, exports) { 21 | // var impl = require('./olist'); 22 | var impl = require('./set'); 23 | 24 | for (var p in impl) 25 | exports[p] = impl[p]; 26 | 27 | return exports; 28 | }); 29 | -------------------------------------------------------------------------------- /src/olist.js: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2013 Max Schaefer 3 | * Copyright (c) 2018 Persper Foundation 4 | * 5 | * All rights reserved. This program and the accompanying materials 6 | * are made available under the terms of the Eclipse Public License v2.0 7 | * which accompanies this distribution, and is available at 8 | * http://www.eclipse.org/legal/epl-2.0. 9 | *******************************************************************************/ 10 | 11 | /** 12 | * Implementation of sets of numbers as sorted lists. Singleton sets 13 | * are represented as single numbers, the empty set as undefined. 14 | */ 15 | 16 | if (typeof define !== 'function') { 17 | var define = require('amdefine')(module); 18 | } 19 | 20 | define(function (require, exports) { 21 | function size(a) { 22 | if (typeof a === 'undefined') 23 | return 0; 24 | 25 | if (typeof a === 'number') 26 | return 1; 27 | 28 | return a.length; 29 | } 30 | 31 | /** 32 | * Check whether set a contains number x. 33 | */ 34 | function contains(a, x) { 35 | if (typeof a === 'undefined') 36 | return false; 37 | 38 | if (typeof a === 'number') 39 | return a === x; 40 | 41 | var lo = 0, hi = a.length - 1, mid, elt; 42 | while (lo <= hi) { 43 | mid = (lo + hi) >> 1; 44 | elt = a[mid]; 45 | if (elt === x) { 46 | return true; 47 | } else if (elt < x) { 48 | lo = mid + 1; 49 | } else { 50 | hi = mid - 1; 51 | } 52 | } 53 | 54 | return false; 55 | } 56 | 57 | /** 58 | * Add number x to set a, and return the possibly modified a. 59 | */ 60 | function add(a, x) { 61 | if (typeof a === 'undefined') 62 | return x; 63 | 64 | if (typeof a === 'number') { 65 | if (a < x) 66 | return [a, x]; 67 | if (a > x) 68 | return [x, a]; 69 | return a; 70 | } 71 | 72 | var lo = 0, hi = a.length - 1, mid, elt; 73 | while (lo <= hi) { 74 | mid = (lo + hi) >> 1; 75 | elt = a[mid]; 76 | if (elt < x) { 77 | lo = mid + 1; 78 | } else if (elt > x) { 79 | hi = mid - 1; 80 | } else { 81 | return a; 82 | } 83 | } 84 | a.splice(lo, 0, x); 85 | return a; 86 | } 87 | 88 | /** 89 | * Add all elements in set b to set a, returning the resulting set. 90 | * While set a may be modified, set b never is. 91 | */ 92 | function addAll(a, b) { 93 | if (typeof a === 'undefined') 94 | return copy(b); 95 | if (typeof b === 'undefined') 96 | return a; 97 | 98 | if (typeof a === 'number' && typeof b === 'object') 99 | return add(b.slice(0), a); 100 | 101 | // 'a' must be an array; check 'b' 102 | var l1 = a.length; 103 | if (l1 === 0) 104 | return copy(b); 105 | 106 | if (typeof b === 'number') { 107 | return add(a, b); 108 | } else { 109 | var l2 = b.length; 110 | if (l2 === 0) 111 | return a; 112 | 113 | var res = new Array(l1 + l2); 114 | var i = 0, j = 0, k = 0; 115 | while (i < l1 || j < l2) { 116 | while (i < l1 && (j >= l2 || a[i] <= b[j])) 117 | res[k++] = a[i++]; 118 | while (k > 0 && j < l2 && b[j] === res[k - 1]) 119 | ++j; 120 | while (j < l2 && (i >= l1 || b[j] < a[i])) 121 | res[k++] = b[j++]; 122 | } 123 | res.length = k; 124 | return res; 125 | } 126 | } 127 | 128 | /* Pitfall: this remove is inplace for array, not inplace for number */ 129 | function remove(a, x) { 130 | if (typeof a === 'undefined') 131 | return a; 132 | 133 | if (typeof a === 'number') 134 | return a === x ? void(0) : a; 135 | 136 | var lo = 0, hi = a.length - 1, mid, elt; 137 | 138 | if (lo === hi) 139 | return a[0] === x ? void(0) : a; 140 | 141 | while (lo <= hi) { 142 | mid = (lo + hi) >> 1; 143 | elt = a[mid]; 144 | if (elt < x) { 145 | lo = mid + 1; 146 | } else if (elt > x) { 147 | hi = mid - 1; 148 | } else { 149 | a.splice(mid, 1); 150 | return a; 151 | } 152 | } 153 | return a; 154 | } 155 | 156 | function removeAll(a, b) { 157 | if (typeof a === 'undefined' || typeof b === 'undefined') 158 | return a; 159 | 160 | if (typeof a === 'number') 161 | return contains(b, a) ? void(0) : a; 162 | 163 | if (typeof b === 'number') 164 | return remove(a, b); 165 | 166 | var i = 0, j = 0, k = 0, m = a.length, n = b.length; 167 | while (i < m && j < n) { 168 | while (i < m && a[i] < b[j]) 169 | a[k++] = a[i++]; 170 | 171 | if (i < m && a[i] === b[j]) 172 | ++i; 173 | 174 | if (i < m) 175 | while (j < n && a[i] > b[j]) 176 | ++j; 177 | } 178 | while (i < m) 179 | a[k++] = a[i++]; 180 | 181 | if (k) { 182 | a.length = k; 183 | return a; 184 | } else { 185 | return void(0); 186 | } 187 | } 188 | 189 | function copy(a) { 190 | if (typeof a === 'undefined' || typeof a === 'number') 191 | return a; 192 | 193 | return a.slice(0); 194 | } 195 | 196 | function iter(a, cb) { 197 | if (a) { 198 | if (typeof a === 'number') 199 | cb(a); 200 | else 201 | a.forEach(cb); 202 | } 203 | } 204 | 205 | function map(a, f) { 206 | if (a) { 207 | if (typeof a === 'number') 208 | return [f(a)]; 209 | else 210 | return a.map(f); 211 | } else { 212 | return []; 213 | } 214 | } 215 | 216 | function some(a, f) { 217 | var r = false; 218 | if (a) { 219 | if (typeof a === 'number') 220 | return f(a); 221 | else 222 | for (var i = 0, l = a.length; i < l; ++i) { 223 | r = f(a); 224 | if (r) 225 | return r; 226 | } 227 | } 228 | return r; 229 | } 230 | 231 | function all(a, f) { 232 | var r = true; 233 | if (a) { 234 | if (typeof a === 'number') 235 | return f(a); 236 | else 237 | for (var i = 0, l = a.length; i < l; ++i) { 238 | r = f(a); 239 | if (!r) 240 | return r; 241 | } 242 | } 243 | return r; 244 | } 245 | 246 | function fromArray(ary) { 247 | var a; 248 | ary.forEach(function (x) { 249 | a = add(a, x); 250 | }); 251 | return a; 252 | } 253 | 254 | function toArray(a) { 255 | return map(a, function f(x) { 256 | return x; 257 | }); 258 | } 259 | 260 | exports.copy = copy; 261 | exports.size = size; 262 | exports.contains = contains; 263 | exports.add = add; 264 | exports.addAll = addAll; 265 | exports.remove = remove; 266 | exports.removeAll = removeAll; 267 | exports.iter = iter; 268 | exports.map = map; 269 | exports.some = some; 270 | exports.all = all; 271 | exports.fromArray = fromArray; 272 | exports.toArray = toArray; 273 | return exports; 274 | }); 275 | -------------------------------------------------------------------------------- /src/pessimistic.js: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2013 Max Schaefer 3 | * Copyright (c) 2018 Persper Foundation 4 | * 5 | * All rights reserved. This program and the accompanying materials 6 | * are made available under the terms of the Eclipse Public License v2.0 7 | * which accompanies this distribution, and is available at 8 | * http://www.eclipse.org/legal/epl-2.0. 9 | *******************************************************************************/ 10 | 11 | /* Pessimistic call graph builder. */ 12 | if (typeof define !== 'function') { 13 | var define = require('amdefine')(module); 14 | } 15 | 16 | define(function (require, exports) { 17 | var graph = require('./graph'), 18 | natives = require('./natives'), 19 | flowgraph = require('./flowgraph'), 20 | callgraph = require('./callgraph'); 21 | 22 | function addOneShotEdges(ast, fg) { 23 | // set up flow for one-shot calls 24 | ast.attr.functions.forEach(function (fn) { 25 | var parent = fn.attr.parent, 26 | childProp = fn.attr.childProp; 27 | 28 | if (childProp === 'callee' && parent && 29 | (parent.type === 'CallExpression' || parent.type === 'NewExpression')) { 30 | // one-shot closure 31 | parent.attr.oneshot = true; 32 | for (var i = 0, nargs = parent.arguments.length; i < nargs; ++i) { 33 | if (i >= fn.params.length) 34 | break; 35 | fg.addEdge(flowgraph.argVertex(parent, i + 1), flowgraph.parmVertex(fn, i + 1)); 36 | } 37 | fg.addEdge(flowgraph.retVertex(fn), flowgraph.resVertex(parent)); 38 | } else { 39 | // not a one-shot closure 40 | for (var i = 0, nparms = fn.params.length; i <= nparms; ++i) 41 | fg.addEdge(flowgraph.unknownVertex(), flowgraph.parmVertex(fn, i)); 42 | fg.addEdge(flowgraph.retVertex(fn), flowgraph.unknownVertex()); 43 | } 44 | }); 45 | 46 | // set up flow for all other calls 47 | ast.attr.calls.forEach(function (call) { 48 | if (!call.attr.oneshot) 49 | for (var i = 0, nargs = call.arguments.length; i <= nargs; ++i) 50 | fg.addEdge(flowgraph.argVertex(call, i), flowgraph.unknownVertex()); 51 | fg.addEdge(flowgraph.unknownVertex(), flowgraph.resVertex(call)); 52 | }); 53 | } 54 | 55 | function buildCallGraph(ast, noOneShot) { 56 | var fg = new graph.FlowGraph(); 57 | natives.addNativeFlowEdges(fg); 58 | if (!noOneShot) 59 | addOneShotEdges(ast, fg); 60 | flowgraph.addIntraproceduralFlowGraphEdges(ast, fg); 61 | return callgraph.extractCG(ast, fg); 62 | } 63 | 64 | exports.buildCallGraph = buildCallGraph; 65 | return exports; 66 | }); 67 | -------------------------------------------------------------------------------- /src/requireJsGraph.js: -------------------------------------------------------------------------------- 1 | if (typeof define !== 'function') { 2 | var define = require('amdefine')(module); 3 | } 4 | 5 | define(function(require, exports) { 6 | 7 | var assert = require('assert'), 8 | astutil = require('./astutil'), 9 | _ = require('./underscore'), 10 | fs = require('fs'); 11 | 12 | 13 | function makeRequireJsGraph(ast) { 14 | assert.equal(1, ast.programs.length, "Can only have one starting point at the moment."); 15 | 16 | var rx = /^.*\\(.+\\)*(.+)\.(.+)$/g; 17 | var regexParse = rx.exec(ast.programs[0].attr.filename); 18 | var partialFileName = regexParse[2] + ".js", 19 | fileName = "./" + partialFileName, 20 | folder = regexParse[0].split(/[a-zA-Z]+\.js/)[0].replace(/\/$/, "\\"); 21 | var dependencyGraph = []; 22 | astutil.visit(ast, function(node) { 23 | switch (node.type) { 24 | case 'CallExpression' : 25 | if (node.callee.name === "define" || node.callee.name === "require") { 26 | var dependencies = [], argument = node.arguments[0]; 27 | if (argument.type === "ArrayExpression") { 28 | argument.elements.forEach(function(element) { 29 | dependencies.push(element.value + ".js"); 30 | }); 31 | } else if (argument.type === "Literal") { 32 | dependencies.push(argument.value + ".js"); 33 | } 34 | dependencies.forEach(function(dependency) { 35 | dependencyGraph.push(new Dependency(fileName, dependency)); 36 | }); 37 | } 38 | break; 39 | } 40 | }); 41 | dependencyGraph.map(function(dep){return dep.to}).forEach(function(outgoingDep) { 42 | var normOutgoingDep = outgoingDep.replace(/^.\//, ""); 43 | normOutgoingDep = normOutgoingDep.replace(/^\//, ""); 44 | normOutgoingDep = normOutgoingDep.replace(/\//, "\\"); 45 | var newStart = folder + normOutgoingDep; 46 | if (fs.existsSync(newStart)) { 47 | var referencedAST = astutil.astFromFiles([newStart]); 48 | dependencyGraph = dependencyGraph.concat(makeRequireJsGraph(referencedAST)) 49 | } 50 | }); 51 | return _.uniq(dependencyGraph, function(edge) { 52 | return edge.toString(); 53 | }); 54 | } 55 | 56 | function Dependency(from, to) { 57 | this.from = from; 58 | this.to = to; 59 | 60 | this.toString = function() { 61 | return removeLeadingPointSlash(this.from) + " -> " + removeLeadingPointSlash(this.to); 62 | }; 63 | 64 | function removeLeadingPointSlash(path) { 65 | return path.replace(/^\.?\//, ""); 66 | } 67 | } 68 | exports.makeRequireJsGraph = makeRequireJsGraph; 69 | return exports; 70 | }); -------------------------------------------------------------------------------- /src/runner.js: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2013 Max Schaefer 3 | * Copyright (c) 2018 Persper Foundation 4 | * 5 | * All rights reserved. This program and the accompanying materials 6 | * are made available under the terms of the Eclipse Public License v2.0 7 | * which accompanies this distribution, and is available at 8 | * http://www.eclipse.org/legal/epl-2.0. 9 | *******************************************************************************/ 10 | if (typeof define !== 'function') { 11 | var define = require('amdefine')(module); 12 | } 13 | define(function (require, exports) { 14 | const bindings = require('./bindings'), 15 | astutil = require('./astutil'), 16 | pessimistic = require('./pessimistic'), 17 | semioptimistic = require('./semioptimistic'), 18 | callbackCounter = require('./callbackCounter'), 19 | requireJsGraph = require('./requireJsGraph'), 20 | path = require('path'), 21 | fs = require('fs'), 22 | utils = require('./utils'), 23 | JSONStream = require('JSONStream'); 24 | this.args = null; 25 | this.files = null; 26 | this.consoleOutput = null; 27 | 28 | Array.prototype.remove = function () { 29 | var what, a = arguments, L = a.length, ax; 30 | while (L && this.length) { 31 | what = a[--L]; 32 | while ((ax = this.indexOf(what)) !== -1) { 33 | this.splice(ax, 1); 34 | } 35 | } 36 | return this; 37 | }; 38 | 39 | let addNode = function (edge, v) { 40 | if (v.type === 'CalleeVertex') { 41 | let nd = v.call; 42 | edge.label = astutil.encFuncName(nd.attr.enclosingFunction); 43 | edge.file = nd.attr.enclosingFile; 44 | edge.start = {row: nd.loc.start.line, column: nd.loc.start.column}; 45 | edge.end = {row: nd.loc.end.line, column: nd.loc.end.column}; 46 | edge.range = {start: nd.range[0], end: nd.range[1]}; 47 | return edge; 48 | } 49 | if (v.type === 'FuncVertex') { 50 | edge.label = astutil.funcname(v.func); 51 | edge.file = v.func.attr.enclosingFile; 52 | edge.start = {row: v.func.loc.start.line, column: v.func.loc.start.column}; 53 | edge.end = {row: v.func.loc.end.line, column: v.func.loc.end.column}; 54 | edge.range = {start: v.func.range[0], end: v.func.range[1]}; 55 | return edge; 56 | } 57 | if (v.type === 'NativeVertex') { 58 | //'Math_log' (Native) 59 | edge.label = v.name; 60 | edge.file = "Native"; 61 | edge.start.row = null; 62 | edge.end.row = null; 63 | edge.start.column = null; 64 | edge.end.column = null; 65 | edge.range = {start: null, end: null}; 66 | return edge; 67 | } 68 | throw new Error("strange vertex: " + v); 69 | }; 70 | 71 | let buildBinding = function (call, fn) { 72 | let edge = { 73 | source: { 74 | label: null, 75 | file: null, 76 | start: {row: null, column: null}, 77 | end: {row: null, column: null}, 78 | range: {start: null, end: null} 79 | }, 80 | target: { 81 | label: null, 82 | file: null, 83 | start: {row: null, column: null}, 84 | end: {row: null, column: null}, 85 | range: {start: null, end: null} 86 | } 87 | }; 88 | addNode(edge.source, call); 89 | addNode(edge.target, fn); 90 | return edge; 91 | }; 92 | 93 | let pp = function (v) { 94 | if (v.type === 'CalleeVertex') 95 | return '\'' + astutil.encFuncName(v.call.attr.enclosingFunction) + '\' (' + astutil.ppPos(v.call) + ')'; 96 | if (v.type === 'FuncVertex') 97 | return '\'' + astutil.funcname(v.func) + '\' (' + astutil.ppPos(v.func) + ')'; 98 | if (v.type === 'NativeVertex') 99 | return '\'' + v.name + '\' (Native)'; 100 | throw new Error("strange vertex: " + v); 101 | }; 102 | 103 | let build = function () { 104 | let args = this.args; 105 | let files = this.files; 106 | let consoleOutput = this.consoleOutput; 107 | 108 | let filter = this.filter; 109 | 110 | if (filter !== undefined && filter.length > 0) { 111 | let filteredfiles = []; 112 | files.forEach(function (file) { 113 | filteredfiles.push(file); 114 | filter.forEach(function (elem) { 115 | let trunk = elem.substr(1).trim(); 116 | let expression = new RegExp(trunk, "gm"); 117 | let result = expression.test(file); 118 | 119 | if (result && elem.startsWith('-')) { 120 | filteredfiles.remove(file); 121 | } 122 | 123 | if (result && elem.startsWith('+')) { 124 | filteredfiles.push(file); 125 | } 126 | 127 | }); 128 | }); 129 | files = Array.from(new Set(filteredfiles)); 130 | } 131 | 132 | args.strategy = args.strategy || 'ONESHOT'; 133 | if (!args.strategy.match(/^(NONE|ONESHOT|DEMAND|FULL)$/)) { 134 | process.exit(-1); 135 | } 136 | if (args.strategy === 'FULL') { 137 | console.warn('strategy FULL not implemented yet; using DEMAND instead'); 138 | args.strategy = 'DEMAND'; 139 | } 140 | if (args.time) console.time("parsing "); 141 | var ast = astutil.astFromFiles(files); 142 | if (args.time) console.timeEnd("parsing "); 143 | 144 | if (args.time) console.time("bindings "); 145 | bindings.addBindings(ast); 146 | if (args.time) console.timeEnd("bindings "); 147 | 148 | if (args.time) console.time("callgraph"); 149 | var cg; 150 | if (args.strategy === 'NONE' || args.strategy === 'ONESHOT') 151 | cg = pessimistic.buildCallGraph(ast, args.strategy === 'NONE'); 152 | else if (args.strategy === 'DEMAND') 153 | cg = semioptimistic.buildCallGraph(ast); 154 | if (args.time) console.timeEnd("callgraph"); 155 | 156 | if (args.fg){ 157 | let serializedGraph = cg.fg.graph.serialize(); 158 | serializedGraph.links.forEach((link) => { 159 | console.log(link.source, "=>", link.target); 160 | }); 161 | } 162 | 163 | if (args.countCB) 164 | callbackCounter.countCallbacks(ast); 165 | 166 | if (args.reqJs) 167 | requireJsGraph.makeRequireJsGraph(ast).forEach(function (edge) { 168 | console.log(edge.toString()); 169 | }); 170 | if (args.cg) { 171 | let result = []; 172 | cg.edges.iter(function (call, fn) { 173 | result.push(buildBinding(call, fn)); 174 | if (consoleOutput) 175 | console.log(pp(call) + " -> " + pp(fn)); 176 | }); 177 | if (this.args.output !== null) { 178 | let filename = this.args.output[0]; 179 | if (!filename.endsWith(".json")) { 180 | filename += ".json"; 181 | } 182 | fs.writeFile(filename, JSON.stringify(result, null, 2), function (err) { 183 | if (err) { 184 | /* 185 | When happened something wrong (usually out of memory when we want print 186 | the result into a file), then we try to file with JSONStream. 187 | */ 188 | let transformStream = JSONStream.stringify(); 189 | let outputStream = fs.createWriteStream(filename); 190 | transformStream.pipe(outputStream); 191 | result.forEach(transformStream.write); 192 | transformStream.end(); 193 | } 194 | }); 195 | 196 | } 197 | return result; 198 | } 199 | }; 200 | 201 | exports.setFiles = function (inputList) { 202 | let filelist = []; 203 | inputList.forEach(function (file) { 204 | file = path.resolve(file); 205 | if (!fs.existsSync(file)) { 206 | console.warn('The path "' + file + '" does not exists.'); 207 | } 208 | else if (fs.statSync(file).isDirectory()) { 209 | filelist = utils.collectFiles(file, filelist); 210 | } 211 | else if (file.endsWith(".js") || file.endsWith(".ts") || file.endsWith(".vue")) { 212 | filelist.push(file); 213 | } 214 | }); 215 | this.files = Array.from(new Set(filelist)); 216 | if (this.files.length === 0) { 217 | console.warn("Input file list is empty!"); 218 | process.exit(-1); 219 | } 220 | }; 221 | 222 | exports.setFilter = function (filter) { 223 | this.filter = filter; 224 | }; 225 | 226 | exports.setArgs = function (args) { 227 | this.args = args; 228 | }; 229 | 230 | exports.setConsoleOutput = function (value) { 231 | this.consoleOutput = value; 232 | }; 233 | 234 | exports.build = build; 235 | return exports; 236 | }); -------------------------------------------------------------------------------- /src/semioptimistic.js: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2013 Max Schaefer 3 | * Copyright (c) 2018 Persper Foundation 4 | * 5 | * All rights reserved. This program and the accompanying materials 6 | * are made available under the terms of the Eclipse Public License v2.0 7 | * which accompanies this distribution, and is available at 8 | * http://www.eclipse.org/legal/epl-2.0. 9 | *******************************************************************************/ 10 | 11 | /* Optimistic call graph builder that tries to be clever about 12 | * which interprocedural flows to propagate: it only propagates 13 | * along edges that lead to a function call. */ 14 | 15 | if (typeof define !== 'function') { 16 | var define = require('amdefine')(module); 17 | } 18 | 19 | define(function (require, exports) { 20 | var graph = require('./graph'), 21 | natives = require('./natives'), 22 | flowgraph = require('./flowgraph'), 23 | callgraph = require('./callgraph'), 24 | mod = require('./module'), 25 | dftc = require('./dftc'); 26 | 27 | function addInterproceduralFlowEdges(ast, fg) { 28 | fg = fg || new graph.FlowGraph(); 29 | 30 | var changed; 31 | do { 32 | changed = false; 33 | 34 | var reach = dftc.reachability(fg, function (nd) { 35 | return nd.type !== 'UnknownVertex'; 36 | }); 37 | 38 | ast.attr.calls.forEach(function (call) { 39 | var res = flowgraph.resVertex(call); 40 | if (!res.attr.interesting) 41 | reach.iterReachable(res, function (nd) { 42 | if (nd.type === 'CalleeVertex') { 43 | res.attr.interesting = true; 44 | } 45 | }); 46 | }); 47 | 48 | ast.attr.functions.forEach(function (fn) { 49 | var interesting = false, nparams = fn.params.length; 50 | 51 | for (var i = 0; i <= nparams; ++i) { 52 | var param = flowgraph.parmVertex(fn, i); 53 | if (!param.attr.interesting) { 54 | reach.iterReachable(param, function (nd) { 55 | if (nd.type === 'CalleeVertex') { 56 | param.attr.interesting = true; 57 | } 58 | }); 59 | } 60 | interesting = interesting || param.attr.interesting; 61 | } 62 | 63 | reach.iterReachable(flowgraph.funcVertex(fn), function (nd) { 64 | if (nd.type === 'CalleeVertex') { 65 | var call = nd.call, res = flowgraph.resVertex(call); 66 | 67 | if (res.attr.interesting) { 68 | var ret = flowgraph.retVertex(fn); 69 | if (!fg.hasEdge(ret, res)) { 70 | changed = true; 71 | fg.addEdge(ret, res); 72 | } 73 | } 74 | 75 | if (interesting) 76 | for (var i = 0; i <= nparams; ++i) { 77 | if (i > call.arguments.length) 78 | break; 79 | 80 | var param = flowgraph.parmVertex(fn, i); 81 | if (param.attr.interesting) { 82 | var arg = flowgraph.argVertex(call, i); 83 | if (!fg.hasEdge(arg, param)) { 84 | changed = true; 85 | fg.addEdge(arg, param); 86 | } 87 | } 88 | } 89 | } 90 | }); 91 | }); 92 | } while (changed); // until fixpoint 93 | 94 | return fg; 95 | } 96 | 97 | function buildCallGraph(ast) { 98 | var fg = new graph.FlowGraph(); 99 | natives.addNativeFlowEdges(fg); 100 | flowgraph.addIntraproceduralFlowGraphEdges(ast, fg); 101 | 102 | let expFuncs = {}, 103 | impFuncs = {}; 104 | mod.collectExportsImports(ast, expFuncs, impFuncs); 105 | mod.connectImports(fg, expFuncs, impFuncs); 106 | 107 | addInterproceduralFlowEdges(ast, fg); 108 | 109 | return callgraph.extractCG(ast, fg); 110 | } 111 | 112 | exports.addInterproceduralFlowEdges = addInterproceduralFlowEdges; 113 | exports.buildCallGraph = buildCallGraph; 114 | return exports; 115 | }); 116 | -------------------------------------------------------------------------------- /src/set.js: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2013 Max Schaefer 3 | * Copyright (c) 2018 Persper Foundation 4 | * 5 | * All rights reserved. This program and the accompanying materials 6 | * are made available under the terms of the Eclipse Public License v2.0 7 | * which accompanies this distribution, and is available at 8 | * http://www.eclipse.org/legal/epl-2.0. 9 | *******************************************************************************/ 10 | 11 | /* 12 | * Keep the interface of ./olist.js, but use ES6 Set under the hood 13 | */ 14 | 15 | function size(s) { 16 | if (typeof s === 'undefined') 17 | return 0; 18 | 19 | if (typeof s === 'number') 20 | return 1; 21 | 22 | return s.size; 23 | } 24 | 25 | /* Check whether set s contains number n */ 26 | function contains(s, n) { 27 | if (typeof s === 'undefined') 28 | return false; 29 | 30 | if (typeof s === 'number') 31 | return s === n; 32 | 33 | return s.has(n); 34 | } 35 | 36 | /* Add number n to set s, and return the possibly modified s */ 37 | function add(s, n) { 38 | if (typeof s === 'undefined') 39 | return n; 40 | 41 | if (typeof s === 'number') 42 | return new Set([s, n]); 43 | 44 | s.add(n); 45 | return s; 46 | } 47 | 48 | /* 49 | * Add all elements in set s2 to set s1, return the resulting set 50 | * While set s1 may be modified, set s2 never is. 51 | */ 52 | function addAll(s1, s2) { 53 | if (typeof s1 === 'undefined') 54 | return copy(s2); 55 | 56 | if (typeof s2 === 'undefined') 57 | return s1; 58 | 59 | if (typeof s1 === 'number' && typeof s2 === 'number') { 60 | return new Set([s1, s2]); 61 | } 62 | else if (typeof s1 === 'number' && typeof s2 === 'object') { 63 | return new Set([s1, ...s2]); 64 | } 65 | else if (typeof s1 === 'object' && typeof s2 === 'number') { 66 | s1.add(s2); 67 | return s1; 68 | } 69 | else { 70 | for (let n of s2) 71 | s1.add(n); 72 | return s1 73 | } 74 | } 75 | 76 | /* 77 | * Remove number n from set s and return the modified set 78 | * Pitfall: this remove is inplace for array, not inplace for number 79 | */ 80 | function remove(s, n) { 81 | if (typeof s === 'undefined') 82 | return s; 83 | 84 | if (typeof s === 'number') 85 | return s === n ? void(0) : s; 86 | 87 | s.delete(n) 88 | return s; 89 | } 90 | 91 | /* 92 | * Remove all elements in set s2 from set s1, return the resulting set 93 | */ 94 | function removeAll(s1, s2) { 95 | if (typeof s1 === 'undefined' || typeof s2 === 'undefined') 96 | return s1; 97 | 98 | if (typeof s1 === 'number') 99 | return contains(s2, s1) ? void(0) : s1; 100 | 101 | if (typeof s2 === 'number') { 102 | s1.delete(s2); 103 | return s1; 104 | } 105 | 106 | for (let n of s2) 107 | s1.delete(n); 108 | 109 | if (s1.size === 0) 110 | return void(0); 111 | else 112 | return s1; 113 | } 114 | 115 | function copy(s) { 116 | if (typeof s === 'undefined' || typeof s === 'number') 117 | return s; 118 | 119 | // Return a shallow clone of the original set s 120 | return new Set(s); 121 | } 122 | 123 | function iter(s, cb) { 124 | if (typeof s !== 'undefined') { 125 | if (typeof s === 'number') 126 | cb(s); 127 | else 128 | s.forEach(cb); 129 | } 130 | } 131 | 132 | function map(s, f) { 133 | if (typeof s !== 'undefined') { 134 | if (typeof s === 'number') 135 | return [f(s)]; 136 | else { 137 | return Array.from(s).map(f); 138 | } 139 | } 140 | else { 141 | return []; 142 | } 143 | } 144 | 145 | function fromArray(ary) { 146 | return new Set(ary); 147 | } 148 | 149 | function toArray(s) { 150 | if (typeof s === 'undefined') 151 | return [] 152 | 153 | if (typeof s === 'number') 154 | return [s] 155 | 156 | return Array.from(s); 157 | } 158 | 159 | module.exports.size = size; 160 | module.exports.copy = copy; 161 | module.exports.contains = contains; 162 | module.exports.add = add; 163 | module.exports.addAll = addAll; 164 | module.exports.remove = remove; 165 | module.exports.removeAll = removeAll; 166 | module.exports.iter = iter; 167 | module.exports.map = map; 168 | module.exports.fromArray = fromArray; 169 | module.exports.toArray = toArray; 170 | 171 | -------------------------------------------------------------------------------- /src/srcPreprocessor.js: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2018 Persper Foundation 3 | * 4 | * All rights reserved. This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License v2.0 6 | * which accompanies this distribution, and is available at 7 | * http://www.eclipse.org/legal/epl-2.0. 8 | *******************************************************************************/ 9 | 10 | /* 11 | * This file includes the source preprocessors. 12 | * Preprocessors are meant to be run before the parser. 13 | * A preprocessor takes a string (src code) as input 14 | * and return a string (processed src code) as output. 15 | * All preprocessors should retain line numbers. 16 | */ 17 | 18 | const babel = require('@babel/core'); 19 | 20 | const nameToFunc = { 21 | 'flow': stripFlowPrep, 22 | 'hashbang': trimHashbangPrep 23 | }; 24 | 25 | /* Apply a list of preprocessors to src 26 | Args: 27 | src - A string, the source code to be processed 28 | fname - A string, name of the source file 29 | prepNames - A list of preprocessor names 30 | */ 31 | function applyPreps(src, fname, prepNames) { 32 | try { 33 | for (let prepName of prepNames) 34 | src = nameToFunc[prepName](src); 35 | } 36 | catch (err) { 37 | console.log('-------------------------------------------'); 38 | console.log('Warning: Preprocessing errored ' + fname); 39 | console.log(err.stack); 40 | console.log('-------------------------------------------'); 41 | return null; 42 | } 43 | return src; 44 | } 45 | 46 | function stripFlowPrep(src) { 47 | return babel.transform(src, { 48 | presets: ["@babel/preset-flow"], 49 | retainLines: true, 50 | parserOpts: {strictMode: false} 51 | }).code; 52 | } 53 | 54 | /* Trim hashbang to avoid the parser blowing up 55 | Example: 56 | #!/usr/bin/env node 57 | Reference: 58 | https://github.com/jquery/esprima/issues/1151 59 | */ 60 | function trimHashbangPrep(src) { 61 | if (src.substring(0, 2) === '#!') { 62 | var end = src.indexOf('\n'); 63 | var filler = ''; 64 | for (var i = 0; i < end; ++i) { 65 | filler += ' '; 66 | } 67 | src = filler + src.substring(end, src.length); 68 | } 69 | return src; 70 | } 71 | 72 | /* Compile Typescript into plain Javascript 73 | 74 | General reference: https://github.com/Microsoft/TypeScript-Babel-Starter 75 | 76 | Plugin @babel/typescript needs filename option to trigger, 77 | see the following issue for details: 78 | https://github.com/babel/babel/issues/8065 79 | 80 | Plugins: 81 | 82 | @babel/plugin-proposal-class-properties 83 | https://medium.com/@jacobworrel/babels-transform-class-properties-plugin-how-it-works-and-what-it-means-for-your-react-apps-6983539ffc22 84 | https://github.com/babel/babelify/issues/167 85 | */ 86 | function typescriptPrep(fname, src) { 87 | return babel.transform(src, { 88 | presets: ["@babel/preset-typescript"], 89 | plugins: ["@babel/plugin-proposal-class-properties"], 90 | filename: fname, 91 | retainLines: true, 92 | parserOpts: {strictMode: false} 93 | }).code; 94 | } 95 | 96 | module.exports.applyPreps = applyPreps; 97 | module.exports.stripFlowPrep = stripFlowPrep; 98 | module.exports.trimHashbangPrep = trimHashbangPrep; 99 | module.exports.typescriptPrep = typescriptPrep; 100 | 101 | -------------------------------------------------------------------------------- /src/symtab.js: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2013 Max Schaefer 3 | * Copyright (c) 2018 Persper Foundation 4 | * 5 | * All rights reserved. This program and the accompanying materials 6 | * are made available under the terms of the Eclipse Public License v2.0 7 | * which accompanies this distribution, and is available at 8 | * http://www.eclipse.org/legal/epl-2.0. 9 | *******************************************************************************/ 10 | 11 | /* Simple implementation of symbol tables. Uses 12 | * prototypal inheritance to model scoping. */ 13 | if (typeof define !== 'function') { 14 | var define = require('amdefine')(module); 15 | } 16 | 17 | define(function (require, exports) { 18 | function mangle(name) { 19 | return '$' + name; 20 | } 21 | 22 | function isMangled(name) { 23 | return name && name[0] === '$'; 24 | } 25 | 26 | function Symtab(outer) { 27 | var self = Object.create(outer || Symtab.prototype); 28 | // every scope has a pointer to its outer scope, which may be null 29 | self.outer = outer; 30 | return self; 31 | } 32 | 33 | Symtab.prototype.get = function (name, deflt) { 34 | var mangled = mangle(name); 35 | if (!deflt || this.has(name)) 36 | return this[mangled]; 37 | this[mangled] = deflt; 38 | return deflt; 39 | }; 40 | 41 | Symtab.prototype.has = function (name) { 42 | return mangle(name) in this; 43 | }; 44 | 45 | Symtab.prototype.hasOwn = function (name) { 46 | return this.hasOwnProperty(mangle(name)); 47 | }; 48 | 49 | Symtab.prototype.set = function (name, value) { 50 | if (!name) 51 | console.log('WARNING: name is falsy for Symtab, check bindings.js.'); 52 | return this[mangle(name)] = value; 53 | }; 54 | 55 | Symtab.prototype.values = function () { 56 | var values = []; 57 | for (var p in this) 58 | if (isMangled(p)) 59 | values[values.length] = this[p]; 60 | return values; 61 | }; 62 | 63 | exports.Symtab = Symtab; 64 | return exports; 65 | }); 66 | -------------------------------------------------------------------------------- /src/tests.js: -------------------------------------------------------------------------------- 1 | // function go(x) { x = 3; } 2 | function y() { 3 | var x = 1; 4 | } 5 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | 4 | /** 5 | * Collect all the files in the given directory, recursively 6 | */ 7 | function collectFiles(dir, filelist) { 8 | let files = fs.readdirSync(dir); 9 | filelist = filelist || []; 10 | files.forEach(function (file) { 11 | if (fs.statSync(path.join(dir, file)).isDirectory()) { 12 | filelist = collectFiles(path.join(dir, file), filelist); 13 | } 14 | else if (file.endsWith(".js")) { 15 | filelist.push(path.join(dir, file)); 16 | } 17 | }); 18 | return filelist; 19 | } 20 | 21 | module.exports.collectFiles = collectFiles; 22 | -------------------------------------------------------------------------------- /tests/README.md: -------------------------------------------------------------------------------- 1 | # Test Documentation 2 | 3 | ## Overview 4 | 5 | Our tests consist of two parts: unit tests run by jest and integration tests run by a test framework written in python. The jest component is relatively straightforward, so we'll focus more on the python test framework. 6 | 7 | Each test case in the python framework again has two parts: the Javascript source code and the ground truth file containing the correct call graph. The python framework runs our tool against the js source code and compares the output with the ground truth file. 8 | 9 | * `test/test.py`: the entry point, loops over all the test directories and finds all js files. They it tries to find a .truth file that has the same name with the js file. These two files together form a test case. It calls `precision_recall` function and determine the correctness of a test case. 10 | * `test/process.py`: implements the `precision_recall` function. Given the tool's output and the truth file, computes the precision and recall. 11 | * `test/callgraph.py`: runs our tool and captures the call graph output through subprocess. 12 | * `test/required_files.py`: given a js file, find the other js files that it requires. 13 | 14 | ## How to add a new test case 15 | 16 | Simply add all the js files and a truth file under one of the test directories specified in `test/test.py`. The python framework will automatically collect the new test case. Note that the truth file needs to have the same name with one of the js files, only within which function calls are checked. 17 | 18 | ## Details 19 | 20 | * `test/process.py` and `test/callgraph.py` ignore edges that have at least one native end point. 21 | * If a test case is composed of multiple js files, only the function calls in the one that has same name with the truth file are checked. 22 | -------------------------------------------------------------------------------- /tests/basics/arrow.js: -------------------------------------------------------------------------------- 1 | arrowFunc = x => { return x; }; 2 | 3 | arrowFunc(10); 4 | -------------------------------------------------------------------------------- /tests/basics/arrow.truth: -------------------------------------------------------------------------------- 1 | arrow.js:global:3 -> arrow.js:anon:1 2 | -------------------------------------------------------------------------------- /tests/basics/assignment.js: -------------------------------------------------------------------------------- 1 | // This file tests assigning a function object to an existing local variable 2 | function main() { 3 | let a = 1; 4 | let b = function funcB () { 5 | console.log('funcB is called!'); 6 | }; 7 | a = b; 8 | a(); 9 | } 10 | 11 | main(); 12 | -------------------------------------------------------------------------------- /tests/basics/assignment.truth: -------------------------------------------------------------------------------- 1 | assignment.js:funcB:5 -> Native 2 | assignment.js:main:8 -> assignment.js:funcB:4 3 | assignment.js:global:11 -> assignment.js:main:2 4 | -------------------------------------------------------------------------------- /tests/basics/global-as-prop.js: -------------------------------------------------------------------------------- 1 | /* 2 | Since we introduced `GlobVertex`, this problem has been fixed and we can now distinguish 3 | between the two function calls in this file. 4 | 5 | Original Comment: 6 | This file illustrates one of the unexpected behaviors of the call graph algorithm 7 | The algorithm should be able to distinguish the following two function calls, 8 | however, variables in global scope are handled by creating property vertex in the flow graph 9 | */ 10 | 11 | let a = function (x) { return x; }; 12 | let b = { 'a': function (x) { return x; }}; 13 | 14 | b.a() 15 | a() 16 | -------------------------------------------------------------------------------- /tests/basics/global-as-prop.truth: -------------------------------------------------------------------------------- 1 | global-as-prop.js:global:14 -> global-as-prop.js:a:12 2 | global-as-prop.js:global:15 -> global-as-prop.js:anon:11 3 | -------------------------------------------------------------------------------- /tests/basics/local-is-fine.js: -------------------------------------------------------------------------------- 1 | // Same code with global-as-prop.js, wrapped in a function 2 | (function () { 3 | let a = function (x) { return x; }; 4 | let b = {'a': function (x) { return x; }}; 5 | a(); 6 | b.a(); 7 | }()); 8 | -------------------------------------------------------------------------------- /tests/basics/local-is-fine.truth: -------------------------------------------------------------------------------- 1 | local-is-fine.js:global:2 -> local-is-fine.js:anon:2 2 | local-is-fine.js:anon:5 -> local-is-fine.js:anon:3 3 | local-is-fine.js:anon:6 -> local-is-fine.js:a:4 4 | -------------------------------------------------------------------------------- /tests/basics/method-def.js: -------------------------------------------------------------------------------- 1 | var obj = { 2 | randomMethod1: function () { 3 | return 42; 4 | }, 5 | 'randomMethod2': function () { 6 | return 'red'; 7 | } 8 | } 9 | 10 | obj.randomMethod1(); 11 | obj.randomMethod2(); 12 | -------------------------------------------------------------------------------- /tests/basics/method-def.truth: -------------------------------------------------------------------------------- 1 | method-def.js:global:10 -> method-def.js:randomMethod1:2 2 | method-def.js:global:11 -> method-def.js:randomMethod2:5 3 | -------------------------------------------------------------------------------- /tests/callgraph.py: -------------------------------------------------------------------------------- 1 | # Summary: Methods for collecting callgraph output 2 | # and formatting it into truth file output 3 | 4 | import subprocess 5 | import re 6 | 7 | NODE_PROGRAM = 'js-callgraph' 8 | 9 | 10 | def callgraph(files): 11 | """Returns raw standard output from callgraph generator""" 12 | program = ['node', NODE_PROGRAM, '--cg', *files, '--strategy', 'DEMAND'] 13 | 14 | process = subprocess.run(program, stdout=subprocess.PIPE, 15 | stderr=subprocess.PIPE) 16 | 17 | return process.stdout 18 | 19 | 20 | def callgraph_formatted(files, keep='', natives=True): 21 | """Returns reformatted standard output from callgraph generator 22 | 23 | Arguments: 24 | files - list of files to run callgraph on 25 | keep - set to a filename if only that file's calls are desired 26 | natives - set to False to filter out native calls 27 | """ 28 | stdout = callgraph(files) 29 | lines = str(stdout)[2:-1].split('\\n')[:-1] 30 | 31 | frmttd_lines = [] 32 | for line in lines: 33 | # ignore exceptions caused by formatting js output other than call edges (for example, warnings) 34 | try: 35 | frmttd_line = format_output(line) 36 | except: 37 | continue 38 | frmttd_lines.append(frmttd_line) 39 | 40 | if keep: 41 | reg = re.compile(keep + r':.*:[0-9]+ ->.*') 42 | frmttd_lines = [line for line in frmttd_lines if reg.match(line)] 43 | 44 | if not natives: 45 | frmttd_lines = [line for line in frmttd_lines if 'Native' not in line] 46 | 47 | return frmttd_lines 48 | 49 | 50 | # Regex representing call graph 51 | call_func = r'\'(.*)\' \(([^@]*)@([0-9]*):[0-9]*-[0-9]*\)' 52 | native = r'\'(.*)\' \(Native\)' 53 | 54 | reg_func = re.compile(call_func + ' -> ' + call_func) 55 | reg_native = re.compile(call_func + ' -> ' + native) 56 | 57 | 58 | def format_func(out): 59 | """Convert function call to truth file format. 60 | 61 | >>> format_func("'global' (arrow.js@3:33-46) -> 'anon' (arrow.js@1:12-30)") 62 | 'arrow.js:global:3 -> arrow.js:anon:1' 63 | """ 64 | m = reg_func.match(out) 65 | 66 | fn_name1 = m.group(1) 67 | file1 = m.group(2) 68 | line1 = m.group(3) 69 | 70 | fn_name2 = m.group(4) 71 | file2 = m.group(5) 72 | line2 = m.group(6) 73 | 74 | output = '{0}:{1}:{2} -> {3}:{4}:{5}' 75 | 76 | return output.format(file1, fn_name1, line1, file2, fn_name2, line2) 77 | 78 | 79 | def format_native(out): 80 | """Convert native call to truth file format 81 | 82 | >>> format_native("'global' (a.js@1:1-3) -> 'eval' (Native)") 83 | 'a.js:global:1 -> Native' 84 | """ 85 | m = reg_native.match(out) 86 | 87 | fn_name1 = m.group(1) 88 | file1 = m.group(2) 89 | line1 = m.group(3) 90 | 91 | native_name = m.group(4) 92 | 93 | output = '{0}:{1}:{2} -> Native' 94 | 95 | return output.format(file1, fn_name1, line1) 96 | 97 | 98 | def format_output(out): 99 | """Convert either a function call or a native call to truth file format 100 | 101 | >>> format_ouput("'global' (arrow.js@3:3-16) -> 'anon' (arrow.js@1:12-30)") 102 | 'arrow.js:global:3 -> arrow.js:anon:1' 103 | >>> format_output("'global' (a.js@1:1-3) -> 'eval' (Native)") 104 | 'a.js:global:1 -> Native' 105 | """ 106 | if reg_native.match(out): 107 | return format_native(out) 108 | else: 109 | return format_func(out) 110 | -------------------------------------------------------------------------------- /tests/classes/anonymous-class.js: -------------------------------------------------------------------------------- 1 | export default class { 2 | constructor () { 3 | this.p = 0; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /tests/classes/basic-class.js: -------------------------------------------------------------------------------- 1 | function Apple () { 2 | this.grow = function () { 3 | 4 | } 5 | } 6 | 7 | apple = new Apple(); 8 | apple.grow(); 9 | -------------------------------------------------------------------------------- /tests/classes/basic-class.truth: -------------------------------------------------------------------------------- 1 | basic-class.js:global:7 -> basic-class.js:Apple:1 2 | basic-class.js:global:8 -> basic-class.js:anon:2 3 | -------------------------------------------------------------------------------- /tests/classes/basic-class2.js: -------------------------------------------------------------------------------- 1 | class Apple { 2 | grow () { 3 | 4 | } 5 | } 6 | 7 | apple = new Apple(); 8 | apple.grow(); 9 | -------------------------------------------------------------------------------- /tests/classes/basic-class2.truth: -------------------------------------------------------------------------------- 1 | basic-class2.js:global:8 -> basic-class2.js:grow:2 2 | -------------------------------------------------------------------------------- /tests/classes/class-expression1.js: -------------------------------------------------------------------------------- 1 | let x = class Apple { 2 | constructor() { 3 | this.p = 10; 4 | } 5 | } 6 | 7 | x(); 8 | -------------------------------------------------------------------------------- /tests/classes/class-expression1.truth: -------------------------------------------------------------------------------- 1 | class-expression1.js:global:7 -> class-expression1.js:constructor:2 2 | -------------------------------------------------------------------------------- /tests/classes/class-expression2.js: -------------------------------------------------------------------------------- 1 | let x = class { 2 | constructor() { 3 | this.p = 10; 4 | } 5 | } 6 | 7 | x(); 8 | -------------------------------------------------------------------------------- /tests/classes/class-expression2.truth: -------------------------------------------------------------------------------- 1 | class-expression2.js:global:7 -> class-expression2.js:constructor:2 2 | -------------------------------------------------------------------------------- /tests/classes/class.js: -------------------------------------------------------------------------------- 1 | class Rectangle { 2 | constructor(height, width) { 3 | this.height = height; 4 | this.width = width; 5 | } 6 | // Getter 7 | get area() { 8 | return this.calcArea(); 9 | } 10 | // Method 11 | calcArea() { 12 | return this.height * this.width; 13 | } 14 | } 15 | 16 | const square = new Rectangle(10, 10); 17 | 18 | console.log(square.calcArea()); 19 | -------------------------------------------------------------------------------- /tests/classes/class.truth: -------------------------------------------------------------------------------- 1 | class.js:area:8 -> class.js:calcArea:11 2 | class.js:global:16 -> class.js:constructor:2 3 | class.js:global:18 -> Native 4 | class.js:global:18 -> class.js:calcArea:11 5 | -------------------------------------------------------------------------------- /tests/classes/export-class-expression1.js: -------------------------------------------------------------------------------- 1 | let x = class Apple { 2 | constructor() { 3 | this.p = 10; 4 | } 5 | } 6 | 7 | export default x; 8 | -------------------------------------------------------------------------------- /tests/classes/export-class-expression2.js: -------------------------------------------------------------------------------- 1 | let x = class { 2 | constructor() { 3 | this.p = 10; 4 | } 5 | } 6 | 7 | export default x; 8 | -------------------------------------------------------------------------------- /tests/classes/import-anonymous-class.js: -------------------------------------------------------------------------------- 1 | import a from 'anonymous-class'; 2 | 3 | var x = new a(); 4 | -------------------------------------------------------------------------------- /tests/classes/import-anonymous-class.truth: -------------------------------------------------------------------------------- 1 | import-anonymous-class.js:global:3 -> anonymous-class.js:constructor:2 2 | -------------------------------------------------------------------------------- /tests/classes/import-class-expression1.js: -------------------------------------------------------------------------------- 1 | import a from 'export-class-expression1'; 2 | 3 | var x = new a(); 4 | -------------------------------------------------------------------------------- /tests/classes/import-class-expression1.truth: -------------------------------------------------------------------------------- 1 | import-class-expression1.js:global:3 -> export-class-expression1.js:constructor:2 2 | -------------------------------------------------------------------------------- /tests/classes/import-class-expression2.js: -------------------------------------------------------------------------------- 1 | import a from 'export-class-expression2'; 2 | 3 | var x = new a(); 4 | -------------------------------------------------------------------------------- /tests/classes/import-class-expression2.truth: -------------------------------------------------------------------------------- 1 | import-class-expression2.js:global:3 -> export-class-expression2.js:constructor:2 2 | -------------------------------------------------------------------------------- /tests/classes/outer-fn.js: -------------------------------------------------------------------------------- 1 | function f2() { 2 | return 5 3 | } 4 | 5 | class A { 6 | f() { 7 | f2() 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/classes/outer-fn.truth: -------------------------------------------------------------------------------- 1 | outer-fn.js:f:7 -> outer-fn.js:f2:1 2 | -------------------------------------------------------------------------------- /tests/creating-vue-compiled.txt: -------------------------------------------------------------------------------- 1 | The steps to making the vue-compiled directory were: 2 | 3 | 1. copy the entire vue directory to a fold called vue-compiled 4 | 2. Run this script in the directory above it 5 | 6 | for f in $(find ./vue-compiled -name '*.js'); 7 | do 8 | $(babel --presets flow $f > $f.o) 9 | echo $f 10 | 11 | done 12 | 13 | This traverses the directories and runs babel on each of the files and saves their compiled output to a .js.o file with the same name and location. 14 | -------------------------------------------------------------------------------- /tests/es6/array-pattern.js: -------------------------------------------------------------------------------- 1 | /* 2 | Array destructuing: basic variable assignment 3 | https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment 4 | */ 5 | function main () { 6 | const a = [ () => { return 1; } ]; 7 | let [b] = a; 8 | b(); 9 | } 10 | 11 | main(); 12 | -------------------------------------------------------------------------------- /tests/es6/array-pattern.truth: -------------------------------------------------------------------------------- 1 | array-pattern.js:main:8 -> array-pattern.js:anon:6 2 | array-pattern.js:global:11 -> array-pattern.js:main:5 3 | -------------------------------------------------------------------------------- /tests/es6/array-pattern2.js: -------------------------------------------------------------------------------- 1 | /* 2 | Array destructuing: assignment separate from declaration 3 | https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment 4 | */ 5 | function main () { 6 | let b; 7 | const a = [ () => { return 1; } ]; 8 | [b] = a; 9 | b(); 10 | } 11 | 12 | main(); 13 | -------------------------------------------------------------------------------- /tests/es6/array-pattern2.truth: -------------------------------------------------------------------------------- 1 | array-pattern2.js:main:9 -> array-pattern2.js:anon:7 2 | array-pattern2.js:global:12 -> array-pattern2.js:main:5 3 | -------------------------------------------------------------------------------- /tests/es6/array-pattern3.js: -------------------------------------------------------------------------------- 1 | /* 2 | Array destructuing: parsing an array returned from a function 3 | https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment 4 | */ 5 | function main () { 6 | const f = () => { return [() => { return 1; }]; }; 7 | const [a] = f(); 8 | a(); 9 | } 10 | 11 | main(); 12 | -------------------------------------------------------------------------------- /tests/es6/array-pattern3.truth: -------------------------------------------------------------------------------- 1 | array-pattern3.js:main:7 -> array-pattern3.js:anon:6 2 | array-pattern3.js:main:8 -> array-pattern3.js:anon:6 3 | array-pattern3.js:global:11 -> array-pattern3.js:main:5 4 | -------------------------------------------------------------------------------- /tests/es6/array-pattern4.js: -------------------------------------------------------------------------------- 1 | /* 2 | Array destructuing: ignoring some return values 3 | https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment 4 | */ 5 | function main () { 6 | const f = () => { return [2, 3, () => { return 1; }]; }; 7 | const [,,a] = f(); 8 | a(); 9 | } 10 | 11 | main(); 12 | -------------------------------------------------------------------------------- /tests/es6/array-pattern4.truth: -------------------------------------------------------------------------------- 1 | array-pattern4.js:main:7 -> array-pattern4.js:anon:6 2 | array-pattern4.js:main:8 -> array-pattern4.js:anon:6 3 | array-pattern4.js:global:11 -> array-pattern4.js:main:5 4 | -------------------------------------------------------------------------------- /tests/es6/binding-pattern-global.js: -------------------------------------------------------------------------------- 1 | let v = () => { return 1; }; 2 | 3 | function main () { 4 | v(); 5 | [v] = [null]; 6 | } 7 | 8 | main(); 9 | /* 10 | This test illustrates how the current implementation incorrectly set decl_scope 11 | for 'BindingPattern' in binding.js. 12 | The arrow functinon defined on line 1 is called on line 4. 13 | But when the 'ArrayPattern' on line 5 is visited, the name 'v' is added to local symbol table. 14 | Later when flowgraph.js adds intra-procedural edges for line 4, the new edge will be 15 | 16 | Var(v) -> Callee(4) 17 | 18 | instead of 19 | 20 | Prop(v) -> Callee(4) 21 | 22 | resulting the call to arrow function defined on line 1 not detected. 23 | */ 24 | -------------------------------------------------------------------------------- /tests/es6/binding-pattern-global.truth: -------------------------------------------------------------------------------- 1 | binding-pattern-global.js:main:4 -> binding-pattern-global.js:anon:1 2 | binding-pattern-global.js:global:8 -> binding-pattern-global.js:main:3 3 | -------------------------------------------------------------------------------- /tests/es6/destructured-parameter.js: -------------------------------------------------------------------------------- 1 | /* 2 | Destructured parameter 3 | See the last code example in the following link: 4 | https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Default_parameters 5 | */ 6 | function main () { 7 | function f ([x, y], {z}) { 8 | return x() + y + z(); 9 | } 10 | const f2 = () => { return 2; }; 11 | const f3 = () => { return 3; }; 12 | f([f2, 2], {z: f3}); 13 | } 14 | 15 | main(); 16 | -------------------------------------------------------------------------------- /tests/es6/destructured-parameter.truth: -------------------------------------------------------------------------------- 1 | destructured-parameter.js:f:8 -> destructured-parameter.js:anon:10 2 | destructured-parameter.js:f:8 -> destructured-parameter.js:anon:11 3 | destructured-parameter.js:main:12 -> destructured-parameter.js:f:7 4 | destructured-parameter.js:global:15 -> destructured-parameter.js:main:6 5 | -------------------------------------------------------------------------------- /tests/es6/destructured-parameter2.js: -------------------------------------------------------------------------------- 1 | /* 2 | Destructured parameter 3 | See the last code example in the following link: 4 | https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Default_parameters 5 | */ 6 | let v = function () { return 0; }; 7 | 8 | function main () { 9 | function f ({u: {v}}) { 10 | return v(); 11 | } 12 | const f2 = () => { return 2; }; 13 | f({u: {v: f2}}); 14 | } 15 | 16 | main(); 17 | 18 | /* 19 | Before supporting nested destructuring, running 20 | 21 | node src/main.js --cg tests/es6/destructured-parameter2.js --strategy DEMAND 22 | 23 | gives the following output: 24 | 25 | 'f' (destructured-parameter2.js@10:262-265) -> 'anon' (destructured-parameter2.js@6:181-206) 26 | 'f' (destructured-parameter2.js@10:262-265) -> 'anon' (destructured-parameter2.js@12:282-301) 27 | 'main' (destructured-parameter2.js@13:304-319) -> 'f' (destructured-parameter2.js@9:229-269) 28 | 'global' (destructured-parameter2.js@16:324-330) -> 'main' (destructured-parameter2.js@8:209-322) 29 | 30 | Note that the anonymous function defined on line 6 and assigned to 31 | global variable v is never called in the program, which our algorithm mistakenly detects that 32 | it's called by function f. 33 | This is due to the nested destructuring on line 9 is not handled correctly. 34 | v on line 9 is not found in the scope, and thus is considered as a global variable at line 10, 35 | which leads to the error of connecting Prop(v) -> Callee(10). 36 | */ -------------------------------------------------------------------------------- /tests/es6/destructured-parameter2.truth: -------------------------------------------------------------------------------- 1 | destructured-parameter2.js:f:10 -> destructured-parameter2.js:anon:12 2 | destructured-parameter2.js:main:13 -> destructured-parameter2.js:f:9 3 | destructured-parameter2.js:global:16 -> destructured-parameter2.js:main:8 4 | -------------------------------------------------------------------------------- /tests/es6/destructured-parameter3.js: -------------------------------------------------------------------------------- 1 | /* 2 | Destructured parameter 3 | See the last code example in the following link: 4 | https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Default_parameters 5 | */ 6 | let v = function () { return 0; }; 7 | 8 | function main () { 9 | function f ([u, [v]]) { 10 | return u + v(); 11 | } 12 | const f2 = () => { return 2; }; 13 | f([1, [f2]]); 14 | } 15 | 16 | main(); 17 | 18 | /* 19 | Before supporting nested destructuring, running 20 | 21 | node src/main.js --cg tests/es6/destructured-parameter3.js --strategy DEMAND 22 | 23 | gives the following output: 24 | 25 | 'f' (destructured-parameter3.js@10:266-269) -> 'undefined' (destructured-parameter3.js@6:181-206) 26 | 'f' (destructured-parameter3.js@10:266-269) -> 'undefined' (destructured-parameter3.js@12:286-305) 27 | 'main' (destructured-parameter3.js@13:308-320) -> 'f' (destructured-parameter3.js@9:229-273) 28 | 'global' (destructured-parameter3.js@16:325-331) -> 'main' (destructured-parameter3.js@8:209-323) 29 | 30 | Note that the anonymous function defined on line 6 and assigned to 31 | global variable v is never called in the program, which our algorithm mistakenly detects that 32 | it's called by function f. 33 | This is due to the nested destructuring on line 9 is not handled correctly. 34 | v on line 9 is not found in the scope, and thus is considered as a global variable at line 10, 35 | which leads to the error of connecting Prop(v) -> Callee(10). 36 | */ -------------------------------------------------------------------------------- /tests/es6/destructured-parameter3.truth: -------------------------------------------------------------------------------- 1 | destructured-parameter3.js:f:10 -> destructured-parameter3.js:anon:12 2 | destructured-parameter3.js:main:13 -> destructured-parameter3.js:f:9 3 | destructured-parameter3.js:global:16 -> destructured-parameter3.js:main:8 4 | -------------------------------------------------------------------------------- /tests/es6/lib.js: -------------------------------------------------------------------------------- 1 | export const sqrt = Math.sqrt; 2 | export function square(x) { 3 | return x * x; 4 | } 5 | export function diag(x, y) { 6 | return sqrt(square(x) + square(y)); 7 | } 8 | -------------------------------------------------------------------------------- /tests/es6/main.js: -------------------------------------------------------------------------------- 1 | import { square, diag } from 'lib'; 2 | console.log(square(11)); // 121 3 | console.log(diag(4, 3)); // 5 4 | -------------------------------------------------------------------------------- /tests/es6/main.truth: -------------------------------------------------------------------------------- 1 | main.js:global:2 -> Native 2 | main.js:global:2 -> lib.js:square:2 3 | main.js:global:3 -> Native 4 | main.js:global:3 -> lib.js:diag:5 5 | -------------------------------------------------------------------------------- /tests/es6/object-pattern.js: -------------------------------------------------------------------------------- 1 | /* 2 | Object destructuing: basic assignment 3 | https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment 4 | */ 5 | function main () { 6 | const b = {'a': () => { return 1; } }; 7 | const {a} = b; 8 | a(); 9 | } 10 | 11 | main(); 12 | -------------------------------------------------------------------------------- /tests/es6/object-pattern.truth: -------------------------------------------------------------------------------- 1 | object-pattern.js:main:8 -> object-pattern.js:anon:6 2 | object-pattern.js:global:11 -> object-pattern.js:main:5 3 | -------------------------------------------------------------------------------- /tests/es6/object-pattern2.js: -------------------------------------------------------------------------------- 1 | /* 2 | Object destructuing: assign without declaration 3 | https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment 4 | */ 5 | function main () { 6 | let a; 7 | const b = {'a': () => { return 1; } }; 8 | ({a} = b); 9 | a(); 10 | } 11 | 12 | main(); 13 | -------------------------------------------------------------------------------- /tests/es6/object-pattern2.truth: -------------------------------------------------------------------------------- 1 | object-pattern2.js:main:9 -> object-pattern2.js:anon:7 2 | object-pattern2.js:global:12 -> object-pattern2.js:main:5 3 | -------------------------------------------------------------------------------- /tests/es6/object-pattern3.js: -------------------------------------------------------------------------------- 1 | /* 2 | Object destructuing: assigning to new variable names 3 | https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment 4 | */ 5 | function main () { 6 | const b = {'a': () => { return 1; } }; 7 | const {a: foo} = b; 8 | foo(); 9 | } 10 | 11 | main(); 12 | -------------------------------------------------------------------------------- /tests/es6/object-pattern3.truth: -------------------------------------------------------------------------------- 1 | object-pattern3.js:main:8 -> object-pattern3.js:anon:6 2 | object-pattern3.js:global:11 -> object-pattern3.js:main:5 3 | -------------------------------------------------------------------------------- /tests/ground_truths/create-component.txt: -------------------------------------------------------------------------------- 1 | create-component.js.o:anon:3 -> Native 2 | create-component.js.o:anon:9 -> Native 3 | create-component.js.o:anon:11 -> create-component.js.o:_interopRequireDefault:27 4 | create-component.js.o:anon:13 -> Native 5 | create-component.js.o:anon:15 -> Native 6 | create-component.js.o:anon:17 -> Native 7 | create-component.js.o:anon:19 -> Native 8 | create-component.js.o:anon:21 -> Native 9 | create-component.js.o:anon:23 -> Native 10 | create-component.js.o:anon:25 -> Native 11 | create-component.js.o:anon:29 -> create-component.js.o:init:33 12 | create-component.js.o:init:37 -> create-component.js.o:prepatch:43 13 | create-component.js.o:init:39 -> create-component.js.o:createComponentInstanceForVnode:178 14 | create-component.js.o:init:40 -> 15 | create-component.js.o:prepatch:46 -> lifecycle.js.o:updateChildComponent:218 16 | create-component.js.o:insert:58 -> lifecycle.js.o:callHook:319 17 | create-component.js.o:insert:67 -> scheduler.js.o:queueActivatedComponent:114 18 | create-component.js.o:insert:69 -> lifecycle.js.o:activateChildComponent:285 19 | create-component.js.o:destroy:78 -> lifecycle.js.o:anon:113 20 | create-component.js.o:destroy:80 -> lifecycle.js.o:deactivateChildComponent:303 21 | create-component.js.o:anon:86 -> Native 22 | create-component.js.o:createComponent:89 -> util.js.o:isUndef:37 23 | create-component.js.o:createComponent:96 -> util.js.o:isObject:67 24 | create-component.js.o:createComponent:97 -> 25 | create-component.js.o:createComponent:104 -> debug.js.o:warn:30 26 | create-component.js.o:createComponent:104 -> Native 27 | create-component.js.o:createComponent:111 -> util.js.o:isUndef:37 28 | create-component.js.o:createComponent:113 -> resolve-async-component.js.o:resolveAsyncComponent:27 29 | create-component.js.o:createComponent:118 -> resolve-async-component.js.o:createAsyncPlaceholder:20 30 | create-component.js.o:createComponent:126 -> init.js.o:resolveConstructorOptions:111 31 | create-component.js.o:createComponent:129 -> util.js.o:isDef:41 32 | create-component.js.o:createComponent:130 -> create-component.js.o:transformModel:206 33 | create-component.js.o:createComponent:134 -> extract-props.js.o:extractPropsFromVNodeData:10 34 | create-component.js.o:createComponent:137 -> util.js.o:isTrue:45 35 | create-component.js.o:createComponent:138 -> create-functional-component.js.o:createFunctionalComponent:81 36 | create-component.js.o:createComponent:148 -> util.js.o:isTrue:45 37 | create-component.js.o:createComponent:161 -> create-component.js.o:installComponentHooks:196 38 | create-component.js.o:createComponent:165 -> vnode.js.o:VNode:23 39 | create-component.js.o:createComponent:171 -> render-component-template.js.o:isRecyclableComponent:19 40 | create-component.js.o:createComponent:172 -> render-component-template.js.o:renderRecyclableComponentTemplate:23 41 | create-component.js.o:createComponentInstanceForVnode:189 -> util.js.o:isDef:41 42 | create-component.js.o:createComponentInstanceForVnode:193 -> 43 | create-component.js.o:transformModel:210 -> util.js.o:isDef:41 44 | create-component.js.o:transformModel:211 -> Native 45 | -------------------------------------------------------------------------------- /tests/ground_truths/create-element.txt: -------------------------------------------------------------------------------- 1 | create-element.js.o:anon:3 -> Native 2 | create-element.js.o:anon:9 -> Native 3 | create-element.js.o:anon:11 -> create-element.js.o:_interopRequireDefault:25 4 | create-element.js.o:anon:13 -> Native 5 | create-element.js.o:anon:15 -> create-element.js.o:_interopRequireDefault:25 6 | create-element.js.o:anon:17 -> Native 7 | create-element.js.o:anon:19 -> Native 8 | create-element.js.o:anon:21 -> Native 9 | create-element.js.o:anon:23 -> Native 10 | create-element.js.o:createElement -> Native 11 | create-element.js.o:createElement:33 -> util.js.o:isPrimitive:56 12 | create-element.js.o:createElement:38 -> util.js.o:isTrue:45 13 | create-element.js.o:createElement:41 -> create-element.js.o:_createElement:44 14 | create-element.js.o:_createElement:45 -> util.js.o:isDef:41 15 | create-element.js.o:_createElement:45 -> util.js.o:isDef:41 16 | create-element.js.o:_createElement:46 -> debug.js.o:warn:30 17 | create-element.js.o:_createElement:46 -> Native 18 | create-element.js.o:_createElement:47 -> vnode.js.o:createEmptyVNode:74 19 | create-element.js.o:_createElement:50 -> util.js.o:isDef:41 20 | create-element.js.o:_createElement:50 -> util.js.o:isDef:41 21 | create-element.js.o:_createElement:55 -> vnode.js.o:createEmptyVNode:74 22 | create-element.js.o:_createElement:58 -> util.js.o:isDef:41 23 | create-element.js.o:_createElement:58 -> util.js.o:isDef:41 24 | create-element.js.o:_createElement:58 -> util.js.o:isPrimitive:56 25 | create-element.js.o:_createElement:60 -> debug.js.o:warn:30 26 | create-element.js.o:_createElement:64 -> Native 27 | create-element.js.o:_createElement:70 -> normalize-children.js.o:normalizeChildren:42 28 | create-element.js.o:_createElement:72 -> normalize-children.js.o:simpleNormalizeChildren:29 29 | create-element.js.o:_createElement:78 -> util.js.o:noop:265 30 | create-element.js.o:_createElement:79 -> util.js.o:no:270 31 | create-element.js.o:_createElement:81 -> vnode.js.o:VNode:23 32 | create-element.js.o:_createElement:81 -> util.js.o:identity:277 33 | create-element.js.o:_createElement:82 -> util.js.o:isDef:41 34 | create-element.js.o:_createElement:84 -> create-component.js.o:createComponent:88 35 | create-element.js.o:_createElement:89 -> vnode.js.o:VNode:23 36 | create-element.js.o:_createElement:93 -> create-component.js.o:createComponent:88 37 | create-element.js.o:_createElement:95 -> Native 38 | create-element.js.o:_createElement:97 -> util.js.o:isDef:41 39 | create-element.js.o:_createElement:98 -> util.js.o:isDef:41 40 | create-element.js.o:_createElement:98 -> create-element.js.o:applyNS:106 41 | create-element.js.o:_createElement:99 -> util.js.o:isDef:41 42 | create-element.js.o:_createElement:99 -> create-element.js.o:registerDeepBindings:126 43 | create-element.js.o:_createElement:102 -> vnode.js.o:createEmptyVNode:74 44 | create-element.js.o:applyNS:113 -> util.js.o:isDef:41 45 | create-element.js.o:applyNS:116 -> util.js.o:isDef:41 46 | create-element.js.o:applyNS:116 -> util.js.o:isUndef:37 47 | create-element.js.o:applyNS:116 -> util.js.o:isTrue:45 48 | create-element.js.o:applyNS:117 -> create-element.js.o:applyNS:106 49 | create-element.js.o:registerDeepBindings:127 -> util.js.o:isObject:67 50 | create-element.js.o:registerDeepBindings:128 -> traverse.js.o:traverse:23 51 | create-element.js.o:registerDeepBindings:130 -> util.js.o:isObject:67 52 | create-element.js.o:registerDeepBindings:131 -> traverse.js.o:traverse:23 53 | -------------------------------------------------------------------------------- /tests/ground_truths/ground-truth.txt: -------------------------------------------------------------------------------- 1 | Template: 2 | :: -> :: 3 | 4 | The general format of a line follows the above syntax. 5 | An example input and output might be: 6 | 7 | INPUT (from create-component.js.o): 8 | 9 | componentVNodeHooks.init(); 10 | 11 | // inline hooks to be invoked on component VNodes during patch 12 | var componentVNodeHooks = { 13 | init: function init(vnode, hydrating, parentElm, refElm) { 14 | if (vnode.componentInstance && !vnode.componentInstance._isDestroyed && vnode.data.keepAlive) { 15 | // kept-alive components, treat as a patch 16 | var mountedNode = vnode; // work around flow 17 | componentVNodeHooks.prepatch(mountedNode, mountedNode); 18 | } else { 19 | ... 20 | 21 | OUTPUT: 22 | create-component.js.o:anon:29 -> create-component.js.o:init:33 23 | create-component.js.o:init:37 -> create-component.js.o:prepatch:43 24 | create-component.js.o:init:39 -> create-component.js.o:createComponentIntanceForVnode:178 25 | 26 | 27 | If you are unsure where the function being call is defined, you can add and arrow without 28 | a right side like so: 29 | create-component.js.o:init:40 -> 30 | 31 | These will be filtered along with native functions in the final calculation 32 | -------------------------------------------------------------------------------- /tests/ground_truths/patch.txt: -------------------------------------------------------------------------------- 1 | patch.js.o:3 -> NativeVertex 2 | patch.js.o:9 -> NativeVertex 3 | patch.js.o:11 -> patch.js.o:_interopRequireDefault:29 4 | patch.js.o:13 -> NativeVertex 5 | patch.js.o:15 -> patch.js.o:_interopRequireDefault:29 6 | patch.js.o:17 -> NativeVertex 7 | patch.js.o:19 -> NativeVertex 8 | patch.js.o:21 -> NativeVertex 9 | patch.js.o:23 -> NativeVertex 10 | patch.js.o:25 -> NativeVertex 11 | patch.js.o:27 -> NativeVertex 12 | patch.js.o:43 -> vnode.js.o:VNode:23 13 | patch.js.o:48 -> util.js.o:isDef:41 14 | patch.js.o:48 -> util.js.o:isDef:41 15 | patch.js.o:48 -> patch.js.o:sameInputType:51 16 | patch.js.o:48 -> util.js.o:isTrue:45 17 | patch.js.o:48 -> util.js.o:isUndef:37 18 | patch.js.o:54 -> util.js.o:isDef:41 19 | patch.js.o:54 -> util.js.o:isDef:41 20 | patch.js.o:55 -> util.js.o:isDef:41 21 | patch.js.o:55 -> util.js.o:isDef:41 22 | patch.js.o:56 -> element.js.o:isTextInputType:67 23 | patch.js.o:56 -> element.js.o:isTextInputType:67 24 | patch.js.o:65 -> util.js.o:isDef:41 25 | patch.js.o:82 -> util.js.o:isDef:41 26 | patch.js.o:83 -> NativeVertex 27 | patch.js.o:89 -> vnode.js.o:VNode:23 28 | patch.js.o:89 -> NativeVertex 29 | patch.js.o:95 -> patch.js.o:102 30 | patch.js.o:103 -> 31 | patch.js.o:105 -> util.js.o:isDef:41 32 | patch.js.o:106 -> 33 | patch.js.o:111 -> 34 | patch.js.o:112 -> util.js.o:isRegExp:88 35 | patch.js.o:112 -> 36 | patch.js.o:113 -> util.js.o:no:270 37 | patch.js.o:119 -> util.js.o:isDef:41 38 | patch.js.o:119 -> util.js.o:isDef:41 39 | patch.js.o:125 -> vnode.js.o:cloneVNode:91 40 | patch.js.o:129 -> patch.js.o:createComponent:188 41 | patch.js.o:136 -> util.js.o:isDef:41 42 | patch.js.o:141 -> patch.js.o:isUnknownElement:110 43 | patch.js.o:142 -> debug.js.o:warn:30 44 | patch.js.o:146 -> 45 | patch.js.o:146 -> 46 | patch.js.o:147 -> patch.js.o:setScope:295 47 | patch.js.o: 48 | patch.js.o: 49 | patch.js.o: 50 | patch.js.o: 51 | patch.js.o: 52 | patch.js.o: 53 | patch.js.o: 54 | patch.js.o: 55 | -------------------------------------------------------------------------------- /tests/ground_truths/vnode.txt: -------------------------------------------------------------------------------- 1 | vnode.js.o:anon:3 -> Native 2 | vnode.js.o:anon:7 -> vnode.js.o:anon:7 3 | vnode.js.o:defineProperties:7 -> Native 4 | vnode.js.o:anon:7 -> vnode.js.o:defineProperties:7 5 | vnode.js.o:anon:7 -> vnode.js.o:defineProperties:7 6 | vnode.js.o:anon:14 -> vnode.js.o:anon:14 7 | vnode.js.o:VNode:24 -> vnode.js.o:_classCallCheck:12 8 | vnode.js.o:anon:63 -> vnode.js.o:anon:7 9 | vnode.js.o:createEmptyVNode:77 -> vnode.js.o:VNode:23 10 | vnode.js.o:createTextVNode:84 -> vnode.js.o:VNode:23 11 | vnode.js.o:cloneVNode:92 -> vnode.js.o:VNode:23 12 | -------------------------------------------------------------------------------- /tests/import-export/define/arrow-func-no-block-statement-module.js: -------------------------------------------------------------------------------- 1 | // This module directly assigns the constructor function for User to module.exports 2 | if (typeof define !== 'function') { 3 | var define = require('amdefine')(module); 4 | } 5 | 6 | define(() => () => {return 1;}); 7 | -------------------------------------------------------------------------------- /tests/import-export/define/arrow-func-no-block-statement-require.js: -------------------------------------------------------------------------------- 1 | // This file tests directly assigning a constructor function to module.exports 2 | let arrow = require('./arrow-func-no-block-statement-module'); 3 | 4 | console.log(arrow()) 5 | -------------------------------------------------------------------------------- /tests/import-export/define/arrow-func-no-block-statement-require.truth: -------------------------------------------------------------------------------- 1 | arrow-func-no-block-statement-require.js:global:4 -> arrow-func-no-block-statement-module.js:anon:6 2 | -------------------------------------------------------------------------------- /tests/import-export/define/define-module.js: -------------------------------------------------------------------------------- 1 | // This module directly assigns the constructor function for User to module.exports 2 | if (typeof define !== 'function') { 3 | var define = require('amdefine')(module); 4 | } 5 | 6 | define(function() { 7 | let User = function(name, email) { 8 | this.name = name; 9 | this.email = email; 10 | }; 11 | return User; 12 | }); 13 | -------------------------------------------------------------------------------- /tests/import-export/define/define-require.js: -------------------------------------------------------------------------------- 1 | // This file tests directly assigning a constructor function to module.exports 2 | let user = require('./define-module'); 3 | 4 | let u = new user('random user', 'random-user@persper.org'); 5 | console.log("%s's email is %s", u.name, u.email); 6 | -------------------------------------------------------------------------------- /tests/import-export/define/define-require.truth: -------------------------------------------------------------------------------- 1 | define-require.js:global:2 -> Native 2 | define-require.js:global:4 -> define-module.js:anon:7 3 | define-require.js:global:5 -> Native 4 | -------------------------------------------------------------------------------- /tests/import-export/es6/es6-cyclic-dependencies1.js: -------------------------------------------------------------------------------- 1 | import { Lib } from 'es6-cyclic-dependencies2'; 2 | export default function double(x) { 3 | return x * 2; 4 | } 5 | Lib(4); 6 | -------------------------------------------------------------------------------- /tests/import-export/es6/es6-cyclic-dependencies1.truth: -------------------------------------------------------------------------------- 1 | es6-cyclic-dependencies1.js:global:5 -> es6-cyclic-dependencies2.js:anon:3 2 | -------------------------------------------------------------------------------- /tests/import-export/es6/es6-cyclic-dependencies2.js: -------------------------------------------------------------------------------- 1 | import Main from 'es6-cyclic-dependencies1'; 2 | Main(); 3 | var lib = function (x) { return x; }; 4 | export { lib as Lib }; 5 | -------------------------------------------------------------------------------- /tests/import-export/es6/es6-cyclic-dependencies2.truth: -------------------------------------------------------------------------------- 1 | es6-cyclic-dependencies2.js:global:2 -> es6-cyclic-dependencies1.js:double:2 2 | -------------------------------------------------------------------------------- /tests/import-export/es6/es6-export-default.js: -------------------------------------------------------------------------------- 1 | export default function () { return 4; }; 2 | -------------------------------------------------------------------------------- /tests/import-export/es6/es6-export-fns.js: -------------------------------------------------------------------------------- 1 | export const sqrt = Math.sqrt; 2 | export function square(x) { 3 | return x * x; 4 | } 5 | export function diag(x, y) { 6 | return sqrt(square(x) + square(y)); 7 | } 8 | -------------------------------------------------------------------------------- /tests/import-export/es6/es6-export-hasOwnProperty.js: -------------------------------------------------------------------------------- 1 | function hasOwnProperty(obj, prop) { 2 | return prop in obj; 3 | } 4 | export { hasOwnProperty }; 5 | -------------------------------------------------------------------------------- /tests/import-export/es6/es6-export-mixed.js: -------------------------------------------------------------------------------- 1 | export default function () { 2 | return 9; 3 | } 4 | export function each() { 5 | return 7; 6 | } 7 | export { each as forEach }; 8 | -------------------------------------------------------------------------------- /tests/import-export/es6/es6-import-default.js: -------------------------------------------------------------------------------- 1 | import myFunc from 'es6-export-default'; 2 | myFunc(); 3 | -------------------------------------------------------------------------------- /tests/import-export/es6/es6-import-default.truth: -------------------------------------------------------------------------------- 1 | es6-import-default.js:global:2 -> es6-export-default.js:anon:1 2 | -------------------------------------------------------------------------------- /tests/import-export/es6/es6-import-entire.js: -------------------------------------------------------------------------------- 1 | import * as lib from 'es6-export-fns'; 2 | console.log(lib.square(11)); // 121 3 | console.log(lib.diag(4, 3)); // 5 4 | -------------------------------------------------------------------------------- /tests/import-export/es6/es6-import-entire.truth: -------------------------------------------------------------------------------- 1 | es6-import-entire.js:global:2 -> Native 2 | es6-import-entire.js:global:2 -> es6-export-fns.js:square:2 3 | es6-import-entire.js:global:3 -> Native 4 | es6-import-entire.js:global:3 -> es6-export-fns.js:diag:5 5 | -------------------------------------------------------------------------------- /tests/import-export/es6/es6-import-hasOwnProperty.js: -------------------------------------------------------------------------------- 1 | import { hasOwnProperty } from 'es6-export-hasOwnProperty'; 2 | console.log(hasOwnProperty({'prop': true}, 'prop')); 3 | -------------------------------------------------------------------------------- /tests/import-export/es6/es6-import-hasOwnProperty.truth: -------------------------------------------------------------------------------- 1 | es6-import-hasOwnProperty.js:global:2 -> es6-export-hasOwnProperty.js:hasOwnProperty:1 2 | -------------------------------------------------------------------------------- /tests/import-export/es6/es6-import-mixed.js: -------------------------------------------------------------------------------- 1 | import _, { each } from 'es6-export-mixed'; 2 | 3 | _(); 4 | each(); 5 | -------------------------------------------------------------------------------- /tests/import-export/es6/es6-import-mixed.truth: -------------------------------------------------------------------------------- 1 | es6-import-mixed.js:global:3 -> es6-export-mixed.js:anon:1 2 | es6-import-mixed.js:global:4 -> es6-export-mixed.js:each:4 3 | -------------------------------------------------------------------------------- /tests/import-export/es6/es6-import-named.js: -------------------------------------------------------------------------------- 1 | import { square, diag } from 'es6-export-fns'; 2 | console.log(square(11)); // 121 3 | console.log(diag(4, 3)); // 5 4 | -------------------------------------------------------------------------------- /tests/import-export/es6/es6-import-named.truth: -------------------------------------------------------------------------------- 1 | es6-import-named.js:global:2 -> Native 2 | es6-import-named.js:global:2 -> es6-export-fns.js:square:2 3 | es6-import-named.js:global:3 -> Native 4 | es6-import-named.js:global:3 -> es6-export-fns.js:diag:5 5 | -------------------------------------------------------------------------------- /tests/import-export/es6/es6-import-redirect.js: -------------------------------------------------------------------------------- 1 | import {login, logout, addProject, updateProject, executeSearch} from 'redirect/index'; 2 | // import {updateProject} from 'redirect/index'; 3 | login(4); 4 | logout(8, 0); 5 | updateProject(); 6 | addProject(); 7 | executeSearch(1, 2, 3); 8 | -------------------------------------------------------------------------------- /tests/import-export/es6/es6-import-redirect.truth: -------------------------------------------------------------------------------- 1 | es6-import-redirect.js:global:3 -> auth.js:login:1 2 | es6-import-redirect.js:global:4 -> auth.js:logout:5 3 | es6-import-redirect.js:global:5 -> project.js:update:5 4 | es6-import-redirect.js:global:6 -> project.js:addProject:1 5 | es6-import-redirect.js:global:7 -> search.js:executeSearch:1 6 | -------------------------------------------------------------------------------- /tests/import-export/es6/redirect/auth.js: -------------------------------------------------------------------------------- 1 | function login(x) { 2 | return x; 3 | } 4 | 5 | function logout(x, y) { 6 | return x + y; 7 | } 8 | export { login, logout }; 9 | -------------------------------------------------------------------------------- /tests/import-export/es6/redirect/index.js: -------------------------------------------------------------------------------- 1 | export {login, logout} from './auth'; 2 | export {addProject, updateProject} from './project'; 3 | export {executeSearch} from './search'; 4 | -------------------------------------------------------------------------------- /tests/import-export/es6/redirect/project.js: -------------------------------------------------------------------------------- 1 | function addProject() { 2 | return 'whooplah'; 3 | } 4 | 5 | function update() { 6 | return 'dooplah'; 7 | } 8 | 9 | export { addProject, update as updateProject }; 10 | -------------------------------------------------------------------------------- /tests/import-export/es6/redirect/search.js: -------------------------------------------------------------------------------- 1 | function executeSearch(x, y, z) { 2 | return x * y * z; 3 | } 4 | export { executeSearch } 5 | -------------------------------------------------------------------------------- /tests/import-export/module.exports/module-exports-module.js: -------------------------------------------------------------------------------- 1 | // This module directly assigns the constructor function for User to module.exports 2 | let User = function(name, email) { 3 | this.name = name; 4 | this.email = email; 5 | }; 6 | 7 | module.exports = User; 8 | -------------------------------------------------------------------------------- /tests/import-export/module.exports/module-exports-module2.js: -------------------------------------------------------------------------------- 1 | let x = 5; 2 | let addX = function(value) { 3 | return value + x; 4 | }; 5 | 6 | module.exports.x = x; 7 | module.exports.addX = addX; 8 | -------------------------------------------------------------------------------- /tests/import-export/module.exports/module-exports-require.js: -------------------------------------------------------------------------------- 1 | // This file tests directly assigning a constructor function to module.exports 2 | let user = require('./module-exports-module'); 3 | 4 | let u = new user('random user', 'random-user@persper.org'); 5 | console.log("%s's email is %s", u.name, u.email); 6 | -------------------------------------------------------------------------------- /tests/import-export/module.exports/module-exports-require.truth: -------------------------------------------------------------------------------- 1 | module-exports-require.js:global:2 -> Native 2 | module-exports-require.js:global:4 -> module-exports-module.js:anon:2 3 | module-exports-require.js:global:5 -> Native 4 | -------------------------------------------------------------------------------- /tests/import-export/module.exports/module-exports-require2.js: -------------------------------------------------------------------------------- 1 | // This file tests the most common usage of exports/require 2 | let m1 = require('./module-exports-module2'); 3 | console.log("Adding %d to 10 gives us %d", m1.x, m1.addX(10)); 4 | -------------------------------------------------------------------------------- /tests/import-export/module.exports/module-exports-require2.truth: -------------------------------------------------------------------------------- 1 | module-exports-require2.js:global:2 -> Native 2 | module-exports-require2.js:global:3 -> Native 3 | module-exports-require2.js:global:3 -> module-exports-module2.js:anon:2 4 | -------------------------------------------------------------------------------- /tests/jest/graph.test.js: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2018 Persper Foundation 3 | * 4 | * All rights reserved. This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License v2.0 6 | * which accompanies this distribution, and is available at 7 | * http://www.eclipse.org/legal/epl-2.0. 8 | *******************************************************************************/ 9 | 10 | const graph = require('../../src/graph'); 11 | const flowgraph = require('../../src/flowgraph'); 12 | const astutil = require('../../src/astutil'); 13 | /* 14 | test('TEST FORMAT', () => { 15 | const G = new graph.Graph(); 16 | 17 | var nodeA = new Node('A'); 18 | var nodeB = new Node('B'); 19 | 20 | }); 21 | */ 22 | 23 | fn1 = { 24 | "type": "FunctionDeclaration", 25 | "id": { 26 | "type": "Identifier", 27 | "name": "f", 28 | "range": [ 29 | 9, 30 | 10 31 | ], 32 | "loc": { 33 | "start": { 34 | "line": 1, 35 | "column": 9 36 | }, 37 | "end": { 38 | "line": 1, 39 | "column": 10 40 | } 41 | } 42 | }, 43 | "params": [], 44 | "body": { 45 | "type": "BlockStatement", 46 | "body": [], 47 | "range": [ 48 | 13, 49 | 15 50 | ], 51 | "loc": { 52 | "start": { 53 | "line": 1, 54 | "column": 13 55 | }, 56 | "end": { 57 | "line": 1, 58 | "column": 15 59 | } 60 | } 61 | }, 62 | "generator": false, 63 | "expression": false, 64 | "async": false, 65 | "range": [ 66 | 0, 67 | 15 68 | ], 69 | "loc": { 70 | "start": { 71 | "line": 1, 72 | "column": 0 73 | }, 74 | "end": { 75 | "line": 1, 76 | "column": 15 77 | } 78 | } 79 | } 80 | fn2 = { 81 | "type": "FunctionDeclaration", 82 | "id": { 83 | "type": "Identifier", 84 | "name": "g", 85 | "range": [ 86 | 10, 87 | 11 88 | ], 89 | "loc": { 90 | "start": { 91 | "line": 2, 92 | "column": 9 93 | }, 94 | "end": { 95 | "line": 2, 96 | "column": 10 97 | } 98 | } 99 | }, 100 | "params": [], 101 | "body": { 102 | "type": "BlockStatement", 103 | "body": [], 104 | "range": [ 105 | 14, 106 | 16 107 | ], 108 | "loc": { 109 | "start": { 110 | "line": 2, 111 | "column": 13 112 | }, 113 | "end": { 114 | "line": 2, 115 | "column": 15 116 | } 117 | } 118 | }, 119 | "generator": false, 120 | "expression": false, 121 | "async": false, 122 | "range": [ 123 | 1, 124 | 16 125 | ], 126 | "loc": { 127 | "start": { 128 | "line": 2, 129 | "column": 0 130 | }, 131 | "end": { 132 | "line": 2, 133 | "column": 15 134 | } 135 | } 136 | } 137 | 138 | fn3 = { 139 | "type": "FunctionDeclaration", 140 | "id": { 141 | "type": "Identifier", 142 | "name": "h", 143 | "range": [ 144 | 11, 145 | 12 146 | ], 147 | "loc": { 148 | "start": { 149 | "line": 3, 150 | "column": 9 151 | }, 152 | "end": { 153 | "line": 3, 154 | "column": 10 155 | } 156 | } 157 | }, 158 | "params": [], 159 | "body": { 160 | "type": "BlockStatement", 161 | "body": [], 162 | "range": [ 163 | 15, 164 | 17 165 | ], 166 | "loc": { 167 | "start": { 168 | "line": 3, 169 | "column": 13 170 | }, 171 | "end": { 172 | "line": 3, 173 | "column": 15 174 | } 175 | } 176 | }, 177 | "generator": false, 178 | "expression": false, 179 | "async": false, 180 | "range": [ 181 | 2, 182 | 17 183 | ], 184 | "loc": { 185 | "start": { 186 | "line": 3, 187 | "column": 0 188 | }, 189 | "end": { 190 | "line": 3, 191 | "column": 15 192 | } 193 | } 194 | } 195 | 196 | fn1.attr = {} 197 | fn2.attr = {} 198 | fn3.attr = {} 199 | 200 | fn_vertex1 = flowgraph.funcVertex(fn1); 201 | fn_vertex2 = flowgraph.funcVertex(fn2); 202 | fn_vertex3 = flowgraph.funcVertex(fn3); 203 | 204 | fv1 = fn_vertex1; 205 | fv2 = fn_vertex2; 206 | fv3 = fn_vertex3; 207 | 208 | test('Add edge between two nodes and check graph has nodes', () => { 209 | const G = new graph.Graph(); 210 | 211 | G.addEdge(fv1, fv2); 212 | 213 | expect(G.hasNode(fv1)).toBe(true); 214 | expect(G.hasNode(fv2)).toBe(true); 215 | }); 216 | 217 | test('Add edge between two nodes and check graph has edge between them', () => { 218 | const G = new graph.Graph(); 219 | 220 | G.addEdge(fv1, fv2); 221 | 222 | expect(G.hasEdge(fv1, fv2)).toBe(true); 223 | }); 224 | 225 | test('Add edge, remove it and verify it is removed', () => { 226 | const G = new graph.Graph(); 227 | 228 | G.addEdge(fv1, fv2); 229 | 230 | expect(G.removeEdge(fv1, fv2)).toBe(true); 231 | 232 | expect(G.hasEdge(fv1, fv2)).toBe(false); 233 | }); 234 | 235 | test('Add edge, remove one of the nodes and verify it is removed', () => { 236 | const G = new graph.Graph(); 237 | 238 | G.addEdge(fv1, fv2); 239 | 240 | expect(G.removeNode(fv2)).toBe(true); 241 | 242 | expect(G.hasNode(fv2)).toBe(false); 243 | expect(G.hasEdge(fv1, fv2)).toBe(false); 244 | }); 245 | 246 | test('Add edge and verify the reverse direction edge is not in the graph', () => { 247 | const G = new graph.Graph(); 248 | 249 | G.addEdge(fv1, fv2); 250 | 251 | expect(G.hasEdge(fv2, fv1)).toBe(false); 252 | }); 253 | 254 | test('Add many edges, remove the nodes and verify they are all removed', () => { 255 | const G = new graph.Graph(); 256 | 257 | var nodes = []; 258 | 259 | function make_pp(s) { 260 | return function() { 261 | return 'Func(' + s + ')' 262 | } 263 | } 264 | 265 | for (var i = 0; i < 1000; ++i) 266 | nodes[i] = { 267 | 'name': i.toString(), 268 | 'type': 'FuncVertex', 269 | 'attr': {pp: make_pp(i.toString()) }, 270 | }; 271 | 272 | for (var i = 0; i < 999; ++i) 273 | G.addEdge(nodes[i], nodes[i + 1]); 274 | 275 | for (var i = 0; i < 1000; ++i) 276 | G.removeNode(nodes[i]); 277 | 278 | for (var i = 0; i < 1000; ++i) 279 | expect(G.hasNode(nodes[i])).toBe(false); 280 | 281 | for (var i = 0; i < 999; ++i) 282 | expect(G.hasEdge(nodes[i], nodes[i + 1])).toBe(false); 283 | }); 284 | 285 | test('Add many edges, remove the edges and verify they are all removed', () => { 286 | const G = new graph.Graph(); 287 | 288 | var nodes = []; 289 | 290 | function make_pp(s) { 291 | return function() { 292 | return 'Func(' + s + ')' 293 | } 294 | } 295 | 296 | for (var i = 0; i < 1000; ++i) 297 | nodes[i] = { 298 | 'name': i.toString(), 299 | 'type': 'FuncVertex', 300 | 'attr': {pp: make_pp(i.toString()) }, 301 | }; 302 | 303 | for (var i = 0; i < 999; ++i) 304 | G.addEdge(nodes[i], nodes[i + 1]); 305 | 306 | for (var i = 0; i < 999; ++i) 307 | G.removeEdge(nodes[i], nodes[i + 1]); 308 | 309 | for (var i = 0; i < 1000; ++i) 310 | expect(G.hasNode(nodes[i])).toBe(true); 311 | 312 | for (var i = 0; i < 999; ++i) 313 | expect(G.hasEdge(nodes[i], nodes[i + 1])).toBe(false); 314 | }); 315 | 316 | test('Add nodes and verify they were added', () => { 317 | const G = new graph.Graph(); 318 | 319 | G.addNode(fv1); 320 | G.addNode(fv2); 321 | G.addNode(fv3); 322 | 323 | expect(G.hasNode(fv1)).toBe(true); 324 | expect(G.hasNode(fv2)).toBe(true); 325 | expect(G.hasNode(fv3)).toBe(true); 326 | }); 327 | 328 | test('Add nodes, remove them and verify they were removed', () => { 329 | const G = new graph.Graph(); 330 | 331 | G.addNode(fv1); 332 | G.addNode(fv2); 333 | G.addNode(fv3); 334 | 335 | G.removeNode(fv1); 336 | G.removeNode(fv2); 337 | G.removeNode(fv3); 338 | 339 | expect(G.hasNode(fv1)).toBe(false); 340 | expect(G.hasNode(fv2)).toBe(false); 341 | expect(G.hasNode(fv3)).toBe(false); 342 | }); 343 | 344 | test('Add nodes, add edge, verify graph has edges and nodes', () => { 345 | const G = new graph.Graph(); 346 | 347 | G.addNode(fv1); 348 | G.addNode(fv2); 349 | 350 | G.addEdge(fv1, fv2); 351 | 352 | expect(G.hasNode(fv1)).toBe(true); 353 | expect(G.hasNode(fv2)).toBe(true); 354 | 355 | expect(G.hasEdge(fv1, fv2)).toBe(true); 356 | }); 357 | 358 | test('Add nodes, applies function to all nodes and verifies changes are made', () => { 359 | const G = new graph.Graph(); 360 | 361 | G.addNode(fv1); 362 | G.addNode(fv2); 363 | G.addNode(fv3); 364 | 365 | G.iterNodes((nd) => { nd.func.id.name = nd.func.id.name.toLowerCase(); }); 366 | 367 | expect(fv1.func.id.name).toBe('f'); 368 | expect(fv2.func.id.name).toBe('g'); 369 | expect(fv3.func.id.name).toBe('h'); 370 | }); 371 | -------------------------------------------------------------------------------- /tests/jest/linkedList.test.js: -------------------------------------------------------------------------------- 1 | 2 | const { LinkedList } = require('../../src/linkedList'); 3 | 4 | test('test LinkedList', () => { 5 | let ll = new LinkedList(); 6 | expect(ll.isEmpty()).toBe(true); 7 | ll.add(10); 8 | expect(ll.size).toBe(1); 9 | ll.add(20); 10 | ll.add(30); 11 | ll.add(40); 12 | ll.add(50); 13 | expect(ll.size).toBe(5); 14 | expect(ll.has(30)).toBe(true); 15 | expect(ll.has(10)).toBe(true); 16 | ll.remove(30); 17 | ll.remove(10); 18 | expect(ll.has(30)).toBe(false); 19 | expect(ll.has(10)).toBe(false); 20 | expect(ll.size).toBe(3); 21 | let lst = []; 22 | for (let lnode of ll) { 23 | lst.push(lnode); 24 | } 25 | expect(lst).toStrictEqual([50, 40, 20]); 26 | ll.remove(50); 27 | ll.remove(40); 28 | ll.remove(20); 29 | expect(ll.size).toBe(0); 30 | expect(ll.isEmpty()).toBe(true); 31 | }); 32 | -------------------------------------------------------------------------------- /tests/limits/history.js: -------------------------------------------------------------------------------- 1 | // This file illustrates one of the limitations of the call graph algorithm 2 | // Only func2 is called in main function, 3 | // but the algorithm can't rule out the possibility that func1 is called too 4 | function main () { 5 | let a = function func1 () { console.log('func1 is called!'); }; 6 | let b = a; 7 | b = function func2() { console.log('func2 is called!'); }; 8 | a = b; 9 | a(); // func2 is called 10 | b(); // func2 is called 11 | } 12 | 13 | main(); 14 | -------------------------------------------------------------------------------- /tests/limits/history.truth: -------------------------------------------------------------------------------- 1 | history.js:func1:5 -> Native 2 | history.js:func1:7 -> Native 3 | history.js:main:9 -> history.js:func2:7 4 | history.js:main:10 -> history.js:func2:7 5 | history.js:global:13 -> history.js:main:4 6 | -------------------------------------------------------------------------------- /tests/limits/overload.js: -------------------------------------------------------------------------------- 1 | function f() { 2 | return 5; 3 | } 4 | 5 | f(); 6 | 7 | function f(x) { 8 | return x; 9 | } 10 | 11 | f(4); 12 | -------------------------------------------------------------------------------- /tests/limits/overload.truth: -------------------------------------------------------------------------------- 1 | overload.js:global:5 -> overload.js:f:1 2 | overload.js:global:11 -> overload.js:f:7 3 | -------------------------------------------------------------------------------- /tests/process.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | # Author: Alex Stennet 4 | # Description: Provides a testing framework for the call graph genereation 5 | # 6 | # An example of how to run: 7 | # ./process.py basics/arrow.js basics/arrow.truth 8 | 9 | 10 | import sys 11 | from pathlib import Path 12 | import re 13 | from required_files import collect_requires 14 | from callgraph import callgraph_formatted 15 | 16 | 17 | def get_output(test_file): 18 | # Recursively descend through require's to find all files that 19 | # must be included to fill the callgraph 20 | required_files = collect_requires(Path(test_file).resolve()) 21 | strd_files = [str(rf) for rf in required_files] 22 | 23 | # Run the javascript call graph program and capture the output 24 | output = callgraph_formatted(strd_files, keep=required_files[0].name, 25 | natives=False) 26 | 27 | return output 28 | 29 | 30 | def precision_recall(test_file, expected_output): 31 | fo = get_output(test_file) 32 | 33 | # Reading in expected output file and comparing with output 34 | f = open(expected_output) 35 | 36 | lines = [line.rstrip('\n') for line in f if 'Native' not in line] 37 | 38 | output_lines = set(fo) 39 | expected_lines = set(lines) 40 | 41 | intersection = output_lines & expected_lines 42 | difference = output_lines ^ expected_lines 43 | 44 | if len(difference) != 0: 45 | missing_output = expected_lines - output_lines 46 | extra_output = output_lines - expected_lines 47 | 48 | print() 49 | 50 | for l in intersection: 51 | print('\t' + l) 52 | 53 | for l in extra_output: 54 | print('\t+', l) 55 | 56 | for l in missing_output: 57 | print('\t-', l) 58 | 59 | if len(output_lines) > 0: 60 | precision = 100 * len(intersection) // len(output_lines) 61 | else: 62 | precision = 0 63 | 64 | recall = 100*len(intersection) // len(expected_lines) 65 | 66 | return precision, recall 67 | 68 | 69 | if __name__ == "__main__": 70 | assert len(sys.argv) == 3,\ 71 | "Incorrect number of arguments: process.py FILENAME TEST_FILE" 72 | 73 | precision_recall(sys.argv[1], sys.argv[2]) 74 | -------------------------------------------------------------------------------- /tests/required_files.py: -------------------------------------------------------------------------------- 1 | import re 2 | from pathlib import Path 3 | 4 | reg1 = re.compile("require\\(\'(.*)\'\\)") 5 | reg2 = re.compile("import .* from \'(.*)\'") 6 | reg3 = re.compile("import \'(.*)\'(?! from)") 7 | reg4 = re.compile("export .* from \'(.*)\'") 8 | 9 | 10 | def match_to_path(match): 11 | if match.endswith('.js') or match.endswith('.ts') or match.endswith('.vue'): 12 | return match 13 | else: 14 | return match + '.js' 15 | 16 | 17 | def get_requires(path): 18 | """ Returns list of imports in path """ 19 | f = path.open() 20 | text = f.read() 21 | f.close() 22 | 23 | matches1 = reg1.findall(text) 24 | matches2 = reg2.findall(text) 25 | matches3 = reg3.findall(text) 26 | matches4 = reg4.findall(text) 27 | 28 | # lst1 = [m + '.js' for m in matches1] 29 | # lst2 = [m + '.js' for m in matches2] 30 | # lst3 = [m + '.js' for m in matches3] 31 | # lst4 = [m + '.js' for m in matches4] 32 | 33 | lst1 = [match_to_path(m) for m in matches1] 34 | lst2 = [match_to_path(m) for m in matches2] 35 | lst3 = [match_to_path(m) for m in matches3] 36 | lst4 = [match_to_path(m) for m in matches4] 37 | 38 | lst = lst1 + lst2 + lst3 + lst4 39 | 40 | return lst 41 | 42 | 43 | def collect_requires(path): 44 | """ Recursively descends imports and returns all imported files """ 45 | read_files = [] 46 | unread_files = [path] 47 | 48 | while unread_files: 49 | uf = unread_files.pop(0) 50 | 51 | if uf in read_files: 52 | continue 53 | 54 | read_files.append(uf.resolve()) 55 | required_files = get_requires(uf) 56 | 57 | for rf in required_files: 58 | rp = uf.parent.joinpath(rf) 59 | if rp and rp.exists(): 60 | unread_files.append(rp.resolve()) 61 | 62 | return read_files 63 | -------------------------------------------------------------------------------- /tests/test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import os, sys, glob, time 4 | from os.path import isfile, join, dirname, isdir, basename 5 | from process import precision_recall 6 | 7 | # -------------- Configuration -------------- 8 | 9 | # Only scan the following directories for test cases 10 | test_dirs = ['basics', 11 | 'unexpected', 12 | 'classes', 13 | 'es6', 14 | 'import-export/define', 15 | 'import-export/es6', 16 | 'import-export/module.exports', 17 | 'typescript', 18 | 'vue'] 19 | 20 | # Only consider test cases in the following languages 21 | langs = ['*.js', '*.ts', '*.vue'] 22 | 23 | # ------------------------------------------- 24 | 25 | print() 26 | print('RUNNING REGRESSION TESTS') 27 | 28 | test_root_dir = dirname(sys.argv[0]) 29 | num_passed = 0 30 | num_failed = 0 31 | 32 | 33 | def find_truth_file(test_file): 34 | truth_file = test_file.rsplit('.', 1)[0] + '.truth' 35 | if os.path.isfile(truth_file): 36 | return truth_file 37 | else: 38 | return None 39 | 40 | start = time.time() 41 | 42 | for d in test_dirs: 43 | print('\n' + '='*5, d, '='*5 + '\n') 44 | d = join(test_root_dir, d) 45 | 46 | for lang in langs: 47 | for test_file in glob.iglob(d + '/**/' + lang, recursive=True): 48 | truth_file = find_truth_file(test_file) 49 | if not truth_file: 50 | continue 51 | precision, recall = precision_recall(test_file, truth_file) 52 | 53 | print(basename(test_file), end='') 54 | if precision == 100 and recall == 100: 55 | print('\r' + test_file + ' ✓') 56 | num_passed += 1 57 | else: 58 | print('\rFAILED: ' + test_file + ' ❌\n') 59 | num_failed += 1 60 | 61 | end = time.time() 62 | 63 | print() 64 | print('Number passed: ' + str(num_passed)) 65 | print('Number failed: ' + str(num_failed)) 66 | print('Total time: ' + str(end - start)) 67 | 68 | if num_failed > 0: 69 | exit(1) 70 | -------------------------------------------------------------------------------- /tests/typescript/simple.truth: -------------------------------------------------------------------------------- 1 | simple.ts:global:7 -> simple.ts:greeter:1 2 | -------------------------------------------------------------------------------- /tests/typescript/simple.ts: -------------------------------------------------------------------------------- 1 | function greeter(person: string) { 2 | return "Hello, " + person; 3 | } 4 | 5 | let user = "Jane User"; 6 | 7 | document.body.innerHTML = greeter(user); 8 | -------------------------------------------------------------------------------- /tests/unexpected/stringiterator.js: -------------------------------------------------------------------------------- 1 | // this code is extracted from Libraries/vendor/core/toIterator.js of the following commit 2 | // https://github.com/facebook/react-native/commit/a15603d8f1ecdd673d80be318293cee53eb4475d 3 | class StringIterator { 4 | // 21.1.5.1 CreateStringIterator Abstract Operation 5 | constructor(string) { 6 | if (typeof string !== 'string') { 7 | throw new TypeError('Object is not a string'); 8 | } 9 | this._iteratedString = string; 10 | this._nextIndex = 0; 11 | } 12 | 13 | // 21.1.5.2.1 %StringIteratorPrototype%.next() 14 | next() { 15 | if (!this instanceof StringIterator) { 16 | throw new TypeError('Object is not a StringIterator'); 17 | } 18 | 19 | if (this._iteratedString == null) { 20 | return createIterResultObject(undefined, true); 21 | } 22 | 23 | var index = this._nextIndex; 24 | var s = this._iteratedString; 25 | var len = s.length; 26 | 27 | if (index >= len) { 28 | this._iteratedString = undefined; 29 | return createIterResultObject(undefined, true); 30 | } 31 | 32 | var ret; 33 | var first = s.charCodeAt(index); 34 | 35 | if (first < 0xD800 || first > 0xDBFF || index + 1 === len) { 36 | ret = s[index]; 37 | } else { 38 | var second = s.charCodeAt(index + 1); 39 | if (second < 0xDC00 || second > 0xDFFF) { 40 | ret = s[index]; 41 | } else { 42 | ret = s[index] + s[index + 1]; 43 | } 44 | } 45 | 46 | this._nextIndex = index + ret.length; 47 | 48 | return createIterResultObject(ret, false); 49 | } 50 | 51 | // 21.1.5.2.2 %StringIteratorPrototype%[@@ITERATOR_SYMBOL]() 52 | '@@iterator'() { 53 | return this; 54 | } 55 | } 56 | 57 | function createIterResultObject(value, done) { 58 | return {value: value, done: done}; 59 | } 60 | -------------------------------------------------------------------------------- /tests/unexpected/stringiterator.truth: -------------------------------------------------------------------------------- 1 | stringiterator.js:next:48 -> stringiterator.js:createIterResultObject:57 2 | stringiterator.js:next:29 -> stringiterator.js:createIterResultObject:57 3 | stringiterator.js:next:20 -> stringiterator.js:createIterResultObject:57 4 | -------------------------------------------------------------------------------- /tests/unhandled/classes/class-compiled.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 4 | 5 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 6 | 7 | var Rectangle = function () { 8 | function Rectangle(height, width) { 9 | _classCallCheck(this, Rectangle); 10 | 11 | this.height = height; 12 | this.width = width; 13 | } 14 | // Getter 15 | 16 | 17 | _createClass(Rectangle, [{ 18 | key: "calcArea", 19 | 20 | // Method 21 | value: function calcArea() { 22 | return this.height * this.width; 23 | } 24 | }, { 25 | key: "area", 26 | get: function get() { 27 | return this.calcArea(); 28 | } 29 | }]); 30 | 31 | return Rectangle; 32 | }(); 33 | 34 | var square = new Rectangle(10, 10); 35 | 36 | console.log(square.area); 37 | 38 | -------------------------------------------------------------------------------- /tests/unhandled/classes/class-compiled.truth: -------------------------------------------------------------------------------- 1 | class-compiled.js:global:3 -> class-compiled.js:anon:3 2 | class-compiled.js:defineProperties:3 -> Native 3 | class-compiled.js:anon:3 -> class-compiled.js:defineProperties:3 4 | class-compiled.js:anon:3 -> class-compiled.js:defineProperties:3 5 | class-compiled.js:_classCallCheck:5 -> Native 6 | class-compiled.js:global:7 -> class-compiled.js:anon:7 7 | class-compiled.js:Rectangle:9 -> class-compiled.js:_classCallCheck:5 8 | class-compiled.js:anon:17 -> class-compiled.js:_createClass:3 9 | class-compiled.js:get:27 -> class-compiled.js:calcArea:21 10 | class-compiled.js:global:34 -> class-compiled.js:Rectangle:8 11 | class-compiled.js:global:36 -> Native 12 | class-compiled.js:global:36 -> class-compiled.js:get:26 13 | -------------------------------------------------------------------------------- /tests/unhandled/classes/class-compiled2.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 4 | 5 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 6 | 7 | var Rectangle = function () { 8 | function Rectangle(height, width) { 9 | _classCallCheck(this, Rectangle); 10 | 11 | this.height = height; 12 | this.width = width; 13 | } 14 | // Getter 15 | 16 | 17 | _createClass(Rectangle, [{ 18 | key: "calcArea", 19 | 20 | // Method 21 | value: function calcArea() { 22 | return this.height * this.width; 23 | } 24 | }, { 25 | key: "area", 26 | get: function get() { 27 | return this.calcArea(); 28 | } 29 | }]); 30 | 31 | return Rectangle; 32 | }(); 33 | 34 | var square = new Rectangle(10, 10); 35 | 36 | console.log(square.calcArea()); 37 | 38 | -------------------------------------------------------------------------------- /tests/unhandled/classes/class-compiled2.truth: -------------------------------------------------------------------------------- 1 | class-compiled2.js:global:3 -> class-compiled2.js:anon:3 2 | class-compiled2.js:defineProperties:3 -> Native 3 | class-compiled2.js:anon:3 -> class-compiled2.js:defineProperties:3 4 | class-compiled2.js:anon:3 -> class-compiled2.js:defineProperties:3 5 | class-compiled2.js:_classCallCheck:5 -> Native 6 | class-compiled2.js:global:7 -> class-compiled2.js:anon:7 7 | class-compiled2.js:Rectangle:9 -> class-compiled2.js:_classCallCheck:5 8 | class-compiled2.js:anon:17 -> class-compiled2.js:_createClass:3 9 | class-compiled2.js:get:27 -> class-compiled2.js:calcArea:21 10 | class-compiled2.js:global:34 -> class-compiled2.js:Rectangle:8 11 | class-compiled2.js:global:36 -> Native 12 | class-compiled2.js:global:36 -> class-compiled2.js:calcArea:21 13 | -------------------------------------------------------------------------------- /tests/unhandled/classes/class-getter.js: -------------------------------------------------------------------------------- 1 | class Rectangle { 2 | constructor(height, width) { 3 | this.height = height; 4 | this.width = width; 5 | } 6 | // Getter 7 | get area() { 8 | return this.calcArea(); 9 | } 10 | // Method 11 | calcArea() { 12 | return this.height * this.width; 13 | } 14 | } 15 | 16 | const square = new Rectangle(10, 10); 17 | 18 | console.log(square.area); 19 | -------------------------------------------------------------------------------- /tests/unhandled/classes/class-getter.truth: -------------------------------------------------------------------------------- 1 | class-getter.js:area:8 -> class-getter.js:calcArea:11 2 | class-getter.js:global:16 -> class-getter.js:constructor:2 3 | class-getter.js:global:18 -> Native 4 | class-getter.js:global:18 -> class-getter.js:area:7 5 | -------------------------------------------------------------------------------- /tests/unhandled/limits/history.js: -------------------------------------------------------------------------------- 1 | // This file illustrates one of the limitations of the call graph algorithm 2 | // Only func2 is called in main function, 3 | // but the algorithm can't rule out the possibility that func1 is called too 4 | function main () { 5 | let a = function func1 () { console.log('func1 is called!'); }; 6 | let b = a; 7 | b = function func2() { console.log('func2 is called!'); }; 8 | a = b; 9 | a(); // func2 is called 10 | b(); // func2 is called 11 | } 12 | 13 | main(); 14 | -------------------------------------------------------------------------------- /tests/unhandled/limits/history.truth: -------------------------------------------------------------------------------- 1 | history.js:func1:5 -> Native 2 | history.js:func1:7 -> Native 3 | history.js:main:9 -> history.js:func2:7 4 | history.js:main:10 -> history.js:func2:7 5 | history.js:global:13 -> history.js:main:4 6 | -------------------------------------------------------------------------------- /tests/unhandled/limits/overload.js: -------------------------------------------------------------------------------- 1 | function f() { 2 | return 5; 3 | } 4 | 5 | f(); 6 | 7 | function f(x) { 8 | return x; 9 | } 10 | 11 | f(4); 12 | -------------------------------------------------------------------------------- /tests/unhandled/limits/overload.truth: -------------------------------------------------------------------------------- 1 | overload.js:global:5 -> overload.js:f:1 2 | overload.js:global:11 -> overload.js:f:7 3 | -------------------------------------------------------------------------------- /tests/vue/TodoList.truth: -------------------------------------------------------------------------------- 1 | TodoList.vue:addTodo:62 -> example.vue:data:10 2 | TodoList.vue:addTodo:62 -> TodoList.vue:data:33 3 | -------------------------------------------------------------------------------- /tests/vue/TodoList.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | -------------------------------------------------------------------------------- /tests/vue/example.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 18 | 19 | 24 | --------------------------------------------------------------------------------