@@ -23,7 +23,7 @@ private class RxJsSubscribeStep extends TaintTracking::SharedTaintStep {
2323 * created by the `map` call.
2424 */
2525private 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
2727 result = pipe .getCallback ( 0 ) .getParameter ( 0 )
2828}
2929
@@ -34,7 +34,8 @@ private DataFlow::Node pipeInput(DataFlow::CallNode pipe) {
3434 * the pipe.
3535 */
3636private 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
3839 result = pipe .getCallback ( 0 ) .getReturnNode ( )
3940 or
4041 pipe = DataFlow:: moduleMember ( "rxjs/operators" , "filter" ) .getACall ( ) and
@@ -48,30 +49,56 @@ private DataFlow::Node pipeOutput(DataFlow::CallNode pipe) {
4849 * be special-cased.
4950 */
5051private 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+ }
5292}
5393
5494/**
5595 * A step in or out of the map callback in a call of form `x.pipe(map(y => ...))`.
5696 */
5797private class RxJsPipeMapStep extends TaintTracking:: SharedTaintStep {
5898 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 )
75102 )
76103 }
77104}
0 commit comments