@@ -629,4 +629,143 @@ module StringOps {
629
629
class HtmlConcatenationLeaf extends ConcatenationLeaf {
630
630
HtmlConcatenationLeaf ( ) { getRoot ( ) instanceof HtmlConcatenationRoot }
631
631
}
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
+ }
632
771
}
0 commit comments