@@ -14,7 +14,7 @@ private class ArrayIndex extends int {
1414 * Provides flow summaries for the `Array` class.
1515 *
1616 * The summaries are ordered (and implemented) based on
17- * https://ruby-doc .org/core- 3.1.0 /Array.html, however for methods that have the
17+ * https://docs. ruby-lang .org/en/ 3.1/Array.html, however for methods that have the
1818 * more general `Enumerable` scope, they are implemented in the `Enumerable`
1919 * module instead.
2020 */
@@ -368,6 +368,10 @@ module Array {
368368 }
369369
370370 override predicate propagatesFlowExt ( string input , string output , boolean preservesValue ) {
371+ // We model this imprecisely, saying that there's flow from any element of
372+ // the argument or the receiver to any element of the receiver. This could
373+ // be made more precise when the range is known, similar to the way it's
374+ // done in `ElementReferenceRangeReadKnownSummary`.
371375 exists ( string arg |
372376 arg = "Argument[" + ( mc .getNumberOfArguments ( ) - 1 ) + "]" and
373377 input = [ "ArrayElement of " + arg , arg , "ArrayElement of Receiver" ] and
@@ -526,18 +530,71 @@ module Array {
526530 DeleteSummary ( ) { this = "delete" }
527531
528532 override predicate propagatesFlowExt ( string input , string output , boolean preservesValue ) {
529- input = [ "ArrayElement of Receiver" , "ReturnValue of BlockArgument" ] and
530- output = "ReturnValue" and
533+ (
534+ input = "ArrayElement of Receiver" and
535+ output = [ "ArrayElement[?] of Receiver" , "ReturnValue" ]
536+ or
537+ input = "ReturnValue of BlockArgument" and
538+ output = "ReturnValue"
539+ ) and
531540 preservesValue = true
532541 }
542+
543+ override predicate clearsContent ( ParameterPosition pos , DataFlow:: Content content ) {
544+ pos .isSelf ( ) and
545+ content instanceof DataFlow:: Content:: ArrayElementContent
546+ }
533547 }
534548
535- private class DeleteAtSummary extends SimpleSummarizedCallable {
536- DeleteAtSummary ( ) { this = "delete_at" }
549+ abstract private class DeleteAtSummary extends SummarizedCallable {
550+ MethodCall mc ;
551+
552+ bindingset [ this ]
553+ DeleteAtSummary ( ) { mc .getMethodName ( ) = "delete_at" }
554+
555+ override predicate clearsContent ( ParameterPosition pos , DataFlow:: Content content ) {
556+ pos .isSelf ( ) and
557+ content instanceof DataFlow:: Content:: ArrayElementContent
558+ }
559+
560+ override MethodCall getACall ( ) { result = mc }
561+ }
562+
563+ private class DeleteAtKnownSummary extends DeleteAtSummary {
564+ int i ;
565+
566+ DeleteAtKnownSummary ( ) {
567+ this = "delete_at(" + i + ")" and
568+ i = mc .getArgument ( 0 ) .getConstantValue ( ) .getInt ( ) and
569+ i >= 0
570+ }
571+
572+ override predicate propagatesFlowExt ( string input , string output , boolean preservesValue ) {
573+ (
574+ input = "ArrayElement[?] of Receiver" and
575+ output = [ "ReturnValue" , "ArrayElement[?] of Receiver" ]
576+ or
577+ exists ( ArrayIndex j | input = "ArrayElement[" + j + "] of Receiver" |
578+ j < i and output = "ArrayElement[" + j + "] of Receiver"
579+ or
580+ j = i and output = "ReturnValue"
581+ or
582+ j > i and output = "ArrayElement[" + ( j - 1 ) + "] of Receiver"
583+ )
584+ ) and
585+ preservesValue = true
586+ }
587+ }
588+
589+ private class DeleteAtUnknownSummary extends DeleteAtSummary {
590+ DeleteAtUnknownSummary ( ) {
591+ this = "delete_at(index)" and
592+ not exists ( int i | i = mc .getArgument ( 0 ) .getConstantValue ( ) .getInt ( ) and i >= 0 )
593+ }
537594
538595 override predicate propagatesFlowExt ( string input , string output , boolean preservesValue ) {
539596 input = "ArrayElement of Receiver" and
540- output = "ReturnValue" and
597+ output = [ "ReturnValue" , "ArrayElement[?] of Receiver" ] and
541598 preservesValue = true
542599 }
543600 }
@@ -547,15 +604,26 @@ module Array {
547604
548605 override predicate propagatesFlowExt ( string input , string output , boolean preservesValue ) {
549606 input = "ArrayElement of Receiver" and
550- output = [ "Parameter[0] of BlockArgument" , "ArrayElement[?] of ReturnValue" ] and
607+ output =
608+ [
609+ "Parameter[0] of BlockArgument" , "ArrayElement[?] of ReturnValue" ,
610+ "ArrayElement[?] of Receiver"
611+ ] and
551612 preservesValue = true
552613 }
614+
615+ override predicate clearsContent ( ParameterPosition pos , DataFlow:: Content content ) {
616+ pos .isSelf ( ) and
617+ content instanceof DataFlow:: Content:: ArrayElementContent
618+ }
553619 }
554620
555621 private class DifferenceSummary extends SimpleSummarizedCallable {
556622 DifferenceSummary ( ) { this = "difference" }
557623
558624 override predicate propagatesFlowExt ( string input , string output , boolean preservesValue ) {
625+ // `Array#difference` and `Array#-` do not behave exactly the same way,
626+ // but we model their flow the same way.
559627 any ( SetDifferenceSummary s ) .propagatesFlowExt ( input , output , preservesValue )
560628 }
561629 }
@@ -653,12 +721,53 @@ module Array {
653721 }
654722 }
655723
656- private class FetchSummary extends SimpleSummarizedCallable {
657- FetchSummary ( ) { this = "fetch" }
724+ abstract private class FetchSummary extends SummarizedCallable {
725+ MethodCall mc ;
726+
727+ bindingset [ this ]
728+ FetchSummary ( ) { mc .getMethodName ( ) = "fetch" }
729+
730+ override MethodCall getACall ( ) { result = mc }
731+ }
732+
733+ private class FetchKnownSummary extends FetchSummary {
734+ int i ;
735+
736+ FetchKnownSummary ( ) {
737+ this = "fetch(" + i + ")" and
738+ i = mc .getArgument ( 0 ) .getConstantValue ( ) .getInt ( ) and
739+ i >= 0
740+ }
658741
659742 override predicate propagatesFlowExt ( string input , string output , boolean preservesValue ) {
660743 (
661- input = "ArrayElement of Receiver" and
744+ input = "ArrayElement[?] of Receiver" and
745+ output = [ "ReturnValue" , "ArrayElement[?] of Receiver" ]
746+ or
747+ exists ( ArrayIndex j | input = "ArrayElement[" + j + "] of Receiver" |
748+ j = i and output = "ReturnValue"
749+ or
750+ j != i and output = "ArrayElement[" + j + "] of Receiver"
751+ )
752+ or
753+ input = "Argument[0]" and
754+ output = "Parameter[0] of BlockArgument"
755+ or
756+ input = "Argument[1]" and output = "ReturnValue"
757+ ) and
758+ preservesValue = true
759+ }
760+ }
761+
762+ private class FetchUnknownSummary extends FetchSummary {
763+ FetchUnknownSummary ( ) {
764+ this = "fetch(index)" and
765+ not exists ( int i | i = mc .getArgument ( 0 ) .getConstantValue ( ) .getInt ( ) and i >= 0 )
766+ }
767+
768+ override predicate propagatesFlowExt ( string input , string output , boolean preservesValue ) {
769+ (
770+ input = [ "ArrayElement of Receiver" , "Argument[1]" ] and
662771 output = "ReturnValue"
663772 or
664773 input = "Argument[0]" and
@@ -702,6 +811,12 @@ module Array {
702811 }
703812 }
704813
814+ /**
815+ * A call to `flatten`.
816+ *
817+ * Note that we model flow from elements up to 3 levels of nesting
818+ * (`[[[1],[2]]]`), but not beyond that.
819+ */
705820 private class FlattenSummary extends SimpleSummarizedCallable {
706821 FlattenSummary ( ) { this = "flatten" }
707822
@@ -1494,7 +1609,7 @@ module Array {
14941609 * Provides flow summaries for the `Enumerable` class.
14951610 *
14961611 * The summaries are ordered (and implemented) based on
1497- * https://ruby-doc .org/core- 3.1.0 /Enumerable.html
1612+ * https://docs. ruby-lang .org/en/ 3.1/Enumerable.html
14981613 */
14991614module Enumerable {
15001615 private class ChunkSummary extends SimpleSummarizedCallable {
0 commit comments