@@ -18,60 +18,91 @@ import Dispose
1818import semmle.code.csharp.frameworks.System
1919import semmle.code.csharp.commons.Disposal
2020
21- /** Holds if expression `e` escapes the local method scope. */
22- predicate escapes ( Expr e ) {
23- // Things that return escape
24- exists ( Callable c | c .canReturn ( e ) or c .canYieldReturn ( e ) )
25- or
26- // Things that are assigned to fields, properties, or indexers escape the scope
27- exists ( AssignableDefinition def , Assignable a |
28- def .getSource ( ) = e and
29- a = def .getTarget ( )
30- |
31- a instanceof Field or
32- a instanceof Property or
33- a instanceof Indexer
34- )
35- or
36- // Things that are added to a collection of some kind are likely to escape the scope
37- exists ( MethodCall mc | mc .getTarget ( ) .hasName ( "Add" ) | mc .getAnArgument ( ) = e )
21+ private class ReturnNode extends DataFlow:: ExprNode {
22+ ReturnNode ( ) {
23+ exists ( Callable c , Expr e | e = this .getExpr ( ) | c .canReturn ( e ) or c .canYieldReturn ( e ) )
24+ }
3825}
3926
40- /** Holds if the `disposable` is a whitelisted result. */
41- predicate isWhitelisted ( LocalScopeDisposableCreation disposable ) {
42- exists ( MethodCall mc |
43- // Close can often be used instead of Dispose
44- mc .getTarget ( ) .hasName ( "Close" )
27+ private class Conf extends DataFlow:: Configuration {
28+ Conf ( ) { this = "NoDisposeCallOnLocalIDisposable" }
29+
30+ override predicate isSource ( DataFlow:: Node node ) {
31+ node .asExpr ( ) = any ( LocalScopeDisposableCreation disposable |
32+ // Only care about library types - user types often have spurious IDisposable declarations
33+ disposable .getType ( ) .fromLibrary ( ) and
34+ // WebControls are usually disposed automatically
35+ not disposable .getType ( ) instanceof WebControl
36+ )
37+ }
38+
39+ override predicate isSink ( DataFlow:: Node node ) {
40+ // Things that return may be disposed elsewhere
41+ node instanceof ReturnNode
4542 or
46- // Used as an alias for Dispose
47- mc .getTarget ( ) .hasName ( "Clear" )
48- |
49- mc .getQualifier ( ) = disposable .getADisposeTarget ( )
43+ exists ( Expr e | e = node .asExpr ( ) |
44+ // Disposables constructed in the initializer of a `using` are safe
45+ exists ( UsingStmt us | us .getAnExpr ( ) = e )
46+ or
47+ // Foreach calls Dispose
48+ exists ( ForeachStmt fs | fs .getIterableExpr ( ) = e )
49+ or
50+ // As are disposables on which the Dispose method is called explicitly
51+ exists ( MethodCall mc |
52+ mc .getTarget ( ) instanceof DisposeMethod and
53+ mc .getQualifier ( ) = e
54+ )
55+ or
56+ // A disposing method
57+ exists ( Call c , Parameter p | e = c .getArgumentForParameter ( p ) | mayBeDisposed ( p ) )
58+ or
59+ // Things that are assigned to fields, properties, or indexers may be disposed
60+ exists ( AssignableDefinition def , Assignable a |
61+ def .getSource ( ) = e and
62+ a = def .getTarget ( )
63+ |
64+ a instanceof Field or
65+ a instanceof Property or
66+ a instanceof Indexer
67+ )
68+ or
69+ // Things that are added to a collection of some kind are likely to escape the scope
70+ exists ( MethodCall mc | mc .getAnArgument ( ) = e | mc .getTarget ( ) .hasName ( "Add" ) )
71+ or
72+ exists ( MethodCall mc | mc .getQualifier ( ) = e |
73+ // Close can often be used instead of Dispose
74+ mc .getTarget ( ) .hasName ( "Close" )
75+ or
76+ // Used as an alias for Dispose
77+ mc .getTarget ( ) .hasName ( "Clear" )
78+ )
79+ )
80+ }
81+
82+ override predicate isAdditionalFlowStep ( DataFlow:: Node node1 , DataFlow:: Node node2 ) {
83+ node2 .asExpr ( ) = any ( LocalScopeDisposableCreation other | other .getAnArgument ( ) = node1 .asExpr ( ) )
84+ }
85+
86+ override predicate isBarrierOut ( DataFlow:: Node node ) {
87+ this .isSink ( node ) and
88+ not node instanceof ReturnNode
89+ }
90+ }
91+
92+ /** Holds if `disposable` may not be disposed. */
93+ predicate mayNotBeDiposed ( LocalScopeDisposableCreation disposable ) {
94+ exists ( Conf conf , DataFlow:: ExprNode e |
95+ e .getExpr ( ) = disposable and
96+ conf .isSource ( e ) and
97+ not exists ( DataFlow:: Node sink |
98+ conf .hasFlow ( DataFlow:: exprNode ( disposable ) , sink ) |
99+ sink instanceof ReturnNode
100+ implies
101+ sink .getEnclosingCallable ( ) = disposable .getEnclosingCallable ( )
102+ )
50103 )
51- or
52- // WebControls are usually disposed automatically
53- disposable .getType ( ) instanceof WebControl
54104}
55105
56106from LocalScopeDisposableCreation disposable
57- // The disposable is local scope - the lifetime is the execution of this method
58- where
59- not escapes ( disposable .getADisposeTarget ( ) ) and
60- // Only care about library types - user types often have spurious IDisposable declarations
61- disposable .getType ( ) .fromLibrary ( ) and
62- // Disposables constructed in the initializer of a `using` are safe
63- not exists ( UsingStmt us | us .getAnExpr ( ) = disposable .getADisposeTarget ( ) ) and
64- // Foreach calls Dispose
65- not exists ( ForeachStmt fs | fs .getIterableExpr ( ) = disposable .getADisposeTarget ( ) ) and
66- // As are disposables on which the Dispose method is called explicitly
67- not exists ( MethodCall mc |
68- mc .getTarget ( ) instanceof DisposeMethod and
69- mc .getQualifier ( ) = disposable .getADisposeTarget ( )
70- ) and
71- // Ignore whitelisted results
72- not isWhitelisted ( disposable ) and
73- // Not passed to a disposing method
74- not exists ( Call c , Parameter p | disposable .getADisposeTarget ( ) = c .getArgumentForParameter ( p ) |
75- mayBeDisposed ( p )
76- )
107+ where mayNotBeDiposed ( disposable )
77108select disposable , "Disposable '" + disposable .getType ( ) + "' is created here but is not disposed."
0 commit comments