1 : // Copyright 2012 Google Inc. All Rights Reserved.
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 "syzygy/reorder/reorderer.h"
16 :
17 : #include "gmock/gmock.h"
18 : #include "gtest/gtest.h"
19 : #include "syzygy/core/unittest_util.h"
20 : #include "syzygy/pdb/omap.h"
21 : #include "syzygy/pe/unittest_util.h"
22 : #include "syzygy/reorder/order_generator_test.h"
23 : #include "syzygy/trace/parse/parse_engine.h"
24 : #include "syzygy/trace/parse/parser.h"
25 :
26 : namespace reorder {
27 :
28 : namespace {
29 :
30 : using block_graph::BlockGraph;
31 : using block_graph::ConstBlockVector;
32 : using testing::_;
33 : using testing::BlockSpecsAreEqual;
34 : using testing::DoAll;
35 : using testing::InSequence;
36 : using testing::InvokeWithoutArgs;
37 : using testing::OrdersAreEqual;
38 : using testing::Return;
39 : using testing::SectionSpecsAreEqual;
40 : using trace::parser::ParseEngine;
41 : using trace::parser::Parser;
42 :
43 : typedef Reorderer::Order::BlockSpec BlockSpec;
44 : typedef Reorderer::Order::SectionSpec SectionSpec;
45 : typedef Reorderer::Order::SectionSpecVector SectionSpecVector;
46 :
47 : // A wrapper for Reorderer giving us access to some of its internals.
48 : class TestReorderer : public Reorderer {
49 : public:
50 : TestReorderer(const base::FilePath& module_path,
51 : const base::FilePath& instrumented_path,
52 : const TraceFileList& trace_files,
53 : Flags flags);
54 :
55 E : const PEFile::Signature& instr_signature() const {
56 E : return playback_.instr_signature();
57 E : }
58 :
59 E : Parser& parser() { return parser_; }
60 E : Playback* playback() { return &playback_; }
61 : };
62 :
63 : // A dummy order generator that does nothing but record events fed to it via
64 : // the reorderer.
65 : class TestOrderGenerator : public Reorderer::OrderGenerator {
66 : public:
67 E : TestOrderGenerator() : Reorderer::OrderGenerator("TestOrderGenerator") {
68 E : }
69 :
70 E : virtual ~TestOrderGenerator() {
71 E : }
72 :
73 : virtual bool OnCodeBlockEntry(const BlockGraph::Block* block,
74 : RelativeAddress address,
75 : uint32 process_id,
76 : uint32 thread_id,
77 E : const Reorderer::UniqueTime& time) OVERRIDE {
78 : // Record the visited block.
79 E : blocks.push_back(block);
80 E : return true;
81 E : }
82 :
83 : virtual bool CalculateReordering(const PEFile& pe_file,
84 : const ImageLayout& image,
85 : bool reorder_code,
86 : bool reorder_data,
87 E : Reorderer::Order* order) OVERRIDE {
88 : // We don't actually generate an ordering.
89 E : return true;
90 E : }
91 :
92 : ConstBlockVector blocks;
93 : };
94 :
95 : class MockOrderGenerator : public Reorderer::OrderGenerator {
96 : public:
97 E : MockOrderGenerator() : Reorderer::OrderGenerator("MockOrderGenerator") {
98 E : }
99 :
100 : MOCK_METHOD2(OnProcessStarted,
101 E : bool(uint32 process_id, const Reorderer::UniqueTime& time));
102 :
103 : MOCK_METHOD2(OnProcessEnded,
104 E : bool(uint32 process_id, const Reorderer::UniqueTime& time));
105 :
106 : MOCK_METHOD5(OnCodeBlockEntry,
107 : bool(const BlockGraph::Block* block,
108 : RelativeAddress address,
109 : uint32 process_id,
110 : uint32 thread_id,
111 E : const Reorderer::UniqueTime& time));
112 :
113 : MOCK_METHOD5(CalculateReordering,
114 : bool(const PEFile& pe_file,
115 : const ImageLayout& image,
116 : bool reorder_code,
117 : bool reorder_data,
118 E : Reorderer::Order* order));
119 : };
120 :
121 : // A dummy parse engine. This lets us feed hand-crafted events to any consumer.
122 : class TestParseEngine : public ParseEngine {
123 : public:
124 : typedef block_graph::BlockGraph BlockGraph;
125 : typedef core::RelativeAddress RelativeAddress;
126 : typedef Reorderer::ImageLayout ImageLayout;
127 : typedef Reorderer::PEFile PEFile;
128 :
129 : explicit TestParseEngine(TestReorderer* reorderer)
130 : : ParseEngine("TestParseEngine", true),
131 E : reorderer_(reorderer) {
132 E : }
133 :
134 E : virtual ~TestParseEngine() {
135 E : }
136 :
137 E : virtual bool IsRecognizedTraceFile(const base::FilePath& trace_file_path) {
138 E : return true;
139 E : }
140 :
141 E : virtual bool OpenTraceFile(const base::FilePath& trace_file_path) {
142 E : return true;
143 E : }
144 :
145 : virtual bool ConsumeAllEvents();
146 :
147 E : virtual bool CloseAllTraceFiles() {
148 E : return true;
149 E : }
150 :
151 : using ParseEngine::AddModuleInformation;
152 :
153 : // This will hold the list of blocks that we expect the order generator to
154 : // build.
155 : ConstBlockVector blocks;
156 :
157 : private:
158 : // The parser needs to have a pointer to the reorderer in order to get image
159 : // data from it for producing false events.
160 : TestReorderer* reorderer_;
161 : };
162 :
163 : TestReorderer::TestReorderer(const base::FilePath& module_path,
164 : const base::FilePath& instrumented_path,
165 : const TraceFileList& trace_files,
166 : Flags flags)
167 E : : Reorderer(module_path, instrumented_path, trace_files, flags) {
168 E : }
169 :
170 : const DWORD kProcessId = 0xAAAAAAAA;
171 : const DWORD kThreadId = 0xBBBBBBBB;
172 E : const sym_util::ModuleInformation kExeInfo = {
173 E : 0x11111111, 0x22222222, 0x33333333, 0x44444444, L"file_name.exe" };
174 :
175 E : bool TestParseEngine::ConsumeAllEvents() {
176 : // Add dummy module information for some running process.
177 E : if (!AddModuleInformation(kProcessId, kExeInfo))
178 i : return false;
179 :
180 : // Simulate a process starting.
181 E : base::Time time = base::Time::Now();
182 E : event_handler_->OnProcessStarted(time, kProcessId, NULL);
183 :
184 E : sym_util::ModuleInformation dll_info = {};
185 E : const PEFile::Signature& sig = reorderer_->instr_signature();
186 E : dll_info.base_address = sig.base_address.value();
187 E : dll_info.image_checksum = sig.module_checksum;
188 E : dll_info.image_file_name = sig.path;
189 E : dll_info.module_size = sig.module_size;
190 E : dll_info.time_date_stamp = sig.module_time_date_stamp;
191 :
192 E : TraceModuleData dll_data = {};
193 : dll_data.module_base_addr =
194 E : reinterpret_cast<ModuleAddr>(dll_info.base_address);
195 E : dll_data.module_base_size = dll_info.module_size;
196 E : dll_data.module_exe[0] = 0;
197 E : dll_data.module_checksum = dll_info.image_checksum;
198 E : dll_data.module_time_date_stamp = dll_info.time_date_stamp;
199 : wcscpy_s(dll_data.module_name,
200 : sizeof(dll_data.module_name),
201 E : sig.path.c_str());
202 :
203 : // Simulate the process and thread attaching to the DLL. This adds the DLL
204 : // to the list of modules.
205 E : EVENT_TRACE event_record = {};
206 : event_record.Header.TimeStamp =
207 E : reinterpret_cast<LARGE_INTEGER&>(time.ToFileTime());
208 E : event_record.Header.ProcessId = kProcessId;
209 E : event_record.Header.ThreadId = kThreadId;
210 E : event_record.Header.Guid = kCallTraceEventClass;
211 E : event_record.Header.Class.Type = TRACE_PROCESS_ATTACH_EVENT;
212 E : event_record.MofData = &dll_data;
213 E : event_record.MofLength = sizeof(dll_data);
214 E : if (!DispatchEvent(&event_record))
215 i : return false;
216 :
217 E : event_record.Header.Class.Type = TRACE_THREAD_ATTACH_EVENT;
218 E : if (!DispatchEvent(&event_record))
219 i : return false;
220 :
221 : // Get all of the non-padding code blocks in the original image. (Padding
222 : // blocks don't make it to the instrumented DLL, so any events with addresses
223 : // that refer to padding blocks will fail to resolve via the OMAP info.)
224 : BlockGraph::AddressSpace::RangeMapConstIter block_it =
225 E : reorderer_->playback()->image()->blocks.begin();
226 E : for (; block_it != reorderer_->playback()->image()->blocks.end();
227 E : ++block_it) {
228 : if (block_it->second->type() == BlockGraph::CODE_BLOCK &&
229 E : (block_it->second->attributes() & BlockGraph::PADDING_BLOCK) == 0) {
230 E : blocks.push_back(block_it->second);
231 : }
232 E : }
233 :
234 : // Shuffle the code blocks.
235 E : std::random_shuffle(blocks.begin(), blocks.end());
236 :
237 : // Simulate half of the blocks using batch events.
238 : static const size_t kBatchCallCount = 5;
239 E : size_t i = 0;
240 E : for (; i < blocks.size() / 2; i += kBatchCallCount) {
241 : uint8 raw_data[sizeof(TraceBatchEnterData) +
242 E : kBatchCallCount * sizeof(TraceEnterEventData)] = {};
243 : TraceBatchEnterData& event_data =
244 E : *reinterpret_cast<TraceBatchEnterData*>(&raw_data);
245 E : event_data.thread_id = kThreadId;
246 E : event_data.num_calls = kBatchCallCount;
247 :
248 E : for (size_t j = 0; j < kBatchCallCount; ++j) {
249 : // Get the address of this block as an RVA in the instrumented module.
250 E : RelativeAddress rva = blocks[i + j]->addr();
251 : rva = pdb::TranslateAddressViaOmap(reorderer_->playback()->omap_from(),
252 E : rva);
253 :
254 : // Convert this to an absolute address using the base address from above.
255 E : uint64 abs = sig.base_address.value() + rva.value();
256 E : void* block_pointer = reinterpret_cast<void*>(abs);
257 :
258 E : event_data.calls[j].function = block_pointer;
259 E : }
260 :
261 E : event_record.Header.Class.Type = TRACE_BATCH_ENTER;
262 E : event_record.MofData = &raw_data;
263 E : event_record.MofLength = sizeof(raw_data);
264 E : if (!DispatchEvent(&event_record))
265 i : return false;
266 E : }
267 :
268 : // Simulate entry/exit pairs with the remaining blocks.
269 E : for (; i < blocks.size(); ++i) {
270 : // Get the address of this block as an RVA in the instrumented module.
271 E : RelativeAddress rva = blocks[i]->addr();
272 : rva = pdb::TranslateAddressViaOmap(reorderer_->playback()->omap_from(),
273 E : rva);
274 :
275 : // Convert this to an absolute address using the base address from above.
276 E : uint64 abs = sig.base_address.value() + rva.value();
277 E : void* block_pointer = reinterpret_cast<void*>(abs);
278 :
279 E : TraceEnterEventData event_data = {};
280 E : event_data.function = block_pointer;
281 :
282 : // Simulate an entry event.
283 E : event_record.Header.Class.Type = TRACE_ENTER_EVENT;
284 E : event_record.MofData = &event_data;
285 E : event_record.MofLength = sizeof(event_data);
286 E : if (!DispatchEvent(&event_record))
287 i : return false;
288 :
289 : // Simulate a corresponding exit event.
290 E : event_record.Header.Class.Type = TRACE_EXIT_EVENT;
291 E : if (!DispatchEvent(&event_record))
292 i : return false;
293 E : }
294 :
295 : // Simulate the thread and process detaching from the DLL.
296 E : event_record.Header.Class.Type = TRACE_THREAD_DETACH_EVENT;
297 E : event_record.MofData = &dll_data;
298 E : event_record.MofLength = sizeof(dll_data);
299 E : if (!DispatchEvent(&event_record))
300 i : return false;
301 :
302 E : event_record.Header.Class.Type = TRACE_PROCESS_DETACH_EVENT;
303 E : if (!DispatchEvent(&event_record))
304 i : return false;
305 :
306 : // Simulate the process ending.
307 E : event_handler_->OnProcessEnded(time, kProcessId);
308 :
309 E : return true;
310 E : }
311 :
312 : class ReordererTest : public testing::PELibUnitTest {
313 : public:
314 : typedef testing::PELibUnitTest Super;
315 :
316 : typedef block_graph::BlockGraph BlockGraph;
317 : typedef core::RelativeAddress RelativeAddress;
318 : typedef Reorderer::ImageLayout ImageLayout;
319 : typedef Reorderer::PEFile PEFile;
320 :
321 E : ReordererTest() : test_parse_engine_(NULL) {
322 E : }
323 :
324 E : void SetUp() OVERRIDE {
325 E : Super::SetUp();
326 :
327 : // Create the dummy trace file list.
328 E : Reorderer::TraceFileList trace_file_list;
329 E : trace_file_list.push_back(base::FilePath(L"foo"));
330 :
331 : // Set up the reorderer. These tests rely on
332 : // call_trace_instrumented_test_dll.dll, as generated by the test_data
333 : // project.
334 : const Reorderer::Flags kFlags = Reorderer::kFlagReorderCode |
335 E : Reorderer::kFlagReorderData;
336 : test_reorderer_.reset(new TestReorderer(
337 : testing::GetExeTestDataRelativePath(testing::kTestDllName),
338 : testing::GetExeTestDataRelativePath(
339 : testing::kCallTraceInstrumentedTestDllName),
340 : trace_file_list,
341 E : kFlags));
342 :
343 : // Setup the test parse engine and register it with the parser used
344 : // by the test reorderer. Note that ownership of the pointer is also
345 : // being passed.
346 E : ASSERT_TRUE(test_parse_engine_ == NULL);
347 E : test_parse_engine_ = new TestParseEngine(test_reorderer_.get());
348 E : ASSERT_TRUE(test_parse_engine_ != NULL);
349 E : test_reorderer_->parser().AddParseEngine(test_parse_engine_);
350 E : }
351 :
352 : // A reorderer will be initialized, in SetUp(), for each test run.
353 : scoped_ptr<TestReorderer> test_reorderer_;
354 :
355 : // The reorderer needs to be set up to use a custom parse engine before a
356 : // call to Reorder. This must be heap allocated and the responsibility for
357 : // deleting it rests with the parser.
358 : TestParseEngine* test_parse_engine_;
359 : };
360 :
361 : } // namespace
362 :
363 E : TEST_F(ReordererTest, ValidateCallbacks) {
364 E : MockOrderGenerator mock_order_generator;
365 :
366 : // Setup the expected calls.
367 E : InSequence s;
368 : EXPECT_CALL(mock_order_generator, OnProcessStarted(_, _))
369 E : .WillOnce(Return(true));
370 : EXPECT_CALL(mock_order_generator, OnCodeBlockEntry(_, _, _, _, _))
371 E : .WillRepeatedly(Return(true));
372 : EXPECT_CALL(mock_order_generator, OnProcessEnded(_, _))
373 E : .WillOnce(Return(true));
374 : EXPECT_CALL(mock_order_generator, CalculateReordering(_, _, _, _, _))
375 E : .WillOnce(Return(true));
376 :
377 : // Run the reorderer.
378 E : Reorderer::Order order;
379 E : PEFile pe_file;
380 E : BlockGraph block_graph;
381 E : ImageLayout image_layout(&block_graph);
382 : EXPECT_TRUE(test_reorderer_->Reorder(&mock_order_generator,
383 : &order,
384 : &pe_file,
385 E : &image_layout));
386 E : }
387 :
388 E : TEST_F(ReordererTest, Reorder) {
389 E : TestOrderGenerator test_order_generator;
390 :
391 : // Run the reorderer.
392 E : Reorderer::Order order;
393 E : PEFile pe_file;
394 E : BlockGraph block_graph;
395 E : ImageLayout image_layout(&block_graph);
396 : EXPECT_TRUE(test_reorderer_->Reorder(&test_order_generator,
397 : &order,
398 : &pe_file,
399 E : &image_layout));
400 :
401 : // We expect the order generator to have come up with the same list of
402 : // blocks that the parse engine used for generating dummy trace events.
403 : EXPECT_EQ(test_parse_engine_->blocks,
404 E : test_order_generator.blocks);
405 E : }
406 :
407 E : TEST(OrderTest, OrderConstructor) {
408 E : Reorderer::Order order;
409 E : EXPECT_TRUE(order.sections.empty());
410 E : EXPECT_TRUE(order.comment.empty());
411 E : }
412 :
413 E : TEST(OrderTest, SectionSpecConstructorsAndCopies) {
414 : // Create default constructed section spec.
415 E : Reorderer::Order::SectionSpec default_section_spec;
416 : EXPECT_EQ(Reorderer::Order::SectionSpec::kNewSectionId,
417 E : default_section_spec.id);
418 E : EXPECT_TRUE(default_section_spec.name.empty());
419 E : EXPECT_EQ(0U, default_section_spec.characteristics);
420 E : EXPECT_TRUE(default_section_spec.blocks.empty());
421 :
422 : // Create a customized section spec.
423 E : Reorderer::Order::SectionSpec other_section_spec;
424 E : EXPECT_TRUE(SectionSpecsAreEqual(default_section_spec, other_section_spec));
425 E : other_section_spec.id = 7;
426 E : other_section_spec.characteristics = 19;
427 E : other_section_spec.name = "other";
428 E : EXPECT_FALSE(SectionSpecsAreEqual(default_section_spec, other_section_spec));
429 :
430 : // Copy construct a section spec.
431 E : Reorderer::Order::SectionSpec copied_section_spec(other_section_spec);
432 E : EXPECT_TRUE(SectionSpecsAreEqual(other_section_spec, copied_section_spec));
433 E : EXPECT_FALSE(SectionSpecsAreEqual(copied_section_spec, default_section_spec));
434 :
435 : // Assign to the default section spec.
436 E : default_section_spec = other_section_spec;
437 E : EXPECT_TRUE(SectionSpecsAreEqual(copied_section_spec, default_section_spec));
438 E : }
439 :
440 E : TEST(OrderTest, BlockSpecConstructorsAndCopier) {
441 : static const BlockGraph::Block* kFauxBlockPtr =
442 : reinterpret_cast<BlockGraph::Block*>(0xCCCCCCCC);
443 :
444 : // Default construct a block spec.
445 E : Reorderer::Order::BlockSpec default_block_spec;
446 E : EXPECT_EQ(NULL, default_block_spec.block);
447 E : EXPECT_TRUE(default_block_spec.basic_block_offsets.empty());
448 :
449 : // Explicitly construct a block spec.
450 E : Reorderer::Order::BlockSpec explicit_block_spec(kFauxBlockPtr);
451 E : EXPECT_EQ(kFauxBlockPtr, explicit_block_spec.block);
452 E : EXPECT_TRUE(explicit_block_spec.basic_block_offsets.empty());
453 :
454 : // Default construct another block spec.
455 E : Reorderer::Order::BlockSpec block_spec;
456 E : EXPECT_TRUE(BlockSpecsAreEqual(default_block_spec, block_spec));
457 :
458 : // Modify the block spec.
459 E : block_spec.block = kFauxBlockPtr;
460 E : block_spec.basic_block_offsets.push_back(0);
461 E : block_spec.basic_block_offsets.push_back(8);
462 E : EXPECT_FALSE(BlockSpecsAreEqual(default_block_spec, block_spec));
463 E : EXPECT_FALSE(BlockSpecsAreEqual(explicit_block_spec, block_spec));
464 :
465 : // Copy construct a block spec.
466 E : Reorderer::Order::BlockSpec copied_block_spec(block_spec);
467 E : EXPECT_TRUE(BlockSpecsAreEqual(block_spec, copied_block_spec));
468 E : EXPECT_FALSE(BlockSpecsAreEqual(default_block_spec, block_spec));
469 E : EXPECT_FALSE(BlockSpecsAreEqual(explicit_block_spec, block_spec));
470 :
471 : // Assign to the explicit block spec.
472 E : explicit_block_spec = block_spec;
473 E : EXPECT_TRUE(BlockSpecsAreEqual(copied_block_spec, explicit_block_spec));
474 E : }
475 :
476 E : TEST(OrderTest, SerializeToJsonRoundTrip) {
477 : // Build a dummy block graph.
478 E : BlockGraph block_graph;
479 E : BlockGraph::Section* section1 = block_graph.AddSection(".text", 0);
480 E : BlockGraph::Section* section2 = block_graph.AddSection(".rdata", 0);
481 : BlockGraph::Block* block1 = block_graph.AddBlock(BlockGraph::CODE_BLOCK, 10,
482 E : "block1");
483 : BlockGraph::Block* block2 = block_graph.AddBlock(BlockGraph::DATA_BLOCK, 10,
484 E : "block2");
485 : BlockGraph::Block* block3 = block_graph.AddBlock(BlockGraph::DATA_BLOCK, 10,
486 E : "block3");
487 E : block1->set_section(section1->id());
488 E : block2->set_section(section2->id());
489 E : block3->set_section(section2->id());
490 :
491 : // Build a dummy image layout.
492 E : pe::ImageLayout layout(&block_graph);
493 E : pe::ImageLayout::SectionInfo section_info1 = {};
494 E : section_info1.name = section1->name();
495 E : section_info1.addr = core::RelativeAddress(0x1000);
496 E : section_info1.size = 0x1000;
497 E : section_info1.data_size = 0x1000;
498 E : layout.sections.push_back(section_info1);
499 :
500 E : pe::ImageLayout::SectionInfo section_info2 = {};
501 E : section_info2.name = section2->name();
502 E : section_info2.addr = core::RelativeAddress(0x2000);
503 E : section_info2.size = 0x1000;
504 E : section_info2.data_size = 0x1000;
505 E : layout.sections.push_back(section_info2);
506 :
507 : layout.blocks.InsertBlock(section_info1.addr,
508 E : block1);
509 : layout.blocks.InsertBlock(section_info2.addr,
510 E : block2);
511 : layout.blocks.InsertBlock(section_info2.addr + block2->size(),
512 E : block3);
513 :
514 : // Build a dummy order.
515 E : Reorderer::Order order;
516 E : order.comment = "This is a comment.";
517 E : order.sections.resize(2);
518 E : order.sections[0].id = section1->id();
519 E : order.sections[0].name = section1->name();
520 E : order.sections[0].characteristics = section1->characteristics();
521 E : order.sections[0].blocks.push_back(BlockSpec(block1));
522 E : order.sections[0].blocks.back().basic_block_offsets.push_back(0);
523 E : order.sections[0].blocks.back().basic_block_offsets.push_back(8);
524 E : order.sections[1].id = section2->id();
525 E : order.sections[1].name = section2->name();
526 E : order.sections[1].characteristics = section2->characteristics();
527 E : order.sections[1].blocks.push_back(BlockSpec(block2));
528 E : order.sections[1].blocks.push_back(BlockSpec(block3));
529 :
530 : base::FilePath module = testing::GetExeTestDataRelativePath(
531 E : testing::kTestDllName);
532 E : pe::PEFile pe_file;
533 E : ASSERT_TRUE(pe_file.Init(module));
534 :
535 : // Serialize the order.
536 E : base::FilePath temp_file;
537 E : ASSERT_TRUE(file_util::CreateTemporaryFile(&temp_file));
538 E : EXPECT_TRUE(order.SerializeToJSON(pe_file, temp_file, true));
539 :
540 : // Get the original module from the file.
541 E : base::FilePath orig_module;
542 E : EXPECT_TRUE(Reorderer::Order::GetOriginalModulePath(temp_file, &orig_module));
543 E : EXPECT_EQ(module, orig_module);
544 :
545 : // Deserialize it.
546 E : Reorderer::Order order2;
547 E : EXPECT_FALSE(OrdersAreEqual(order, order2));
548 E : EXPECT_TRUE(order2.LoadFromJSON(pe_file, layout, temp_file));
549 :
550 : // Expect them to be the same.
551 E : EXPECT_TRUE(OrdersAreEqual(order, order2));
552 :
553 E : EXPECT_TRUE(file_util::Delete(temp_file, false));
554 E : }
555 :
556 : } // namespace reorder
|