{"id":15432845,"url":"https://github.com/ojj11/analyse-control","last_synced_at":"2025-04-19T17:46:43.216Z","repository":{"id":29237088,"uuid":"88187481","full_name":"ojj11/analyse-control","owner":"ojj11","description":"Control flow analysis for JavaScript","archived":false,"fork":false,"pushed_at":"2022-12-30T20:31:06.000Z","size":1081,"stargazers_count":11,"open_issues_count":2,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2024-10-18T07:40:17.829Z","etag":null,"topics":["control-flow","javascript","nodejs"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ojj11.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-04-13T16:59:31.000Z","updated_at":"2024-04-29T15:30:13.000Z","dependencies_parsed_at":"2023-01-14T14:26:28.602Z","dependency_job_id":null,"html_url":"https://github.com/ojj11/analyse-control","commit_stats":null,"previous_names":[],"tags_count":8,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ojj11%2Fanalyse-control","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ojj11%2Fanalyse-control/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ojj11%2Fanalyse-control/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ojj11%2Fanalyse-control/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ojj11","download_url":"https://codeload.github.com/ojj11/analyse-control/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249752151,"owners_count":21320450,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["control-flow","javascript","nodejs"],"created_at":"2024-10-01T18:28:49.288Z","updated_at":"2025-04-19T17:46:43.194Z","avatar_url":"https://github.com/ojj11.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"Analyse-Control\n===============\n\n![Node.js CI](https://github.com/ojj11/analyse-control/workflows/Node.js%20CI/badge.svg) [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fojj11%2Fanalyse-control.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2Fojj11%2Fanalyse-control?ref=badge_shield)\n\n**Extract the control flow graph from a script.**\nControl flow refers to what order a set of instructions execute in. By using\nconditional statements and loops, the order of a set of instructions can be\nchanged. This library extracts all possible execution flows through a script as\na graph of nodes.\n\n    Author: Olli Jones\n    License: MIT\n\n```zsh\nnpm install analyse-control --save\n```\n\n## Introduction\n\n[ECMAScript 5](https://github.com/estree/estree/blob/master/es5.md) and below are supported.\n\nGiven this example piece of [ECMAScript 5](https://github.com/estree/estree/blob/master/es5.md) (JavaScript) as input:\n\n```javascript\n{\n  helloWorld();\n}\n```\n\nAn abstract syntax tree (AST) generated by\n[acorn](https://www.npmjs.com/package/acorn) would look similar to this:\n\n```json\n{\n  \"type\": \"Program\",\n  \"body\": [\n    {\n      \"type\": \"BlockStatement\",\n      \"body\": [\n        {\n          \"type\": \"ExpressionStatement\",\n          \"expression\": {\n            \"type\": \"CallExpression\",\n            \"callee\": {\n              \"type\": \"Identifier\",\n              \"name\": \"helloWorld\"\n            },\n            \"arguments\": []\n          }\n        }\n      ]\n    }\n  ]\n}\n```\n\nThis contains 5 nested AST types:\n\n - [Program](https://github.com/estree/estree/blob/master/es5.md#programs)\n - [BlockStatement](https://github.com/estree/estree/blob/master/es5.md#blockstatement)\n - [ExpressionStatement](https://github.com/estree/estree/blob/master/es5.md#expressionstatement)\n - [CallExpression](https://github.com/estree/estree/blob/master/es5.md#callexpression)\n - [Identifier](https://github.com/estree/estree/blob/master/es5.md#identifier)\n\nWhen thinking about how this program would execute in an interpreter, we would\nexpect the code to step through the elements, first evaluating what value is\nset for `helloWorld`, then calling the function `helloWorld()`, etc, until\nthe Program has been fully evaluated.\n\nThis library will represent this control flow, as a graph where control moves\nfrom one statement to the next:\n\n - `Program: hoist`\n - `Program: enter`\n - `BlockStatement: enter`\n - `ExpressionStatement: enter`\n - `CallExpression: enter`\n - `Identifier: enter`\n - `Identifier: exit`\n - `CallExpression: exit`\n - `ExpressionStatement: exit`\n - `BlockStatement: exit`\n - `Program: exit`\n\n\n \u003e *Hoist* refers to variable hoisting, there's a good\n [w3 tutorial](https://www.w3schools.com/js/js_hoisting.asp) that gives a good\n overview\n\nAnalyse-control allows for the inspection of possible control flow execution\nwithout actually executing any of the given code.\n\nThe bullet point list above, for example, can be generated using:\n\n```javascript\nvar flow = require(\"analyse-control\")(ast).getStartOfFlow();\n\nwhile(flow != undefined) {\n  if (flow.isHoist()) {\n    console.log(flow.getNode().type + \": hoist\");\n  }\n  if (flow.isEnter()) {\n    console.log(flow.getNode().type + \": enter\");\n  }\n  if (flow.isExit()) {\n    console.log(flow.getNode().type + \": exit\");\n  }\n  flow = flow.getForwardFlows()[0];\n}\n```\n\nNotice that `getForwardFlows()` returns an array. This is because:\n - When encountering a fork in the control flow graph, ie. an `IfStatement`,\n    `WhileStatement`, or similar conditional expression: both possible control\n    flow progressions are returned.\n - The control flow could also terminate (in non-looping programs) through the\n    use of a `ThrowStatement` or the program naturally coming to the end of the script: then the array will be empty.\n\nThe example code below shows how this functionality can be used to calculate\nthe unique number of possible execution paths through a branching algorithm:\n\n## Example usage\n\n  \u003e Look in [./test/integration.js](./test/integration.js) for more examples\n\n```javascript\nvar analyse = require(\"analyse-control\");\nvar acorn = require(\"acorn\");\n\nvar ast = acorn.parse([\n  \"if (x) {\",\n  \"  hello();\",\n  \"} else {\",\n  \"  world();\",\n  \"}\"\n].join(\"\\n\"));\n\nvar flow = analyse(ast);\n\nfunction countBranches(node, visited) {\n  var nodeId = node.getId();\n  var outgoing = node.getForwardFlows();\n  if (visited.indexOf(nodeId) != -1) {\n    // we've visited this node before - we're in a loop\n    return Infinity;\n  }\n  if (outgoing.length == 0) {\n    // we've reached the termination of this single control flow\n    return 1;\n  }\n  // we can count the number of execution paths, by adding up the\n  // counts from recursively exploring all branches from this node:\n  return outgoing.reduce((counter, node) =\u003e (\n    counter + countBranches(node, visited.concat(nodeId))\n  ), 0);\n}\n\nconsole.log(\n  \"There are \" +\n  countBranches(flow.getStartOfFlow(), []) +\n  \" possible paths through the code\");\n// prints \"There are 2 possible paths through the code\"\n```\n\nAppending an additional `IfStatement` should correctly return \"4 possible\npaths\". Similarly adding a `WhileStatement` will return \"Infinity possible\npaths\" (because the loop has infinite variations on how many times it will\nexecute).\n\n## API documentation\n\n### `analyse(ast: AST) : Graph`\n\n`require('analyse-control')` returns a method that when given an AST (abstract\nsyntax tree) from a parser like [acorn](https://www.npmjs.com/package/acorn) it\nwill return a control flow graph \"Graph\".\n\n### `Graph.getStartOfFlow() : FlowNode`\n\nThe control flow graph can either be traversed from start to finish, or from\nfinish to start. `getStartOfFlow()` will get the first executed node of the\nAST. This will probably be of type [Program](https://github.com/estree/estree/blob/master/es5.md#programs).\n\n`Graph.getStartOfFlow().isHoist()` will be true.\n\n### `Graph.getEndOfFlow() : FlowNode`\n\nSimilar to the method above, but this method will return the last executed\nnode, which will also be [Program](https://github.com/estree/estree/blob/master/es5.md#programs).\n\n`Graph.getEndOfFlow().isExit()` will be true.\n\n### `FlowNode.isHoist() : Boolean`\n\nJavaScript programs have their `var` definitions hoisted.\n\nFor example:\n```javascript\nvar x = \"hello\";\nfunction y() {\n  return x;\n  var x;\n}\ny() == undefined;\n```\n\nEven though `x` is seemingly defined as `\"hello\"` inside `y()`, the second\ndeclaration inside the function is *hoisted* before the return statement. Thus\nthe output is `undefined`.\n\nThis library will correctly return `hoist` flows to cover this behaviour. The\nFlowNodes will be marked as `isHoist() == true`\n\nNote this library adheres to the\n[ECMAScript 5](http://www.ecma-international.org/ecma-262/5.1/#sec-13)\nhoisting definition, where function declarations inside statements are not\nhoisted.\n\n### `FlowNode.isEnter() : Boolean`\n\nFlow can enter and exit statements, for example we'll first enter a\nBlockStatement, execute any statements inside, and then exit the BlockStatement.\n\nThis method will confirm whether we are entering into the statement by returning\ntrue.\n\n### `FlowNode.isExit() : Boolean`\n\nFlow can enter and exit statements, for example we'll first enter a\nBlockStatement, execute any statements inside, and then exit the BlockStatement.\n\nThis method will confirm whether we are exiting the statement by returning true.\n\n### `FlowNode.getId() : Number | String`\n\nReturns a unique value for this flow, encapsulating whether it represents\nentering, exiting or hoisting a statement, and which statement specifically.\n\nThis can be used for detecting cycles, or for memoizing dynamic programming\ncalls.\n\nUsually this will be a positive integer, but where there aren't enough unique\nintegers to represent every FlowNode then both integers and strings will be\nused.\n\n### `FlowNode.getForwardFlows() : [FlowNode]`\n\nThis will get all nodes that could be executed next.\n\nCommonly used in combination with `Graph.getStartOfFlow()`.\n\nFor example if we're in the conditional of an `IfStatement`, the next executed\nstatement could be either the consequent or alternate.\n\nThe return value is an array of all nodes, which can be an array of a single\nvalue when there are no conditional statements following this FlowNode.\nIt can be an array of two FlowNodes, where there is a conditional.\nIt can be an empty array when there are no statements to execute after this one,\nfor example this statement is the last FlowNode in a Program.\n\n### `FlowNode.getBackwardsFlows() : [FlowNode]`\n\nThis will get all nodes that flowed into this node.\n\nCommonly used in combination with `Graph.getEndOfFlow()`.\n\nFor example if we're inside an if consequent or alternate, the backwards flows\nwould be the conditional statement that led up to the execution of this node.\n\nThe return value is an array of all nodes, which can be an array of a single\nnode when there were no conditionals leading up to the current FlowNode, it can\nbe an array of two FlowNodes when this statement follows a conditional\nstatement. Finally it can be an empty array when the FlowNode is unreachable,\nfor example because of throw or return statement.\n\n### `FlowNode.getNode() : ASTNode`\n\nGets a representation for the node which is similar to the input AST, however,\nthe inner ASTNodes will be replaced by numerical placeholders. Use `Graph.getNode(id)` to resolve these references.\n\n### `Graph.getNode(id: Number) : ASTNode`\n\nThis will return a representation similar to a node in the input AST, however,\nit will have had all inner ASTNodes replaced by numerical placeholders. Use the\nmethod recursively to obtain a copy of the original AST.\n\n## Installing dependencies and running tests\n\n    npm it\n\n## Running visualiser\n\n![Image of visualisation tool](docs/flow_animation.gif)\n\n\u003e Use `npx analyse-control ./path/to/script.js` to see a visualisation of the\n  control flow graph of a given script\n\n## Known issues\n\n - Only [ECMAScript 5](https://github.com/estree/estree/blob/master/es5.md) is\n   supported\n\n - Does not model exception flow, it only models flows from an explicit `throws`\n   statement through to a `catch` block\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fojj11%2Fanalyse-control","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fojj11%2Fanalyse-control","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fojj11%2Fanalyse-control/lists"}