@@ -23,7 +23,7 @@ private class RxJsSubscribeStep extends TaintTracking::SharedTaintStep {
23
23
* created by the `map` call.
24
24
*/
25
25
private DataFlow:: Node pipeInput ( DataFlow:: CallNode pipe ) {
26
- pipe = DataFlow:: moduleMember ( "rxjs/operators" , [ "map" , "filter" ] ) .getACall ( ) and
26
+ pipe = DataFlow:: moduleMember ( "rxjs/operators" , any ( string s | not s = "catchError" ) ) .getACall ( ) and
27
27
result = pipe .getCallback ( 0 ) .getParameter ( 0 )
28
28
}
29
29
@@ -34,7 +34,8 @@ private DataFlow::Node pipeInput(DataFlow::CallNode pipe) {
34
34
* the pipe.
35
35
*/
36
36
private DataFlow:: Node pipeOutput ( DataFlow:: CallNode pipe ) {
37
- pipe = DataFlow:: moduleMember ( "rxjs/operators" , "map" ) .getACall ( ) and
37
+ // we assume if there is a return, it is an output.
38
+ pipe = DataFlow:: moduleMember ( "rxjs/operators" , _) .getACall ( ) and
38
39
result = pipe .getCallback ( 0 ) .getReturnNode ( )
39
40
or
40
41
pipe = DataFlow:: moduleMember ( "rxjs/operators" , "filter" ) .getACall ( ) and
@@ -48,30 +49,56 @@ private DataFlow::Node pipeOutput(DataFlow::CallNode pipe) {
48
49
* be special-cased.
49
50
*/
50
51
private predicate isIdentityPipe ( DataFlow:: CallNode pipe ) {
51
- pipe = DataFlow:: moduleMember ( "rxjs/operators" , "catchError" ) .getACall ( )
52
+ pipe = DataFlow:: moduleMember ( "rxjs/operators" , [ "catchError" , "tap" ] ) .getACall ( )
53
+ }
54
+
55
+ /**
56
+ * A call to `pipe`, which is assumed to be an `rxjs/operators` pipe.
57
+ *
58
+ * Has utility methods `getInput`/`getOutput` to get the input/output of each
59
+ * element of the pipe.
60
+ * These utility methods automatically handle itentity pipes, and the
61
+ * first/last elements of the pipe.
62
+ */
63
+ private class RxJSPipe extends DataFlow:: MethodCallNode {
64
+ RxJSPipe ( ) { this .getMethodName ( ) = "pipe" }
65
+
66
+ /**
67
+ * Gets an input to pipe element `i`.
68
+ * Or if `i` is equal to the number of elements, gets the output of the pipe (the call itself)
69
+ */
70
+ DataFlow:: Node getInput ( int i ) {
71
+ result = pipeInput ( this .getArgument ( i ) .getALocalSource ( ) )
72
+ or
73
+ i = this .getNumArgument ( ) and
74
+ result = this
75
+ }
76
+
77
+ /**
78
+ * Gets an output from pipe element `i`.
79
+ * Handles identity pipes by getting the output from the previous element.
80
+ * If `i` is -1, gets the receiver to the call, which started the pipe.
81
+ */
82
+ DataFlow:: Node getOutput ( int i ) {
83
+ isIdentityPipe ( this .getArgument ( i ) .getALocalSource ( ) ) and
84
+ result = getOutput ( i - 1 )
85
+ or
86
+ not isIdentityPipe ( this .getArgument ( i ) .getALocalSource ( ) ) and
87
+ result = pipeOutput ( this .getArgument ( i ) .getALocalSource ( ) )
88
+ or
89
+ i = - 1 and
90
+ result = this .getReceiver ( )
91
+ }
52
92
}
53
93
54
94
/**
55
95
* A step in or out of the map callback in a call of form `x.pipe(map(y => ...))`.
56
96
*/
57
97
private class RxJsPipeMapStep extends TaintTracking:: SharedTaintStep {
58
98
override predicate heapStep ( DataFlow:: Node pred , DataFlow:: Node succ ) {
59
- exists ( DataFlow:: MethodCallNode call | call .getMethodName ( ) = "pipe" |
60
- pred = call .getReceiver ( ) and
61
- succ = pipeInput ( call .getArgument ( 0 ) .getALocalSource ( ) )
62
- or
63
- exists ( int i |
64
- pred = pipeOutput ( call .getArgument ( i ) .getALocalSource ( ) ) and
65
- succ = pipeInput ( call .getArgument ( i + 1 ) .getALocalSource ( ) )
66
- )
67
- or
68
- pred = pipeOutput ( call .getLastArgument ( ) .getALocalSource ( ) ) and
69
- succ = call
70
- or
71
- // Handle a common case where the last step is `catchError`.
72
- isIdentityPipe ( call .getLastArgument ( ) .getALocalSource ( ) ) and
73
- pred = pipeOutput ( call .getArgument ( call .getNumArgument ( ) - 2 ) ) and
74
- succ = call
99
+ exists ( RxJSPipe pipe , int i |
100
+ pred = pipe .getOutput ( i ) and
101
+ succ = pipe .getInput ( i + 1 )
75
102
)
76
103
}
77
104
}
0 commit comments