Skip to content

Commit e3ef258

Browse files
authored
Merge pull request #9287 from aibaars/instance-variable-flow-2
Ruby: flow through getters/setters
2 parents 4383aef + 033df76 commit e3ef258

File tree

8 files changed

+166
-3
lines changed

8 files changed

+166
-3
lines changed

ruby/ql/lib/codeql/ruby/ast/Call.qll

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,21 @@ class SetterMethodCall extends MethodCall, TMethodCallSynth {
145145
SetterMethodCall() { this = TMethodCallSynth(_, _, _, true, _) }
146146

147147
final override string getAPrimaryQlClass() { result = "SetterMethodCall" }
148+
149+
/**
150+
* Gets the name of the method being called without the trailing `=`. For example, in the following
151+
* two statements the target name is `value`:
152+
* ```rb
153+
* foo.value=(1)
154+
* foo.value = 1
155+
* ```
156+
*/
157+
final string getTargetName() {
158+
exists(string methodName |
159+
methodName = this.getMethodName() and
160+
result = methodName.prefix(methodName.length() - 1)
161+
)
162+
}
148163
}
149164

150165
/**

ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ private import DataFlowDispatch
77
private import SsaImpl as SsaImpl
88
private import FlowSummaryImpl as FlowSummaryImpl
99
private import FlowSummaryImplSpecific as FlowSummaryImplSpecific
10+
private import codeql.ruby.frameworks.data.ModelsAsData
1011

1112
/** Gets the callable in which this node occurs. */
1213
DataFlowCallable nodeGetEnclosingCallable(NodeImpl n) { result = n.getEnclosingCallable() }
@@ -358,7 +359,22 @@ private module Cached {
358359
TUnknownElementContent() or
359360
TKnownPairValueContent(ConstantValue cv) or
360361
TUnknownPairValueContent() or
361-
TFieldContent(string name) { name = any(InstanceVariable v).getName() }
362+
TFieldContent(string name) {
363+
name = any(InstanceVariable v).getName()
364+
or
365+
name = "@" + any(SetterMethodCall c).getTargetName()
366+
or
367+
// The following equation unfortunately leads to a non-monotonic recursion error:
368+
// name = any(AccessPathToken a).getAnArgument("Field")
369+
// Therefore, we use the following instead to extract the field names from the
370+
// external model data. This, unfortunately, does not included any field names used
371+
// in models defined in QL code.
372+
exists(string input, string output |
373+
ModelOutput::relevantSummaryModel(_, _, _, input, output, _)
374+
|
375+
name = [input, output].regexpFind("(?<=(^|\\.)Field\\[)[^\\]]+(?=\\])", _, _).trim()
376+
)
377+
}
362378

363379
/**
364380
* Holds if `e` is an `ExprNode` that may be returned by a call to `c`.
@@ -880,6 +896,16 @@ predicate storeStep(Node node1, ContentSet c, Node node2) {
880896
)
881897
).getReceiver()
882898
or
899+
// Attribute assignment, `receiver.property = value`
900+
node2.(PostUpdateNode).getPreUpdateNode().asExpr() =
901+
any(CfgNodes::ExprNodes::MethodCallCfgNode call |
902+
node1.asExpr() = call.getArgument(0) and
903+
call.getNumberOfArguments() = 1 and
904+
c.isSingleton(any(Content::FieldContent ct |
905+
ct.getName() = "@" + call.getExpr().(SetterMethodCall).getTargetName()
906+
))
907+
).getReceiver()
908+
or
883909
FlowSummaryImpl::Private::Steps::summaryStoreStep(node1, c, node2)
884910
or
885911
// Needed for pairs passed into method calls where the key is not a symbol,
@@ -923,6 +949,19 @@ predicate readStep(Node node1, ContentSet c, Node node2) {
923949
))
924950
)
925951
or
952+
// Attribute read, `receiver.field`. Note that we do not check whether
953+
// the `field` method is really an attribute reader. This is probably fine
954+
// because the read step has only effect if there exists a matching store step
955+
// (instance variable assignment or setter method call).
956+
node2.asExpr() =
957+
any(CfgNodes::ExprNodes::MethodCallCfgNode call |
958+
node1.asExpr() = call.getReceiver() and
959+
call.getNumberOfArguments() = 0 and
960+
c.isSingleton(any(Content::FieldContent ct |
961+
ct.getName() = "@" + call.getExpr().getMethodName()
962+
))
963+
)
964+
or
926965
FlowSummaryImpl::Private::Steps::summaryReadStep(node1, c, node2)
927966
}
928967

ruby/ql/lib/codeql/ruby/dataflow/internal/FlowSummaryImplSpecific.qll

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,9 @@ SummaryComponent interpretComponentSpecific(AccessPathToken c) {
101101
or
102102
result = interpretElementArg(c.getAnArgument("Element"))
103103
or
104+
result =
105+
FlowSummary::SummaryComponent::content(TSingletonContent(TFieldContent(c.getAnArgument("Field"))))
106+
or
104107
exists(ContentSet cs |
105108
FlowSummary::SummaryComponent::content(cs) = interpretElementArg(c.getAnArgument("WithElement")) and
106109
result = FlowSummary::SummaryComponent::withContent(cs)

ruby/ql/test/library-tests/dataflow/global/Flow.expected

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,34 @@ edges
3333
| instance_variables.rb:20:15:20:23 | call to source : | instance_variables.rb:20:1:20:3 | [post] bar [@field] : |
3434
| instance_variables.rb:21:6:21:8 | bar [@field] : | instance_variables.rb:8:5:10:7 | self in inc_field [@field] : |
3535
| instance_variables.rb:21:6:21:8 | bar [@field] : | instance_variables.rb:21:6:21:18 | call to inc_field |
36+
| instance_variables.rb:24:1:24:4 | [post] foo1 [@field] : | instance_variables.rb:25:6:25:9 | foo1 [@field] : |
37+
| instance_variables.rb:24:1:24:4 | [post] foo1 [@field] : | instance_variables.rb:25:6:25:9 | foo1 [@field] : |
38+
| instance_variables.rb:24:14:24:23 | call to source : | instance_variables.rb:24:1:24:4 | [post] foo1 [@field] : |
39+
| instance_variables.rb:24:14:24:23 | call to source : | instance_variables.rb:24:1:24:4 | [post] foo1 [@field] : |
40+
| instance_variables.rb:25:6:25:9 | foo1 [@field] : | instance_variables.rb:25:6:25:15 | call to field |
41+
| instance_variables.rb:25:6:25:9 | foo1 [@field] : | instance_variables.rb:25:6:25:15 | call to field |
42+
| instance_variables.rb:28:1:28:4 | [post] foo2 [@field] : | instance_variables.rb:29:6:29:9 | foo2 [@field] : |
43+
| instance_variables.rb:28:1:28:4 | [post] foo2 [@field] : | instance_variables.rb:29:6:29:9 | foo2 [@field] : |
44+
| instance_variables.rb:28:14:28:23 | call to source : | instance_variables.rb:28:1:28:4 | [post] foo2 [@field] : |
45+
| instance_variables.rb:28:14:28:23 | call to source : | instance_variables.rb:28:1:28:4 | [post] foo2 [@field] : |
46+
| instance_variables.rb:29:6:29:9 | foo2 [@field] : | instance_variables.rb:5:5:7:7 | self in get_field [@field] : |
47+
| instance_variables.rb:29:6:29:9 | foo2 [@field] : | instance_variables.rb:5:5:7:7 | self in get_field [@field] : |
48+
| instance_variables.rb:29:6:29:9 | foo2 [@field] : | instance_variables.rb:29:6:29:19 | call to get_field |
49+
| instance_variables.rb:29:6:29:9 | foo2 [@field] : | instance_variables.rb:29:6:29:19 | call to get_field |
50+
| instance_variables.rb:32:1:32:4 | [post] foo3 [@field] : | instance_variables.rb:33:6:33:9 | foo3 [@field] : |
51+
| instance_variables.rb:32:1:32:4 | [post] foo3 [@field] : | instance_variables.rb:33:6:33:9 | foo3 [@field] : |
52+
| instance_variables.rb:32:16:32:25 | call to source : | instance_variables.rb:2:19:2:19 | x : |
53+
| instance_variables.rb:32:16:32:25 | call to source : | instance_variables.rb:2:19:2:19 | x : |
54+
| instance_variables.rb:32:16:32:25 | call to source : | instance_variables.rb:32:1:32:4 | [post] foo3 [@field] : |
55+
| instance_variables.rb:32:16:32:25 | call to source : | instance_variables.rb:32:1:32:4 | [post] foo3 [@field] : |
56+
| instance_variables.rb:33:6:33:9 | foo3 [@field] : | instance_variables.rb:33:6:33:15 | call to field |
57+
| instance_variables.rb:33:6:33:9 | foo3 [@field] : | instance_variables.rb:33:6:33:15 | call to field |
58+
| instance_variables.rb:36:1:36:4 | [post] foo4 [@other] : | instance_variables.rb:37:6:37:9 | foo4 [@other] : |
59+
| instance_variables.rb:36:1:36:4 | [post] foo4 [@other] : | instance_variables.rb:37:6:37:9 | foo4 [@other] : |
60+
| instance_variables.rb:36:14:36:23 | call to source : | instance_variables.rb:36:1:36:4 | [post] foo4 [@other] : |
61+
| instance_variables.rb:36:14:36:23 | call to source : | instance_variables.rb:36:1:36:4 | [post] foo4 [@other] : |
62+
| instance_variables.rb:37:6:37:9 | foo4 [@other] : | instance_variables.rb:37:6:37:15 | call to other |
63+
| instance_variables.rb:37:6:37:9 | foo4 [@other] : | instance_variables.rb:37:6:37:15 | call to other |
3664
nodes
3765
| instance_variables.rb:2:19:2:19 | x : | semmle.label | x : |
3866
| instance_variables.rb:2:19:2:19 | x : | semmle.label | x : |
@@ -70,6 +98,38 @@ nodes
7098
| instance_variables.rb:20:15:20:23 | call to source : | semmle.label | call to source : |
7199
| instance_variables.rb:21:6:21:8 | bar [@field] : | semmle.label | bar [@field] : |
72100
| instance_variables.rb:21:6:21:18 | call to inc_field | semmle.label | call to inc_field |
101+
| instance_variables.rb:24:1:24:4 | [post] foo1 [@field] : | semmle.label | [post] foo1 [@field] : |
102+
| instance_variables.rb:24:1:24:4 | [post] foo1 [@field] : | semmle.label | [post] foo1 [@field] : |
103+
| instance_variables.rb:24:14:24:23 | call to source : | semmle.label | call to source : |
104+
| instance_variables.rb:24:14:24:23 | call to source : | semmle.label | call to source : |
105+
| instance_variables.rb:25:6:25:9 | foo1 [@field] : | semmle.label | foo1 [@field] : |
106+
| instance_variables.rb:25:6:25:9 | foo1 [@field] : | semmle.label | foo1 [@field] : |
107+
| instance_variables.rb:25:6:25:15 | call to field | semmle.label | call to field |
108+
| instance_variables.rb:25:6:25:15 | call to field | semmle.label | call to field |
109+
| instance_variables.rb:28:1:28:4 | [post] foo2 [@field] : | semmle.label | [post] foo2 [@field] : |
110+
| instance_variables.rb:28:1:28:4 | [post] foo2 [@field] : | semmle.label | [post] foo2 [@field] : |
111+
| instance_variables.rb:28:14:28:23 | call to source : | semmle.label | call to source : |
112+
| instance_variables.rb:28:14:28:23 | call to source : | semmle.label | call to source : |
113+
| instance_variables.rb:29:6:29:9 | foo2 [@field] : | semmle.label | foo2 [@field] : |
114+
| instance_variables.rb:29:6:29:9 | foo2 [@field] : | semmle.label | foo2 [@field] : |
115+
| instance_variables.rb:29:6:29:19 | call to get_field | semmle.label | call to get_field |
116+
| instance_variables.rb:29:6:29:19 | call to get_field | semmle.label | call to get_field |
117+
| instance_variables.rb:32:1:32:4 | [post] foo3 [@field] : | semmle.label | [post] foo3 [@field] : |
118+
| instance_variables.rb:32:1:32:4 | [post] foo3 [@field] : | semmle.label | [post] foo3 [@field] : |
119+
| instance_variables.rb:32:16:32:25 | call to source : | semmle.label | call to source : |
120+
| instance_variables.rb:32:16:32:25 | call to source : | semmle.label | call to source : |
121+
| instance_variables.rb:33:6:33:9 | foo3 [@field] : | semmle.label | foo3 [@field] : |
122+
| instance_variables.rb:33:6:33:9 | foo3 [@field] : | semmle.label | foo3 [@field] : |
123+
| instance_variables.rb:33:6:33:15 | call to field | semmle.label | call to field |
124+
| instance_variables.rb:33:6:33:15 | call to field | semmle.label | call to field |
125+
| instance_variables.rb:36:1:36:4 | [post] foo4 [@other] : | semmle.label | [post] foo4 [@other] : |
126+
| instance_variables.rb:36:1:36:4 | [post] foo4 [@other] : | semmle.label | [post] foo4 [@other] : |
127+
| instance_variables.rb:36:14:36:23 | call to source : | semmle.label | call to source : |
128+
| instance_variables.rb:36:14:36:23 | call to source : | semmle.label | call to source : |
129+
| instance_variables.rb:37:6:37:9 | foo4 [@other] : | semmle.label | foo4 [@other] : |
130+
| instance_variables.rb:37:6:37:9 | foo4 [@other] : | semmle.label | foo4 [@other] : |
131+
| instance_variables.rb:37:6:37:15 | call to other | semmle.label | call to other |
132+
| instance_variables.rb:37:6:37:15 | call to other | semmle.label | call to other |
73133
subpaths
74134
| instance_variables.rb:16:15:16:24 | call to source : | instance_variables.rb:2:19:2:19 | x : | instance_variables.rb:3:9:3:14 | [post] self [@field] : | instance_variables.rb:16:1:16:3 | [post] foo [@field] : |
75135
| instance_variables.rb:16:15:16:24 | call to source : | instance_variables.rb:2:19:2:19 | x : | instance_variables.rb:3:9:3:14 | [post] self [@field] : | instance_variables.rb:16:1:16:3 | [post] foo [@field] : |
@@ -78,7 +138,15 @@ subpaths
78138
| instance_variables.rb:20:15:20:23 | call to source : | instance_variables.rb:2:19:2:19 | x : | instance_variables.rb:3:9:3:14 | [post] self [@field] : | instance_variables.rb:20:1:20:3 | [post] bar [@field] : |
79139
| instance_variables.rb:21:6:21:8 | bar [@field] : | instance_variables.rb:8:5:10:7 | self in inc_field [@field] : | instance_variables.rb:8:5:10:7 | self in inc_field [@field] : | instance_variables.rb:21:6:21:18 | call to inc_field |
80140
| instance_variables.rb:21:6:21:8 | bar [@field] : | instance_variables.rb:8:5:10:7 | self in inc_field [@field] : | instance_variables.rb:9:9:9:14 | [post] self [@field] : | instance_variables.rb:21:6:21:18 | call to inc_field |
141+
| instance_variables.rb:29:6:29:9 | foo2 [@field] : | instance_variables.rb:5:5:7:7 | self in get_field [@field] : | instance_variables.rb:6:9:6:21 | return : | instance_variables.rb:29:6:29:19 | call to get_field |
142+
| instance_variables.rb:29:6:29:9 | foo2 [@field] : | instance_variables.rb:5:5:7:7 | self in get_field [@field] : | instance_variables.rb:6:9:6:21 | return : | instance_variables.rb:29:6:29:19 | call to get_field |
143+
| instance_variables.rb:32:16:32:25 | call to source : | instance_variables.rb:2:19:2:19 | x : | instance_variables.rb:3:9:3:14 | [post] self [@field] : | instance_variables.rb:32:1:32:4 | [post] foo3 [@field] : |
144+
| instance_variables.rb:32:16:32:25 | call to source : | instance_variables.rb:2:19:2:19 | x : | instance_variables.rb:3:9:3:14 | [post] self [@field] : | instance_variables.rb:32:1:32:4 | [post] foo3 [@field] : |
81145
#select
82146
| instance_variables.rb:12:10:12:13 | @foo | instance_variables.rb:11:12:11:22 | call to source : | instance_variables.rb:12:10:12:13 | @foo | $@ | instance_variables.rb:11:12:11:22 | call to source : | call to source : |
83147
| instance_variables.rb:17:6:17:18 | call to get_field | instance_variables.rb:16:15:16:24 | call to source : | instance_variables.rb:17:6:17:18 | call to get_field | $@ | instance_variables.rb:16:15:16:24 | call to source : | call to source : |
84148
| instance_variables.rb:21:6:21:18 | call to inc_field | instance_variables.rb:20:15:20:23 | call to source : | instance_variables.rb:21:6:21:18 | call to inc_field | $@ | instance_variables.rb:20:15:20:23 | call to source : | call to source : |
149+
| instance_variables.rb:25:6:25:15 | call to field | instance_variables.rb:24:14:24:23 | call to source : | instance_variables.rb:25:6:25:15 | call to field | $@ | instance_variables.rb:24:14:24:23 | call to source : | call to source : |
150+
| instance_variables.rb:29:6:29:19 | call to get_field | instance_variables.rb:28:14:28:23 | call to source : | instance_variables.rb:29:6:29:19 | call to get_field | $@ | instance_variables.rb:28:14:28:23 | call to source : | call to source : |
151+
| instance_variables.rb:33:6:33:15 | call to field | instance_variables.rb:32:16:32:25 | call to source : | instance_variables.rb:33:6:33:15 | call to field | $@ | instance_variables.rb:32:16:32:25 | call to source : | call to source : |
152+
| instance_variables.rb:37:6:37:15 | call to other | instance_variables.rb:36:14:36:23 | call to source : | instance_variables.rb:37:6:37:15 | call to other | $@ | instance_variables.rb:36:14:36:23 | call to source : | call to source : |

ruby/ql/test/library-tests/dataflow/global/instance_variables.rb

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,20 @@ def inc_field
1818

1919
bar = Foo.new
2020
bar.set_field(source(5))
21-
sink(bar.inc_field) # $ hasTaintFlow=5
21+
sink(bar.inc_field) # $ hasTaintFlow=5
22+
23+
foo1 = Foo.new
24+
foo1.field = source(20)
25+
sink(foo1.field) # $ hasValueFlow=20
26+
27+
foo2 = Foo.new
28+
foo2.field = source(21)
29+
sink(foo2.get_field) # $ hasValueFlow=21
30+
31+
foo3 = Foo.new
32+
foo3.set_field(source(22))
33+
sink(foo3.field) # $ hasValueFlow=22
34+
35+
foo4 = "hello"
36+
foo4.other = source(23)
37+
sink(foo4.other) # $ hasValueFlow=23

ruby/ql/test/library-tests/dataflow/summaries/Summaries.expected

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,12 @@ edges
8686
| summaries.rb:82:1:82:1 | a [element 2] : | summaries.rb:82:1:82:1 | [post] a [element 2] : |
8787
| summaries.rb:85:6:85:6 | a [element 2] : | summaries.rb:85:6:85:9 | ...[...] |
8888
| summaries.rb:85:6:85:6 | a [element 2] : | summaries.rb:85:6:85:9 | ...[...] |
89+
| summaries.rb:88:1:88:1 | [post] x [@value] : | summaries.rb:89:6:89:6 | x [@value] : |
90+
| summaries.rb:88:1:88:1 | [post] x [@value] : | summaries.rb:89:6:89:6 | x [@value] : |
91+
| summaries.rb:88:13:88:26 | call to source : | summaries.rb:88:1:88:1 | [post] x [@value] : |
92+
| summaries.rb:88:13:88:26 | call to source : | summaries.rb:88:1:88:1 | [post] x [@value] : |
93+
| summaries.rb:89:6:89:6 | x [@value] : | summaries.rb:89:6:89:16 | call to get_value |
94+
| summaries.rb:89:6:89:6 | x [@value] : | summaries.rb:89:6:89:16 | call to get_value |
8995
nodes
9096
| summaries.rb:1:11:1:36 | call to identity : | semmle.label | call to identity : |
9197
| summaries.rb:1:11:1:36 | call to identity : | semmle.label | call to identity : |
@@ -183,6 +189,14 @@ nodes
183189
| summaries.rb:85:6:85:6 | a [element 2] : | semmle.label | a [element 2] : |
184190
| summaries.rb:85:6:85:9 | ...[...] | semmle.label | ...[...] |
185191
| summaries.rb:85:6:85:9 | ...[...] | semmle.label | ...[...] |
192+
| summaries.rb:88:1:88:1 | [post] x [@value] : | semmle.label | [post] x [@value] : |
193+
| summaries.rb:88:1:88:1 | [post] x [@value] : | semmle.label | [post] x [@value] : |
194+
| summaries.rb:88:13:88:26 | call to source : | semmle.label | call to source : |
195+
| summaries.rb:88:13:88:26 | call to source : | semmle.label | call to source : |
196+
| summaries.rb:89:6:89:6 | x [@value] : | semmle.label | x [@value] : |
197+
| summaries.rb:89:6:89:6 | x [@value] : | semmle.label | x [@value] : |
198+
| summaries.rb:89:6:89:16 | call to get_value | semmle.label | call to get_value |
199+
| summaries.rb:89:6:89:16 | call to get_value | semmle.label | call to get_value |
186200
subpaths
187201
invalidSpecComponent
188202
#select
@@ -227,6 +241,8 @@ invalidSpecComponent
227241
| summaries.rb:80:6:80:9 | ...[...] | summaries.rb:74:15:74:29 | call to source : | summaries.rb:80:6:80:9 | ...[...] | $@ | summaries.rb:74:15:74:29 | call to source : | call to source : |
228242
| summaries.rb:85:6:85:9 | ...[...] | summaries.rb:74:32:74:46 | call to source : | summaries.rb:85:6:85:9 | ...[...] | $@ | summaries.rb:74:32:74:46 | call to source : | call to source : |
229243
| summaries.rb:85:6:85:9 | ...[...] | summaries.rb:74:32:74:46 | call to source : | summaries.rb:85:6:85:9 | ...[...] | $@ | summaries.rb:74:32:74:46 | call to source : | call to source : |
244+
| summaries.rb:89:6:89:16 | call to get_value | summaries.rb:88:13:88:26 | call to source : | summaries.rb:89:6:89:16 | call to get_value | $@ | summaries.rb:88:13:88:26 | call to source : | call to source : |
245+
| summaries.rb:89:6:89:16 | call to get_value | summaries.rb:88:13:88:26 | call to source : | summaries.rb:89:6:89:16 | call to get_value | $@ | summaries.rb:88:13:88:26 | call to source : | call to source : |
230246
warning
231247
| CSV type row should have 5 columns but has 2: test;TooFewColumns |
232248
| CSV type row should have 5 columns but has 8: test;TooManyColumns;;;Member[Foo].Instance;too;many;columns |

ruby/ql/test/library-tests/dataflow/summaries/Summaries.ql

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ private class StepsFromModel extends ModelInput::SummaryModelCsv {
6666
override predicate row(string row) {
6767
row =
6868
[
69+
";any;Method[set_value];Argument[0];Argument[self].Field[@value];value",
70+
";any;Method[get_value];Argument[self].Field[@value];ReturnValue;value",
6971
";;Member[Foo].Method[firstArg];Argument[0];ReturnValue;taint",
7072
";;Member[Foo].Method[secondArg];Argument[1];ReturnValue;taint",
7173
";;Member[Foo].Method[onlyWithoutBlock].WithoutBlock;Argument[0];ReturnValue;taint",

ruby/ql/test/library-tests/dataflow/summaries/summaries.rb

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,4 +82,8 @@ def userDefinedFunction(x, y)
8282
a.withoutElementOne()
8383
sink(a[0])
8484
sink(a[1])
85-
sink(a[2]) # $ hasValueFlow=elem2
85+
sink(a[2]) # $ hasValueFlow=elem2
86+
87+
x = Foo.new
88+
x.set_value(source("attr"))
89+
sink(x.get_value) # $ hasValueFlow=attr

0 commit comments

Comments
 (0)