@@ -629,4 +629,143 @@ module StringOps {
629629 class HtmlConcatenationLeaf extends ConcatenationLeaf {
630630 HtmlConcatenationLeaf ( ) { getRoot ( ) instanceof HtmlConcatenationRoot }
631631 }
632+
633+ /**
634+ * A data flow node whose boolean value indicates whether a regexp matches a given string.
635+ *
636+ * For example, the condition of each of the following `if`-statements are `RegExpTest` nodes:
637+ * ```js
638+ * if (regexp.test(str)) { ... }
639+ * if (regexp.exec(str) != null) { ... }
640+ * if (str.matches(regexp)) { ... }
641+ * ```
642+ *
643+ * Note that `RegExpTest` represents a boolean-valued expression or one
644+ * that is coerced to a boolean, which is not always the same as the call that performs the
645+ * regexp-matching. For example, the `exec` call below is not itself a `RegExpTest`,
646+ * but the `match` variable in the condition is:
647+ * ```js
648+ * let match = regexp.exec(str);
649+ * if (!match) { ... } // <--- 'match' is the RegExpTest
650+ * ```
651+ */
652+ class RegExpTest extends DataFlow:: Node {
653+ RegExpTest:: Range range ;
654+
655+ RegExpTest ( ) { this = range }
656+
657+ /**
658+ * Gets the AST of the regular expression used in the test, if it can be seen locally.
659+ */
660+ RegExpTerm getRegExp ( ) {
661+ result = getRegExpOperand ( ) .getALocalSource ( ) .( DataFlow:: RegExpCreationNode ) .getRoot ( )
662+ or
663+ result = range .getRegExpOperand ( true ) .asExpr ( ) .( StringLiteral ) .asRegExp ( )
664+ }
665+
666+ /**
667+ * Gets the data flow node corresponding to the regular expression object used in the test.
668+ *
669+ * In some cases this represents a string value being coerced to a RegExp object.
670+ */
671+ DataFlow:: Node getRegExpOperand ( ) { result = range .getRegExpOperand ( _) }
672+
673+ /**
674+ * Gets the data flow node corresponding to the string being tested against the regular expression.
675+ */
676+ DataFlow:: Node getStringOperand ( ) { result = range .getStringOperand ( ) }
677+
678+ /**
679+ * Gets the return value indicating that the string matched the regular expression.
680+ *
681+ * For example, for `regexp.exec(str) == null`, the polarity is `false`, and for
682+ * `regexp.exec(str) != null` the polarity is `true`.
683+ */
684+ boolean getPolarity ( ) { result = range .getPolarity ( ) }
685+ }
686+
687+ /**
688+ * Companion module to the `RegExpTest` class.
689+ */
690+ module RegExpTest {
691+ /**
692+ * A data flow node whose boolean value indicates whether a regexp matches a given string.
693+ *
694+ * This class can be extended to contribute new kinds of `RegExpTest` nodes.
695+ */
696+ abstract class Range extends DataFlow:: Node {
697+ /**
698+ * Gets the data flow node corresponding to the regular expression object used in the test.
699+ */
700+ abstract DataFlow:: Node getRegExpOperand ( boolean coerced ) ;
701+
702+ /**
703+ * Gets the data flow node corresponding to the string being tested against the regular expression.
704+ */
705+ abstract DataFlow:: Node getStringOperand ( ) ;
706+
707+ /**
708+ * Gets the return value indicating that the string matched the regular expression.
709+ */
710+ boolean getPolarity ( ) { result = true }
711+ }
712+
713+ private class TestCall extends Range , DataFlow:: MethodCallNode {
714+ TestCall ( ) { getMethodName ( ) = "test" }
715+
716+ override DataFlow:: Node getRegExpOperand ( boolean coerced ) { result = getReceiver ( ) and coerced = false }
717+
718+ override DataFlow:: Node getStringOperand ( ) { result = getArgument ( 0 ) }
719+ }
720+
721+ private class MatchesCall extends Range , DataFlow:: MethodCallNode {
722+ MatchesCall ( ) { getMethodName ( ) = "matches" }
723+
724+ override DataFlow:: Node getRegExpOperand ( boolean coerced ) { result = getArgument ( 0 ) and coerced = true }
725+
726+ override DataFlow:: Node getStringOperand ( ) { result = getReceiver ( ) }
727+ }
728+
729+ private class ExecCall extends DataFlow:: MethodCallNode {
730+ ExecCall ( ) { getMethodName ( ) = "exec" }
731+ }
732+
733+ predicate isCoercedToBoolean ( Expr e ) {
734+ e = any ( ConditionGuardNode guard ) .getTest ( )
735+ or
736+ e = any ( LogNotExpr n ) .getOperand ( )
737+ }
738+
739+ /**
740+ * Holds if `e` evaluating to `polarity` implies that `operand` is not null.
741+ */
742+ private predicate impliesNotNull ( Expr e , Expr operand , boolean polarity ) {
743+ exists ( EqualityTest test |
744+ e = test and
745+ polarity = test .getPolarity ( ) .booleanNot ( ) and
746+ test .hasOperands ( any ( NullLiteral n ) , operand )
747+ )
748+ or
749+ isCoercedToBoolean ( e ) and
750+ operand = e and
751+ polarity = true
752+ }
753+
754+ private class ExecTest extends Range , DataFlow:: ValueNode {
755+ ExecCall exec ;
756+ boolean polarity ;
757+
758+ ExecTest ( ) {
759+ exists ( Expr use | exec .flowsToExpr ( use ) |
760+ impliesNotNull ( astNode , use , polarity )
761+ )
762+ }
763+
764+ override DataFlow:: Node getRegExpOperand ( boolean coerced ) { result = exec .getReceiver ( ) and coerced = false }
765+
766+ override DataFlow:: Node getStringOperand ( ) { result = exec .getArgument ( 0 ) }
767+
768+ override boolean getPolarity ( ) { result = polarity }
769+ }
770+ }
632771}
0 commit comments