11package com .semmle .js .extractor ;
22
33import com .semmle .js .ast .AssignmentExpression ;
4- import com .semmle .js .ast .BlockStatement ;
54import com .semmle .js .ast .CallExpression ;
65import com .semmle .js .ast .Expression ;
7- import com .semmle .js .ast .ExpressionStatement ;
8- import com .semmle .js .ast .IFunction ;
9- import com .semmle .js .ast .Identifier ;
10- import com .semmle .js .ast .IfStatement ;
116import com .semmle .js .ast .MemberExpression ;
127import com .semmle .js .ast .Node ;
13- import com .semmle .js .ast .ParenthesizedExpression ;
14- import com .semmle .js .ast .Program ;
15- import com .semmle .js .ast .Statement ;
16- import com .semmle .js .ast .TryStatement ;
17- import com .semmle .js .ast .UnaryExpression ;
18- import com .semmle .js .ast .VariableDeclaration ;
19- import com .semmle .js .ast .VariableDeclarator ;
20- import java .util .List ;
218
229/** A utility class for detecting Node.js code. */
23- public class NodeJSDetector {
10+ public class NodeJSDetector extends AbstractDetector {
2411 /**
2512 * Is {@code ast} a program that looks like Node.js code, that is, does it contain a top-level
26- * {@code require} or an export ?
13+ * {@code require} or an {@code module.exports = ...}/{@code exports = ...} ?
2714 */
2815 public static boolean looksLikeNodeJS (Node ast ) {
29- if (!(ast instanceof Program )) return false ;
30-
31- return hasToplevelRequireOrExport (((Program ) ast ).getBody ());
32- }
33-
34- /**
35- * Does this program contain a statement that looks like a Node.js {@code require} or an export?
36- *
37- * <p>We recursively traverse argument-less immediately invoked function expressions (i.e., no UMD
38- * modules), but not loops or if statements.
39- */
40- private static boolean hasToplevelRequireOrExport (List <Statement > stmts ) {
41- for (Statement stmt : stmts ) if (hasToplevelRequireOrExport (stmt )) return true ;
42- return false ;
16+ return new NodeJSDetector ().programDetection (ast );
4317 }
4418
45- private static boolean hasToplevelRequireOrExport (Statement stmt ) {
46- if (stmt instanceof ExpressionStatement ) {
47- Expression e = stripParens (((ExpressionStatement ) stmt ).getExpression ());
48-
49- // check whether `e` is an iife; if so, recursively check its body
50-
51- // strip off unary operators to handle `!function(){...}()`
52- if (e instanceof UnaryExpression ) e = ((UnaryExpression ) e ).getArgument ();
53-
54- if (e instanceof CallExpression && ((CallExpression ) e ).getArguments ().isEmpty ()) {
55- Expression callee = stripParens (((CallExpression ) e ).getCallee ());
56- if (callee instanceof IFunction ) {
57- Node body = ((IFunction ) callee ).getBody ();
58- if (body instanceof BlockStatement )
59- return hasToplevelRequireOrExport (((BlockStatement ) body ).getBody ());
60- }
61- }
62-
63- if (isRequireCall (e ) || isExport (e )) return true ;
64-
65- } else if (stmt instanceof VariableDeclaration ) {
66- for (VariableDeclarator decl : ((VariableDeclaration ) stmt ).getDeclarations ()) {
67- Expression init = stripParens (decl .getInit ());
68- if (isRequireCall (init ) || isExport (init )) return true ;
69- }
70-
71- } else if (stmt instanceof TryStatement ) {
72- return hasToplevelRequireOrExport (((TryStatement ) stmt ).getBlock ());
73-
74- } else if (stmt instanceof BlockStatement ) {
75- return hasToplevelRequireOrExport (((BlockStatement ) stmt ).getBody ());
76-
77- } else if (stmt instanceof IfStatement ) {
78- IfStatement is = (IfStatement ) stmt ;
79- return hasToplevelRequireOrExport (is .getConsequent ())
80- || hasToplevelRequireOrExport (is .getAlternate ());
81- }
82-
83- return false ;
84- }
85-
86- private static Expression stripParens (Expression e ) {
87- if (e instanceof ParenthesizedExpression )
88- return stripParens (((ParenthesizedExpression ) e ).getExpression ());
89- return e ;
90- }
91-
92- /**
93- * Is {@code e} a call to a function named {@code require} with one argument, or an assignment
94- * whose right hand side is the result of such a call?
95- */
96- private static boolean isRequireCall (Expression e ) {
19+ @ Override
20+ protected boolean visitExpression (Expression e ) {
21+ // require('...')
9722 if (e instanceof CallExpression ) {
9823 CallExpression call = (CallExpression ) e ;
9924 Expression callee = call .getCallee ();
10025 if (isIdentifier (callee , "require" ) && call .getArguments ().size () == 1 ) return true ;
101- if (isRequireCall (callee )) return true ;
102- return false ;
103- } else if (e instanceof MemberExpression ) {
104- return isRequireCall (((MemberExpression ) e ).getObject ());
105- } else if (e instanceof AssignmentExpression ) {
106- AssignmentExpression assgn = (AssignmentExpression ) e ;
107-
108- // filter out compound assignments
109- if (!"=" .equals (assgn .getOperator ())) return false ;
110-
111- return isRequireCall (assgn .getRight ());
11226 }
113- return false ;
114- }
11527
116- /**
117- * Does {@code e} look like a Node.js export?
118- *
119- * <p>Currently, three kinds of exports are recognised:
120- *
121- * <ul>
122- * <li><code>exports.foo = ...</code>
123- * <li><code>module.exports = ...</code>
124- * <li><code>module.exports.foo = ...</code>
125- * </ul>
126- *
127- * Detection is done recursively, so <code>foo = exports.foo = ...</code> is handled correctly.
128- */
129- private static boolean isExport (Expression e ) {
13028 if (e instanceof AssignmentExpression ) {
13129 AssignmentExpression assgn = (AssignmentExpression ) e ;
13230
@@ -149,12 +47,9 @@ private static boolean isExport(Expression e) {
14947 if (isModuleExports (targetBase )) return true ;
15048 }
15149 }
152-
153- // recursively check right hand side
154- return isExport (assgn .getRight ());
15550 }
15651
157- return false ;
52+ return super . visitExpression ( e ) ;
15853 }
15954
16055 /** Is {@code me} a member expression {@code module.exports}? */
@@ -163,9 +58,4 @@ private static boolean isModuleExports(MemberExpression me) {
16358 && isIdentifier (me .getObject (), "module" )
16459 && isIdentifier (me .getProperty (), "exports" );
16560 }
166-
167- /** Is {@code e} an identifier with name {@code name}? */
168- private static boolean isIdentifier (Expression e , String name ) {
169- return e instanceof Identifier && name .equals (((Identifier ) e ).getName ());
170- }
17161}
0 commit comments