Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 1d05bcc

Browse files
authored
Merge pull request #952 from calumgrant/cs/non-null-functions
C#: Better call analysis using CIL
2 parents 4075f57 + eafb6d8 commit 1d05bcc

35 files changed

Lines changed: 772 additions & 81 deletions

csharp/extractor/Semmle.Extraction.CIL/Entities/Assembly.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,19 +37,21 @@ public Assembly(Context cx) : base(cx)
3737
if (!def.PublicKey.IsNil)
3838
assemblyName.SetPublicKey(cx.mdReader.GetBlobBytes(def.PublicKey));
3939

40-
ShortId = cx.GetId(assemblyName.FullName) + "#file:///" + cx.assemblyPath.Replace("\\", "/");
40+
ShortId = cx.GetId(FullName) + "#file:///" + cx.assemblyPath.Replace("\\", "/");
4141

4242
file = new File(cx, cx.assemblyPath);
4343
}
4444

4545
static readonly Id suffix = new StringId(";assembly");
4646

47+
string FullName => assemblyName.GetPublicKey() is null ? assemblyName.FullName + ", PublicKeyToken=null" : assemblyName.FullName;
48+
4749
public override IEnumerable<IExtractionProduct> Contents
4850
{
4951
get
5052
{
5153
yield return file;
52-
yield return Tuples.assemblies(this, file, assemblyName.FullName, assemblyName.Name, assemblyName.Version.ToString());
54+
yield return Tuples.assemblies(this, file, FullName, assemblyName.Name, assemblyName.Version.ToString());
5355

5456
if (cx.pdb != null)
5557
{

csharp/ql/src/semmle/code/cil/CIL.qll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,4 @@ import Handler
1717
import ControlFlow
1818
import DataFlow
1919
import Attribute
20+
import Stubs
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/**
2+
* Provides predicates for analysing the return values of callables.
3+
*/
4+
5+
private import CIL
6+
7+
cached
8+
private module Cached {
9+
/** Holds if method `m` always returns null. */
10+
cached
11+
predicate alwaysNullMethod(Method m) { forex(Expr e | m.canReturn(e) | alwaysNullExpr(e)) }
12+
13+
/** Holds if method `m` always returns non-null. */
14+
cached
15+
predicate alwaysNotNullMethod(Method m) { forex(Expr e | m.canReturn(e) | alwaysNotNullExpr(e)) }
16+
17+
/** Holds if method `m` always throws an exception. */
18+
cached
19+
predicate alwaysThrowsMethod(Method m) {
20+
m.hasBody() and
21+
not exists(m.getImplementation().getAnInstruction().(Return))
22+
}
23+
24+
/** Holds if method `m` always throws an exception of type `t`. */
25+
cached
26+
predicate alwaysThrowsException(Method m, Type t) {
27+
alwaysThrowsMethod(m) and
28+
forex(Throw ex | ex = m.getImplementation().getAnInstruction() | t = ex.getExpr().getType())
29+
}
30+
}
31+
import Cached
32+
33+
/** Holds if expression `expr` always evaluates to `null`. */
34+
private predicate alwaysNullExpr(Expr expr) {
35+
expr instanceof NullLiteral
36+
or
37+
alwaysNullMethod(expr.(StaticCall).getTarget())
38+
or
39+
forex(VariableUpdate vu | DefUse::variableUpdateUse(_, vu, expr) |
40+
alwaysNullExpr(vu.getSource())
41+
)
42+
}
43+
44+
/** Holds if expression `expr` always evaluates to non-null. */
45+
private predicate alwaysNotNullExpr(Expr expr) {
46+
expr instanceof Opcodes::Newobj
47+
or
48+
expr instanceof Literal and not expr instanceof NullLiteral
49+
or
50+
alwaysNotNullMethod(expr.(StaticCall).getTarget())
51+
or
52+
forex(VariableUpdate vu | DefUse::variableUpdateUse(_, vu, expr) |
53+
alwaysNotNullExpr(vu.getSource())
54+
)
55+
}

csharp/ql/src/semmle/code/cil/ControlFlow.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,6 @@ class TrueFlow extends FlowType, TTrueFlow {
137137
}
138138

139139
/** False control flow. */
140-
class FalseFlow extends FlowType, TTrueFlow {
140+
class FalseFlow extends FlowType, TFalseFlow {
141141
override string toString() { result = "false" }
142142
}

csharp/ql/src/semmle/code/cil/DataFlow.qll

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ class Tainted extends TaintType, TTaintedValue { }
6060
private predicate localExactStep(DataFlowNode src, DataFlowNode sink) {
6161
src = sink.(Opcodes::Dup).getAnOperand()
6262
or
63-
DefUse::defUse(_, src, sink)
63+
defUse(_, src, sink)
6464
or
6565
src = sink.(ParameterReadAccess).getTarget()
6666
or
@@ -88,7 +88,7 @@ private predicate localTaintStep(DataFlowNode src, DataFlowNode sink) {
8888
}
8989

9090
cached
91-
private module DefUse {
91+
module DefUse {
9292
/**
9393
* A classification of variable references into reads and writes.
9494
*/
@@ -185,21 +185,26 @@ private module DefUse {
185185
)
186186
}
187187

188-
/** Holds if the update `def` can be used at the read `use`. */
188+
/** Holds if the variable update `vu` can be used at the read `use`. */
189189
cached
190-
predicate defUse(StackVariable target, DataFlowNode def, ReadAccess use) {
191-
exists(VariableUpdate vu | def = vu.getSource() |
192-
defReachesReadWithinBlock(target, vu, use)
193-
or
194-
exists(BasicBlock bb, int i |
195-
exists(refRank(bb, i, target, Read())) and
196-
use = bb.getNode(i) and
197-
defReachesEndOfBlock(bb.getAPredecessor(), vu, target) and
198-
not defReachesReadWithinBlock(target, _, use)
199-
)
190+
predicate variableUpdateUse(StackVariable target, VariableUpdate vu, ReadAccess use) {
191+
defReachesReadWithinBlock(target, vu, use)
192+
or
193+
exists(BasicBlock bb, int i |
194+
exists(refRank(bb, i, target, Read())) and
195+
use = bb.getNode(i) and
196+
defReachesEndOfBlock(bb.getAPredecessor(), vu, target) and
197+
not defReachesReadWithinBlock(target, _, use)
200198
)
201199
}
200+
201+
/** Holds if the update `def` can be used at the read `use`. */
202+
cached
203+
predicate defUse(StackVariable target, Expr def, ReadAccess use) {
204+
exists(VariableUpdate vu | def = vu.getSource() | variableUpdateUse(target, vu, use))
205+
}
202206
}
207+
private import DefUse
203208

204209
abstract library class VariableUpdate extends Instruction {
205210
abstract Expr getSource();

csharp/ql/src/semmle/code/cil/InstructionGroups.qll

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,9 @@ class FloatLiteral extends Literal, @cil_ldc_r { }
127127
/** An expression that pushes a `null` value onto the stack. */
128128
class NullLiteral extends Literal, @cil_ldnull { }
129129

130+
/** An expression that pushes a string onto the stack. */
131+
class StringLiteral extends Literal, @cil_ldstr { }
132+
130133
/** A branch with one operand. */
131134
class UnaryBranch extends ConditionalBranch, @cil_unary_jump {
132135
override int getPopCount() { result = 1 }

csharp/ql/src/semmle/code/cil/Instructions.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ module Opcodes {
234234
override string getOpcodeName() { result = "nop" }
235235
}
236236

237-
class Ldstr extends Literal, @cil_ldstr {
237+
class Ldstr extends StringLiteral, @cil_ldstr {
238238
override string getOpcodeName() { result = "ldstr" }
239239

240240
override string getExtra() { result = "\"" + getValue() + "\"" }

csharp/ql/src/semmle/code/cil/Method.qll

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,11 @@ class MethodImplementation extends EntryPoint, @cil_method_implementation {
5050
int getStackSize() { cil_method_stack_size(this, result) }
5151

5252
override string toString() { result = getMethod().toString() }
53+
54+
/** Gets a string representing the disassembly of this implementation. */
55+
string getDisassembly() {
56+
result = concat(Instruction i | i = this.getAnInstruction() | i.toString(), ", " order by i.getIndex())
57+
}
5358
}
5459

5560
/**
@@ -63,6 +68,11 @@ class Method extends DotNet::Callable, Element, Member, TypeContainer, DataFlowN
6368
*/
6469
MethodImplementation getAnImplementation() { result.getMethod() = this }
6570

71+
/** Gets the "best" implementation of this method, if any. */
72+
BestImplementation getImplementation() {
73+
result = getAnImplementation()
74+
}
75+
6676
override Method getMethod() { result = this }
6777

6878
override string getName() { cil_method(this, result, _, _) }
@@ -102,7 +112,7 @@ class Method extends DotNet::Callable, Element, Member, TypeContainer, DataFlowN
102112
int getCallPopCount() { result = count(getRawParameter(_)) }
103113

104114
/** Gets a method called by this method. */
105-
Method getACallee() { result = getAnImplementation().getAnInstruction().(Call).getTarget() }
115+
Method getACallee() { result = getImplementation().getAnInstruction().(Call).getTarget() }
106116

107117
/** Holds if this method is `virtual`. */
108118
predicate isVirtual() { cil_virtual(this) }
@@ -168,10 +178,10 @@ class Method extends DotNet::Callable, Element, Member, TypeContainer, DataFlowN
168178
/** Gets a method that overrides this method, if any. */
169179
final Method getAnOverrider() { result.getOverriddenMethod() = this }
170180

171-
override predicate hasBody() { exists(getAnImplementation()) }
181+
override predicate hasBody() { exists(getImplementation()) }
172182

173183
override predicate canReturn(DotNet::Expr expr) {
174-
exists(Return ret | ret.getImplementation().getMethod() = this and expr = ret.getExpr())
184+
exists(Return ret | ret.getImplementation() = this.getImplementation() and expr = ret.getExpr())
175185
}
176186
}
177187

@@ -198,7 +208,7 @@ class InstanceConstructor extends Constructor {
198208
/** A method that always returns the `this` parameter. */
199209
class ChainingMethod extends Method {
200210
ChainingMethod() {
201-
forex(Return ret | ret = getAnImplementation().getAnInstruction() |
211+
forex(Return ret | ret = getImplementation().getAnInstruction() |
202212
ret.getExpr() instanceof ThisAccess
203213
)
204214
}
@@ -232,7 +242,7 @@ class TrivialGetter extends Method {
232242

233243
/** Gets the underlying field of this getter. */
234244
Field getField() {
235-
getAnImplementation().getAnInstruction().(FieldReadAccess).getTarget() = result
245+
getImplementation().getAnInstruction().(FieldReadAccess).getTarget() = result
236246
}
237247
}
238248

@@ -249,7 +259,7 @@ class Setter extends Accessor {
249259
*/
250260
class TrivialSetter extends Method {
251261
TrivialSetter() {
252-
exists(MethodImplementation impl | impl = getAnImplementation() |
262+
exists(MethodImplementation impl | impl = getImplementation() |
253263
impl.getInstruction(0) instanceof ThisAccess and
254264
impl.getInstruction(1).(ParameterReadAccess).getTarget().getIndex() = 1 and
255265
impl.getInstruction(2) instanceof FieldWriteAccess
@@ -258,7 +268,7 @@ class TrivialSetter extends Method {
258268

259269
/** Gets the underlying field of this setter. */
260270
Field getField() {
261-
result = getAnImplementation().getAnInstruction().(FieldWriteAccess).getTarget()
271+
result = getImplementation().getAnInstruction().(FieldWriteAccess).getTarget()
262272
}
263273
}
264274

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/**
2+
* Provides classes and predicates for identifying stub code.
3+
*/
4+
5+
import CIL
6+
7+
/**
8+
* The average number of instructions per method,
9+
* below which an assembly is probably a stub.
10+
*/
11+
private float stubInstructionThreshold() { result = 5.1 }
12+
13+
cached
14+
private module Cached {
15+
/**
16+
* A simple heuristic for determining whether an assembly is a
17+
* reference assembly where the method bodies have dummy implementations.
18+
* Look at the average number of instructions per method.
19+
*/
20+
cached
21+
predicate assemblyIsStubImpl(Assembly asm) {
22+
exists(int totalInstructions, int totalImplementations |
23+
totalInstructions = count(Instruction i | i.getImplementation().getLocation() = asm) and
24+
totalImplementations = count(MethodImplementation i |
25+
i.getImplementation().getLocation() = asm
26+
) and
27+
totalInstructions.(float) / totalImplementations.(float) < stubInstructionThreshold()
28+
)
29+
}
30+
31+
cached
32+
predicate bestImplementation(MethodImplementation mi) {
33+
not assemblyIsStubImpl(mi.getLocation()) and
34+
not exists(MethodImplementation better | mi.getMethod() = better.getMethod() |
35+
mi.getNumberOfInstructions() < better.getNumberOfInstructions()
36+
or
37+
mi.getNumberOfInstructions() = better.getNumberOfInstructions() and
38+
mi.getLocation().getFile().toString() > better.getLocation().getFile().toString()
39+
) and
40+
exists(mi.getAnInstruction())
41+
}
42+
}
43+
private import Cached
44+
45+
predicate assemblyIsStub = assemblyIsStubImpl/1;
46+
47+
/**
48+
* A method implementation that is the "best" one for a particular method,
49+
* if there are several potential implementations to choose between, and
50+
* excludes implementations that are probably from stub/reference assemblies.
51+
*/
52+
class BestImplementation extends MethodImplementation {
53+
BestImplementation() { bestImplementation(this) }
54+
}

csharp/ql/src/semmle/code/csharp/Generics.qll

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -126,11 +126,7 @@ class TypeParameter extends DotNet::TypeParameter, Type, @type_parameter {
126126
/** Gets the constraints on this type parameter, if any. */
127127
TypeParameterConstraints getConstraints() { result.getTypeParameter() = this }
128128

129-
/**
130-
* Holds if this type parameter is guaranteed to always be instantiated
131-
* to a reference type.
132-
*/
133-
predicate isRefType() {
129+
override predicate isRefType() {
134130
exists(TypeParameterConstraints tpc | tpc = getConstraints() |
135131
tpc.hasRefTypeConstraint() or
136132
exists(tpc.getClassConstraint()) or
@@ -139,11 +135,7 @@ class TypeParameter extends DotNet::TypeParameter, Type, @type_parameter {
139135
)
140136
}
141137

142-
/**
143-
* Holds if this type parameter is guaranteed to always be instantiated
144-
* to a value type.
145-
*/
146-
predicate isValueType() {
138+
override predicate isValueType() {
147139
exists(TypeParameterConstraints tpc | tpc = getConstraints() |
148140
tpc.hasValueTypeConstraint() or
149141
tpc.getATypeParameterConstraint().isValueType()

0 commit comments

Comments
 (0)