33 */
44
55import javascript
6+ import DOMProperties
7+ import semmle.javascript.frameworks.xUnit
8+ import semmle.javascript.RestrictedLocations
69
710/**
811 * Holds if `e` appears in a syntactic context where its value is discarded.
@@ -37,3 +40,121 @@ predicate inVoidContext(Expr e) {
3740 or
3841 exists ( LogicalBinaryExpr logical | e = logical .getRightOperand ( ) and inVoidContext ( logical ) )
3942}
43+
44+
45+ /**
46+ * Holds if `e` is of the form `x;` or `e.p;` and has a JSDoc comment containing a tag.
47+ * In that case, it is probably meant as a declaration and shouldn't be flagged by this query.
48+ *
49+ * This will still flag cases where the JSDoc comment contains no tag at all (and hence carries
50+ * no semantic information), and expression statements with an ordinary (non-JSDoc) comment
51+ * attached to them.
52+ */
53+ predicate isDeclaration ( Expr e ) {
54+ ( e instanceof VarAccess or e instanceof PropAccess ) and
55+ exists ( e .getParent ( ) .( ExprStmt ) .getDocumentation ( ) .getATag ( ) )
56+ }
57+
58+ /**
59+ * Holds if there exists a getter for a property called `name` anywhere in the program.
60+ */
61+ predicate isGetterProperty ( string name ) {
62+ // there is a call of the form `Object.defineProperty(..., name, descriptor)` ...
63+ exists ( CallToObjectDefineProperty defProp | name = defProp .getPropertyName ( ) |
64+ // ... where `descriptor` defines a getter
65+ defProp .hasPropertyAttributeWrite ( "get" , _)
66+ or
67+ // ... where `descriptor` may define a getter
68+ exists ( DataFlow:: SourceNode descriptor | descriptor .flowsTo ( defProp .getPropertyDescriptor ( ) ) |
69+ descriptor .isIncomplete ( _)
70+ or
71+ // minimal escape analysis for the descriptor
72+ exists ( DataFlow:: InvokeNode invk |
73+ not invk = defProp and
74+ descriptor .flowsTo ( invk .getAnArgument ( ) )
75+ )
76+ )
77+ )
78+ or
79+ // there is an object expression with a getter property `name`
80+ exists ( ObjectExpr obj | obj .getPropertyByName ( name ) instanceof PropertyGetter )
81+ }
82+
83+ /**
84+ * A property access that may invoke a getter.
85+ */
86+ class GetterPropertyAccess extends PropAccess {
87+ override predicate isImpure ( ) { isGetterProperty ( getPropertyName ( ) ) }
88+ }
89+
90+ /**
91+ * Holds if `c` is an indirect eval call of the form `(dummy, eval)(...)`, where
92+ * `dummy` is some expression whose value is discarded, and which simply
93+ * exists to prevent the call from being interpreted as a direct eval.
94+ */
95+ predicate isIndirectEval ( CallExpr c , Expr dummy ) {
96+ exists ( SeqExpr seq | seq = c .getCallee ( ) .stripParens ( ) |
97+ dummy = seq .getOperand ( 0 ) and
98+ seq .getOperand ( 1 ) .( GlobalVarAccess ) .getName ( ) = "eval" and
99+ seq .getNumOperands ( ) = 2
100+ )
101+ }
102+
103+ /**
104+ * Holds if `c` is a call of the form `(dummy, e[p])(...)`, where `dummy` is
105+ * some expression whose value is discarded, and which simply exists
106+ * to prevent the call from being interpreted as a method call.
107+ */
108+ predicate isReceiverSuppressingCall ( CallExpr c , Expr dummy , PropAccess callee ) {
109+ exists ( SeqExpr seq | seq = c .getCallee ( ) .stripParens ( ) |
110+ dummy = seq .getOperand ( 0 ) and
111+ seq .getOperand ( 1 ) = callee and
112+ seq .getNumOperands ( ) = 2
113+ )
114+ }
115+
116+ /**
117+ * Holds if evaluating `e` has no side effects (except potentially allocating
118+ * and initializing a new object).
119+ *
120+ * For calls, we do not check whether their arguments have any side effects:
121+ * even if they do, the call itself is useless and should be flagged by this
122+ * query.
123+ */
124+ predicate noSideEffects ( Expr e ) {
125+ e .isPure ( )
126+ or
127+ // `new Error(...)`, `new SyntaxError(...)`, etc.
128+ forex ( Function f | f = e .flow ( ) .( DataFlow:: NewNode ) .getACallee ( ) |
129+ f .( ExternalType ) .getASupertype * ( ) .getName ( ) = "Error"
130+ )
131+ }
132+
133+ /**
134+ * Holds if the expression `e` should be reported as having no effect.
135+ */
136+ predicate hasNoEffect ( Expr e ) {
137+ noSideEffects ( e ) and
138+ inVoidContext ( e ) and
139+ // disregard pure expressions wrapped in a void(...)
140+ not e instanceof VoidExpr and
141+ // filter out directives (unknown directives are handled by UnknownDirective.ql)
142+ not exists ( Directive d | e = d .getExpr ( ) ) and
143+ // or about externs
144+ not e .inExternsFile ( ) and
145+ // don't complain about declarations
146+ not isDeclaration ( e ) and
147+ // exclude DOM properties, which sometimes have magical auto-update properties
148+ not isDOMProperty ( e .( PropAccess ) .getPropertyName ( ) ) and
149+ // exclude xUnit.js annotations
150+ not e instanceof XUnitAnnotation and
151+ // exclude common patterns that are most likely intentional
152+ not isIndirectEval ( _, e ) and
153+ not isReceiverSuppressingCall ( _, e , _) and
154+ // exclude anonymous function expressions as statements; these can only arise
155+ // from a syntax error we already flag
156+ not exists ( FunctionExpr fe , ExprStmt es | fe = e |
157+ fe = es .getExpr ( ) and
158+ not exists ( fe .getName ( ) )
159+ )
160+ }
0 commit comments