1
1
package com .semmle .js .extractor ;
2
2
3
3
import com .semmle .js .ast .AssignmentExpression ;
4
- import com .semmle .js .ast .BlockStatement ;
5
4
import com .semmle .js .ast .CallExpression ;
6
5
import 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 ;
11
6
import com .semmle .js .ast .MemberExpression ;
12
7
import 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 ;
21
8
22
9
/** A utility class for detecting Node.js code. */
23
- public class NodeJSDetector {
10
+ public class NodeJSDetector extends AbstractDetector {
24
11
/**
25
12
* 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 = ...} ?
27
14
*/
28
15
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 );
43
17
}
44
18
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('...')
97
22
if (e instanceof CallExpression ) {
98
23
CallExpression call = (CallExpression ) e ;
99
24
Expression callee = call .getCallee ();
100
25
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 ());
112
26
}
113
- return false ;
114
- }
115
27
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 ) {
130
28
if (e instanceof AssignmentExpression ) {
131
29
AssignmentExpression assgn = (AssignmentExpression ) e ;
132
30
@@ -149,12 +47,9 @@ private static boolean isExport(Expression e) {
149
47
if (isModuleExports (targetBase )) return true ;
150
48
}
151
49
}
152
-
153
- // recursively check right hand side
154
- return isExport (assgn .getRight ());
155
50
}
156
51
157
- return false ;
52
+ return super . visitExpression ( e ) ;
158
53
}
159
54
160
55
/** Is {@code me} a member expression {@code module.exports}? */
@@ -163,9 +58,4 @@ private static boolean isModuleExports(MemberExpression me) {
163
58
&& isIdentifier (me .getObject (), "module" )
164
59
&& isIdentifier (me .getProperty (), "exports" );
165
60
}
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
- }
171
61
}
0 commit comments