Skip to content

Commit ed00f2b

Browse files
committed
Ruby: address some feedback on array flow summaries
1 parent 161d766 commit ed00f2b

File tree

3 files changed

+6336
-5947
lines changed

3 files changed

+6336
-5947
lines changed

ruby/ql/lib/codeql/ruby/frameworks/Array.qll

Lines changed: 126 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -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
*/
14991614
module Enumerable {
15001615
private class ChunkSummary extends SimpleSummarizedCallable {

0 commit comments

Comments
 (0)