Skip to content

Commit 54c3574

Browse files
authored
Merge pull request #4193 from tamasvajk/feature/sign-analysis
C#: Sign analysis
2 parents 66e2ed9 + 06dbec7 commit 54c3574

33 files changed

+2670
-696
lines changed

config/identical-files.json

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,18 @@
5050
"csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImplConsistency.qll",
5151
"python/ql/src/experimental/dataflow/internal/DataFlowImplConsistency.qll"
5252
],
53+
"SsaReadPosition Java/C#": [
54+
"java/ql/src/semmle/code/java/dataflow/internal/rangeanalysis/SsaReadPositionCommon.qll",
55+
"csharp/ql/src/semmle/code/csharp/dataflow/internal/rangeanalysis/SsaReadPositionCommon.qll"
56+
],
57+
"Sign Java/C#": [
58+
"java/ql/src/semmle/code/java/dataflow/internal/rangeanalysis/Sign.qll",
59+
"csharp/ql/src/semmle/code/csharp/dataflow/internal/rangeanalysis/Sign.qll"
60+
],
61+
"SignAnalysis Java/C#": [
62+
"java/ql/src/semmle/code/java/dataflow/internal/rangeanalysis/SignAnalysisCommon.qll",
63+
"csharp/ql/src/semmle/code/csharp/dataflow/internal/rangeanalysis/SignAnalysisCommon.qll"
64+
],
5365
"C++ SubBasicBlocks": [
5466
"cpp/ql/src/semmle/code/cpp/controlflow/SubBasicBlocks.qll",
5567
"cpp/ql/src/semmle/code/cpp/dataflow/internal/SubBasicBlocks.qll"
@@ -87,7 +99,7 @@
8799
"cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/Operand.qll",
88100
"cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/Operand.qll",
89101
"csharp/ql/src/experimental/ir/implementation/raw/Operand.qll",
90-
"csharp/ql/src/experimental/ir/implementation/unaliased_ssa/Operand.qll"
102+
"csharp/ql/src/experimental/ir/implementation/unaliased_ssa/Operand.qll"
91103
],
92104
"IR IRType": [
93105
"cpp/ql/src/semmle/code/cpp/ir/implementation/IRType.qll",
@@ -109,11 +121,11 @@
109121
"cpp/ql/src/semmle/code/cpp/ir/implementation/internal/OperandTag.qll",
110122
"csharp/ql/src/experimental/ir/implementation/internal/OperandTag.qll"
111123
],
112-
"IR TInstruction":[
124+
"IR TInstruction": [
113125
"cpp/ql/src/semmle/code/cpp/ir/implementation/internal/TInstruction.qll",
114126
"csharp/ql/src/experimental/ir/implementation/internal/TInstruction.qll"
115127
],
116-
"IR TIRVariable":[
128+
"IR TIRVariable": [
117129
"cpp/ql/src/semmle/code/cpp/ir/implementation/internal/TIRVariable.qll",
118130
"csharp/ql/src/experimental/ir/implementation/internal/TIRVariable.qll"
119131
],
@@ -381,4 +393,4 @@
381393
"javascript/ql/src/Comments/CommentedOutCodeReferences.qhelp",
382394
"python/ql/src/Lexical/CommentedOutCodeReferences.qhelp"
383395
]
384-
}
396+
}

csharp/ql/src/Linq/Helpers.qll

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,17 @@ class AnyCall extends MethodCall {
132132
}
133133
}
134134

135+
/** A LINQ Count(...) call. */
136+
class CountCall extends MethodCall {
137+
CountCall() {
138+
exists(Method m |
139+
m = getTarget() and
140+
isEnumerableType(m.getDeclaringType()) and
141+
m.hasName("Count")
142+
)
143+
}
144+
}
145+
135146
/** A variable of type IEnumerable<T>, for some T. */
136147
class IEnumerableSequence extends Variable {
137148
IEnumerableSequence() { isIEnumerableType(getType()) }

csharp/ql/src/semmle/code/csharp/commons/ComparisonTest.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* Provides classes for capturing various ways of performing comparison tests.
33
*/
44

5-
import csharp
5+
private import csharp
66
private import semmle.code.csharp.frameworks.System
77
private import semmle.code.csharp.frameworks.system.Collections
88
private import semmle.code.csharp.frameworks.system.collections.Generic

csharp/ql/src/semmle/code/csharp/controlflow/Guards.qll

Lines changed: 75 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,25 @@ class Guard extends Expr {
3131
predicate controlsNode(ControlFlow::Nodes::ElementNode cfn, AccessOrCallExpr sub, AbstractValue v) {
3232
isGuardedByNode(cfn, this, sub, v)
3333
}
34+
35+
/**
36+
* Holds if basic block `bb` is guarded by this expression having value `v`.
37+
*/
38+
predicate controlsBasicBlock(BasicBlock bb, AbstractValue v) {
39+
Internal::guardControls(this, bb, v)
40+
}
41+
42+
/**
43+
* Holds if this guard is an equality test between `e1` and `e2`. If the test is
44+
* negated, that is `!=`, then `polarity` is false, otherwise `polarity` is
45+
* true.
46+
*/
47+
predicate isEquality(Expr e1, Expr e2, boolean polarity) {
48+
exists(BooleanValue v |
49+
this = Internal::getAnEqualityCheck(e1, v, e2) and
50+
polarity = v.getValue()
51+
)
52+
}
3453
}
3554

3655
/** An abstract value. */
@@ -943,31 +962,28 @@ module Internal {
943962
e = any(BinaryArithmeticOperation bao | result = bao.getAnOperand())
944963
}
945964

946-
/** Holds if basic block `bb` only is reached when guard `g` has abstract value `v`. */
947-
private predicate guardControls(Guard g, BasicBlock bb, AbstractValue v) {
948-
exists(ControlFlowElement cfe, ConditionalSuccessor s, AbstractValue v0, Guard g0 |
949-
cfe.controlsBlock(bb, s)
950-
|
951-
v0.branch(cfe, s, g0) and
952-
impliesSteps(g0, v0, g, v)
965+
pragma[noinline]
966+
private predicate assertionControlsNodeInSameBasicBlock0(
967+
Guard g, AbstractValue v, BasicBlock bb, int i
968+
) {
969+
exists(Assertion a, Guard g0, AbstractValue v0 |
970+
asserts(a, g0, v0) and
971+
impliesSteps(g0, v0, g, v) and
972+
bb.getNode(i) = a.getAControlFlowNode()
953973
)
954974
}
955975

956976
/**
957977
* Holds if control flow node `cfn` only is reached when guard `g` evaluates to `v`,
958978
* because of an assertion.
959979
*/
960-
private predicate guardAssertionControlsNode(Guard g, ControlFlow::Node cfn, AbstractValue v) {
961-
exists(Assertion a, Guard g0, AbstractValue v0 |
962-
asserts(a, g0, v0) and
963-
impliesSteps(g0, v0, g, v)
964-
|
965-
a.strictlyDominates(cfn.getBasicBlock())
966-
or
967-
exists(BasicBlock bb, int i, int j | bb.getNode(i) = a.getAControlFlowNode() |
968-
bb.getNode(j) = cfn and
969-
j > i
970-
)
980+
private predicate assertionControlsNodeInSameBasicBlock(
981+
Guard g, ControlFlow::Node cfn, AbstractValue v
982+
) {
983+
exists(BasicBlock bb, int i, int j |
984+
assertionControlsNodeInSameBasicBlock0(g, v, bb, i) and
985+
bb.getNode(j) = cfn and
986+
j > i
971987
)
972988
}
973989

@@ -977,7 +993,7 @@ module Internal {
977993
*/
978994
private predicate guardAssertionControlsElement(Guard g, ControlFlowElement cfe, AbstractValue v) {
979995
forex(ControlFlow::Node cfn | cfn = cfe.getAControlFlowNode() |
980-
guardAssertionControlsNode(g, cfn, v)
996+
assertionControlsNodeInSameBasicBlock(g, cfn, v)
981997
)
982998
}
983999

@@ -1291,24 +1307,6 @@ module Internal {
12911307
)
12921308
}
12931309

1294-
/**
1295-
* Gets an expression that tests whether expression `e1` is equal to
1296-
* expression `e2`.
1297-
*
1298-
* If the returned expression has abstract value `v`, then expression `e1` is
1299-
* guaranteed to be equal to `e2`, and if the returned expression has abstract
1300-
* value `v.getDualValue()`, then this expression is guaranteed to be
1301-
* non-equal to `e`.
1302-
*
1303-
* For example, if the expression `x != ""` evaluates to `false` then the
1304-
* expression `x` is guaranteed to be equal to `""`.
1305-
*/
1306-
Expr getAnEqualityCheck(Expr e1, AbstractValue v, Expr e2) {
1307-
result = getABooleanEqualityCheck(e1, v, e2)
1308-
or
1309-
result = getAMatchingEqualityCheck(e1, v, e2)
1310-
}
1311-
13121310
private Expr getAnEqualityCheckVal(Expr e, AbstractValue v, AbstractValue vExpr) {
13131311
result = getAnEqualityCheck(e, v, vExpr.getAnExpr())
13141312
}
@@ -1464,6 +1462,29 @@ module Internal {
14641462
not e = any(LocalVariableDeclStmt s).getAVariableDeclExpr()
14651463
}
14661464

1465+
/**
1466+
* Gets an expression that tests whether expression `e1` is equal to
1467+
* expression `e2`.
1468+
*
1469+
* If the returned expression has abstract value `v`, then expression `e1` is
1470+
* guaranteed to be equal to `e2`, and if the returned expression has abstract
1471+
* value `v.getDualValue()`, then this expression is guaranteed to be
1472+
* non-equal to `e`.
1473+
*
1474+
* For example, if the expression `x != ""` evaluates to `false` then the
1475+
* expression `x` is guaranteed to be equal to `""`.
1476+
*/
1477+
cached
1478+
Expr getAnEqualityCheck(Expr e1, AbstractValue v, Expr e2) {
1479+
result = getABooleanEqualityCheck(e1, v, e2)
1480+
or
1481+
result = getABooleanEqualityCheck(e2, v, e1)
1482+
or
1483+
result = getAMatchingEqualityCheck(e1, v, e2)
1484+
or
1485+
result = getAMatchingEqualityCheck(e2, v, e1)
1486+
}
1487+
14671488
cached
14681489
predicate isCustomNullCheck(Call call, Expr arg, BooleanValue v, boolean isNull) {
14691490
exists(Callable callable, Parameter p |
@@ -1749,7 +1770,7 @@ module Internal {
17491770
exists(Guard g | e = getAChildExprStar(g) |
17501771
guardControls(g, bb, _)
17511772
or
1752-
guardAssertionControlsNode(g, bb.getANode(), _)
1773+
assertionControlsNodeInSameBasicBlock(g, bb.getANode(), _)
17531774
)
17541775
}
17551776
}
@@ -1758,6 +1779,21 @@ module Internal {
17581779
private module Cached {
17591780
private import semmle.code.csharp.Caching
17601781

1782+
/** Holds if basic block `bb` only is reached when guard `g` has abstract value `v`. */
1783+
cached
1784+
predicate guardControls(Guard g, BasicBlock bb, AbstractValue v) {
1785+
exists(AbstractValue v0, Guard g0 | impliesSteps(g0, v0, g, v) |
1786+
exists(ControlFlowElement cfe, ConditionalSuccessor s |
1787+
v0.branch(cfe, s, g0) and cfe.controlsBlock(bb, s)
1788+
)
1789+
or
1790+
exists(Assertion a |
1791+
asserts(a, g0, v0) and
1792+
a.strictlyDominates(bb)
1793+
)
1794+
)
1795+
}
1796+
17611797
pragma[noinline]
17621798
private predicate isGuardedByNode0(
17631799
ControlFlow::Node cfn, AccessOrCallExpr guarded, Guard g, AccessOrCallExpr sub,
@@ -1813,7 +1849,7 @@ module Internal {
18131849
) {
18141850
isGuardedByNode0(guarded, _, g, sub, v)
18151851
or
1816-
guardAssertionControlsNode(g, guarded, v) and
1852+
assertionControlsNodeInSameBasicBlock(g, guarded, v) and
18171853
exists(ConditionOnExprComparisonConfig c | c.same(sub, guarded.getElement()))
18181854
}
18191855

csharp/ql/src/semmle/code/csharp/dataflow/Nullness.qll

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,8 @@ import csharp
2121
private import ControlFlow
2222
private import internal.CallableReturns
2323
private import semmle.code.csharp.commons.Assertions
24-
private import semmle.code.csharp.commons.ComparisonTest
2524
private import semmle.code.csharp.controlflow.Guards as G
2625
private import semmle.code.csharp.controlflow.Guards::AbstractValues
27-
private import semmle.code.csharp.dataflow.SSA
2826
private import semmle.code.csharp.frameworks.System
2927
private import semmle.code.csharp.frameworks.Test
3028

csharp/ql/src/semmle/code/csharp/dataflow/SSA.qll

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2537,6 +2537,13 @@ module Ssa {
25372537
)
25382538
}
25392539

2540+
/** Holds if `inp` is an input to the phi node along the edge originating in `bb`. */
2541+
predicate hasInputFromBlock(Definition inp, BasicBlock bb) {
2542+
this.getAnInput() = inp and
2543+
this.getBasicBlock().getAPredecessor() = bb and
2544+
inp.isLiveAtEndOfBlock(bb)
2545+
}
2546+
25402547
override string toString() {
25412548
result = getToStringPrefix(this) + "SSA phi(" + getSourceVariable() + ")"
25422549
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/**
2+
* Provides sign analysis to determine whether expression are always positive
3+
* or negative.
4+
*
5+
* The analysis is implemented as an abstract interpretation over the
6+
* three-valued domain `{negative, zero, positive}`.
7+
*/
8+
9+
import semmle.code.csharp.dataflow.internal.rangeanalysis.SignAnalysisCommon
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/**
2+
* Provides classes and predicates to represent constant integer expressions.
3+
*/
4+
5+
private import csharp
6+
private import Ssa
7+
8+
/**
9+
* Holds if property `p` matches `property` in `baseClass` or any overrides.
10+
*/
11+
predicate propertyOverrides(Property p, string baseClass, string property) {
12+
exists(Property p2 |
13+
p2.getSourceDeclaration().getDeclaringType().hasQualifiedName(baseClass) and
14+
p2.hasName(property)
15+
|
16+
p.overridesOrImplementsOrEquals(p2)
17+
)
18+
}
19+
20+
/**
21+
* Holds if expression `e` is either
22+
* - a compile time constant with integer value `val`, or
23+
* - a read of a compile time constant with integer value `val`, or
24+
* - a read of the `Length` of an array with `val` lengths.
25+
*/
26+
private predicate constantIntegerExpr(Expr e, int val) {
27+
e.getValue().toInt() = val
28+
or
29+
exists(ExplicitDefinition v, Expr src |
30+
e = v.getARead() and
31+
src = v.getADefinition().getSource() and
32+
constantIntegerExpr(src, val)
33+
)
34+
or
35+
isArrayLengthAccess(e, val)
36+
}
37+
38+
private int getArrayLength(ArrayCreation arrCreation, int index) {
39+
constantIntegerExpr(arrCreation.getLengthArgument(index), result)
40+
}
41+
42+
private int getArrayLengthRec(ArrayCreation arrCreation, int index) {
43+
index = 0 and result = getArrayLength(arrCreation, 0)
44+
or
45+
index > 0 and
46+
result = getArrayLength(arrCreation, index) * getArrayLengthRec(arrCreation, index - 1)
47+
}
48+
49+
private predicate isArrayLengthAccess(PropertyAccess pa, int length) {
50+
propertyOverrides(pa.getTarget(), "System.Array", "Length") and
51+
exists(ExplicitDefinition arr, ArrayCreation arrCreation |
52+
getArrayLengthRec(arrCreation, arrCreation.getNumberOfLengthArguments() - 1) = length and
53+
arrCreation = arr.getADefinition().getSource() and
54+
pa.getQualifier() = arr.getARead()
55+
)
56+
}
57+
58+
/** An expression that always has the same integer value. */
59+
class ConstantIntegerExpr extends Expr {
60+
ConstantIntegerExpr() { constantIntegerExpr(this, _) }
61+
62+
/** Gets the integer value of this expression. */
63+
int getIntValue() { constantIntegerExpr(this, result) }
64+
}

0 commit comments

Comments
 (0)