Skip to content

Commit ca0a1dc

Browse files
authored
Merge pull request github#15267 from atorralba/atorralba/go/fmt-appenderorsprinter-mad
Go: Migrate AppenderOrSprinter model to models-as-data
2 parents e64ce22 + 46df585 commit ca0a1dc

File tree

12 files changed

+115
-4
lines changed

12 files changed

+115
-4
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
category: deprecated
3+
---
4+
* The class `Fmt::AppenderOrSprinter` of the `Fmt.qll` module has been deprecated. Use the new `Fmt::AppenderOrSprinterFunc` class instead. Its taint flow features have been migrated to models-as-data.

go/ql/lib/ext/fmt.model.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,14 @@ extensions:
88
- ["fmt", "ScanState", True, "Token", "", "", "Argument[-1]", "ReturnValue[0]", "taint", "manual"]
99
- ["fmt", "State", True, "Write", "", "", "Argument[0]", "Argument[-1]", "taint", "manual"]
1010
- ["fmt", "Stringer", True, "String", "", "", "Argument[-1]", "ReturnValue", "taint", "manual"]
11+
- ["fmt", "", True, "Append", "", "", "Argument[0]", "ReturnValue", "taint", "manual"]
12+
- ["fmt", "", True, "Append", "", "", "Argument[1].ArrayElement", "ReturnValue", "taint", "manual"]
13+
- ["fmt", "", True, "Appendf", "", "", "Argument[0]", "ReturnValue", "taint", "manual"]
14+
- ["fmt", "", True, "Appendf", "", "", "Argument[1]", "ReturnValue", "taint", "manual"]
15+
- ["fmt", "", True, "Appendf", "", "", "Argument[2].ArrayElement", "ReturnValue", "taint", "manual"]
16+
- ["fmt", "", True, "Appendln", "", "", "Argument[0]", "ReturnValue", "taint", "manual"]
17+
- ["fmt", "", True, "Appendln", "", "", "Argument[1].ArrayElement", "ReturnValue", "taint", "manual"]
18+
- ["fmt", "", True, "Sprint", "", "", "Argument[0].ArrayElement", "ReturnValue", "taint", "manual"]
19+
- ["fmt", "", True, "Sprintf", "", "", "Argument[0]", "ReturnValue", "taint", "manual"]
20+
- ["fmt", "", True, "Sprintf", "", "", "Argument[1].ArrayElement", "ReturnValue", "taint", "manual"]
21+
- ["fmt", "", True, "Sprintln", "", "", "Argument[0].ArrayElement", "ReturnValue", "taint", "manual"]

go/ql/lib/semmle/go/frameworks/stdlib/Fmt.qll

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,28 @@ import go
77
// Some TaintTracking::FunctionModel subclasses remain because varargs functions don't work with Models-as-Data sumamries yet.
88
/** Provides models of commonly used functions in the `fmt` package. */
99
module Fmt {
10-
/** The `Sprint` or `Append` functions or one of their variants. */
11-
class AppenderOrSprinter extends TaintTracking::FunctionModel {
10+
/**
11+
* The `Sprint` or `Append` functions or one of their variants.
12+
*
13+
* DEPRECATED: Use AppenderOrSprinterFunc instead.
14+
*/
15+
deprecated class AppenderOrSprinter extends TaintTracking::FunctionModel {
1216
AppenderOrSprinter() { this.hasQualifiedName("fmt", ["Append", "Sprint"] + ["", "f", "ln"]) }
1317

1418
override predicate hasTaintFlow(FunctionInput inp, FunctionOutput outp) {
1519
inp.isParameter(_) and outp.isResult()
1620
}
1721
}
1822

23+
/** The `Sprint` or `Append` functions or one of their variants. */
24+
class AppenderOrSprinterFunc extends Function {
25+
AppenderOrSprinterFunc() {
26+
this.hasQualifiedName("fmt", ["Append", "Sprint"] + ["", "f", "ln"])
27+
}
28+
}
29+
1930
/** The `Sprint` function or one of its variants. */
20-
class Sprinter extends AppenderOrSprinter {
31+
class Sprinter extends AppenderOrSprinterFunc {
2132
Sprinter() { this.getName().matches("Sprint%") }
2233
}
2334

go/ql/src/Security/CWE-352/ConstantOauth2State.ql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ module PrivateUrlFlowsToAuthCodeUrlCallConfig implements DataFlow::ConfigSig {
9999
or
100100
// Propagate across Sprintf and similar calls
101101
exists(DataFlow::CallNode cn |
102-
cn.getACalleeIncludingExternals().asFunction() instanceof Fmt::AppenderOrSprinter
102+
cn.getACalleeIncludingExternals().asFunction() instanceof Fmt::AppenderOrSprinterFunc
103103
|
104104
pred = cn.getASyntacticArgument() and succ = cn.getResult()
105105
)

go/ql/test/experimental/CWE-74/DsnInjection.expected

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
edges
22
| Dsn.go:47:10:47:30 | call to FormValue | Dsn.go:49:102:49:105 | name |
3+
| Dsn.go:49:11:49:106 | []type{args} [array] | Dsn.go:49:11:49:106 | call to Sprintf |
34
| Dsn.go:49:11:49:106 | call to Sprintf | Dsn.go:50:29:50:33 | dbDSN |
5+
| Dsn.go:49:102:49:105 | name | Dsn.go:49:11:49:106 | []type{args} [array] |
46
| Dsn.go:49:102:49:105 | name | Dsn.go:49:11:49:106 | call to Sprintf |
57
nodes
68
| Dsn.go:47:10:47:30 | call to FormValue | semmle.label | call to FormValue |
9+
| Dsn.go:49:11:49:106 | []type{args} [array] | semmle.label | []type{args} [array] |
710
| Dsn.go:49:11:49:106 | call to Sprintf | semmle.label | call to Sprintf |
811
| Dsn.go:49:102:49:105 | name | semmle.label | name |
912
| Dsn.go:50:29:50:33 | dbDSN | semmle.label | dbDSN |

go/ql/test/experimental/CWE-74/DsnInjectionLocal.expected

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
edges
22
| Dsn.go:26:11:26:17 | selection of Args | Dsn.go:28:102:28:109 | index expression |
3+
| Dsn.go:28:11:28:110 | []type{args} [array] | Dsn.go:28:11:28:110 | call to Sprintf |
34
| Dsn.go:28:11:28:110 | call to Sprintf | Dsn.go:29:29:29:33 | dbDSN |
5+
| Dsn.go:28:102:28:109 | index expression | Dsn.go:28:11:28:110 | []type{args} [array] |
46
| Dsn.go:28:102:28:109 | index expression | Dsn.go:28:11:28:110 | call to Sprintf |
57
| Dsn.go:62:2:62:4 | definition of cfg [pointer] | Dsn.go:63:9:63:11 | cfg [pointer] |
68
| Dsn.go:62:2:62:4 | definition of cfg [pointer] | Dsn.go:67:102:67:104 | cfg [pointer] |
@@ -10,13 +12,16 @@ edges
1012
| Dsn.go:63:9:63:11 | implicit dereference | Dsn.go:67:102:67:108 | selection of dsn |
1113
| Dsn.go:63:19:63:25 | selection of Args | Dsn.go:63:19:63:29 | slice expression |
1214
| Dsn.go:63:19:63:29 | slice expression | Dsn.go:63:9:63:11 | implicit dereference |
15+
| Dsn.go:67:11:67:109 | []type{args} [array] | Dsn.go:67:11:67:109 | call to Sprintf |
1316
| Dsn.go:67:11:67:109 | call to Sprintf | Dsn.go:68:29:68:33 | dbDSN |
1417
| Dsn.go:67:102:67:104 | cfg [pointer] | Dsn.go:67:102:67:104 | implicit dereference |
1518
| Dsn.go:67:102:67:104 | implicit dereference | Dsn.go:63:9:63:11 | implicit dereference |
1619
| Dsn.go:67:102:67:104 | implicit dereference | Dsn.go:67:102:67:108 | selection of dsn |
20+
| Dsn.go:67:102:67:108 | selection of dsn | Dsn.go:67:11:67:109 | []type{args} [array] |
1721
| Dsn.go:67:102:67:108 | selection of dsn | Dsn.go:67:11:67:109 | call to Sprintf |
1822
nodes
1923
| Dsn.go:26:11:26:17 | selection of Args | semmle.label | selection of Args |
24+
| Dsn.go:28:11:28:110 | []type{args} [array] | semmle.label | []type{args} [array] |
2025
| Dsn.go:28:11:28:110 | call to Sprintf | semmle.label | call to Sprintf |
2126
| Dsn.go:28:102:28:109 | index expression | semmle.label | index expression |
2227
| Dsn.go:29:29:29:33 | dbDSN | semmle.label | dbDSN |
@@ -25,6 +30,7 @@ nodes
2530
| Dsn.go:63:9:63:11 | implicit dereference | semmle.label | implicit dereference |
2631
| Dsn.go:63:19:63:25 | selection of Args | semmle.label | selection of Args |
2732
| Dsn.go:63:19:63:29 | slice expression | semmle.label | slice expression |
33+
| Dsn.go:67:11:67:109 | []type{args} [array] | semmle.label | []type{args} [array] |
2834
| Dsn.go:67:11:67:109 | call to Sprintf | semmle.label | call to Sprintf |
2935
| Dsn.go:67:102:67:104 | cfg [pointer] | semmle.label | cfg [pointer] |
3036
| Dsn.go:67:102:67:104 | implicit dereference | semmle.label | implicit dereference |

go/ql/test/experimental/CWE-918/SSRF.expected

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,14 @@ edges
77
| new-tests.go:26:26:26:30 | &... | new-tests.go:31:48:31:56 | selection of word |
88
| new-tests.go:26:26:26:30 | &... | new-tests.go:32:48:32:56 | selection of safe |
99
| new-tests.go:26:26:26:30 | &... | new-tests.go:35:49:35:57 | selection of word |
10+
| new-tests.go:31:11:31:57 | []type{args} [array] | new-tests.go:31:11:31:57 | call to Sprintf |
11+
| new-tests.go:31:48:31:56 | selection of word | new-tests.go:31:11:31:57 | []type{args} [array] |
1012
| new-tests.go:31:48:31:56 | selection of word | new-tests.go:31:11:31:57 | call to Sprintf |
13+
| new-tests.go:32:11:32:57 | []type{args} [array] | new-tests.go:32:11:32:57 | call to Sprintf |
14+
| new-tests.go:32:48:32:56 | selection of safe | new-tests.go:32:11:32:57 | []type{args} [array] |
1115
| new-tests.go:32:48:32:56 | selection of safe | new-tests.go:32:11:32:57 | call to Sprintf |
16+
| new-tests.go:35:12:35:58 | []type{args} [array] | new-tests.go:35:12:35:58 | call to Sprintf |
17+
| new-tests.go:35:49:35:57 | selection of word | new-tests.go:35:12:35:58 | []type{args} [array] |
1218
| new-tests.go:35:49:35:57 | selection of word | new-tests.go:35:12:35:58 | call to Sprintf |
1319
| new-tests.go:39:18:39:30 | call to Param | new-tests.go:47:11:47:46 | ...+... |
1420
| new-tests.go:49:18:49:30 | call to Query | new-tests.go:50:11:50:46 | ...+... |
@@ -18,8 +24,14 @@ edges
1824
| new-tests.go:63:26:63:30 | &... | new-tests.go:68:48:68:56 | selection of word |
1925
| new-tests.go:63:26:63:30 | &... | new-tests.go:69:48:69:56 | selection of safe |
2026
| new-tests.go:63:26:63:30 | &... | new-tests.go:74:49:74:57 | selection of word |
27+
| new-tests.go:68:11:68:57 | []type{args} [array] | new-tests.go:68:11:68:57 | call to Sprintf |
28+
| new-tests.go:68:48:68:56 | selection of word | new-tests.go:68:11:68:57 | []type{args} [array] |
2129
| new-tests.go:68:48:68:56 | selection of word | new-tests.go:68:11:68:57 | call to Sprintf |
30+
| new-tests.go:69:11:69:57 | []type{args} [array] | new-tests.go:69:11:69:57 | call to Sprintf |
31+
| new-tests.go:69:48:69:56 | selection of safe | new-tests.go:69:11:69:57 | []type{args} [array] |
2232
| new-tests.go:69:48:69:56 | selection of safe | new-tests.go:69:11:69:57 | call to Sprintf |
33+
| new-tests.go:74:12:74:58 | []type{args} [array] | new-tests.go:74:12:74:58 | call to Sprintf |
34+
| new-tests.go:74:49:74:57 | selection of word | new-tests.go:74:12:74:58 | []type{args} [array] |
2335
| new-tests.go:74:49:74:57 | selection of word | new-tests.go:74:12:74:58 | call to Sprintf |
2436
| new-tests.go:78:18:78:24 | selection of URL | new-tests.go:78:18:78:32 | call to Query |
2537
| new-tests.go:78:18:78:32 | call to Query | new-tests.go:78:18:78:46 | call to Get |
@@ -41,10 +53,13 @@ nodes
4153
| builtin.go:129:21:129:31 | call to Referer | semmle.label | call to Referer |
4254
| builtin.go:132:38:132:51 | untrustedInput | semmle.label | untrustedInput |
4355
| new-tests.go:26:26:26:30 | &... | semmle.label | &... |
56+
| new-tests.go:31:11:31:57 | []type{args} [array] | semmle.label | []type{args} [array] |
4457
| new-tests.go:31:11:31:57 | call to Sprintf | semmle.label | call to Sprintf |
4558
| new-tests.go:31:48:31:56 | selection of word | semmle.label | selection of word |
59+
| new-tests.go:32:11:32:57 | []type{args} [array] | semmle.label | []type{args} [array] |
4660
| new-tests.go:32:11:32:57 | call to Sprintf | semmle.label | call to Sprintf |
4761
| new-tests.go:32:48:32:56 | selection of safe | semmle.label | selection of safe |
62+
| new-tests.go:35:12:35:58 | []type{args} [array] | semmle.label | []type{args} [array] |
4863
| new-tests.go:35:12:35:58 | call to Sprintf | semmle.label | call to Sprintf |
4964
| new-tests.go:35:49:35:57 | selection of word | semmle.label | selection of word |
5065
| new-tests.go:39:18:39:30 | call to Param | semmle.label | call to Param |
@@ -55,10 +70,13 @@ nodes
5570
| new-tests.go:62:31:62:38 | selection of Body | semmle.label | selection of Body |
5671
| new-tests.go:63:17:63:23 | reqBody | semmle.label | reqBody |
5772
| new-tests.go:63:26:63:30 | &... | semmle.label | &... |
73+
| new-tests.go:68:11:68:57 | []type{args} [array] | semmle.label | []type{args} [array] |
5874
| new-tests.go:68:11:68:57 | call to Sprintf | semmle.label | call to Sprintf |
5975
| new-tests.go:68:48:68:56 | selection of word | semmle.label | selection of word |
76+
| new-tests.go:69:11:69:57 | []type{args} [array] | semmle.label | []type{args} [array] |
6077
| new-tests.go:69:11:69:57 | call to Sprintf | semmle.label | call to Sprintf |
6178
| new-tests.go:69:48:69:56 | selection of safe | semmle.label | selection of safe |
79+
| new-tests.go:74:12:74:58 | []type{args} [array] | semmle.label | []type{args} [array] |
6280
| new-tests.go:74:12:74:58 | call to Sprintf | semmle.label | call to Sprintf |
6381
| new-tests.go:74:49:74:57 | selection of word | semmle.label | selection of word |
6482
| new-tests.go:78:18:78:24 | selection of URL | semmle.label | selection of URL |

go/ql/test/library-tests/semmle/go/dataflow/FlowSteps/LocalTaintStep.expected

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1+
| file://:0:0:0:0 | [summary param] 0 in Append | file://:0:0:0:0 | [summary] to write: ReturnValue in Append |
12
| file://:0:0:0:0 | [summary param] 0 in AppendQuote | file://:0:0:0:0 | [summary] to write: ReturnValue in AppendQuote |
23
| file://:0:0:0:0 | [summary param] 0 in AppendQuoteToASCII | file://:0:0:0:0 | [summary] to write: ReturnValue in AppendQuoteToASCII |
34
| file://:0:0:0:0 | [summary param] 0 in AppendQuoteToGraphic | file://:0:0:0:0 | [summary] to write: ReturnValue in AppendQuoteToGraphic |
45
| file://:0:0:0:0 | [summary param] 0 in AppendSlice | file://:0:0:0:0 | [summary] to write: ReturnValue in AppendSlice |
6+
| file://:0:0:0:0 | [summary param] 0 in Appendf | file://:0:0:0:0 | [summary] to write: ReturnValue in Appendf |
7+
| file://:0:0:0:0 | [summary param] 0 in Appendln | file://:0:0:0:0 | [summary] to write: ReturnValue in Appendln |
58
| file://:0:0:0:0 | [summary param] 0 in As | file://:0:0:0:0 | [summary] to write: Argument[1] in As |
69
| file://:0:0:0:0 | [summary param] 0 in Base | file://:0:0:0:0 | [summary] to write: ReturnValue in Base |
710
| file://:0:0:0:0 | [summary param] 0 in BytePtrFromString | file://:0:0:0:0 | [summary] to write: ReturnValue in BytePtrFromString |
@@ -67,6 +70,7 @@
6770
| file://:0:0:0:0 | [summary param] 0 in SplitAfter | file://:0:0:0:0 | [summary] to write: ReturnValue in SplitAfter |
6871
| file://:0:0:0:0 | [summary param] 0 in SplitAfterN | file://:0:0:0:0 | [summary] to write: ReturnValue in SplitAfterN |
6972
| file://:0:0:0:0 | [summary param] 0 in SplitN | file://:0:0:0:0 | [summary] to write: ReturnValue in SplitN |
73+
| file://:0:0:0:0 | [summary param] 0 in Sprintf | file://:0:0:0:0 | [summary] to write: ReturnValue in Sprintf |
7074
| file://:0:0:0:0 | [summary param] 0 in Store | file://:0:0:0:0 | [summary] to write: Argument[-1] in Store |
7175
| file://:0:0:0:0 | [summary param] 0 in Store | file://:0:0:0:0 | [summary] to write: Argument[-1] in Store |
7276
| file://:0:0:0:0 | [summary param] 0 in Store | file://:0:0:0:0 | [summary] to write: Argument[-1] in Store |
@@ -133,6 +137,7 @@
133137
| file://:0:0:0:0 | [summary param] 1 in AppendQuoteToASCII | file://:0:0:0:0 | [summary] to write: ReturnValue in AppendQuoteToASCII |
134138
| file://:0:0:0:0 | [summary param] 1 in AppendQuoteToGraphic | file://:0:0:0:0 | [summary] to write: ReturnValue in AppendQuoteToGraphic |
135139
| file://:0:0:0:0 | [summary param] 1 in AppendSlice | file://:0:0:0:0 | [summary] to write: ReturnValue in AppendSlice |
140+
| file://:0:0:0:0 | [summary param] 1 in Appendf | file://:0:0:0:0 | [summary] to write: ReturnValue in Appendf |
136141
| file://:0:0:0:0 | [summary param] 1 in Copy | file://:0:0:0:0 | [summary] to write: Argument[0] in Copy |
137142
| file://:0:0:0:0 | [summary param] 1 in Copy | file://:0:0:0:0 | [summary] to write: Argument[0] in Copy |
138143
| file://:0:0:0:0 | [summary param] 1 in CopyBuffer | file://:0:0:0:0 | [summary] to write: Argument[0] in CopyBuffer |
@@ -287,6 +292,12 @@
287292
| file://:0:0:0:0 | [summary param] -1 in WriteTo | file://:0:0:0:0 | [summary] to write: Argument[0] in WriteTo |
288293
| file://:0:0:0:0 | [summary param] -1 in WriteTo | file://:0:0:0:0 | [summary] to write: Argument[0] in WriteTo |
289294
| file://:0:0:0:0 | [summary param] -1 in WriteTo | file://:0:0:0:0 | [summary] to write: Argument[0] in WriteTo |
295+
| file://:0:0:0:0 | [summary] read: Argument[0].ArrayElement in Sprint | file://:0:0:0:0 | [summary] to write: ReturnValue in Sprint |
296+
| file://:0:0:0:0 | [summary] read: Argument[0].ArrayElement in Sprintln | file://:0:0:0:0 | [summary] to write: ReturnValue in Sprintln |
297+
| file://:0:0:0:0 | [summary] read: Argument[1].ArrayElement in Append | file://:0:0:0:0 | [summary] to write: ReturnValue in Append |
298+
| file://:0:0:0:0 | [summary] read: Argument[1].ArrayElement in Appendln | file://:0:0:0:0 | [summary] to write: ReturnValue in Appendln |
299+
| file://:0:0:0:0 | [summary] read: Argument[1].ArrayElement in Sprintf | file://:0:0:0:0 | [summary] to write: ReturnValue in Sprintf |
300+
| file://:0:0:0:0 | [summary] read: Argument[2].ArrayElement in Appendf | file://:0:0:0:0 | [summary] to write: ReturnValue in Appendf |
290301
| main.go:26:11:26:17 | type assertion | main.go:26:2:26:17 | ... := ...[0] |
291302
| main.go:26:11:26:17 | type assertion | main.go:26:2:26:17 | ... := ...[1] |
292303
| main.go:38:13:38:13 | 1 | main.go:38:7:38:20 | slice literal |

go/ql/test/library-tests/semmle/go/frameworks/TaintSteps/TaintStep.expected

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,9 +318,14 @@
318318
| errors.As | file://:0:0:0:0 | [summary param] 0 in As | file://:0:0:0:0 | [summary] to write: Argument[1] in As |
319319
| errors.New | file://:0:0:0:0 | [summary param] 0 in New | file://:0:0:0:0 | [summary] to write: ReturnValue in New |
320320
| errors.Unwrap | file://:0:0:0:0 | [summary param] 0 in Unwrap | file://:0:0:0:0 | [summary] to write: ReturnValue in Unwrap |
321+
| fmt.Append | file://:0:0:0:0 | [summary param] 0 in Append | file://:0:0:0:0 | [summary] to write: ReturnValue in Append |
322+
| fmt.Appendf | file://:0:0:0:0 | [summary param] 0 in Appendf | file://:0:0:0:0 | [summary] to write: ReturnValue in Appendf |
323+
| fmt.Appendf | file://:0:0:0:0 | [summary param] 1 in Appendf | file://:0:0:0:0 | [summary] to write: ReturnValue in Appendf |
324+
| fmt.Appendln | file://:0:0:0:0 | [summary param] 0 in Appendln | file://:0:0:0:0 | [summary] to write: ReturnValue in Appendln |
321325
| fmt.GoStringer.GoString | file://:0:0:0:0 | [summary param] -1 in GoString | file://:0:0:0:0 | [summary] to write: ReturnValue in GoString |
322326
| fmt.ScanState.Read | file://:0:0:0:0 | [summary param] -1 in Read | file://:0:0:0:0 | [summary] to write: Argument[0] in Read |
323327
| fmt.ScanState.Token | file://:0:0:0:0 | [summary param] -1 in Token | file://:0:0:0:0 | [summary] to write: ReturnValue in Token |
328+
| fmt.Sprintf | file://:0:0:0:0 | [summary param] 0 in Sprintf | file://:0:0:0:0 | [summary] to write: ReturnValue in Sprintf |
324329
| fmt.State.Write | file://:0:0:0:0 | [summary param] 0 in Write | file://:0:0:0:0 | [summary] to write: Argument[-1] in Write |
325330
| fmt.Stringer.String | file://:0:0:0:0 | [summary param] -1 in String | file://:0:0:0:0 | [summary] to write: ReturnValue in String |
326331
| fmt.pp.Write | file://:0:0:0:0 | [summary param] 0 in Write | file://:0:0:0:0 | [summary] to write: Argument[-1] in Write |

0 commit comments

Comments
 (0)