Files
codeql/javascript/ql/src/Expressions/ImplicitOperandConversion.ql
2018-08-02 17:53:23 +01:00

213 lines
5.9 KiB
Plaintext

/**
* @name Implicit operand conversion
* @description Relying on implicit conversion of operands is error-prone and makes code
* hard to read.
* @kind problem
* @problem.severity warning
* @id js/implicit-operand-conversion
* @tags reliability
* readability
* external/cwe/cwe-704
* @precision very-high
*/
import javascript
private import semmle.javascript.dataflow.InferredTypes
/**
* An expression that appears in a syntactic position where its value may be
* implicitly converted.
*/
abstract class ImplicitConversion extends DataFlow::AnalyzedNode {
Expr parent;
ImplicitConversion() {
this.asExpr() = parent.getAChildExpr()
}
/**
* Gets a description of the type(s) to which the value `v`, which is
* a possible runtime value of this expression, is converted.
*
* This predicate only considers conversions that are likely to be
* unintentional or have unexpected results, for example `null` being
* converted to a string as part of a string concatenation.
*/
abstract string getAnImplicitConversionTarget(AbstractValue v);
}
/**
* An implicit conversion with a whitelist of types for which the implicit conversion
* is harmless.
*/
abstract class ImplicitConversionWithWhitelist extends ImplicitConversion {
/** Gets a type for which this implicit conversion is harmless. */
abstract InferredType getAWhitelistedType();
/**
* Gets a description of the type(s) to which any value of this expression
* is converted.
*/
abstract string getConversionTarget();
override string getAnImplicitConversionTarget(AbstractValue v) {
v = getAValue() and
not (v.getType() = getAWhitelistedType()) and
result = getConversionTarget()
}
}
/**
* Property names in `in` expressions are converted to strings,
* so they should be strings or numbers.
*/
class PropertyNameConversion extends ImplicitConversionWithWhitelist {
PropertyNameConversion() {
this.asExpr() = parent.(InExpr).getLeftOperand()
}
override InferredType getAWhitelistedType() {
result = TTString() or result = TTNumber()
}
override string getConversionTarget() {
result = "string"
}
}
/**
* Property names in index expressions are converted to strings,
* so they should be Booleans, strings or numbers.
*/
class IndexExprConversion extends ImplicitConversionWithWhitelist {
IndexExprConversion() {
this.asExpr() = parent.(IndexExpr).getIndex()
}
override InferredType getAWhitelistedType() {
result = TTBoolean() or result = TTString() or result = TTNumber()
}
override string getConversionTarget() {
result = "string"
}
}
/**
* Expressions that are interpreted as objects shouldn't be primitive values.
*/
class ObjectConversion extends ImplicitConversionWithWhitelist {
ObjectConversion() {
this.asExpr() = parent.(InExpr).getRightOperand() or
this.asExpr() = parent.(InstanceofExpr).getLeftOperand()
}
override InferredType getAWhitelistedType() {
result instanceof NonPrimitiveType
}
override string getConversionTarget() {
result = "object"
}
}
/**
* The right-hand operand of `instanceof` should be a function or class.
*/
class ConstructorConversion extends ImplicitConversionWithWhitelist {
ConstructorConversion() {
this.asExpr() = parent.(InstanceofExpr).getRightOperand()
}
override InferredType getAWhitelistedType() {
result = TTFunction() or result = TTClass()
}
override string getConversionTarget() {
result = "function"
}
}
/**
* Operands of relational operators are converted to strings or numbers,
* and hence should be strings, numbers or Dates.
*/
class RelationalOperandConversion extends ImplicitConversionWithWhitelist {
RelationalOperandConversion() {
parent instanceof RelationalComparison
}
override InferredType getAWhitelistedType() {
result = TTString() or result = TTNumber() or result = TTDate()
}
override string getConversionTarget() {
result = "number or string"
}
}
/**
* Operands of arithmetic and bitwise operations are converted to numbers,
* so they should be Booleans, numbers or Dates.
*/
class NumericConversion extends ImplicitConversion {
NumericConversion() {
parent instanceof BitwiseExpr or
parent instanceof ArithmeticExpr and not parent instanceof AddExpr or
parent instanceof CompoundAssignExpr and not parent instanceof AssignAddExpr or
parent instanceof UpdateExpr
}
override string getAnImplicitConversionTarget(AbstractValue v) {
v = getAValue() and
not v.isCoercibleToNumber() and
result = "number"
}
}
/**
* An expression whose value should not be `null` or `undefined`.
*/
abstract class NullOrUndefinedConversion extends ImplicitConversion {
abstract string getConversionTarget();
override string getAnImplicitConversionTarget(AbstractValue v) {
v = getAValue() and
(v instanceof AbstractNull or v instanceof AbstractUndefined) and
result = getConversionTarget()
}
}
/**
* Operands of `+` or `+=` are converted to strings or numbers, and hence
* should not be `null` or `undefined`.
*/
class PlusConversion extends NullOrUndefinedConversion {
PlusConversion() {
parent instanceof AddExpr or parent instanceof AssignAddExpr
}
override string getConversionTarget() {
result = "number or string"
}
}
/**
* Template literal elements are converted to strings, and hence should not
* be `null` or `undefined`.
*/
class TemplateElementConversion extends NullOrUndefinedConversion {
TemplateElementConversion() {
parent instanceof TemplateLiteral
}
override string getConversionTarget() {
result = "string"
}
}
from ImplicitConversion e, string convType
where convType = e.getAnImplicitConversionTarget(_) and
forall (AbstractValue v | v = e.getAValue() | exists(e.getAnImplicitConversionTarget(v)))
select e, "This expression will be implicitly converted from " +
e.ppTypes() + " to " + convType + "."