@@ -18,60 +18,91 @@ import Dispose
18
18
import semmle.code.csharp.frameworks.System
19
19
import semmle.code.csharp.commons.Disposal
20
20
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
+ }
38
25
}
39
26
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
45
42
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
+ )
50
103
)
51
- or
52
- // WebControls are usually disposed automatically
53
- disposable .getType ( ) instanceof WebControl
54
104
}
55
105
56
106
from 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 )
77
108
select disposable , "Disposable '" + disposable .getType ( ) + "' is created here but is not disposed."
0 commit comments