import python private import semmle.python.pointsto.PointsTo private import semmle.python.objects.ObjectInternal private import semmle.python.internal.CachedStages /** An expression */ class Expr extends Expr_, AstNode { /** Gets the scope of this expression */ override Scope getScope() { py_scopes(this, result) } /** Gets a textual representation of this element. */ cached override string toString() { Stages::AST::ref() and result = "Expression" } /** Gets the module in which this expression occurs */ Module getEnclosingModule() { result = this.getScope().getEnclosingModule() } /** * Whether this expression defines variable `v` * If doing dataflow, then consider using SsaVariable.getDefinition() for more precision. */ predicate defines(Variable v) { this.getASubExpression().defines(v) } /** Whether this expression may have a side effect (as determined purely from its syntax) */ predicate hasSideEffects() { /* If an exception raised by this expression handled, count that as a side effect */ this.getAFlowNode().getASuccessor().getNode() instanceof ExceptStmt or this.getASubExpression().hasSideEffects() } /** Whether this expression is a constant */ predicate isConstant() { not this.isVariable() } /** Whether the parenthesized property of this expression is true. */ predicate isParenthesized() { Expr_.super.isParenthesised() } private predicate isVariable() { this.hasSideEffects() or this instanceof Name or exists(Expr e | e = this.getASubExpression() and e.isVariable()) } override Location getLocation() { result = Expr_.super.getLocation() } /** Gets an immediate (non-nested) sub-expression of this expression */ Expr getASubExpression() { none() } override AstNode getAChildNode() { result = this.getASubExpression() } /** * NOTE: `refersTo` will be deprecated in 2019. Use `pointsTo` instead. * Gets what this expression might "refer-to". Performs a combination of localized (intra-procedural) points-to * analysis and global module-level analysis. This points-to analysis favours precision over recall. It is highly * precise, but may not provide information for a significant number of flow-nodes. * If the class is unimportant then use `refersTo(value)` or `refersTo(value, origin)` instead. * NOTE: For complex dataflow, involving multiple stages of points-to analysis, it may be more precise to use * `ControlFlowNode.refersTo(...)` instead. */ predicate refersTo(Object obj, ClassObject cls, AstNode origin) { this.refersTo(_, obj, cls, origin) } /** * NOTE: `refersTo` will be deprecated in 2019. Use `pointsTo` instead. * Gets what this expression might "refer-to" in the given `context`. */ predicate refersTo(Context context, Object obj, ClassObject cls, AstNode origin) { this.getAFlowNode().refersTo(context, obj, cls, origin.getAFlowNode()) } /** * NOTE: `refersTo` will be deprecated in 2019. Use `pointsTo` instead. * Holds if this expression might "refer-to" to `value` which is from `origin` * Unlike `this.refersTo(value, _, origin)`, this predicate includes results * where the class cannot be inferred. */ pragma[nomagic] predicate refersTo(Object obj, AstNode origin) { this.getAFlowNode().refersTo(obj, origin.getAFlowNode()) } /** * NOTE: `refersTo` will be deprecated in 2019. Use `pointsTo` instead. * Equivalent to `this.refersTo(value, _)` */ predicate refersTo(Object obj) { this.refersTo(obj, _) } /** * Holds if this expression might "point-to" to `value` which is from `origin` * in the given `context`. */ predicate pointsTo(Context context, Value value, AstNode origin) { this.getAFlowNode().pointsTo(context, value, origin.getAFlowNode()) } /** * Holds if this expression might "point-to" to `value` which is from `origin`. */ predicate pointsTo(Value value, AstNode origin) { this.getAFlowNode().pointsTo(value, origin.getAFlowNode()) } /** * Holds if this expression might "point-to" to `value`. */ predicate pointsTo(Value value) { this.pointsTo(value, _) } /** Gets a value that this expression might "point-to". */ Value pointsTo() { this.pointsTo(result) } } /** An assignment expression, such as `x := y` */ class AssignExpr extends AssignExpr_ { override Expr getASubExpression() { result = this.getValue() or result = this.getTarget() } } /** An attribute expression, such as `value.attr` */ class Attribute extends Attribute_ { /* syntax: Expr.name */ override Expr getASubExpression() { result = this.getObject() } override AttrNode getAFlowNode() { result = super.getAFlowNode() } /** Gets the name of this attribute. That is the `name` in `obj.name` */ string getName() { result = Attribute_.super.getAttr() } /** Gets the object of this attribute. That is the `obj` in `obj.name` */ Expr getObject() { result = Attribute_.super.getValue() } /** * Gets the expression corresponding to the object of the attribute, if the name of the attribute is `name`. * Equivalent to `this.getObject() and this.getName() = name`. */ Expr getObject(string name) { result = Attribute_.super.getValue() and name = Attribute_.super.getAttr() } } /** A subscript expression, such as `value[slice]` */ class Subscript extends Subscript_ { /* syntax: Expr[Expr] */ override Expr getASubExpression() { result = this.getIndex() or result = this.getObject() } Expr getObject() { result = Subscript_.super.getValue() } override SubscriptNode getAFlowNode() { result = super.getAFlowNode() } } /** A call expression, such as `func(...)` */ class Call extends Call_ { /* syntax: Expr(...) */ override Expr getASubExpression() { result = this.getAPositionalArg() or result = this.getAKeyword().getValue() or result = this.getFunc() } override predicate hasSideEffects() { any() } override string toString() { result = this.getFunc().toString() + "()" } override CallNode getAFlowNode() { result = super.getAFlowNode() } /** Gets a tuple (*) argument of this call. */ Expr getStarargs() { result = this.getAPositionalArg().(Starred).getValue() } /** Gets a dictionary (**) argument of this call. */ Expr getKwargs() { result = this.getANamedArg().(DictUnpacking).getValue() } /* Backwards compatibility */ /** * Gets the nth keyword argument of this call expression, provided it is not preceded by a double-starred argument. * This exists primarily for backwards compatibility. You are recommended to use * Call.getNamedArg(index) instead. */ Keyword getKeyword(int index) { result = this.getNamedArg(index) and ( not exists(this.getMinimumUnpackingIndex()) or index <= this.getMinimumUnpackingIndex() ) } /** Gets the minimum index (if any) at which a dictionary unpacking (`**foo`) occurs in this call. */ private int getMinimumUnpackingIndex() { result = min(int i | this.getNamedArg(i) instanceof DictUnpacking) } /** * Gets a keyword argument of this call expression, provided it is not preceded by a double-starred argument. * This exists primarily for backwards compatibility. You are recommended to use * Call.getANamedArg() instead. */ Keyword getAKeyword() { result = this.getKeyword(_) } /** * Gets the positional argument at `index`, provided it is not preceded by a starred argument. * This exists primarily for backwards compatibility. You are recommended to use * Call.getPositionalArg(index) instead. */ Expr getArg(int index) { result = this.getPositionalArg(index) and not result instanceof Starred and not exists(Starred s, int lower | s = this.getPositionalArg(lower) and lower < index) } /** * Gets a positional argument, provided it is not preceded by a starred argument. * This exists primarily for backwards compatibility. You are recommended to use * Call.getAPositionalArg() instead. */ Expr getAnArg() { result = this.getArg(_) } override AstNode getAChildNode() { result = this.getAPositionalArg() or result = this.getANamedArg() or result = this.getFunc() } /** Gets the name of a named argument, including those passed in dict literals. */ string getANamedArgumentName() { result = this.getAKeyword().getArg() or result = this.getKwargs().(Dict).getAKey().(StrConst).getText() } /** Gets the positional argument count of this call, provided there is no more than one tuple (*) argument. */ int getPositionalArgumentCount() { count(this.getStarargs()) < 2 and result = count(Expr arg | arg = this.getAPositionalArg() and not arg instanceof Starred) } /** Gets the tuple (*) argument of this call, provided there is exactly one. */ Expr getStarArg() { count(this.getStarargs()) < 2 and result = this.getStarargs() } } /** A conditional expression such as, `body if test else orelse` */ class IfExp extends IfExp_ { /* syntax: Expr if Expr else Expr */ override Expr getASubExpression() { result = this.getTest() or result = this.getBody() or result = this.getOrelse() } override IfExprNode getAFlowNode() { result = super.getAFlowNode() } } /** A starred expression, such as the `*rest` in the assignment `first, *rest = seq` */ class Starred extends Starred_ { /* syntax: *Expr */ override Expr getASubExpression() { result = this.getValue() } } /** A yield expression, such as `yield value` */ class Yield extends Yield_ { /* syntax: yield Expr */ override Expr getASubExpression() { result = this.getValue() } override predicate hasSideEffects() { any() } } /** A yield expression, such as `yield from value` */ class YieldFrom extends YieldFrom_ { /* syntax: yield from Expr */ override Expr getASubExpression() { result = this.getValue() } override predicate hasSideEffects() { any() } } /** A repr (backticks) expression, such as `` `value` `` */ class Repr extends Repr_ { /* syntax: `Expr` */ override Expr getASubExpression() { result = this.getValue() } override predicate hasSideEffects() { any() } } /* Constants */ /** * A bytes constant, such as `b'ascii'`. Note that unadorned string constants such as * `"hello"` are treated as Bytes for Python2, but Unicode for Python3. */ class Bytes extends StrConst { /* syntax: b"hello" */ Bytes() { not this.isUnicode() } override Object getLiteralObject() { py_cobjecttypes(result, theBytesType()) and py_cobjectnames(result, this.quotedString()) } /** * The extractor puts quotes into the name of each string (to prevent "0" clashing with 0). * The following predicate help us match up a string/byte literals in the source * which the equivalent object. */ private string quotedString() { exists(string b_unquoted | b_unquoted = this.getS() | result = "b'" + b_unquoted + "'") } } /** An ellipsis expression, such as `...` */ class Ellipsis extends Ellipsis_ { /* syntax: ... */ override Expr getASubExpression() { none() } } /** * An immutable literal expression (except tuples). * Consists of string (both unicode and byte) literals and numeric literals. */ abstract class ImmutableLiteral extends Expr { abstract Object getLiteralObject(); abstract boolean booleanValue(); final Value getLiteralValue() { result.(ConstantObjectInternal).getLiteral() = this } } /** A numerical constant expression, such as `7` or `4.2` */ abstract class Num extends Num_, ImmutableLiteral { override Expr getASubExpression() { none() } /* We want to declare this abstract, but currently we cannot. */ override string toString() { result = "Num with missing toString" } } /** An integer numeric constant, such as `7` or `0x9` */ class IntegerLiteral extends Num { /* syntax: 4 */ IntegerLiteral() { not this instanceof FloatLiteral and not this instanceof ImaginaryLiteral } /** * Gets the (integer) value of this constant. Will not return a result if the value does not fit into * a 32 bit signed value */ int getValue() { result = this.getN().toInt() } override string toString() { result = "IntegerLiteral" } override Object getLiteralObject() { py_cobjecttypes(result, theIntType()) and py_cobjectnames(result, this.getN()) or py_cobjecttypes(result, theLongType()) and py_cobjectnames(result, this.getN()) } override boolean booleanValue() { this.getValue() = 0 and result = false or this.getValue() != 0 and result = true } } /** A floating point numeric constant, such as `0.4` or `4e3` */ class FloatLiteral extends Num { /* syntax: 4.2 */ FloatLiteral() { not this instanceof ImaginaryLiteral and this.getN().regexpMatch(".*[.eE].*") } float getValue() { result = this.getN().toFloat() } override string toString() { result = "FloatLiteral" } override Object getLiteralObject() { py_cobjecttypes(result, theFloatType()) and py_cobjectnames(result, this.getN()) } override boolean booleanValue() { this.getValue() = 0.0 and result = false or // In QL 0.0 != -0.0 this.getValue() = -0.0 and result = false or this.getValue() != 0.0 and this.getValue() != -0.0 and result = true } } /** An imaginary numeric constant, such as `3j` */ class ImaginaryLiteral extends Num { private float value; /* syntax: 1.0j */ ImaginaryLiteral() { value = this.getN().regexpCapture("(.+)j.*", 1).toFloat() } /** Gets the value of this constant as a floating point value */ float getValue() { result = value } override string toString() { result = "ImaginaryLiteral" } override Object getLiteralObject() { py_cobjecttypes(result, theComplexType()) and py_cobjectnames(result, this.getN()) } override boolean booleanValue() { this.getValue() = 0.0 and result = false or // In QL 0.0 != -0.0 this.getValue() = -0.0 and result = false or this.getValue() != 0.0 and this.getValue() != -0.0 and result = true } } class NegativeIntegerLiteral extends ImmutableLiteral, UnaryExpr { NegativeIntegerLiteral() { this.getOp() instanceof USub and this.getOperand() instanceof IntegerLiteral } override boolean booleanValue() { result = this.getOperand().(IntegerLiteral).booleanValue() } override Object getLiteralObject() { (py_cobjecttypes(result, theIntType()) or py_cobjecttypes(result, theLongType())) and py_cobjectnames(result, "-" + this.getOperand().(IntegerLiteral).getN()) } /** * Gets the (integer) value of this constant. Will not return a result if the value does not fit into * a 32 bit signed value */ int getValue() { result = -this.getOperand().(IntegerLiteral).getValue() } } /** * A unicode string expression, such as `u"\u20ac"`. Note that unadorned string constants such as * "hello" are treated as Bytes for Python2, but Unicode for Python3. */ class Unicode extends StrConst { /* syntax: "hello" */ Unicode() { this.isUnicode() } override Object getLiteralObject() { py_cobjecttypes(result, theUnicodeType()) and py_cobjectnames(result, this.quotedString()) } /** * Gets the quoted representation fo this string. * * The extractor puts quotes into the name of each string (to prevent "0" clashing with 0). * The following predicate help us match up a string/byte literals in the source * which the equivalent object. */ string quotedString() { exists(string u_unquoted | u_unquoted = this.getS() | result = "u'" + u_unquoted + "'") } } /* Compound Values */ /** A dictionary expression, such as `{'key':'value'}` */ class Dict extends Dict_ { /* syntax: {Expr: Expr, ...} */ /** Gets the value of an item of this dict display */ Expr getAValue() { result = this.getAnItem().(DictDisplayItem).getValue() } /** * Gets the key of an item of this dict display, for those items that have keys * E.g, in {'a':1, **b} this returns only 'a' */ Expr getAKey() { result = this.getAnItem().(KeyValuePair).getKey() } override Expr getASubExpression() { result = this.getAValue() or result = this.getAKey() } override AstNode getAChildNode() { result = this.getAnItem() } } /** A list expression, such as `[ 1, 3, 5, 7, 9 ]` */ class List extends List_ { /* syntax: [Expr, ...] */ override Expr getASubExpression() { result = this.getAnElt() } } /** A set expression such as `{ 1, 3, 5, 7, 9 }` */ class Set extends Set_ { /* syntax: {Expr, ...} */ override Expr getASubExpression() { result = this.getAnElt() } } class PlaceHolder extends PlaceHolder_ { string getId() { result = this.getVariable().getId() } override Expr getASubExpression() { none() } override string toString() { result = "$" + this.getId() } override NameNode getAFlowNode() { result = super.getAFlowNode() } } /** A tuple expression such as `( 1, 3, 5, 7, 9 )` */ class Tuple extends Tuple_ { /* syntax: (Expr, ...) */ override Expr getASubExpression() { result = this.getAnElt() } } /** * A (plain variable) name expression, such as `var`. * `None`, `True` and `False` are excluded. */ class Name extends Name_ { /* syntax: name */ string getId() { result = this.getVariable().getId() } /** Whether this expression is a definition */ predicate isDefinition() { py_expr_contexts(_, 5, this) or /* Treat Param as a definition (which it is) */ py_expr_contexts(_, 4, this) or /* The target in an augmented assignment is also a definition (and a use) */ exists(AugAssign aa | aa.getTarget() = this) } /** * Whether this expression defines variable `v` * If doing dataflow, then consider using SsaVariable.getDefinition() for more precision. */ override predicate defines(Variable v) { this.isDefinition() and v = this.getVariable() } /** Whether this expression is a deletion */ predicate isDeletion() { py_expr_contexts(_, 2, this) } /** * Whether this expression deletes variable `v`. * If doing dataflow, then consider using SsaVariable.getDefinition() for more precision. */ predicate deletes(Variable v) { this.isDeletion() and v = this.getVariable() } /** Whether this expression is a use */ predicate isUse() { py_expr_contexts(_, 3, this) } /** * Whether this expression is a use of variable `v` * If doing dataflow, then consider using SsaVariable.getAUse() for more precision. */ predicate uses(Variable v) { this.isUse() and v = this.getVariable() } override predicate isConstant() { none() } override Expr getASubExpression() { none() } override string toString() { result = this.getId() } override NameNode getAFlowNode() { result = super.getAFlowNode() } override predicate isArtificial() { /* Artificial variable names in comprehensions all start with "." */ this.getId().charAt(0) = "." } } class Filter extends Filter_ { override Expr getASubExpression() { result = this.getFilter() or result = this.getValue() } } /** A slice. E.g `0:1` in the expression `x[0:1]` */ class Slice extends Slice_ { override Expr getASubExpression() { result = this.getStart() or result = this.getStop() or result = this.getStep() } } /** * Returns all string prefixes in the database that are explicitly marked as Unicode strings. * * Helper predicate for `StrConst::isUnicode`. */ pragma[nomagic] private string unicode_prefix() { result = any(Str_ s).getPrefix() and result.charAt(_) in ["u", "U"] } /** * Returns all string prefixes in the database that are _not_ explicitly marked as bytestrings. * * Helper predicate for `StrConst::isUnicode`. */ pragma[nomagic] private string non_byte_prefix() { result = any(Str_ s).getPrefix() and not result.charAt(_) in ["b", "B"] } /** A string constant. */ class StrConst extends Str_, ImmutableLiteral { /* syntax: "hello" */ predicate isUnicode() { this.getPrefix() = unicode_prefix() or this.getPrefix() = non_byte_prefix() and ( major_version() = 3 or this.getEnclosingModule().hasFromFuture("unicode_literals") ) } override Expr getASubExpression() { none() } override AstNode getAChildNode() { result = this.getAnImplicitlyConcatenatedPart() } /** Gets the text of this str constant */ string getText() { result = this.getS() } /** Whether this is a docstring */ predicate isDocString() { exists(Scope s | s.getDocString() = this) } override boolean booleanValue() { this.getText() = "" and result = false or this.getText() != "" and result = true } override Object getLiteralObject() { none() } } private predicate name_consts(Name_ n, string id) { exists(Variable v | py_variables(v, n) and id = v.getId() | id = "True" or id = "False" or id = "None" ) } /** A named constant, one of `None`, `True` or `False` */ abstract class NameConstant extends Name, ImmutableLiteral { NameConstant() { name_consts(this, _) } override Expr getASubExpression() { none() } override string toString() { name_consts(this, result) } override predicate isConstant() { any() } override NameConstantNode getAFlowNode() { result = Name.super.getAFlowNode() } override predicate isArtificial() { none() } } /** A boolean named constant, either `True` or `False` */ abstract class BooleanLiteral extends NameConstant { } /** The boolean named constant `True` */ class True extends BooleanLiteral { /* syntax: True */ True() { name_consts(this, "True") } override Object getLiteralObject() { name_consts(this, "True") and result = theTrueObject() } override boolean booleanValue() { result = true } } /** The boolean named constant `False` */ class False extends BooleanLiteral { /* syntax: False */ False() { name_consts(this, "False") } override Object getLiteralObject() { name_consts(this, "False") and result = theFalseObject() } override boolean booleanValue() { result = false } } /** The `None` constant. */ class None extends NameConstant { /* syntax: None */ None() { name_consts(this, "None") } override Object getLiteralObject() { name_consts(this, "None") and result = theNoneObject() } override boolean booleanValue() { result = false } } /** An await expression such as `await coro`. */ class Await extends Await_ { /* syntax: await Expr */ override Expr getASubExpression() { result = this.getValue() } } /** A formatted string literal expression, such as `f'hello {world!s}'` */ class Fstring extends Fstring_ { /* syntax: f"Yes!" */ override Expr getASubExpression() { result = this.getAValue() } } /** * A formatted value (within a formatted string literal). * For example, in the string `f'hello {world!s}'` the formatted value is `world!s`. */ class FormattedValue extends FormattedValue_ { override Expr getASubExpression() { result = this.getValue() or result = this.getFormatSpec() } } /** A guard in a case statement */ class Guard extends Guard_ { /* syntax: if Expr */ override Expr getASubExpression() { result = this.getTest() } } /* Expression Contexts */ /** A context in which an expression used */ class ExprContext extends ExprContext_ { } /** The load context, the context of var in len(var) */ class Load extends Load_ { } /** The store context, the context of var in var = 0 */ class Store extends Store_ { } /** The delete context, the context of var in del var */ class Del extends Del_ { } /** The context of an augmented load. This is an artifact of the Python grammar which includes an AugLoad context, even though it is never used. */ class AugLoad extends AugLoad_ { } /** The augmented store context, the context of var in var += 1 */ class AugStore extends AugStore_ { } /** The parameter context, the context of var in def f(var): pass */ class Param extends Param_ { }