diff --git a/ql/lib/semmle/go/dataflow/internal/DataFlowNodes.qll b/ql/lib/semmle/go/dataflow/internal/DataFlowNodes.qll new file mode 100644 index 00000000000..d883de2be0b --- /dev/null +++ b/ql/lib/semmle/go/dataflow/internal/DataFlowNodes.qll @@ -0,0 +1,985 @@ +private import go +private import semmle.go.dataflow.FunctionInputsAndOutputs +private import semmle.go.dataflow.FlowSummary +private import DataFlowPrivate +private import FlowSummaryImpl as FlowSummaryImpl + +cached +private newtype TNode = + MkInstructionNode(IR::Instruction insn) or + MkSsaNode(SsaDefinition ssa) or + MkGlobalFunctionNode(Function f) + +/** Nodes intended for only use inside the data-flow libraries. */ +module Private { + /** A data flow node that represents returning a value from a function. */ + class ReturnNode extends ResultNode { + ReturnKind kind; + + ReturnNode() { kind = MkReturnKind(i) } + + /** Gets the kind of this returned value. */ + ReturnKind getKind() { result = kind } + } + + /** A data flow node that represents the output of a call. */ + class OutNode extends Node { + DataFlow::CallNode call; + int i; + + OutNode() { this = call.getResult(i) } + + /** Gets the underlying call. */ + DataFlowCall getCall() { result = call.asExpr() } + } +} + +/** Nodes intended for use outside the data-flow libraries. */ +module Public { + /** + * A node in a data flow graph. + * + * A node can be either an IR instruction or an SSA definition. + * Such nodes are created with `DataFlow::instructionNode` + * and `DataFlow::ssaNode` respectively. + */ + class Node extends TNode { + /** Gets the function to which this node belongs. */ + ControlFlow::Root getRoot() { none() } // overridden in subclasses + + /** INTERNAL: Use `getRoot()` instead. */ + FuncDef getEnclosingCallable() { result = this.getRoot() } + + /** Gets the type of this node. */ + Type getType() { none() } // overridden in subclasses + + /** Gets the expression corresponding to this node, if any. */ + Expr asExpr() { none() } // overridden in subclasses + + /** Gets the parameter corresponding to this node, if any. */ + Parameter asParameter() { none() } // overridden in subclasses + + /** Gets the IR instruction corresponding to this node, if any. */ + IR::Instruction asInstruction() { none() } // overridden in subclasses + + /** Gets a textual representation of the kind of this data-flow node. */ + string getNodeKind() { none() } // overridden in subclasses + + /** Gets the basic block to which this data-flow node belongs, if any. */ + BasicBlock getBasicBlock() { result = this.asInstruction().getBasicBlock() } + + /** Gets a textual representation of this element. */ + string toString() { result = "data-flow node" } // overridden in subclasses + + /** + * Holds if this element is at the specified location. + * The location spans column `startcolumn` of line `startline` to + * column `endcolumn` of line `endline` in file `filepath`. + * For more information, see + * [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/). + */ + predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + filepath = "" and + startline = 0 and + startcolumn = 0 and + endline = 0 and + endcolumn = 0 + } + + /** Gets the file in which this node appears. */ + File getFile() { this.hasLocationInfo(result.getAbsolutePath(), _, _, _, _) } + + /** Gets the start line of the location of this node. */ + int getStartLine() { this.hasLocationInfo(_, result, _, _, _) } + + /** Gets the start column of the location of this node. */ + int getStartColumn() { this.hasLocationInfo(_, _, result, _, _) } + + /** Gets the end line of the location of this node. */ + int getEndLine() { this.hasLocationInfo(_, _, _, result, _) } + + /** Gets the end column of the location of this node. */ + int getEndColumn() { this.hasLocationInfo(_, _, _, _, result) } + + /** + * Gets an upper bound on the type of this node. + */ + Type getTypeBound() { result = this.getType() } + + /** Gets the floating-point value this data-flow node contains, if any. */ + float getFloatValue() { result = this.asExpr().getFloatValue() } + + /** + * Gets the integer value this data-flow node contains, if any. + * + * Note that this does not have a result if the value is too large to fit in a + * 32-bit signed integer type. + */ + int getIntValue() { result = this.asInstruction().getIntValue() } + + /** Gets either `getFloatValue` or `getIntValue`. */ + float getNumericValue() { result = this.asInstruction().getNumericValue() } + + /** + * Holds if the complex value this data-flow node contains has real part `real` and imaginary + * part `imag`. + */ + predicate hasComplexValue(float real, float imag) { + this.asInstruction().hasComplexValue(real, imag) + } + + /** Gets the string value this data-flow node contains, if any. */ + string getStringValue() { result = this.asInstruction().getStringValue() } + + /** + * Gets the string representation of the exact value this data-flow node + * contains, if any. + * + * For example, for the constant 3.141592653589793238462, this will + * result in 1570796326794896619231/500000000000000000000 + */ + string getExactValue() { result = this.asInstruction().getExactValue() } + + /** Gets the Boolean value this data-flow node contains, if any. */ + boolean getBoolValue() { result = this.asInstruction().getBoolValue() } + + /** Holds if the value of this data-flow node is known at compile time. */ + predicate isConst() { this.asInstruction().isConst() } + + /** + * Holds if the result of this instruction is known at compile time, and is guaranteed not to + * depend on the platform where it is evaluated. + */ + predicate isPlatformIndependentConstant() { + this.asInstruction().isPlatformIndependentConstant() + } + + /** + * Gets a data-flow node to which data may flow from this node in one (intra-procedural) step. + */ + Node getASuccessor() { localFlowStep(this, result) } + + /** + * Gets a data-flow node from which data may flow to this node in one (intra-procedural) step. + */ + Node getAPredecessor() { this = result.getASuccessor() } + } + + /** + * An IR instruction, viewed as a node in a data flow graph. + */ + class InstructionNode extends Node, MkInstructionNode { + IR::Instruction insn; + + InstructionNode() { this = MkInstructionNode(insn) } + + override IR::Instruction asInstruction() { result = insn } + + override ControlFlow::Root getRoot() { result = insn.getRoot() } + + override Type getType() { result = insn.getResultType() } + + override string getNodeKind() { result = insn.getInsnKind() } + + override string toString() { result = insn.toString() } + + override predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + insn.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + } + } + + /** + * An expression, viewed as a node in a data flow graph. + */ + class ExprNode extends InstructionNode { + override IR::EvalInstruction insn; + Expr expr; + + ExprNode() { expr = insn.getExpr() } + + override Expr asExpr() { result = expr } + + /** Gets the underlying expression this node corresponds to. */ + Expr getExpr() { result = expr } + } + + /** + * An SSA variable, viewed as a node in a data flow graph. + */ + class SsaNode extends Node, MkSsaNode { + SsaDefinition ssa; + + SsaNode() { this = MkSsaNode(ssa) } + + /** Gets the node whose value is stored in this SSA variable, if any. */ + Node getInit() { result = instructionNode(ssa.(SsaExplicitDefinition).getRhs()) } + + /** Gets a use of this SSA variable. */ + InstructionNode getAUse() { result = instructionNode(ssa.getVariable().getAUse()) } + + /** Gets the program variable corresponding to this SSA variable. */ + SsaSourceVariable getSourceVariable() { result = ssa.getSourceVariable() } + + /** Gets the unique definition of this SSA variable. */ + SsaDefinition getDefinition() { result = ssa } + + override ControlFlow::Root getRoot() { result = ssa.getRoot() } + + override Type getType() { result = ssa.getSourceVariable().getType() } + + override string getNodeKind() { result = "SSA variable " + ssa.getSourceVariable().getName() } + + override string toString() { result = ssa.toString() } + + override predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + ssa.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + } + } + + private module FunctionNode { + /** A function, viewed as a node in a data flow graph. */ + abstract class Range extends Node { + /** Gets the `i`th parameter of this function. */ + abstract ParameterNode getParameter(int i); + + /** Gets the name of this function, if it has one. */ + abstract string getName(); + + /** + * Gets the dataflow node holding the value of the receiver, if any. + */ + abstract ReceiverNode getReceiver(); + + /** + * Gets a value returned by the given function via a return statement or an assignment to a + * result variable. + */ + abstract ResultNode getAResult(); + + /** + * Gets the function entity this node corresponds to. + * + * Note that this predicate has no result for function literals. + */ + Function getFunction() { none() } + } + } + + /** A function, viewed as a node in a data flow graph. */ + class FunctionNode extends Node { + FunctionNode::Range self; + + FunctionNode() { this = self } + + /** Gets the `i`th parameter of this function. */ + ParameterNode getParameter(int i) { result = self.getParameter(i) } + + /** Gets a parameter of this function. */ + ParameterNode getAParameter() { result = this.getParameter(_) } + + /** Gets the number of parameters declared on this function. */ + int getNumParameter() { result = count(this.getAParameter()) } + + /** Gets the name of this function, if it has one. */ + string getName() { result = self.getName() } + + /** + * Gets the dataflow node holding the value of the receiver, if any. + */ + ReceiverNode getReceiver() { result = self.getReceiver() } + + /** + * Gets a value returned by the given function via a return statement or an assignment to a + * result variable. + */ + ResultNode getAResult() { result = self.getAResult() } + + /** + * Gets the data-flow node corresponding to the `i`th result of this function. + */ + ResultNode getResult(int i) { result = this.getAResult() and result.getIndex() = i } + + /** + * Gets the function entity this node corresponds to. + * + * Note that this predicate has no result for function literals. + */ + Function getFunction() { result = self.getFunction() } + } + + /** A representation of a function that is declared in the module scope. */ + class GlobalFunctionNode extends FunctionNode::Range, MkGlobalFunctionNode { + Function func; + + GlobalFunctionNode() { this = MkGlobalFunctionNode(func) } + + override ParameterNode getParameter(int i) { result = parameterNode(func.getParameter(i)) } + + override string getName() { result = func.getName() } + + override Function getFunction() { result = func } + + override ReceiverNode getReceiver() { result = receiverNode(func.(Method).getReceiver()) } + + override string getNodeKind() { result = "function " + func.getName() } + + override string toString() { result = "function " + func.getName() } + + override predicate hasLocationInfo( + string filepath, int startline, int startcolumn, int endline, int endcolumn + ) { + func.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) + } + + override ResultNode getAResult() { + result.getRoot() = this.getFunction().(DeclaredFunction).getFuncDecl() + } + } + + /** A representation of the function that is defined by a function literal. */ + class FuncLitNode extends FunctionNode::Range, ExprNode { + override FuncLit expr; + + override ParameterNode getParameter(int i) { result = parameterNode(expr.getParameter(i)) } + + override string getName() { none() } + + override ReceiverNode getReceiver() { none() } + + override string toString() { result = "function literal" } + + override ResultNode getAResult() { result.getRoot() = this.getExpr() } + } + + /** + * Gets a possible target of call `cn`.class + * + * This is written explicitly like this instead of using `getCalleeNode().getAPredecessor*()` + * or `result.getASuccessor*() = cn.getCalleeNode()` because the explicit form inhibits the + * optimizer from combining this with other uses of `getASuccessor*()`, which can lead to + * recursion through a magic side-condition if those other users call `getACallee()` and thus + * pointless recomputation of `getACallee()` each recursive iteration. + */ + private DataFlow::Node getACalleeSource(DataFlow::CallNode cn) { + result = cn.getCalleeNode() or + result.getASuccessor() = getACalleeSource(cn) + } + + /** A data flow node that represents a call. */ + class CallNode extends ExprNode { + override CallExpr expr; + + /** Gets the declared target of this call */ + Function getTarget() { result = expr.getTarget() } + + private DataFlow::Node getACalleeSource() { result = getACalleeSource(this) } + + /** + * Gets the definition of a possible target of this call. + * + * For non-virtual calls, there is at most one possible call target (but there may be none if the + * target has no declaration). + * + * For virtual calls, we look up possible targets in all types that implement the receiver + * interface type. + */ + FuncDef getACallee() { + result = this.getTarget().(DeclaredFunction).getFuncDecl() + or + exists(DataFlow::Node calleeSource | calleeSource = this.getACalleeSource() | + result = calleeSource.asExpr() + or + exists(Method declared, Method actual | + calleeSource = declared.getARead() and + actual.implements(declared) and + result = actual.(DeclaredFunction).getFuncDecl() + ) + ) + } + + /** Gets the name of the function or method being called, if it can be determined. */ + string getCalleeName() { result = expr.getTarget().getName() or result = expr.getCalleeName() } + + /** Gets the data flow node specifying the function to be called. */ + Node getCalleeNode() { result = exprNode(expr.getCalleeExpr()) } + + /** Gets the underlying call. */ + CallExpr getCall() { result = this.getExpr() } + + /** + * Gets the data flow node corresponding to the `i`th argument of this call. + * + * Note that the first argument in calls to the built-in function `make` is a type, which is + * not a data-flow node. It is skipped for the purposes of this predicate, so the (syntactically) + * second argument becomes the first argument in terms of data flow. + * + * For calls of the form `f(g())` where `g` has multiple results, the arguments of the call to + * `i` are the (implicit) element extraction nodes for the call to `g`. + */ + Node getArgument(int i) { + if expr.getArgument(0).getType() instanceof TupleType + then result = extractTupleElement(exprNode(expr.getArgument(0)), i) + else + result = rank[i + 1](Expr arg, int j | arg = expr.getArgument(j) | exprNode(arg) order by j) + } + + /** Gets the data flow node corresponding to an argument of this call. */ + Node getAnArgument() { result = this.getArgument(_) } + + /** Gets the number of arguments of this call, if it can be determined. */ + int getNumArgument() { result = count(this.getAnArgument()) } + + /** Gets a function passed as the `i`th argument of this call. */ + FunctionNode getCallback(int i) { result.getASuccessor*() = this.getArgument(i) } + + /** + * Gets the data-flow node corresponding to the `i`th result of this call. + * + * If there is a single result then it is considered to be the 0th result. + */ + Node getResult(int i) { + i = 0 and result = this.getResult() + or + result = extractTupleElement(this, i) + } + + /** + * Gets the data-flow node corresponding to the result of this call. + * + * Note that this predicate is not defined for calls with multiple results; use the one-argument + * variant `getResult(i)` for such calls. + */ + Node getResult() { not this.getType() instanceof TupleType and result = this } + + /** Gets a result of this call. */ + Node getAResult() { result = this.getResult(_) } + + /** Gets the data flow node corresponding to the receiver of this call, if any. */ + Node getReceiver() { result = this.getACalleeSource().(MethodReadNode).getReceiver() } + + /** Holds if this call has an ellipsis after its last argument. */ + predicate hasEllipsis() { expr.hasEllipsis() } + } + + /** A data flow node that represents a call to a method. */ + class MethodCallNode extends CallNode { + MethodCallNode() { expr.getTarget() instanceof Method } + + override Method getTarget() { result = expr.getTarget() } + + override MethodDecl getACallee() { result = super.getACallee() } + } + + /** A representation of a parameter initialization. */ + class ParameterNode extends SsaNode { + override SsaExplicitDefinition ssa; + Parameter parm; + + ParameterNode() { ssa.getInstruction() = IR::initParamInstruction(parm) } + + /** Gets the parameter this node initializes. */ + override Parameter asParameter() { result = parm } + + /** Holds if this node initializes the `i`th parameter of `fd`. */ + predicate isParameterOf(FuncDef fd, int i) { parm.isParameterOf(fd, i) } + } + + /** A representation of a receiver initialization. */ + class ReceiverNode extends ParameterNode { + override ReceiverVariable parm; + + /** Gets the receiver variable this node initializes. */ + ReceiverVariable asReceiverVariable() { result = parm } + + /** Holds if this node initializes the receiver variable of `m`. */ + predicate isReceiverOf(MethodDecl m) { parm.isReceiverOf(m) } + } + + private Node getADirectlyWrittenNode() { + exists(Write w | w.writesField(result, _, _) or w.writesElement(result, _, _)) + } + + private DataFlow::Node getAccessPathPredecessor(DataFlow::Node node) { + result = node.(PointerDereferenceNode).getOperand() + or + result = node.(ComponentReadNode).getBase() + } + + private Node getAWrittenNode() { result = getAccessPathPredecessor*(getADirectlyWrittenNode()) } + + /** + * Holds if `tp` is a type that may (directly or indirectly) reference a memory location. + * + * If a value with a mutable type is passed to a function, the function could potentially + * mutate it or something it points to. + */ + predicate mutableType(Type tp) { + exists(Type underlying | underlying = tp.getUnderlyingType() | + not underlying instanceof BoolType and + not underlying instanceof NumericType and + not underlying instanceof StringType and + not underlying instanceof LiteralType + ) + } + + /** + * A node associated with an object after an operation that might have + * changed its state. + * + * This can be either the argument to a callable after the callable returns + * (which might have mutated the argument), or the qualifier of a field after + * an update to the field. + * + * Nodes corresponding to AST elements, for example `ExprNode`, usually refer + * to the value before the update with the exception of `ClassInstanceExpr`, + * which represents the value after the constructor has run. + */ + class PostUpdateNode extends Node { + Node preupd; + + PostUpdateNode() { + ( + preupd instanceof AddressOperationNode + or + preupd = any(AddressOperationNode addr).getOperand() + or + preupd = any(PointerDereferenceNode deref).getOperand() + or + preupd = getAWrittenNode() + or + preupd instanceof ArgumentNode and + mutableType(preupd.getType()) + ) and + ( + preupd = this.(SsaNode).getAUse() + or + preupd = this and + not basicLocalFlowStep(_, this) + ) + } + + /** + * Gets the node before the state update. + */ + Node getPreUpdateNode() { result = preupd } + } + + /** + * A data-flow node that occurs as an argument in a call, including receiver arguments. + */ + class ArgumentNode extends Node { + CallNode c; + int i; + + ArgumentNode() { this = getArgument(c, i) } + + /** + * Holds if this argument occurs at the given position in the given call. + * + * The receiver argument is considered to have index `-1`. + * + * Note that we currently do not track receiver arguments into calls to interface methods. + */ + predicate argumentOf(CallExpr call, int pos) { + call = c.asExpr() and + pos = i and + ( + i != -1 + or + exists(c.(MethodCallNode).getTarget().getBody()) + ) + } + + /** + * Gets the `CallNode` this is an argument to. + */ + CallNode getCall() { result = c } + } + + /** + * A node whose value is returned as a result from a function. + * + * This can either be a node corresponding to an expression in a return statement, + * or a node representing the current value of a named result variable at the exit + * of the function. + */ + class ResultNode extends InstructionNode { + FuncDef fd; + int i; + + ResultNode() { + exists(IR::ReturnInstruction ret | ret.getRoot() = fd | insn = ret.getResult(i)) + or + insn.(IR::ReadResultInstruction).reads(fd.getResultVar(i)) + } + + /** Gets the index of this result among all results of the function. */ + int getIndex() { result = i } + } + + /** + * A data-flow node that reads the value of a variable, constant, field or array element, + * or refers to a function. + */ + class ReadNode extends InstructionNode { + override IR::ReadInstruction insn; + + /** + * Holds if this data-flow node evaluates to value of `v`, which is a value entity, that is, a + * constant, variable, field, function, or method. + */ + predicate reads(ValueEntity v) { insn.reads(v) } + + /** + * Holds if this data-flow node reads the value of SSA variable `v`. + */ + predicate readsSsaVariable(SsaVariable v) { insn = v.getAUse() } + + /** + * Holds if this data-flow node reads the value of field `f` on the value of `base` or its + * implicit dereference. + * + * For example, for the field read `x.width`, `base` is either the data-flow node corresponding + * to `x` or (if `x` is a pointer) the data-flow node corresponding to the implicit dereference + * `*x`, and `f` is the field referenced by `width`. + */ + predicate readsField(Node base, Field f) { + insn.readsField(base.asInstruction(), f) + or + insn.readsField(IR::implicitDerefInstruction(base.asExpr()), f) + } + + /** + * Holds if this data-flow node reads the value of field `package.type.field` on the value of `base` or its + * implicit dereference. + * + * For example, for the field read `x.width`, `base` is either the data-flow node corresponding + * to `x` or (if `x` is a pointer) the data-flow node corresponding to the implicit dereference + * `*x`, and `x` has the type `package.type`. + */ + predicate readsField(Node base, string package, string type, string field) { + exists(Field f | f.hasQualifiedName(package, type, field) | this.readsField(base, f)) + } + + /** + * Holds if this data-flow node looks up method `m` on the value of `receiver` or its implicit + * dereference. + * + * For example, for the method read `x.area`, `receiver` is either the data-flow node corresponding + * to `x` or (if `x` is a pointer) the data-flow node corresponding to the implicit dereference + * `*x`, and `m` is the method referenced by `area`. + */ + predicate readsMethod(Node receiver, Method m) { + insn.readsMethod(receiver.asInstruction(), m) + or + insn.readsMethod(IR::implicitDerefInstruction(receiver.asExpr()), m) + } + + /** + * Holds if this data-flow node looks up method `package.type.name` on the value of `receiver` + * or its implicit dereference. + * + * For example, for the method read `x.name`, `receiver` is either the data-flow node corresponding + * to `x` or (if `x` is a pointer) the data-flow node corresponding to the implicit dereference + * `*x`, and `package.type` is a type of `x` that defines a method named `name`. + */ + predicate readsMethod(Node receiver, string package, string type, string name) { + exists(Method m | m.hasQualifiedName(package, type, name) | this.readsMethod(receiver, m)) + } + + /** + * Holds if this data-flow node reads the value of element `index` on the value of `base` or its + * implicit dereference. + * + * For example, for the element read `xs[i]`, `base` is either the data-flow node corresponding + * to `xs` or (if `xs` is a pointer) the data-flow node corresponding to the implicit dereference + * `*xs`, and `index` is the data-flow node corresponding to `i`. + */ + predicate readsElement(Node base, Node index) { + insn.readsElement(base.asInstruction(), index.asInstruction()) + or + insn.readsElement(IR::implicitDerefInstruction(base.asExpr()), index.asInstruction()) + } + } + + /** + * A data-flow node that reads the value of a field from a struct, or an element from an array, slice, map or string. + */ + class ComponentReadNode extends ReadNode { + override IR::ComponentReadInstruction insn; + + /** Gets the data-flow node representing the base from which the fieldĀ or element is read. */ + Node getBase() { result = instructionNode(insn.getBase()) } + } + + /** + * A data-flow node that reads an element of an array, map, slice or string. + */ + class ElementReadNode extends ComponentReadNode { + override IR::ElementReadInstruction insn; + + /** Gets the data-flow node representing the index of the element being read. */ + Node getIndex() { result = instructionNode(insn.getIndex()) } + + /** Holds if this data-flow node reads element `index` of `base`. */ + predicate reads(Node base, Node index) { this.readsElement(base, index) } + } + + /** + * A data-flow node that extracts a substring or slice from a string, array, pointer to array, + * or slice. + */ + class SliceNode extends InstructionNode { + override IR::SliceInstruction insn; + + /** Gets the base of this slice node. */ + Node getBase() { result = instructionNode(insn.getBase()) } + + /** Gets the lower bound of this slice node. */ + Node getLow() { result = instructionNode(insn.getLow()) } + + /** Gets the upper bound of this slice node. */ + Node getHigh() { result = instructionNode(insn.getHigh()) } + + /** Gets the maximum of this slice node. */ + Node getMax() { result = instructionNode(insn.getMax()) } + } + + /** + * A data-flow node corresponding to an expression with a binary operator. + */ + class BinaryOperationNode extends Node { + Node left; + Node right; + string op; + + BinaryOperationNode() { + exists(BinaryExpr bin | bin = this.asExpr() | + left = exprNode(bin.getLeftOperand()) and + right = exprNode(bin.getRightOperand()) and + op = bin.getOperator() + ) + or + exists(IR::EvalCompoundAssignRhsInstruction rhs, CompoundAssignStmt assgn, string o | + rhs = this.asInstruction() and assgn = rhs.getAssignment() and o = assgn.getOperator() + | + left = exprNode(assgn.getLhs()) and + right = exprNode(assgn.getRhs()) and + op = o.substring(0, o.length() - 1) + ) + or + exists(IR::EvalIncDecRhsInstruction rhs, IncDecStmt ids | + rhs = this.asInstruction() and ids = rhs.getStmt() + | + left = exprNode(ids.getOperand()) and + right = instructionNode(any(IR::EvalImplicitOneInstruction one | one.getStmt() = ids)) and + op = ids.getOperator().charAt(0) + ) + } + + /** Holds if this operation may have observable side effects. */ + predicate mayHaveSideEffects() { this.asExpr().mayHaveOwnSideEffects() } + + /** Gets the left operand of this operation. */ + Node getLeftOperand() { result = left } + + /** Gets the right operand of this operation. */ + Node getRightOperand() { result = right } + + /** Gets an operand of this operation. */ + Node getAnOperand() { result = left or result = right } + + /** Gets the operator of this operation. */ + string getOperator() { result = op } + + /** Holds if `x` and `y` are the operands of this operation, in either order. */ + predicate hasOperands(Node x, Node y) { + x = this.getAnOperand() and + y = this.getAnOperand() and + x != y + } + } + + /** + * A data-flow node corresponding to an expression with a unary operator. + */ + class UnaryOperationNode extends InstructionNode { + UnaryOperationNode() { + this.asExpr() instanceof UnaryExpr + or + this.asExpr() instanceof StarExpr + or + insn instanceof IR::EvalImplicitDerefInstruction + } + + /** Holds if this operation may have observable side effects. */ + predicate mayHaveSideEffects() { + this.asExpr().mayHaveOwnSideEffects() + or + insn instanceof IR::EvalImplicitDerefInstruction + } + + /** Gets the operand of this operation. */ + Node getOperand() { + result = exprNode(this.asExpr().(UnaryExpr).getOperand()) + or + result = exprNode(this.asExpr().(StarExpr).getBase()) + or + result = exprNode(insn.(IR::EvalImplicitDerefInstruction).getOperand()) + } + + /** Gets the operator of this operation. */ + string getOperator() { + result = this.asExpr().(UnaryExpr).getOperator() + or + this.asExpr() instanceof StarExpr and + result = "*" + or + insn instanceof IR::EvalImplicitDerefInstruction and + result = "*" + } + } + + /** + * A data-flow node that dereferences a pointer. + */ + class PointerDereferenceNode extends UnaryOperationNode { + PointerDereferenceNode() { + this.asExpr() instanceof StarExpr + or + this.asExpr() instanceof DerefExpr + or + insn instanceof IR::EvalImplicitDerefInstruction + } + } + + /** + * A data-flow node that takes the address of a memory location. + */ + class AddressOperationNode extends UnaryOperationNode, ExprNode { + override AddressExpr expr; + } + + /** + * A data-flow node that reads the value of a field. + */ + class FieldReadNode extends ComponentReadNode { + override IR::FieldReadInstruction insn; + + /** Gets the field this node reads. */ + Field getField() { result = insn.getField() } + + /** Gets the name of the field this node reads. */ + string getFieldName() { result = this.getField().getName() } + } + + /** + * A data-flow node that refers to a method. + */ + class MethodReadNode extends ReadNode { + override IR::MethodReadInstruction insn; + + /** Gets the receiver node on which the method is referenced. */ + Node getReceiver() { result = instructionNode(insn.getReceiver()) } + + /** Gets the method this node refers to. */ + Method getMethod() { result = insn.getMethod() } + + /** Gets the name of the method this node refers to. */ + string getMethodName() { result = this.getMethod().getName() } + } + + /** + * A data-flow node performing a relational comparison using `<`, `<=`, `>` or `>=`. + */ + class RelationalComparisonNode extends BinaryOperationNode, ExprNode { + override RelationalComparisonExpr expr; + + /** Holds if this comparison evaluates to `outcome` iff `lesser <= greater + bias`. */ + predicate leq(boolean outcome, Node lesser, Node greater, int bias) { + outcome = true and + lesser = exprNode(expr.getLesserOperand()) and + greater = exprNode(expr.getGreaterOperand()) and + (if expr.isStrict() then bias = -1 else bias = 0) + or + outcome = false and + lesser = exprNode(expr.getGreaterOperand()) and + greater = exprNode(expr.getLesserOperand()) and + (if expr.isStrict() then bias = 0 else bias = -1) + } + } + + /** + * A data-flow node performing an equality test using `==` or `!=`. + */ + class EqualityTestNode extends BinaryOperationNode, ExprNode { + override EqualityTestExpr expr; + + /** Holds if this comparison evaluates to `outcome` iff `lhs == rhs`. */ + predicate eq(boolean outcome, Node lhs, Node rhs) { + outcome = expr.getPolarity() and + expr.hasOperands(lhs.asExpr(), rhs.asExpr()) + } + + /** Gets the polarity of this equality test, that is, `true` for `==` and `false` for `!=`. */ + boolean getPolarity() { result = expr.getPolarity() } + } + + /** + * A data-flow node performing a type cast using either a type conversion + * or an assertion. + */ + class TypeCastNode extends ExprNode { + TypeCastNode() { + expr instanceof TypeAssertExpr + or + expr instanceof ConversionExpr + } + + /** + * Gets the type being converted to. Note this differs from `this.getType()` for + * `TypeAssertExpr`s that return a (result, ok) tuple. + */ + Type getResultType() { + if this.getType() instanceof TupleType + then result = this.getType().(TupleType).getComponentType(0) + else result = this.getType() + } + + /** Gets the operand of the type cast. */ + DataFlow::Node getOperand() { + result.asExpr() = expr.(TypeAssertExpr).getExpr() + or + result.asExpr() = expr.(ConversionExpr).getOperand() + } + } + + /** + * A data-flow node representing an element of an array, map, slice or string defined from `range` statement. + * + * Example: in `_, x := range y { ... }`, this represents the `Node` that extracts the element from the + * range statement, which will flow to `x`. + */ + class RangeElementNode extends Node { + DataFlow::Node base; + IR::ExtractTupleElementInstruction extract; + + RangeElementNode() { + this.asInstruction() = extract and + extract.extractsElement(_, 1) and + extract.getBase().(IR::GetNextEntryInstruction).getDomain() = base.asInstruction() + } + + /** Gets the data-flow node representing the base from which the element is read. */ + DataFlow::Node getBase() { result = base } + } +} + +private import Private +private import Public diff --git a/ql/lib/semmle/go/dataflow/internal/DataFlowPrivate.qll b/ql/lib/semmle/go/dataflow/internal/DataFlowPrivate.qll index c2093b2af14..a3b2552d748 100644 --- a/ql/lib/semmle/go/dataflow/internal/DataFlowPrivate.qll +++ b/ql/lib/semmle/go/dataflow/internal/DataFlowPrivate.qll @@ -1,6 +1,7 @@ private import go private import DataFlowUtil private import DataFlowImplCommon +import DataFlowNodes::Private private newtype TReturnKind = MkReturnKind(int i) { exists(SignatureType st | exists(st.getResultType(i))) } @@ -15,27 +16,6 @@ class ReturnKind extends TReturnKind { string toString() { exists(int i | this = MkReturnKind(i) | result = "return[" + i + "]") } } -/** A data flow node that represents returning a value from a function. */ -class ReturnNode extends ResultNode { - ReturnKind kind; - - ReturnNode() { kind = MkReturnKind(i) } - - /** Gets the kind of this returned value. */ - ReturnKind getKind() { result = kind } -} - -/** A data flow node that represents the output of a call. */ -class OutNode extends DataFlow::Node { - DataFlow::CallNode call; - int i; - - OutNode() { this = call.getResult(i) } - - /** Gets the underlying call. */ - DataFlowCall getCall() { result = call.asExpr() } -} - /** * Gets a node that can read the value returned from `call` with return kind * `kind`. diff --git a/ql/lib/semmle/go/dataflow/internal/DataFlowUtil.qll b/ql/lib/semmle/go/dataflow/internal/DataFlowUtil.qll index fd60d108376..4d4fda199b5 100644 --- a/ql/lib/semmle/go/dataflow/internal/DataFlowUtil.qll +++ b/ql/lib/semmle/go/dataflow/internal/DataFlowUtil.qll @@ -5,954 +5,7 @@ import go import semmle.go.dataflow.FunctionInputsAndOutputs private import DataFlowPrivate - -cached -private newtype TNode = - MkInstructionNode(IR::Instruction insn) or - MkSsaNode(SsaDefinition ssa) or - MkGlobalFunctionNode(Function f) - -/** - * A node in a data flow graph. - * - * A node can be either an IR instruction or an SSA definition. - * Such nodes are created with `DataFlow::instructionNode` - * and `DataFlow::ssaNode` respectively. - */ -class Node extends TNode { - /** Gets the function to which this node belongs. */ - ControlFlow::Root getRoot() { none() } // overridden in subclasses - - /** INTERNAL: Use `getRoot()` instead. */ - FuncDef getEnclosingCallable() { result = this.getRoot() } - - /** Gets the type of this node. */ - Type getType() { none() } // overridden in subclasses - - /** Gets the expression corresponding to this node, if any. */ - Expr asExpr() { none() } // overridden in subclasses - - /** Gets the parameter corresponding to this node, if any. */ - Parameter asParameter() { none() } // overridden in subclasses - - /** Gets the IR instruction corresponding to this node, if any. */ - IR::Instruction asInstruction() { none() } // overridden in subclasses - - /** Gets a textual representation of the kind of this data-flow node. */ - string getNodeKind() { none() } // overridden in subclasses - - /** Gets the basic block to which this data-flow node belongs, if any. */ - BasicBlock getBasicBlock() { result = this.asInstruction().getBasicBlock() } - - /** Gets a textual representation of this element. */ - string toString() { result = "data-flow node" } // overridden in subclasses - - /** - * Holds if this element is at the specified location. - * The location spans column `startcolumn` of line `startline` to - * column `endcolumn` of line `endline` in file `filepath`. - * For more information, see - * [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/). - */ - predicate hasLocationInfo( - string filepath, int startline, int startcolumn, int endline, int endcolumn - ) { - filepath = "" and - startline = 0 and - startcolumn = 0 and - endline = 0 and - endcolumn = 0 - } - - /** Gets the file in which this node appears. */ - File getFile() { this.hasLocationInfo(result.getAbsolutePath(), _, _, _, _) } - - /** Gets the start line of the location of this node. */ - int getStartLine() { this.hasLocationInfo(_, result, _, _, _) } - - /** Gets the start column of the location of this node. */ - int getStartColumn() { this.hasLocationInfo(_, _, result, _, _) } - - /** Gets the end line of the location of this node. */ - int getEndLine() { this.hasLocationInfo(_, _, _, result, _) } - - /** Gets the end column of the location of this node. */ - int getEndColumn() { this.hasLocationInfo(_, _, _, _, result) } - - /** - * Gets an upper bound on the type of this node. - */ - Type getTypeBound() { result = this.getType() } - - /** Gets the floating-point value this data-flow node contains, if any. */ - float getFloatValue() { result = this.asExpr().getFloatValue() } - - /** - * Gets the integer value this data-flow node contains, if any. - * - * Note that this does not have a result if the value is too large to fit in a - * 32-bit signed integer type. - */ - int getIntValue() { result = this.asInstruction().getIntValue() } - - /** Gets either `getFloatValue` or `getIntValue`. */ - float getNumericValue() { result = this.asInstruction().getNumericValue() } - - /** - * Holds if the complex value this data-flow node contains has real part `real` and imaginary - * part `imag`. - */ - predicate hasComplexValue(float real, float imag) { - this.asInstruction().hasComplexValue(real, imag) - } - - /** Gets the string value this data-flow node contains, if any. */ - string getStringValue() { result = this.asInstruction().getStringValue() } - - /** - * Gets the string representation of the exact value this data-flow node - * contains, if any. - * - * For example, for the constant 3.141592653589793238462, this will - * result in 1570796326794896619231/500000000000000000000 - */ - string getExactValue() { result = this.asInstruction().getExactValue() } - - /** Gets the Boolean value this data-flow node contains, if any. */ - boolean getBoolValue() { result = this.asInstruction().getBoolValue() } - - /** Holds if the value of this data-flow node is known at compile time. */ - predicate isConst() { this.asInstruction().isConst() } - - /** - * Holds if the result of this instruction is known at compile time, and is guaranteed not to - * depend on the platform where it is evaluated. - */ - predicate isPlatformIndependentConstant() { this.asInstruction().isPlatformIndependentConstant() } - - /** - * Gets a data-flow node to which data may flow from this node in one (intra-procedural) step. - */ - Node getASuccessor() { localFlowStep(this, result) } - - /** - * Gets a data-flow node from which data may flow to this node in one (intra-procedural) step. - */ - Node getAPredecessor() { this = result.getASuccessor() } -} - -/** - * An IR instruction, viewed as a node in a data flow graph. - */ -class InstructionNode extends Node, MkInstructionNode { - IR::Instruction insn; - - InstructionNode() { this = MkInstructionNode(insn) } - - override IR::Instruction asInstruction() { result = insn } - - override ControlFlow::Root getRoot() { result = insn.getRoot() } - - override Type getType() { result = insn.getResultType() } - - override string getNodeKind() { result = insn.getInsnKind() } - - override string toString() { result = insn.toString() } - - override predicate hasLocationInfo( - string filepath, int startline, int startcolumn, int endline, int endcolumn - ) { - insn.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } -} - -/** - * An expression, viewed as a node in a data flow graph. - */ -class ExprNode extends InstructionNode { - override IR::EvalInstruction insn; - Expr expr; - - ExprNode() { expr = insn.getExpr() } - - override Expr asExpr() { result = expr } - - /** Gets the underlying expression this node corresponds to. */ - Expr getExpr() { result = expr } -} - -/** - * An SSA variable, viewed as a node in a data flow graph. - */ -class SsaNode extends Node, MkSsaNode { - SsaDefinition ssa; - - SsaNode() { this = MkSsaNode(ssa) } - - /** Gets the node whose value is stored in this SSA variable, if any. */ - Node getInit() { result = instructionNode(ssa.(SsaExplicitDefinition).getRhs()) } - - /** Gets a use of this SSA variable. */ - InstructionNode getAUse() { result = instructionNode(ssa.getVariable().getAUse()) } - - /** Gets the program variable corresponding to this SSA variable. */ - SsaSourceVariable getSourceVariable() { result = ssa.getSourceVariable() } - - /** Gets the unique definition of this SSA variable. */ - SsaDefinition getDefinition() { result = ssa } - - override ControlFlow::Root getRoot() { result = ssa.getRoot() } - - override Type getType() { result = ssa.getSourceVariable().getType() } - - override string getNodeKind() { result = "SSA variable " + ssa.getSourceVariable().getName() } - - override string toString() { result = ssa.toString() } - - override predicate hasLocationInfo( - string filepath, int startline, int startcolumn, int endline, int endcolumn - ) { - ssa.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } -} - -private module FunctionNode { - /** A function, viewed as a node in a data flow graph. */ - abstract class Range extends Node { - /** Gets the `i`th parameter of this function. */ - abstract ParameterNode getParameter(int i); - - /** Gets the name of this function, if it has one. */ - abstract string getName(); - - /** - * Gets the dataflow node holding the value of the receiver, if any. - */ - abstract ReceiverNode getReceiver(); - - /** - * Gets a value returned by the given function via a return statement or an assignment to a - * result variable. - */ - abstract ResultNode getAResult(); - - /** - * Gets the function entity this node corresponds to. - * - * Note that this predicate has no result for function literals. - */ - Function getFunction() { none() } - } -} - -/** A function, viewed as a node in a data flow graph. */ -class FunctionNode extends Node { - FunctionNode::Range self; - - FunctionNode() { this = self } - - /** Gets the `i`th parameter of this function. */ - ParameterNode getParameter(int i) { result = self.getParameter(i) } - - /** Gets a parameter of this function. */ - ParameterNode getAParameter() { result = this.getParameter(_) } - - /** Gets the number of parameters declared on this function. */ - int getNumParameter() { result = count(this.getAParameter()) } - - /** Gets the name of this function, if it has one. */ - string getName() { result = self.getName() } - - /** - * Gets the dataflow node holding the value of the receiver, if any. - */ - ReceiverNode getReceiver() { result = self.getReceiver() } - - /** - * Gets a value returned by the given function via a return statement or an assignment to a - * result variable. - */ - ResultNode getAResult() { result = self.getAResult() } - - /** - * Gets the data-flow node corresponding to the `i`th result of this function. - */ - ResultNode getResult(int i) { result = this.getAResult() and result.getIndex() = i } - - /** - * Gets the function entity this node corresponds to. - * - * Note that this predicate has no result for function literals. - */ - Function getFunction() { result = self.getFunction() } -} - -/** A representation of a function that is declared in the module scope. */ -class GlobalFunctionNode extends FunctionNode::Range, MkGlobalFunctionNode { - Function func; - - GlobalFunctionNode() { this = MkGlobalFunctionNode(func) } - - override ParameterNode getParameter(int i) { result = parameterNode(func.getParameter(i)) } - - override string getName() { result = func.getName() } - - override Function getFunction() { result = func } - - override ReceiverNode getReceiver() { result = receiverNode(func.(Method).getReceiver()) } - - override string getNodeKind() { result = "function " + func.getName() } - - override string toString() { result = "function " + func.getName() } - - override predicate hasLocationInfo( - string filepath, int startline, int startcolumn, int endline, int endcolumn - ) { - func.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) - } - - override ResultNode getAResult() { - result.getRoot() = this.getFunction().(DeclaredFunction).getFuncDecl() - } -} - -/** A representation of the function that is defined by a function literal. */ -class FuncLitNode extends FunctionNode::Range, ExprNode { - override FuncLit expr; - - override ParameterNode getParameter(int i) { result = parameterNode(expr.getParameter(i)) } - - override string getName() { none() } - - override ReceiverNode getReceiver() { none() } - - override string toString() { result = "function literal" } - - override ResultNode getAResult() { result.getRoot() = this.getExpr() } -} - -/** - * Gets a possible target of call `cn`.class - * - * This is written explicitly like this instead of using `getCalleeNode().getAPredecessor*()` - * or `result.getASuccessor*() = cn.getCalleeNode()` because the explicit form inhibits the - * optimizer from combining this with other uses of `getASuccessor*()`, which can lead to - * recursion through a magic side-condition if those other users call `getACallee()` and thus - * pointless recomputation of `getACallee()` each recursive iteration. - */ -private DataFlow::Node getACalleeSource(DataFlow::CallNode cn) { - result = cn.getCalleeNode() or - result.getASuccessor() = getACalleeSource(cn) -} - -/** A data flow node that represents a call. */ -class CallNode extends ExprNode { - override CallExpr expr; - - /** Gets the declared target of this call */ - Function getTarget() { result = expr.getTarget() } - - private DataFlow::Node getACalleeSource() { result = getACalleeSource(this) } - - /** - * Gets the definition of a possible target of this call. - * - * For non-virtual calls, there is at most one possible call target (but there may be none if the - * target has no declaration). - * - * For virtual calls, we look up possible targets in all types that implement the receiver - * interface type. - */ - FuncDef getACallee() { - result = this.getTarget().(DeclaredFunction).getFuncDecl() - or - exists(DataFlow::Node calleeSource | calleeSource = this.getACalleeSource() | - result = calleeSource.asExpr() - or - exists(Method declared, Method actual | - calleeSource = declared.getARead() and - actual.implements(declared) and - result = actual.(DeclaredFunction).getFuncDecl() - ) - ) - } - - /** Gets the name of the function or method being called, if it can be determined. */ - string getCalleeName() { result = expr.getTarget().getName() or result = expr.getCalleeName() } - - /** Gets the data flow node specifying the function to be called. */ - Node getCalleeNode() { result = exprNode(expr.getCalleeExpr()) } - - /** Gets the underlying call. */ - CallExpr getCall() { result = this.getExpr() } - - /** - * Gets the data flow node corresponding to the `i`th argument of this call. - * - * Note that the first argument in calls to the built-in function `make` is a type, which is - * not a data-flow node. It is skipped for the purposes of this predicate, so the (syntactically) - * second argument becomes the first argument in terms of data flow. - * - * For calls of the form `f(g())` where `g` has multiple results, the arguments of the call to - * `i` are the (implicit) element extraction nodes for the call to `g`. - */ - Node getArgument(int i) { - if expr.getArgument(0).getType() instanceof TupleType - then result = extractTupleElement(exprNode(expr.getArgument(0)), i) - else - result = rank[i + 1](Expr arg, int j | arg = expr.getArgument(j) | exprNode(arg) order by j) - } - - /** Gets the data flow node corresponding to an argument of this call. */ - Node getAnArgument() { result = this.getArgument(_) } - - /** Gets the number of arguments of this call, if it can be determined. */ - int getNumArgument() { result = count(this.getAnArgument()) } - - /** Gets a function passed as the `i`th argument of this call. */ - FunctionNode getCallback(int i) { result.getASuccessor*() = this.getArgument(i) } - - /** - * Gets the data-flow node corresponding to the `i`th result of this call. - * - * If there is a single result then it is considered to be the 0th result. - */ - Node getResult(int i) { - i = 0 and result = this.getResult() - or - result = extractTupleElement(this, i) - } - - /** - * Gets the data-flow node corresponding to the result of this call. - * - * Note that this predicate is not defined for calls with multiple results; use the one-argument - * variant `getResult(i)` for such calls. - */ - Node getResult() { not this.getType() instanceof TupleType and result = this } - - /** Gets a result of this call. */ - Node getAResult() { result = this.getResult(_) } - - /** Gets the data flow node corresponding to the receiver of this call, if any. */ - Node getReceiver() { result = this.getACalleeSource().(MethodReadNode).getReceiver() } - - /** Holds if this call has an ellipsis after its last argument. */ - predicate hasEllipsis() { expr.hasEllipsis() } -} - -/** A data flow node that represents a call to a method. */ -class MethodCallNode extends CallNode { - MethodCallNode() { expr.getTarget() instanceof Method } - - override Method getTarget() { result = expr.getTarget() } - - override MethodDecl getACallee() { result = super.getACallee() } -} - -/** A representation of a parameter initialization. */ -class ParameterNode extends SsaNode { - override SsaExplicitDefinition ssa; - Parameter parm; - - ParameterNode() { ssa.getInstruction() = IR::initParamInstruction(parm) } - - /** Gets the parameter this node initializes. */ - override Parameter asParameter() { result = parm } - - /** Holds if this node initializes the `i`th parameter of `fd`. */ - predicate isParameterOf(FuncDef fd, int i) { parm.isParameterOf(fd, i) } -} - -/** A representation of a receiver initialization. */ -class ReceiverNode extends ParameterNode { - override ReceiverVariable parm; - - /** Gets the receiver variable this node initializes. */ - ReceiverVariable asReceiverVariable() { result = parm } - - /** Holds if this node initializes the receiver variable of `m`. */ - predicate isReceiverOf(MethodDecl m) { parm.isReceiverOf(m) } -} - -private Node getADirectlyWrittenNode() { - exists(Write w | w.writesField(result, _, _) or w.writesElement(result, _, _)) -} - -private DataFlow::Node getAccessPathPredecessor(DataFlow::Node node) { - result = node.(PointerDereferenceNode).getOperand() - or - result = node.(ComponentReadNode).getBase() -} - -private Node getAWrittenNode() { result = getAccessPathPredecessor*(getADirectlyWrittenNode()) } - -/** - * A node associated with an object after an operation that might have - * changed its state. - * - * This can be either the argument to a callable after the callable returns - * (which might have mutated the argument), or the qualifier of a field after - * an update to the field. - * - * Nodes corresponding to AST elements, for example `ExprNode`, usually refer - * to the value before the update with the exception of `ClassInstanceExpr`, - * which represents the value after the constructor has run. - */ -class PostUpdateNode extends Node { - Node preupd; - - PostUpdateNode() { - ( - preupd instanceof AddressOperationNode - or - preupd = any(AddressOperationNode addr).getOperand() - or - preupd = any(PointerDereferenceNode deref).getOperand() - or - preupd = getAWrittenNode() - or - preupd instanceof ArgumentNode and - mutableType(preupd.getType()) - ) and - ( - preupd = this.(SsaNode).getAUse() - or - preupd = this and - not basicLocalFlowStep(_, this) - ) - } - - /** - * Gets the node before the state update. - */ - Node getPreUpdateNode() { result = preupd } -} - -/** - * A data-flow node that occurs as an argument in a call, including receiver arguments. - */ -class ArgumentNode extends Node { - CallNode c; - int i; - - ArgumentNode() { this = getArgument(c, i) } - - /** - * Holds if this argument occurs at the given position in the given call. - * - * The receiver argument is considered to have index `-1`. - * - * Note that we currently do not track receiver arguments into calls to interface methods. - */ - predicate argumentOf(CallExpr call, int pos) { - call = c.asExpr() and - pos = i and - ( - i != -1 - or - exists(c.(MethodCallNode).getTarget().getBody()) - ) - } - - /** - * Gets the `CallNode` this is an argument to. - */ - CallNode getCall() { result = c } -} - -/** - * Holds if `tp` is a type that may (directly or indirectly) reference a memory location. - * - * If a value with a mutable type is passed to a function, the function could potentially - * mutate it or something it points to. - */ -predicate mutableType(Type tp) { - exists(Type underlying | underlying = tp.getUnderlyingType() | - not underlying instanceof BoolType and - not underlying instanceof NumericType and - not underlying instanceof StringType and - not underlying instanceof LiteralType - ) -} - -/** - * A node whose value is returned as a result from a function. - * - * This can either be a node corresponding to an expression in a return statement, - * or a node representing the current value of a named result variable at the exit - * of the function. - */ -class ResultNode extends InstructionNode { - FuncDef fd; - int i; - - ResultNode() { - exists(IR::ReturnInstruction ret | ret.getRoot() = fd | insn = ret.getResult(i)) - or - insn.(IR::ReadResultInstruction).reads(fd.getResultVar(i)) - } - - /** Gets the index of this result among all results of the function. */ - int getIndex() { result = i } -} - -/** - * A data-flow node that reads the value of a variable, constant, field or array element, - * or refers to a function. - */ -class ReadNode extends InstructionNode { - override IR::ReadInstruction insn; - - /** - * Holds if this data-flow node evaluates to value of `v`, which is a value entity, that is, a - * constant, variable, field, function, or method. - */ - predicate reads(ValueEntity v) { insn.reads(v) } - - /** - * Holds if this data-flow node reads the value of SSA variable `v`. - */ - predicate readsSsaVariable(SsaVariable v) { insn = v.getAUse() } - - /** - * Holds if this data-flow node reads the value of field `f` on the value of `base` or its - * implicit dereference. - * - * For example, for the field read `x.width`, `base` is either the data-flow node corresponding - * to `x` or (if `x` is a pointer) the data-flow node corresponding to the implicit dereference - * `*x`, and `f` is the field referenced by `width`. - */ - predicate readsField(Node base, Field f) { - insn.readsField(base.asInstruction(), f) - or - insn.readsField(IR::implicitDerefInstruction(base.asExpr()), f) - } - - /** - * Holds if this data-flow node reads the value of field `package.type.field` on the value of `base` or its - * implicit dereference. - * - * For example, for the field read `x.width`, `base` is either the data-flow node corresponding - * to `x` or (if `x` is a pointer) the data-flow node corresponding to the implicit dereference - * `*x`, and `x` has the type `package.type`. - */ - predicate readsField(Node base, string package, string type, string field) { - exists(Field f | f.hasQualifiedName(package, type, field) | this.readsField(base, f)) - } - - /** - * Holds if this data-flow node looks up method `m` on the value of `receiver` or its implicit - * dereference. - * - * For example, for the method read `x.area`, `receiver` is either the data-flow node corresponding - * to `x` or (if `x` is a pointer) the data-flow node corresponding to the implicit dereference - * `*x`, and `m` is the method referenced by `area`. - */ - predicate readsMethod(Node receiver, Method m) { - insn.readsMethod(receiver.asInstruction(), m) - or - insn.readsMethod(IR::implicitDerefInstruction(receiver.asExpr()), m) - } - - /** - * Holds if this data-flow node looks up method `package.type.name` on the value of `receiver` - * or its implicit dereference. - * - * For example, for the method read `x.name`, `receiver` is either the data-flow node corresponding - * to `x` or (if `x` is a pointer) the data-flow node corresponding to the implicit dereference - * `*x`, and `package.type` is a type of `x` that defines a method named `name`. - */ - predicate readsMethod(Node receiver, string package, string type, string name) { - exists(Method m | m.hasQualifiedName(package, type, name) | this.readsMethod(receiver, m)) - } - - /** - * Holds if this data-flow node reads the value of element `index` on the value of `base` or its - * implicit dereference. - * - * For example, for the element read `xs[i]`, `base` is either the data-flow node corresponding - * to `xs` or (if `xs` is a pointer) the data-flow node corresponding to the implicit dereference - * `*xs`, and `index` is the data-flow node corresponding to `i`. - */ - predicate readsElement(Node base, Node index) { - insn.readsElement(base.asInstruction(), index.asInstruction()) - or - insn.readsElement(IR::implicitDerefInstruction(base.asExpr()), index.asInstruction()) - } -} - -/** - * A data-flow node that reads the value of a field from a struct, or an element from an array, slice, map or string. - */ -class ComponentReadNode extends ReadNode { - override IR::ComponentReadInstruction insn; - - /** Gets the data-flow node representing the base from which the fieldĀ or element is read. */ - Node getBase() { result = instructionNode(insn.getBase()) } -} - -/** - * A data-flow node that reads an element of an array, map, slice or string. - */ -class ElementReadNode extends ComponentReadNode { - override IR::ElementReadInstruction insn; - - /** Gets the data-flow node representing the index of the element being read. */ - Node getIndex() { result = instructionNode(insn.getIndex()) } - - /** Holds if this data-flow node reads element `index` of `base`. */ - predicate reads(Node base, Node index) { this.readsElement(base, index) } -} - -/** - * A data-flow node that extracts a substring or slice from a string, array, pointer to array, - * or slice. - */ -class SliceNode extends InstructionNode { - override IR::SliceInstruction insn; - - /** Gets the base of this slice node. */ - Node getBase() { result = instructionNode(insn.getBase()) } - - /** Gets the lower bound of this slice node. */ - Node getLow() { result = instructionNode(insn.getLow()) } - - /** Gets the upper bound of this slice node. */ - Node getHigh() { result = instructionNode(insn.getHigh()) } - - /** Gets the maximum of this slice node. */ - Node getMax() { result = instructionNode(insn.getMax()) } -} - -/** - * A data-flow node corresponding to an expression with a binary operator. - */ -class BinaryOperationNode extends Node { - Node left; - Node right; - string op; - - BinaryOperationNode() { - exists(BinaryExpr bin | bin = this.asExpr() | - left = exprNode(bin.getLeftOperand()) and - right = exprNode(bin.getRightOperand()) and - op = bin.getOperator() - ) - or - exists(IR::EvalCompoundAssignRhsInstruction rhs, CompoundAssignStmt assgn, string o | - rhs = this.asInstruction() and assgn = rhs.getAssignment() and o = assgn.getOperator() - | - left = exprNode(assgn.getLhs()) and - right = exprNode(assgn.getRhs()) and - op = o.substring(0, o.length() - 1) - ) - or - exists(IR::EvalIncDecRhsInstruction rhs, IncDecStmt ids | - rhs = this.asInstruction() and ids = rhs.getStmt() - | - left = exprNode(ids.getOperand()) and - right = instructionNode(any(IR::EvalImplicitOneInstruction one | one.getStmt() = ids)) and - op = ids.getOperator().charAt(0) - ) - } - - /** Holds if this operation may have observable side effects. */ - predicate mayHaveSideEffects() { this.asExpr().mayHaveOwnSideEffects() } - - /** Gets the left operand of this operation. */ - Node getLeftOperand() { result = left } - - /** Gets the right operand of this operation. */ - Node getRightOperand() { result = right } - - /** Gets an operand of this operation. */ - Node getAnOperand() { result = left or result = right } - - /** Gets the operator of this operation. */ - string getOperator() { result = op } - - /** Holds if `x` and `y` are the operands of this operation, in either order. */ - predicate hasOperands(Node x, Node y) { - x = this.getAnOperand() and - y = this.getAnOperand() and - x != y - } -} - -/** - * A data-flow node corresponding to an expression with a unary operator. - */ -class UnaryOperationNode extends InstructionNode { - UnaryOperationNode() { - this.asExpr() instanceof UnaryExpr - or - this.asExpr() instanceof StarExpr - or - insn instanceof IR::EvalImplicitDerefInstruction - } - - /** Holds if this operation may have observable side effects. */ - predicate mayHaveSideEffects() { - this.asExpr().mayHaveOwnSideEffects() - or - insn instanceof IR::EvalImplicitDerefInstruction - } - - /** Gets the operand of this operation. */ - Node getOperand() { - result = exprNode(this.asExpr().(UnaryExpr).getOperand()) - or - result = exprNode(this.asExpr().(StarExpr).getBase()) - or - result = exprNode(insn.(IR::EvalImplicitDerefInstruction).getOperand()) - } - - /** Gets the operator of this operation. */ - string getOperator() { - result = this.asExpr().(UnaryExpr).getOperator() - or - this.asExpr() instanceof StarExpr and - result = "*" - or - insn instanceof IR::EvalImplicitDerefInstruction and - result = "*" - } -} - -/** - * A data-flow node that dereferences a pointer. - */ -class PointerDereferenceNode extends UnaryOperationNode { - PointerDereferenceNode() { - this.asExpr() instanceof StarExpr - or - this.asExpr() instanceof DerefExpr - or - insn instanceof IR::EvalImplicitDerefInstruction - } -} - -/** - * A data-flow node that takes the address of a memory location. - */ -class AddressOperationNode extends UnaryOperationNode, ExprNode { - override AddressExpr expr; -} - -/** - * A data-flow node that reads the value of a field. - */ -class FieldReadNode extends ComponentReadNode { - override IR::FieldReadInstruction insn; - - /** Gets the field this node reads. */ - Field getField() { result = insn.getField() } - - /** Gets the name of the field this node reads. */ - string getFieldName() { result = this.getField().getName() } -} - -/** - * A data-flow node that refers to a method. - */ -class MethodReadNode extends ReadNode { - override IR::MethodReadInstruction insn; - - /** Gets the receiver node on which the method is referenced. */ - Node getReceiver() { result = instructionNode(insn.getReceiver()) } - - /** Gets the method this node refers to. */ - Method getMethod() { result = insn.getMethod() } - - /** Gets the name of the method this node refers to. */ - string getMethodName() { result = this.getMethod().getName() } -} - -/** - * A data-flow node performing a relational comparison using `<`, `<=`, `>` or `>=`. - */ -class RelationalComparisonNode extends BinaryOperationNode, ExprNode { - override RelationalComparisonExpr expr; - - /** Holds if this comparison evaluates to `outcome` iff `lesser <= greater + bias`. */ - predicate leq(boolean outcome, Node lesser, Node greater, int bias) { - outcome = true and - lesser = exprNode(expr.getLesserOperand()) and - greater = exprNode(expr.getGreaterOperand()) and - (if expr.isStrict() then bias = -1 else bias = 0) - or - outcome = false and - lesser = exprNode(expr.getGreaterOperand()) and - greater = exprNode(expr.getLesserOperand()) and - (if expr.isStrict() then bias = 0 else bias = -1) - } -} - -/** - * A data-flow node performing an equality test using `==` or `!=`. - */ -class EqualityTestNode extends BinaryOperationNode, ExprNode { - override EqualityTestExpr expr; - - /** Holds if this comparison evaluates to `outcome` iff `lhs == rhs`. */ - predicate eq(boolean outcome, Node lhs, Node rhs) { - outcome = expr.getPolarity() and - expr.hasOperands(lhs.asExpr(), rhs.asExpr()) - } - - /** Gets the polarity of this equality test, that is, `true` for `==` and `false` for `!=`. */ - boolean getPolarity() { result = expr.getPolarity() } -} - -/** - * A data-flow node performing a type cast using either a type conversion - * or an assertion. - */ -class TypeCastNode extends ExprNode { - TypeCastNode() { - expr instanceof TypeAssertExpr - or - expr instanceof ConversionExpr - } - - /** - * Gets the type being converted to. Note this differs from `this.getType()` for - * `TypeAssertExpr`s that return a (result, ok) tuple. - */ - Type getResultType() { - if this.getType() instanceof TupleType - then result = this.getType().(TupleType).getComponentType(0) - else result = this.getType() - } - - /** Gets the operand of the type cast. */ - DataFlow::Node getOperand() { - result.asExpr() = expr.(TypeAssertExpr).getExpr() - or - result.asExpr() = expr.(ConversionExpr).getOperand() - } -} - -/** - * A data-flow node representing an element of an array, map, slice or string defined from `range` statement. - * - * Example: in `_, x := range y { ... }`, this represents the `Node` that extracts the element from the - * range statement, which will flow to `x`. - */ -class RangeElementNode extends Node { - DataFlow::Node base; - IR::ExtractTupleElementInstruction extract; - - RangeElementNode() { - this.asInstruction() = extract and - extract.extractsElement(_, 1) and - extract.getBase().(IR::GetNextEntryInstruction).getDomain() = base.asInstruction() - } - - /** Gets the data-flow node representing the base from which the element is read. */ - DataFlow::Node getBase() { result = base } -} +import DataFlowNodes::Public /** * Holds if `node` reads an element from `base`, either via an element-read (`base[y]`) expression