OLD | NEW |
1 // Copyright 2014 The Crashpad Authors. All rights reserved. | 1 // Copyright 2014 The Crashpad Authors. All rights reserved. |
2 // | 2 // |
3 // Licensed under the Apache License, Version 2.0 (the "License"); | 3 // Licensed under the Apache License, Version 2.0 (the "License"); |
4 // you may not use this file except in compliance with 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 | 5 // You may obtain a copy of the License at |
6 // | 6 // |
7 // http://www.apache.org/licenses/LICENSE-2.0 | 7 // http://www.apache.org/licenses/LICENSE-2.0 |
8 // | 8 // |
9 // Unless required by applicable law or agreed to in writing, software | 9 // Unless required by applicable law or agreed to in writing, software |
10 // distributed under the License is distributed on an "AS IS" BASIS, | 10 // distributed under the License is distributed on an "AS IS" BASIS, |
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
12 // See the License for the specific language governing permissions and | 12 // See the License for the specific language governing permissions and |
13 // limitations under the License. | 13 // limitations under the License. |
14 | 14 |
15 #include "minidump/minidump_memory_writer.h" | 15 #include "minidump/minidump_memory_writer.h" |
16 | 16 |
17 #include <dbghelp.h> | 17 #include <dbghelp.h> |
18 #include <stdint.h> | 18 #include <stdint.h> |
19 | 19 |
20 #include "base/basictypes.h" | 20 #include "base/basictypes.h" |
21 #include "gtest/gtest.h" | 21 #include "gtest/gtest.h" |
22 #include "minidump/minidump_extensions.h" | 22 #include "minidump/minidump_extensions.h" |
23 #include "minidump/minidump_file_writer.h" | 23 #include "minidump/minidump_file_writer.h" |
| 24 #include "minidump/minidump_memory_writer_test_util.h" |
24 #include "minidump/minidump_stream_writer.h" | 25 #include "minidump/minidump_stream_writer.h" |
25 #include "minidump/minidump_test_util.h" | 26 #include "minidump/minidump_test_util.h" |
26 #include "util/file/string_file_writer.h" | 27 #include "util/file/string_file_writer.h" |
27 | 28 |
28 namespace { | 29 namespace { |
29 | 30 |
30 using namespace crashpad; | 31 using namespace crashpad; |
31 using namespace crashpad::test; | 32 using namespace crashpad::test; |
32 | 33 |
33 const MinidumpStreamType kBogusStreamType = | 34 const MinidumpStreamType kBogusStreamType = |
(...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
94 | 95 |
95 const MINIDUMP_MEMORY_LIST* memory_list; | 96 const MINIDUMP_MEMORY_LIST* memory_list; |
96 GetMemoryListStream(file_writer.string(), &memory_list, 1); | 97 GetMemoryListStream(file_writer.string(), &memory_list, 1); |
97 if (Test::HasFatalFailure()) { | 98 if (Test::HasFatalFailure()) { |
98 return; | 99 return; |
99 } | 100 } |
100 | 101 |
101 EXPECT_EQ(0u, memory_list->NumberOfMemoryRanges); | 102 EXPECT_EQ(0u, memory_list->NumberOfMemoryRanges); |
102 } | 103 } |
103 | 104 |
104 class TestMemoryWriter final : public MinidumpMemoryWriter { | |
105 public: | |
106 TestMemoryWriter(uint64_t base_address, size_t size, uint8_t value) | |
107 : MinidumpMemoryWriter(), | |
108 base_address_(base_address), | |
109 expected_offset_(-1), | |
110 size_(size), | |
111 value_(value) {} | |
112 | |
113 ~TestMemoryWriter() {} | |
114 | |
115 protected: | |
116 // MinidumpMemoryWriter: | |
117 virtual uint64_t MemoryRangeBaseAddress() const override { | |
118 EXPECT_EQ(state(), kStateFrozen); | |
119 return base_address_; | |
120 } | |
121 | |
122 virtual size_t MemoryRangeSize() const override { | |
123 EXPECT_GE(state(), kStateFrozen); | |
124 return size_; | |
125 } | |
126 | |
127 // MinidumpWritable: | |
128 virtual bool WillWriteAtOffsetImpl(off_t offset) override { | |
129 EXPECT_EQ(state(), kStateFrozen); | |
130 expected_offset_ = offset; | |
131 bool rv = MinidumpMemoryWriter::WillWriteAtOffsetImpl(offset); | |
132 EXPECT_TRUE(rv); | |
133 return rv; | |
134 } | |
135 | |
136 virtual bool WriteObject(FileWriterInterface* file_writer) override { | |
137 EXPECT_EQ(state(), kStateWritable); | |
138 EXPECT_EQ(expected_offset_, file_writer->Seek(0, SEEK_CUR)); | |
139 | |
140 bool rv = true; | |
141 if (size_ > 0) { | |
142 std::string data(size_, value_); | |
143 rv = file_writer->Write(&data[0], size_); | |
144 EXPECT_TRUE(rv); | |
145 } | |
146 | |
147 return rv; | |
148 } | |
149 | |
150 private: | |
151 uint64_t base_address_; | |
152 off_t expected_offset_; | |
153 size_t size_; | |
154 uint8_t value_; | |
155 | |
156 DISALLOW_COPY_AND_ASSIGN(TestMemoryWriter); | |
157 }; | |
158 | |
159 void ExpectMemoryDescriptorAndContents( | |
160 const MINIDUMP_MEMORY_DESCRIPTOR* expected, | |
161 const MINIDUMP_MEMORY_DESCRIPTOR* observed, | |
162 const std::string& file_contents, | |
163 uint8_t value, | |
164 bool at_eof) { | |
165 const uint32_t kMemoryAlignment = 16; | |
166 | |
167 EXPECT_EQ(expected->StartOfMemoryRange, observed->StartOfMemoryRange); | |
168 EXPECT_EQ(expected->Memory.DataSize, observed->Memory.DataSize); | |
169 EXPECT_EQ( | |
170 (expected->Memory.Rva + kMemoryAlignment - 1) & ~(kMemoryAlignment - 1), | |
171 observed->Memory.Rva); | |
172 if (at_eof) { | |
173 EXPECT_EQ(file_contents.size(), | |
174 observed->Memory.Rva + observed->Memory.DataSize); | |
175 } else { | |
176 EXPECT_GE(file_contents.size(), | |
177 observed->Memory.Rva + observed->Memory.DataSize); | |
178 } | |
179 | |
180 std::string expected_data(expected->Memory.DataSize, value); | |
181 std::string observed_data(&file_contents[observed->Memory.Rva], | |
182 observed->Memory.DataSize); | |
183 EXPECT_EQ(expected_data, observed_data); | |
184 } | |
185 | |
186 TEST(MinidumpMemoryWriter, OneMemoryRegion) { | 105 TEST(MinidumpMemoryWriter, OneMemoryRegion) { |
187 MinidumpFileWriter minidump_file_writer; | 106 MinidumpFileWriter minidump_file_writer; |
188 MinidumpMemoryListWriter memory_list_writer; | 107 MinidumpMemoryListWriter memory_list_writer; |
189 | 108 |
190 const uint64_t kBaseAddress = 0xfedcba9876543210; | 109 const uint64_t kBaseAddress = 0xfedcba9876543210; |
191 const uint64_t kSize = 0x1000; | 110 const uint64_t kSize = 0x1000; |
192 const uint8_t kValue = 'm'; | 111 const uint8_t kValue = 'm'; |
193 | 112 |
194 TestMemoryWriter memory_writer(kBaseAddress, kSize, kValue); | 113 TestMinidumpMemoryWriter memory_writer(kBaseAddress, kSize, kValue); |
195 memory_list_writer.AddMemory(&memory_writer); | 114 memory_list_writer.AddMemory(&memory_writer); |
196 | 115 |
197 minidump_file_writer.AddStream(&memory_list_writer); | 116 minidump_file_writer.AddStream(&memory_list_writer); |
198 | 117 |
199 StringFileWriter file_writer; | 118 StringFileWriter file_writer; |
200 ASSERT_TRUE(minidump_file_writer.WriteEverything(&file_writer)); | 119 ASSERT_TRUE(minidump_file_writer.WriteEverything(&file_writer)); |
201 | 120 |
202 const MINIDUMP_MEMORY_LIST* memory_list; | 121 const MINIDUMP_MEMORY_LIST* memory_list; |
203 GetMemoryListStream(file_writer.string(), &memory_list, 1); | 122 GetMemoryListStream(file_writer.string(), &memory_list, 1); |
204 if (Test::HasFatalFailure()) { | 123 if (Test::HasFatalFailure()) { |
205 return; | 124 return; |
206 } | 125 } |
207 | 126 |
208 MINIDUMP_MEMORY_DESCRIPTOR expected; | 127 MINIDUMP_MEMORY_DESCRIPTOR expected; |
209 expected.StartOfMemoryRange = kBaseAddress; | 128 expected.StartOfMemoryRange = kBaseAddress; |
210 expected.Memory.DataSize = kSize; | 129 expected.Memory.DataSize = kSize; |
211 expected.Memory.Rva = | 130 expected.Memory.Rva = |
212 sizeof(MINIDUMP_HEADER) + sizeof(MINIDUMP_DIRECTORY) + | 131 sizeof(MINIDUMP_HEADER) + sizeof(MINIDUMP_DIRECTORY) + |
213 sizeof(MINIDUMP_MEMORY_LIST) + | 132 sizeof(MINIDUMP_MEMORY_LIST) + |
214 memory_list->NumberOfMemoryRanges * sizeof(MINIDUMP_MEMORY_DESCRIPTOR); | 133 memory_list->NumberOfMemoryRanges * sizeof(MINIDUMP_MEMORY_DESCRIPTOR); |
215 ExpectMemoryDescriptorAndContents(&expected, | 134 ExpectMinidumpMemoryDescriptorAndContents(&expected, |
216 &memory_list->MemoryRanges[0], | 135 &memory_list->MemoryRanges[0], |
217 file_writer.string(), | 136 file_writer.string(), |
218 kValue, | 137 kValue, |
219 true); | 138 true); |
220 } | 139 } |
221 | 140 |
222 TEST(MinidumpMemoryWriter, TwoMemoryRegions) { | 141 TEST(MinidumpMemoryWriter, TwoMemoryRegions) { |
223 MinidumpFileWriter minidump_file_writer; | 142 MinidumpFileWriter minidump_file_writer; |
224 MinidumpMemoryListWriter memory_list_writer; | 143 MinidumpMemoryListWriter memory_list_writer; |
225 | 144 |
226 const uint64_t kBaseAddress1 = 0xc0ffee; | 145 const uint64_t kBaseAddress1 = 0xc0ffee; |
227 const uint64_t kSize1 = 0x0100; | 146 const uint64_t kSize1 = 0x0100; |
228 const uint8_t kValue1 = '6'; | 147 const uint8_t kValue1 = '6'; |
229 const uint64_t kBaseAddress2 = 0xfac00fac; | 148 const uint64_t kBaseAddress2 = 0xfac00fac; |
230 const uint64_t kSize2 = 0x0200; | 149 const uint64_t kSize2 = 0x0200; |
231 const uint8_t kValue2 = '!'; | 150 const uint8_t kValue2 = '!'; |
232 | 151 |
233 TestMemoryWriter memory_writer_1(kBaseAddress1, kSize1, kValue1); | 152 TestMinidumpMemoryWriter memory_writer_1(kBaseAddress1, kSize1, kValue1); |
234 memory_list_writer.AddMemory(&memory_writer_1); | 153 memory_list_writer.AddMemory(&memory_writer_1); |
235 TestMemoryWriter memory_writer_2(kBaseAddress2, kSize2, kValue2); | 154 TestMinidumpMemoryWriter memory_writer_2(kBaseAddress2, kSize2, kValue2); |
236 memory_list_writer.AddMemory(&memory_writer_2); | 155 memory_list_writer.AddMemory(&memory_writer_2); |
237 | 156 |
238 minidump_file_writer.AddStream(&memory_list_writer); | 157 minidump_file_writer.AddStream(&memory_list_writer); |
239 | 158 |
240 StringFileWriter file_writer; | 159 StringFileWriter file_writer; |
241 ASSERT_TRUE(minidump_file_writer.WriteEverything(&file_writer)); | 160 ASSERT_TRUE(minidump_file_writer.WriteEverything(&file_writer)); |
242 | 161 |
243 const MINIDUMP_MEMORY_LIST* memory_list; | 162 const MINIDUMP_MEMORY_LIST* memory_list; |
244 GetMemoryListStream(file_writer.string(), &memory_list, 1); | 163 GetMemoryListStream(file_writer.string(), &memory_list, 1); |
245 if (Test::HasFatalFailure()) { | 164 if (Test::HasFatalFailure()) { |
246 return; | 165 return; |
247 } | 166 } |
248 | 167 |
249 EXPECT_EQ(2u, memory_list->NumberOfMemoryRanges); | 168 EXPECT_EQ(2u, memory_list->NumberOfMemoryRanges); |
250 | 169 |
251 MINIDUMP_MEMORY_DESCRIPTOR expected; | 170 MINIDUMP_MEMORY_DESCRIPTOR expected; |
252 | 171 |
253 { | 172 { |
254 SCOPED_TRACE("region 0"); | 173 SCOPED_TRACE("region 0"); |
255 | 174 |
256 expected.StartOfMemoryRange = kBaseAddress1; | 175 expected.StartOfMemoryRange = kBaseAddress1; |
257 expected.Memory.DataSize = kSize1; | 176 expected.Memory.DataSize = kSize1; |
258 expected.Memory.Rva = | 177 expected.Memory.Rva = |
259 sizeof(MINIDUMP_HEADER) + sizeof(MINIDUMP_DIRECTORY) + | 178 sizeof(MINIDUMP_HEADER) + sizeof(MINIDUMP_DIRECTORY) + |
260 sizeof(MINIDUMP_MEMORY_LIST) + | 179 sizeof(MINIDUMP_MEMORY_LIST) + |
261 memory_list->NumberOfMemoryRanges * sizeof(MINIDUMP_MEMORY_DESCRIPTOR); | 180 memory_list->NumberOfMemoryRanges * sizeof(MINIDUMP_MEMORY_DESCRIPTOR); |
262 ExpectMemoryDescriptorAndContents(&expected, | 181 ExpectMinidumpMemoryDescriptorAndContents(&expected, |
263 &memory_list->MemoryRanges[0], | 182 &memory_list->MemoryRanges[0], |
264 file_writer.string(), | 183 file_writer.string(), |
265 kValue1, | 184 kValue1, |
266 false); | 185 false); |
267 } | 186 } |
268 | 187 |
269 { | 188 { |
270 SCOPED_TRACE("region 1"); | 189 SCOPED_TRACE("region 1"); |
271 | 190 |
272 expected.StartOfMemoryRange = kBaseAddress2; | 191 expected.StartOfMemoryRange = kBaseAddress2; |
273 expected.Memory.DataSize = kSize2; | 192 expected.Memory.DataSize = kSize2; |
274 expected.Memory.Rva = memory_list->MemoryRanges[0].Memory.Rva + | 193 expected.Memory.Rva = memory_list->MemoryRanges[0].Memory.Rva + |
275 memory_list->MemoryRanges[0].Memory.DataSize; | 194 memory_list->MemoryRanges[0].Memory.DataSize; |
276 ExpectMemoryDescriptorAndContents(&expected, | 195 ExpectMinidumpMemoryDescriptorAndContents(&expected, |
277 &memory_list->MemoryRanges[1], | 196 &memory_list->MemoryRanges[1], |
278 file_writer.string(), | 197 file_writer.string(), |
279 kValue2, | 198 kValue2, |
280 true); | 199 true); |
281 } | 200 } |
282 } | 201 } |
283 | 202 |
284 class TestMemoryStream final : public internal::MinidumpStreamWriter { | 203 class TestMemoryStream final : public internal::MinidumpStreamWriter { |
285 public: | 204 public: |
286 TestMemoryStream(uint64_t base_address, size_t size, uint8_t value) | 205 TestMemoryStream(uint64_t base_address, size_t size, uint8_t value) |
287 : MinidumpStreamWriter(), memory_(base_address, size, value) {} | 206 : MinidumpStreamWriter(), memory_(base_address, size, value) {} |
288 | 207 |
289 ~TestMemoryStream() {} | 208 ~TestMemoryStream() {} |
290 | 209 |
291 TestMemoryWriter* memory() { return &memory_; } | 210 TestMinidumpMemoryWriter* memory() { return &memory_; } |
292 | 211 |
293 // MinidumpStreamWriter: | 212 // MinidumpStreamWriter: |
294 virtual MinidumpStreamType StreamType() const override { | 213 virtual MinidumpStreamType StreamType() const override { |
295 return kBogusStreamType; | 214 return kBogusStreamType; |
296 } | 215 } |
297 | 216 |
298 protected: | 217 protected: |
299 // MinidumpWritable: | 218 // MinidumpWritable: |
300 virtual size_t SizeOfObject() override { | 219 virtual size_t SizeOfObject() override { |
301 EXPECT_GE(state(), kStateFrozen); | 220 EXPECT_GE(state(), kStateFrozen); |
302 return 0; | 221 return 0; |
303 } | 222 } |
304 | 223 |
305 virtual std::vector<MinidumpWritable*> Children() override { | 224 virtual std::vector<MinidumpWritable*> Children() override { |
306 EXPECT_GE(state(), kStateFrozen); | 225 EXPECT_GE(state(), kStateFrozen); |
307 std::vector<MinidumpWritable*> children(1, memory()); | 226 std::vector<MinidumpWritable*> children(1, memory()); |
308 return children; | 227 return children; |
309 } | 228 } |
310 | 229 |
311 virtual bool WriteObject(FileWriterInterface* file_writer) override { | 230 virtual bool WriteObject(FileWriterInterface* file_writer) override { |
312 EXPECT_EQ(kStateWritable, state()); | 231 EXPECT_EQ(kStateWritable, state()); |
313 return true; | 232 return true; |
314 } | 233 } |
315 | 234 |
316 private: | 235 private: |
317 TestMemoryWriter memory_; | 236 TestMinidumpMemoryWriter memory_; |
318 | 237 |
319 DISALLOW_COPY_AND_ASSIGN(TestMemoryStream); | 238 DISALLOW_COPY_AND_ASSIGN(TestMemoryStream); |
320 }; | 239 }; |
321 | 240 |
322 TEST(MinidumpMemoryWriter, ExtraMemory) { | 241 TEST(MinidumpMemoryWriter, ExtraMemory) { |
323 // This tests MinidumpMemoryListWriter::AddExtraMemory(). That method adds | 242 // This tests MinidumpMemoryListWriter::AddExtraMemory(). That method adds |
324 // a MinidumpMemoryWriter to the MinidumpMemoryListWriter without making the | 243 // a MinidumpMemoryWriter to the MinidumpMemoryListWriter without making the |
325 // memory writer a child of the memory list writer. | 244 // memory writer a child of the memory list writer. |
326 MinidumpFileWriter minidump_file_writer; | 245 MinidumpFileWriter minidump_file_writer; |
327 | 246 |
328 const uint64_t kBaseAddress1 = 0x1000; | 247 const uint64_t kBaseAddress1 = 0x1000; |
329 const uint64_t kSize1 = 0x0400; | 248 const uint64_t kSize1 = 0x0400; |
330 const uint8_t kValue1 = '1'; | 249 const uint8_t kValue1 = '1'; |
331 TestMemoryStream test_memory_stream(kBaseAddress1, kSize1, kValue1); | 250 TestMemoryStream test_memory_stream(kBaseAddress1, kSize1, kValue1); |
332 | 251 |
333 MinidumpMemoryListWriter memory_list_writer; | 252 MinidumpMemoryListWriter memory_list_writer; |
334 memory_list_writer.AddExtraMemory(test_memory_stream.memory()); | 253 memory_list_writer.AddExtraMemory(test_memory_stream.memory()); |
335 | 254 |
336 minidump_file_writer.AddStream(&test_memory_stream); | 255 minidump_file_writer.AddStream(&test_memory_stream); |
337 | 256 |
338 const uint64_t kBaseAddress2 = 0x2000; | 257 const uint64_t kBaseAddress2 = 0x2000; |
339 const uint64_t kSize2 = 0x0400; | 258 const uint64_t kSize2 = 0x0400; |
340 const uint8_t kValue2 = 'm'; | 259 const uint8_t kValue2 = 'm'; |
341 | 260 |
342 TestMemoryWriter memory_writer(kBaseAddress2, kSize2, kValue2); | 261 TestMinidumpMemoryWriter memory_writer(kBaseAddress2, kSize2, kValue2); |
343 memory_list_writer.AddMemory(&memory_writer); | 262 memory_list_writer.AddMemory(&memory_writer); |
344 | 263 |
345 minidump_file_writer.AddStream(&memory_list_writer); | 264 minidump_file_writer.AddStream(&memory_list_writer); |
346 | 265 |
347 StringFileWriter file_writer; | 266 StringFileWriter file_writer; |
348 ASSERT_TRUE(minidump_file_writer.WriteEverything(&file_writer)); | 267 ASSERT_TRUE(minidump_file_writer.WriteEverything(&file_writer)); |
349 | 268 |
350 const MINIDUMP_MEMORY_LIST* memory_list; | 269 const MINIDUMP_MEMORY_LIST* memory_list; |
351 GetMemoryListStream(file_writer.string(), &memory_list, 2); | 270 GetMemoryListStream(file_writer.string(), &memory_list, 2); |
352 if (Test::HasFatalFailure()) { | 271 if (Test::HasFatalFailure()) { |
353 return; | 272 return; |
354 } | 273 } |
355 | 274 |
356 EXPECT_EQ(2u, memory_list->NumberOfMemoryRanges); | 275 EXPECT_EQ(2u, memory_list->NumberOfMemoryRanges); |
357 | 276 |
358 MINIDUMP_MEMORY_DESCRIPTOR expected; | 277 MINIDUMP_MEMORY_DESCRIPTOR expected; |
359 | 278 |
360 { | 279 { |
361 SCOPED_TRACE("region 0"); | 280 SCOPED_TRACE("region 0"); |
362 | 281 |
363 expected.StartOfMemoryRange = kBaseAddress1; | 282 expected.StartOfMemoryRange = kBaseAddress1; |
364 expected.Memory.DataSize = kSize1; | 283 expected.Memory.DataSize = kSize1; |
365 expected.Memory.Rva = | 284 expected.Memory.Rva = |
366 sizeof(MINIDUMP_HEADER) + 2 * sizeof(MINIDUMP_DIRECTORY) + | 285 sizeof(MINIDUMP_HEADER) + 2 * sizeof(MINIDUMP_DIRECTORY) + |
367 sizeof(MINIDUMP_MEMORY_LIST) + | 286 sizeof(MINIDUMP_MEMORY_LIST) + |
368 memory_list->NumberOfMemoryRanges * sizeof(MINIDUMP_MEMORY_DESCRIPTOR); | 287 memory_list->NumberOfMemoryRanges * sizeof(MINIDUMP_MEMORY_DESCRIPTOR); |
369 ExpectMemoryDescriptorAndContents(&expected, | 288 ExpectMinidumpMemoryDescriptorAndContents(&expected, |
370 &memory_list->MemoryRanges[0], | 289 &memory_list->MemoryRanges[0], |
371 file_writer.string(), | 290 file_writer.string(), |
372 kValue1, | 291 kValue1, |
373 false); | 292 false); |
374 } | 293 } |
375 | 294 |
376 { | 295 { |
377 SCOPED_TRACE("region 1"); | 296 SCOPED_TRACE("region 1"); |
378 | 297 |
379 expected.StartOfMemoryRange = kBaseAddress2; | 298 expected.StartOfMemoryRange = kBaseAddress2; |
380 expected.Memory.DataSize = kSize2; | 299 expected.Memory.DataSize = kSize2; |
381 expected.Memory.Rva = memory_list->MemoryRanges[0].Memory.Rva + | 300 expected.Memory.Rva = memory_list->MemoryRanges[0].Memory.Rva + |
382 memory_list->MemoryRanges[0].Memory.DataSize; | 301 memory_list->MemoryRanges[0].Memory.DataSize; |
383 ExpectMemoryDescriptorAndContents(&expected, | 302 ExpectMinidumpMemoryDescriptorAndContents(&expected, |
384 &memory_list->MemoryRanges[1], | 303 &memory_list->MemoryRanges[1], |
385 file_writer.string(), | 304 file_writer.string(), |
386 kValue2, | 305 kValue2, |
387 true); | 306 true); |
388 } | 307 } |
389 } | 308 } |
390 | 309 |
391 } // namespace | 310 } // namespace |
OLD | NEW |