Skip to content

Commit d12dfab

Browse files
committed
C#: Use synthetic globals instead of jump returns in the EntityFramework implementation.
1 parent cd251f4 commit d12dfab

File tree

2 files changed

+135
-25
lines changed

2 files changed

+135
-25
lines changed

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,9 @@ module SummaryComponentStack {
121121
SummaryComponentStack syntheticGlobal(string synthetic) {
122122
result = singleton(SummaryComponent::syntheticGlobal(synthetic))
123123
}
124+
125+
/** Gets a textual representation of this stack used for flow summaries. */
126+
string getComponentStack(SummaryComponentStack s) { result = Impl::Public::getComponentStack(s) }
124127
}
125128

126129
class SummarizedCallable = Impl::Public::SummarizedCallable;

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

Lines changed: 132 additions & 25 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
@@ -334,6 +335,18 @@ module EntityFramework {
334335
result.getName().matches("SaveChanges%")
335336
}
336337

338+
/**
339+
* Gets the string representation for synthetic identifiers for SaveChanges methods
340+
* on this.
341+
*/
342+
string getSyntheticNames() {
343+
exists(string qualifier, string type, string name |
344+
this.getASaveChanges().hasQualifiedName(qualifier, type, name)
345+
|
346+
result = getQualifiedName(qualifier, type, name)
347+
)
348+
}
349+
337350
/** Holds if component stack `head :: tail` is required for the input specification. */
338351
predicate requiresComponentStackIn(
339352
Content head, Type headType, SummaryComponentStack tail, int dist
@@ -351,56 +364,150 @@ module EntityFramework {
351364

352365
/** Holds if component stack `head :: tail` is required for the output specification. */
353366
predicate requiresComponentStackOut(
354-
Content head, Type headType, SummaryComponentStack tail, int dist
367+
Content head, Type headType, SummaryComponentStack tail, int dist,
368+
DbContextClassSetProperty dbSetProp
355369
) {
356-
exists(Property dbSetProp, PropertyContent c1 |
370+
exists(PropertyContent c1 |
357371
dbSetProp = this.getADbSetProperty(headType) and
358372
this.stepRev(c1, _, head, headType, 0) and
359373
c1.getProperty() = dbSetProp and
360-
tail = SummaryComponentStack::jump(dbSetProp.getGetter()) and
374+
tail = SummaryComponentStack::return() and
361375
dist = 0
362376
)
363377
or
364378
exists(Content tailHead, SummaryComponentStack tailTail, Type tailType |
365-
this.requiresComponentStackOut(tailHead, tailType, tailTail, dist - 1) and
379+
this.requiresComponentStackOut(tailHead, tailType, tailTail, dist - 1, dbSetProp) and
366380
tail = SummaryComponentStack::push(SummaryComponent::content(tailHead), tailTail) and
367381
this.stepRev(tailHead, tailType, head, headType, dist)
368382
)
369383
}
370384
}
371385

372-
private class DbContextSaveChanges extends EFSummarizedCallable {
386+
private class DbContextClassSetProperty extends Property {
373387
private DbContextClass c;
374388

375-
DbContextSaveChanges() { this = c.getASaveChanges() }
389+
DbContextClassSetProperty() { this = c.getADbSetProperty(_) }
376390

377-
pragma[noinline]
378-
private predicate input(SummaryComponentStack input, Property mapped) {
379-
exists(PropertyContent head, SummaryComponentStack tail |
380-
c.requiresComponentStackIn(head, _, tail, _) and
381-
head.getProperty() = mapped and
382-
mapped = c.getAColumnProperty(_) and
383-
input = SummaryComponentStack::push(SummaryComponent::content(head), tail)
391+
/**
392+
* Gets the string representation for a synthetic identifier for this.
393+
*/
394+
string getSyntheticName() {
395+
exists(string qualifier, string type, string name |
396+
this.hasQualifiedName(qualifier, type, name)
397+
|
398+
result = getQualifiedName(qualifier, type, name)
384399
)
385400
}
386401

387-
pragma[noinline]
388-
private predicate output(SummaryComponentStack output, Property mapped) {
389-
exists(PropertyContent head, SummaryComponentStack tail |
390-
c.requiresComponentStackOut(head, _, tail, _) and
391-
head.getProperty() = mapped and
392-
mapped = c.getAColumnProperty(_) and
393-
output = SummaryComponentStack::push(SummaryComponent::content(head), tail)
402+
/**
403+
* Gets the context class where this is a Db set property.
404+
*/
405+
DbContextClass getDbContextClass() { result = c }
406+
}
407+
408+
/**
409+
* Holds if `input` is a valid summary component stack for property `mapped`
410+
* for the context class `c`.
411+
*/
412+
pragma[noinline]
413+
predicate input(DbContextClass c, SummaryComponentStack input, Property mapped) {
414+
exists(PropertyContent head, SummaryComponentStack tail |
415+
c.requiresComponentStackIn(head, _, tail, _) and
416+
head.getProperty() = mapped and
417+
mapped = c.getAColumnProperty(_) and
418+
input = SummaryComponentStack::push(SummaryComponent::content(head), tail)
419+
)
420+
}
421+
422+
/**
423+
* Holds if `output` is a valid summary component stack for the getter of `dbSet`
424+
* for property `mapped` for the context class `c`.
425+
*/
426+
pragma[noinline]
427+
predicate output(
428+
DbContextClass c, SummaryComponentStack output, Property mapped, DbContextClassSetProperty dbSet
429+
) {
430+
exists(PropertyContent head, SummaryComponentStack tail |
431+
c.requiresComponentStackOut(head, _, tail, _, dbSet) and
432+
head.getProperty() = mapped and
433+
mapped = c.getAColumnProperty(_) and
434+
output = SummaryComponentStack::push(SummaryComponent::content(head), tail)
435+
)
436+
}
437+
438+
bindingset[save, prop, stack1, stack2]
439+
private string getFullSyntheticName(
440+
string save, string prop, SummaryComponentStack stack1, SummaryComponentStack stack2
441+
) {
442+
result =
443+
save + "#" //
444+
+ prop + "#" //
445+
+ SummaryComponentStack::getComponentStack(stack1) + "#" //
446+
+ SummaryComponentStack::getComponentStack(stack2)
447+
}
448+
449+
private class DbContextClassSetPropertySynthetic extends EFSummarizedCallable {
450+
private DbContextClassSetProperty p;
451+
452+
DbContextClassSetPropertySynthetic() { this = p.getGetter() }
453+
454+
override predicate propagatesFlow(
455+
SummaryComponentStack input, SummaryComponentStack output, boolean preservesValue
456+
) {
457+
exists(SummaryComponentStack synthetic, string name, DbContextClass c, Property mapped |
458+
preservesValue = true and
459+
c = p.getDbContextClass() and
460+
input(c, synthetic, mapped) and
461+
output(c, output, mapped, p) and
462+
name = getFullSyntheticName(c.getSyntheticNames(), p.getSyntheticName(), synthetic, output) and
463+
input = SummaryComponentStack::syntheticGlobal(name)
464+
)
465+
}
466+
}
467+
468+
private class DbContextSaveChanges extends EFSummarizedCallable {
469+
private DbContextClass c;
470+
471+
DbContextSaveChanges() { this = c.getASaveChanges() }
472+
473+
private string getSyntheticName() {
474+
exists(string qualifier, string type, string name |
475+
this.(Method).hasQualifiedName(qualifier, type, name)
476+
|
477+
result = getQualifiedName(qualifier, type, name)
394478
)
395479
}
396480

397481
override predicate propagatesFlow(
398482
SummaryComponentStack input, SummaryComponentStack output, boolean preservesValue
399483
) {
400-
exists(Property mapped |
484+
exists(
485+
SummaryComponentStack synthetic, string name, Property mapped,
486+
DbContextClassSetProperty dbSet
487+
|
401488
preservesValue = true and
402-
this.input(input, mapped) and
403-
this.output(output, mapped)
489+
input(c, input, mapped) and
490+
output(c, synthetic, mapped, dbSet) and
491+
name =
492+
getFullSyntheticName(this.getSyntheticName(), dbSet.getSyntheticName(), input, synthetic) and
493+
output = SummaryComponentStack::syntheticGlobal(name)
494+
)
495+
}
496+
}
497+
498+
/**
499+
* Add all `DbContext` property names as potential synthetic globals.
500+
*/
501+
private class EFSummarizedCallableSyntheticGlobal extends SummaryComponent::SyntheticGlobal {
502+
EFSummarizedCallableSyntheticGlobal() {
503+
exists(
504+
DbContextClass c, SummaryComponentStack input, SummaryComponentStack output,
505+
Property mapped, DbContextClassSetProperty dbSet
506+
|
507+
input(c, input, mapped) and
508+
output(c, output, mapped, dbSet)
509+
|
510+
this = getFullSyntheticName(c.getSyntheticNames(), dbSet.getSyntheticName(), input, output)
404511
)
405512
}
406513
}
@@ -411,7 +518,7 @@ module EntityFramework {
411518
exists(Content c | head = SummaryComponent::content(c) |
412519
any(DbContextClass cls).requiresComponentStackIn(c, _, tail, _)
413520
or
414-
any(DbContextClass cls).requiresComponentStackOut(c, _, tail, _)
521+
any(DbContextClass cls).requiresComponentStackOut(c, _, tail, _, _)
415522
)
416523
}
417524
}

0 commit comments

Comments
 (0)