Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

callgraph

Package Overview
Dependencies
Maintainers
1
Versions
10
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

callgraph - npm Package Compare versions

Comparing version 1.0.3 to 1.0.4

callgraph.dot

210

index.js

@@ -11,117 +11,135 @@ // TODO take scope in consideration when parsing calls

const UglifyJS = require('uglify-js')
const babel = require('babel-core')
const execSync = require('child_process').execSync;
const fs = require('fs');
const path = require('path')
const execSync = require('child_process').execSync;
const promisify = require('es6-promisify')
const UglifyJS = require('uglify-js')
const queue = require('queue')
const localize = (currentFile, fn) => `[${currentFile}]${fn}`
const fsReadFile = promisify(fs.readFile)
const readFile = file => fsReadFile(file, 'utf-8')
const inputPath = path.parse(input)
let files = [inputPath.base]
const calls = []
const definedFunctions = []
const rootDir = path.join('./', inputPath.dir)
const q = queue()
const processedFiles = []
while (files.length > 0) {
const currentFile = files[0].replace(/\.js$/, '')
files.shift()
let totalRunning = 0
function processFile(requiredFile, cb) {
if (processedFiles.indexOf(requiredFile) > -1) return cb()
processedFiles.push(requiredFile)
totalRunning++
const currentFile = requiredFile.replace(/\.js$/, '')
const file = path.join(rootDir, currentFile+'.js')
console.log('file', file)
let babelOk = false
try {
execSync('./node_modules/.bin/babel -o bundle.js '+ file)
babelOk = true
} catch (e) {
console.log(`Couldn't read file ${file}`)
console.log(e.message)
break
}
console.log(file)
readFile(file)
.then(es6 => {
let code
try {
code = babel
.transform(es6, {
presets: ['es2015'],
}).code
} catch (e) { console.error(e) }
definedFunctions.push(localize(currentFile, 'Program'))
const code = fs.readFileSync('bundle.js', 'utf-8')
const toplevel = UglifyJS.parse(code);
const localModules = []
const walker = new UglifyJS.TreeWalker(function(node){
//check for function calls
if (node instanceof UglifyJS.AST_Definitions) {
// TODO: Should loop though all definitions
if (node.definitions[0].value.expression && node.definitions[0].value.expression.name === 'require') {
const localName = node.definitions[0].name.name
const location = node.definitions[0].value.args[0].value
if (/^\./.test(location)) {
localModules.push([localName, location])
}
}
} else if (node instanceof UglifyJS.AST_Call) {
//find where the calling function is defined
const p = walker.find_parent(UglifyJS.AST_Defun);
if (/(Number|Date|callback|require)/.test(node.expression.name)) return
if (p !== undefined) {
if (node.expression.name !== undefined) {
// common call e.g. function()
calls.push([localize(currentFile, p.name.name), localize(currentFile, node.expression.name)])
} else {
// method call e.g. lib.function()
const name = p.name.name
const module = node.expression.start.value
const moduleAsFile = localModules
.filter(t => t[0] == module)
.map(t => t[1])
.pop()
const prop = node.expression.property
if (moduleAsFile) {
calls.push([localize(currentFile, name), localize(moduleAsFile, prop)])
definedFunctions.push(localize(currentFile, 'Program'))
const toplevel = UglifyJS.parse(code);
const localModules = []
const walker = new UglifyJS.TreeWalker(function(node){
//check for function calls
if (node instanceof UglifyJS.AST_Definitions) {
// TODO: Should loop though all definitions
if (node.definitions[0].value.expression && node.definitions[0].value.expression.name === 'require') {
const localName = node.definitions[0].name.name
const location = node.definitions[0].value.args[0].value
if (/^\./.test(location)) {
localModules.push([localName, location])
}
}
} else if (node instanceof UglifyJS.AST_Call) {
//find where the calling function is defined
const p = walker.find_parent(UglifyJS.AST_Defun);
if (/(Number|Date|callback|require)/.test(node.expression.name)) return
if (p !== undefined) {
if (node.expression.name !== undefined) {
// common call e.g. function()
calls.push([localize(currentFile, p.name.name), localize(currentFile, node.expression.name)])
} else {
// method call e.g. lib.function()
const name = p.name.name
const module = node.expression.start.value
const moduleAsFile = localModules
.filter(t => t[0] == module)
.map(t => t[1])
.pop()
const prop = node.expression.property
if (moduleAsFile) {
calls.push([localize(currentFile, name), localize(moduleAsFile, prop)])
} else {
calls.push([localize(currentFile, name), localize(currentFile, `${module}.${prop}`)])
}
}
} else {
calls.push([localize(currentFile, name), localize(currentFile, `${module}.${prop}`)])
// it's a top level function
if (node.expression.name !== undefined) {
calls.push([localize(currentFile, 'Program'), localize(currentFile, node.expression.name)])
} else {
calls.push([localize(currentFile, 'Program'), localize(currentFile, `${node.expression.start.value}.${node.expression.property}`)])
}
}
}
if(node instanceof UglifyJS.AST_Defun)
{
//defined but not called
definedFunctions.push(localize(currentFile, node.name.name))
}
} else {
// it's a top level function
if (node.expression.name !== undefined) {
calls.push([localize(currentFile, 'Program'), localize(currentFile, node.expression.name)])
} else {
calls.push([localize(currentFile, 'Program'), localize(currentFile, `${node.expression.start.value}.${node.expression.property}`)])
}
}
}
if(node instanceof UglifyJS.AST_Defun)
{
//defined but not called
definedFunctions.push(localize(currentFile, node.name.name))
}
});
toplevel.walk(walker);
const modulesAsFiles = localModules.map(t => t[1])
files = files.concat(modulesAsFiles)
})
toplevel.walk(walker);
const modulesAsFiles = localModules.map(t => t[1])
modulesAsFiles.forEach(file => {
q.push(processFile.bind(null, file))
})
totalRunning--
cb()
}).catch(e => {
console.error(e)
cb()
})
}
const out = fs.openSync('callgraph.dot', 'w');
fs.writeSync(out, `
digraph test{
overlap=scalexy;
`);
q.push(processFile.bind(null, inputPath.base))
const filterTuple = tuple => {
// This removes calls to libraries i.e. functions not defined in the file
return definedFunctions.indexOf(tuple[1]) > -1
}
const mapTupleToString = t => `"${t[0]}" -> "${t[1]}"`
const callsStr = calls
.filter(filterTuple)
.map(mapTupleToString)
.join(`\n`)
fs.writeSync(out, callsStr + '}');
// console.log('definedFunctions', definedFunctions)
// console.log('calls', calls)
const unshown = calls
.filter(tuple => !filterTuple(tuple))
.map(mapTupleToString)
.join(`\n`)
// console.log('unshown calls', unshown)
q.start(function(err) {
console.log('all done');
const out = fs.openSync('callgraph.dot', 'w');
fs.writeSync(out, `\ndigraph test{\noverlap=scalexy;\n`);
const filterTuple = tuple => {
// This removes calls to libraries i.e. functions not defined in the file
return definedFunctions.indexOf(tuple[1]) > -1
}
const mapTupleToString = t => `"${t[0]}" -> "${t[1]}"`
const callsStr = calls
.filter(filterTuple)
.map(mapTupleToString)
.join(`\n`)
fs.writeSync(out, callsStr + '}');
// console.log('definedFunctions', definedFunctions)
// console.log('calls', calls)
const unshown = calls
.filter(tuple => !filterTuple(tuple))
.map(mapTupleToString)
.join(`\n`)
// console.log('unshown calls', unshown)
execSync('dot -Tpng -o callgraph.png callgraph.dot')
execSync('viewnior callgraph.png')
execSync('dot -Tpng -o callgraph.png callgraph.dot')
execSync('viewnior callgraph.png')
});
// q.on('success', function(result, job) {
// console.log('job finished processing:', job.toString().replace(/\n/g, ''));
// });
{
"name": "callgraph",
"version": "1.0.3",
"version": "1.0.4",
"description": "Parse JS into call graphs",

@@ -15,4 +15,6 @@ "scripts": {

"dependencies": {
"babel-cli": "^6.6.5",
"babel-core": "^6.7.4",
"babel-preset-es2015": "^6.6.0",
"es6-promisify": "^3.0.0",
"queue": "^3.1.0",
"uglify-js": "^2.6.2"

@@ -19,0 +21,0 @@ },

@@ -5,4 +5,12 @@ # Callgraph

![output example](example.png)
## Questions
### Why UglifyJS for AST parsing and not X?
Does X have `walker.find_parent`? Please let me know.
## License
MIT [http://gunar.mit-license.org]()

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc