grpc 1.53.0.pre2 → 1.53.1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of grpc might be problematic. Click here for more details.

Files changed (31) hide show
  1. checksums.yaml +4 -4
  2. data/Makefile +4 -2
  3. data/include/grpc/impl/grpc_types.h +11 -2
  4. data/src/core/ext/filters/client_channel/http_proxy.cc +1 -1
  5. data/src/core/ext/filters/client_channel/lb_policy/rls/rls.cc +1 -1
  6. data/src/core/ext/filters/client_channel/lb_policy/weighted_target/weighted_target.cc +2 -2
  7. data/src/core/ext/transport/chttp2/transport/bin_encoder.cc +12 -8
  8. data/src/core/ext/transport/chttp2/transport/bin_encoder.h +5 -1
  9. data/src/core/ext/transport/chttp2/transport/chttp2_transport.cc +33 -2
  10. data/src/core/ext/transport/chttp2/transport/hpack_encoder.cc +118 -222
  11. data/src/core/ext/transport/chttp2/transport/hpack_encoder.h +295 -113
  12. data/src/core/ext/transport/chttp2/transport/hpack_encoder_table.cc +2 -0
  13. data/src/core/ext/transport/chttp2/transport/hpack_encoder_table.h +2 -0
  14. data/src/core/ext/transport/chttp2/transport/hpack_parser.cc +466 -273
  15. data/src/core/ext/transport/chttp2/transport/hpack_parser.h +7 -3
  16. data/src/core/ext/transport/chttp2/transport/hpack_parser_table.cc +14 -12
  17. data/src/core/ext/transport/chttp2/transport/hpack_parser_table.h +9 -1
  18. data/src/core/ext/transport/chttp2/transport/internal.h +2 -0
  19. data/src/core/ext/transport/chttp2/transport/parsing.cc +6 -0
  20. data/src/core/lib/backoff/random_early_detection.cc +31 -0
  21. data/src/core/lib/backoff/random_early_detection.h +59 -0
  22. data/src/core/lib/iomgr/endpoint_pair.h +2 -2
  23. data/src/core/lib/iomgr/endpoint_pair_posix.cc +2 -2
  24. data/src/core/lib/iomgr/endpoint_pair_windows.cc +1 -1
  25. data/src/core/lib/surface/validate_metadata.cc +43 -42
  26. data/src/core/lib/surface/validate_metadata.h +9 -0
  27. data/src/core/lib/transport/metadata_batch.cc +4 -4
  28. data/src/core/lib/transport/metadata_batch.h +153 -15
  29. data/src/core/lib/transport/parsed_metadata.h +19 -9
  30. data/src/ruby/lib/grpc/version.rb +1 -1
  31. metadata +7 -5
@@ -29,6 +29,7 @@
29
29
 
30
30
  #include "src/core/ext/transport/chttp2/transport/frame.h"
31
31
  #include "src/core/ext/transport/chttp2/transport/hpack_parser_table.h"
32
+ #include "src/core/lib/backoff/random_early_detection.h"
32
33
  #include "src/core/lib/iomgr/error.h"
33
34
  #include "src/core/lib/transport/metadata_batch.h"
34
35
 
@@ -80,7 +81,8 @@ class HPackParser {
80
81
  // Begin parsing a new frame
81
82
  // Sink receives each parsed header,
82
83
  void BeginFrame(grpc_metadata_batch* metadata_buffer,
83
- uint32_t metadata_size_limit, Boundary boundary,
84
+ uint32_t metadata_size_soft_limit,
85
+ uint32_t metadata_size_hard_limit, Boundary boundary,
84
86
  Priority priority, LogInfo log_info);
85
87
  // Start throwing away any received headers after parsing them.
86
88
  void StopBufferingFrame() { metadata_buffer_ = nullptr; }
@@ -103,7 +105,9 @@ class HPackParser {
103
105
  class String;
104
106
 
105
107
  grpc_error_handle ParseInput(Input input, bool is_last);
106
- bool ParseInputInner(Input* input);
108
+ void ParseInputInner(Input* input);
109
+ GPR_ATTRIBUTE_NOINLINE
110
+ void HandleMetadataSoftSizeLimitExceeded(Input* input);
107
111
 
108
112
  // Target metadata buffer
109
113
  grpc_metadata_batch* metadata_buffer_ = nullptr;
@@ -121,7 +125,7 @@ class HPackParser {
121
125
  uint8_t dynamic_table_updates_allowed_;
122
126
  // Length of frame so far.
123
127
  uint32_t frame_length_;
124
- uint32_t metadata_size_limit_;
128
+ RandomEarlyDetection metadata_early_detection_;
125
129
  // Information for logging
126
130
  LogInfo log_info_;
127
131
 
@@ -82,8 +82,8 @@ void HPackTable::MementoRingBuffer::Rebuild(uint32_t max_entries) {
82
82
  // Evict one element from the table
83
83
  void HPackTable::EvictOne() {
84
84
  auto first_entry = entries_.PopOne();
85
- GPR_ASSERT(first_entry.transport_size() <= mem_used_);
86
- mem_used_ -= first_entry.transport_size();
85
+ GPR_ASSERT(first_entry.md.transport_size() <= mem_used_);
86
+ mem_used_ -= first_entry.md.transport_size();
87
87
  }
88
88
 
89
89
  void HPackTable::SetMaxBytes(uint32_t max_bytes) {
@@ -104,7 +104,7 @@ grpc_error_handle HPackTable::SetCurrentTableSize(uint32_t bytes) {
104
104
  return absl::OkStatus();
105
105
  }
106
106
  if (bytes > max_bytes_) {
107
- return GRPC_ERROR_CREATE(absl::StrFormat(
107
+ return absl::InternalError(absl::StrFormat(
108
108
  "Attempt to make hpack table %d bytes when max is %d bytes", bytes,
109
109
  max_bytes_));
110
110
  }
@@ -130,7 +130,7 @@ grpc_error_handle HPackTable::Add(Memento md) {
130
130
  }
131
131
 
132
132
  // we can't add elements bigger than the max table size
133
- if (md.transport_size() > current_table_bytes_) {
133
+ if (md.md.transport_size() > current_table_bytes_) {
134
134
  // HPACK draft 10 section 4.4 states:
135
135
  // If the size of the new entry is less than or equal to the maximum
136
136
  // size, that entry is added to the table. It is not an error to
@@ -145,13 +145,13 @@ grpc_error_handle HPackTable::Add(Memento md) {
145
145
  }
146
146
 
147
147
  // evict entries to ensure no overflow
148
- while (md.transport_size() >
148
+ while (md.md.transport_size() >
149
149
  static_cast<size_t>(current_table_bytes_) - mem_used_) {
150
150
  EvictOne();
151
151
  }
152
152
 
153
153
  // copy the finalized entry in
154
- mem_used_ += md.transport_size();
154
+ mem_used_ += md.md.transport_size();
155
155
  entries_.Put(std::move(md));
156
156
  return absl::OkStatus();
157
157
  }
@@ -228,12 +228,14 @@ const StaticTableEntry kStaticTable[hpack_constants::kLastStaticEntry] = {
228
228
 
229
229
  HPackTable::Memento MakeMemento(size_t i) {
230
230
  auto sm = kStaticTable[i];
231
- return grpc_metadata_batch::Parse(
232
- sm.key, Slice::FromStaticString(sm.value),
233
- strlen(sm.key) + strlen(sm.value) + hpack_constants::kEntryOverhead,
234
- [](absl::string_view, const Slice&) {
235
- abort(); // not expecting to see this
236
- });
231
+ return HPackTable::Memento{
232
+ grpc_metadata_batch::Parse(
233
+ sm.key, Slice::FromStaticString(sm.value),
234
+ strlen(sm.key) + strlen(sm.value) + hpack_constants::kEntryOverhead,
235
+ [](absl::string_view, const Slice&) {
236
+ abort(); // not expecting to see this
237
+ }),
238
+ absl::OkStatus()};
237
239
  }
238
240
 
239
241
  } // namespace
@@ -25,6 +25,8 @@
25
25
 
26
26
  #include <vector>
27
27
 
28
+ #include "absl/status/status.h"
29
+
28
30
  #include "src/core/ext/transport/chttp2/transport/hpack_constants.h"
29
31
  #include "src/core/lib/gprpp/no_destruct.h"
30
32
  #include "src/core/lib/iomgr/error.h"
@@ -45,7 +47,10 @@ class HPackTable {
45
47
  void SetMaxBytes(uint32_t max_bytes);
46
48
  grpc_error_handle SetCurrentTableSize(uint32_t bytes);
47
49
 
48
- using Memento = ParsedMetadata<grpc_metadata_batch>;
50
+ struct Memento {
51
+ ParsedMetadata<grpc_metadata_batch> md;
52
+ absl::Status parse_status;
53
+ };
49
54
 
50
55
  // Lookup, but don't ref.
51
56
  const Memento* Lookup(uint32_t index) const {
@@ -68,6 +73,9 @@ class HPackTable {
68
73
  // Current entry count in the table.
69
74
  uint32_t num_entries() const { return entries_.num_entries(); }
70
75
 
76
+ // Current size of the table.
77
+ uint32_t test_only_table_size() const { return mem_used_; }
78
+
71
79
  private:
72
80
  struct StaticMementos {
73
81
  StaticMementos();
@@ -457,6 +457,8 @@ struct grpc_chttp2_transport
457
457
  bool keepalive_ping_started = false;
458
458
  /// keep-alive state machine state
459
459
  grpc_chttp2_keepalive_state keepalive_state;
460
+ // Soft limit on max header size.
461
+ uint32_t max_header_list_size_soft_limit = 0;
460
462
  grpc_core::ContextList* cl = nullptr;
461
463
  grpc_core::RefCountedPtr<grpc_core::channelz::SocketNode> channelz_socket;
462
464
  uint32_t num_messages_in_next_write = 0;
@@ -475,6 +475,9 @@ static grpc_error_handle init_header_skip_frame_parser(
475
475
  "header", grpc_chttp2_header_parser_parse, &t->hpack_parser};
476
476
  t->hpack_parser.BeginFrame(
477
477
  nullptr,
478
+ /*metadata_size_soft_limit=*/
479
+ t->max_header_list_size_soft_limit,
480
+ /*metadata_size_hard_limit=*/
478
481
  t->settings[GRPC_ACKED_SETTINGS]
479
482
  [GRPC_CHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE],
480
483
  hpack_boundary_type(t, is_eoh), priority_type,
@@ -691,6 +694,9 @@ static grpc_error_handle init_header_frame_parser(grpc_chttp2_transport* t,
691
694
  }
692
695
  t->hpack_parser.BeginFrame(
693
696
  incoming_metadata_buffer,
697
+ /*metadata_size_soft_limit=*/
698
+ t->max_header_list_size_soft_limit,
699
+ /*metadata_size_hard_limit=*/
694
700
  t->settings[GRPC_ACKED_SETTINGS]
695
701
  [GRPC_CHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE],
696
702
  hpack_boundary_type(t, is_eoh), priority_type,
@@ -0,0 +1,31 @@
1
+ // Copyright 2023 gRPC authors.
2
+ //
3
+ // Licensed under the Apache License, Version 2.0 (the "License");
4
+ // you may not use this file except in compliance with the License.
5
+ // You may obtain a copy of the License at
6
+ //
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ //
9
+ // Unless required by applicable law or agreed to in writing, software
10
+ // distributed under the License is distributed on an "AS IS" BASIS,
11
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ // See the License for the specific language governing permissions and
13
+ // limitations under the License.
14
+
15
+ #include <grpc/support/port_platform.h>
16
+
17
+ #include "src/core/lib/backoff/random_early_detection.h"
18
+
19
+ namespace grpc_core {
20
+
21
+ bool RandomEarlyDetection::Reject(uint64_t size) {
22
+ if (size <= soft_limit_) return false;
23
+ if (size < hard_limit_) {
24
+ return absl::Bernoulli(bitgen_,
25
+ static_cast<double>(size - soft_limit_) /
26
+ static_cast<double>(hard_limit_ - soft_limit_));
27
+ }
28
+ return true;
29
+ }
30
+
31
+ } // namespace grpc_core
@@ -0,0 +1,59 @@
1
+ // Copyright 2023 gRPC authors.
2
+ //
3
+ // Licensed under the Apache License, Version 2.0 (the "License");
4
+ // you may not use this file except in compliance with the License.
5
+ // You may obtain a copy of the License at
6
+ //
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ //
9
+ // Unless required by applicable law or agreed to in writing, software
10
+ // distributed under the License is distributed on an "AS IS" BASIS,
11
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ // See the License for the specific language governing permissions and
13
+ // limitations under the License.
14
+
15
+ #ifndef GRPC_SRC_CORE_LIB_BACKOFF_RANDOM_EARLY_DETECTION_H
16
+ #define GRPC_SRC_CORE_LIB_BACKOFF_RANDOM_EARLY_DETECTION_H
17
+
18
+ #include <grpc/support/port_platform.h>
19
+
20
+ #include <limits.h>
21
+
22
+ #include <cstdint>
23
+
24
+ #include "absl/random/random.h"
25
+
26
+ namespace grpc_core {
27
+
28
+ // Implements the random early detection algorithm - allows items to be rejected
29
+ // or accepted based upon their size.
30
+ class RandomEarlyDetection {
31
+ public:
32
+ RandomEarlyDetection() : soft_limit_(INT_MAX), hard_limit_(INT_MAX) {}
33
+ RandomEarlyDetection(uint64_t soft_limit, uint64_t hard_limit)
34
+ : soft_limit_(soft_limit), hard_limit_(hard_limit) {}
35
+
36
+ // Returns true if the size is greater than or equal to the hard limit - ie if
37
+ // this item must be rejected.
38
+ bool MustReject(uint64_t size) { return size >= hard_limit_; }
39
+
40
+ // Returns true if the item should be rejected.
41
+ bool Reject(uint64_t size);
42
+
43
+ uint64_t soft_limit() const { return soft_limit_; }
44
+ uint64_t hard_limit() const { return hard_limit_; }
45
+
46
+ private:
47
+ // The soft limit is the size at which we start rejecting items with a
48
+ // probability that increases linearly to 1 as the size approaches the hard
49
+ // limit.
50
+ uint64_t soft_limit_;
51
+ // The hard limit is the size at which we reject all items.
52
+ uint64_t hard_limit_;
53
+ // The bit generator used to generate random numbers.
54
+ absl::InsecureBitGen bitgen_;
55
+ };
56
+
57
+ } // namespace grpc_core
58
+
59
+ #endif // GRPC_SRC_CORE_LIB_BACKOFF_RANDOM_EARLY_DETECTION_H
@@ -28,7 +28,7 @@ struct grpc_endpoint_pair {
28
28
  grpc_endpoint* server;
29
29
  };
30
30
 
31
- grpc_endpoint_pair grpc_iomgr_create_endpoint_pair(const char* name,
32
- grpc_channel_args* args);
31
+ grpc_endpoint_pair grpc_iomgr_create_endpoint_pair(
32
+ const char* name, const grpc_channel_args* args);
33
33
 
34
34
  #endif // GRPC_SRC_CORE_LIB_IOMGR_ENDPOINT_PAIR_H
@@ -55,8 +55,8 @@ static void create_sockets(int sv[2]) {
55
55
  GPR_ASSERT(grpc_set_socket_no_sigpipe_if_possible(sv[1]) == absl::OkStatus());
56
56
  }
57
57
 
58
- grpc_endpoint_pair grpc_iomgr_create_endpoint_pair(const char* name,
59
- grpc_channel_args* args) {
58
+ grpc_endpoint_pair grpc_iomgr_create_endpoint_pair(
59
+ const char* name, const grpc_channel_args* args) {
60
60
  int sv[2];
61
61
  grpc_endpoint_pair p;
62
62
  create_sockets(sv);
@@ -80,7 +80,7 @@ static void create_sockets(SOCKET sv[2]) {
80
80
  }
81
81
 
82
82
  grpc_endpoint_pair grpc_iomgr_create_endpoint_pair(
83
- const char*, grpc_channel_args* channel_args) {
83
+ const char*, const grpc_channel_args* /* channel_args */) {
84
84
  SOCKET sv[2];
85
85
  grpc_endpoint_pair p;
86
86
  create_sockets(sv);
@@ -21,46 +21,20 @@
21
21
  #include "src/core/lib/surface/validate_metadata.h"
22
22
 
23
23
  #include "absl/status/status.h"
24
+ #include "absl/strings/escaping.h"
25
+ #include "absl/strings/str_cat.h"
24
26
  #include "absl/strings/string_view.h"
25
27
 
26
28
  #include <grpc/grpc.h>
27
29
 
28
- #include "src/core/lib/gpr/string.h"
29
30
  #include "src/core/lib/gprpp/bitset.h"
30
- #include "src/core/lib/gprpp/memory.h"
31
- #include "src/core/lib/gprpp/status_helper.h"
32
31
  #include "src/core/lib/iomgr/error.h"
32
+ #include "src/core/lib/slice/slice_internal.h"
33
33
 
34
- static grpc_error_handle conforms_to(const grpc_slice& slice,
35
- const grpc_core::BitSet<256>& legal_bits,
36
- const char* err_desc) {
37
- const uint8_t* p = GRPC_SLICE_START_PTR(slice);
38
- const uint8_t* e = GRPC_SLICE_END_PTR(slice);
39
- for (; p != e; p++) {
40
- if (!legal_bits.is_set(*p)) {
41
- size_t len;
42
- grpc_core::UniquePtr<char> ptr(gpr_dump_return_len(
43
- reinterpret_cast<const char*> GRPC_SLICE_START_PTR(slice),
44
- GRPC_SLICE_LENGTH(slice), GPR_DUMP_HEX | GPR_DUMP_ASCII, &len));
45
- grpc_error_handle error = grpc_error_set_str(
46
- grpc_error_set_int(GRPC_ERROR_CREATE(err_desc),
47
- grpc_core::StatusIntProperty::kOffset,
48
- p - GRPC_SLICE_START_PTR(slice)),
49
- grpc_core::StatusStrProperty::kRawBytes,
50
- absl::string_view(ptr.get(), len));
51
- return error;
52
- }
53
- }
54
- return absl::OkStatus();
55
- }
56
-
57
- static int error2int(grpc_error_handle error) {
58
- int r = (error.ok());
59
- return r;
60
- }
34
+ namespace grpc_core {
61
35
 
62
36
  namespace {
63
- class LegalHeaderKeyBits : public grpc_core::BitSet<256> {
37
+ class LegalHeaderKeyBits : public BitSet<256> {
64
38
  public:
65
39
  constexpr LegalHeaderKeyBits() {
66
40
  for (int i = 'a'; i <= 'z'; i++) set(i);
@@ -71,19 +45,45 @@ class LegalHeaderKeyBits : public grpc_core::BitSet<256> {
71
45
  }
72
46
  };
73
47
  constexpr LegalHeaderKeyBits g_legal_header_key_bits;
74
- } // namespace
75
48
 
76
- grpc_error_handle grpc_validate_header_key_is_legal(const grpc_slice& slice) {
77
- if (GRPC_SLICE_LENGTH(slice) == 0) {
78
- return GRPC_ERROR_CREATE("Metadata keys cannot be zero length");
49
+ GPR_ATTRIBUTE_NOINLINE
50
+ absl::Status DoesNotConformTo(absl::string_view x, const char* err_desc) {
51
+ return absl::InternalError(absl::StrCat(err_desc, ": ", x, " (hex ",
52
+ absl::BytesToHexString(x), ")"));
53
+ }
54
+
55
+ absl::Status ConformsTo(absl::string_view x, const BitSet<256>& legal_bits,
56
+ const char* err_desc) {
57
+ for (uint8_t c : x) {
58
+ if (!legal_bits.is_set(c)) {
59
+ return DoesNotConformTo(x, err_desc);
60
+ }
79
61
  }
80
- if (GRPC_SLICE_LENGTH(slice) > UINT32_MAX) {
81
- return GRPC_ERROR_CREATE("Metadata keys cannot be larger than UINT32_MAX");
62
+ return absl::OkStatus();
63
+ }
64
+ } // namespace
65
+
66
+ absl::Status ValidateHeaderKeyIsLegal(absl::string_view key) {
67
+ if (key.empty()) {
68
+ return absl::InternalError("Metadata keys cannot be zero length");
82
69
  }
83
- if (GRPC_SLICE_START_PTR(slice)[0] == ':') {
84
- return GRPC_ERROR_CREATE("Metadata keys cannot start with :");
70
+ if (key.size() > UINT32_MAX) {
71
+ return absl::InternalError(
72
+ "Metadata keys cannot be larger than UINT32_MAX");
85
73
  }
86
- return conforms_to(slice, g_legal_header_key_bits, "Illegal header key");
74
+ return ConformsTo(key, g_legal_header_key_bits, "Illegal header key");
75
+ }
76
+
77
+ } // namespace grpc_core
78
+
79
+ static int error2int(grpc_error_handle error) {
80
+ int r = (error.ok());
81
+ return r;
82
+ }
83
+
84
+ grpc_error_handle grpc_validate_header_key_is_legal(const grpc_slice& slice) {
85
+ return grpc_core::ValidateHeaderKeyIsLegal(
86
+ grpc_core::StringViewFromSlice(slice));
87
87
  }
88
88
 
89
89
  int grpc_header_key_is_legal(grpc_slice slice) {
@@ -104,8 +104,9 @@ constexpr LegalHeaderNonBinValueBits g_legal_header_non_bin_value_bits;
104
104
 
105
105
  grpc_error_handle grpc_validate_header_nonbin_value_is_legal(
106
106
  const grpc_slice& slice) {
107
- return conforms_to(slice, g_legal_header_non_bin_value_bits,
108
- "Illegal header value");
107
+ return grpc_core::ConformsTo(grpc_core::StringViewFromSlice(slice),
108
+ g_legal_header_non_bin_value_bits,
109
+ "Illegal header value");
109
110
  }
110
111
 
111
112
  int grpc_header_nonbin_value_is_legal(grpc_slice slice) {
@@ -25,11 +25,20 @@
25
25
 
26
26
  #include <cstring>
27
27
 
28
+ #include "absl/status/status.h"
29
+ #include "absl/strings/string_view.h"
30
+
28
31
  #include <grpc/slice.h>
29
32
  #include <grpc/support/log.h>
30
33
 
31
34
  #include "src/core/lib/iomgr/error.h"
32
35
 
36
+ namespace grpc_core {
37
+
38
+ absl::Status ValidateHeaderKeyIsLegal(absl::string_view key);
39
+
40
+ }
41
+
33
42
  grpc_error_handle grpc_validate_header_key_is_legal(const grpc_slice& slice);
34
43
  grpc_error_handle grpc_validate_header_nonbin_value_is_legal(
35
44
  const grpc_slice& slice);
@@ -93,7 +93,7 @@ StaticSlice ContentTypeMetadata::Encode(ValueType x) {
93
93
  return StaticSlice::FromStaticString("unrepresentable value"));
94
94
  }
95
95
 
96
- const char* ContentTypeMetadata::DisplayValue(MementoType content_type) {
96
+ const char* ContentTypeMetadata::DisplayValue(ValueType content_type) {
97
97
  switch (content_type) {
98
98
  case ValueType::kApplicationGrpc:
99
99
  return "application/grpc";
@@ -137,7 +137,7 @@ TeMetadata::MementoType TeMetadata::ParseMemento(
137
137
  return out;
138
138
  }
139
139
 
140
- const char* TeMetadata::DisplayValue(MementoType te) {
140
+ const char* TeMetadata::DisplayValue(ValueType te) {
141
141
  switch (te) {
142
142
  case ValueType::kTrailers:
143
143
  return "trailers";
@@ -222,7 +222,7 @@ StaticSlice HttpMethodMetadata::Encode(ValueType x) {
222
222
  }
223
223
  }
224
224
 
225
- const char* HttpMethodMetadata::DisplayValue(MementoType content_type) {
225
+ const char* HttpMethodMetadata::DisplayValue(ValueType content_type) {
226
226
  switch (content_type) {
227
227
  case kPost:
228
228
  return "POST";
@@ -264,7 +264,7 @@ Slice LbCostBinMetadata::Encode(const ValueType& x) {
264
264
  return Slice(std::move(slice));
265
265
  }
266
266
 
267
- std::string LbCostBinMetadata::DisplayValue(MementoType x) {
267
+ std::string LbCostBinMetadata::DisplayValue(ValueType x) {
268
268
  return absl::StrCat(x.name, ":", x.cost);
269
269
  }
270
270