Skip to content

Commit feb45e5

Browse files
authored
Merge pull request #21348 from hvitved/csharp/remove-tcs
C#: Remove some unbounded TC computations
2 parents c9fa7fa + acd6f41 commit feb45e5

File tree

8 files changed

+176
-50
lines changed

8 files changed

+176
-50
lines changed

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

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ private module GuardsInput implements
142142
}
143143
}
144144

145+
pragma[nomagic]
145146
predicate equalityTest(Expr eqtest, Expr left, Expr right, boolean polarity) {
146147
exists(ComparisonTest ct |
147148
ct.getExpr() = eqtest and
@@ -410,6 +411,22 @@ private predicate typePattern(PatternMatch pm, TypePatternExpr tpe, Type t) {
410411
t = pm.getExpr().getType()
411412
}
412413

414+
pragma[nomagic]
415+
private predicate dereferenceableExpr(Expr e, boolean isNullableType) {
416+
exists(Type t | t = e.getType() |
417+
t instanceof NullableType and
418+
isNullableType = true
419+
or
420+
t instanceof RefType and
421+
isNullableType = false
422+
)
423+
or
424+
exists(Expr parent |
425+
dereferenceableExpr(parent, isNullableType) and
426+
e = getNullEquivParent(parent)
427+
)
428+
}
429+
413430
/**
414431
* An expression that evaluates to a value that can be dereferenced. That is,
415432
* an expression that may evaluate to `null`.
@@ -418,21 +435,12 @@ class DereferenceableExpr extends Expr {
418435
private boolean isNullableType;
419436

420437
DereferenceableExpr() {
421-
exists(Expr e, Type t |
422-
// There is currently a bug in the extractor: the type of `x?.Length` is
423-
// incorrectly `int`, while it should have been `int?`. We apply
424-
// `getNullEquivParent()` as a workaround
425-
this = getNullEquivParent*(e) and
426-
t = e.getType() and
427-
not this instanceof SwitchCaseExpr and
428-
not this instanceof PatternExpr
429-
|
430-
t instanceof NullableType and
431-
isNullableType = true
432-
or
433-
t instanceof RefType and
434-
isNullableType = false
435-
)
438+
// There is currently a bug in the extractor: the type of `x?.Length` is
439+
// incorrectly `int`, while it should have been `int?`. We apply
440+
// `getNullEquivParent()` as a workaround
441+
dereferenceableExpr(this, isNullableType) and
442+
not this instanceof SwitchCaseExpr and
443+
not this instanceof PatternExpr
436444
}
437445

438446
/** Holds if this expression has a nullable type `T?`. */

csharp/ql/lib/semmle/code/csharp/controlflow/internal/ControlFlowGraphImpl.qll

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,9 +94,19 @@ private Element getAChild(Element p) {
9494
result = p.(AssignOperation).getExpandedAssignment()
9595
}
9696

97+
pragma[nomagic]
98+
private predicate astNode(Element e) {
99+
e = any(@top_level_exprorstmt_parent p | not p instanceof Attribute)
100+
or
101+
exists(Element parent |
102+
astNode(parent) and
103+
e = getAChild(parent)
104+
)
105+
}
106+
97107
/** An AST node. */
98108
class AstNode extends Element, TAstNode {
99-
AstNode() { this = getAChild*(any(@top_level_exprorstmt_parent p | not p instanceof Attribute)) }
109+
AstNode() { astNode(this) }
100110

101111
int getId() { idOf(this, result) }
102112
}

csharp/ql/lib/semmle/code/csharp/dataflow/internal/ControlFlowReachability.qll

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,47 @@ private class ControlFlowScope extends ControlFlowElement {
1515
predicate isNonExact() { exactScope = false }
1616
}
1717

18-
private ControlFlowElement getANonExactScopeChild(ControlFlowScope scope) {
19-
scope.isNonExact() and
20-
result = scope
18+
private newtype TControlFlowElementOrBasicBlock =
19+
TControlFlowElement(ControlFlowElement cfe) or
20+
TBasicBlock(ControlFlow::BasicBlock bb)
21+
22+
class ControlFlowElementOrBasicBlock extends TControlFlowElementOrBasicBlock {
23+
ControlFlowElement asControlFlowElement() { this = TControlFlowElement(result) }
24+
25+
ControlFlow::BasicBlock asBasicBlock() { this = TBasicBlock(result) }
26+
27+
string toString() {
28+
result = this.asControlFlowElement().toString()
29+
or
30+
result = this.asBasicBlock().toString()
31+
}
32+
33+
Location getLocation() {
34+
result = this.asControlFlowElement().getLocation()
35+
or
36+
result = this.asBasicBlock().getLocation()
37+
}
38+
}
39+
40+
private predicate isBasicBlock(ControlFlowElementOrBasicBlock c) { c instanceof TBasicBlock }
41+
42+
private predicate isNonExactScope(ControlFlowElementOrBasicBlock c) {
43+
c.asControlFlowElement().(ControlFlowScope).isNonExact()
44+
}
45+
46+
private predicate step(ControlFlowElementOrBasicBlock pred, ControlFlowElementOrBasicBlock succ) {
47+
pred.asBasicBlock().getANode().getAstNode() = succ.asControlFlowElement()
2148
or
22-
result = getANonExactScopeChild(scope).getAChild()
49+
pred.asControlFlowElement() = succ.asControlFlowElement().getAChild()
2350
}
2451

52+
private predicate basicBlockInNonExactScope(
53+
ControlFlowElementOrBasicBlock bb, ControlFlowElementOrBasicBlock scope
54+
) = doublyBoundedFastTC(step/2, isBasicBlock/1, isNonExactScope/1)(bb, scope)
55+
2556
pragma[noinline]
2657
private ControlFlow::BasicBlock getABasicBlockInScope(ControlFlowScope scope, boolean exactScope) {
27-
result.getANode().getAstNode() = getANonExactScopeChild(scope) and
58+
basicBlockInNonExactScope(TBasicBlock(result), TControlFlowElement(scope)) and
2859
exactScope = false
2960
or
3061
scope.isExact() and

csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowPrivate.qll

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ private import semmle.code.csharp.dataflow.FlowSummary as FlowSummary
88
private import semmle.code.csharp.dataflow.internal.ExternalFlow
99
private import semmle.code.csharp.commons.Collections
1010
private import semmle.code.csharp.Conversion
11+
private import semmle.code.csharp.exprs.internal.Expr
1112
private import semmle.code.csharp.dataflow.internal.SsaImpl as SsaImpl
1213
private import semmle.code.csharp.ExprOrStmtParent
1314
private import semmle.code.csharp.Unification
@@ -2377,6 +2378,16 @@ predicate storeStep(Node node1, ContentSet c, Node node2) {
23772378
storeStepDelegateCall(node1, c, node2)
23782379
}
23792380

2381+
pragma[nomagic]
2382+
private predicate isAssignExprLValueDescendant(Expr e) {
2383+
e = any(AssignExpr ae).getLValue()
2384+
or
2385+
exists(Expr parent |
2386+
isAssignExprLValueDescendant(parent) and
2387+
e = parent.getAChildExpr()
2388+
)
2389+
}
2390+
23802391
private class ReadStepConfiguration extends ControlFlowReachabilityConfiguration {
23812392
ReadStepConfiguration() { this = "ReadStepConfiguration" }
23822393

@@ -2432,7 +2443,7 @@ private class ReadStepConfiguration extends ControlFlowReachabilityConfiguration
24322443
scope =
24332444
any(AssignExpr ae |
24342445
ae = defTo.(AssignableDefinitions::TupleAssignmentDefinition).getAssignment() and
2435-
e = ae.getLValue().getAChildExpr*().(TupleExpr) and
2446+
isAssignExprLValueDescendant(e.(TupleExpr)) and
24362447
exactScope = false and
24372448
isSuccessor = true
24382449
)
@@ -2488,7 +2499,7 @@ private predicate readContentStep(Node node1, Content c, Node node2) {
24882499
)
24892500
or
24902501
// item = variable in node1 = (..., variable, ...) in a case/is var (..., ...)
2491-
te = any(PatternExpr pe).getAChildExpr*() and
2502+
isPatternExprDescendant(te) and
24922503
exists(AssignableDefinitions::LocalVariableDefinition lvd |
24932504
node2.(AssignableDefinitionNode).getDefinition() = lvd and
24942505
lvd.getDeclaration() = item and

csharp/ql/lib/semmle/code/csharp/dataflow/internal/rangeanalysis/ControlFlowReachability.qll

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,47 @@ private class ControlFlowScope extends ControlFlowElement {
1515
predicate isNonExact() { exactScope = false }
1616
}
1717

18-
private ControlFlowElement getANonExactScopeChild(ControlFlowScope scope) {
19-
scope.isNonExact() and
20-
result = scope
18+
private newtype TControlFlowElementOrBasicBlock =
19+
TControlFlowElement(ControlFlowElement cfe) or
20+
TBasicBlock(ControlFlow::BasicBlock bb)
21+
22+
class ControlFlowElementOrBasicBlock extends TControlFlowElementOrBasicBlock {
23+
ControlFlowElement asControlFlowElement() { this = TControlFlowElement(result) }
24+
25+
ControlFlow::BasicBlock asBasicBlock() { this = TBasicBlock(result) }
26+
27+
string toString() {
28+
result = this.asControlFlowElement().toString()
29+
or
30+
result = this.asBasicBlock().toString()
31+
}
32+
33+
Location getLocation() {
34+
result = this.asControlFlowElement().getLocation()
35+
or
36+
result = this.asBasicBlock().getLocation()
37+
}
38+
}
39+
40+
private predicate isBasicBlock(ControlFlowElementOrBasicBlock c) { c instanceof TBasicBlock }
41+
42+
private predicate isNonExactScope(ControlFlowElementOrBasicBlock c) {
43+
c.asControlFlowElement().(ControlFlowScope).isNonExact()
44+
}
45+
46+
private predicate step(ControlFlowElementOrBasicBlock pred, ControlFlowElementOrBasicBlock succ) {
47+
pred.asBasicBlock().getANode().getAstNode() = succ.asControlFlowElement()
2148
or
22-
result = getANonExactScopeChild(scope).getAChild()
49+
pred.asControlFlowElement() = succ.asControlFlowElement().getAChild()
2350
}
2451

52+
private predicate basicBlockInNonExactScope(
53+
ControlFlowElementOrBasicBlock bb, ControlFlowElementOrBasicBlock scope
54+
) = doublyBoundedFastTC(step/2, isBasicBlock/1, isNonExactScope/1)(bb, scope)
55+
2556
pragma[noinline]
2657
private ControlFlow::BasicBlock getABasicBlockInScope(ControlFlowScope scope, boolean exactScope) {
27-
result.getANode().getAstNode() = getANonExactScopeChild(scope) and
58+
basicBlockInNonExactScope(TBasicBlock(result), TControlFlowElement(scope)) and
2859
exactScope = false
2960
or
3061
scope.isExact() and

csharp/ql/lib/semmle/code/csharp/exprs/Expr.qll

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import semmle.code.csharp.Type
2121
private import semmle.code.csharp.ExprOrStmtParent
2222
private import semmle.code.csharp.frameworks.System
2323
private import semmle.code.csharp.TypeRef
24+
private import internal.Expr
2425

2526
/**
2627
* An expression. Either an access (`Access`), a call (`Call`), an object or
@@ -64,14 +65,24 @@ class Expr extends ControlFlowElement, @expr {
6465
/** Gets the enclosing callable of this expression, if any. */
6566
override Callable getEnclosingCallable() { enclosingCallable(this, result) }
6667

68+
pragma[nomagic]
69+
private predicate isExpandedAssignmentRValueDescendant() {
70+
this =
71+
any(AssignOperation op).getExpandedAssignment().getRValue().getChildExpr(0).getAChildExpr()
72+
or
73+
exists(Expr parent |
74+
parent.isExpandedAssignmentRValueDescendant() and
75+
this = parent.getAChildExpr()
76+
)
77+
}
78+
6779
/**
6880
* Holds if this expression is generated by the compiler and does not appear
6981
* explicitly in the source code.
7082
*/
7183
final predicate isImplicit() {
7284
compiler_generated(this) or
73-
this =
74-
any(AssignOperation op).getExpandedAssignment().getRValue().getChildExpr(0).getAChildExpr+()
85+
this.isExpandedAssignmentRValueDescendant()
7586
}
7687

7788
/**
@@ -1133,7 +1144,7 @@ class TupleExpr extends Expr, @tuple_expr {
11331144
/** Holds if this expression is a tuple construction. */
11341145
predicate isConstruction() {
11351146
not this = getAnAssignOrForeachChild() and
1136-
not this = any(PatternExpr pe).getAChildExpr*()
1147+
not isPatternExprDescendant(this)
11371148
}
11381149

11391150
override string getAPrimaryQlClass() { result = "TupleExpr" }
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
private import csharp
2+
3+
pragma[nomagic]
4+
predicate isPatternExprDescendant(Expr e) {
5+
e instanceof PatternExpr
6+
or
7+
exists(Expr parent |
8+
isPatternExprDescendant(parent) and
9+
e = parent.getAChildExpr()
10+
)
11+
}

csharp/ql/src/Likely Bugs/DangerousNonShortCircuitLogic.ql

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -15,23 +15,6 @@
1515

1616
import csharp
1717

18-
/** An expression containing a qualified member access, a method call, or an array access. */
19-
class DangerousExpression extends Expr {
20-
DangerousExpression() {
21-
exists(Expr e | this = e.getParent*() |
22-
exists(Expr q | q = e.(MemberAccess).getQualifier() |
23-
not q instanceof ThisAccess and
24-
not q instanceof BaseAccess
25-
)
26-
or
27-
e instanceof MethodCall
28-
or
29-
e instanceof ArrayAccess
30-
) and
31-
not exists(Expr e | this = e.getParent*() | e.(Call).getTarget().getAParameter().isOutOrRef())
32-
}
33-
}
34-
3518
/** A use of `&` or `|` on operands of type boolean. */
3619
class NonShortCircuit extends BinaryBitwiseOperation {
3720
NonShortCircuit() {
@@ -42,10 +25,40 @@ class NonShortCircuit extends BinaryBitwiseOperation {
4225
) and
4326
not exists(AssignBitwiseOperation abo | abo.getExpandedAssignment().getRValue() = this) and
4427
this.getLeftOperand().getType() instanceof BoolType and
45-
this.getRightOperand().getType() instanceof BoolType and
46-
this.getRightOperand() instanceof DangerousExpression
28+
this.getRightOperand().getType() instanceof BoolType
29+
}
30+
31+
pragma[nomagic]
32+
private predicate hasRightOperandDescendant(Expr e) {
33+
e = this.getRightOperand()
34+
or
35+
exists(Expr parent |
36+
this.hasRightOperandDescendant(parent) and
37+
e.getParent() = parent
38+
)
39+
}
40+
41+
/**
42+
* Holds if this non-short-circuit expression contains a qualified member access,
43+
* a method call, or an array access inside the right operand.
44+
*/
45+
predicate isDangerous() {
46+
exists(Expr e | this.hasRightOperandDescendant(e) |
47+
exists(Expr q | q = e.(MemberAccess).getQualifier() |
48+
not q instanceof ThisAccess and
49+
not q instanceof BaseAccess
50+
)
51+
or
52+
e instanceof MethodCall
53+
or
54+
e instanceof ArrayAccess
55+
) and
56+
not exists(Expr e | this.hasRightOperandDescendant(e) |
57+
e.(Call).getTarget().getAParameter().isOutOrRef()
58+
)
4759
}
4860
}
4961

5062
from NonShortCircuit e
63+
where e.isDangerous()
5164
select e, "Potentially dangerous use of non-short circuit logic."

0 commit comments

Comments
 (0)