/** Provides classes for working with Node.js modules. */ import javascript private import NodeModuleResolutionImpl private import semmle.javascript.DynamicPropertyAccess as DynamicPropertyAccess private import semmle.javascript.internal.CachedStages private import semmle.javascript.dataflow.internal.DataFlowNode /** * A Node.js module. * * Example: * * ``` * const fs = require('fs'); * for (var i=2;i/node_modules`, where * `` is a (not necessarily proper) prefix of `f` and does not end in `/node_modules`, * and `distance` is the number of path elements of `f` that are missing from ``. * * This predicate implements the `NODE_MODULES_PATHS` procedure from the * [specification of `require.resolve`](https://nodejs.org/api/modules.html#modules_all_together). * * For example, if `f` is `/a/node_modules/b`, we get the following results: * * * * * * *
nodeModulesdistance
/a/node_modules/b/node_modules0
/a/node_modules2
/node_modules3
*/ predicate findNodeModulesFolder(Folder f, Folder nodeModules, int distance) { nodeModules = f.getFolder("node_modules") and not f.getBaseName() = "node_modules" and distance = 0 or findNodeModulesFolder(f.getParentContainer(), nodeModules, distance - 1) } /** * A Node.js `require` variable. */ overlay[local?] private class RequireVariable extends Variable { RequireVariable() { this = any(ModuleScope m).getVariable("require") or // cover cases where we failed to detect Node.js code this.(GlobalVariable).getName() = "require" or // track through assignments to other variables this.getAnAssignedExpr().(VarAccess).getVariable() instanceof RequireVariable } } overlay[local?] private predicate isModuleModule(EarlyStageNode nd) { exists(ImportDeclaration imp | imp.getRawImportPath() = "module" | nd = TDestructuredModuleImportNode(imp) or nd = TValueNode(imp.getASpecifier().(ImportNamespaceSpecifier)) ) or exists(EarlyStageNode other | isModuleModule(other) and DataFlow::localFlowStep(other, nd) ) } overlay[local?] private predicate isCreateRequire(EarlyStageNode nd) { exists(PropAccess prop | isModuleModule(TValueNode(prop.getBase())) and prop.getPropertyName() = "createRequire" and nd = TValueNode(prop) ) or exists(PropertyPattern prop | isModuleModule(TValueNode(prop.getObjectPattern())) and prop.getName() = "createRequire" and nd = TValueNode(prop.getValuePattern()) ) or exists(ImportDeclaration decl, NamedImportSpecifier spec | decl.getRawImportPath() = "module" and spec = decl.getASpecifier() and spec.getImportedName() = "createRequire" and nd = TValueNode(spec) ) or exists(EarlyStageNode other | isCreateRequire(other) and DataFlow::localFlowStep(other, nd) ) } /** * Holds if `nd` may refer to `require`, either directly or modulo local data flow. */ overlay[local?] cached private predicate isRequire(EarlyStageNode nd) { exists(VarAccess access | access = any(RequireVariable v).getAnAccess() and nd = TValueNode(access) and // `mjs` files explicitly disallow `require` not access.getFile().getExtension() = "mjs" ) or exists(EarlyStageNode other | isRequire(other) and DataFlow::localFlowStep(other, nd) ) or // `import { createRequire } from 'module';`. // specialized to ES2015 modules to avoid recursion in the `DataFlow::moduleImport()` predicate and to avoid // negative recursion between `Import.getImportedModuleNode()` and `Import.getImportedModule()`, and // to avoid depending on `SourceNode` as this would make `SourceNode::Range` recursive. exists(CallExpr call | isCreateRequire(TValueNode(call.getCallee())) and nd = TValueNode(call) ) or // `$.require('underscore');`. // NPM as supported in [XSJS files](https://www.npmjs.com/package/@sap/async-xsjs#npm-packages-support). exists(MethodCallExpr require | require.getFile().getExtension() = ["xsjs", "xsjslib"] and require.getCalleeName() = "require" and require.getReceiver().(GlobalVarAccess).getName() = "$" and nd = TValueNode(require.getCallee()) ) } /** * A `require` import. * * Example: * * ``` * require('fs') * ``` */ overlay[local?] class Require extends CallExpr, Import { Require() { isRequire(TValueNode(this.getCallee())) } override Expr getImportedPathExpr() { result = this.getArgument(0) } override Module getEnclosingModule() { this = result.getAnImport() } override DataFlow::Node getImportedModuleNode() { result = DataFlow::valueNode(this) } } /** An argument to `require` or `require.resolve`, considered as a path expression. */ deprecated private class RequirePath extends PathExprCandidate { RequirePath() { this = any(Require req).getArgument(0) or exists(MethodCallExpr reqres | isRequire(TValueNode(reqres.getReceiver())) and reqres.getMethodName() = "resolve" and this = reqres.getArgument(0) ) } } /** A constant path element appearing in a call to `require` or `require.resolve`. */ deprecated private class ConstantRequirePathElement extends PathExpr, ConstantString { ConstantRequirePathElement() { this = any(RequirePath rp).getAPart() } override string getValue() { result = this.getStringValue() } } /** A `__dirname` path expression. */ deprecated private class DirNamePath extends PathExpr, VarAccess { DirNamePath() { this.getName() = "__dirname" and this.getVariable().getScope() instanceof ModuleScope } override string getValue() { result = this.getFile().getParentContainer().getAbsolutePath() } } /** A `__filename` path expression. */ deprecated private class FileNamePath extends PathExpr, VarAccess { FileNamePath() { this.getName() = "__filename" and this.getVariable().getScope() instanceof ModuleScope } override string getValue() { result = this.getFile().getAbsolutePath() } } /** * A path expression of the form `path.join(p, "...")` where * `p` is also a path expression. */ deprecated private class JoinedPath extends PathExpr, @call_expr { JoinedPath() { exists(MethodCallExpr call | call = this | call.getReceiver().(VarAccess).getName() = "path" and call.getMethodName() = "join" and call.getNumArgument() = 2 and call.getArgument(0) instanceof PathExpr and call.getArgument(1) instanceof ConstantString ) } override string getValue() { exists(CallExpr call, PathExpr left, ConstantString right | call = this and left = call.getArgument(0) and right = call.getArgument(1) | result = left.getValue() + "/" + right.getStringValue() ) } } /** * A reference to the special `module` variable. * * Example: * * ``` * module * ``` */ class ModuleAccess extends VarAccess { ModuleAccess() { exists(ModuleScope ms | this = ms.getVariable("module").getAnAccess()) } }