@@ -438,51 +438,96 @@ module StringOps {
438438 }
439439
440440 /**
441- * A data flow node that concatenates strings and returns the result .
441+ * Holds if `first` and `second` are adjacent leaves in a concatenation tree .
442442 */
443- class Concatenation extends DataFlow:: Node {
444- Concatenation ( ) {
443+ pragma [ nomagic]
444+ private predicate adjacentLeaves ( ConcatenationLeaf first , ConcatenationLeaf second ) {
445+ exists ( Concatenation parent , int i |
446+ first = parent .getOperand ( i ) .getLastLeaf ( ) and
447+ second = parent .getOperand ( i + 1 ) .getFirstLeaf ( )
448+ )
449+ }
450+
451+ /**
452+ * A data flow node performs a string concatenation or occurs as an operand
453+ * in a string concatenation.
454+ *
455+ * For example, the expression `x + y + z` contains the following concatenation
456+ * nodes:
457+ * - The leaf nodes `x`, `y`, and `z`
458+ * - The intermediate node `x + y`, which is both a concatenation and an operand
459+ * - The root node `x + y + z`
460+ *
461+ *
462+ * Note that the following are not recognized a string concatenations:
463+ * - Array `join()` calls with a non-empty separator
464+ * - Tagged template literals
465+ *
466+ *
467+ * Also note that all `+` operators are seen as string concatenations,
468+ * even in cases where it is used for arithmetic.
469+ *
470+ * Examples of string concatenations:
471+ * ```
472+ * x + y
473+ * x += y
474+ * [x, y].join('')
475+ * Array(x, y).join('')
476+ * `Hello, ${message}`
477+ * ```
478+ */
479+ class ConcatenationNode extends DataFlow:: Node {
480+ pragma [ inline]
481+ ConcatenationNode ( ) {
445482 exists ( StringConcatenation:: getAnOperand ( this ) )
483+ or
484+ this = StringConcatenation:: getAnOperand ( _)
446485 }
447486
448487 /**
449488 * Gets the `n`th operand of this string concatenation.
450489 */
451- DataFlow:: Node getOperand ( int n ) {
490+ pragma [ inline]
491+ ConcatenationOperand getOperand ( int n ) {
452492 result = StringConcatenation:: getOperand ( this , n )
453493 }
454494
455495 /**
456496 * Gets an operand of this string concatenation.
457497 */
458- DataFlow:: Node getAnOperand ( ) {
498+ pragma [ inline]
499+ ConcatenationOperand getAnOperand ( ) {
459500 result = StringConcatenation:: getAnOperand ( this )
460501 }
461502
462503 /**
463504 * Gets the number of operands of this string concatenation.
464505 */
506+ pragma [ inline]
465507 int getNumOperand ( ) {
466508 result = StringConcatenation:: getNumOperand ( this )
467509 }
468510
469511 /**
470512 * Gets the first operand of this string concatenation.
471513 */
472- DataFlow:: Node getFirstOperand ( ) {
514+ pragma [ inline]
515+ ConcatenationOperand getFirstOperand ( ) {
473516 result = StringConcatenation:: getFirstOperand ( this )
474517 }
475518
476519 /**
477520 * Gets the last operand of this string concatenation
478521 */
479- DataFlow:: Node getLastOperand ( ) {
522+ pragma [ inline]
523+ ConcatenationOperand getLastOperand ( ) {
480524 result = StringConcatenation:: getLastOperand ( this )
481525 }
482526
483527 /**
484528 * Holds if this only acts as a string coercion, such as `"" + x`.
485529 */
530+ pragma [ inline]
486531 predicate isCoercion ( ) {
487532 StringConcatenation:: isCoercion ( this )
488533 }
@@ -492,15 +537,161 @@ module StringOps {
492537 * it is a concatenation operator that is not itself the immediate operand to
493538 * another concatenation operator.
494539 */
540+ pragma [ inline]
495541 predicate isRoot ( ) {
496542 StringConcatenation:: isRoot ( this )
497543 }
498544
545+ /**
546+ * Holds if this is a leaf in the concatenation tree, that is, it is not
547+ * itself a concatenation.
548+ */
549+ pragma [ inline]
550+ predicate isLeaf ( ) {
551+ not exists ( StringConcatenation:: getAnOperand ( this ) )
552+ }
553+
499554 /**
500555 * Gets the root of the concatenation tree in which this is an operator.
501556 */
502- Concatenation getRoot ( ) {
557+ pragma [ inline]
558+ ConcatenationRoot getRoot ( ) {
503559 result = StringConcatenation:: getRoot ( this )
504560 }
561+
562+ /**
563+ * Gets the enclosing concatenation in which this is an operand, if any.
564+ */
565+ pragma [ inline]
566+ Concatenation getParentConcatenation ( ) {
567+ this = StringConcatenation:: getAnOperand ( result )
568+ }
569+
570+ /**
571+ * Gets the last leaf in this concatenation tree.
572+ *
573+ * For example, `x` is the first leaf in `x + y + z`.
574+ */
575+ pragma [ inline]
576+ ConcatenationLeaf getLastLeaf ( ) {
577+ result = StringConcatenation:: getLastOperand * ( this )
578+ }
579+
580+ /**
581+ * Gets the first leaf in this concatenation tree.
582+ *
583+ * For example, `z` is the last leaf in `x + y + z`.
584+ */
585+ pragma [ inline]
586+ ConcatenationLeaf getFirstLeaf ( ) {
587+ result = StringConcatenation:: getFirstOperand * ( this )
588+ }
589+
590+ /**
591+ * Gets the leaf that is occurs immediately before this leaf in the
592+ * concatenation tree, if any.
593+ *
594+ * For example, `y` is the previous leaf from `z` in `x + y + z`.
595+ */
596+ pragma [ inline]
597+ ConcatenationLeaf getPreviousLeaf ( ) {
598+ adjacentLeaves ( result , this )
599+ }
600+
601+ /**
602+ * Gets the leaf that is occurs immediately after this leaf in the
603+ * concatenation tree, if any.
604+ *
605+ * For example, `y` is the next leaf from `x` in `x + y + z`.
606+ */
607+ pragma [ inline]
608+ ConcatenationLeaf getNextLeaf ( ) {
609+ adjacentLeaves ( this , result )
610+ }
611+ }
612+
613+ /**
614+ * A data flow node that performs a string concatenation and returns the result.
615+ *
616+ * Examples:
617+ * ```
618+ * x + y
619+ * x += y
620+ * [x, y].join('')
621+ * Array(x, y).join('')
622+ * `Hello ${message}`
623+ * ```
624+ *
625+ * See `ConcatenationNode` for more information.
626+ */
627+ class Concatenation extends ConcatenationNode {
628+ pragma [ inline]
629+ Concatenation ( ) {
630+ exists ( StringConcatenation:: getAnOperand ( this ) )
631+ }
632+ }
633+
634+ /**
635+ * One of the operands in a string concatenation.
636+ *
637+ * Examples:
638+ * ```
639+ * x + y // x and y are operands
640+ * [x, y].join('') // x and y are operands
641+ * `Hello ${message}` // `Hello ` and message are operands
642+ * ```
643+ *
644+ * See `ConcatenationNode` for more information.
645+ */
646+ class ConcatenationOperand extends ConcatenationNode {
647+ pragma [ inline]
648+ ConcatenationOperand ( ) {
649+ this = StringConcatenation:: getAnOperand ( _)
650+ }
651+ }
652+
653+ /**
654+ * A data flow node that performs a string concatenation, and is not an
655+ * immediate operand in a larger string concatenation.
656+ *
657+ * Examples:
658+ * ```
659+ * // x + y + z is a root, but the inner x + y is not
660+ * return x + y + z;
661+ * ```
662+ *
663+ * See `ConcatenationNode` for more information.
664+ */
665+ class ConcatenationRoot extends Concatenation {
666+ pragma [ inline]
667+ ConcatenationRoot ( ) {
668+ isRoot ( )
669+ }
670+
671+ /**
672+ * Gets a leaf in this concatenation tree that this node is the root of.
673+ */
674+ pragma [ inline]
675+ ConcatenationLeaf getALeaf ( ) {
676+ this = StringConcatenation:: getRoot ( result )
677+ }
678+ }
679+
680+ /**
681+ * An operand to a concatenation that is not itself a concatenation.
682+ *
683+ * Example:
684+ * ```
685+ * x + y + z // x, y, and z are leaves
686+ * [x, y + z].join('') // x, y, and z are leaves
687+ * ```
688+ *
689+ * See `ConcatenationNode` for more information.
690+ */
691+ class ConcatenationLeaf extends ConcatenationOperand {
692+ pragma [ inline]
693+ ConcatenationLeaf ( ) {
694+ isLeaf ( )
695+ }
505696 }
506697}
0 commit comments