From 08cf2bcf6f4a02c4e74c535792cd556894010950 Mon Sep 17 00:00:00 2001 From: eyang-pjrcorp Date: Tue, 28 Mar 2023 19:08:50 -0700 Subject: [PATCH 1/3] parseFunction + linting --- src/index.js | 44 +++++-- src/lib/Detector.js | 2 +- src/lib/Stat.js | 221 ++++++++++++++++++++++------------- src/lib/models/AppBuilder.js | 23 ++-- src/package-lock.json | 69 +++++------ 5 files changed, 211 insertions(+), 148 deletions(-) diff --git a/src/index.js b/src/index.js index 7ccedd2..25ea53d 100755 --- a/src/index.js +++ b/src/index.js @@ -63,10 +63,10 @@ let entries = []; // 3. add to individual modules used globals. for (let modul of _app.modules) { for (let mem in modul.memusages) { - _app.globals.forEach((i) => { - if (i.name === mem) { + _app.globals.forEach((globalVar) => { + if (globalVar.name === mem) { for (let m of modul.memusages[mem]) { - i.members.push(m); + globalVar.members.push(m); } } }); @@ -185,10 +185,11 @@ async function init() { } else { _app.main.push(utils.entryPoint(location, packageJson.main)); } - - if (packageJson.hasOwnProperty('directories')) { - if (packageJson.directories.hasOwnProperty('test')) { - _app.directories = packageJson.directories.test + _app.parsedBlacklist = parseScriptsGetBlacklistFiles(packageJson); + + if (packageJson.hasOwnProperty("directories")) { + if (packageJson.directories.hasOwnProperty("test")) { + _app.directories = packageJson.directories.test; } } @@ -196,9 +197,9 @@ async function init() { if (!_app.main) { throw new Error("NO_ENTRY_POINT"); } - _app.main.forEach(entryP => { + _app.main.forEach((entryP) => { entries.push(path.join(_app.path, entryP)); - }) + }); } else { for (let seed of config.seeds) { let s = utils.entryPoint(location, seed); @@ -377,7 +378,7 @@ async function traverseGenerateModule(directory, packageJsonType) { } break; case "module": - if ([".js", ""].includes(itemPathExtension)) { + if ([".js", ".mjs", ""].includes(itemPathExtension)) { _module.type = "module"; } else if (itemPathExtension === ".cjs") { _module.type = "commonjs"; @@ -509,3 +510,26 @@ function isValidJavascriptFile(file) { } return true; } + +function parseScriptsGetBlacklistFiles(packageJson) { + let result = []; + if (packageJson.hasOwnProperty("scripts")) { + let scripts = packageJson.scripts; + for (let key in scripts) { + let stringToDealWith = scripts[key]; + let words = stringToDealWith.split(" "); + for (let wPotentialFile of words) { + let splitWordArr = wPotentialFile.split("."); + if (splitWordArr.length > 1) { + if ( + [".js", ".mjs", ".cjs"].includes( + splitWordArr[splitWordArr.length - 1] + ) + ) { + result.push(wPotentialFile); + } + } + } + } + } else return []; +} diff --git a/src/lib/Detector.js b/src/lib/Detector.js index bc9665d..95a426c 100644 --- a/src/lib/Detector.js +++ b/src/lib/Detector.js @@ -91,7 +91,7 @@ async function run(modul) { } //node.specifiers.forEach(specifier => { // const alias = specifier.local.name; - // parse value + // parse value //}); break; case syntax.ImportExpression: diff --git a/src/lib/Stat.js b/src/lib/Stat.js index 683d272..70944b0 100644 --- a/src/lib/Stat.js +++ b/src/lib/Stat.js @@ -108,7 +108,7 @@ async function initialPass(modul) { } break; case syntax.MemberExpression: - // TODO-Hui: this branch only tracks "exports". + // TODO-Hui: this branch only tracks "exports". // We will see whether we need to handle case #3 in syntax.ImportExpression if (node.object.type === syntax.Identifier) { if ( @@ -219,10 +219,12 @@ async function initialPass(modul) { modul.exporters.push(node.left.name); break; } - } else if ( (node.right.type === syntax.ImportExpression) || - ( (node.right.type === syntax.AwaitExpression ) && (node.right.argument.type === syntax.ImportExpression) ) - ) { - // TODO-Hui: do we need this? + } else if ( + node.right.type === syntax.ImportExpression || + (node.right.type === syntax.AwaitExpression && + node.right.argument.type === syntax.ImportExpression) + ) { + // TODO-Hui: do we need this? // add the support of Case 4 (see case syntax.ImportExpression) here modul.requires.push(node.left.name); } else if (node.right.type === syntax.MemberExpression) { @@ -275,7 +277,7 @@ async function initialPass(modul) { modul.exporters.push(node.id.name); } } else if (node.init.type === syntax.ImportExpression) { - // TODO-Hui: do we need this? + // TODO-Hui: do we need this? // add the support of Case 5 (see case syntax.ImportExpression) here modul.requires.push(node.id.name); } @@ -311,20 +313,20 @@ async function initialPass(modul) { modul.functions += 1; break; case syntax.ImportDeclaration: - // ImportDeclaration: static import in ES6. + // ImportDeclaration: static import in ES6. // type: "ImportDeclaration" // specifiers: [ ImportSpecifier | ImportDefaultSpecifier | ImportNamespaceSpecifier ] // source: Literal const modulePath = node.source.value; - node.specifiers.forEach(specifier => { + node.specifiers.forEach((specifier) => { const alias = specifier.local.name; let name; switch (specifier.type) { - case 'ImportSpecifier': + case "ImportSpecifier": // type: "ImportSpecifier" // imported: Identifier // example: {foo} in import {foo} from "mode", or {foo as bar} in import {foo as bar} from "mode" - // In the first example, imported and local are equivalent Identifier node. + // In the first example, imported and local are equivalent Identifier node. // In the second example, imported represents foo while local represents bar name = specifier.imported.name; modul.staticRequire += 1; @@ -332,20 +334,20 @@ async function initialPass(modul) { //modul.identifiers.addIdentifier(modulePath); // should we add identifier here? //modul.requires.push(name); // we do not push requires at this stage break; - case 'ImportDefaultSpecifier': + case "ImportDefaultSpecifier": // type: "ImportDefaultSpecifier" // example: foo in import foo from "mod.js" - name = 'default'; + name = "default"; modul.staticRequire += 1; modules.push(alias); //modul.identifiers.addIdentifier(modulePath); // should we add identifier here? //modul.requires.push(modulePath); // we do not push requires at this stage break; - case 'ImportNamespaceSpecifier': + case "ImportNamespaceSpecifier": // type: "ImportNamespaceSpecifier" // example: * as foo in import * as foo from "mod.js" // TODO-Hui: document all functions in modulePath? - name = '*'; + name = "*"; modul.staticRequire += 1; modules.push(alias); //modul.identifiers.addIdentifier(modulePath); // should we add identifier here? @@ -355,32 +357,35 @@ async function initialPass(modul) { // attack surface marking. TODO-Hui: check the logic if (utils.hasKey(attack, modulePath)) { - helper.VariableAssignmentName(parent, (name) => { - if (name) { - if (Array.isArray(name)) { - console.log(name); + helper.VariableAssignmentName(parent, (name) => { + if (name) { + if (Array.isArray(name)) { + console.log(name); + } + tracker.push(name); + let vector = { name: name, value: arg.value, members: [] }; + modul.attackVectors.push(vector); + //if (parent.type === syntax.MemberExpression) { + // vector.members.push(parent.property.name); + //} } - tracker.push(name); - let vector = { name: name, value: arg.value, members: [] }; - modul.attackVectors.push(vector); - //if (parent.type === syntax.MemberExpression) { - // vector.members.push(parent.property.name); - //} - } }); } - + // console.log below is for debugging only. They will be removed after the processing is done in the above three cases - if (name) { - console.log(`Static Import: modul name: ${modul.name}, alias: ${alias}, name: ${name}, modulePath: ${modulePath}`); - } - else { - console.log(`Static Import: NOT supported. modul name: ${modul.name}, modulePath: ${modulePath}`); - } - }) + // if (name) { + // console.log( + // `Static Import: modul name: ${modul.name}, alias: ${alias}, name: ${name}, modulePath: ${modulePath}` + // ); + // } else { + // console.log( + // `Static Import: NOT supported. modul name: ${modul.name}, modulePath: ${modulePath}` + // ); + // } + }); break; case syntax.ImportExpression: - // This is for import() which is a import from an ESM module to a commonjs module. + // This is for import() which is a import from an ESM module to a commonjs module. // It can be either static import or dynamic import, depending on the argument // five cases to support as of 03/22/2023: @@ -389,40 +394,60 @@ async function initialPass(modul) { // 3: import("/my-module.mjs").init; await import("/my-module.mjs").init; // 4: exs = import("/my-module.mjs"); exs = await import("/my-module.mjs"); // 5: const exs = import("/my-module.mjs"); - // Note: + // Note: // Case 1 & 2: these imports are not used by themselves in the application // The processing of 3, 4 and 5 is in syntax.MemberExpression, syntax.AssignmentExpression and syntax.VariableDeclarator, respectively // The ConditionalExpression is not supported yet: import(someCondition ? "./mod1.js" : "./mod2.js"); await import(someCondition ? "./mod1.js" : "./mod2.js"); - + // The following is for debugging only - if ( (parent.type === syntax.ExpressionStatement) || - ( (parent.type === syntax.AwaitExpression) && (parent.xParent.type === syntax.ExpressionStatement) ) + if ( + parent.type === syntax.ExpressionStatement || + (parent.type === syntax.AwaitExpression && + parent.xParent.type === syntax.ExpressionStatement) ) { if (node.source.type === syntax.Literal) { // case 1 - either a standalone statement, or part of a IfStatement or BlockStatement - console.log(`Import() case 1. modul name: ${modul.name}, modulePath: ${node.source.value}, parent type: ${parent.type}, ${parent.xParent.type}`); + console.log( + `Import() case 1. modul name: ${modul.name}, modulePath: ${node.source.value}, parent type: ${parent.type}, ${parent.xParent.type}` + ); } else if (node.source.type === syntax.BinaryExpression) { // case 2 - either a standalone statement, or part of a IfStatement or BlockStatement - console.log(`Import() case 2. modul name: ${modul.name}, modulePath: ${node.source.type}, parent type: ${parent.type}, ${parent.xParent.type}`); + console.log( + `Import() case 2. modul name: ${modul.name}, modulePath: ${node.source.type}, parent type: ${parent.type}, ${parent.xParent.type}` + ); } else { // general case 2 - console.log(`Import() general case 2. modul name: ${modul.name}, modulePath: ${node.source.type}, parent type: ${parent.type}, ${parent.xParent.type}`); + console.log( + `Import() general case 2. modul name: ${modul.name}, modulePath: ${node.source.type}, parent type: ${parent.type}, ${parent.xParent.type}` + ); } } else if (parent.type === syntax.MemberExpression) { // case 3 - console.log(`Import() case 3. modul name: ${modul.name}, modulePath: ${node.source.type}, parent type: ${parent.type}, ${parent.xParent.type}`); - } else if ( (parent.type === syntax.AssignmentExpression) || - ( (parent.type === syntax.AwaitExpression) && (parent.xParent.type === syntax.AssignmentExpression) ) + console.log( + `Import() case 3. modul name: ${modul.name}, modulePath: ${node.source.type}, parent type: ${parent.type}, ${parent.xParent.type}` + ); + } else if ( + parent.type === syntax.AssignmentExpression || + (parent.type === syntax.AwaitExpression && + parent.xParent.type === syntax.AssignmentExpression) ) { // case 4 - console.log(`Import() case 4. modul name: ${modul.name}, modulePath: ${node.source.type}, parent type: ${parent.type}, ${parent.xParent.type}`); - } else if ( (parent.type === syntax.VariableDeclarator) || - ( (parent.type === syntax.AwaitExpression) && (parent.xParent.type === syntax.VariableDeclarator) ) + console.log( + `Import() case 4. modul name: ${modul.name}, modulePath: ${node.source.type}, parent type: ${parent.type}, ${parent.xParent.type}` + ); + } else if ( + parent.type === syntax.VariableDeclarator || + (parent.type === syntax.AwaitExpression && + parent.xParent.type === syntax.VariableDeclarator) ) { // case 5 - console.log(`Import() case 5. modul name: ${modul.name}, modulePath: ${node.source.type}, parent type: ${parent.type}, ${parent.xParent.type}`); + console.log( + `Import() case 5. modul name: ${modul.name}, modulePath: ${node.source.type}, parent type: ${parent.type}, ${parent.xParent.type}` + ); } else { - console.log(`Import() case unknown. modul name: ${modul.name}, modulePath: ${node.source.type}, parent type: ${parent.type}, ${parent.xParent.type}`); + console.log( + `Import() case unknown. modul name: ${modul.name}, modulePath: ${node.source.type}, parent type: ${parent.type}, ${parent.xParent.type}` + ); } // end of the debugging block @@ -431,7 +456,8 @@ async function initialPass(modul) { if (arg.type !== syntax.Literal) { modul.dynamicRequire += 1; if (arg.type === syntax.BinaryExpression) { - let isDynamicBinaryExpression = utils.BinaryExpression.isDynamic(arg); + let isDynamicBinaryExpression = + utils.BinaryExpression.isDynamic(arg); if (isDynamicBinaryExpression) modul.complexDynamicRequire += 1; } else if ( arg.type === syntax.Identifier && @@ -465,10 +491,7 @@ async function initialPass(modul) { if (vardeclarator) { modules.push(vardeclarator.id.name); } - let assignment = helper.closests( - node, - syntax.AssignmentExpression - ); + let assignment = helper.closests(node, syntax.AssignmentExpression); if (assignment && assignment.left.type === syntax.Identifier) { modules.push(assignment.left.name); } @@ -489,20 +512,23 @@ async function initialPass(modul) { // export const move = _move.move modul.staticExport += 1; - console.log(`ExportNamedDeclaration: modul name: ${modul.name}, source: ${node.source}`); + console.log( + `ExportNamedDeclaration: modul name: ${modul.name}, source: ${node.source}` + ); if (node.declaration) { // for now I only see the following types in the declaration of ExportNamedDeclaration console.log(` declaration type: ${node.declaration.type}`); if (node.declaration.type === syntax.VariableDeclaration) { // this supports multiple VariableDeclarator in declaration.declarations for (const declaElem of node.declaration.declarations) { - if ( (declaElem.type === syntax.VariableDeclarator) ) { + if (declaElem.type === syntax.VariableDeclarator) { if (declaElem.id.type === syntax.Identifier) { self.push(declaElem.id.name); // TODO-Hui: we may not need to use self to store this - if (!modul.exporters.includes(declaElem.id.name)) { // TODO-Hui: do we need to check this? + if (!modul.exporters.includes(declaElem.id.name)) { + // TODO-Hui: do we need to check this? modul.exporters.push(declaElem.id.name); } - } + } // TODO-Hui: do we need to consider the init (e.g., MemberExpression)? perhaps not since the detail is only needed in Analyzer.js? // also, do we need to update attackVectors if tracker.includes(node.object.name) when syntax.MemberExpression? example: module.exports = _copy @@ -511,9 +537,10 @@ async function initialPass(modul) { } console.log(` declaration.id.name: ${declaElem.id.name}`); } - } else if ( (node.declaration.type === syntax.FunctionDeclaration) || - (node.declaration.type === syntax.ClassDeclaration) - ) { + } else if ( + node.declaration.type === syntax.FunctionDeclaration || + node.declaration.type === syntax.ClassDeclaration + ) { // there is no declarations in the function or class declaration. Use declaration directly // follow the same to-dos in syntax.VariableDeclaration if (node.declaration.id.type === syntax.Identifier) { @@ -524,19 +551,24 @@ async function initialPass(modul) { } } else { // TODO-Hui: will there be MemberExpression or AssignmentExpression? The answer is no for now. - console.log(` warning: this declaration.id.type ${node.declaration.type} is not supported!`); + console.log( + ` warning: this declaration.id.type ${node.declaration.type} is not supported!` + ); } } - + if (node.specifiers) { // follow the same to-dos in syntax.VariableDeclaration - node.specifiers.forEach(specifier => { - console.log(` specifier: Exported: ${specifier.exported.name}, Local: ${specifier.local.name}`); + // specifier.exported.name is the alias, specifier.local.name is the actual + node.specifiers.forEach((specifier) => { + console.log( + ` specifier: Exported: ${specifier.exported.name}, Local: ${specifier.local.name}` + ); self.push(specifier.local.name); if (!modul.exporters.includes(specifier.local.name)) { - modul.exporters.push(specifier.local.name); + modul.exporters.push(specifier.local.name); } - }) + }); } break; case syntax.ExportDefaultDeclaration: @@ -548,10 +580,12 @@ async function initialPass(modul) { // export default class MyClass{}, export default class {} // export default function myfunc2() {}, export default function () {} // Note: export default Literal is not considered here. I believe it does not have impact on our debloating. - + // TODO-Hui: follow the same to-dos in syntax.ExportNamedDeclaration modul.staticExport += 1; - console.log(`ExportDefaultDeclaration: modul name: ${modul.name}, declaration type: ${node.declaration.type}`); + console.log( + `ExportDefaultDeclaration: modul name: ${modul.name}, declaration type: ${node.declaration.type}` + ); switch (node.declaration.type) { case syntax.Identifier: self.push(node.declaration.name); @@ -561,41 +595,58 @@ async function initialPass(modul) { console.log(` declaration.name: ${node.declaration.name}`); break; case syntax.ClassDeclaration: - if (node.declaration.id && (node.declaration.id.type === syntax.Identifier) ) { + if ( + node.declaration.id && + node.declaration.id.type === syntax.Identifier + ) { self.push(node.declaration.id.name); if (!modul.exporters.includes(node.declaration.id.name)) { modul.exporters.push(node.declaration.id.name); } - console.log(` declaration.id.name: ${node.declaration.id.name}`); + console.log( + ` declaration.id.name: ${node.declaration.id.name}` + ); } break; case syntax.FunctionDeclaration: - if (node.declaration.id && (node.declaration.id.type === syntax.Identifier) ) { + if ( + node.declaration.id && + node.declaration.id.type === syntax.Identifier + ) { self.push(node.declaration.id.name); if (!modul.exporters.includes(node.declaration.id.name)) { modul.exporters.push(node.declaration.id.name); } - console.log(` declaration.id.name: ${node.declaration.id.name}`); + console.log( + ` declaration.id.name: ${node.declaration.id.name}` + ); } break; case syntax.ObjectExpression: - // I only see ObjectExpression from the specification/examples. + // I only see ObjectExpression from the specification/examples. // The peroperties of ObjectExpression include SpreadElements. The argument of each SpreadElement is an identifier if (node.declaration.properties) { for (const spreadElem of node.declaration.properties) { - if ( spreadElem.argument && (spreadElem.argument.type === syntax.Identifier) ) { - self.push(spreadElem.argument.name); + if ( + spreadElem.argument && + spreadElem.argument.type === syntax.Identifier + ) { + self.push(spreadElem.argument.name); if (!modul.exporters.includes(spreadElem.argument.name)) { modul.exporters.push(spreadElem.argument.name); } - } - console.log(` declaration.properties.element.argument: ${spreadElem.argument.name}`); + } + console.log( + ` declaration.properties.element.argument: ${spreadElem.argument.name}` + ); } } break; default: // in case there is other Expression type - console.log(`Warning: Not supported node.declaration.type ${node.declaration.type} in ExportDefaultDeclaration.`); + console.log( + `Warning: Not supported node.declaration.type ${node.declaration.type} in ExportDefaultDeclaration.` + ); } break; case syntax.ExportAllDeclaration: @@ -604,8 +655,10 @@ async function initialPass(modul) { // TODO-Hui: This is also called re-exporting since the exported is from another file. Does this make our analysis task easier? // To get the detail, this may need to go to 'source' to retrieve all related identifiers // Note: in commonjs modules, to support the same function, we need module.exports = {a function to loop all items in the source and then require(item)} - console.log(`ExportAllDeclaration: modul name: ${modul.name}, source: ${node.source}`); - break; + console.log( + `ExportAllDeclaration: modul name: ${modul.name}, source: ${node.source}` + ); + break; } }, leave: function (node, parent) { @@ -630,9 +683,11 @@ async function initialPass(modul) { // do the same thing for dynamic import (from ESM to CommonJS module) // TODO-Hui: after the implementation of ImportExpression, check whether this is duplicate if (node.type === syntax.ImportExpression) { - console.log(`leave function for dynamic import: modul name: ${modul.name}`); + console.log( + `leave function for dynamic import: modul name: ${modul.name}` + ); if ( - node.source.type === syntax.Identifier && + node.source.type === syntax.Identifier && modul.identifiers.hasIdentifier(node.source.value) ) { // marking the identifier as a module diff --git a/src/lib/models/AppBuilder.js b/src/lib/models/AppBuilder.js index ed1ac4f..19377a0 100644 --- a/src/lib/models/AppBuilder.js +++ b/src/lib/models/AppBuilder.js @@ -1,9 +1,9 @@ -const ModuleBuilder = require('./ModuleBuilder'); +const ModuleBuilder = require("./ModuleBuilder"); module.exports = function () { - this.appname = ''; - this.version = ''; - this.type = ''; - this.path = ''; + this.appname = ""; + this.version = ""; + this.type = ""; + this.path = ""; // Hui Zeng: in Node.js v19.8.1, "exports" provides a modern alternative to "main" allowing multiple entry points to defined this.main = []; this.testsDirectory = null; @@ -25,10 +25,10 @@ module.exports = function () { this.declaredDependencyCount = 0; this.installedUniqueDependencyCount = 0; this.installedTotalDependencyCount = 0; - + this.totalStaticExports = 0; this.totalDynamicExports = 0; - + this.totalStaticRequire = 0; this.totalDynamicRequire = 0; this.totalComplexDynamicRequire = 0; @@ -36,7 +36,7 @@ module.exports = function () { this.totalFunctions = 0; this.totalVariables = 0; - + this.totalEval = 0; this.totalEvalWithVar = 0; this.totalFunctionNew = 0; @@ -60,9 +60,10 @@ module.exports = function () { this.globals = []; // all global variables that Application has. Ex: {name: 'foo', path: 'lib/foo.js', members: []} this.dimports = []; // all modules that dynamically imported. Ex: {name: 'foo', members: [], by: ''}. DIMPORTS has higher precedence than GLOBALS /** @type {ModuleBuilder} */ - this.modules = [];// all modules that Application has, i.e. all js files inside every package. Ex: express/lib/express.js, mocha/index.js - - this.dependencies = {};// all packages Application has. Ex: express, mocha, lodash. {parent: "", name:"", version:""} + this.modules = []; // all modules that Application has, i.e. all js files inside every package. Ex: express/lib/express.js, mocha/index.js + + this.dependencies = {}; // all packages Application has. Ex: express, mocha, lodash. {parent: "", name:"", version:""} this.builtins = {}; // all native modules that were used inside the application. + this.parsedBlacklist = []; }; diff --git a/src/package-lock.json b/src/package-lock.json index cf56a6f..57e2aea 100644 --- a/src/package-lock.json +++ b/src/package-lock.json @@ -10,10 +10,10 @@ "license": "ISC", "dependencies": { "chalk": "^2.4.1", - "escodegen": "^1.10.0", + "escodegen": "^2.0.0", "espree": "^9.4.1", "esprima": "^4.0.0", - "estraverse": "^4.2.0", + "estraverse": "^5.3.0", "fs-extra": "^6.0.1", "glob": "^7.1.3", "log-to-file": "^3.0.3", @@ -229,12 +229,12 @@ } }, "node_modules/escodegen": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.10.0.tgz", - "integrity": "sha512-fjUOf8johsv23WuIKdNQU4P9t9jhQ4Qzx6pC2uW890OloK3Zs1ZAoCNpg/2larNF501jLl3UNy0kIRcF6VI22g==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz", + "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==", "dependencies": { - "esprima": "^3.1.3", - "estraverse": "^4.2.0", + "esprima": "^4.0.1", + "estraverse": "^5.2.0", "esutils": "^2.0.2", "optionator": "^0.8.1" }, @@ -243,24 +243,12 @@ "esgenerate": "bin/esgenerate.js" }, "engines": { - "node": ">=4.0" + "node": ">=6.0" }, "optionalDependencies": { "source-map": "~0.6.1" } }, - "node_modules/escodegen/node_modules/esprima": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", - "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/escodegen/node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -295,9 +283,9 @@ } }, "node_modules/esprima": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz", - "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "bin": { "esparse": "bin/esparse.js", "esvalidate": "bin/esvalidate.js" @@ -307,11 +295,11 @@ } }, "node_modules/estraverse": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", - "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "engines": { - "node": ">=0.10.0" + "node": ">=4.0" } }, "node_modules/esutils": { @@ -1137,22 +1125,17 @@ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" }, "escodegen": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.10.0.tgz", - "integrity": "sha512-fjUOf8johsv23WuIKdNQU4P9t9jhQ4Qzx6pC2uW890OloK3Zs1ZAoCNpg/2larNF501jLl3UNy0kIRcF6VI22g==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz", + "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==", "requires": { - "esprima": "^3.1.3", - "estraverse": "^4.2.0", + "esprima": "^4.0.1", + "estraverse": "^5.2.0", "esutils": "^2.0.2", "optionator": "^0.8.1", "source-map": "~0.6.1" }, "dependencies": { - "esprima": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", - "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=" - }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -1177,14 +1160,14 @@ } }, "esprima": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz", - "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==" + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" }, "estraverse": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", - "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=" + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==" }, "esutils": { "version": "2.0.2", From 446ef783c670fe9c947bde4a405f9ddc471ba483 Mon Sep 17 00:00:00 2001 From: eyang-pjrcorp Date: Fri, 31 Mar 2023 10:56:53 -0700 Subject: [PATCH 2/3] remove test folders logic + fix nit in Analyzer --- src/index.js | 29 +++++++++-------------------- src/lib/Analyzer.js | 2 +- src/lib/Configurator.js | 6 ------ 3 files changed, 10 insertions(+), 27 deletions(-) diff --git a/src/index.js b/src/index.js index b264a1e..96e9983 100755 --- a/src/index.js +++ b/src/index.js @@ -191,16 +191,16 @@ async function init() { if (testEntryPoints && testEntryPoints.length > 0) { //TODO-Hui: here do we need to find the path to these test entry points? //TODO-Hui: also, with these test entry points, perhaps we should not block test directories? - testEntryPoints.forEach(entryP => { + testEntryPoints.forEach((entryP) => { if (!_app.main.includes(entryP)) { _app.main.push(entryP); } - }) + }); } - - if (packageJson.hasOwnProperty('directories')) { - if (packageJson.directories.hasOwnProperty('test')) { - _app.directories = packageJson.directories.test + + if (packageJson.hasOwnProperty("directories")) { + if (packageJson.directories.hasOwnProperty("test")) { + _app.directories = packageJson.directories.test; } } @@ -358,15 +358,6 @@ async function traverseGenerateModule(directory, packageJsonType) { let itemPath = path.join(directory, item); let stat = fs.statSync(itemPath); if (stat.isDirectory()) { - if (config.includeTestFolders === false) { - if (_app.testsDirectory) { - if (item === _app.testsDirectory) continue; - } else { - if (item.toLowerCase().startsWith("test")) { - continue; - } - } - } await traverseGenerateModule(itemPath, packageJsonType); } else if (stat.isFile()) { let itemPathExtension = path.extname(itemPath).toLowerCase(); @@ -531,21 +522,19 @@ function getJSFilenamesInScriptsField(packageJson) { let stringToDealWith = scripts[key]; let words = stringToDealWith.split(" "); for (let wPotentialFile of words) { - if (wPotentialFile.indexOf('*') > -1) { + if (wPotentialFile.indexOf("*") > -1) { continue; } let splitWordArr = wPotentialFile.split("."); if (splitWordArr.length > 1) { if ( - ["js", "mjs", "cjs"].includes( - splitWordArr[splitWordArr.length - 1] - ) + ["js", "mjs", "cjs"].includes(splitWordArr[splitWordArr.length - 1]) ) { result.push(wPotentialFile); } } } } - } + } return result; } diff --git a/src/lib/Analyzer.js b/src/lib/Analyzer.js index 5f0e8a8..af4f022 100644 --- a/src/lib/Analyzer.js +++ b/src/lib/Analyzer.js @@ -547,7 +547,7 @@ async function traverse(modul) { //TODO-Hui: Need to debug this to see whether it gets the string correctly let right = helper.getMemberExpressionString(declaElem.init); if (right) { - right = right.object + "." + rightproperty; + right = right.object + "." + right.property; if (!modul.selfUsed.includes(right)) { modul.selfUsed.push(right); } diff --git a/src/lib/Configurator.js b/src/lib/Configurator.js index 418edb8..bd63661 100644 --- a/src/lib/Configurator.js +++ b/src/lib/Configurator.js @@ -52,11 +52,6 @@ const argv = require("yargs") describe: "Runs in verbose mode, i.e. prints debug statements", type: "boolean", }) - .option("include-test-folders", { - describe: "Whether to exclude test folders from debloating", - default: false, - type: "boolean", - }) .epilog("Thank you for checking out the project").argv; settings.origin = argv._[0]; @@ -71,7 +66,6 @@ settings.skipRemove = argv.skipRemove; settings.silent = argv.silent; settings.compressLog = argv.compressLog; settings.logOutput = argv.logOutput ? argv.logOutput : settings.logOutput; -settings.includeTestFolders = argv.includeTestFolders settings.ignored = ["license", "makefile", "make"]; From d9c1ba2dc00270fe395ed434b722fbfc164f6695 Mon Sep 17 00:00:00 2001 From: eyang-pjrcorp Date: Mon, 10 Apr 2023 20:50:14 -0700 Subject: [PATCH 3/3] mininode.bash update - no validation --- mininode.bash | 102 ++++++++++++++++++++++++++++++++++++++++++++++++ src/lib/Stat.js | 73 ++++++++++++++++++++++------------ 2 files changed, 151 insertions(+), 24 deletions(-) create mode 100755 mininode.bash diff --git a/mininode.bash b/mininode.bash new file mode 100755 index 0000000..31690d2 --- /dev/null +++ b/mininode.bash @@ -0,0 +1,102 @@ +#!/bin/bash +: ' +This is for the package installation, debloating, and validation in the Docker +Container. Assuming the source code of the package is already downloaded as +tar.gz file and stored under the root directory. +' + +: 'SHAREDMOUNT is the directory that stores the package tar.gz file' +SHAREDMOUNT="/sharedMount" +ROOT="/" +MININODE="index.js" +SUFFIX_ORIG="orig" +SUFFIX_PROD="prod" +SUFFIX_RDUC="reduced" + +File_Name=$1 +: 'Check whether the file exists' +if [ ! -f "$File_Name" ]; then + echo "File not found!" + exit 1 +fi + +echo "File_Name is $File_Name" +: 'for the full path, use this: PACKAGE_NAME=”${fullfile##*/}” ' + +: ' +extract the tar.gz file for the original version of the package +obtain the directory of the unpacked package +modify the name of the directory by appending SUFFIX_ORIG +' +unpacked=$(tar -xvzf $File_Name) +echo "unpacked: $unpacked" +packdir=$(echo $unpacked | cut -f1 -d" ") + +: 'remove the last "/" from packdir if it exists' +packdir=${packdir%/} + +PACKAGE_ORIG="$packdir-$SUFFIX_ORIG" +echo "unpacked: $packdir" +echo "PACKAGE_ORIG: $PACKAGE_ORIG" +mv $packdir $PACKAGE_ORIG + +: ' +go to the orig directory, install the package, and run the test +' +cd $PACKAGE_ORIG +if npm install; then + echo "$PACKAGE_ORIG sucessfully installed." +else + echo "$PACKAGE_ORIG failed to be installed!" + exit 1 +fi + +if npm run test; then + echo "$PACKAGE_ORIG test run sucessfully." +else + echo "$PACKAGE_ORIG failed to run test!" + exit 1 +fi + +: ' +re-extract the tar.gz file for the product version of this package +obtain the directory of the unpacked package +modify the name of the directory by appending SUFFIX_PROD +' +cd $SHAREDMOUNT +tar -xvzf $File_Name + +# reuse the $packdir obtained from the original extraction +PACKAGE_PROD="$packdir-$SUFFIX_PROD" +echo "unpacked: $packdir" +echo "PACKAGE_PROD: $PACKAGE_PROD" +mv $packdir $PACKAGE_PROD + +: ' +go to the prod directory, install the package +' +cd $PACKAGE_PROD +if npm install --only=prod; then + echo "$PACKAGE_PROD sucessfully installed." +else + echo "$PACKAGE_PROD failed to be installed!" + exit 1 +fi + +cd $ROOT +# reuse the $packdir obtained from the original extraction +PACKAGE_RDUC="$packdir-$SUFFIX_RDUC" +# specify the full path of the produce version, and the reduced version, respectively +package_prod_full="$SHAREDMOUNT/$PACKAGE_PROD/" +package_rduc_full="$SHAREDMOUNT/$PACKAGE_RDUC" # take out slash so program works correctly + +echo "node --max-old-space-size=8192 $MININODE $package_prod_full --mode=fine --destination=$package_rduc_full" +echo $package_rduc_full +if node --max-old-space-size=8192 $MININODE $package_prod_full --mode=fine --destination=$package_rduc_full; then + echo "$PACKAGE_PROD sucessfully debloated." +else + echo "$PACKAGE_PROD failed to be debloated!" + exit 1 +fi +echo "skipped validation" +exit 1 diff --git a/src/lib/Stat.js b/src/lib/Stat.js index 2222b68..3fae9da 100644 --- a/src/lib/Stat.js +++ b/src/lib/Stat.js @@ -310,10 +310,12 @@ async function initialPass(modul) { } break; case syntax.FunctionDeclaration: - if (parent.type === syntax.ExportNamedDeclaration || - parent.type === syntax.ExportDefaultDeclaration) { - console.debug(`modul name: ${modul.name}`); - } + if ( + parent.type === syntax.ExportNamedDeclaration || + parent.type === syntax.ExportDefaultDeclaration + ) { + console.debug(`modul name: ${modul.name}`); + } modul.functions += 1; break; case syntax.ImportDeclaration: @@ -378,12 +380,15 @@ async function initialPass(modul) { // console.log below is for debugging only. They will be removed after the processing is done in the above three cases if (name) { - console.debug(`Static Import: modul name: ${modul.name}, alias: ${alias}, name: ${name}, modulePath: ${modulePath}`); - } - else { - console.debug(`Static Import: NOT supported. modul name: ${modul.name}, modulePath: ${modulePath}`); + console.debug( + `Static Import: modul name: ${modul.name}, alias: ${alias}, name: ${name}, modulePath: ${modulePath}` + ); + } else { + console.debug( + `Static Import: NOT supported. modul name: ${modul.name}, modulePath: ${modulePath}` + ); } - }) + }); break; case syntax.ImportExpression: // This is for import() which is a import from an ESM module to a commonjs module. @@ -408,29 +413,47 @@ async function initialPass(modul) { ) { if (node.source.type === syntax.Literal) { // case 1 - either a standalone statement, or part of a IfStatement or BlockStatement - console.debug(`Import() case 1. modul name: ${modul.name}, modulePath: ${node.source.value}, parent type: ${parent.type}, ${parent.xParent.type}`); + console.debug( + `Import() case 1. modul name: ${modul.name}, modulePath: ${node.source.value}, parent type: ${parent.type}, ${parent.xParent.type}` + ); } else if (node.source.type === syntax.BinaryExpression) { // case 2 - either a standalone statement, or part of a IfStatement or BlockStatement - console.debug(`Import() case 2. modul name: ${modul.name}, modulePath: ${node.source.value}, parent type: ${parent.type}, ${parent.xParent.type}`); + console.debug( + `Import() case 2. modul name: ${modul.name}, modulePath: ${node.source.value}, parent type: ${parent.type}, ${parent.xParent.type}` + ); } else { // general case 2 - console.debug(`Import() general case 2. modul name: ${modul.name}, modulePath: ${node.source.value}, parent type: ${parent.type}, ${parent.xParent.type}`); + console.debug( + `Import() general case 2. modul name: ${modul.name}, modulePath: ${node.source.value}, parent type: ${parent.type}, ${parent.xParent.type}` + ); } } else if (parent.type === syntax.MemberExpression) { // case 3 - console.debug(`Import() case 3. modul name: ${modul.name}, modulePath: ${node.source.type}, parent type: ${parent.type}, ${parent.xParent.type}`); - } else if ( (parent.type === syntax.AssignmentExpression) || - ( (parent.type === syntax.AwaitExpression) && (parent.xParent.type === syntax.AssignmentExpression) ) + console.debug( + `Import() case 3. modul name: ${modul.name}, modulePath: ${node.source.type}, parent type: ${parent.type}, ${parent.xParent.type}` + ); + } else if ( + parent.type === syntax.AssignmentExpression || + (parent.type === syntax.AwaitExpression && + parent.xParent.type === syntax.AssignmentExpression) ) { // case 4 - console.debug(`Import() case 4. modul name: ${modul.name}, modulePath: ${node.source.value}, parent type: ${parent.type}, ${parent.xParent.type}`); - } else if ( (parent.type === syntax.VariableDeclarator) || - ( (parent.type === syntax.AwaitExpression) && (parent.xParent.type === syntax.VariableDeclarator) ) + console.debug( + `Import() case 4. modul name: ${modul.name}, modulePath: ${node.source.value}, parent type: ${parent.type}, ${parent.xParent.type}` + ); + } else if ( + parent.type === syntax.VariableDeclarator || + (parent.type === syntax.AwaitExpression && + parent.xParent.type === syntax.VariableDeclarator) ) { // case 5 - console.debug(`Import() case 5. modul name: ${modul.name}, modulePath: ${node.source.value}, parent type: ${parent.type}, ${parent.xParent.type}`); + console.debug( + `Import() case 5. modul name: ${modul.name}, modulePath: ${node.source.value}, parent type: ${parent.type}, ${parent.xParent.type}` + ); } else { - console.debug(`Import() case unknown. modul name: ${modul.name}, modulePath: ${node.source.value}, parent type: ${parent.type}, ${parent.xParent.type}`); + console.debug( + `Import() case unknown. modul name: ${modul.name}, modulePath: ${node.source.value}, parent type: ${parent.type}, ${parent.xParent.type}` + ); } // end of the debugging block @@ -539,7 +562,7 @@ async function initialPass(modul) { ); } } - + if (node.specifiers && node.specifiers.length > 0) { // follow the same to-dos in syntax.VariableDeclaration // specifier.exported.name is the alias, specifier.local.name is the actual @@ -547,9 +570,9 @@ async function initialPass(modul) { console.log( ` specifier: Exported: ${specifier.exported.name}, Local: ${specifier.local.name}` ); - self.push(specifier.local.name); + self.push(specifier.local.name); if (!modul.exporters.includes(specifier.local.name)) { - modul.exporters.push(specifier.local.name); + modul.exporters.push(specifier.local.name); } }); } @@ -586,7 +609,9 @@ async function initialPass(modul) { if (!modul.exporters.includes(node.declaration.left.name)) { modul.exporters.push(node.declaration.left.name); } - console.log(` declaration.left.name: ${node.declaration.left.name}`); + console.log( + ` declaration.left.name: ${node.declaration.left.name}` + ); break; case syntax.ClassDeclaration: if (