/** * Provides classes and predicates for the 'js/useless-expression' query. */ overlay[local] module; import javascript import DOMProperties /** * Holds if `e` appears in a syntactic context where its value is discarded. */ predicate inVoidContext(Expr e) { exists(ExprStmt parent | // e is a toplevel expression in an expression statement parent = e.getParent() and // but it isn't an HTML attribute or a configuration object not exists(TopLevel tl | tl = parent.getParent() | tl instanceof CodeInAttribute or // if the toplevel in its entirety is of the form `({ ... })`, // it is probably a configuration object (e.g., a require.js build configuration) tl.getNumChildStmt() = 1 and e.stripParens() instanceof ObjectExpr ) ) or // propagate void context through parenthesized expressions inVoidContext(e.getParent().(ParExpr)) or exists(SeqExpr seq, int i, int n | e = seq.getOperand(i) and n = seq.getNumOperands() | i < n - 1 or inVoidContext(seq) ) or exists(ForStmt stmt | e = stmt.getUpdate()) or exists(ForStmt stmt | e = stmt.getInit() | // Allow the pattern `for(i; i < 10; i++)` not e instanceof VarAccess ) or exists(LogicalBinaryExpr logical | e = logical.getRightOperand() and inVoidContext(logical)) or exists(ConditionalExpr cond | e = cond.getABranch() | inVoidContext(cond)) } /** * Holds if `e` is of the form `x;` or `e.p;` and has a JSDoc comment containing a tag. * In that case, it is probably meant as a declaration and shouldn't be flagged by this query. * * This will still flag cases where the JSDoc comment contains no tag at all (and hence carries * no semantic information), and expression statements with an ordinary (non-JSDoc) comment * attached to them. */ predicate isDeclaration(Expr e) { (e instanceof VarAccess or e instanceof PropAccess) and exists(e.getParent().(ExprStmt).getDocumentation().getATag()) } /** * Holds if there exists a getter for a property called `name` anywhere in the program. */ overlay[global] predicate isGetterProperty(string name) { // there is a call of the form `Object.defineProperty(..., name, descriptor)` ... exists(CallToObjectDefineProperty defProp | name = defProp.getPropertyName() | // ... where `descriptor` defines a getter defProp.hasPropertyAttributeWrite("get", _) or // ... where `descriptor` may define a getter exists(DataFlow::SourceNode descriptor | descriptor.flowsTo(defProp.getPropertyDescriptor()) | descriptor.isIncomplete(_) or // minimal escape analysis for the descriptor exists(DataFlow::InvokeNode invk | not invk = defProp and descriptor.flowsTo(invk.getAnArgument()) ) ) ) or // there is an object expression with a getter property `name` exists(ObjectExpr obj | obj.getPropertyByName(name) instanceof PropertyGetter) } /** * A property access that may invoke a getter. */ overlay[global] class GetterPropertyAccess extends PropAccess { override predicate isImpure() { isGetterProperty(this.getPropertyName()) } } /** * Holds if `c` is an indirect eval call of the form `(dummy, eval)(...)`, where * `dummy` is some expression whose value is discarded, and which simply * exists to prevent the call from being interpreted as a direct eval. */ predicate isIndirectEval(CallExpr c, Expr dummy) { exists(SeqExpr seq | seq = c.getCallee().stripParens() | dummy = seq.getOperand(0) and seq.getOperand(1).(GlobalVarAccess).getName() = "eval" and seq.getNumOperands() = 2 ) } /** * Holds if `c` is a call of the form `(dummy, e[p])(...)`, where `dummy` is * some expression whose value is discarded, and which simply exists * to prevent the call from being interpreted as a method call. */ predicate isReceiverSuppressingCall(CallExpr c, Expr dummy, PropAccess callee) { exists(SeqExpr seq | seq = c.getCallee().stripParens() | dummy = seq.getOperand(0) and seq.getOperand(1) = callee and seq.getNumOperands() = 2 ) } /** * Holds if evaluating `e` has no side effects (except potentially allocating * and initializing a new object). * * For calls, we do not check whether their arguments have any side effects: * even if they do, the call itself is useless and should be flagged by this * query. */ overlay[global] predicate noSideEffects(Expr e) { e.isPure() or // `new Error(...)`, `new SyntaxError(...)`, etc. forex(Function f | f = e.flow().(DataFlow::NewNode).getACallee() | f.(ExternalType).getASupertype*().getName() = "Error" ) } /** * Holds if `e` is a compound expression that may contain sub-expressions with side effects. * We should not flag these directly as useless since we want to flag only the innermost * expressions that actually have no effect. */ predicate isCompoundExpression(Expr e) { e instanceof LogicalBinaryExpr or e instanceof SeqExpr or e instanceof ParExpr } /** * Holds if the expression `e` should be reported as having no effect. */ overlay[global] predicate hasNoEffect(Expr e) { noSideEffects(e) and inVoidContext(e) and // disregard pure expressions wrapped in a void(...) not e instanceof VoidExpr and // filter out directives (unknown directives are handled by UnknownDirective.ql) not exists(Directive d | e = d.getExpr()) and // or about externs not e.inExternsFile() and // don't complain about declarations not isDeclaration(e) and // exclude DOM properties, which sometimes have magical auto-update properties not isDomProperty(e.(PropAccess).getPropertyName()) and not isCompoundExpression(e) and // exclude xUnit.js annotations not e instanceof XUnitAnnotation and // exclude common patterns that are most likely intentional not isIndirectEval(_, e) and not isReceiverSuppressingCall(_, e, _) and // exclude anonymous function expressions as statements; these can only arise // from a syntax error we already flag not exists(FunctionExpr fe, ExprStmt es | fe = e | fe = es.getExpr() and not exists(fe.getName()) ) and // exclude block-level flow type annotations. For example: `(name: empty)`. not exists(ParExpr parent | e.getParent() = parent and e.getLastToken().getNextToken().getValue() = ":" ) and // exclude expressions that are part of a conditional expression not exists(ConditionalExpr cond | e = cond.getABranch() | e instanceof NullLiteral or e.(GlobalVarAccess).getName() = "undefined" or e.(NumberLiteral).getIntValue() = 0 or e instanceof VoidExpr ) and // exclude the first statement of a try block not e = any(TryStmt stmt).getBody().getStmt(0).(ExprStmt).getExpr() and // exclude expressions that are alone in a file, and file doesn't contain a function. not exists(TopLevel top | top = e.getParent().(ExprStmt).getParent() and top.getNumChild() = 1 and not exists(Function fun | fun.getEnclosingContainer() = top) ) and // ignore Angular templates not e.getTopLevel() instanceof Angular2::TemplateTopLevel }