Skip to content

Commit afec9b0

Browse files
authored
Merge pull request github#13147 from michaelnebel/csharp/entityframeworkrefactor
C#: Use synthetic global in the EntityFramework code instead of jump steps.
2 parents f737054 + 2200a2a commit afec9b0

File tree

12 files changed

+291
-145
lines changed

12 files changed

+291
-145
lines changed

csharp/ql/lib/semmle/code/csharp/dataflow/FlowSummary.qll

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,10 @@ module SummaryComponent {
7272
jrk.getTargetReturnKind() instanceof DataFlowDispatch::NormalReturnKind
7373
))
7474
}
75+
76+
predicate syntheticGlobal = SummaryComponentInternal::syntheticGlobal/1;
77+
78+
class SyntheticGlobal = SummaryComponentInternal::SyntheticGlobal;
7579
}
7680

7781
class SummaryComponentStack = Impl::Public::SummaryComponentStack;
@@ -112,6 +116,14 @@ module SummaryComponentStack {
112116

113117
/** Gets a singleton stack representing a jump to `c`. */
114118
SummaryComponentStack jump(Callable c) { result = singleton(SummaryComponent::jump(c)) }
119+
120+
/** Gets a singleton stack representing a synthetic global with name `name`. */
121+
SummaryComponentStack syntheticGlobal(string synthetic) {
122+
result = singleton(SummaryComponent::syntheticGlobal(synthetic))
123+
}
124+
125+
/** Gets a textual representation of this stack used for flow summaries. */
126+
string getComponentStack(SummaryComponentStack s) { result = Impl::Public::getComponentStack(s) }
115127
}
116128

117129
class SummarizedCallable = Impl::Public::SummarizedCallable;

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,11 @@ module Public {
180180
result = "Argument[" + getParameterPosition(pos) + "]"
181181
)
182182
or
183+
exists(string synthetic |
184+
sc = TSyntheticGlobalSummaryComponent(synthetic) and
185+
result = "SyntheticGlobal[" + synthetic + "]"
186+
)
187+
or
183188
sc = TReturnSummaryComponent(getReturnValueKind()) and result = "ReturnValue"
184189
}
185190

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,13 @@ class SummarizedCallableBase extends Callable {
1919
SummarizedCallableBase() { this.isUnboundDeclaration() }
2020
}
2121

22+
/**
23+
* A module for importing frameworks that define synthetic globals.
24+
*/
25+
private module SyntheticGlobals {
26+
private import semmle.code.csharp.frameworks.EntityFramework
27+
}
28+
2229
DataFlowCallable inject(SummarizedCallable c) { result.asSummarizedCallable() = c }
2330

2431
/** Gets the parameter position of the instance parameter. */

csharp/ql/lib/semmle/code/csharp/frameworks/EntityFramework.qll

Lines changed: 92 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import csharp
66
private import DataFlow
7+
private import semmle.code.csharp.commons.QualifiedName
78
private import semmle.code.csharp.frameworks.System
89
private import semmle.code.csharp.frameworks.system.data.Entity
910
private import semmle.code.csharp.frameworks.system.collections.Generic
@@ -236,7 +237,7 @@ module EntityFramework {
236237
* }
237238
* ```
238239
*/
239-
private Property getADbSetProperty(Class elementType) {
240+
Property getADbSetProperty(Class elementType) {
240241
exists(ConstructedClass c |
241242
result.getType() = c and
242243
c.getUnboundDeclaration() instanceof DbSet and
@@ -351,67 +352,137 @@ module EntityFramework {
351352

352353
/** Holds if component stack `head :: tail` is required for the output specification. */
353354
predicate requiresComponentStackOut(
354-
Content head, Type headType, SummaryComponentStack tail, int dist
355+
Content head, Type headType, SummaryComponentStack tail, int dist,
356+
DbContextClassSetProperty dbSetProp
355357
) {
356-
exists(Property dbSetProp, PropertyContent c1 |
358+
exists(PropertyContent c1 |
357359
dbSetProp = this.getADbSetProperty(headType) and
358360
this.stepRev(c1, _, head, headType, 0) and
359361
c1.getProperty() = dbSetProp and
360-
tail = SummaryComponentStack::jump(dbSetProp.getGetter()) and
362+
tail = SummaryComponentStack::return() and
361363
dist = 0
362364
)
363365
or
364366
exists(Content tailHead, SummaryComponentStack tailTail, Type tailType |
365-
this.requiresComponentStackOut(tailHead, tailType, tailTail, dist - 1) and
367+
this.requiresComponentStackOut(tailHead, tailType, tailTail, dist - 1, dbSetProp) and
366368
tail = SummaryComponentStack::push(SummaryComponent::content(tailHead), tailTail) and
367369
this.stepRev(tailHead, tailType, head, headType, dist)
368370
)
369371
}
370-
}
371-
372-
private class DbContextSaveChanges extends EFSummarizedCallable {
373-
private DbContextClass c;
374-
375-
DbContextSaveChanges() { this = c.getASaveChanges() }
376372

373+
/**
374+
* Holds if `input` is a valid summary component stack for property `mapped` for this.
375+
*/
377376
pragma[noinline]
378-
private predicate input(SummaryComponentStack input, Property mapped) {
377+
predicate input(SummaryComponentStack input, Property mapped) {
379378
exists(PropertyContent head, SummaryComponentStack tail |
380-
c.requiresComponentStackIn(head, _, tail, _) and
379+
this.requiresComponentStackIn(head, _, tail, _) and
381380
head.getProperty() = mapped and
382-
mapped = c.getAColumnProperty(_) and
381+
mapped = this.getAColumnProperty(_) and
383382
input = SummaryComponentStack::push(SummaryComponent::content(head), tail)
384383
)
385384
}
386385

386+
/**
387+
* Holds if `output` is a valid summary component stack for the getter of `dbSet`
388+
* for property `mapped` for this.
389+
*/
387390
pragma[noinline]
388-
private predicate output(SummaryComponentStack output, Property mapped) {
391+
private predicate output(
392+
SummaryComponentStack output, Property mapped, DbContextClassSetProperty dbSet
393+
) {
389394
exists(PropertyContent head, SummaryComponentStack tail |
390-
c.requiresComponentStackOut(head, _, tail, _) and
395+
this.requiresComponentStackOut(head, _, tail, _, dbSet) and
391396
head.getProperty() = mapped and
392-
mapped = c.getAColumnProperty(_) and
397+
mapped = this.getAColumnProperty(_) and
393398
output = SummaryComponentStack::push(SummaryComponent::content(head), tail)
394399
)
395400
}
396401

402+
/**
403+
* Gets the synthetic name for the getter of `dbSet` for property `mapped` for this,
404+
* where `output` is a valid summary component stack for the getter of `dbSet`
405+
* for the property `mapped`.
406+
*/
407+
pragma[nomagic]
408+
string getSyntheticName(
409+
SummaryComponentStack output, Property mapped, DbContextClassSetProperty dbSet
410+
) {
411+
this = dbSet.getDbContextClass() and
412+
this.output(output, mapped, dbSet) and
413+
result = dbSet.getFullName() + "#" + SummaryComponentStack::getComponentStack(output)
414+
}
415+
}
416+
417+
private class DbContextClassSetProperty extends Property {
418+
private DbContextClass c;
419+
420+
DbContextClassSetProperty() { this = c.getADbSetProperty(_) }
421+
422+
/**
423+
* Gets the fully qualified name for this.
424+
*/
425+
string getFullName() {
426+
exists(string qualifier, string type, string name |
427+
this.hasQualifiedName(qualifier, type, name)
428+
|
429+
result = getQualifiedName(qualifier, type, name)
430+
)
431+
}
432+
433+
/**
434+
* Gets the context class where this is a DbSet property.
435+
*/
436+
DbContextClass getDbContextClass() { result = c }
437+
}
438+
439+
private class DbContextClassSetPropertySynthetic extends EFSummarizedCallable {
440+
private DbContextClassSetProperty p;
441+
442+
DbContextClassSetPropertySynthetic() { this = p.getGetter() }
443+
397444
override predicate propagatesFlow(
398445
SummaryComponentStack input, SummaryComponentStack output, boolean preservesValue
399446
) {
400-
exists(Property mapped |
447+
exists(string name, DbContextClass c |
401448
preservesValue = true and
402-
this.input(input, mapped) and
403-
this.output(output, mapped)
449+
name = c.getSyntheticName(output, _, p) and
450+
input = SummaryComponentStack::syntheticGlobal(name)
404451
)
405452
}
406453
}
407454

455+
private class DbContextSaveChanges extends EFSummarizedCallable {
456+
private DbContextClass c;
457+
458+
DbContextSaveChanges() { this = c.getASaveChanges() }
459+
460+
override predicate propagatesFlow(
461+
SummaryComponentStack input, SummaryComponentStack output, boolean preservesValue
462+
) {
463+
exists(string name, Property mapped |
464+
preservesValue = true and
465+
c.input(input, mapped) and
466+
name = c.getSyntheticName(_, mapped, _) and
467+
output = SummaryComponentStack::syntheticGlobal(name)
468+
)
469+
}
470+
}
471+
472+
/**
473+
* Add all possible synthetic global names.
474+
*/
475+
private class EFSummarizedCallableSyntheticGlobal extends SummaryComponent::SyntheticGlobal {
476+
EFSummarizedCallableSyntheticGlobal() { this = any(DbContextClass c).getSyntheticName(_, _, _) }
477+
}
478+
408479
private class DbContextSaveChangesRequiredSummaryComponentStack extends RequiredSummaryComponentStack
409480
{
410481
override predicate required(SummaryComponent head, SummaryComponentStack tail) {
411482
exists(Content c | head = SummaryComponent::content(c) |
412483
any(DbContextClass cls).requiresComponentStackIn(c, _, tail, _)
413484
or
414-
any(DbContextClass cls).requiresComponentStackOut(c, _, tail, _)
485+
any(DbContextClass cls).requiresComponentStackOut(c, _, tail, _, _)
415486
)
416487
}
417488
}

csharp/ql/test/library-tests/frameworks/EntityFramework/EntityFramework.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
namespace EFTests
77
{
8-
class Person
8+
public class Person
99
{
1010
public virtual int Id { get; set; }
1111
public virtual string Name { get; set; }
@@ -17,13 +17,13 @@ class Person
1717
public ICollection<Address> Addresses { get; set; }
1818
}
1919

20-
class Address
20+
public class Address
2121
{
2222
public int Id { get; set; }
2323
public string Street { get; set; }
2424
}
2525

26-
class PersonAddressMap
26+
public class PersonAddressMap
2727
{
2828
public int Id { get; set; }
2929
public int PersonId { get; set; }
@@ -34,7 +34,7 @@ class PersonAddressMap
3434
public Address Address { get; set; }
3535
}
3636

37-
class MyContext : DbContext
37+
public class MyContext : DbContext
3838
{
3939
public virtual DbSet<Person> Persons { get; set; }
4040
public virtual DbSet<Address> Addresses { get; set; }

csharp/ql/test/library-tests/frameworks/EntityFramework/EntityFrameworkCore.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
namespace EFCoreTests
88
{
9-
class Person
9+
public class Person
1010
{
1111
public virtual int Id { get; set; }
1212
public virtual string Name { get; set; }
@@ -18,13 +18,13 @@ class Person
1818
public ICollection<Address> Addresses { get; set; }
1919
}
2020

21-
class Address
21+
public class Address
2222
{
2323
public int Id { get; set; }
2424
public string Street { get; set; }
2525
}
2626

27-
class PersonAddressMap
27+
public class PersonAddressMap
2828
{
2929
public int Id { get; set; }
3030
public int PersonId { get; set; }
@@ -35,7 +35,7 @@ class PersonAddressMap
3535
public Address Address { get; set; }
3636
}
3737

38-
class MyContext : DbContext
38+
public class MyContext : DbContext
3939
{
4040
public virtual DbSet<Person> Persons { get; set; }
4141
public virtual DbSet<Address> Addresses { get; set; }

0 commit comments

Comments
 (0)