@@ -858,6 +858,107 @@ module NodeJSLib {
858858 }
859859 }
860860
861+ signature predicate isSuperClassSig ( ClassDefinition clazz ) ;
862+
863+ overlay [ local?]
864+ private module StreamSuperClass< isSuperClassSig / 1 isSuperClass> {
865+ /** Gets a relevant super class of `subclass`. */
866+ ClassDefinition streamSuperClass ( ClassDefinition subclass ) {
867+ isSuperClass ( result ) and
868+ DataFlow:: localFlowStep * ( TValueNode ( result ) , TValueNode ( subclass .getSuperClass ( ) ) )
869+ }
870+ }
871+
872+ signature predicate hasStreamKindSig ( string kind ) ;
873+
874+ overlay [ local?]
875+ private module WritableStreamClass< hasStreamKindSig / 1 hasStreamKind> {
876+ /**
877+ * Holds if `c` extends a built-in Node.js stream class whose name
878+ * satisfies `hasStreamKind`.
879+ */
880+ predicate writableStreamClass ( ClassDefinition c ) {
881+ exists ( string kind , EarlyStageNode constructor |
882+ hasStreamKind ( kind ) and
883+ memberRead ( getAStreamModuleNode ( ) , kind , constructor ) and
884+ DataFlow:: localFlowStep * ( constructor , TValueNode ( c .getSuperClass ( ) ) )
885+ )
886+ or
887+ exists ( StreamSuperClass< writableStreamClass / 1 > :: streamSuperClass ( c ) )
888+ }
889+ }
890+
891+ overlay [ local?]
892+ private predicate isWritableOrDuplex ( string kind ) { kind = [ "Writable" , "Duplex" ] }
893+
894+ overlay [ local?]
895+ private predicate isTransform ( string kind ) { kind = "Transform" }
896+
897+ signature predicate hasStreamMethodNameSig ( string name ) ;
898+
899+ overlay [ local?]
900+ private module StreamMethodOverride< hasStreamMethodNameSig / 1 hasStreamMethodName> {
901+ predicate overridesStreamMethod ( ClassDefinition c ) {
902+ exists ( string name , MemberDeclaration member |
903+ hasStreamMethodName ( name ) and
904+ member = StreamSuperClass< overridesStreamMethod / 1 > :: streamSuperClass * ( c ) .getMember ( name ) and
905+ not member .isStatic ( )
906+ )
907+ }
908+
909+ predicate overridesStreamInstanceMethod ( NewExpr creation ) {
910+ exists ( Assignment assignment , PropAccess property |
911+ property = assignment .getLhs ( ) and
912+ hasStreamMethodName ( property .getPropertyName ( ) ) and
913+ DataFlow:: localFlowStep * ( TValueNode ( creation ) , TValueNode ( property .getBase ( ) ) )
914+ )
915+ }
916+ }
917+
918+ overlay [ local?]
919+ private predicate isWriteMethod ( string name ) { name = "write" }
920+
921+ overlay [ local?]
922+ private predicate isWriteHook ( string name ) { name = "_write" }
923+
924+ overlay [ local?]
925+ private ClassDefinition instantiatedClassWithoutWriteOverride ( NewExpr creation ) {
926+ DataFlow:: localFlowStep * ( TValueNode ( result ) , TValueNode ( creation .getCallee ( ) ) ) and
927+ not StreamMethodOverride< isWriteMethod / 1 > :: overridesStreamMethod ( result )
928+ }
929+
930+ overlay [ local?]
931+ private NewExpr pipeDestinationWithoutWriteOverride ( PipeCall pipe ) {
932+ DataFlow:: localFlowStep * ( TValueNode ( result ) , TValueNode ( pipe .getArgument ( 0 ) ) ) and
933+ not StreamMethodOverride< isWriteMethod / 1 > :: overridesStreamInstanceMethod ( result )
934+ }
935+
936+ /** Gets the implementation hook invoked by an inherited stream `write` method. */
937+ overlay [ local?]
938+ private predicate pipeImplementationHook ( PipeCall pipe , string hook ) {
939+ exists ( ClassDefinition c , NewExpr creation |
940+ creation = pipeDestinationWithoutWriteOverride ( pipe ) and
941+ c = instantiatedClassWithoutWriteOverride ( creation )
942+ |
943+ WritableStreamClass< isWritableOrDuplex / 1 > :: writableStreamClass ( c ) and
944+ hook = "_write"
945+ or
946+ WritableStreamClass< isTransform / 1 > :: writableStreamClass ( c ) and
947+ if
948+ not StreamMethodOverride< isWriteHook / 1 > :: overridesStreamMethod ( c ) and
949+ not StreamMethodOverride< isWriteHook / 1 > :: overridesStreamInstanceMethod ( creation )
950+ then hook = "_transform"
951+ else hook = "_write"
952+ )
953+ }
954+
955+ overlay [ local?]
956+ private string pipeHookTag ( string hook , string part ) {
957+ hook = [ "_write" , "_transform" ] and
958+ part = [ "member" , "call" , "encoding" , "callback" ] and
959+ result = "nodejs.pipe." + hook + "." + part
960+ }
961+
861962 private DataFlow:: SourceNode bufferInstance ( DataFlow:: TypeTracker t ) {
862963 t .start ( ) and
863964 exists ( DataFlow:: SourceNode constructor |
@@ -888,7 +989,14 @@ module NodeJSLib {
888989 override predicate needsSynthesizedNode ( AstNode node , string tag , DataFlowCallable container ) {
889990 (
890991 node instanceof PipeCall and
891- tag = [ "nodejs.pipe.write.member" , "nodejs.pipe.write.call" , "nodejs.pipe.write.chunk" ]
992+ (
993+ tag = [ "nodejs.pipe.write.member" , "nodejs.pipe.write.call" , "nodejs.pipe.write.chunk" ]
994+ or
995+ exists ( string hook |
996+ pipeImplementationHook ( node , hook ) and
997+ tag = pipeHookTag ( hook , _)
998+ )
999+ )
8921000 or
8931001 node instanceof ReadableFromCall and
8941002 tag = "nodejs.readable.from.chunk"
@@ -973,6 +1081,8 @@ module NodeJSLib {
9731081 this =
9741082 getSynthesizedNode ( pipe ,
9751083 [ "nodejs.pipe.write.member" , "nodejs.pipe.write.call" , "nodejs.pipe.write.chunk" ] )
1084+ or
1085+ this = getSynthesizedNode ( pipe , pipeHookTag ( _, _) )
9761086 }
9771087
9781088 override BasicBlock getBasicBlock ( ) { result = pipe .getBasicBlock ( ) }
@@ -1001,6 +1111,9 @@ module NodeJSLib {
10011111 getSynthesizedNode ( any ( PipeCall call ) ,
10021112 [ "nodejs.pipe.write.member" , "nodejs.pipe.write.chunk" ] )
10031113 or
1114+ this =
1115+ getSynthesizedNode ( any ( PipeCall call ) , pipeHookTag ( _, [ "member" , "encoding" , "callback" ] ) )
1116+ or
10041117 this = getSynthesizedNode ( any ( ReadableFromCall call ) , "nodejs.readable.from.chunk" )
10051118 }
10061119 }
@@ -1033,6 +1146,53 @@ module NodeJSLib {
10331146 override int getNumArgument ( ) { result = 1 }
10341147 }
10351148
1149+ overlay [ local?]
1150+ private class PipeHookMember extends PipeNode , DataFlow:: PropRead {
1151+ string hook ;
1152+
1153+ PipeHookMember ( ) { this = getSynthesizedNode ( pipe , pipeHookTag ( hook , "member" ) ) }
1154+
1155+ override DataFlow:: Node getBase ( ) { result = pipe .getArgument ( 0 ) .flow ( ) }
1156+
1157+ override string getPropertyName ( ) { result = hook }
1158+
1159+ override Expr getPropertyNameExpr ( ) { none ( ) }
1160+ }
1161+
1162+ /** An implicit `_write` or `_transform` invocation made by the native `write` method. */
1163+ overlay [ local?]
1164+ private class PipeHookCall extends PipeNode , DataFlow:: Impl:: MethodCallNodeDef {
1165+ string hook ;
1166+
1167+ PipeHookCall ( ) { this = getSynthesizedNode ( pipe , pipeHookTag ( hook , "call" ) ) }
1168+
1169+ override InvokeExpr getInvokeExpr ( ) { result = pipe }
1170+
1171+ override string getCalleeName ( ) { result = hook }
1172+
1173+ override string getMethodName ( ) { result = hook }
1174+
1175+ override DataFlow:: Node getCalleeNode ( ) {
1176+ result = getSynthesizedNode ( pipe , pipeHookTag ( hook , "member" ) )
1177+ }
1178+
1179+ override DataFlow:: Node getReceiver ( ) { result = pipe .getArgument ( 0 ) .flow ( ) }
1180+
1181+ override DataFlow:: Node getArgument ( int index ) {
1182+ index = 0 and result = getSynthesizedNode ( pipe , "nodejs.pipe.write.chunk" )
1183+ or
1184+ index = 1 and result = getSynthesizedNode ( pipe , pipeHookTag ( hook , "encoding" ) )
1185+ or
1186+ index = 2 and result = getSynthesizedNode ( pipe , pipeHookTag ( hook , "callback" ) )
1187+ }
1188+
1189+ override DataFlow:: Node getAnArgument ( ) { result = this .getArgument ( _) }
1190+
1191+ override DataFlow:: Node getASpreadArgument ( ) { none ( ) }
1192+
1193+ override int getNumArgument ( ) { result = 3 }
1194+ }
1195+
10361196 /**
10371197 * A write to the file system, using a stream.
10381198 */
0 commit comments