mirror of
https://github.com/github/codeql.git
synced 2026-05-03 04:39:29 +02:00
Merge remote-tracking branch 'upstream/rc/1.20' into csharp/dataflow/performance
This commit is contained in:
@@ -30,5 +30,9 @@
|
||||
|
||||
* The class `TrivialProperty` now includes library properties determined to be trivial using CIL analysis. This may increase the number of results for all queries that use data flow.
|
||||
* Taint-tracking steps have been added for the `Json.NET` package. This will improve results for queries that use taint-tracking.
|
||||
* Support has been added for EntityFrameworkCore, including
|
||||
- Stored data flow sources
|
||||
- Sinks for SQL expressions
|
||||
- Data flow through fields that are mapped to the database.
|
||||
|
||||
## Changes to the autobuilder
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
<p>Consider changing the surrounding expression to match the floating point type. If rounding is intended, explicitly round using a standard function such as `trunc`, `floor` or `round`.</p>
|
||||
<p>Consider changing the surrounding expression to match the floating point type. If rounding is intended, explicitly round using a standard function such as <code>trunc</code>, <code>floor</code> or <code>round</code>.</p>
|
||||
|
||||
</recommendation>
|
||||
<example><sample src="LossyFunctionResultCast.cpp" />
|
||||
|
||||
@@ -8,9 +8,9 @@
|
||||
<p>A function is called with arguments despite having an empty parameter list. This may indicate
|
||||
that the incorrect function is being called, or that the author misunderstood the function.</p>
|
||||
|
||||
<p>In C, a function declared with an empty parameter list `()` is considered to have an unknown
|
||||
<p>In C, a function declared with an empty parameter list <code>()</code> is considered to have an unknown
|
||||
parameter list, and therefore can be called with any set of arguments. To declare a function
|
||||
which takes no arguments, you must use `(void)` as the parameter list in any forward declarations.
|
||||
which takes no arguments, you must use <code>(void)</code> as the parameter list in any forward declarations.
|
||||
In C++, either style of declaration indicates that the function accepts no arguments.</p>
|
||||
|
||||
</overview>
|
||||
|
||||
@@ -1310,6 +1310,13 @@ class CallInstruction extends Instruction {
|
||||
result = getAnOperand()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the `Function` that the call targets, if this is statically known.
|
||||
*/
|
||||
final Function getStaticCallTarget() {
|
||||
result = getCallTarget().(FunctionInstruction).getFunctionSymbol()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all of the arguments of the call, including the `this` pointer, if any.
|
||||
*/
|
||||
|
||||
@@ -3,6 +3,8 @@ import cpp
|
||||
private import InputIR
|
||||
private import semmle.code.cpp.ir.internal.IntegerConstant as Ints
|
||||
|
||||
private import semmle.code.cpp.models.interfaces.Alias
|
||||
|
||||
private class IntValue = Ints::IntValue;
|
||||
|
||||
/**
|
||||
@@ -46,7 +48,7 @@ private IntValue getFieldBitOffset(Field field) {
|
||||
* not result in any address held in that operand from escaping beyond the
|
||||
* instruction.
|
||||
*/
|
||||
predicate operandIsConsumedWithoutEscaping(Operand operand) {
|
||||
private predicate operandIsConsumedWithoutEscaping(Operand operand) {
|
||||
// The source/destination address of a Load/Store does not escape (but the
|
||||
// loaded/stored value could).
|
||||
operand instanceof AddressOperand or
|
||||
@@ -60,7 +62,18 @@ predicate operandIsConsumedWithoutEscaping(Operand operand) {
|
||||
// Converting an address to a `bool` does not escape the address.
|
||||
instr.(ConvertInstruction).getResultType() instanceof BoolType
|
||||
)
|
||||
)
|
||||
) or
|
||||
// Some standard function arguments never escape
|
||||
isNeverEscapesArgument(operand)
|
||||
}
|
||||
|
||||
private predicate operandEscapesDomain(Operand operand) {
|
||||
not operandIsConsumedWithoutEscaping(operand) and
|
||||
not operandIsPropagated(operand, _) and
|
||||
not isArgumentForParameter(_, operand, _) and
|
||||
not isOnlyEscapesViaReturnArgument(operand) and
|
||||
not operand.getUseInstruction() instanceof ReturnValueInstruction and
|
||||
not operand instanceof PhiOperand
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -98,7 +111,7 @@ IntValue getPointerBitOffset(PointerOffsetInstruction instr) {
|
||||
* `bitOffset`. If the address is propagated, but the offset is not known to be
|
||||
* a constant, then `bitOffset` is unknown.
|
||||
*/
|
||||
predicate operandIsPropagated(Operand operand, IntValue bitOffset) {
|
||||
private predicate operandIsPropagated(Operand operand, IntValue bitOffset) {
|
||||
exists(Instruction instr |
|
||||
instr = operand.getUseInstruction() and
|
||||
(
|
||||
@@ -134,34 +147,134 @@ predicate operandIsPropagated(Operand operand, IntValue bitOffset) {
|
||||
// offset of the field.
|
||||
bitOffset = getFieldBitOffset(instr.(FieldAddressInstruction).getField()) or
|
||||
// A copy propagates the source value.
|
||||
operand = instr.(CopyInstruction).getSourceValueOperand() and bitOffset = 0
|
||||
operand = instr.(CopyInstruction).getSourceValueOperand() and bitOffset = 0 or
|
||||
// Some functions are known to propagate an argument
|
||||
isAlwaysReturnedArgument(operand) and bitOffset = 0
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if any address held in operand number `tag` of instruction `instr`
|
||||
* escapes outside the domain of the analysis.
|
||||
*/
|
||||
predicate operandEscapes(Operand operand) {
|
||||
// Conservatively assume that the address escapes unless one of the following
|
||||
// holds:
|
||||
not (
|
||||
// The operand is used in a way that does not escape the instruction
|
||||
operandIsConsumedWithoutEscaping(operand) or
|
||||
// The address is propagated to the result of the instruction, but that
|
||||
// result does not itself escape.
|
||||
operandIsPropagated(operand, _) and not resultEscapes(operand.getUseInstruction())
|
||||
private predicate operandEscapesNonReturn(Operand operand) {
|
||||
// The address is propagated to the result of the instruction, and that result itself is returned
|
||||
operandIsPropagated(operand, _) and resultEscapesNonReturn(operand.getUseInstruction())
|
||||
or
|
||||
// The operand is used in a function call which returns it, and the return value is then returned
|
||||
exists(CallInstruction ci, Instruction init |
|
||||
isArgumentForParameter(ci, operand, init) and
|
||||
(
|
||||
resultMayReachReturn(init) and
|
||||
resultEscapesNonReturn(ci)
|
||||
or
|
||||
resultEscapesNonReturn(init)
|
||||
)
|
||||
)
|
||||
or
|
||||
isOnlyEscapesViaReturnArgument(operand) and resultEscapesNonReturn(operand.getUseInstruction())
|
||||
or
|
||||
operand instanceof PhiOperand and
|
||||
resultEscapesNonReturn(operand.getUseInstruction())
|
||||
or
|
||||
operandEscapesDomain(operand)
|
||||
}
|
||||
|
||||
private predicate operandMayReachReturn(Operand operand) {
|
||||
// The address is propagated to the result of the instruction, and that result itself is returned
|
||||
operandIsPropagated(operand, _) and
|
||||
resultMayReachReturn(operand.getUseInstruction())
|
||||
or
|
||||
// The operand is used in a function call which returns it, and the return value is then returned
|
||||
exists(CallInstruction ci, Instruction init |
|
||||
isArgumentForParameter(ci, operand, init) and
|
||||
resultMayReachReturn(init) and
|
||||
resultMayReachReturn(ci)
|
||||
)
|
||||
or
|
||||
// The address is returned
|
||||
operand.getUseInstruction() instanceof ReturnValueInstruction
|
||||
or
|
||||
isOnlyEscapesViaReturnArgument(operand) and resultMayReachReturn(operand.getUseInstruction())
|
||||
or
|
||||
operand instanceof PhiOperand and
|
||||
resultMayReachReturn(operand.getUseInstruction())
|
||||
}
|
||||
|
||||
private predicate operandReturned(Operand operand, IntValue bitOffset) {
|
||||
// The address is propagated to the result of the instruction, and that result itself is returned
|
||||
exists(IntValue bitOffset1, IntValue bitOffset2 |
|
||||
operandIsPropagated(operand, bitOffset1) and
|
||||
resultReturned(operand.getUseInstruction(), bitOffset2) and
|
||||
bitOffset = Ints::add(bitOffset1, bitOffset2)
|
||||
)
|
||||
or
|
||||
// The operand is used in a function call which returns it, and the return value is then returned
|
||||
exists(CallInstruction ci, Instruction init, IntValue bitOffset1, IntValue bitOffset2 |
|
||||
isArgumentForParameter(ci, operand, init) and
|
||||
resultReturned(init, bitOffset1) and
|
||||
resultReturned(ci, bitOffset2) and
|
||||
bitOffset = Ints::add(bitOffset1, bitOffset2)
|
||||
|
||||
)
|
||||
or
|
||||
// The address is returned
|
||||
operand.getUseInstruction() instanceof ReturnValueInstruction and
|
||||
bitOffset = 0
|
||||
or
|
||||
isOnlyEscapesViaReturnArgument(operand) and resultReturned(operand.getUseInstruction(), _) and
|
||||
bitOffset = Ints::unknown()
|
||||
}
|
||||
|
||||
private predicate isArgumentForParameter(CallInstruction ci, Operand operand, Instruction init) {
|
||||
exists(Function f |
|
||||
ci = operand.getUseInstruction() and
|
||||
f = ci.getStaticCallTarget() and
|
||||
(
|
||||
init.(InitializeParameterInstruction).getParameter() = f.getParameter(operand.(PositionalArgumentOperand).getIndex())
|
||||
or
|
||||
init instanceof InitializeThisInstruction and
|
||||
init.getEnclosingFunction() = f and
|
||||
operand instanceof ThisArgumentOperand
|
||||
) and
|
||||
not f.isVirtual() and
|
||||
not f instanceof AliasFunction
|
||||
)
|
||||
}
|
||||
|
||||
private predicate isAlwaysReturnedArgument(Operand operand) {
|
||||
exists(AliasFunction f |
|
||||
f = operand.getUseInstruction().(CallInstruction).getStaticCallTarget() and
|
||||
f.parameterIsAlwaysReturned(operand.(PositionalArgumentOperand).getIndex())
|
||||
)
|
||||
}
|
||||
|
||||
private predicate isOnlyEscapesViaReturnArgument(Operand operand) {
|
||||
exists(AliasFunction f |
|
||||
f = operand.getUseInstruction().(CallInstruction).getStaticCallTarget() and
|
||||
f.parameterEscapesOnlyViaReturn(operand.(PositionalArgumentOperand).getIndex())
|
||||
)
|
||||
}
|
||||
|
||||
private predicate isNeverEscapesArgument(Operand operand) {
|
||||
exists(AliasFunction f |
|
||||
f = operand.getUseInstruction().(CallInstruction).getStaticCallTarget() and
|
||||
f.parameterNeverEscapes(operand.(PositionalArgumentOperand).getIndex())
|
||||
)
|
||||
}
|
||||
|
||||
private predicate resultReturned(Instruction instr, IntValue bitOffset) {
|
||||
operandReturned(instr.getAUse(), bitOffset)
|
||||
}
|
||||
|
||||
private predicate resultMayReachReturn(Instruction instr) {
|
||||
operandMayReachReturn(instr.getAUse())
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if any address held in the result of instruction `instr` escapes
|
||||
* outside the domain of the analysis.
|
||||
*/
|
||||
predicate resultEscapes(Instruction instr) {
|
||||
private predicate resultEscapesNonReturn(Instruction instr) {
|
||||
// The result escapes if it has at least one use that escapes.
|
||||
operandEscapes(instr.getAUse())
|
||||
operandEscapesNonReturn(instr.getAUse())
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -169,15 +282,11 @@ predicate resultEscapes(Instruction instr) {
|
||||
* domain of the analysis.
|
||||
*/
|
||||
private predicate automaticVariableAddressEscapes(IRAutomaticVariable var) {
|
||||
exists(FunctionIR funcIR |
|
||||
funcIR = var.getEnclosingFunctionIR() and
|
||||
// The variable's address escapes if the result of any
|
||||
// VariableAddressInstruction that computes the variable's address escapes.
|
||||
exists(VariableAddressInstruction instr |
|
||||
instr.getEnclosingFunctionIR() = funcIR and
|
||||
instr.getVariable() = var and
|
||||
resultEscapes(instr)
|
||||
)
|
||||
// The variable's address escapes if the result of any
|
||||
// VariableAddressInstruction that computes the variable's address escapes.
|
||||
exists(VariableAddressInstruction instr |
|
||||
instr.getVariable() = var and
|
||||
resultEscapesNonReturn(instr)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -207,7 +316,14 @@ predicate resultPointsTo(Instruction instr, IRVariable var, IntValue bitOffset)
|
||||
// If an operand is propagated, then the result points to the same variable,
|
||||
// offset by the bit offset from the propagation.
|
||||
resultPointsTo(operand.getDefinitionInstruction(), var, originalBitOffset) and
|
||||
operandIsPropagated(operand, propagatedBitOffset) and
|
||||
(
|
||||
operandIsPropagated(operand, propagatedBitOffset)
|
||||
or
|
||||
exists(CallInstruction ci, Instruction init |
|
||||
isArgumentForParameter(ci, operand, init) and
|
||||
resultReturned(init, propagatedBitOffset)
|
||||
)
|
||||
) and
|
||||
bitOffset = Ints::add(originalBitOffset, propagatedBitOffset)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1310,6 +1310,13 @@ class CallInstruction extends Instruction {
|
||||
result = getAnOperand()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the `Function` that the call targets, if this is statically known.
|
||||
*/
|
||||
final Function getStaticCallTarget() {
|
||||
result = getCallTarget().(FunctionInstruction).getFunctionSymbol()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all of the arguments of the call, including the `this` pointer, if any.
|
||||
*/
|
||||
|
||||
@@ -1310,6 +1310,13 @@ class CallInstruction extends Instruction {
|
||||
result = getAnOperand()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the `Function` that the call targets, if this is statically known.
|
||||
*/
|
||||
final Function getStaticCallTarget() {
|
||||
result = getCallTarget().(FunctionInstruction).getFunctionSymbol()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all of the arguments of the call, including the `this` pointer, if any.
|
||||
*/
|
||||
|
||||
@@ -3,6 +3,8 @@ import cpp
|
||||
private import InputIR
|
||||
private import semmle.code.cpp.ir.internal.IntegerConstant as Ints
|
||||
|
||||
private import semmle.code.cpp.models.interfaces.Alias
|
||||
|
||||
private class IntValue = Ints::IntValue;
|
||||
|
||||
/**
|
||||
@@ -46,7 +48,7 @@ private IntValue getFieldBitOffset(Field field) {
|
||||
* not result in any address held in that operand from escaping beyond the
|
||||
* instruction.
|
||||
*/
|
||||
predicate operandIsConsumedWithoutEscaping(Operand operand) {
|
||||
private predicate operandIsConsumedWithoutEscaping(Operand operand) {
|
||||
// The source/destination address of a Load/Store does not escape (but the
|
||||
// loaded/stored value could).
|
||||
operand instanceof AddressOperand or
|
||||
@@ -60,7 +62,18 @@ predicate operandIsConsumedWithoutEscaping(Operand operand) {
|
||||
// Converting an address to a `bool` does not escape the address.
|
||||
instr.(ConvertInstruction).getResultType() instanceof BoolType
|
||||
)
|
||||
)
|
||||
) or
|
||||
// Some standard function arguments never escape
|
||||
isNeverEscapesArgument(operand)
|
||||
}
|
||||
|
||||
private predicate operandEscapesDomain(Operand operand) {
|
||||
not operandIsConsumedWithoutEscaping(operand) and
|
||||
not operandIsPropagated(operand, _) and
|
||||
not isArgumentForParameter(_, operand, _) and
|
||||
not isOnlyEscapesViaReturnArgument(operand) and
|
||||
not operand.getUseInstruction() instanceof ReturnValueInstruction and
|
||||
not operand instanceof PhiOperand
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -98,7 +111,7 @@ IntValue getPointerBitOffset(PointerOffsetInstruction instr) {
|
||||
* `bitOffset`. If the address is propagated, but the offset is not known to be
|
||||
* a constant, then `bitOffset` is unknown.
|
||||
*/
|
||||
predicate operandIsPropagated(Operand operand, IntValue bitOffset) {
|
||||
private predicate operandIsPropagated(Operand operand, IntValue bitOffset) {
|
||||
exists(Instruction instr |
|
||||
instr = operand.getUseInstruction() and
|
||||
(
|
||||
@@ -134,34 +147,134 @@ predicate operandIsPropagated(Operand operand, IntValue bitOffset) {
|
||||
// offset of the field.
|
||||
bitOffset = getFieldBitOffset(instr.(FieldAddressInstruction).getField()) or
|
||||
// A copy propagates the source value.
|
||||
operand = instr.(CopyInstruction).getSourceValueOperand() and bitOffset = 0
|
||||
operand = instr.(CopyInstruction).getSourceValueOperand() and bitOffset = 0 or
|
||||
// Some functions are known to propagate an argument
|
||||
isAlwaysReturnedArgument(operand) and bitOffset = 0
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if any address held in operand number `tag` of instruction `instr`
|
||||
* escapes outside the domain of the analysis.
|
||||
*/
|
||||
predicate operandEscapes(Operand operand) {
|
||||
// Conservatively assume that the address escapes unless one of the following
|
||||
// holds:
|
||||
not (
|
||||
// The operand is used in a way that does not escape the instruction
|
||||
operandIsConsumedWithoutEscaping(operand) or
|
||||
// The address is propagated to the result of the instruction, but that
|
||||
// result does not itself escape.
|
||||
operandIsPropagated(operand, _) and not resultEscapes(operand.getUseInstruction())
|
||||
private predicate operandEscapesNonReturn(Operand operand) {
|
||||
// The address is propagated to the result of the instruction, and that result itself is returned
|
||||
operandIsPropagated(operand, _) and resultEscapesNonReturn(operand.getUseInstruction())
|
||||
or
|
||||
// The operand is used in a function call which returns it, and the return value is then returned
|
||||
exists(CallInstruction ci, Instruction init |
|
||||
isArgumentForParameter(ci, operand, init) and
|
||||
(
|
||||
resultMayReachReturn(init) and
|
||||
resultEscapesNonReturn(ci)
|
||||
or
|
||||
resultEscapesNonReturn(init)
|
||||
)
|
||||
)
|
||||
or
|
||||
isOnlyEscapesViaReturnArgument(operand) and resultEscapesNonReturn(operand.getUseInstruction())
|
||||
or
|
||||
operand instanceof PhiOperand and
|
||||
resultEscapesNonReturn(operand.getUseInstruction())
|
||||
or
|
||||
operandEscapesDomain(operand)
|
||||
}
|
||||
|
||||
private predicate operandMayReachReturn(Operand operand) {
|
||||
// The address is propagated to the result of the instruction, and that result itself is returned
|
||||
operandIsPropagated(operand, _) and
|
||||
resultMayReachReturn(operand.getUseInstruction())
|
||||
or
|
||||
// The operand is used in a function call which returns it, and the return value is then returned
|
||||
exists(CallInstruction ci, Instruction init |
|
||||
isArgumentForParameter(ci, operand, init) and
|
||||
resultMayReachReturn(init) and
|
||||
resultMayReachReturn(ci)
|
||||
)
|
||||
or
|
||||
// The address is returned
|
||||
operand.getUseInstruction() instanceof ReturnValueInstruction
|
||||
or
|
||||
isOnlyEscapesViaReturnArgument(operand) and resultMayReachReturn(operand.getUseInstruction())
|
||||
or
|
||||
operand instanceof PhiOperand and
|
||||
resultMayReachReturn(operand.getUseInstruction())
|
||||
}
|
||||
|
||||
private predicate operandReturned(Operand operand, IntValue bitOffset) {
|
||||
// The address is propagated to the result of the instruction, and that result itself is returned
|
||||
exists(IntValue bitOffset1, IntValue bitOffset2 |
|
||||
operandIsPropagated(operand, bitOffset1) and
|
||||
resultReturned(operand.getUseInstruction(), bitOffset2) and
|
||||
bitOffset = Ints::add(bitOffset1, bitOffset2)
|
||||
)
|
||||
or
|
||||
// The operand is used in a function call which returns it, and the return value is then returned
|
||||
exists(CallInstruction ci, Instruction init, IntValue bitOffset1, IntValue bitOffset2 |
|
||||
isArgumentForParameter(ci, operand, init) and
|
||||
resultReturned(init, bitOffset1) and
|
||||
resultReturned(ci, bitOffset2) and
|
||||
bitOffset = Ints::add(bitOffset1, bitOffset2)
|
||||
|
||||
)
|
||||
or
|
||||
// The address is returned
|
||||
operand.getUseInstruction() instanceof ReturnValueInstruction and
|
||||
bitOffset = 0
|
||||
or
|
||||
isOnlyEscapesViaReturnArgument(operand) and resultReturned(operand.getUseInstruction(), _) and
|
||||
bitOffset = Ints::unknown()
|
||||
}
|
||||
|
||||
private predicate isArgumentForParameter(CallInstruction ci, Operand operand, Instruction init) {
|
||||
exists(Function f |
|
||||
ci = operand.getUseInstruction() and
|
||||
f = ci.getStaticCallTarget() and
|
||||
(
|
||||
init.(InitializeParameterInstruction).getParameter() = f.getParameter(operand.(PositionalArgumentOperand).getIndex())
|
||||
or
|
||||
init instanceof InitializeThisInstruction and
|
||||
init.getEnclosingFunction() = f and
|
||||
operand instanceof ThisArgumentOperand
|
||||
) and
|
||||
not f.isVirtual() and
|
||||
not f instanceof AliasFunction
|
||||
)
|
||||
}
|
||||
|
||||
private predicate isAlwaysReturnedArgument(Operand operand) {
|
||||
exists(AliasFunction f |
|
||||
f = operand.getUseInstruction().(CallInstruction).getStaticCallTarget() and
|
||||
f.parameterIsAlwaysReturned(operand.(PositionalArgumentOperand).getIndex())
|
||||
)
|
||||
}
|
||||
|
||||
private predicate isOnlyEscapesViaReturnArgument(Operand operand) {
|
||||
exists(AliasFunction f |
|
||||
f = operand.getUseInstruction().(CallInstruction).getStaticCallTarget() and
|
||||
f.parameterEscapesOnlyViaReturn(operand.(PositionalArgumentOperand).getIndex())
|
||||
)
|
||||
}
|
||||
|
||||
private predicate isNeverEscapesArgument(Operand operand) {
|
||||
exists(AliasFunction f |
|
||||
f = operand.getUseInstruction().(CallInstruction).getStaticCallTarget() and
|
||||
f.parameterNeverEscapes(operand.(PositionalArgumentOperand).getIndex())
|
||||
)
|
||||
}
|
||||
|
||||
private predicate resultReturned(Instruction instr, IntValue bitOffset) {
|
||||
operandReturned(instr.getAUse(), bitOffset)
|
||||
}
|
||||
|
||||
private predicate resultMayReachReturn(Instruction instr) {
|
||||
operandMayReachReturn(instr.getAUse())
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if any address held in the result of instruction `instr` escapes
|
||||
* outside the domain of the analysis.
|
||||
*/
|
||||
predicate resultEscapes(Instruction instr) {
|
||||
private predicate resultEscapesNonReturn(Instruction instr) {
|
||||
// The result escapes if it has at least one use that escapes.
|
||||
operandEscapes(instr.getAUse())
|
||||
operandEscapesNonReturn(instr.getAUse())
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -169,15 +282,11 @@ predicate resultEscapes(Instruction instr) {
|
||||
* domain of the analysis.
|
||||
*/
|
||||
private predicate automaticVariableAddressEscapes(IRAutomaticVariable var) {
|
||||
exists(FunctionIR funcIR |
|
||||
funcIR = var.getEnclosingFunctionIR() and
|
||||
// The variable's address escapes if the result of any
|
||||
// VariableAddressInstruction that computes the variable's address escapes.
|
||||
exists(VariableAddressInstruction instr |
|
||||
instr.getEnclosingFunctionIR() = funcIR and
|
||||
instr.getVariable() = var and
|
||||
resultEscapes(instr)
|
||||
)
|
||||
// The variable's address escapes if the result of any
|
||||
// VariableAddressInstruction that computes the variable's address escapes.
|
||||
exists(VariableAddressInstruction instr |
|
||||
instr.getVariable() = var and
|
||||
resultEscapesNonReturn(instr)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -207,7 +316,14 @@ predicate resultPointsTo(Instruction instr, IRVariable var, IntValue bitOffset)
|
||||
// If an operand is propagated, then the result points to the same variable,
|
||||
// offset by the bit offset from the propagation.
|
||||
resultPointsTo(operand.getDefinitionInstruction(), var, originalBitOffset) and
|
||||
operandIsPropagated(operand, propagatedBitOffset) and
|
||||
(
|
||||
operandIsPropagated(operand, propagatedBitOffset)
|
||||
or
|
||||
exists(CallInstruction ci, Instruction init |
|
||||
isArgumentForParameter(ci, operand, init) and
|
||||
resultReturned(init, propagatedBitOffset)
|
||||
)
|
||||
) and
|
||||
bitOffset = Ints::add(originalBitOffset, propagatedBitOffset)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
| test.cpp:66:30:66:36 | test.cpp:71:8:71:9 | AST only |
|
||||
| test.cpp:89:28:89:34 | test.cpp:92:8:92:14 | IR only |
|
||||
| test.cpp:100:13:100:18 | test.cpp:103:10:103:12 | AST only |
|
||||
| test.cpp:109:9:109:14 | test.cpp:110:10:110:12 | IR only |
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
| test.cpp:30:8:30:8 | Load: t | test.cpp:35:10:35:15 | Call: call to source |
|
||||
| test.cpp:31:8:31:8 | Load: c | test.cpp:36:13:36:18 | Call: call to source |
|
||||
| test.cpp:58:10:58:10 | Load: t | test.cpp:50:14:50:19 | Call: call to source |
|
||||
| test.cpp:71:8:71:9 | Load: x4 | test.cpp:66:30:66:36 | InitializeParameter: source1 |
|
||||
| test.cpp:76:8:76:9 | Load: u1 | test.cpp:75:7:75:8 | Uninitialized: definition of u1 |
|
||||
| test.cpp:84:8:84:18 | Load: ... ? ... : ... | test.cpp:83:7:83:8 | Uninitialized: definition of u2 |
|
||||
| test.cpp:86:8:86:9 | Load: i1 | test.cpp:83:7:83:8 | Uninitialized: definition of u2 |
|
||||
|
||||
@@ -1,5 +1,44 @@
|
||||
void CallByPointer(int* p);
|
||||
void CallByReference(int& r);
|
||||
int *GetPointer();
|
||||
int &GetReference();
|
||||
|
||||
int FetchFromPointer(int *no_p) {
|
||||
return *no_p;
|
||||
}
|
||||
|
||||
int FetchFromReference(int &no_r) {
|
||||
return no_r;
|
||||
}
|
||||
|
||||
int *ReturnPointer(int *no_p) {
|
||||
return no_p;
|
||||
}
|
||||
|
||||
int &ReturnReference(int &no_r) {
|
||||
return no_r;
|
||||
}
|
||||
|
||||
void CallByPointerParamEscape(int *no_p) {
|
||||
CallByPointer(no_p);
|
||||
}
|
||||
|
||||
void CallByReferenceParamEscape(int &no_r) {
|
||||
CallByReference(no_r);
|
||||
}
|
||||
|
||||
int *MaybeReturn(int *no_p, int *no_q, bool no_b) {
|
||||
if (no_b) {
|
||||
return no_p;
|
||||
} else {
|
||||
return no_q;
|
||||
}
|
||||
}
|
||||
|
||||
int &EscapeAndReturn(int &no_r) {
|
||||
CallByReference(no_r);
|
||||
return no_r;
|
||||
}
|
||||
|
||||
struct Point {
|
||||
float x;
|
||||
@@ -27,6 +66,40 @@ struct Derived : Intermediate1, Intermediate2 {
|
||||
float d;
|
||||
};
|
||||
|
||||
class C;
|
||||
|
||||
void CEscapes(C *no_c);
|
||||
|
||||
class C {
|
||||
public:
|
||||
void ThisEscapes() {
|
||||
CEscapes(this);
|
||||
}
|
||||
|
||||
C *ThisReturned() {
|
||||
return this;
|
||||
}
|
||||
|
||||
virtual C *Overridden() {
|
||||
CEscapes(this);
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
class OverrideReturns : C {
|
||||
public:
|
||||
virtual C *Overridden() {
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
class OverrideNone : C {
|
||||
public:
|
||||
virtual C *Overridden() {
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
void Escape()
|
||||
{
|
||||
int no_result;
|
||||
@@ -95,4 +168,86 @@ void Escape()
|
||||
|
||||
int passByRef;
|
||||
CallByReference(passByRef);
|
||||
|
||||
int no_ssa_passByPtr;
|
||||
FetchFromPointer(&no_ssa_passByPtr);
|
||||
|
||||
int no_ssa_passByRef;
|
||||
FetchFromReference(no_ssa_passByRef);
|
||||
|
||||
int no_ssa_passByPtr_ret;
|
||||
FetchFromPointer(&no_ssa_passByPtr_ret);
|
||||
|
||||
int no_ssa_passByRef_ret;
|
||||
FetchFromReference(no_ssa_passByRef_ret);
|
||||
|
||||
int passByPtr2;
|
||||
CallByPointerParamEscape(&passByPtr2);
|
||||
|
||||
int passByRef2;
|
||||
CallByReferenceParamEscape(passByRef2);
|
||||
|
||||
int passByPtr3;
|
||||
CallByPointerParamEscape(ReturnPointer(&passByPtr3));
|
||||
|
||||
int passByRef3;
|
||||
CallByReferenceParamEscape(ReturnReference(passByRef3));
|
||||
|
||||
int no_ssa_passByPtr4;
|
||||
int no_ssa_passByPtr5;
|
||||
bool no_b2 = false;
|
||||
MaybeReturn(&no_ssa_passByPtr4, &no_ssa_passByPtr5, no_b2);
|
||||
|
||||
int passByRef6;
|
||||
EscapeAndReturn(passByRef6);
|
||||
|
||||
int no_ssa_passByRef7;
|
||||
ReturnReference(no_ssa_passByRef7);
|
||||
|
||||
C no_ssa_c;
|
||||
|
||||
no_ssa_c.ThisReturned();
|
||||
|
||||
C c;
|
||||
|
||||
c.ThisEscapes();
|
||||
|
||||
C c2;
|
||||
|
||||
CEscapes(&c2);
|
||||
|
||||
C c3;
|
||||
|
||||
c3.ThisReturned()->ThisEscapes();
|
||||
|
||||
C c4;
|
||||
|
||||
CEscapes(c4.ThisReturned());
|
||||
|
||||
C c5;
|
||||
|
||||
c5.Overridden();
|
||||
|
||||
OverrideReturns or1;
|
||||
or1.Overridden();
|
||||
|
||||
OverrideReturns or2;
|
||||
CEscapes(or2.Overridden());
|
||||
|
||||
OverrideNone on1;
|
||||
on1.Overridden();
|
||||
|
||||
OverrideNone on2;
|
||||
CEscapes(on2.Overridden());
|
||||
|
||||
int condEscape1, condEscape2;
|
||||
|
||||
int *no_condTemp;
|
||||
if(GetPointer()) {
|
||||
no_condTemp = &condEscape1;
|
||||
} else {
|
||||
no_condTemp = &condEscape2;
|
||||
}
|
||||
CallByPointer(no_condTemp);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,108 +1,47 @@
|
||||
| escape.cpp:32:9:32:17 | VariableAddress[no_result] | no_result+0:0 |
|
||||
| escape.cpp:33:9:33:11 | VariableAddress[no_] | no_+0:0 |
|
||||
| escape.cpp:35:5:35:7 | VariableAddress[no_] | no_+0:0 |
|
||||
| escape.cpp:36:5:36:7 | VariableAddress[no_] | no_+0:0 |
|
||||
| escape.cpp:36:11:36:13 | VariableAddress[no_] | no_+0:0 |
|
||||
| escape.cpp:37:5:37:13 | VariableAddress[no_result] | no_result+0:0 |
|
||||
| escape.cpp:37:17:37:19 | VariableAddress[no_] | no_+0:0 |
|
||||
| escape.cpp:38:5:38:13 | VariableAddress[no_result] | no_result+0:0 |
|
||||
| escape.cpp:38:19:38:21 | VariableAddress[no_] | no_+0:0 |
|
||||
| escape.cpp:40:5:40:7 | VariableAddress[no_] | no_+0:0 |
|
||||
| escape.cpp:41:6:41:8 | VariableAddress[no_] | no_+0:0 |
|
||||
| escape.cpp:42:5:42:13 | VariableAddress[no_result] | no_result+0:0 |
|
||||
| escape.cpp:42:19:42:28 | PointerAdd[4] | no_+0:0 |
|
||||
| escape.cpp:42:21:42:23 | VariableAddress[no_] | no_+0:0 |
|
||||
| escape.cpp:43:5:43:13 | VariableAddress[no_result] | no_result+0:0 |
|
||||
| escape.cpp:43:19:43:28 | PointerSub[4] | no_+0:0 |
|
||||
| escape.cpp:43:21:43:23 | VariableAddress[no_] | no_+0:0 |
|
||||
| escape.cpp:44:5:44:13 | VariableAddress[no_result] | no_result+0:0 |
|
||||
| escape.cpp:44:19:44:26 | PointerAdd[4] | no_+0:0 |
|
||||
| escape.cpp:44:24:44:26 | VariableAddress[no_] | no_+0:0 |
|
||||
| escape.cpp:45:10:45:12 | VariableAddress[no_] | no_+0:0 |
|
||||
| escape.cpp:47:13:47:15 | VariableAddress[no_] | no_+0:0 |
|
||||
| escape.cpp:50:15:50:17 | VariableAddress[no_] | no_+0:0 |
|
||||
| escape.cpp:51:10:51:12 | VariableAddress[no_] | no_+0:0 |
|
||||
| escape.cpp:51:16:51:18 | VariableAddress[no_] | no_+0:0 |
|
||||
| escape.cpp:51:22:51:24 | VariableAddress[no_] | no_+0:0 |
|
||||
| escape.cpp:54:10:54:12 | VariableAddress[no_] | no_+0:0 |
|
||||
| escape.cpp:56:13:56:15 | VariableAddress[no_] | no_+0:0 |
|
||||
| escape.cpp:59:9:59:16 | VariableAddress[no_Array] | no_Array+0:0 |
|
||||
| escape.cpp:60:5:60:12 | VariableAddress[no_Array] | no_Array+0:0 |
|
||||
| escape.cpp:61:5:61:18 | Convert | no_Array+0:0 |
|
||||
| escape.cpp:61:11:61:18 | Convert | no_Array+0:0 |
|
||||
| escape.cpp:61:11:61:18 | VariableAddress[no_Array] | no_Array+0:0 |
|
||||
| escape.cpp:62:5:62:12 | Convert | no_Array+0:0 |
|
||||
| escape.cpp:62:5:62:12 | VariableAddress[no_Array] | no_Array+0:0 |
|
||||
| escape.cpp:62:5:62:15 | PointerAdd[4] | no_Array+20:0 |
|
||||
| escape.cpp:63:5:63:15 | PointerAdd[4] | no_Array+20:0 |
|
||||
| escape.cpp:63:7:63:14 | Convert | no_Array+0:0 |
|
||||
| escape.cpp:63:7:63:14 | VariableAddress[no_Array] | no_Array+0:0 |
|
||||
| escape.cpp:64:5:64:13 | VariableAddress[no_result] | no_result+0:0 |
|
||||
| escape.cpp:64:17:64:24 | Convert | no_Array+0:0 |
|
||||
| escape.cpp:64:17:64:24 | VariableAddress[no_Array] | no_Array+0:0 |
|
||||
| escape.cpp:64:17:64:27 | PointerAdd[4] | no_Array+20:0 |
|
||||
| escape.cpp:65:5:65:13 | VariableAddress[no_result] | no_result+0:0 |
|
||||
| escape.cpp:65:17:65:27 | PointerAdd[4] | no_Array+20:0 |
|
||||
| escape.cpp:65:19:65:26 | Convert | no_Array+0:0 |
|
||||
| escape.cpp:65:19:65:26 | VariableAddress[no_Array] | no_Array+0:0 |
|
||||
| escape.cpp:67:11:67:18 | VariableAddress[no_Point] | no_Point+0:0 |
|
||||
| escape.cpp:67:21:67:32 | FieldAddress[x] | no_Point+0:0 |
|
||||
| escape.cpp:67:21:67:32 | FieldAddress[y] | no_Point+4:0 |
|
||||
| escape.cpp:67:21:67:32 | FieldAddress[z] | no_Point+8:0 |
|
||||
| escape.cpp:68:11:68:14 | VariableAddress[no_x] | no_x+0:0 |
|
||||
| escape.cpp:68:18:68:25 | VariableAddress[no_Point] | no_Point+0:0 |
|
||||
| escape.cpp:68:27:68:27 | FieldAddress[x] | no_Point+0:0 |
|
||||
| escape.cpp:69:5:69:12 | VariableAddress[no_Point] | no_Point+0:0 |
|
||||
| escape.cpp:69:14:69:14 | FieldAddress[y] | no_Point+4:0 |
|
||||
| escape.cpp:69:18:69:21 | VariableAddress[no_x] | no_x+0:0 |
|
||||
| escape.cpp:70:11:70:14 | VariableAddress[no_y] | no_y+0:0 |
|
||||
| escape.cpp:70:20:70:27 | VariableAddress[no_Point] | no_Point+0:0 |
|
||||
| escape.cpp:70:31:70:31 | FieldAddress[y] | no_Point+4:0 |
|
||||
| escape.cpp:71:7:71:14 | VariableAddress[no_Point] | no_Point+0:0 |
|
||||
| escape.cpp:71:18:71:18 | FieldAddress[y] | no_Point+4:0 |
|
||||
| escape.cpp:71:22:71:25 | VariableAddress[no_y] | no_y+0:0 |
|
||||
| escape.cpp:72:11:72:14 | VariableAddress[no_z] | no_z+0:0 |
|
||||
| escape.cpp:72:21:72:28 | VariableAddress[no_Point] | no_Point+0:0 |
|
||||
| escape.cpp:72:30:72:30 | FieldAddress[z] | no_Point+8:0 |
|
||||
| escape.cpp:73:8:73:15 | VariableAddress[no_Point] | no_Point+0:0 |
|
||||
| escape.cpp:73:17:73:17 | FieldAddress[z] | no_Point+8:0 |
|
||||
| escape.cpp:73:22:73:25 | VariableAddress[no_z] | no_z+0:0 |
|
||||
| escape.cpp:75:13:75:22 | VariableAddress[no_Derived] | no_Derived+0:0 |
|
||||
| escape.cpp:76:5:76:14 | ConvertToBase[Derived : Intermediate1] | no_Derived+0:0 |
|
||||
| escape.cpp:76:5:76:14 | ConvertToBase[Intermediate1 : Base] | no_Derived+0:0 |
|
||||
| escape.cpp:76:5:76:14 | VariableAddress[no_Derived] | no_Derived+0:0 |
|
||||
| escape.cpp:76:16:76:16 | FieldAddress[b] | no_Derived+0:0 |
|
||||
| escape.cpp:77:11:77:14 | VariableAddress[no_b] | no_b+0:0 |
|
||||
| escape.cpp:77:18:77:27 | ConvertToBase[Derived : Intermediate1] | no_Derived+0:0 |
|
||||
| escape.cpp:77:18:77:27 | ConvertToBase[Intermediate1 : Base] | no_Derived+0:0 |
|
||||
| escape.cpp:77:18:77:27 | VariableAddress[no_Derived] | no_Derived+0:0 |
|
||||
| escape.cpp:77:29:77:29 | FieldAddress[b] | no_Derived+0:0 |
|
||||
| escape.cpp:78:5:78:14 | ConvertToBase[Derived : Intermediate2] | no_Derived+12:0 |
|
||||
| escape.cpp:78:5:78:14 | VariableAddress[no_Derived] | no_Derived+0:0 |
|
||||
| escape.cpp:78:16:78:17 | FieldAddress[i2] | no_Derived+16:0 |
|
||||
| escape.cpp:79:11:79:15 | VariableAddress[no_i2] | no_i2+0:0 |
|
||||
| escape.cpp:79:19:79:28 | ConvertToBase[Derived : Intermediate2] | no_Derived+12:0 |
|
||||
| escape.cpp:79:19:79:28 | VariableAddress[no_Derived] | no_Derived+0:0 |
|
||||
| escape.cpp:79:30:79:31 | FieldAddress[i2] | no_Derived+16:0 |
|
||||
| escape.cpp:81:9:81:21 | VariableAddress[no_ssa_addrOf] | no_ssa_addrOf+0:0 |
|
||||
| escape.cpp:82:10:82:13 | VariableAddress[no_p] | no_p+0:0 |
|
||||
| escape.cpp:82:17:82:30 | Store | no_ssa_addrOf+0:0 |
|
||||
| escape.cpp:82:18:82:30 | VariableAddress[no_ssa_addrOf] | no_ssa_addrOf+0:0 |
|
||||
| escape.cpp:84:9:84:20 | VariableAddress[no_ssa_refTo] | no_ssa_refTo+0:0 |
|
||||
| escape.cpp:85:10:85:13 | VariableAddress[no_r] | no_r+0:0 |
|
||||
| escape.cpp:85:17:85:28 | Store | no_ssa_refTo+0:0 |
|
||||
| escape.cpp:85:17:85:28 | VariableAddress[no_ssa_refTo] | no_ssa_refTo+0:0 |
|
||||
| escape.cpp:87:9:87:32 | VariableAddress[no_ssa_refToArrayElement] | no_ssa_refToArrayElement+0:0 |
|
||||
| escape.cpp:88:10:88:15 | VariableAddress[no_rae] | no_rae+0:0 |
|
||||
| escape.cpp:88:19:88:42 | Convert | no_ssa_refToArrayElement+0:0 |
|
||||
| escape.cpp:88:19:88:42 | VariableAddress[no_ssa_refToArrayElement] | no_ssa_refToArrayElement+0:0 |
|
||||
| escape.cpp:88:19:88:45 | PointerAdd[4] | no_ssa_refToArrayElement+20:0 |
|
||||
| escape.cpp:88:19:88:45 | Store | no_ssa_refToArrayElement+20:0 |
|
||||
| escape.cpp:90:9:90:25 | VariableAddress[no_ssa_refToArray] | no_ssa_refToArray+0:0 |
|
||||
| escape.cpp:91:11:91:15 | VariableAddress[no_ra] | no_ra+0:0 |
|
||||
| escape.cpp:91:24:91:40 | Store | no_ssa_refToArray+0:0 |
|
||||
| escape.cpp:91:24:91:40 | VariableAddress[no_ssa_refToArray] | no_ssa_refToArray+0:0 |
|
||||
| escape.cpp:93:9:93:17 | VariableAddress[passByPtr] | passByPtr+0:0 |
|
||||
| escape.cpp:94:20:94:28 | VariableAddress[passByPtr] | passByPtr+0:0 |
|
||||
| escape.cpp:96:9:96:17 | VariableAddress[passByRef] | passByRef+0:0 |
|
||||
| escape.cpp:97:21:97:29 | VariableAddress[passByRef] | passByRef+0:0 |
|
||||
| escape.cpp:115:19:115:28 | PointerAdd[4] | no_+0:0 | no_+0:0 |
|
||||
| escape.cpp:116:19:116:28 | PointerSub[4] | no_+0:0 | no_+0:0 |
|
||||
| escape.cpp:117:19:117:26 | PointerAdd[4] | no_+0:0 | no_+0:0 |
|
||||
| escape.cpp:134:5:134:18 | Convert | no_Array+0:0 | no_Array+0:0 |
|
||||
| escape.cpp:134:11:134:18 | Convert | no_Array+0:0 | no_Array+0:0 |
|
||||
| escape.cpp:135:5:135:12 | Convert | no_Array+0:0 | no_Array+0:0 |
|
||||
| escape.cpp:135:5:135:15 | PointerAdd[4] | no_Array+20:0 | no_Array+20:0 |
|
||||
| escape.cpp:136:5:136:15 | PointerAdd[4] | no_Array+20:0 | no_Array+20:0 |
|
||||
| escape.cpp:136:7:136:14 | Convert | no_Array+0:0 | no_Array+0:0 |
|
||||
| escape.cpp:137:17:137:24 | Convert | no_Array+0:0 | no_Array+0:0 |
|
||||
| escape.cpp:137:17:137:27 | PointerAdd[4] | no_Array+20:0 | no_Array+20:0 |
|
||||
| escape.cpp:138:17:138:27 | PointerAdd[4] | no_Array+20:0 | no_Array+20:0 |
|
||||
| escape.cpp:138:19:138:26 | Convert | no_Array+0:0 | no_Array+0:0 |
|
||||
| escape.cpp:140:21:140:32 | FieldAddress[x] | no_Point+0:0 | no_Point+0:0 |
|
||||
| escape.cpp:140:21:140:32 | FieldAddress[y] | no_Point+4:0 | no_Point+4:0 |
|
||||
| escape.cpp:140:21:140:32 | FieldAddress[z] | no_Point+8:0 | no_Point+8:0 |
|
||||
| escape.cpp:141:27:141:27 | FieldAddress[x] | no_Point+0:0 | no_Point+0:0 |
|
||||
| escape.cpp:142:14:142:14 | FieldAddress[y] | no_Point+4:0 | no_Point+4:0 |
|
||||
| escape.cpp:143:31:143:31 | FieldAddress[y] | no_Point+4:0 | no_Point+4:0 |
|
||||
| escape.cpp:144:18:144:18 | FieldAddress[y] | no_Point+4:0 | no_Point+4:0 |
|
||||
| escape.cpp:145:30:145:30 | FieldAddress[z] | no_Point+8:0 | no_Point+8:0 |
|
||||
| escape.cpp:146:17:146:17 | FieldAddress[z] | no_Point+8:0 | no_Point+8:0 |
|
||||
| escape.cpp:149:5:149:14 | ConvertToBase[Derived : Intermediate1] | no_Derived+0:0 | no_Derived+0:0 |
|
||||
| escape.cpp:149:5:149:14 | ConvertToBase[Intermediate1 : Base] | no_Derived+0:0 | no_Derived+0:0 |
|
||||
| escape.cpp:149:16:149:16 | FieldAddress[b] | no_Derived+0:0 | no_Derived+0:0 |
|
||||
| escape.cpp:150:18:150:27 | ConvertToBase[Derived : Intermediate1] | no_Derived+0:0 | no_Derived+0:0 |
|
||||
| escape.cpp:150:18:150:27 | ConvertToBase[Intermediate1 : Base] | no_Derived+0:0 | no_Derived+0:0 |
|
||||
| escape.cpp:150:29:150:29 | FieldAddress[b] | no_Derived+0:0 | no_Derived+0:0 |
|
||||
| escape.cpp:151:5:151:14 | ConvertToBase[Derived : Intermediate2] | no_Derived+12:0 | no_Derived+12:0 |
|
||||
| escape.cpp:151:16:151:17 | FieldAddress[i2] | no_Derived+16:0 | no_Derived+16:0 |
|
||||
| escape.cpp:152:19:152:28 | ConvertToBase[Derived : Intermediate2] | no_Derived+12:0 | no_Derived+12:0 |
|
||||
| escape.cpp:152:30:152:31 | FieldAddress[i2] | no_Derived+16:0 | no_Derived+16:0 |
|
||||
| escape.cpp:155:17:155:30 | Store | no_ssa_addrOf+0:0 | no_ssa_addrOf+0:0 |
|
||||
| escape.cpp:158:17:158:28 | Store | no_ssa_refTo+0:0 | no_ssa_refTo+0:0 |
|
||||
| escape.cpp:161:19:161:42 | Convert | no_ssa_refToArrayElement+0:0 | no_ssa_refToArrayElement+0:0 |
|
||||
| escape.cpp:161:19:161:45 | PointerAdd[4] | no_ssa_refToArrayElement+20:0 | no_ssa_refToArrayElement+20:0 |
|
||||
| escape.cpp:161:19:161:45 | Store | no_ssa_refToArrayElement+20:0 | no_ssa_refToArrayElement+20:0 |
|
||||
| escape.cpp:164:24:164:40 | Store | no_ssa_refToArray+0:0 | no_ssa_refToArray+0:0 |
|
||||
| escape.cpp:191:30:191:42 | Call | none | passByPtr3+0:0 |
|
||||
| escape.cpp:194:32:194:46 | Call | none | passByRef3+0:0 |
|
||||
| escape.cpp:202:5:202:19 | Call | none | passByRef6+0:0 |
|
||||
| escape.cpp:205:5:205:19 | Call | none | no_ssa_passByRef7+0:0 |
|
||||
| escape.cpp:209:14:209:25 | Call | none | no_ssa_c+0:0 |
|
||||
| escape.cpp:221:8:221:19 | Call | none | c3+0:0 |
|
||||
| escape.cpp:225:17:225:28 | Call | none | c4+0:0 |
|
||||
| escape.cpp:247:2:247:27 | Store | condEscape1+0:0 | condEscape1+0:0 |
|
||||
| escape.cpp:249:9:249:34 | Store | condEscape2+0:0 | condEscape2+0:0 |
|
||||
|
||||
@@ -1,11 +1,34 @@
|
||||
import default
|
||||
import semmle.code.cpp.ir.implementation.unaliased_ssa.internal.AliasAnalysis
|
||||
import semmle.code.cpp.ir.implementation.raw.IR
|
||||
import semmle.code.cpp.ir.implementation.unaliased_ssa.internal.AliasAnalysis as RawAA
|
||||
import semmle.code.cpp.ir.implementation.raw.IR as Raw
|
||||
import semmle.code.cpp.ir.implementation.aliased_ssa.internal.AliasAnalysis as UnAA
|
||||
import semmle.code.cpp.ir.implementation.unaliased_ssa.IR as Un
|
||||
import semmle.code.cpp.ir.implementation.unaliased_ssa.internal.SSAConstruction
|
||||
|
||||
from Instruction instr, string pointsTo
|
||||
from Raw::Instruction rawInstr, Un::Instruction unInstr, string rawPointsTo, string unPointsTo
|
||||
where
|
||||
exists(IRVariable var, int bitOffset |
|
||||
resultPointsTo(instr, var, bitOffset) and
|
||||
pointsTo = var.toString() + getBitOffsetString(bitOffset)
|
||||
rawInstr = getOldInstruction(unInstr) and
|
||||
not rawInstr instanceof Raw::VariableAddressInstruction and
|
||||
(
|
||||
exists(Variable var, int rawBitOffset, int unBitOffset |
|
||||
RawAA::resultPointsTo(rawInstr, Raw::getIRUserVariable(_, var), rawBitOffset) and
|
||||
rawPointsTo = var.toString() + RawAA::getBitOffsetString(rawBitOffset) and
|
||||
UnAA::resultPointsTo(unInstr, Un::getIRUserVariable(_, var), unBitOffset) and
|
||||
unPointsTo = var.toString() + UnAA::getBitOffsetString(unBitOffset)
|
||||
)
|
||||
or
|
||||
exists(Variable var, int unBitOffset |
|
||||
not RawAA::resultPointsTo(rawInstr, Raw::getIRUserVariable(_, var), _) and
|
||||
rawPointsTo = "none" and
|
||||
UnAA::resultPointsTo(unInstr, Un::getIRUserVariable(_, var), unBitOffset) and
|
||||
unPointsTo = var.toString() + UnAA::getBitOffsetString(unBitOffset)
|
||||
)
|
||||
or
|
||||
exists(Variable var, int rawBitOffset |
|
||||
RawAA::resultPointsTo(rawInstr, Raw::getIRUserVariable(_, var), rawBitOffset) and
|
||||
rawPointsTo = var.toString() + RawAA::getBitOffsetString(rawBitOffset) and
|
||||
not UnAA::resultPointsTo(unInstr, Un::getIRUserVariable(_, var), _) and
|
||||
unPointsTo = "none"
|
||||
)
|
||||
)
|
||||
select instr.getLocation().toString(), instr.getOperationString(), pointsTo
|
||||
select rawInstr.getLocation().toString(), rawInstr.getOperationString(), rawPointsTo, unPointsTo
|
||||
|
||||
@@ -57,7 +57,7 @@ namespace Semmle.Util
|
||||
/// Finds the path for the program <paramref name="prog"/> based on the
|
||||
/// <code>PATH</code> environment variable, and in the case of Windows the
|
||||
/// <code>PATHEXT</code> environment variable.
|
||||
///
|
||||
///
|
||||
/// Returns <code>null</code> of no path can be found.
|
||||
/// </summary>
|
||||
public static string FindProgramOnPath(string prog)
|
||||
|
||||
@@ -7,7 +7,7 @@ public class HttpHandler : IHttpHandler
|
||||
public void ProcessRequest(HttpContext ctx)
|
||||
{
|
||||
string format = ctx.Request.QueryString["nameformat"];
|
||||
|
||||
|
||||
// BAD: Uncontrolled format string.
|
||||
FormattedName = string.Format(format, Surname, Forenames);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ module DataFlow {
|
||||
private import semmle.code.csharp.dataflow.CallContext
|
||||
private import semmle.code.csharp.dataflow.DelegateDataFlow
|
||||
private import semmle.code.csharp.dataflow.LibraryTypeDataFlow
|
||||
private import semmle.code.csharp.frameworks.EntityFramework
|
||||
private import Internal::Cached
|
||||
private import dotnet
|
||||
private import cil
|
||||
@@ -103,6 +104,15 @@ module DataFlow {
|
||||
|
||||
predicate localFlowStep = Internal::LocalFlow::step/2;
|
||||
|
||||
/**
|
||||
* A dataflow node that jumps between callables. This can be extended in framework code
|
||||
* to add additional dataflow steps.
|
||||
*/
|
||||
abstract class NonLocalJumpNode extends Node {
|
||||
/** Gets a successor node that is potentially in another callable. */
|
||||
abstract Node getAJumpSuccessor(boolean preservesValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* A data flow node augmented with a call context and a configuration. Only
|
||||
* nodes that are reachable from a source, and which can reach a sink, are
|
||||
@@ -675,7 +685,7 @@ module DataFlow {
|
||||
CilCall() {
|
||||
call = this and
|
||||
// No need to include calls that are compiled from source
|
||||
not call.getImplementation().getMethod().compiledFromSource()
|
||||
cilCallWithoutSource(call)
|
||||
}
|
||||
|
||||
override DotNet::Callable getARuntimeTarget() {
|
||||
@@ -1062,13 +1072,20 @@ module DataFlow {
|
||||
(isSuccessor = true or isSuccessor = false)
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
private predicate localFlowStep0(Node pred, Node succ, Configuration config, DotNet::Callable c) {
|
||||
config.isAdditionalFlowStep(pred, succ) and
|
||||
pred.getEnclosingCallable() = c
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if data may flow in one local step from `pred` to `succ`.
|
||||
*/
|
||||
bindingset[config]
|
||||
predicate localFlowStep(Node pred, Node succ, Configuration config) {
|
||||
localFlowStep(pred, succ) or
|
||||
config.isAdditionalFlowStep(pred, succ)
|
||||
localFlowStep(pred, succ)
|
||||
or
|
||||
localFlowStep0(pred, succ, config, succ.getEnclosingCallable())
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1079,7 +1096,7 @@ module DataFlow {
|
||||
Pruning::nodeCand(node, config) and
|
||||
(
|
||||
config.isSource(node) or
|
||||
jumpStep(_, node) or
|
||||
jumpStep(_, node, config) or
|
||||
node instanceof ParameterNode or
|
||||
node instanceof OutNode or
|
||||
node instanceof NormalReturnNode or
|
||||
@@ -1095,7 +1112,7 @@ module DataFlow {
|
||||
predicate localFlowExit(Node node, Configuration config) {
|
||||
Pruning::nodeCand(node, config) and
|
||||
(
|
||||
jumpStep(node, _)
|
||||
jumpStep(node, _, config)
|
||||
or
|
||||
node instanceof ArgumentNode
|
||||
or
|
||||
@@ -1145,6 +1162,26 @@ module DataFlow {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the additional step from `node1` to `node2` jumps between callables.
|
||||
*/
|
||||
pragma[noinline]
|
||||
private predicate additionalJumpStep(Node node1, Node node2, Configuration config) {
|
||||
config.isAdditionalFlowStep(node1, node2) and
|
||||
node1.getEnclosingCallable() != node2.getEnclosingCallable()
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `pred` can flow to `succ`, by jumping from one callable to
|
||||
* another.
|
||||
*/
|
||||
bindingset[config]
|
||||
private predicate jumpStep(Node node1, Node node2, Configuration config) {
|
||||
additionalJumpStep(node1, node2, config)
|
||||
or
|
||||
jumpStepNoConfig(node1, node2)
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides predicates for pruning the data flow graph, by only including
|
||||
* nodes that may potentially be reached in flow from some source to some
|
||||
@@ -1162,7 +1199,7 @@ module DataFlow {
|
||||
or
|
||||
exists(Node mid | nodeCandFwd1(mid, config) | LocalFlow::localFlowStep(mid, node, config))
|
||||
or
|
||||
exists(Node mid | nodeCandFwd1(mid, config) | jumpStep(mid, node))
|
||||
exists(Node mid | nodeCandFwd1(mid, config) | jumpStep(mid, node, config))
|
||||
or
|
||||
exists(ArgumentNode arg | nodeCandFwd1(arg, config) |
|
||||
flowIntoCallableStep(_, arg, node, _, config)
|
||||
@@ -1183,7 +1220,7 @@ module DataFlow {
|
||||
or
|
||||
exists(Node mid | nodeCand1(mid, config) | LocalFlow::localFlowStep(node, mid, config))
|
||||
or
|
||||
exists(Node mid | nodeCand1(mid, config) | jumpStep(node, mid))
|
||||
exists(Node mid | nodeCand1(mid, config) | jumpStep(node, mid, config))
|
||||
or
|
||||
exists(ParameterNode p | nodeCand1(p, config) |
|
||||
flowIntoCallableStep(_, node, p, _, config)
|
||||
@@ -1238,7 +1275,7 @@ module DataFlow {
|
||||
pragma[noinline]
|
||||
private predicate jumpStepCand1(Node pred, Node succ, Configuration config) {
|
||||
nodeCand1(succ, config) and
|
||||
jumpStep(pred, succ)
|
||||
jumpStep(pred, succ, config)
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
@@ -1333,7 +1370,7 @@ module DataFlow {
|
||||
or
|
||||
nodeCandFwd2(node, _, config) and
|
||||
exists(Node mid | nodeCand2(mid, _, config) |
|
||||
jumpStep(node, mid) and
|
||||
jumpStep(node, mid, config) and
|
||||
isReturned = false
|
||||
)
|
||||
or
|
||||
@@ -1454,16 +1491,32 @@ module DataFlow {
|
||||
|
||||
/**
|
||||
* Holds if `pred` can flow to `succ`, by jumping from one callable to
|
||||
* another.
|
||||
* another. Additional steps specified by the configuration are *not* taken into account.
|
||||
*/
|
||||
cached
|
||||
predicate jumpStep(ExprNode pred, ExprNode succ) {
|
||||
exists(FieldLike fl, FieldLikeRead flr | fl.isStatic() |
|
||||
fl.getAnAssignedValue() = pred.getExpr() and
|
||||
predicate jumpStepNoConfig(ExprNode pred, ExprNode succ) {
|
||||
pred.(NonLocalJumpNode).getAJumpSuccessor(true) = succ
|
||||
}
|
||||
|
||||
/** A dataflow node that has field-like dataflow. */
|
||||
private class FieldLikeJumpNode extends NonLocalJumpNode, ExprNode {
|
||||
FieldLike fl;
|
||||
|
||||
FieldLikeRead flr;
|
||||
|
||||
ExprNode succ;
|
||||
|
||||
FieldLikeJumpNode() {
|
||||
fl.isStatic() and
|
||||
fl.getAnAssignedValue() = this.getExpr() and
|
||||
fl.getAnAccess() = flr and
|
||||
flr = succ.getExpr() and
|
||||
hasNonlocalValue(flr)
|
||||
)
|
||||
}
|
||||
|
||||
override ExprNode getAJumpSuccessor(boolean preservesValue) {
|
||||
result = succ and preservesValue = true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1538,6 +1591,11 @@ module DataFlow {
|
||||
ret.flowsOut(out.getDefinition()) and
|
||||
call.asExpr() = out.getCall()
|
||||
}
|
||||
|
||||
cached
|
||||
predicate cilCallWithoutSource(CIL::Call call) {
|
||||
not call.getImplementation().getMethod().compiledFromSource()
|
||||
}
|
||||
}
|
||||
|
||||
private newtype TContext =
|
||||
@@ -1600,9 +1658,7 @@ module DataFlow {
|
||||
/**
|
||||
* A data flow context describing flow into a callable via a call argument.
|
||||
*/
|
||||
abstract private class ArgumentContext extends Context {
|
||||
abstract DotNet::Expr getCall();
|
||||
}
|
||||
abstract private class ArgumentContext extends Context { abstract DotNet::Expr getCall(); }
|
||||
|
||||
/**
|
||||
* A data flow context describing flow into a callable via an explicit call argument.
|
||||
@@ -1736,7 +1792,7 @@ module DataFlow {
|
||||
ctx = mid.getContext() and
|
||||
LocalFlow::localFlowBigStep(mid.getNode(), node, mid.getConfiguration())
|
||||
or
|
||||
jumpStep(mid.getNode(), node) and
|
||||
jumpStep(mid.getNode(), node, mid.getConfiguration()) and
|
||||
ctx instanceof NoContext
|
||||
or
|
||||
flowIntoCallable(mid, node, ctx)
|
||||
|
||||
@@ -172,7 +172,7 @@ private predicate flowsFrom(
|
||||
// Flow through static field or property
|
||||
exists(DataFlow::Node mid |
|
||||
flowsFrom(sink, mid, _, _) and
|
||||
Cached::jumpStep(node, mid) and
|
||||
Cached::jumpStepNoConfig(node, mid) and
|
||||
isReturned = false and
|
||||
lastCall instanceof EmptyCallContext
|
||||
)
|
||||
|
||||
@@ -80,8 +80,11 @@ module TaintTracking {
|
||||
predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) { none() }
|
||||
|
||||
final override predicate isAdditionalFlowStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
isAdditionalTaintStep(pred, succ) or
|
||||
isAdditionalTaintStep(pred, succ)
|
||||
or
|
||||
localAdditionalTaintStep(pred, succ)
|
||||
or
|
||||
succ = pred.(DataFlow::NonLocalJumpNode).getAJumpSuccessor(false)
|
||||
}
|
||||
|
||||
final override predicate isAdditionalFlowStepIntoCall(
|
||||
|
||||
@@ -222,11 +222,43 @@ private module Internal {
|
||||
}
|
||||
}
|
||||
|
||||
private class FieldOrProperty extends Assignable {
|
||||
FieldOrProperty() {
|
||||
this instanceof Field or
|
||||
this instanceof Property
|
||||
private class DynamicFieldOrProperty extends Assignable {
|
||||
DynamicFieldOrProperty() {
|
||||
(
|
||||
this instanceof Field or
|
||||
this instanceof Property
|
||||
) and
|
||||
this.getName() = any(DynamicMemberAccess dma).getLateBoundTargetName()
|
||||
}
|
||||
|
||||
predicate isMemberOf(string name, ValueOrRefType t) {
|
||||
name = this.getName() and t.hasMember(this)
|
||||
}
|
||||
}
|
||||
|
||||
private class TypeWithDynamicFieldOrProperty extends ValueOrRefType {
|
||||
DynamicFieldOrProperty fp;
|
||||
|
||||
TypeWithDynamicFieldOrProperty() { fp.isMemberOf(_, this) }
|
||||
|
||||
predicate isImplicitlyConvertibleTo(string name, Type t) {
|
||||
name = fp.getName() and
|
||||
this.isImplicitlyConvertibleTo(t)
|
||||
}
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
private predicate isPossibleDynamicMemberAccessQualifierType(
|
||||
DynamicMemberAccess dma, string name, TypeWithDynamicFieldOrProperty t
|
||||
) {
|
||||
exists(Type qt, boolean isExact |
|
||||
qt = getAPossibleType(dma.getQualifier(), isExact) and
|
||||
name = dma.getLateBoundTargetName()
|
||||
|
|
||||
isExact = true and t = qt
|
||||
or
|
||||
isExact = false and t.isImplicitlyConvertibleTo(name, qt)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -235,14 +267,10 @@ private module Internal {
|
||||
* corresponding to the type of a relevant field or property are included.
|
||||
*/
|
||||
private Type getAPossibleType(Expr e, boolean isExact) {
|
||||
exists(ValueOrRefType qualifierType, FieldOrProperty fp, boolean qualifierTypeIsExact |
|
||||
qualifierType = getAPossibleTypeDynamicMemberAccessQualifier(e, qualifierTypeIsExact, fp)
|
||||
exists(DynamicFieldOrProperty fp, string name, TypeWithDynamicFieldOrProperty t |
|
||||
isPossibleDynamicMemberAccessQualifierType(e, name, t) and
|
||||
fp.isMemberOf(name, t)
|
||||
|
|
||||
(
|
||||
if qualifierTypeIsExact = true
|
||||
then qualifierType.hasMember(fp)
|
||||
else fp.getDeclaringType().isImplicitlyConvertibleTo(qualifierType)
|
||||
) and
|
||||
result = fp.getType() and
|
||||
isExact = false
|
||||
)
|
||||
@@ -251,13 +279,6 @@ private module Internal {
|
||||
result = getASourceType(e, isExact)
|
||||
}
|
||||
|
||||
private Type getAPossibleTypeDynamicMemberAccessQualifier(
|
||||
DynamicMemberAccess dma, boolean isExact, FieldOrProperty fp
|
||||
) {
|
||||
result = getAPossibleType(dma.getQualifier(), isExact) and
|
||||
fp.getName() = dma.getLateBoundTargetName()
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the predicate `getASourceType()` for finding all relevant source
|
||||
* types for a given expression.
|
||||
@@ -799,22 +820,14 @@ private module Internal {
|
||||
// conflicting types (for example, `Tuple<int, string>` is considered
|
||||
// compatible with `Tuple<T, T>`).
|
||||
override RuntimeCallable getADynamicTarget() {
|
||||
// Condition 1
|
||||
result = getADynamicTargetCandidate() and
|
||||
// Condition 2
|
||||
forall(int i | i in [0 .. getNumberOfArguments() - 1] |
|
||||
result = getADynamicTargetCandidateWithCompatibleArg(i)
|
||||
)
|
||||
result = this.getADynamicTarget(this.getNumberOfArguments() - 1)
|
||||
}
|
||||
|
||||
private RuntimeCallable getADynamicTargetCandidateWithCompatibleArg(int i) {
|
||||
result = getADynamicTargetCandidateWithCompatibleArg1(i) or
|
||||
result = getADynamicTargetCandidateWithCompatibleArg2(i)
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
private RuntimeCallable getADynamicTargetCandidateWithCompatibleArg1(int i) {
|
||||
result = this.getADynamicTargetCandidate() and
|
||||
private RuntimeCallable getADynamicTarget(int i) {
|
||||
i = -1 and
|
||||
result = this.getADynamicTargetCandidate()
|
||||
or
|
||||
result = this.getADynamicTarget(i - 1) and
|
||||
exists(Type parameterType, Type argumentType |
|
||||
parameterType = this.getAParameterType(result, i) and
|
||||
argumentType = getAPossibleType(this.getArgument(i), _)
|
||||
@@ -827,6 +840,12 @@ private module Internal {
|
||||
or
|
||||
reflectionOrDynamicArgEqualsParamModuloTypeParameters(argumentType, parameterType)
|
||||
)
|
||||
or
|
||||
result = this.getADynamicTarget(i - 1) and
|
||||
exists(Type parameterType, Type t | parameterType = this.getAParameterType(result, i) |
|
||||
this.argumentConvConstExpr(i, t) and
|
||||
t.isImplicitlyConvertibleTo(parameterType)
|
||||
)
|
||||
}
|
||||
|
||||
private Type getAParameterType(RuntimeCallable c, int i) {
|
||||
@@ -840,15 +859,6 @@ private module Internal {
|
||||
)
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
private RuntimeCallable getADynamicTargetCandidateWithCompatibleArg2(int i) {
|
||||
result = this.getADynamicTargetCandidate() and
|
||||
exists(Type parameterType, Type t | parameterType = this.getAParameterType(result, i) |
|
||||
this.argumentConvConstExpr(i, t) and
|
||||
t.isImplicitlyConvertibleTo(parameterType)
|
||||
)
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
private predicate argumentConvConstExpr(int i, Type t) {
|
||||
convConstantExpr(this.getArgument(i), t)
|
||||
@@ -954,7 +964,7 @@ private module Internal {
|
||||
*/
|
||||
private predicate isReflectionOrDynamicCallArgumentWithTypeParameters(Type argType, Type paramType) {
|
||||
exists(DispatchReflectionOrDynamicCall call, Parameter p, int i, int j |
|
||||
p = call.getAStaticTarget().getParameter(i) and
|
||||
p = call.getADynamicTargetCandidate().getParameter(i) and
|
||||
(
|
||||
if p.isParams()
|
||||
then (
|
||||
|
||||
@@ -654,6 +654,14 @@ class QualifiableExpr extends Expr, @qualifiable_expr {
|
||||
predicate isConditional() { conditional_access(this) }
|
||||
}
|
||||
|
||||
private Expr getAnAssignOrForeachChild() {
|
||||
result = any(AssignExpr e).getLValue()
|
||||
or
|
||||
result = any(ForeachStmt fs).getVariableDeclTuple()
|
||||
or
|
||||
result = getAnAssignOrForeachChild().getAChildExpr()
|
||||
}
|
||||
|
||||
/**
|
||||
* An expression representing a tuple, for example
|
||||
* `(1, 2)` on line 2 or `(var x, var y)` on line 5 in
|
||||
@@ -678,10 +686,7 @@ class TupleExpr extends Expr, @tuple_expr {
|
||||
Expr getAnArgument() { result = getArgument(_) }
|
||||
|
||||
/** Holds if this tuple is a read access. */
|
||||
predicate isReadAccess() {
|
||||
not exists(AssignExpr e | this = e.getLValue().getAChildExpr*()) and
|
||||
not exists(ForeachStmt fs | this = fs.getVariableDeclTuple().getAChildExpr*())
|
||||
}
|
||||
predicate isReadAccess() { not this = getAnAssignOrForeachChild() }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
193
csharp/ql/src/semmle/code/csharp/frameworks/EntityFramework.qll
Normal file
193
csharp/ql/src/semmle/code/csharp/frameworks/EntityFramework.qll
Normal file
@@ -0,0 +1,193 @@
|
||||
/**
|
||||
* Classes modelling EntityFramework and EntityFrameworkCore.
|
||||
*/
|
||||
|
||||
import csharp
|
||||
private import semmle.code.csharp.frameworks.system.data.Entity
|
||||
private import semmle.code.csharp.frameworks.system.collections.Generic
|
||||
private import semmle.code.csharp.frameworks.Sql
|
||||
private import semmle.code.csharp.dataflow.LibraryTypeDataFlow
|
||||
|
||||
module DataAnnotations {
|
||||
class NotMappedAttribute extends Attribute {
|
||||
NotMappedAttribute() {
|
||||
this
|
||||
.getType()
|
||||
.hasQualifiedName("System.ComponentModel.DataAnnotations.Schema.NotMappedAttribute")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module EntityFramework {
|
||||
/** An EF6 or EFCore namespace. */
|
||||
class EFNamespace extends Namespace {
|
||||
EFNamespace() {
|
||||
this.getQualifiedName() = "Microsoft.EntityFrameworkCore"
|
||||
or
|
||||
this.getQualifiedName() = "System.Data.Entity"
|
||||
}
|
||||
}
|
||||
|
||||
/** A taint source where the data has come from a mapped property stored in the database. */
|
||||
class StoredFlowSource extends DataFlow::Node {
|
||||
StoredFlowSource() {
|
||||
this.asExpr() = any(PropertyRead read | read.getTarget() instanceof MappedProperty)
|
||||
}
|
||||
}
|
||||
|
||||
private class EFClass extends Class {
|
||||
EFClass() { this.getDeclaringNamespace() instanceof EFNamespace }
|
||||
}
|
||||
|
||||
/** The class `Microsoft.EntityFrameworkCore.DbContext` or `System.Data.Entity.DbContext`. */
|
||||
class DbContext extends EFClass {
|
||||
DbContext() { this.getName() = "DbContext" }
|
||||
|
||||
Method getAFindMethod() {
|
||||
result = this.getAMethod("Find")
|
||||
or
|
||||
result = this.getAMethod("FindAsync")
|
||||
}
|
||||
|
||||
Method getAnUpdateMethod() { result = this.getAMethod("Update") }
|
||||
}
|
||||
|
||||
/** The class `Microsoft.EntityFrameworkCore.DbSet<>` or `System.Data.Entity.DbSet<>`. */
|
||||
class DbSet extends EFClass, UnboundGenericClass { DbSet() { this.getName() = "DbSet<>" } }
|
||||
|
||||
/** The class `Microsoft.EntityFrameworkCore.DbQuery<>` or `System.Data.Entity.DbQuery<>`. */
|
||||
class DbQuery extends EFClass, UnboundGenericClass { DbQuery() { this.hasName("DbQuery<>") } }
|
||||
|
||||
/** A generic type or method that takes a mapped type as its type argument. */
|
||||
private predicate usesMappedType(UnboundGeneric g) {
|
||||
g instanceof DbSet
|
||||
or
|
||||
g instanceof DbQuery
|
||||
or
|
||||
exists(DbContext db |
|
||||
g = db.getAnUpdateMethod()
|
||||
or
|
||||
g = db.getAFindMethod()
|
||||
)
|
||||
}
|
||||
|
||||
/** A type that is mapped to database table, or used as a query. */
|
||||
class MappedType extends ValueOrRefType {
|
||||
MappedType() {
|
||||
not this instanceof ObjectType and
|
||||
not this instanceof StringType and
|
||||
not this instanceof ValueType and
|
||||
(
|
||||
exists(UnboundGeneric g | usesMappedType(g) |
|
||||
this = g.getAConstructedGeneric().getATypeArgument()
|
||||
)
|
||||
or
|
||||
this.getASubType() instanceof MappedType
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** A property that is potentially stored and retrieved from a database. */
|
||||
class MappedProperty extends Property {
|
||||
MappedProperty() {
|
||||
this = any(MappedType t).getAMember() and
|
||||
this.isPublic() and
|
||||
not this.getAnAttribute() instanceof DataAnnotations::NotMappedAttribute
|
||||
}
|
||||
}
|
||||
|
||||
/** The struct `Microsoft.EntityFrameworkCore.RawSqlString`. */
|
||||
class RawSqlStringStruct extends Struct, LibraryTypeDataFlow {
|
||||
RawSqlStringStruct() { this.getQualifiedName() = "Microsoft.EntityFrameworkCore.RawSqlString" }
|
||||
|
||||
override predicate callableFlow(
|
||||
CallableFlowSource source, CallableFlowSink sink, SourceDeclarationCallable c,
|
||||
boolean preservesValue
|
||||
) {
|
||||
c = this.getAConstructor() and
|
||||
source.(CallableFlowSourceArg).getArgumentIndex() = 0 and
|
||||
sink instanceof CallableFlowSinkReturn and
|
||||
preservesValue = true
|
||||
or
|
||||
c = this.getAConversionTo() and
|
||||
source.(CallableFlowSourceArg).getArgumentIndex() = 0 and
|
||||
sink instanceof CallableFlowSinkReturn and
|
||||
preservesValue = true
|
||||
}
|
||||
|
||||
ConversionOperator getAConversionTo() {
|
||||
result = this.getAMember() and
|
||||
result.getTargetType() instanceof RawSqlStringStruct and
|
||||
result.getSourceType() instanceof StringType
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A parameter that accepts raw SQL. Parameters of type `System.FormattableString`
|
||||
* are not included as they are not vulnerable to SQL injection.
|
||||
*/
|
||||
private class SqlParameter extends Parameter {
|
||||
SqlParameter() {
|
||||
this.getType() instanceof StringType and
|
||||
(
|
||||
exists(Callable c | this = c.getParameter(0) | c.getName().matches("%Sql"))
|
||||
or
|
||||
this.getName() = "sql"
|
||||
) and
|
||||
this.getCallable().getDeclaringType().getDeclaringNamespace().getParentNamespace*() instanceof
|
||||
EFNamespace
|
||||
or
|
||||
this.getType() instanceof RawSqlStringStruct
|
||||
or
|
||||
this = any(RawSqlStringStruct s).getAConstructor().getAParameter()
|
||||
or
|
||||
this = any(RawSqlStringStruct s).getAConversionTo().getAParameter()
|
||||
}
|
||||
}
|
||||
|
||||
/** A call to a method in EntityFrameworkCore that executes SQL. */
|
||||
class EntityFrameworkCoreSqlSink extends SqlExpr, Call {
|
||||
SqlParameter sqlParam;
|
||||
|
||||
EntityFrameworkCoreSqlSink() { this.getTarget().getAParameter() = sqlParam }
|
||||
|
||||
override Expr getSql() { result = this.getArgumentForParameter(sqlParam) }
|
||||
}
|
||||
|
||||
/** A call to `System.Data.Entity.DbSet.SqlQuery`. */
|
||||
class SystemDataEntityDbSetSqlExpr extends SqlExpr, MethodCall {
|
||||
SystemDataEntityDbSetSqlExpr() {
|
||||
this.getTarget() = any(SystemDataEntity::DbSet dbSet).getSqlQueryMethod()
|
||||
}
|
||||
|
||||
override Expr getSql() { result = this.getArgumentForName("sql") }
|
||||
}
|
||||
|
||||
/** A call to a method in `System.Data.Entity.Database` that executes SQL. */
|
||||
class SystemDataEntityDatabaseSqlExpr extends SqlExpr, MethodCall {
|
||||
SystemDataEntityDatabaseSqlExpr() {
|
||||
exists(SystemDataEntity::Database db |
|
||||
this.getTarget() = db.getSqlQueryMethod() or
|
||||
this.getTarget() = db.getExecuteSqlCommandMethod() or
|
||||
this.getTarget() = db.getExecuteSqlCommandAsyncMethod()
|
||||
)
|
||||
}
|
||||
|
||||
override Expr getSql() { result = this.getArgumentForName("sql") }
|
||||
}
|
||||
|
||||
/**
|
||||
* A dataflow node whereby data flows from a property write to a property read
|
||||
* via some database. The assumption is that all writes can flow to all reads.
|
||||
*/
|
||||
class MappedPropertyJumpNode extends DataFlow::NonLocalJumpNode {
|
||||
MappedProperty property;
|
||||
|
||||
MappedPropertyJumpNode() { this.asExpr() = property.getAnAssignedValue() }
|
||||
|
||||
override DataFlow::Node getAJumpSuccessor(boolean preservesValue) {
|
||||
result.asExpr().(PropertyRead).getTarget() = property and
|
||||
preservesValue = false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
import csharp
|
||||
private import semmle.code.csharp.frameworks.system.Data
|
||||
private import semmle.code.csharp.frameworks.system.data.Entity
|
||||
private import semmle.code.csharp.frameworks.system.data.SqlClient
|
||||
private import semmle.code.csharp.frameworks.EntityFramework
|
||||
|
||||
/** An expression containing a SQL command. */
|
||||
abstract class SqlExpr extends Expr {
|
||||
@@ -22,7 +22,7 @@ class CommandTextAssignmentSqlExpr extends SqlExpr, AssignExpr {
|
||||
)
|
||||
}
|
||||
|
||||
override Expr getSql() { result = getRValue() }
|
||||
override Expr getSql() { result = this.getRValue() }
|
||||
}
|
||||
|
||||
/** A construction of an `IDbCommand` object. */
|
||||
@@ -34,7 +34,7 @@ class IDbCommandConstructionSqlExpr extends SqlExpr, ObjectCreation {
|
||||
)
|
||||
}
|
||||
|
||||
override Expr getSql() { result = getArgument(0) }
|
||||
override Expr getSql() { result = this.getArgument(0) }
|
||||
}
|
||||
|
||||
/** A construction of an `SqlDataAdapter` object. */
|
||||
@@ -47,7 +47,7 @@ class SqlDataAdapterConstructionSqlExpr extends SqlExpr, ObjectCreation {
|
||||
)
|
||||
}
|
||||
|
||||
override Expr getSql() { result = getArgument(0) }
|
||||
override Expr getSql() { result = this.getArgument(0) }
|
||||
}
|
||||
|
||||
/** A `MySql.Data.MySqlClient.MySqlHelper` method. */
|
||||
@@ -83,25 +83,3 @@ class MicrosoftSqlHelperMethodCallSqlExpr extends SqlExpr, MethodCall {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** A call to `System.Data.Entity.DbSet.SqlQuery`. */
|
||||
class SystemDataEntityDbSetSqlExpr extends SqlExpr, MethodCall {
|
||||
SystemDataEntityDbSetSqlExpr() {
|
||||
this.getTarget() = any(SystemDataEntity::DbSet dbSet).getSqlQueryMethod()
|
||||
}
|
||||
|
||||
override Expr getSql() { result = getArgumentForName("sql") }
|
||||
}
|
||||
|
||||
/** A call to a method in `System.Data.Entity.Database` that executes SQL. */
|
||||
class SystemDataEntityDatabaseSqlExpr extends SqlExpr, MethodCall {
|
||||
SystemDataEntityDatabaseSqlExpr() {
|
||||
exists(SystemDataEntity::Database db |
|
||||
this.getTarget() = db.getSqlQueryMethod() or
|
||||
this.getTarget() = db.getExecuteSqlCommandMethod() or
|
||||
this.getTarget() = db.getExecuteSqlCommandAsyncMethod()
|
||||
)
|
||||
}
|
||||
|
||||
override Expr getSql() { result = getArgumentForName("sql") }
|
||||
}
|
||||
|
||||
@@ -5,18 +5,12 @@
|
||||
import csharp
|
||||
private import semmle.code.csharp.frameworks.system.data.Common
|
||||
private import semmle.code.csharp.frameworks.system.data.Entity
|
||||
private import semmle.code.csharp.frameworks.EntityFramework
|
||||
private import semmle.code.csharp.frameworks.Sql
|
||||
|
||||
/** A data flow source of stored user input. */
|
||||
abstract class StoredFlowSource extends DataFlow::Node { }
|
||||
|
||||
/** An access of an Entity Framework `Entity` property that may hold stored data. */
|
||||
class EntityPropertyStoredFlowSource extends StoredFlowSource {
|
||||
EntityPropertyStoredFlowSource() {
|
||||
this.asExpr().(PropertyAccess).getTarget() = any(SystemDataEntity::Entity e).getAProperty()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An expression that has a type of `DbRawSqlQuery`, representing the result of an Entity Framework
|
||||
* SqlQuery.
|
||||
@@ -37,9 +31,7 @@ class DbDataReaderStoredFlowSource extends StoredFlowSource {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An expression that accesses a method of `DbDataReader` or a sub-class.
|
||||
*/
|
||||
/** An expression that accesses a method of `DbDataReader` or a sub-class. */
|
||||
class DbDataReaderMethodStoredFlowSource extends StoredFlowSource {
|
||||
DbDataReaderMethodStoredFlowSource() {
|
||||
this.asExpr().(MethodCall).getTarget().getDeclaringType() = any(SystemDataCommon::DbDataReader dataReader
|
||||
@@ -47,12 +39,15 @@ class DbDataReaderMethodStoredFlowSource extends StoredFlowSource {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An expression that accesses a property of `DbDataReader` or a sub-class.
|
||||
*/
|
||||
/** An expression that accesses a property of `DbDataReader` or a sub-class. */
|
||||
class DbDataReaderPropertyStoredFlowSource extends StoredFlowSource {
|
||||
DbDataReaderPropertyStoredFlowSource() {
|
||||
this.asExpr().(PropertyAccess).getTarget().getDeclaringType() = any(SystemDataCommon::DbDataReader dataReader
|
||||
).getASubType*()
|
||||
}
|
||||
}
|
||||
|
||||
/** A read of a mapped property. */
|
||||
class EntityFrameworkMappedProperty extends StoredFlowSource {
|
||||
EntityFrameworkMappedProperty() { this instanceof EntityFramework::StoredFlowSource }
|
||||
}
|
||||
|
||||
@@ -166,4 +166,4 @@ class SplittingStressTest
|
||||
;
|
||||
;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
| EntityFramework.cs:52:18:52:24 | access to property Name | EntityFramework.cs:47:34:47:42 | "tainted" |
|
||||
| EntityFramework.cs:53:18:53:34 | access to property Name | EntityFramework.cs:47:34:47:42 | "tainted" |
|
||||
| EntityFrameworkCore.cs:50:18:50:28 | access to local variable taintSource | EntityFrameworkCore.cs:47:31:47:39 | "tainted" |
|
||||
| EntityFrameworkCore.cs:51:18:51:46 | (...) ... | EntityFrameworkCore.cs:47:31:47:39 | "tainted" |
|
||||
| EntityFrameworkCore.cs:52:18:52:42 | (...) ... | EntityFrameworkCore.cs:47:31:47:39 | "tainted" |
|
||||
| EntityFrameworkCore.cs:60:18:60:24 | access to property Name | EntityFrameworkCore.cs:47:31:47:39 | "tainted" |
|
||||
| EntityFrameworkCore.cs:61:18:61:34 | access to property Name | EntityFrameworkCore.cs:47:31:47:39 | "tainted" |
|
||||
@@ -0,0 +1,16 @@
|
||||
import csharp
|
||||
import semmle.code.csharp.dataflow.TaintTracking
|
||||
|
||||
class MyConfiguration extends TaintTracking::Configuration {
|
||||
MyConfiguration() { this = "EntityFramework dataflow" }
|
||||
|
||||
override predicate isSource(DataFlow::Node node) { node.asExpr().getValue() = "tainted" }
|
||||
|
||||
override predicate isSink(DataFlow::Node node) {
|
||||
node.asExpr() = any(MethodCall c | c.getTarget().hasName("Sink")).getAnArgument()
|
||||
}
|
||||
}
|
||||
|
||||
from MyConfiguration config, DataFlow::Node source, DataFlow::Node sink
|
||||
where config.hasFlow(source, sink)
|
||||
select sink, source
|
||||
@@ -0,0 +1,64 @@
|
||||
// semmle-extractor-options: /r:System.Data.dll /r:System.ComponentModel.Primitives.dll /r:System.ComponentModel.TypeConverter.dll ${testdir}/../../../resources/stubs/EntityFramework.cs ${testdir}/../../../resources/stubs/System.Data.cs /r:System.ComponentModel.TypeConverter.dll /r:System.Data.Common.dll
|
||||
|
||||
using System.Data.Entity;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Data;
|
||||
using System.Data.Common;
|
||||
|
||||
namespace EFTests
|
||||
{
|
||||
class Person
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
|
||||
[NotMapped]
|
||||
public int Age { get; set; }
|
||||
}
|
||||
|
||||
class MyContext : DbContext
|
||||
{
|
||||
DbSet<Person> person { get; set; }
|
||||
|
||||
void FlowSources()
|
||||
{
|
||||
var p = new Person();
|
||||
var id = p.Id; // Remote flow source
|
||||
var name = p.Name; // Remote flow source
|
||||
var age = p.Age; // Not a remote flow source
|
||||
}
|
||||
|
||||
DbCommand command;
|
||||
|
||||
async void SqlSinks()
|
||||
{
|
||||
// System.Data.Common.DbCommand.set_CommandText
|
||||
command.CommandText = ""; // SqlExpr
|
||||
|
||||
// System.Data.SqlClient.SqlCommand.SqlCommand
|
||||
new System.Data.SqlClient.SqlCommand(""); // SqlExpr
|
||||
|
||||
this.Database.ExecuteSqlCommand(""); // SqlExpr
|
||||
await this.Database.ExecuteSqlCommandAsync(""); // SqlExpr
|
||||
}
|
||||
|
||||
void TestDataFlow()
|
||||
{
|
||||
string taintSource = "tainted";
|
||||
|
||||
// Tainted via database, even though technically there were no reads or writes to the database in this particular case.
|
||||
var p1 = new Person { Name = taintSource };
|
||||
var p2 = new Person();
|
||||
Sink(p2.Name); // Tainted
|
||||
Sink(new Person().Name); // Tainted
|
||||
|
||||
p1.Age = int.Parse(taintSource);
|
||||
Sink(p2.Age); // Not tainted due to NotMappedAttribute
|
||||
}
|
||||
|
||||
void Sink(object @object)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Data.Common;
|
||||
|
||||
namespace EFCoreTests
|
||||
{
|
||||
class Person
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
|
||||
[NotMapped]
|
||||
public int Age { get; set; }
|
||||
}
|
||||
|
||||
class MyContext : DbContext
|
||||
{
|
||||
DbSet<Person> person;
|
||||
|
||||
void FlowSources()
|
||||
{
|
||||
var p = new Person();
|
||||
var id = p.Id; // Remote flow source
|
||||
var name = p.Name; // Remote flow source
|
||||
var age = p.Age; // Not a remote flow source
|
||||
}
|
||||
|
||||
Microsoft.EntityFrameworkCore.Storage.IRawSqlCommandBuilder builder;
|
||||
|
||||
async void SqlExprs()
|
||||
{
|
||||
// Microsoft.EntityFrameworkCore.RelationalDatabaseFacadeExtensions.ExecuteSqlCommand
|
||||
this.Database.ExecuteSqlCommand(""); // SqlExpr
|
||||
await this.Database.ExecuteSqlCommandAsync(""); // SqlExpr
|
||||
|
||||
// Microsoft.EntityFrameworkCore.Storage.IRawSqlCommandBuilder.Build
|
||||
builder.Build(""); // SqlExpr
|
||||
|
||||
// Microsoft.EntityFrameworkCore.RawSqlString
|
||||
new RawSqlString(""); // SqlExpr
|
||||
RawSqlString str = ""; // SqlExpr
|
||||
}
|
||||
|
||||
void TestDataFlow()
|
||||
{
|
||||
var taintSource = "tainted";
|
||||
var untaintedSource = "untainted";
|
||||
|
||||
Sink(taintSource); // Tainted
|
||||
Sink(new RawSqlString(taintSource)); // Tainted
|
||||
Sink((RawSqlString)taintSource); // Tainted
|
||||
Sink((RawSqlString)(FormattableString)$"{taintSource}"); // Not tainted
|
||||
|
||||
// Tainted via database, even though technically there were no reads or writes to the database in this particular case.
|
||||
var p1 = new Person { Name = taintSource };
|
||||
p1.Name = untaintedSource;
|
||||
var p2 = new Person();
|
||||
|
||||
Sink(p2.Name); // Tainted
|
||||
Sink(new Person().Name); // Tainted
|
||||
|
||||
p1.Age = int.Parse(taintSource);
|
||||
Sink(p2.Age); // Not tainted due to NotMappedAttribute
|
||||
}
|
||||
|
||||
void Sink(object @object)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
| EntityFramework.cs:12:20:12:21 | Id |
|
||||
| EntityFramework.cs:13:23:13:26 | Name |
|
||||
| EntityFrameworkCore.cs:10:18:10:19 | Id |
|
||||
| EntityFrameworkCore.cs:11:21:11:24 | Name |
|
||||
@@ -0,0 +1,5 @@
|
||||
import csharp
|
||||
import semmle.code.csharp.frameworks.EntityFramework
|
||||
|
||||
from EntityFramework::MappedProperty property
|
||||
select property
|
||||
@@ -0,0 +1,11 @@
|
||||
| EntityFramework.cs:36:13:36:36 | ... = ... |
|
||||
| EntityFramework.cs:39:13:39:52 | object creation of type SqlCommand |
|
||||
| EntityFramework.cs:41:13:41:47 | call to method ExecuteSqlCommand |
|
||||
| EntityFramework.cs:42:19:42:58 | call to method ExecuteSqlCommandAsync |
|
||||
| EntityFrameworkCore.cs:34:13:34:47 | call to method ExecuteSqlCommand |
|
||||
| EntityFrameworkCore.cs:35:19:35:58 | call to method ExecuteSqlCommandAsync |
|
||||
| EntityFrameworkCore.cs:38:13:38:29 | call to method Build |
|
||||
| EntityFrameworkCore.cs:41:13:41:32 | object creation of type RawSqlString |
|
||||
| EntityFrameworkCore.cs:42:32:42:33 | call to operator implicit conversion |
|
||||
| EntityFrameworkCore.cs:51:18:51:46 | object creation of type RawSqlString |
|
||||
| EntityFrameworkCore.cs:52:18:52:42 | call to operator implicit conversion |
|
||||
@@ -0,0 +1,5 @@
|
||||
import csharp
|
||||
import semmle.code.csharp.frameworks.Sql
|
||||
|
||||
from SqlExpr expr
|
||||
select expr
|
||||
@@ -0,0 +1,8 @@
|
||||
| EntityFramework.cs:26:22:26:25 | access to property Id |
|
||||
| EntityFramework.cs:27:24:27:29 | access to property Name |
|
||||
| EntityFramework.cs:52:18:52:24 | access to property Name |
|
||||
| EntityFramework.cs:53:18:53:34 | access to property Name |
|
||||
| EntityFrameworkCore.cs:24:22:24:25 | access to property Id |
|
||||
| EntityFrameworkCore.cs:25:24:25:29 | access to property Name |
|
||||
| EntityFrameworkCore.cs:60:18:60:24 | access to property Name |
|
||||
| EntityFrameworkCore.cs:61:18:61:34 | access to property Name |
|
||||
@@ -0,0 +1,5 @@
|
||||
import csharp
|
||||
import semmle.code.csharp.security.dataflow.flowsources.Stored
|
||||
|
||||
from StoredFlowSource source
|
||||
select source
|
||||
@@ -32,14 +32,14 @@ public class BloggingContext : DbContext
|
||||
DbRawSqlQuery<Blog> blogs = Database.SqlQuery<Blog>("SELECT * FROM Blogs");
|
||||
foreach (var blog in blogs)
|
||||
{
|
||||
// This will be a sink because it is an access of an entity property
|
||||
// This will be a source because it is an access of an entity property
|
||||
Console.WriteLine(blog.Name);
|
||||
}
|
||||
|
||||
DbRawSqlQuery<string> blogNames = Database.SqlQuery<string>("SELECT Name FROM Blogs");
|
||||
foreach (var blogName in blogNames)
|
||||
{
|
||||
// This will be a sink because it is returned from an SqlQuery
|
||||
// This will be a source because it is returned from an SqlQuery
|
||||
Console.WriteLine(blogName);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ class Program
|
||||
{
|
||||
if (obj2 == null)
|
||||
{
|
||||
obj2 = null;
|
||||
obj2 = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,6 +55,6 @@ public class CommandInjectionHandler : IHttpHandler
|
||||
void OnButtonClicked()
|
||||
{
|
||||
// BAD: Use the Roslyn APIs to dynamically evaluate C#
|
||||
CSharpScript.EvaluateAsync(box1.Text);
|
||||
CSharpScript.EvaluateAsync(box1.Text);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@ nodes
|
||||
| CodeInjection.cs:25:23:25:45 | access to property QueryString |
|
||||
| CodeInjection.cs:31:64:31:67 | access to local variable code |
|
||||
| CodeInjection.cs:42:36:42:39 | access to local variable code |
|
||||
| CodeInjection.cs:58:33:58:41 | access to property Text |
|
||||
| CodeInjection.cs:58:36:58:44 | access to property Text |
|
||||
#select
|
||||
| CodeInjection.cs:31:64:31:67 | access to local variable code | CodeInjection.cs:25:23:25:45 | access to property QueryString | CodeInjection.cs:31:64:31:67 | access to local variable code | $@ flows to here and is compiled as code. | CodeInjection.cs:25:23:25:45 | access to property QueryString | User-provided value |
|
||||
| CodeInjection.cs:42:36:42:39 | access to local variable code | CodeInjection.cs:25:23:25:45 | access to property QueryString | CodeInjection.cs:42:36:42:39 | access to local variable code | $@ flows to here and is compiled as code. | CodeInjection.cs:25:23:25:45 | access to property QueryString | User-provided value |
|
||||
| CodeInjection.cs:58:33:58:41 | access to property Text | CodeInjection.cs:58:33:58:41 | access to property Text | CodeInjection.cs:58:33:58:41 | access to property Text | $@ flows to here and is compiled as code. | CodeInjection.cs:58:33:58:41 | access to property Text | User-provided value |
|
||||
| CodeInjection.cs:58:36:58:44 | access to property Text | CodeInjection.cs:58:36:58:44 | access to property Text | CodeInjection.cs:58:36:58:44 | access to property Text | $@ flows to here and is compiled as code. | CodeInjection.cs:58:36:58:44 | access to property Text | User-provided value |
|
||||
|
||||
@@ -9,16 +9,16 @@ public class TaintedPathHandler : IHttpHandler
|
||||
public void ProcessRequest(HttpContext ctx)
|
||||
{
|
||||
String path = ctx.Request.QueryString["page"];
|
||||
|
||||
|
||||
// BAD: Uncontrolled format string.
|
||||
String.Format(path, "Do not do this");
|
||||
|
||||
|
||||
// BAD: Using an IFormatProvider.
|
||||
String.Format((IFormatProvider)null, path, "Do not do this");
|
||||
|
||||
// GOOD: Not the format string.
|
||||
String.Format("Do not do this", path);
|
||||
|
||||
|
||||
// GOOD: Not the format string.
|
||||
String.Format((IFormatProvider)null, "Do not do this", path);
|
||||
}
|
||||
@@ -27,7 +27,7 @@ public class TaintedPathHandler : IHttpHandler
|
||||
|
||||
void OnButtonClicked()
|
||||
{
|
||||
// BAD: Uncontrolled format string.
|
||||
String.Format(box1.Text, "Do not do this");
|
||||
// BAD: Uncontrolled format string.
|
||||
String.Format(box1.Text, "Do not do this");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,11 +8,11 @@ nodes
|
||||
| UncontrolledFormatString.cs:17:46:17:49 | access to local variable path |
|
||||
| UncontrolledFormatString.cs:20:23:20:38 | "Do not do this" |
|
||||
| UncontrolledFormatString.cs:23:46:23:61 | "Do not do this" |
|
||||
| UncontrolledFormatString.cs:31:20:31:28 | access to property Text |
|
||||
| UncontrolledFormatString.cs:31:23:31:31 | access to property Text |
|
||||
| UncontrolledFormatStringBad.cs:9:25:9:47 | access to property QueryString |
|
||||
| UncontrolledFormatStringBad.cs:12:39:12:44 | access to local variable format |
|
||||
#select
|
||||
| UncontrolledFormatString.cs:14:23:14:26 | access to local variable path | UncontrolledFormatString.cs:11:23:11:45 | access to property QueryString | UncontrolledFormatString.cs:14:23:14:26 | access to local variable path | $@ flows to here and is used as a format string. | UncontrolledFormatString.cs:11:23:11:45 | access to property QueryString | access to property QueryString |
|
||||
| UncontrolledFormatString.cs:17:46:17:49 | access to local variable path | UncontrolledFormatString.cs:11:23:11:45 | access to property QueryString | UncontrolledFormatString.cs:17:46:17:49 | access to local variable path | $@ flows to here and is used as a format string. | UncontrolledFormatString.cs:11:23:11:45 | access to property QueryString | access to property QueryString |
|
||||
| UncontrolledFormatString.cs:31:20:31:28 | access to property Text | UncontrolledFormatString.cs:31:20:31:28 | access to property Text | UncontrolledFormatString.cs:31:20:31:28 | access to property Text | $@ flows to here and is used as a format string. | UncontrolledFormatString.cs:31:20:31:28 | access to property Text | access to property Text |
|
||||
| UncontrolledFormatString.cs:31:23:31:31 | access to property Text | UncontrolledFormatString.cs:31:23:31:31 | access to property Text | UncontrolledFormatString.cs:31:23:31:31 | access to property Text | $@ flows to here and is used as a format string. | UncontrolledFormatString.cs:31:23:31:31 | access to property Text | access to property Text |
|
||||
| UncontrolledFormatStringBad.cs:12:39:12:44 | access to local variable format | UncontrolledFormatStringBad.cs:9:25:9:47 | access to property QueryString | UncontrolledFormatStringBad.cs:12:39:12:44 | access to local variable format | $@ flows to here and is used as a format string. | UncontrolledFormatStringBad.cs:9:25:9:47 | access to property QueryString | access to property QueryString |
|
||||
|
||||
@@ -7,7 +7,7 @@ public class HttpHandler : IHttpHandler
|
||||
public void ProcessRequest(HttpContext ctx)
|
||||
{
|
||||
string format = ctx.Request.QueryString["nameformat"];
|
||||
|
||||
|
||||
// BAD: Uncontrolled format string.
|
||||
FormattedName = string.Format(format, Surname, Forenames);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections;
|
||||
using System.ComponentModel;
|
||||
using System.Threading.Tasks;
|
||||
using System;
|
||||
|
||||
namespace System.Data.Entity
|
||||
{
|
||||
@@ -16,6 +18,7 @@ namespace System.Data.Entity
|
||||
{
|
||||
public int ExecuteSqlQuery(string sql, params object[] parameters) => 0;
|
||||
public int ExecuteSqlCommand(string sql, params object[] parameters) => 0;
|
||||
public async Task ExecuteSqlCommandAsync(string sql, params object[] parameters) => throw null;
|
||||
public Infrastructure.DbRawSqlQuery<T> SqlQuery<T>(string sql, params object[] parameters) => null;
|
||||
}
|
||||
|
||||
@@ -41,3 +44,52 @@ namespace System.Data.Entity.Infrastructure
|
||||
IList IListSource.GetList() => null;
|
||||
}
|
||||
}
|
||||
|
||||
namespace Microsoft.EntityFrameworkCore
|
||||
{
|
||||
public class DbSet<T>
|
||||
{
|
||||
}
|
||||
|
||||
public class DbContext : IDisposable
|
||||
{
|
||||
public void Dispose() { }
|
||||
public virtual Infrastructure.DatabaseFacade Database => null;
|
||||
// public Infrastructure.DbRawSqlQuery<TElement> SqlQuery<TElement>(string sql, params object[] parameters) => null;
|
||||
}
|
||||
|
||||
namespace Infrastructure
|
||||
{
|
||||
public class DatabaseFacade
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public static class RelationalDatabaseFacaseExtensions
|
||||
{
|
||||
public static void ExecuteSqlCommand(this Infrastructure.DatabaseFacade db, string sql, params object[] parameters) {}
|
||||
public static Task ExecuteSqlCommandAsync(this Infrastructure.DatabaseFacade db, string sql, params object[] parameters) => throw null;
|
||||
}
|
||||
|
||||
struct RawSqlString
|
||||
{
|
||||
public RawSqlString(string str) { }
|
||||
public static implicit operator Microsoft.EntityFrameworkCore.RawSqlString (FormattableString fs) => throw null;
|
||||
public static implicit operator Microsoft.EntityFrameworkCore.RawSqlString (string s) => throw null;
|
||||
}
|
||||
}
|
||||
|
||||
namespace System.ComponentModel.DataAnnotations.Schema
|
||||
{
|
||||
class NotMappedAttribute : Attribute
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
namespace Microsoft.EntityFrameworkCore.Storage
|
||||
{
|
||||
interface IRawSqlCommandBuilder
|
||||
{
|
||||
void Build(string sql);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ namespace Newtonsoft.Json.Linq
|
||||
|
||||
public static explicit operator string(JToken t) => null;
|
||||
|
||||
public IEnumerable<JToken> SelectToken(string s) => null;
|
||||
public IEnumerable<JToken> SelectToken(string s) => null;
|
||||
}
|
||||
|
||||
public class JObject : JToken
|
||||
|
||||
@@ -71,6 +71,7 @@ namespace System.Data
|
||||
IDataReader ExecuteReader();
|
||||
CommandType CommandType { get; set; }
|
||||
IDataParameterCollection Parameters { get; set; }
|
||||
string CommandText { get; set; }
|
||||
}
|
||||
|
||||
public interface IDataReader
|
||||
@@ -117,13 +118,14 @@ namespace System.Data.Common
|
||||
public virtual string GetString(int i) => "";
|
||||
}
|
||||
|
||||
public class DbCommand : IDbCommand, IDisposable
|
||||
public abstract class DbCommand : IDbCommand, IDisposable
|
||||
{
|
||||
public DbDataReader ExecuteReader() => null;
|
||||
public CommandType CommandType { get; set; }
|
||||
public IDataParameterCollection Parameters { get; set; }
|
||||
IDataReader IDbCommand.ExecuteReader() => null;
|
||||
public void Dispose() { }
|
||||
public string CommandText { get; set; }
|
||||
}
|
||||
|
||||
public class DbDataAdapter : IDataAdapter, IDbDataAdapter
|
||||
|
||||
@@ -31,7 +31,7 @@ namespace System.Windows.Forms
|
||||
public char PasswordChar { get; set; }
|
||||
public bool UseSystemPasswordChar { get; set; }
|
||||
}
|
||||
|
||||
|
||||
class RichTextBox : TextBoxBase
|
||||
{
|
||||
public string Rtf => null;
|
||||
|
||||
@@ -35,7 +35,7 @@ or garbled output.
|
||||
<p>
|
||||
Use a (well-tested) sanitization library if at all possible. These libraries are much more
|
||||
likely to handle corner cases correctly than a custom implementation. For URI encoding,
|
||||
you can use the standard `encodeURIComponent` and `decodeURIComponent` functions.
|
||||
you can use the standard <code>encodeURIComponent</code> and <code>decodeURIComponent</code> functions.
|
||||
</p>
|
||||
<p>
|
||||
Otherwise, make sure to always escape the escape character first, and unescape it last.
|
||||
|
||||
@@ -1221,4 +1221,5 @@ module DataFlow {
|
||||
import TypeInference
|
||||
import Configuration
|
||||
import TrackedNodes
|
||||
import TypeTracking
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
*/
|
||||
|
||||
import javascript
|
||||
private import semmle.javascript.dataflow.TypeTracking
|
||||
|
||||
/**
|
||||
* A source node for local data flow, that is, a node from which local data flow is tracked.
|
||||
@@ -153,6 +154,34 @@ class SourceNode extends DataFlow::Node {
|
||||
DataFlow::SourceNode getAPropertySource(string prop) {
|
||||
result.flowsTo(getAPropertyWrite(prop).getRhs())
|
||||
}
|
||||
|
||||
/**
|
||||
* EXPERIMENTAL.
|
||||
*
|
||||
* Gets a node that this node may flow to using one heap and/or interprocedural step.
|
||||
*
|
||||
* See `TypeTracker` for more details about how to use this.
|
||||
*/
|
||||
DataFlow::SourceNode track(TypeTracker t2, TypeTracker t) {
|
||||
exists(StepSummary summary |
|
||||
StepSummary::step(this, result, summary) and
|
||||
t = StepSummary::append(t2, summary)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* EXPERIMENTAL.
|
||||
*
|
||||
* Gets a node that may flow into this one using one heap and/or interprocedural step.
|
||||
*
|
||||
* See `TypeBackTracker` for more details about how to use this.
|
||||
*/
|
||||
DataFlow::SourceNode backtrack(TypeBackTracker t2, TypeBackTracker t) {
|
||||
exists(StepSummary summary |
|
||||
StepSummary::step(result, this, summary) and
|
||||
t = StepSummary::prepend(summary, t2)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
module SourceNode {
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*/
|
||||
|
||||
import javascript
|
||||
private import internal.FlowSteps as FlowSteps
|
||||
|
||||
/**
|
||||
* A data flow node that should be tracked inter-procedurally.
|
||||
|
||||
253
javascript/ql/src/semmle/javascript/dataflow/TypeTracking.qll
Normal file
253
javascript/ql/src/semmle/javascript/dataflow/TypeTracking.qll
Normal file
@@ -0,0 +1,253 @@
|
||||
/**
|
||||
* Provides the `TypeTracker` class for tracking types interprocedurally.
|
||||
*
|
||||
* This provides an alternative to `DataFlow::TrackedNode` and `AbstractValue`
|
||||
* for tracking certain types interprocedurally without computing which source
|
||||
* a given value came from.
|
||||
*/
|
||||
|
||||
import javascript
|
||||
private import internal.FlowSteps
|
||||
|
||||
/**
|
||||
* A pair of booleans, indicating whether a path goes through a return and/or a call.
|
||||
*
|
||||
* Identical to `TPathSummary` except without flow labels.
|
||||
*/
|
||||
private newtype TStepSummary = MkStepSummary(boolean hasReturn, boolean hasCall) {
|
||||
(hasReturn = true or hasReturn = false) and
|
||||
(hasCall = true or hasCall = false)
|
||||
}
|
||||
|
||||
/**
|
||||
* INTERNAL: Use `TypeTracker` or `TypeBackTracker` instead.
|
||||
*
|
||||
* Summary of the steps needed to track a value to a given dataflow node.
|
||||
*/
|
||||
class StepSummary extends TStepSummary {
|
||||
Boolean hasReturn;
|
||||
|
||||
Boolean hasCall;
|
||||
|
||||
StepSummary() { this = MkStepSummary(hasReturn, hasCall) }
|
||||
|
||||
/** Indicates whether the path represented by this summary contains any return steps. */
|
||||
boolean hasReturn() { result = hasReturn }
|
||||
|
||||
/** Indicates whether the path represented by this summary contains any call steps. */
|
||||
boolean hasCall() { result = hasCall }
|
||||
|
||||
/**
|
||||
* Gets the summary for the path obtained by appending `that` to `this`.
|
||||
*
|
||||
* Note that a path containing a `return` step cannot be appended to a path containing
|
||||
* a `call` step in order to maintain well-formedness.
|
||||
*/
|
||||
StepSummary append(StepSummary that) {
|
||||
exists(Boolean hasReturn2, Boolean hasCall2 |
|
||||
that = MkStepSummary(hasReturn2, hasCall2)
|
||||
|
|
||||
result = MkStepSummary(hasReturn.booleanOr(hasReturn2), hasCall.booleanOr(hasCall2)) and
|
||||
// avoid constructing invalid paths
|
||||
not (hasCall = true and hasReturn2 = true)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the summary for the path obtained by appending `this` to `that`.
|
||||
*/
|
||||
StepSummary prepend(StepSummary that) { result = that.append(this) }
|
||||
|
||||
/** Gets a textual representation of this path summary. */
|
||||
string toString() {
|
||||
exists(string withReturn, string withCall |
|
||||
(if hasReturn = true then withReturn = "with" else withReturn = "without") and
|
||||
(if hasCall = true then withCall = "with" else withCall = "without")
|
||||
|
|
||||
result = "path " + withReturn + " return steps and " + withCall + " call steps"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
module StepSummary {
|
||||
/**
|
||||
* Gets a summary describing a path without any calls or returns.
|
||||
*/
|
||||
StepSummary level() { result = MkStepSummary(false, false) }
|
||||
|
||||
/**
|
||||
* Gets a summary describing a path with one or more calls, but no returns.
|
||||
*/
|
||||
StepSummary call() { result = MkStepSummary(false, true) }
|
||||
|
||||
/**
|
||||
* Gets a summary describing a path with one or more returns, but no calls.
|
||||
*/
|
||||
StepSummary return() { result = MkStepSummary(true, false) }
|
||||
|
||||
/**
|
||||
* INTERNAL: Use `SourceNode.track()` or `SourceNode.backtrack()` instead.
|
||||
*/
|
||||
predicate step(DataFlow::SourceNode pred, DataFlow::SourceNode succ, StepSummary summary) {
|
||||
exists (DataFlow::Node predNode | pred.flowsTo(predNode) |
|
||||
// Flow through properties of objects
|
||||
propertyFlowStep(predNode, succ) and
|
||||
summary = level()
|
||||
or
|
||||
// Flow through global variables
|
||||
globalFlowStep(predNode, succ) and
|
||||
summary = level()
|
||||
or
|
||||
// Flow into function
|
||||
callStep(predNode, succ) and
|
||||
summary = call()
|
||||
or
|
||||
// Flow out of function
|
||||
returnStep(predNode, succ) and
|
||||
summary = return()
|
||||
or
|
||||
// Flow through an instance field between members of the same class
|
||||
DataFlow::localFieldStep(predNode, succ) and
|
||||
summary = level()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* INTERNAL. Do not use.
|
||||
*
|
||||
* Appends a step summary onto a type-tracking summary.
|
||||
*/
|
||||
TypeTracker append(TypeTracker type, StepSummary summary) {
|
||||
not (type.hasCall() = true and summary.hasReturn() = true) and
|
||||
result.hasCall() = type.hasCall().booleanOr(summary.hasCall())
|
||||
}
|
||||
|
||||
/**
|
||||
* INTERNAL. Do not use.
|
||||
*
|
||||
* Prepends a step summary before a backwards type-tracking summary.
|
||||
*/
|
||||
TypeBackTracker prepend(StepSummary summary, TypeBackTracker type) {
|
||||
not (type.hasReturn() = true and summary.hasCall() = true) and
|
||||
result.hasReturn() = type.hasReturn().booleanOr(summary.hasReturn())
|
||||
}
|
||||
}
|
||||
|
||||
private newtype TTypeTracker = MkTypeTracker(boolean hasCall) {
|
||||
hasCall = true or hasCall = false
|
||||
}
|
||||
|
||||
/**
|
||||
* EXPERIMENTAL.
|
||||
*
|
||||
* Summary of the steps needed to track a value to a given dataflow node.
|
||||
*
|
||||
* This can be used to track objects that implement a certain API in order to
|
||||
* recognize calls to that API. Note that type-tracking does not provide a
|
||||
* source/sink relation, that is, it may determine that a node has a given type,
|
||||
* but it won't determine where that type came from.
|
||||
*
|
||||
* It is recommended that all uses of this type is written on the following form,
|
||||
* for tracking some type `myType`:
|
||||
* ```
|
||||
* DataFlow::SourceNode myType(DataFlow::TypeTracker t) {
|
||||
* t.start() and
|
||||
* result = < source of myType >
|
||||
* or
|
||||
* exists (DataFlow::TypeTracker t2 |
|
||||
* result = myType(t2).track(t2, t)
|
||||
* )
|
||||
* }
|
||||
*
|
||||
* DataFlow::SourceNode myType() { result = myType(_) }
|
||||
* ```
|
||||
*
|
||||
* To track values backwards, which can be useful for tracking
|
||||
* the type of a callback, use the `TypeBackTracker` class instead.
|
||||
*/
|
||||
class TypeTracker extends TTypeTracker {
|
||||
Boolean hasCall;
|
||||
|
||||
TypeTracker() { this = MkTypeTracker(hasCall) }
|
||||
|
||||
string toString() {
|
||||
hasCall = true and result = "type tracker with call steps"
|
||||
or
|
||||
hasCall = false and result = "type tracker without call steps"
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if this is the starting point of type tracking.
|
||||
*/
|
||||
predicate start() {
|
||||
hasCall = false
|
||||
}
|
||||
|
||||
/**
|
||||
* INTERNAL. DO NOT USE.
|
||||
*
|
||||
* Holds if this type has been tracked into a call.
|
||||
*/
|
||||
boolean hasCall() {
|
||||
result = hasCall
|
||||
}
|
||||
}
|
||||
|
||||
private newtype TTypeBackTracker = MkTypeBackTracker(boolean hasReturn) {
|
||||
hasReturn = true or hasReturn = false
|
||||
}
|
||||
|
||||
/**
|
||||
* EXPERIMENTAL.
|
||||
*
|
||||
* Summary of the steps needed to back-track a use of a value to a given dataflow node.
|
||||
*
|
||||
* This can be used to track callbacks that are passed to a certian API call, and are
|
||||
* therefore expected to called with a certain type of value.
|
||||
*
|
||||
* Note that type back-tracking does not provide a source/sink relation, that is,
|
||||
* it may determine that a node will be used in an API call somwwhere, but it won't
|
||||
* determine exactly where that use was, or the path that led to the use.
|
||||
*
|
||||
* It is recommended that all uses of this type is written on the following form,
|
||||
* for back-tracking some callback type `myCallback`:
|
||||
* ```
|
||||
* DataFlow::SourceNode myCallback(DataFlow::TypeBackTracker t) {
|
||||
* t.start() and
|
||||
* result = (< some API call >).getArgument(< n >).getALocalSource()
|
||||
* or
|
||||
* exists (DataFlow::TypeTracker t2 |
|
||||
* result = myCallback(t2).backtrack(t2, t)
|
||||
* )
|
||||
* }
|
||||
*
|
||||
* DataFlow::SourceNode myCallback() { result = myCallback(_) }
|
||||
* ```
|
||||
*/
|
||||
class TypeBackTracker extends TTypeBackTracker {
|
||||
Boolean hasReturn;
|
||||
|
||||
TypeBackTracker() { this = MkTypeBackTracker(hasReturn) }
|
||||
|
||||
string toString() {
|
||||
hasReturn = true and result = "type back-tracker with return steps"
|
||||
or
|
||||
hasReturn = false and result = "type back-tracker without return steps"
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if this is the starting point of type tracking.
|
||||
*/
|
||||
predicate start() {
|
||||
hasReturn = false
|
||||
}
|
||||
|
||||
/**
|
||||
* INTERNAL. DO NOT USE.
|
||||
*
|
||||
* Holds if this type has been back-tracked into a call through return edge.
|
||||
*/
|
||||
boolean hasReturn() {
|
||||
result = hasReturn
|
||||
}
|
||||
}
|
||||
@@ -47,9 +47,8 @@ AbstractValue getAnInitialPropertyValue(DefiniteAbstractValue baseVal, string pr
|
||||
result = p.getInit().analyze().getALocalValue()
|
||||
)
|
||||
or
|
||||
// `f.prototype` for functions `f` that are instantiated
|
||||
// `f.prototype` for functions `f`
|
||||
propertyName = "prototype" and
|
||||
baseVal = any(NewExpr ne).getCallee().analyze().getALocalValue() and
|
||||
result = TAbstractInstance(baseVal)
|
||||
}
|
||||
|
||||
|
||||
15
javascript/ql/test/library-tests/CallGraphs/protoclass.js
Normal file
15
javascript/ql/test/library-tests/CallGraphs/protoclass.js
Normal file
@@ -0,0 +1,15 @@
|
||||
import * as dummy from 'dummy';
|
||||
|
||||
function F() {
|
||||
this.init();
|
||||
}
|
||||
|
||||
F.prototype.init = function() {
|
||||
this.method();
|
||||
let m = this.method.bind(this);
|
||||
m();
|
||||
};
|
||||
|
||||
F.prototype.method = function() {};
|
||||
|
||||
export default F;
|
||||
@@ -12,6 +12,10 @@ test_isUncertain
|
||||
| n.js:2:1:2:5 | m.f() |
|
||||
| n.js:4:10:4:24 | require('./m2') |
|
||||
| n.js:5:1:5:6 | m2.f() |
|
||||
| protoclass.js:4:3:4:13 | this.init() |
|
||||
| protoclass.js:8:3:8:15 | this.method() |
|
||||
| protoclass.js:9:11:9:32 | this.me ... d(this) |
|
||||
| protoclass.js:10:3:10:5 | m() |
|
||||
| reflection.js:7:1:7:22 | add.cal ... 23, 19) |
|
||||
| reflection.js:8:1:8:25 | add.app ... 3, 19]) |
|
||||
| tst.js:6:1:6:3 | f() |
|
||||
@@ -86,6 +90,18 @@ test_getAFunctionValue
|
||||
| m.js:3:1:3:16 | module.exports.f | m.js:1:13:1:25 | function() {} |
|
||||
| n.js:2:1:2:3 | m.f | m.js:1:13:1:25 | function() {} |
|
||||
| n.js:5:1:5:4 | m2.f | m2.js:2:6:2:18 | function() {} |
|
||||
| protoclass.js:3:1:5:1 | functio ... it();\\n} | protoclass.js:3:1:5:1 | functio ... it();\\n} |
|
||||
| protoclass.js:3:10:3:10 | F | protoclass.js:3:1:5:1 | functio ... it();\\n} |
|
||||
| protoclass.js:4:3:4:11 | this.init | protoclass.js:7:20:11:1 | functio ... m();\\n} |
|
||||
| protoclass.js:7:1:7:1 | F | protoclass.js:3:1:5:1 | functio ... it();\\n} |
|
||||
| protoclass.js:7:1:11:1 | F.proto ... m();\\n} | protoclass.js:7:20:11:1 | functio ... m();\\n} |
|
||||
| protoclass.js:7:20:11:1 | functio ... m();\\n} | protoclass.js:7:20:11:1 | functio ... m();\\n} |
|
||||
| protoclass.js:8:3:8:13 | this.method | protoclass.js:13:22:13:34 | function() {} |
|
||||
| protoclass.js:9:11:9:21 | this.method | protoclass.js:13:22:13:34 | function() {} |
|
||||
| protoclass.js:13:1:13:1 | F | protoclass.js:3:1:5:1 | functio ... it();\\n} |
|
||||
| protoclass.js:13:1:13:34 | F.proto ... on() {} | protoclass.js:13:22:13:34 | function() {} |
|
||||
| protoclass.js:13:22:13:34 | function() {} | protoclass.js:13:22:13:34 | function() {} |
|
||||
| protoclass.js:15:16:15:16 | F | protoclass.js:3:1:5:1 | functio ... it();\\n} |
|
||||
| reflection.js:1:1:3:1 | functio ... x+y;\\n} | reflection.js:1:1:3:1 | functio ... x+y;\\n} |
|
||||
| reflection.js:5:3:5:5 | add | reflection.js:1:1:3:1 | functio ... x+y;\\n} |
|
||||
| reflection.js:5:3:5:39 | add.app ... n 56; } | reflection.js:5:15:5:39 | functio ... n 56; } |
|
||||
@@ -176,6 +192,7 @@ test_getArgument
|
||||
| es2015.js:36:1:36:17 | sum(1, ...[2], 3) | 0 | es2015.js:36:5:36:5 | 1 |
|
||||
| n.js:1:9:1:22 | require('./m') | 0 | n.js:1:17:1:21 | './m' |
|
||||
| n.js:4:10:4:24 | require('./m2') | 0 | n.js:4:18:4:23 | './m2' |
|
||||
| protoclass.js:9:11:9:32 | this.me ... d(this) | 0 | protoclass.js:9:28:9:31 | this |
|
||||
| reflection.js:7:1:7:22 | add.cal ... 23, 19) | 0 | reflection.js:7:10:7:13 | null |
|
||||
| reflection.js:7:1:7:22 | add.cal ... 23, 19) | 1 | reflection.js:7:16:7:17 | 23 |
|
||||
| reflection.js:7:1:7:22 | add.cal ... 23, 19) | 2 | reflection.js:7:20:7:21 | 19 |
|
||||
@@ -207,6 +224,10 @@ test_getNumArgument
|
||||
| n.js:2:1:2:5 | m.f() | 0 |
|
||||
| n.js:4:10:4:24 | require('./m2') | 1 |
|
||||
| n.js:5:1:5:6 | m2.f() | 0 |
|
||||
| protoclass.js:4:3:4:13 | this.init() | 0 |
|
||||
| protoclass.js:8:3:8:15 | this.method() | 0 |
|
||||
| protoclass.js:9:11:9:32 | this.me ... d(this) | 1 |
|
||||
| protoclass.js:10:3:10:5 | m() | 0 |
|
||||
| reflection.js:4:5:4:12 | sneaky() | 0 |
|
||||
| reflection.js:7:1:7:22 | add.cal ... 23, 19) | 3 |
|
||||
| reflection.js:7:1:7:22 | reflective call | 2 |
|
||||
@@ -252,6 +273,10 @@ test_isIncomplete
|
||||
| n.js:2:1:2:5 | m.f() |
|
||||
| n.js:4:10:4:24 | require('./m2') |
|
||||
| n.js:5:1:5:6 | m2.f() |
|
||||
| protoclass.js:4:3:4:13 | this.init() |
|
||||
| protoclass.js:8:3:8:15 | this.method() |
|
||||
| protoclass.js:9:11:9:32 | this.me ... d(this) |
|
||||
| protoclass.js:10:3:10:5 | m() |
|
||||
| reflection.js:7:1:7:22 | add.cal ... 23, 19) |
|
||||
| reflection.js:8:1:8:25 | add.app ... 3, 19]) |
|
||||
| tst.js:6:1:6:3 | f() |
|
||||
@@ -295,6 +320,10 @@ test_getCalleeNode
|
||||
| n.js:2:1:2:5 | m.f() | n.js:2:1:2:3 | m.f |
|
||||
| n.js:4:10:4:24 | require('./m2') | n.js:4:10:4:16 | require |
|
||||
| n.js:5:1:5:6 | m2.f() | n.js:5:1:5:4 | m2.f |
|
||||
| protoclass.js:4:3:4:13 | this.init() | protoclass.js:4:3:4:11 | this.init |
|
||||
| protoclass.js:8:3:8:15 | this.method() | protoclass.js:8:3:8:13 | this.method |
|
||||
| protoclass.js:9:11:9:32 | this.me ... d(this) | protoclass.js:9:11:9:26 | this.method.bind |
|
||||
| protoclass.js:10:3:10:5 | m() | protoclass.js:10:3:10:3 | m |
|
||||
| reflection.js:4:5:4:12 | sneaky() | reflection.js:4:5:4:10 | sneaky |
|
||||
| reflection.js:7:1:7:22 | add.cal ... 23, 19) | reflection.js:7:1:7:8 | add.call |
|
||||
| reflection.js:7:1:7:22 | reflective call | reflection.js:7:1:7:3 | add |
|
||||
@@ -334,6 +363,7 @@ test_getLastArgument
|
||||
| es2015.js:27:5:27:23 | console.log(this.x) | es2015.js:27:17:27:22 | this.x |
|
||||
| n.js:1:9:1:22 | require('./m') | n.js:1:17:1:21 | './m' |
|
||||
| n.js:4:10:4:24 | require('./m2') | n.js:4:18:4:23 | './m2' |
|
||||
| protoclass.js:9:11:9:32 | this.me ... d(this) | protoclass.js:9:28:9:31 | this |
|
||||
| reflection.js:7:1:7:22 | add.cal ... 23, 19) | reflection.js:7:20:7:21 | 19 |
|
||||
| reflection.js:7:1:7:22 | reflective call | reflection.js:7:20:7:21 | 19 |
|
||||
| reflection.js:8:1:8:25 | add.app ... 3, 19]) | reflection.js:8:17:8:24 | [23, 19] |
|
||||
@@ -349,6 +379,7 @@ test_getAnArgument
|
||||
| es2015.js:36:1:36:17 | sum(1, ...[2], 3) | es2015.js:36:16:36:16 | 3 |
|
||||
| n.js:1:9:1:22 | require('./m') | n.js:1:17:1:21 | './m' |
|
||||
| n.js:4:10:4:24 | require('./m2') | n.js:4:18:4:23 | './m2' |
|
||||
| protoclass.js:9:11:9:32 | this.me ... d(this) | protoclass.js:9:28:9:31 | this |
|
||||
| reflection.js:7:1:7:22 | add.cal ... 23, 19) | reflection.js:7:10:7:13 | null |
|
||||
| reflection.js:7:1:7:22 | add.cal ... 23, 19) | reflection.js:7:16:7:17 | 23 |
|
||||
| reflection.js:7:1:7:22 | add.cal ... 23, 19) | reflection.js:7:20:7:21 | 19 |
|
||||
@@ -377,6 +408,8 @@ test_getACallee
|
||||
| m.js:3:1:3:18 | module.exports.f() | m.js:1:13:1:25 | function() {} |
|
||||
| n.js:2:1:2:5 | m.f() | m.js:1:13:1:25 | function() {} |
|
||||
| n.js:5:1:5:6 | m2.f() | m2.js:2:6:2:18 | function() {} |
|
||||
| protoclass.js:4:3:4:13 | this.init() | protoclass.js:7:20:11:1 | functio ... m();\\n} |
|
||||
| protoclass.js:8:3:8:15 | this.method() | protoclass.js:13:22:13:34 | function() {} |
|
||||
| reflection.js:7:1:7:22 | reflective call | reflection.js:1:1:3:1 | functio ... x+y;\\n} |
|
||||
| reflection.js:8:1:8:25 | add.app ... 3, 19]) | reflection.js:5:15:5:39 | functio ... n 56; } |
|
||||
| reflection.js:8:1:8:25 | reflective call | reflection.js:1:1:3:1 | functio ... x+y;\\n} |
|
||||
@@ -430,6 +463,10 @@ test_getCalleeName
|
||||
| n.js:2:1:2:5 | m.f() | f |
|
||||
| n.js:4:10:4:24 | require('./m2') | require |
|
||||
| n.js:5:1:5:6 | m2.f() | f |
|
||||
| protoclass.js:4:3:4:13 | this.init() | init |
|
||||
| protoclass.js:8:3:8:15 | this.method() | method |
|
||||
| protoclass.js:9:11:9:32 | this.me ... d(this) | bind |
|
||||
| protoclass.js:10:3:10:5 | m() | m |
|
||||
| reflection.js:4:5:4:12 | sneaky() | sneaky |
|
||||
| reflection.js:7:1:7:22 | add.cal ... 23, 19) | call |
|
||||
| reflection.js:8:1:8:25 | add.app ... 3, 19]) | apply |
|
||||
|
||||
@@ -1,6 +1,89 @@
|
||||
| ChatListScreen.js:3:1:5:1 | instance of function foo | ChatListScreen.js:3:1:5:1 | instance of function foo |
|
||||
| a.js:3:8:5:1 | instance of function setX | a.js:3:8:5:1 | instance of function setX |
|
||||
| a.js:15:1:17:1 | instance of function bump | a.js:15:1:17:1 | instance of function bump |
|
||||
| amd2.js:1:8:3:1 | instance of anonymous function | amd2.js:1:8:3:1 | instance of anonymous function |
|
||||
| amd3.js:1:24:4:1 | instance of anonymous function | amd3.js:1:24:4:1 | instance of anonymous function |
|
||||
| amd.js:1:31:6:1 | instance of anonymous function | amd.js:1:31:6:1 | instance of anonymous function |
|
||||
| arguments.js:1:2:3:1 | instance of anonymous function | arguments.js:1:2:3:1 | instance of anonymous function |
|
||||
| arguments.js:5:2:8:1 | instance of anonymous function | arguments.js:5:2:8:1 | instance of anonymous function |
|
||||
| arguments.js:10:2:14:1 | instance of anonymous function | arguments.js:10:2:14:1 | instance of anonymous function |
|
||||
| arguments.js:16:2:19:1 | instance of anonymous function | arguments.js:16:2:19:1 | instance of anonymous function |
|
||||
| arguments.js:22:2:28:1 | instance of anonymous function | arguments.js:22:2:28:1 | instance of anonymous function |
|
||||
| arguments.js:30:2:33:1 | instance of anonymous function | arguments.js:30:2:33:1 | instance of anonymous function |
|
||||
| c.js:3:1:5:1 | instance of function f | c.js:3:1:5:1 | instance of function f |
|
||||
| destructuring.js:1:1:4:1 | instance of function f | destructuring.js:1:1:4:1 | instance of function f |
|
||||
| es2015.js:8:1:16:1 | instance of class Sub | es2015.js:1:11:6:1 | instance of class Sup |
|
||||
| es2015.js:18:1:20:1 | instance of function f | es2015.js:18:1:20:1 | instance of function f |
|
||||
| es2015.js:22:2:24:1 | instance of anonymous function | es2015.js:22:2:24:1 | instance of anonymous function |
|
||||
| es2015.js:31:2:35:1 | instance of anonymous function | es2015.js:31:2:35:1 | instance of anonymous function |
|
||||
| es2015.js:38:2:42:1 | instance of anonymous function | es2015.js:38:2:42:1 | instance of anonymous function |
|
||||
| es2015.js:44:2:47:1 | instance of anonymous function | es2015.js:44:2:47:1 | instance of anonymous function |
|
||||
| esLib.js:3:8:3:24 | instance of function foo | esLib.js:3:8:3:24 | instance of function foo |
|
||||
| f.js:1:13:1:25 | instance of anonymous function | f.js:1:13:1:25 | instance of anonymous function |
|
||||
| fundecls.js:1:2:10:1 | instance of anonymous function | fundecls.js:1:2:10:1 | instance of anonymous function |
|
||||
| fundecls.js:4:3:4:17 | instance of function f | fundecls.js:4:3:4:17 | instance of function f |
|
||||
| fundecls.js:8:5:8:19 | instance of function g | fundecls.js:8:5:8:19 | instance of function g |
|
||||
| globals.html:7:8:10:7 | instance of anonymous function | globals.html:7:8:10:7 | instance of anonymous function |
|
||||
| globals.html:16:8:19:7 | instance of anonymous function | globals.html:16:8:19:7 | instance of anonymous function |
|
||||
| globals.html:22:7:22:21 | instance of function x | globals.html:22:7:22:21 | instance of function x |
|
||||
| globals.html:26:23:26:69 | instance of anonymous function | globals.html:26:23:26:69 | instance of anonymous function |
|
||||
| h.js:1:8:1:22 | instance of function f | h.js:1:8:1:22 | instance of function f |
|
||||
| instances.js:1:1:4:1 | instance of function A | instances.js:1:1:4:1 | instance of function A |
|
||||
| instances.js:3:14:3:26 | instance of anonymous function | instances.js:3:14:3:26 | instance of anonymous function |
|
||||
| instances.js:6:19:6:31 | instance of anonymous function | instances.js:6:19:6:31 | instance of anonymous function |
|
||||
| instances.js:13:1:13:18 | instance of function SubA | instances.js:1:1:4:1 | instance of function A |
|
||||
| instances.js:13:1:13:18 | instance of function SubA | instances.js:13:1:13:18 | instance of function SubA |
|
||||
| n.js:1:1:1:15 | instance of function f | n.js:1:1:1:15 | instance of function f |
|
||||
| n.js:2:1:2:15 | instance of function g | n.js:2:1:2:15 | instance of function g |
|
||||
| nestedImport.js:9:1:12:1 | instance of function tst | nestedImport.js:9:1:12:1 | instance of function tst |
|
||||
| nodeJsLib.js:1:18:1:43 | instance of function nodeJsModule | nodeJsLib.js:1:18:1:43 | instance of function nodeJsModule |
|
||||
| nodeJsLib.js:3:15:3:37 | instance of function nodeJsFoo | nodeJsLib.js:3:15:3:37 | instance of function nodeJsFoo |
|
||||
| objlit.js:2:9:2:21 | instance of anonymous function | objlit.js:2:9:2:21 | instance of anonymous function |
|
||||
| objlit.js:4:8:4:20 | instance of method baz | objlit.js:4:8:4:20 | instance of method baz |
|
||||
| objlit.js:10:2:12:1 | instance of anonymous function | objlit.js:10:2:12:1 | instance of anonymous function |
|
||||
| objlit.js:11:11:11:23 | instance of anonymous function | objlit.js:11:11:11:23 | instance of anonymous function |
|
||||
| objlit.js:23:2:48:1 | instance of anonymous function | objlit.js:23:2:48:1 | instance of anonymous function |
|
||||
| objlit.js:41:10:41:22 | instance of anonymous function | objlit.js:41:10:41:22 | instance of anonymous function |
|
||||
| objlit.js:43:12:45:3 | object literal | file://:0:0:0:0 | null |
|
||||
| objlit.js:43:12:45:3 | object literal | objlit.js:33:12:40:3 | object literal |
|
||||
| refinements.js:1:1:8:1 | instance of function f1 | refinements.js:1:1:8:1 | instance of function f1 |
|
||||
| refinements.js:10:1:24:1 | instance of function f2 | refinements.js:10:1:24:1 | instance of function f2 |
|
||||
| refinements.js:26:1:32:1 | instance of function f3 | refinements.js:26:1:32:1 | instance of function f3 |
|
||||
| refinements.js:34:1:40:1 | instance of function f4 | refinements.js:34:1:40:1 | instance of function f4 |
|
||||
| refinements.js:35:20:35:31 | instance of anonymous function | refinements.js:35:20:35:31 | instance of anonymous function |
|
||||
| refinements.js:42:1:56:1 | instance of function f5 | refinements.js:42:1:56:1 | instance of function f5 |
|
||||
| refinements.js:44:3:48:3 | instance of function inner | refinements.js:44:3:48:3 | instance of function inner |
|
||||
| refinements.js:58:1:62:1 | instance of function f6 | refinements.js:58:1:62:1 | instance of function f6 |
|
||||
| ts2.ts:1:10:1:22 | instance of anonymous function | ts2.ts:1:10:1:22 | instance of anonymous function |
|
||||
| tst2.js:3:2:5:1 | instance of anonymous function | tst2.js:3:2:5:1 | instance of anonymous function |
|
||||
| tst.js:1:1:39:1 | instance of function tst | tst.js:1:1:39:1 | instance of function tst |
|
||||
| tst.js:15:12:15:23 | instance of function xd | tst.js:15:12:15:23 | instance of function xd |
|
||||
| tst.js:42:1:44:1 | instance of function nonstrict | tst.js:42:1:44:1 | instance of function nonstrict |
|
||||
| tst.js:46:1:49:1 | instance of function strict | tst.js:46:1:49:1 | instance of function strict |
|
||||
| tst.js:51:1:57:1 | instance of function capturedFn | tst.js:51:1:57:1 | instance of function capturedFn |
|
||||
| tst.js:52:3:52:24 | instance of function captured | tst.js:52:3:52:24 | instance of function captured |
|
||||
| tst.js:53:3:55:3 | instance of function capturing | tst.js:53:3:55:3 | instance of function capturing |
|
||||
| tst.js:59:2:62:1 | instance of anonymous function | tst.js:59:2:62:1 | instance of anonymous function |
|
||||
| tst.js:64:2:66:1 | instance of anonymous function | tst.js:64:2:66:1 | instance of anonymous function |
|
||||
| tst.js:68:2:70:1 | instance of anonymous function | tst.js:68:2:70:1 | instance of anonymous function |
|
||||
| tst.js:72:2:76:1 | instance of function s | tst.js:72:2:76:1 | instance of function s |
|
||||
| tst.js:78:2:82:1 | instance of anonymous function | tst.js:78:2:82:1 | instance of anonymous function |
|
||||
| tst.js:84:2:87:1 | instance of anonymous function | tst.js:84:2:87:1 | instance of anonymous function |
|
||||
| tst.js:89:2:92:1 | instance of anonymous function | tst.js:89:2:92:1 | instance of anonymous function |
|
||||
| tst.js:94:2:104:1 | instance of anonymous function | tst.js:94:2:104:1 | instance of anonymous function |
|
||||
| tst.js:95:3:97:3 | instance of function inner | tst.js:95:3:97:3 | instance of function inner |
|
||||
| tst.js:106:2:109:1 | instance of anonymous function | tst.js:106:2:109:1 | instance of anonymous function |
|
||||
| tst.js:111:1:113:1 | instance of function tst | tst.js:111:1:113:1 | instance of function tst |
|
||||
| tst.js:115:2:132:1 | instance of anonymous function | tst.js:115:2:132:1 | instance of anonymous function |
|
||||
| tst.js:116:12:118:3 | instance of anonymous function | tst.js:116:12:118:3 | instance of anonymous function |
|
||||
| tst.js:119:12:121:3 | instance of anonymous function | tst.js:119:12:121:3 | instance of anonymous function |
|
||||
| tst.js:122:12:124:3 | instance of anonymous function | tst.js:122:12:124:3 | instance of anonymous function |
|
||||
| tst.js:125:12:125:24 | instance of anonymous function | tst.js:125:12:125:24 | instance of anonymous function |
|
||||
| tst.js:126:12:130:3 | instance of anonymous function | tst.js:126:12:130:3 | instance of anonymous function |
|
||||
| tst.js:131:12:131:37 | instance of anonymous function | tst.js:131:12:131:37 | instance of anonymous function |
|
||||
| tst.js:134:1:142:1 | instance of function tst2 | tst.js:134:1:142:1 | instance of function tst2 |
|
||||
| tst.js:144:1:149:1 | instance of function tst3 | tst.js:144:1:149:1 | instance of function tst3 |
|
||||
| tst.js:151:1:162:1 | instance of function tst4 | tst.js:151:1:162:1 | instance of function tst4 |
|
||||
| tst.js:164:1:172:1 | instance of function tst5 | tst.js:164:1:172:1 | instance of function tst5 |
|
||||
| tst.ts:8:1:10:1 | instance of function setX | tst.ts:8:1:10:1 | instance of function setX |
|
||||
| with.js:1:1:17:1 | instance of function f | with.js:1:1:17:1 | instance of function f |
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
test_ApiObject
|
||||
| tst.js:3:11:3:21 | new myapi() |
|
||||
| tst.js:15:10:15:21 | api.chain1() |
|
||||
| tst.js:15:10:15:30 | api.cha ... hain2() |
|
||||
test_Connection
|
||||
| tst.js:6:15:6:18 | conn |
|
||||
| tst.js:10:5:10:19 | this.connection |
|
||||
| tst.js:15:10:15:49 | api.cha ... ction() |
|
||||
| tst.js:18:7:18:21 | getConnection() |
|
||||
| tst.js:30:9:30:23 | getConnection() |
|
||||
| tst.js:39:7:39:21 | getConnection() |
|
||||
| tst.js:47:7:47:21 | getConnection() |
|
||||
test_DataCallback
|
||||
| tst.js:9:11:9:12 | cb |
|
||||
| tst.js:20:1:22:1 | functio ... ata);\\n} |
|
||||
| tst.js:29:26:29:27 | cb |
|
||||
| tst.js:32:17:32:26 | data => {} |
|
||||
| tst.js:37:10:37:19 | data => {} |
|
||||
| tst.js:39:32:39:45 | getDataCurry() |
|
||||
| tst.js:44:19:44:20 | cb |
|
||||
| tst.js:47:32:47:60 | identit ... llback) |
|
||||
test_DataValue
|
||||
| tst.js:20:18:20:21 | data |
|
||||
| tst.js:24:19:24:22 | data |
|
||||
| tst.js:32:17:32:20 | data |
|
||||
| tst.js:37:10:37:13 | data |
|
||||
91
javascript/ql/test/library-tests/TypeTracking/ClassStyle.ql
Normal file
91
javascript/ql/test/library-tests/TypeTracking/ClassStyle.ql
Normal file
@@ -0,0 +1,91 @@
|
||||
import javascript
|
||||
|
||||
string chainableMethod() {
|
||||
result = "chain1" or
|
||||
result = "chain2"
|
||||
}
|
||||
|
||||
class ApiObject extends DataFlow::NewNode {
|
||||
ApiObject() {
|
||||
this = DataFlow::moduleImport("@test/myapi").getAnInstantiation()
|
||||
}
|
||||
|
||||
DataFlow::SourceNode ref(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
result = this
|
||||
or
|
||||
t.start() and
|
||||
result = ref(_).getAMethodCall(chainableMethod())
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 |
|
||||
result = ref(t2).track(t2, t)
|
||||
)
|
||||
}
|
||||
|
||||
DataFlow::SourceNode ref() {
|
||||
result = ref(_)
|
||||
}
|
||||
}
|
||||
|
||||
class Connection extends DataFlow::SourceNode {
|
||||
ApiObject api;
|
||||
|
||||
Connection() {
|
||||
this = api.ref().getAMethodCall("createConnection")
|
||||
}
|
||||
|
||||
DataFlow::SourceNode ref(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
result = this
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 |
|
||||
result = ref(t2).track(t2, t)
|
||||
)
|
||||
}
|
||||
|
||||
DataFlow::SourceNode ref() {
|
||||
result = ref(_)
|
||||
}
|
||||
|
||||
DataFlow::SourceNode getACallbackNode(DataFlow::TypeBackTracker t) {
|
||||
t.start() and
|
||||
result = ref().getAMethodCall("getData").getArgument(0).getALocalSource()
|
||||
or
|
||||
exists(DataFlow::TypeBackTracker t2 |
|
||||
result = getACallbackNode(t2).backtrack(t2, t)
|
||||
)
|
||||
}
|
||||
|
||||
DataFlow::FunctionNode getACallback() {
|
||||
result = getACallbackNode(_).getAFunctionValue()
|
||||
}
|
||||
}
|
||||
|
||||
class DataValue extends DataFlow::SourceNode {
|
||||
Connection connection;
|
||||
|
||||
DataValue() {
|
||||
this = connection.getACallback().getParameter(0)
|
||||
}
|
||||
|
||||
DataFlow::SourceNode ref(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
result = this
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 |
|
||||
result = ref(t2).track(t2, t)
|
||||
)
|
||||
}
|
||||
|
||||
DataFlow::SourceNode ref() {
|
||||
result = ref(_)
|
||||
}
|
||||
}
|
||||
|
||||
query DataFlow::SourceNode test_ApiObject() { result = any(ApiObject obj).ref() }
|
||||
|
||||
query DataFlow::SourceNode test_Connection() { result = any(Connection c).ref() }
|
||||
|
||||
query DataFlow::SourceNode test_DataCallback() { result = any(Connection c).getACallbackNode(_) }
|
||||
|
||||
query DataFlow::SourceNode test_DataValue() { result = any(DataValue v).ref() }
|
||||
@@ -0,0 +1,26 @@
|
||||
apiObject
|
||||
| tst.js:3:11:3:21 | new myapi() |
|
||||
| tst.js:15:10:15:21 | api.chain1() |
|
||||
| tst.js:15:10:15:30 | api.cha ... hain2() |
|
||||
connection
|
||||
| type tracker with call steps | tst.js:6:15:6:18 | conn |
|
||||
| type tracker with call steps | tst.js:10:5:10:19 | this.connection |
|
||||
| type tracker without call steps | tst.js:15:10:15:49 | api.cha ... ction() |
|
||||
| type tracker without call steps | tst.js:18:7:18:21 | getConnection() |
|
||||
| type tracker without call steps | tst.js:30:9:30:23 | getConnection() |
|
||||
| type tracker without call steps | tst.js:39:7:39:21 | getConnection() |
|
||||
| type tracker without call steps | tst.js:47:7:47:21 | getConnection() |
|
||||
dataCallback
|
||||
| tst.js:9:11:9:12 | cb |
|
||||
| tst.js:20:1:22:1 | functio ... ata);\\n} |
|
||||
| tst.js:29:26:29:27 | cb |
|
||||
| tst.js:32:17:32:26 | data => {} |
|
||||
| tst.js:37:10:37:19 | data => {} |
|
||||
| tst.js:39:32:39:45 | getDataCurry() |
|
||||
| tst.js:44:19:44:20 | cb |
|
||||
| tst.js:47:32:47:60 | identit ... llback) |
|
||||
dataValue
|
||||
| tst.js:20:18:20:21 | data |
|
||||
| tst.js:24:19:24:22 | data |
|
||||
| tst.js:32:17:32:20 | data |
|
||||
| tst.js:37:10:37:13 | data |
|
||||
@@ -0,0 +1,61 @@
|
||||
import javascript
|
||||
|
||||
string chainableMethod() {
|
||||
result = "chain1" or
|
||||
result = "chain2"
|
||||
}
|
||||
|
||||
DataFlow::SourceNode apiObject(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
result = DataFlow::moduleImport("@test/myapi").getAnInstantiation()
|
||||
or
|
||||
t.start() and
|
||||
result = apiObject(_).getAMethodCall(chainableMethod())
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 |
|
||||
result = apiObject(t2).track(t2, t)
|
||||
)
|
||||
}
|
||||
|
||||
query DataFlow::SourceNode apiObject() {
|
||||
result = apiObject(_)
|
||||
}
|
||||
|
||||
query DataFlow::SourceNode connection(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
result = apiObject().getAMethodCall("createConnection")
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 |
|
||||
result = connection(t2).track(t2, t)
|
||||
)
|
||||
}
|
||||
|
||||
DataFlow::SourceNode connection() {
|
||||
result = connection(_)
|
||||
}
|
||||
|
||||
DataFlow::SourceNode dataCallback(DataFlow::TypeBackTracker t) {
|
||||
t.start() and
|
||||
result = connection().getAMethodCall("getData").getArgument(0).getALocalSource()
|
||||
or
|
||||
exists(DataFlow::TypeBackTracker t2 |
|
||||
result = dataCallback(t2).backtrack(t2, t)
|
||||
)
|
||||
}
|
||||
|
||||
query DataFlow::SourceNode dataCallback() {
|
||||
result = dataCallback(_)
|
||||
}
|
||||
|
||||
DataFlow::SourceNode dataValue(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
result = dataCallback().getAFunctionValue().getParameter(0)
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 |
|
||||
result = dataValue(t2).track(t2, t)
|
||||
)
|
||||
}
|
||||
|
||||
query DataFlow::SourceNode dataValue() {
|
||||
result = dataValue(_)
|
||||
}
|
||||
51
javascript/ql/test/library-tests/TypeTracking/tst.js
Normal file
51
javascript/ql/test/library-tests/TypeTracking/tst.js
Normal file
@@ -0,0 +1,51 @@
|
||||
import myapi from "@test/myapi";
|
||||
|
||||
let api = new myapi();
|
||||
|
||||
class C {
|
||||
constructor(conn) {
|
||||
this.connection = conn;
|
||||
}
|
||||
getData(cb) {
|
||||
this.connection.getData(cb);
|
||||
}
|
||||
}
|
||||
|
||||
function getConnection() {
|
||||
return api.chain1().chain2().createConnection();
|
||||
}
|
||||
|
||||
new C(getConnection()).getData(useData);
|
||||
|
||||
function useData(data) {
|
||||
useData2(data);
|
||||
}
|
||||
|
||||
function useData2(data) {
|
||||
}
|
||||
|
||||
|
||||
// Test tracking of callback into function
|
||||
function getDataIndirect(cb) {
|
||||
new C(getConnection()).getData(cb);
|
||||
}
|
||||
getDataIndirect(data => {});
|
||||
getDataIndirect(); // suppress precision gains from single-call special case
|
||||
|
||||
// Test tracking of callback out of function
|
||||
function getDataCurry() {
|
||||
return data => {};
|
||||
}
|
||||
new C(getConnection()).getData(getDataCurry());
|
||||
getDataCurry(); // suppress precision gains from single-call special case
|
||||
|
||||
|
||||
// Test call/return matching of callback tracking
|
||||
function identity(cb) {
|
||||
return cb;
|
||||
}
|
||||
new C(getConnection()).getData(identity(realGetDataCallback));
|
||||
identity(fakeGetDataCallback);
|
||||
|
||||
function realGetDataCallback(data) {} // not found due to missing summarization
|
||||
function fakeGetDataCallback(notData) {} // should not be found
|
||||
@@ -19,5 +19,5 @@ import semmle.python.security.Exceptions
|
||||
import semmle.python.web.HttpResponse
|
||||
|
||||
from TaintedPathSource src, TaintedPathSink sink
|
||||
where src.flowsTo(sink)
|
||||
where src.flowsTo(sink) and src.getSource() instanceof ErrorInfoSource
|
||||
select sink.getSink(), src, sink, "$@ may be exposed to an external user", src.getSource(), "Error information"
|
||||
|
||||
@@ -31,6 +31,10 @@ class ExceptionInfo extends StringKind {
|
||||
|
||||
}
|
||||
|
||||
/** A class representing sources of information about
|
||||
* execution state exposed in tracebacks and the like.
|
||||
*/
|
||||
abstract class ErrorInfoSource extends TaintSource {}
|
||||
|
||||
/**
|
||||
* This kind represents exceptions themselves.
|
||||
@@ -56,7 +60,7 @@ class ExceptionKind extends TaintKind {
|
||||
* A source of exception objects, either explicitly created, or captured by an
|
||||
* `except` statement.
|
||||
*/
|
||||
class ExceptionSource extends TaintSource {
|
||||
class ExceptionSource extends ErrorInfoSource {
|
||||
|
||||
ExceptionSource() {
|
||||
exists(ClassObject cls |
|
||||
@@ -91,7 +95,7 @@ class ExceptionInfoSequence extends SequenceKind {
|
||||
* Represents calls to functions in the `traceback` module that return
|
||||
* sequences of exception information.
|
||||
*/
|
||||
class CallToTracebackFunction extends TaintSource {
|
||||
class CallToTracebackFunction extends ErrorInfoSource {
|
||||
|
||||
CallToTracebackFunction() {
|
||||
exists(string name |
|
||||
@@ -120,7 +124,7 @@ class CallToTracebackFunction extends TaintSource {
|
||||
* Represents calls to functions in the `traceback` module that return a single
|
||||
* string of information about an exception.
|
||||
*/
|
||||
class FormattedTracebackSource extends TaintSource {
|
||||
class FormattedTracebackSource extends ErrorInfoSource {
|
||||
|
||||
FormattedTracebackSource() {
|
||||
this = traceback_function("format_exc").getACall()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from flask import Flask
|
||||
from flask import Flask, request, make_response
|
||||
app = Flask(__name__)
|
||||
|
||||
|
||||
@@ -35,3 +35,8 @@ def server_bad_flow():
|
||||
|
||||
def format_error(msg):
|
||||
return "[ERROR] " + msg
|
||||
|
||||
#Unrelated error
|
||||
@app.route('/maybe_xss')
|
||||
def maybe_xss():
|
||||
return make_response(request.args.get('name', ''))
|
||||
|
||||
Reference in New Issue
Block a user