From 995012534e3490799756349d6efc8dd6ce151771 Mon Sep 17 00:00:00 2001 From: Mark Mentovai Date: Fri, 1 Aug 2014 12:48:28 -0400 Subject: [PATCH] Introduce MinidumpWritable, its dependencies, and their tests. MinidumpWritable is the base class for all minidump-writing operations. R=rsesek@chromium.org Review URL: https://codereview.chromium.org/432003005 --- DEPS | 2 +- crashpad.gyp | 2 + minidump/minidump.gyp | 53 ++ minidump/minidump_writable.cc | 264 +++++++++ minidump/minidump_writable.h | 273 +++++++++ minidump/minidump_writable_test.cc | 835 +++++++++++++++++++++++++++ util/file/fd_io.cc | 75 +++ util/file/fd_io.h | 50 ++ util/file/file_writer.cc | 149 +++++ util/file/file_writer.h | 130 +++++ util/file/string_file_writer.cc | 137 +++++ util/file/string_file_writer.h | 70 +++ util/file/string_file_writer_test.cc | 378 ++++++++++++ util/numeric/safe_assignment.h | 44 ++ util/util.gyp | 55 ++ 15 files changed, 2516 insertions(+), 1 deletion(-) create mode 100644 minidump/minidump.gyp create mode 100644 minidump/minidump_writable.cc create mode 100644 minidump/minidump_writable.h create mode 100644 minidump/minidump_writable_test.cc create mode 100644 util/file/fd_io.cc create mode 100644 util/file/fd_io.h create mode 100644 util/file/file_writer.cc create mode 100644 util/file/file_writer.h create mode 100644 util/file/string_file_writer.cc create mode 100644 util/file/string_file_writer.h create mode 100644 util/file/string_file_writer_test.cc create mode 100644 util/numeric/safe_assignment.h create mode 100644 util/util.gyp diff --git a/DEPS b/DEPS index 733bfbd8..ab970fc8 100644 --- a/DEPS +++ b/DEPS @@ -28,7 +28,7 @@ deps = { '39bb8956231c997babf0f25befdfb531f4d0b43c', # svn r1958 'crashpad/third_party/mini_chromium/mini_chromium': Var('chromium_git') + '/chromium/mini_chromium@' + - '7e95e5859f79ef7fe4163e797f99768e13b86132', + '58160e47f55227d1361c65346e0ad4bf26dfaefe', } hooks = [ diff --git a/crashpad.gyp b/crashpad.gyp index f1603f8b..d538df02 100644 --- a/crashpad.gyp +++ b/crashpad.gyp @@ -20,6 +20,8 @@ 'suppress_wildcard': 1, 'dependencies': [ 'compat/compat.gyp:*', + 'minidump/minidump.gyp:*', + 'util/util.gyp:*', ], 'sources': [ 'crashpad.doxy.h', diff --git a/minidump/minidump.gyp b/minidump/minidump.gyp new file mode 100644 index 00000000..6d44486b --- /dev/null +++ b/minidump/minidump.gyp @@ -0,0 +1,53 @@ +# Copyright 2014 The Crashpad Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +{ + 'targets': [ + { + 'target_name': 'minidump', + 'type': 'static_library', + 'dependencies': [ + '../compat/compat.gyp:compat', + '../third_party/mini_chromium/mini_chromium/base/base.gyp:base', + '../util/util.gyp:util', + ], + 'export_dependent_settings': [ + '../compat/compat.gyp:compat', + ], + 'include_dirs': [ + '..', + ], + 'sources': [ + 'minidump_writable.cc', + 'minidump_writable.h', + ], + }, + { + 'target_name': 'minidump_test', + 'type': 'executable', + 'dependencies': [ + 'minidump', + '../third_party/gtest/gtest.gyp:gtest', + '../third_party/mini_chromium/mini_chromium/base/base.gyp:base', + ], + 'include_dirs': [ + '..', + ], + 'sources': [ + '../third_party/gtest/gtest/src/gtest_main.cc', + 'minidump_writable_test.cc', + ], + }, + ], +} diff --git a/minidump/minidump_writable.cc b/minidump/minidump_writable.cc new file mode 100644 index 00000000..f1113a43 --- /dev/null +++ b/minidump/minidump_writable.cc @@ -0,0 +1,264 @@ +// Copyright 2014 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "minidump/minidump_writable.h" + +#include "base/logging.h" +#include "util/numeric/safe_assignment.h" + +namespace { + +const size_t kMaximumAlignment = 16; + +} // namespace + +namespace crashpad { +namespace internal { + +bool MinidumpWritable::WriteEverything(FileWriterInterface* file_writer) { + DCHECK_EQ(state_, kStateMutable); + + if (!Freeze()) { + return false; + } + + DCHECK_EQ(state_, kStateFrozen); + + off_t offset = 0; + std::vector write_sequence; + size_t size = WillWriteAtOffset(kPhaseEarly, &offset, &write_sequence); + if (size == kInvalidSize) { + return false; + } + + offset += size; + if (WillWriteAtOffset(kPhaseLate, &offset, &write_sequence) == kInvalidSize) { + return false; + } + + DCHECK_EQ(state_, kStateWritable); + DCHECK_EQ(write_sequence.front(), this); + + for (MinidumpWritable* writable : write_sequence) { + if (!writable->WritePaddingAndObject(file_writer)) { + return false; + } + } + + DCHECK_EQ(state_, kStateWritten); + + return true; +} + +void MinidumpWritable::RegisterRVA(RVA* rva) { + DCHECK_LE(state_, kStateFrozen); + + registered_rvas_.push_back(rva); +} + +void MinidumpWritable::RegisterLocationDescriptor( + MINIDUMP_LOCATION_DESCRIPTOR* location_descriptor) { + DCHECK_LE(state_, kStateFrozen); + + registered_location_descriptors_.push_back(location_descriptor); +} + +const size_t MinidumpWritable::kInvalidSize = + std::numeric_limits::max(); + +MinidumpWritable::MinidumpWritable() + : registered_rvas_(), + registered_location_descriptors_(), + leading_pad_bytes_(0), + state_(kStateMutable) { +} + +MinidumpWritable::~MinidumpWritable() { +} + +bool MinidumpWritable::Freeze() { + DCHECK_EQ(state_, kStateMutable); + state_ = kStateFrozen; + + std::vector children = Children(); + for (MinidumpWritable* child : children) { + if (!child->Freeze()) { + return false; + } + } + + return true; +} + +size_t MinidumpWritable::Alignment() { + DCHECK_GE(state_, kStateFrozen); + + return 4; +} + +std::vector MinidumpWritable::Children() { + DCHECK_GE(state_, kStateFrozen); + + return std::vector(); +} + +MinidumpWritable::Phase MinidumpWritable::WritePhase() { + return kPhaseEarly; +} + +size_t MinidumpWritable::WillWriteAtOffset( + Phase phase, + off_t* offset, + std::vector* write_sequence) { + off_t local_offset = *offset; + CHECK_GE(local_offset, 0); + + size_t leading_pad_bytes_this_phase; + size_t size; + if (phase == WritePhase()) { + DCHECK_EQ(state_, kStateFrozen); + + // Add this object to the sequence of MinidumpWritable objects to be + // written. + write_sequence->push_back(this); + + size = SizeOfObject(); + + if (size > 0) { + // Honor this object’s request to be aligned to a specific byte boundary. + // Once the alignment is corrected, this object knows exactly what file + // offset it will be written at. + size_t alignment = Alignment(); + CHECK_LE(alignment, kMaximumAlignment); + + leading_pad_bytes_this_phase = + (alignment - (local_offset % alignment)) % alignment; + local_offset += leading_pad_bytes_this_phase; + *offset = local_offset; + } else { + // If the object is size 0, alignment is of no concern. + leading_pad_bytes_this_phase = 0; + } + leading_pad_bytes_ = leading_pad_bytes_this_phase; + + // Now that the file offset that this object will be written at is known, + // let the subclass implementation know in case it’s interested. + if (!WillWriteAtOffsetImpl(local_offset)) { + return kInvalidSize; + } + + // Populate the RVA fields in other objects that have registered to point to + // this one. Typically, a parent object will have registered to point to its + // children, but this can also occur where no parent-child relationship + // exists. + if (!registered_rvas_.empty() || + !registered_location_descriptors_.empty()) { + RVA local_rva; + if (!AssignIfInRange(&local_rva, local_offset)) { + LOG(ERROR) << "offset " << local_offset << " out of range"; + return kInvalidSize; + } + + for (RVA* rva : registered_rvas_) { + *rva = local_rva; + } + + if (!registered_location_descriptors_.empty()) { + typeof(registered_location_descriptors_[0]->DataSize) local_size; + if (!AssignIfInRange(&local_size, size)) { + LOG(ERROR) << "size " << size << " out of range"; + return kInvalidSize; + } + + for (MINIDUMP_LOCATION_DESCRIPTOR* location_descriptor : + registered_location_descriptors_) { + location_descriptor->DataSize = local_size; + location_descriptor->Rva = local_rva; + } + } + } + + // This object is now considered writable. However, if it contains RVA or + // MINIDUMP_LOCATION_DESCRIPTOR fields, they may not be fully updated yet, + // because it’s the repsonsibility of these fields’ pointees to update them. + // Once WillWriteAtOffset has completed running for both phases on an entire + // tree, and the entire tree has moved into kStateFrozen, all RVA and + // MINIDUMP_LOCATION_DESCRIPTOR fields within that tree will be populated. + state_ = kStateWritable; + } else { + if (phase == kPhaseEarly) { + DCHECK_EQ(state_, kStateFrozen); + } else { + DCHECK_EQ(state_, kStateWritable); + } + + size = 0; + leading_pad_bytes_this_phase = 0; + } + + // Loop over children regardless of whether this object itself will write + // during this phase. An object’s children are not required to be written + // during the same phase as their parent. + std::vector children = Children(); + for (MinidumpWritable* child : children) { + // Use “auto” here because it’s impossible to know whether size_t (size) or + // off_t (local_offset) is the wider type, and thus what type the result of + // adding these two variables will have. + auto unaligned_child_offset = local_offset + size; + off_t child_offset; + if (!AssignIfInRange(&child_offset, unaligned_child_offset)) { + LOG(ERROR) << "offset " << unaligned_child_offset << " out of range"; + return kInvalidSize; + } + + size_t child_size = + child->WillWriteAtOffset(phase, &child_offset, write_sequence); + if (child_size == kInvalidSize) { + return kInvalidSize; + } + + size += child_size; + } + + return leading_pad_bytes_this_phase + size; +} + +bool MinidumpWritable::WillWriteAtOffsetImpl(off_t offset) { + return true; +} + +bool MinidumpWritable::WritePaddingAndObject(FileWriterInterface* file_writer) { + DCHECK_EQ(state_, kStateWritable); + + // The number of elements in kZeroes must be at least one less than the + // maximum Alignment() ever encountered. + const uint8_t kZeroes[kMaximumAlignment - 1] = {}; + DCHECK_LE(leading_pad_bytes_, arraysize(kZeroes)); + + if (leading_pad_bytes_) { + if (!file_writer->Write(&kZeroes, leading_pad_bytes_)) { + return false; + } + } + + if (!WriteObject(file_writer)) { + return false; + } + + state_ = kStateWritten; + return true; +} + +} // namespace internal +} // namespace crashpad diff --git a/minidump/minidump_writable.h b/minidump/minidump_writable.h new file mode 100644 index 00000000..32ed81e7 --- /dev/null +++ b/minidump/minidump_writable.h @@ -0,0 +1,273 @@ +// Copyright 2014 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_MINIDUMP_MINIDUMP_WRITABLE_H_ +#define CRASHPAD_MINIDUMP_MINIDUMP_WRITABLE_H_ + +#include +#include +#include + +#include +#include + +#include "base/basictypes.h" +#include "util/file/file_writer.h" + +namespace crashpad { +namespace internal { + +//! \brief The base class for all content that might be written to a minidump +//! file. +class MinidumpWritable { + public: + //! \brief Writes an object and all of its children to a minidump file. + //! + //! Use this on the root object of a tree of MinidumpWritable objects, + //! typically on a MinidumpFileWriter object. + //! + //! \param[in] file_writer The file writer to receive the minidump file’s + //! content. + //! + //! \return `true` on success. `false` on failure, with an appropriate message + //! logged. + //! + //! \note Valid in #kStateMutable, and transitions the object and the entire + //! tree beneath it through all states to #kStateWritten. + //! + //! \note This method should rarely be overridden. + virtual bool WriteEverything(FileWriterInterface* file_writer); + + //! \brief Registers a file offset pointer as one that should point to the + //! object on which this method is called. + //! + //! Once the file offset at which an object will be written is known (when it + //! enters #kStateWritable), registered RVA pointers will be updated. + //! + //! \param[in] rva A pointer to storage for the file offset that should + //! contain this object’s writable file offset, once it is known. + //! + //! \note Valid in #kStateFrozen or any preceding state. + // + // This is public instead of protected because objects of derived classes need + // to be able to register their own pointers with distinct objects. + void RegisterRVA(RVA* rva); + + //! \brief Registers a location descriptor as one that should point to the + //! object on which this method is called. + //! + //! Once an object’s size and the file offset at it will be written is known + //! (when it enters #kStateFrozen), the relevant data in registered location + //! descriptors will be updated. + //! + //! \param[in] location_descriptor A pointer to a location descriptor that + //! should contain this object’s writable size and file offset, once they + //! are known. + //! + //! \note Valid in #kStateFrozen or any preceding state. + // + // This is public instead of protected because objects of derived classes need + // to be able to register their own pointers with distinct objects. + void RegisterLocationDescriptor( + MINIDUMP_LOCATION_DESCRIPTOR* location_descriptor); + + protected: + //! \brief Identifies the state of an object. + //! + //! Objects will normally transition through each of these states as they are + //! created, populated with data, and then written to a minidump file. + enum State { + //! \brief The object’s properties can be modified. + kStateMutable = 0, + + //! \brief The object is “frozen”. + //! + //! Its properties cannot be modified. Pointers to file offsets of other + //! structures may not yet be valid. + kStateFrozen, + + //! \brief The object is writable. + //! + //! The file offset at which it will be written is known. Pointers to file + //! offsets of other structures are valid when all objects in a tree are in + //! this state. + kStateWritable, + + //! \brief The object has been written to a minidump file. + kStateWritten, + }; + + //! \brief Identifies the phase during which an object will be written to a + //! minidump file. + enum Phase { + //! \brief Objects that are written to a minidump file “early”. + //! + //! The normal sequence is for an object to write itself and then write all + //! of its children. + kPhaseEarly = 0, + + //! \brief Objects that are written to a minidump file “late”. + //! + //! Some objects, such as those capturing memory region snapshots, are + //! written to minidump files after all other objects. This “late” phase + //! identifies such objects. This is useful to improve spatial locality in + //! in minidump files in accordance with expected access patterns: unlike + //! most other data, memory snapshots are large and the entire snapshots do + //! not need to be consulted in order to process a minidump file. + kPhaseLate, + }; + + //! \brief A size value used to signal failure by methods that return + //! `size_t`. + static const size_t kInvalidSize; + + MinidumpWritable(); + ~MinidumpWritable(); + + //! \brief The state of the object. + State state() const { return state_; } + + //! \brief Transitions the object from #kStateMutable to #kStateFrozen. + //! + //! The default implementation marks the object as frozen and recursively + //! calls Freeze() on all of its children. Subclasses may override this method + //! to perform processing that should only be done once callers have finished + //! populating an object with data. Typically, a subclass implementation would + //! call RegisterRVA() or RegisterLocationDescriptor() on other objects as + //! appropriate, because at the time Freeze() runs, the in-memory locations of + //! RVAs and location descriptors are known and will not change for the + //! remaining duration of an object’s lifetime. + //! + //! \return `true` on success. `false` on failure, with an appropriate message + //! logged. + virtual bool Freeze(); + + //! \brief Returns the amount of space that this object will consume when + //! written to a minidump file, in bytes, not including any leading or + //! trailing padding necessary to maintain proper alignment. + //! + //! \note Valid in #kStateFrozen or any subsequent state. + virtual size_t SizeOfObject() = 0; + + //! \brief Returns the object’s desired byte-boundary alignment. + //! + //! The default implementation returns `4`. Subclasses may override this as + //! needed. + //! + //! \note Valid in #kStateFrozen or any subsequent state. + virtual size_t Alignment(); + + //! \brief Returns the object’s children. + //! + //! \note Valid in #kStateFrozen or any subsequent state. + virtual std::vector Children(); + + //! \brief Returns the object’s desired write phase. + //! + //! The default implementation returns #kPhaseEarly. Subclasses may override + //! this method to alter their write phase. + //! + //! \note Valid in any state. + virtual Phase WritePhase(); + + //! \brief Prepares the object to be written at a known file offset, + //! transitioning it from #kStateFrozen to #kStateWritable. + //! + //! This method is responsible for determining the final file offset of the + //! object, which may be increased from \a offset to meet alignment + //! requirements. It calls WillWriteAtOffsetImpl() for the benefit of + //! subclasses. It populates all RVAs and location descriptors registered with + //! it via RegisterRVA() and RegisterLocationDescriptor(). It also recurses + //! into all known children. + //! + //! \param[in] phase The phase during which the object will be written. If + //! this does not match Phase(), processing is suppressed, although + //! recursive processing will still occur on all children. This addresses + //! the case where parents and children do not write in the same phase. + //! \param[in] offset The file offset at which the object will be written. The + //! offset may need to be adjusted for alignment. + //! \param[out] write_sequence This object will append itself to this list, + //! such that on return from a recursive tree of WillWriteAtOffset() + //! calls, elements of the vector will be organized in the sequence that + //! the objects will be written to the minidump file. + //! + //! \return The file size consumed by this object and all children, including + //! any padding inserted to meet alignment requirements. On failure, + //! #kInvalidSize, with an appropriate message logged. + //! + //! \note This method cannot be overridden. Subclasses that need to perform + //! processing when an object transitions to #kStateWritable should + //! implement WillWriteAtOffsetImpl(), which is called by this method. + size_t WillWriteAtOffset(Phase phase, + off_t* offset, + std::vector* write_sequence); + + //! \brief Called once an object’s writable file offset is determined, as it + //! transitions into #kStateWritable. + //! + //! Subclasses can override this method if they need to provide additional + //! processing once their writable file offset is known. Typically, this will + //! be done by subclasses that handle certain RVAs themselves instead of using + //! the RegisterRVA() interface. + //! + //! \param[in] offset The file offset at which the object will be written. The + //! value passed to this method will already have been adjusted to meet + //! alignment requirements. + //! + //! \return `true` on success. `false` on error, indicating that the minidump + //! file should not be written. + //! + //! \note Valid in #kStateFrozen. The object will transition to + //! #kStateWritable after this method returns. + virtual bool WillWriteAtOffsetImpl(off_t offset); + + //! \brief Writes the object, transitioning it from #kStateWritable to + //! #kStateWritten. + //! + //! Writes any padding necessary to meet alignment requirements, and then + //! calls WriteObject() to write the object’s content. + //! + //! \param[in] file_writer The file writer to receive the object’s content. + //! + //! \return `true` on success. `false` on error with an appropriate message + //! logged. + //! + //! \note This method cannot be overridden. Subclasses must override + //! WriteObject(). + bool WritePaddingAndObject(FileWriterInterface* file_writer); + + //! \brief Writes the object’s content. + //! + //! \param[in] file_writer The file writer to receive the object’s content. + //! + //! \return `true` on success. `false` on error, indicating that the content + //! could not be written to the minidump file. + //! + //! \note Valid in #kStateWritable. The object will transition to + //! #kStateWritten after this method returns. + virtual bool WriteObject(FileWriterInterface* file_writer) = 0; + + private: + std::vector registered_rvas_; + std::vector registered_location_descriptors_; + size_t leading_pad_bytes_; + State state_; + + DISALLOW_COPY_AND_ASSIGN(MinidumpWritable); +}; + +} // namespace internal +} // namespace crashpad + +#endif // CRASHPAD_MINIDUMP_MINIDUMP_WRITABLE_H_ diff --git a/minidump/minidump_writable_test.cc b/minidump/minidump_writable_test.cc new file mode 100644 index 00000000..6adad968 --- /dev/null +++ b/minidump/minidump_writable_test.cc @@ -0,0 +1,835 @@ +// Copyright 2014 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "minidump/minidump_writable.h" + +#include +#include + +#include "base/basictypes.h" +#include "gtest/gtest.h" +#include "util/file/string_file_writer.h" + +namespace { + +using namespace crashpad; +using namespace testing; + +class BaseTestMinidumpWritable : public crashpad::internal::MinidumpWritable { + public: + BaseTestMinidumpWritable() + : MinidumpWritable(), + children_(), + expected_offset_(-1), + alignment_(0), + phase_(kPhaseEarly), + has_alignment_(false), + has_phase_(false), + verified_(false) {} + + ~BaseTestMinidumpWritable() { EXPECT_TRUE(verified_); } + + void SetAlignment(size_t alignment) { + alignment_ = alignment; + has_alignment_ = true; + } + + void AddChild(BaseTestMinidumpWritable* child) { children_.push_back(child); } + + void SetPhaseLate() { + phase_ = kPhaseLate; + has_phase_ = true; + } + + void Verify() { + verified_ = true; + EXPECT_EQ(kStateWritten, state()); + for (BaseTestMinidumpWritable* child : children_) { + child->Verify(); + } + } + + protected: + virtual bool Freeze() override { + EXPECT_EQ(kStateMutable, state()); + bool rv = MinidumpWritable::Freeze(); + EXPECT_TRUE(rv); + EXPECT_EQ(kStateFrozen, state()); + return rv; + } + + virtual size_t Alignment() override { + EXPECT_GE(state(), kStateFrozen); + return has_alignment_ ? alignment_ : MinidumpWritable::Alignment(); + } + + virtual std::vector Children() override { + EXPECT_GE(state(), kStateFrozen); + if (!children_.empty()) { + std::vector children; + for (BaseTestMinidumpWritable* child : children_) { + children.push_back(child); + } + return children; + } + return MinidumpWritable::Children(); + } + + virtual Phase WritePhase() override { + return has_phase_ ? phase_ : MinidumpWritable::Phase(); + } + + virtual bool WillWriteAtOffsetImpl(off_t offset) override { + EXPECT_EQ(state(), kStateFrozen); + expected_offset_ = offset; + bool rv = MinidumpWritable::WillWriteAtOffsetImpl(offset); + EXPECT_TRUE(rv); + return rv; + } + + virtual bool WriteObject(FileWriterInterface* file_writer) override { + EXPECT_EQ(state(), kStateWritable); + EXPECT_EQ(expected_offset_, file_writer->Seek(0, SEEK_CUR)); + + // Subclasses must override this. + return false; + } + + private: + std::vector children_; + off_t expected_offset_; + size_t alignment_; + Phase phase_; + bool has_alignment_; + bool has_phase_; + bool verified_; + + DISALLOW_COPY_AND_ASSIGN(BaseTestMinidumpWritable); +}; + +class TestStringMinidumpWritable final : public BaseTestMinidumpWritable { + public: + TestStringMinidumpWritable() : BaseTestMinidumpWritable(), data_() {} + + ~TestStringMinidumpWritable() {} + + void SetData(const std::string& string) { data_ = string; } + + protected: + virtual size_t SizeOfObject() override { + EXPECT_GE(state(), kStateFrozen); + return data_.size(); + } + + virtual bool WriteObject(FileWriterInterface* file_writer) override { + BaseTestMinidumpWritable::WriteObject(file_writer); + EXPECT_TRUE(file_writer->Write(&data_[0], data_.size())); + return true; + } + + private: + std::string data_; + + DISALLOW_COPY_AND_ASSIGN(TestStringMinidumpWritable); +}; + +TEST(MinidumpWritable, MinidumpWritable) { + StringFileWriter writer; + + { + SCOPED_TRACE("empty"); + writer.Reset(); + TestStringMinidumpWritable string_writable; + EXPECT_TRUE(string_writable.WriteEverything(&writer)); + EXPECT_TRUE(writer.string().empty()); + string_writable.Verify(); + } + + { + SCOPED_TRACE("childless"); + writer.Reset(); + TestStringMinidumpWritable string_writable; + string_writable.SetData("a"); + EXPECT_TRUE(string_writable.WriteEverything(&writer)); + EXPECT_EQ(1u, writer.string().size()); + EXPECT_EQ("a", writer.string()); + string_writable.Verify(); + } + + { + SCOPED_TRACE("parent-child"); + writer.Reset(); + TestStringMinidumpWritable parent; + parent.SetData("b"); + TestStringMinidumpWritable child; + child.SetData("c"); + parent.AddChild(&child); + EXPECT_TRUE(parent.WriteEverything(&writer)); + EXPECT_EQ(5u, writer.string().size()); + EXPECT_EQ(std::string("b\0\0\0c", 5), writer.string()); + parent.Verify(); + } + + { + SCOPED_TRACE("base alignment 2"); + writer.Reset(); + TestStringMinidumpWritable parent; + parent.SetData("de"); + TestStringMinidumpWritable child; + child.SetData("f"); + parent.AddChild(&child); + EXPECT_TRUE(parent.WriteEverything(&writer)); + EXPECT_EQ(5u, writer.string().size()); + EXPECT_EQ(std::string("de\0\0f", 5), writer.string()); + parent.Verify(); + } + + { + SCOPED_TRACE("base alignment 3"); + writer.Reset(); + TestStringMinidumpWritable parent; + parent.SetData("ghi"); + TestStringMinidumpWritable child; + child.SetData("j"); + parent.AddChild(&child); + EXPECT_TRUE(parent.WriteEverything(&writer)); + EXPECT_EQ(5u, writer.string().size()); + EXPECT_EQ(std::string("ghi\0j", 5), writer.string()); + parent.Verify(); + } + + { + SCOPED_TRACE("base alignment 4"); + writer.Reset(); + TestStringMinidumpWritable parent; + parent.SetData("klmn"); + TestStringMinidumpWritable child; + child.SetData("o"); + parent.AddChild(&child); + EXPECT_TRUE(parent.WriteEverything(&writer)); + EXPECT_EQ(5u, writer.string().size()); + EXPECT_EQ("klmno", writer.string()); + parent.Verify(); + } + + { + SCOPED_TRACE("base alignment 5"); + writer.Reset(); + TestStringMinidumpWritable parent; + parent.SetData("pqrst"); + TestStringMinidumpWritable child; + child.SetData("u"); + parent.AddChild(&child); + EXPECT_TRUE(parent.WriteEverything(&writer)); + EXPECT_EQ(9u, writer.string().size()); + EXPECT_EQ(std::string("pqrst\0\0\0u", 9), writer.string()); + parent.Verify(); + } + + { + SCOPED_TRACE("two children"); + writer.Reset(); + TestStringMinidumpWritable parent; + parent.SetData("parent"); + TestStringMinidumpWritable child_0; + child_0.SetData("child_0"); + parent.AddChild(&child_0); + TestStringMinidumpWritable child_1; + child_1.SetData("child_1"); + parent.AddChild(&child_1); + EXPECT_TRUE(parent.WriteEverything(&writer)); + EXPECT_EQ(23u, writer.string().size()); + EXPECT_EQ(std::string("parent\0\0child_0\0child_1", 23), writer.string()); + parent.Verify(); + } + + { + SCOPED_TRACE("grandchild"); + writer.Reset(); + TestStringMinidumpWritable parent; + parent.SetData("parent"); + TestStringMinidumpWritable child; + child.SetData("child"); + parent.AddChild(&child); + TestStringMinidumpWritable grandchild; + grandchild.SetData("grandchild"); + child.AddChild(&grandchild); + EXPECT_TRUE(parent.WriteEverything(&writer)); + EXPECT_EQ(26u, writer.string().size()); + EXPECT_EQ(std::string("parent\0\0child\0\0\0grandchild", 26), + writer.string()); + parent.Verify(); + } + + { + SCOPED_TRACE("grandchild with empty parent"); + writer.Reset(); + TestStringMinidumpWritable parent; + TestStringMinidumpWritable child; + child.SetData("child"); + parent.AddChild(&child); + TestStringMinidumpWritable grandchild; + grandchild.SetData("grandchild"); + child.AddChild(&grandchild); + EXPECT_TRUE(parent.WriteEverything(&writer)); + EXPECT_EQ(18u, writer.string().size()); + EXPECT_EQ(std::string("child\0\0\0grandchild", 18), writer.string()); + parent.Verify(); + } + + { + SCOPED_TRACE("grandchild with empty child"); + writer.Reset(); + TestStringMinidumpWritable parent; + parent.SetData("parent"); + TestStringMinidumpWritable child; + parent.AddChild(&child); + TestStringMinidumpWritable grandchild; + grandchild.SetData("grandchild"); + child.AddChild(&grandchild); + EXPECT_TRUE(parent.WriteEverything(&writer)); + EXPECT_EQ(18u, writer.string().size()); + EXPECT_EQ(std::string("parent\0\0grandchild", 18), writer.string()); + parent.Verify(); + } + + { + SCOPED_TRACE("grandchild with empty grandchild"); + writer.Reset(); + TestStringMinidumpWritable parent; + parent.SetData("parent"); + TestStringMinidumpWritable child; + child.SetData("child"); + parent.AddChild(&child); + TestStringMinidumpWritable grandchild; + child.AddChild(&grandchild); + EXPECT_TRUE(parent.WriteEverything(&writer)); + EXPECT_EQ(13u, writer.string().size()); + EXPECT_EQ(std::string("parent\0\0child", 13), writer.string()); + parent.Verify(); + } + + { + SCOPED_TRACE("grandchild with late-phase grandchild"); + writer.Reset(); + TestStringMinidumpWritable parent; + parent.SetData("parent"); + TestStringMinidumpWritable child; + child.SetData("child"); + parent.AddChild(&child); + TestStringMinidumpWritable grandchild; + grandchild.SetData("grandchild"); + grandchild.SetPhaseLate(); + child.AddChild(&grandchild); + EXPECT_TRUE(parent.WriteEverything(&writer)); + EXPECT_EQ(26u, writer.string().size()); + EXPECT_EQ(std::string("parent\0\0child\0\0\0grandchild", 26), + writer.string()); + parent.Verify(); + } + + { + SCOPED_TRACE("grandchild with late-phase child"); + writer.Reset(); + TestStringMinidumpWritable parent; + parent.SetData("parent"); + TestStringMinidumpWritable child; + child.SetData("child"); + child.SetPhaseLate(); + parent.AddChild(&child); + TestStringMinidumpWritable grandchild; + grandchild.SetData("grandchild"); + child.AddChild(&grandchild); + EXPECT_TRUE(parent.WriteEverything(&writer)); + EXPECT_EQ(25u, writer.string().size()); + EXPECT_EQ(std::string("parent\0\0grandchild\0\0child", 25), + writer.string()); + parent.Verify(); + } + + { + SCOPED_TRACE("family tree"); + writer.Reset(); + TestStringMinidumpWritable parent; + parent.SetData("P.."); + TestStringMinidumpWritable child_0; + child_0.SetData("C0."); + parent.AddChild(&child_0); + TestStringMinidumpWritable child_1; + child_1.SetData("C1."); + parent.AddChild(&child_1); + TestStringMinidumpWritable grandchild_00; + grandchild_00.SetData("G00"); + child_0.AddChild(&grandchild_00); + TestStringMinidumpWritable grandchild_01; + grandchild_01.SetData("G01"); + child_0.AddChild(&grandchild_01); + TestStringMinidumpWritable grandchild_10; + grandchild_10.SetData("G10"); + child_1.AddChild(&grandchild_10); + TestStringMinidumpWritable grandchild_11; + grandchild_11.SetData("G11"); + child_1.AddChild(&grandchild_11); + EXPECT_TRUE(parent.WriteEverything(&writer)); + EXPECT_EQ(27u, writer.string().size()); + EXPECT_EQ(std::string("P..\0C0.\0G00\0G01\0C1.\0G10\0G11", 27), + writer.string()); + parent.Verify(); + } + + { + SCOPED_TRACE("family tree with C0 late"); + writer.Reset(); + TestStringMinidumpWritable parent; + parent.SetData("P.."); + TestStringMinidumpWritable child_0; + child_0.SetData("C0."); + child_0.SetPhaseLate(); + parent.AddChild(&child_0); + TestStringMinidumpWritable child_1; + child_1.SetData("C1."); + parent.AddChild(&child_1); + TestStringMinidumpWritable grandchild_00; + grandchild_00.SetData("G00"); + child_0.AddChild(&grandchild_00); + TestStringMinidumpWritable grandchild_01; + grandchild_01.SetData("G01"); + child_0.AddChild(&grandchild_01); + TestStringMinidumpWritable grandchild_10; + grandchild_10.SetData("G10"); + child_1.AddChild(&grandchild_10); + TestStringMinidumpWritable grandchild_11; + grandchild_11.SetData("G11"); + child_1.AddChild(&grandchild_11); + EXPECT_TRUE(parent.WriteEverything(&writer)); + EXPECT_EQ(27u, writer.string().size()); + EXPECT_EQ(std::string("P..\0G00\0G01\0C1.\0G10\0G11\0C0.", 27), + writer.string()); + parent.Verify(); + } + + { + SCOPED_TRACE("family tree with G0 late"); + writer.Reset(); + TestStringMinidumpWritable parent; + parent.SetData("P.."); + TestStringMinidumpWritable child_0; + child_0.SetData("C0."); + parent.AddChild(&child_0); + TestStringMinidumpWritable child_1; + child_1.SetData("C1."); + parent.AddChild(&child_1); + TestStringMinidumpWritable grandchild_00; + grandchild_00.SetData("G00"); + grandchild_00.SetPhaseLate(); + child_0.AddChild(&grandchild_00); + TestStringMinidumpWritable grandchild_01; + grandchild_01.SetData("G01"); + grandchild_01.SetPhaseLate(); + child_0.AddChild(&grandchild_01); + TestStringMinidumpWritable grandchild_10; + grandchild_10.SetData("G10"); + child_1.AddChild(&grandchild_10); + TestStringMinidumpWritable grandchild_11; + grandchild_11.SetData("G11"); + child_1.AddChild(&grandchild_11); + EXPECT_TRUE(parent.WriteEverything(&writer)); + EXPECT_EQ(27u, writer.string().size()); + EXPECT_EQ(std::string("P..\0C0.\0C1.\0G10\0G11\0G00\0G01", 27), + writer.string()); + parent.Verify(); + } + + { + SCOPED_TRACE("align 1"); + writer.Reset(); + TestStringMinidumpWritable parent; + parent.SetData("p"); + TestStringMinidumpWritable child; + child.SetData("c"); + child.SetAlignment(1); + parent.AddChild(&child); + EXPECT_TRUE(parent.WriteEverything(&writer)); + EXPECT_EQ(2u, writer.string().size()); + EXPECT_EQ("pc", writer.string()); + parent.Verify(); + } + + { + SCOPED_TRACE("align 2"); + writer.Reset(); + TestStringMinidumpWritable parent; + parent.SetData("p"); + TestStringMinidumpWritable child; + child.SetData("c"); + child.SetAlignment(2); + parent.AddChild(&child); + EXPECT_TRUE(parent.WriteEverything(&writer)); + EXPECT_EQ(3u, writer.string().size()); + EXPECT_EQ(std::string("p\0c", 3), writer.string()); + parent.Verify(); + } +} + +class TestRVAMinidumpWritable final : public BaseTestMinidumpWritable { + public: + TestRVAMinidumpWritable() : BaseTestMinidumpWritable(), rva_() {} + + ~TestRVAMinidumpWritable() {} + + void SetRVA(MinidumpWritable* other) { other->RegisterRVA(&rva_); } + + protected: + virtual size_t SizeOfObject() override { + EXPECT_GE(state(), kStateFrozen); + return sizeof(rva_); + } + + virtual bool WriteObject(FileWriterInterface* file_writer) override { + BaseTestMinidumpWritable::WriteObject(file_writer); + EXPECT_TRUE(file_writer->Write(&rva_, sizeof(rva_))); + return true; + } + + private: + RVA rva_; + + DISALLOW_COPY_AND_ASSIGN(TestRVAMinidumpWritable); +}; + +RVA RVAAtIndex(const std::string& string, size_t index) { + return *reinterpret_cast(&string[index * sizeof(RVA)]); +} + +TEST(MinidumpWritable, RVA) { + StringFileWriter writer; + + { + SCOPED_TRACE("unset"); + writer.Reset(); + TestRVAMinidumpWritable rva_writable; + EXPECT_TRUE(rva_writable.WriteEverything(&writer)); + + ASSERT_EQ(sizeof(RVA), writer.string().size()); + EXPECT_EQ(0 * sizeof(RVA), RVAAtIndex(writer.string(), 0)); + rva_writable.Verify(); + } + + { + SCOPED_TRACE("self"); + writer.Reset(); + TestRVAMinidumpWritable rva_writable; + rva_writable.SetRVA(&rva_writable); + EXPECT_TRUE(rva_writable.WriteEverything(&writer)); + + ASSERT_EQ(sizeof(RVA), writer.string().size()); + EXPECT_EQ(0 * sizeof(RVA), RVAAtIndex(writer.string(), 0)); + rva_writable.Verify(); + } + + { + SCOPED_TRACE("parent-child self"); + writer.Reset(); + TestRVAMinidumpWritable parent; + parent.SetRVA(&parent); + TestRVAMinidumpWritable child; + child.SetRVA(&child); + parent.AddChild(&child); + EXPECT_TRUE(parent.WriteEverything(&writer)); + + ASSERT_EQ(2 * sizeof(RVA), writer.string().size()); + EXPECT_EQ(0 * sizeof(RVA), RVAAtIndex(writer.string(), 0)); + EXPECT_EQ(1 * sizeof(RVA), RVAAtIndex(writer.string(), 1)); + parent.Verify(); + } + + { + SCOPED_TRACE("parent-child only"); + writer.Reset(); + TestRVAMinidumpWritable parent; + TestRVAMinidumpWritable child; + parent.SetRVA(&child); + parent.AddChild(&child); + EXPECT_TRUE(parent.WriteEverything(&writer)); + + ASSERT_EQ(2 * sizeof(RVA), writer.string().size()); + EXPECT_EQ(1 * sizeof(RVA), RVAAtIndex(writer.string(), 0)); + EXPECT_EQ(0 * sizeof(RVA), RVAAtIndex(writer.string(), 1)); + parent.Verify(); + } + + { + SCOPED_TRACE("parent-child circular"); + writer.Reset(); + TestRVAMinidumpWritable parent; + TestRVAMinidumpWritable child; + parent.SetRVA(&child); + child.SetRVA(&parent); + parent.AddChild(&child); + EXPECT_TRUE(parent.WriteEverything(&writer)); + + ASSERT_EQ(2 * sizeof(RVA), writer.string().size()); + EXPECT_EQ(1 * sizeof(RVA), RVAAtIndex(writer.string(), 0)); + EXPECT_EQ(0 * sizeof(RVA), RVAAtIndex(writer.string(), 1)); + parent.Verify(); + } + + { + SCOPED_TRACE("grandchildren"); + writer.Reset(); + TestRVAMinidumpWritable parent; + TestRVAMinidumpWritable child; + parent.SetRVA(&child); + parent.AddChild(&child); + TestRVAMinidumpWritable grandchild_0; + grandchild_0.SetRVA(&child); + child.AddChild(&grandchild_0); + TestRVAMinidumpWritable grandchild_1; + grandchild_1.SetRVA(&child); + child.AddChild(&grandchild_1); + TestRVAMinidumpWritable grandchild_2; + grandchild_2.SetRVA(&child); + child.AddChild(&grandchild_2); + EXPECT_TRUE(parent.WriteEverything(&writer)); + + ASSERT_EQ(5 * sizeof(RVA), writer.string().size()); + EXPECT_EQ(1 * sizeof(RVA), RVAAtIndex(writer.string(), 0)); + EXPECT_EQ(0 * sizeof(RVA), RVAAtIndex(writer.string(), 1)); + EXPECT_EQ(1 * sizeof(RVA), RVAAtIndex(writer.string(), 2)); + EXPECT_EQ(1 * sizeof(RVA), RVAAtIndex(writer.string(), 3)); + EXPECT_EQ(1 * sizeof(RVA), RVAAtIndex(writer.string(), 4)); + parent.Verify(); + } +} + +class TestLocationDescriptorMinidumpWritable final + : public BaseTestMinidumpWritable { + public: + TestLocationDescriptorMinidumpWritable() + : BaseTestMinidumpWritable(), location_descriptor_(), string_() {} + + ~TestLocationDescriptorMinidumpWritable() {} + + void SetLocationDescriptor(MinidumpWritable* other) { + other->RegisterLocationDescriptor(&location_descriptor_); + } + + void SetString(const std::string& string) { string_ = string; } + + protected: + virtual size_t SizeOfObject() override { + EXPECT_GE(state(), kStateFrozen); + // NUL-terminate. + return sizeof(location_descriptor_) + string_.size() + 1; + } + + virtual bool WriteObject(FileWriterInterface* file_writer) override { + BaseTestMinidumpWritable::WriteObject(file_writer); + WritableIoVec iov; + iov.iov_base = &location_descriptor_; + iov.iov_len = sizeof(location_descriptor_); + std::vector iovecs(1, iov); + // NUL-terminate. + iov.iov_base = &string_[0]; + iov.iov_len = string_.size() + 1; + iovecs.push_back(iov); + EXPECT_TRUE(file_writer->WriteIoVec(&iovecs)); + return true; + } + + private: + MINIDUMP_LOCATION_DESCRIPTOR location_descriptor_; + std::string string_; + + DISALLOW_COPY_AND_ASSIGN(TestLocationDescriptorMinidumpWritable); +}; + +struct LocationDescriptorAndData { + MINIDUMP_LOCATION_DESCRIPTOR location_descriptor; + char string[1]; +}; + +const LocationDescriptorAndData* LDDAtIndex(const std::string& string, + size_t index) { + return reinterpret_cast(&string[index]); +} + +TEST(MinidumpWritable, LocationDescriptor) { + StringFileWriter writer; + + { + SCOPED_TRACE("unset"); + writer.Reset(); + TestLocationDescriptorMinidumpWritable location_descriptor_writable; + EXPECT_TRUE(location_descriptor_writable.WriteEverything(&writer)); + + ASSERT_EQ(9u, writer.string().size()); + const LocationDescriptorAndData* ldd = LDDAtIndex(writer.string(), 0); + EXPECT_EQ(0u, ldd->location_descriptor.DataSize); + EXPECT_EQ(0u, ldd->location_descriptor.Rva); + location_descriptor_writable.Verify(); + } + + { + SCOPED_TRACE("self"); + writer.Reset(); + TestLocationDescriptorMinidumpWritable location_descriptor_writable; + location_descriptor_writable.SetLocationDescriptor( + &location_descriptor_writable); + EXPECT_TRUE(location_descriptor_writable.WriteEverything(&writer)); + + ASSERT_EQ(9u, writer.string().size()); + const LocationDescriptorAndData* ldd = LDDAtIndex(writer.string(), 0); + EXPECT_EQ(9u, ldd->location_descriptor.DataSize); + EXPECT_EQ(0u, ldd->location_descriptor.Rva); + location_descriptor_writable.Verify(); + } + + { + SCOPED_TRACE("self with data"); + writer.Reset(); + TestLocationDescriptorMinidumpWritable location_descriptor_writable; + location_descriptor_writable.SetLocationDescriptor( + &location_descriptor_writable); + location_descriptor_writable.SetString("zz"); + EXPECT_TRUE(location_descriptor_writable.WriteEverything(&writer)); + + ASSERT_EQ(11u, writer.string().size()); + const LocationDescriptorAndData* ldd = LDDAtIndex(writer.string(), 0); + EXPECT_EQ(11u, ldd->location_descriptor.DataSize); + EXPECT_EQ(0u, ldd->location_descriptor.Rva); + EXPECT_STREQ("zz", ldd->string); + location_descriptor_writable.Verify(); + } + + { + SCOPED_TRACE("parent-child self"); + writer.Reset(); + TestLocationDescriptorMinidumpWritable parent; + parent.SetLocationDescriptor(&parent); + parent.SetString("yy"); + TestLocationDescriptorMinidumpWritable child; + child.SetLocationDescriptor(&child); + child.SetString("x"); + parent.AddChild(&child); + EXPECT_TRUE(parent.WriteEverything(&writer)); + + ASSERT_EQ(22u, writer.string().size()); + const LocationDescriptorAndData* ldd = LDDAtIndex(writer.string(), 0); + EXPECT_EQ(11u, ldd->location_descriptor.DataSize); + EXPECT_EQ(0u, ldd->location_descriptor.Rva); + EXPECT_STREQ("yy", ldd->string); + ldd = LDDAtIndex(writer.string(), 12); + EXPECT_EQ(10u, ldd->location_descriptor.DataSize); + EXPECT_EQ(12u, ldd->location_descriptor.Rva); + EXPECT_STREQ("x", ldd->string); + parent.Verify(); + } + + { + SCOPED_TRACE("parent-child only"); + writer.Reset(); + TestLocationDescriptorMinidumpWritable parent; + TestLocationDescriptorMinidumpWritable child; + parent.SetLocationDescriptor(&child); + parent.SetString("www"); + child.SetString("vv"); + parent.AddChild(&child); + EXPECT_TRUE(parent.WriteEverything(&writer)); + + ASSERT_EQ(23u, writer.string().size()); + const LocationDescriptorAndData* ldd = LDDAtIndex(writer.string(), 0); + EXPECT_EQ(11u, ldd->location_descriptor.DataSize); + EXPECT_EQ(12u, ldd->location_descriptor.Rva); + EXPECT_STREQ("www", ldd->string); + ldd = LDDAtIndex(writer.string(), 12); + EXPECT_EQ(0u, ldd->location_descriptor.DataSize); + EXPECT_EQ(0u, ldd->location_descriptor.Rva); + EXPECT_STREQ("vv", ldd->string); + parent.Verify(); + } + + { + SCOPED_TRACE("parent-child circular"); + writer.Reset(); + TestLocationDescriptorMinidumpWritable parent; + TestLocationDescriptorMinidumpWritable child; + parent.SetLocationDescriptor(&child); + parent.SetString("uuuu"); + child.SetLocationDescriptor(&parent); + child.SetString("tttt"); + parent.AddChild(&child); + EXPECT_TRUE(parent.WriteEverything(&writer)); + + ASSERT_EQ(29u, writer.string().size()); + const LocationDescriptorAndData* ldd = LDDAtIndex(writer.string(), 0); + EXPECT_EQ(13u, ldd->location_descriptor.DataSize); + EXPECT_EQ(16u, ldd->location_descriptor.Rva); + EXPECT_STREQ("uuuu", ldd->string); + ldd = LDDAtIndex(writer.string(), 16); + EXPECT_EQ(13u, ldd->location_descriptor.DataSize); + EXPECT_EQ(0u, ldd->location_descriptor.Rva); + EXPECT_STREQ("tttt", ldd->string); + parent.Verify(); + } + + { + SCOPED_TRACE("grandchildren"); + writer.Reset(); + TestLocationDescriptorMinidumpWritable parent; + TestLocationDescriptorMinidumpWritable child; + parent.SetLocationDescriptor(&child); + parent.SetString("s"); + parent.AddChild(&child); + child.SetString("r"); + TestLocationDescriptorMinidumpWritable grandchild_0; + grandchild_0.SetLocationDescriptor(&child); + grandchild_0.SetString("q"); + child.AddChild(&grandchild_0); + TestLocationDescriptorMinidumpWritable grandchild_1; + grandchild_1.SetLocationDescriptor(&child); + grandchild_1.SetString("p"); + child.AddChild(&grandchild_1); + TestLocationDescriptorMinidumpWritable grandchild_2; + grandchild_2.SetLocationDescriptor(&child); + grandchild_2.SetString("o"); + child.AddChild(&grandchild_2); + EXPECT_TRUE(parent.WriteEverything(&writer)); + + ASSERT_EQ(58u, writer.string().size()); + const LocationDescriptorAndData* ldd = LDDAtIndex(writer.string(), 0); + EXPECT_EQ(10u, ldd->location_descriptor.DataSize); + EXPECT_EQ(12u, ldd->location_descriptor.Rva); + EXPECT_STREQ("s", ldd->string); + ldd = LDDAtIndex(writer.string(), 12); + EXPECT_EQ(0u, ldd->location_descriptor.DataSize); + EXPECT_EQ(0u, ldd->location_descriptor.Rva); + EXPECT_STREQ("r", ldd->string); + ldd = LDDAtIndex(writer.string(), 24); + EXPECT_EQ(10u, ldd->location_descriptor.DataSize); + EXPECT_EQ(12u, ldd->location_descriptor.Rva); + EXPECT_STREQ("q", ldd->string); + ldd = LDDAtIndex(writer.string(), 36); + EXPECT_EQ(10u, ldd->location_descriptor.DataSize); + EXPECT_EQ(12u, ldd->location_descriptor.Rva); + EXPECT_STREQ("p", ldd->string); + ldd = LDDAtIndex(writer.string(), 48); + EXPECT_EQ(10u, ldd->location_descriptor.DataSize); + EXPECT_EQ(12u, ldd->location_descriptor.Rva); + EXPECT_STREQ("o", ldd->string); + parent.Verify(); + } +} + +} // namespace diff --git a/util/file/fd_io.cc b/util/file/fd_io.cc new file mode 100644 index 00000000..94258800 --- /dev/null +++ b/util/file/fd_io.cc @@ -0,0 +1,75 @@ +// Copyright 2014 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "util/file/fd_io.h" + +#include + +#include "base/posix/eintr_wrapper.h" + +namespace { + +struct ReadTraits { + typedef void* VoidBufferType; + typedef char* CharBufferType; + static ssize_t Operate(int fd, CharBufferType buffer, size_t size) { + return read(fd, buffer, size); + } +}; + +struct WriteTraits { + typedef const void* VoidBufferType; + typedef const char* CharBufferType; + static ssize_t Operate(int fd, CharBufferType buffer, size_t size) { + return write(fd, buffer, size); + } +}; + +template +ssize_t ReadOrWrite(int fd, + typename Traits::VoidBufferType buffer, + size_t size) { + typename Traits::CharBufferType buffer_c = + reinterpret_cast(buffer); + + ssize_t total_bytes = 0; + while (size > 0) { + ssize_t bytes = HANDLE_EINTR(Traits::Operate(fd, buffer_c, size)); + if (bytes < 0) { + return bytes; + } else if (bytes == 0) { + break; + } + + buffer_c += bytes; + size -= bytes; + total_bytes += bytes; + } + + return total_bytes; +} + +} // namespace + +namespace crashpad { + +ssize_t ReadFD(int fd, void* buffer, size_t size) { + return ReadOrWrite(fd, buffer, size); +} + +ssize_t WriteFD(int fd, const void* buffer, size_t size) { + return ReadOrWrite(fd, buffer, size); +} + +} // namespace crashpad diff --git a/util/file/fd_io.h b/util/file/fd_io.h new file mode 100644 index 00000000..07f39b6d --- /dev/null +++ b/util/file/fd_io.h @@ -0,0 +1,50 @@ +// Copyright 2014 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_UTIL_FILE_FD_IO_H_ +#define CRASHPAD_UTIL_FILE_FD_IO_H_ + +#include + +namespace crashpad { + +//! \brief Wraps `read()`, retrying when interrupted or following a short read. +//! +//! This function reads into \a buffer, stopping only when \a size bytes have +//! been read or when `read()` returns 0, indicating that end-of-file has been +//! reached. +//! +//! \return The number of bytes read and placed into \a buffer, or `-1` on +//! error, with `errno` set appropriately. On error, a portion of \a fd may +//! have been read into \a buffer. +//! +//! \sa WriteFD +ssize_t ReadFD(int fd, void* buffer, size_t size); + +//! \brief Wraps `write()`, retrying when interrupted or following a short +//! write. +//! +//! This function writes to \a fd, stopping only when \a size bytes have been +//! written. +//! +//! \return The number of bytes written from \a buffer, or `-1` on error, with +//! `errno` set appropriately. On error, a portion of \a buffer may have +//! been written to \a fd. +//! +//! \sa ReadFD +ssize_t WriteFD(int fd, const void* buffer, size_t size); + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_FILE_FD_IO_H_ diff --git a/util/file/file_writer.cc b/util/file/file_writer.cc new file mode 100644 index 00000000..592f0f61 --- /dev/null +++ b/util/file/file_writer.cc @@ -0,0 +1,149 @@ +// Copyright 2014 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "util/file/file_writer.h" + +#include + +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#include "util/file/fd_io.h" + +namespace crashpad { + +// Ensure type compatibility between WritableIoVec and iovec. +COMPILE_ASSERT(sizeof(WritableIoVec) == sizeof(iovec), WritableIoVec_size); +COMPILE_ASSERT(offsetof(WritableIoVec, iov_base) == offsetof(iovec, iov_base), + WritableIoVec_base_offset); +COMPILE_ASSERT(offsetof(WritableIoVec, iov_len) == offsetof(iovec, iov_len), + WritableIoVec_len_offset); + +FileWriter::FileWriter() : fd_() { +} + +FileWriter::~FileWriter() { +} + +bool FileWriter::Open(const base::FilePath& path, int oflag, mode_t mode) { + CHECK(!fd_.is_valid()); + + DCHECK((oflag & O_WRONLY) || (oflag & O_RDWR)); + + fd_.reset(HANDLE_EINTR(open(path.value().c_str(), oflag, mode))); + if (!fd_.is_valid()) { + PLOG(ERROR) << "open " << path.value(); + return false; + } + + return true; +} + +void FileWriter::Close() { + CHECK(fd_.is_valid()); + + fd_.reset(); +} + +bool FileWriter::Write(const void* data, size_t size) { + DCHECK(fd_.is_valid()); + + // TODO(mark): Write no more than SSIZE_MAX bytes in a single call. + ssize_t written = WriteFD(fd_.get(), data, size); + if (written < 0) { + PLOG(ERROR) << "write"; + return false; + } else if (written == 0) { + LOG(ERROR) << "write: returned 0"; + return false; + } + + return true; +} + +bool FileWriter::WriteIoVec(std::vector* iovecs) { + DCHECK(fd_.is_valid()); + + ssize_t size = 0; + for (const WritableIoVec& iov : *iovecs) { + // TODO(mark): Check to avoid overflow of ssize_t, and fail with EINVAL. + size += iov.iov_len; + } + + // Get an iovec*, because that’s what writev wants. The only difference + // between WritableIoVec and iovec is that WritableIoVec’s iov_base is a + // pointer to a const buffer, where iovec’s iov_base isn’t. writev doesn’t + // actually write to the data, so this cast is safe here. iovec’s iov_base is + // non-const because the same structure is used for readv and writev, and + // readv needs to write to the buffer that iov_base points to. + iovec* iov = reinterpret_cast(&(*iovecs)[0]); + size_t remaining_iovecs = iovecs->size(); + + while (size > 0) { + size_t writev_iovec_count = + std::min(remaining_iovecs, static_cast(IOV_MAX)); + ssize_t written = HANDLE_EINTR(writev(fd_.get(), iov, writev_iovec_count)); + if (written < 0) { + PLOG(ERROR) << "writev"; + return false; + } else if (written == 0) { + LOG(ERROR) << "writev: returned 0"; + return false; + } + + size -= written; + DCHECK_GE(size, 0); + + if (size == 0) { + remaining_iovecs = 0; + break; + } + + while (written > 0) { + size_t wrote_this_iovec = + std::min(static_cast(written), iov->iov_len); + written -= wrote_this_iovec; + if (wrote_this_iovec < iov->iov_len) { + iov->iov_base = + reinterpret_cast(iov->iov_base) + wrote_this_iovec; + iov->iov_len -= wrote_this_iovec; + } else { + ++iov; + --remaining_iovecs; + } + } + } + + DCHECK_EQ(remaining_iovecs, 0u); + +#ifndef NDEBUG + // The interface says that |iovecs| is not sacred, so scramble it to make sure + // that nobody depends on it. + memset(&(*iovecs)[0], 0xa5, sizeof((*iovecs)[0]) * iovecs->size()); +#endif + + return true; +} + +off_t FileWriter::Seek(off_t offset, int whence) { + DCHECK(fd_.is_valid()); + + off_t rv = lseek(fd_.get(), offset, whence); + if (rv < 0) { + PLOG(ERROR) << "lseek"; + } + + return rv; +} + +} // namespace crashpad diff --git a/util/file/file_writer.h b/util/file/file_writer.h new file mode 100644 index 00000000..e454848d --- /dev/null +++ b/util/file/file_writer.h @@ -0,0 +1,130 @@ +// Copyright 2014 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_UTIL_FILE_FILE_WRITER_H_ +#define CRASHPAD_UTIL_FILE_FILE_WRITER_H_ + +#include +#include +#include +#include + +#include +#include + +#include "base/basictypes.h" +#include "base/files/file_path.h" +#include "base/files/scoped_file.h" + +namespace crashpad { + +//! \brief A version of `iovec` with a `const` #iov_base field. +//! +//! This structure is intended to be used for write operations. +// +// Type compatibility with iovec is tested with static assertions in the +// implementation file. +struct WritableIoVec { + //! \brief The base address of a memory region for output. + const void* iov_base; + + //! \brief The size of the memory pointed to by #iov_base. + size_t iov_len; +}; + +//! \brief An interface to write to files and other file-like objects with POSIX +//! semantics. +class FileWriterInterface { + public: + //! \brief Wraps `write()` or provides an alternate implementation with + //! identical semantics. This method will write the entire buffer, + //! continuing after a short write or after being interrupted. + //! + //! \return `true` if the operation succeeded, `false` if it failed, with an + //! error message logged. + virtual bool Write(const void* data, size_t size) = 0; + + //! \brief Wraps `writev()` or provides an alternate implementation with + //! identical semantics. This method will write the entire buffer, + //! continuing after a short write or after being interrupted. + //! + //! \return `true` if the operation succeeded, `false` if it failed, with an + //! error message logged. + //! + //! \note The contents of \a iovecs are undefined when this method returns. + virtual bool WriteIoVec(std::vector* iovecs) = 0; + + //! \brief Wraps `lseek()` or provides an alternate implementation with + //! identical semantics. + //! + //! \return The return value of `lseek()`. `-1` on failure, with an error + //! message logged. + virtual off_t Seek(off_t offset, int whence) = 0; + + protected: + ~FileWriterInterface() {} +}; + +//! \brief A file writer implementation that wraps traditional POSIX file +//! operations on files accessed through the filesystem. +class FileWriter : public FileWriterInterface { + public: + FileWriter(); + ~FileWriter(); + + //! \brief Wraps `open()`. + //! + //! \return `true` if the operation succeeded, `false` if it failed, with an + //! error message logged. + //! + //! \note After a successful call, this method cannot be called again until + //! after Close(). + bool Open(const base::FilePath& path, int oflag, mode_t mode); + + //! \brief Wraps `close().` + //! + //! \note It is only valid to call this method on an object that has had a + //! successful Open() that has not yet been matched by a subsequent call + //! to this method. + void Close(); + + // FileWriterInterface: + + //! \copydoc FileWriterInterface::Write() + //! + //! \note It is only valid to call this method between a successful Open() and + //! a Close(). + virtual bool Write(const void* data, size_t size) override; + + //! \copydoc FileWriterInterface::WriteIoVec() + //! + //! \note It is only valid to call this method between a successful Open() and + //! a Close(). + virtual bool WriteIoVec(std::vector* iovecs) override; + + //! \copydoc FileWriterInterface::Seek() + //! + //! \note It is only valid to call this method between a successful Open() and + //! a Close(). + virtual off_t Seek(off_t offset, int whence) override; + + private: + base::ScopedFD fd_; + + DISALLOW_COPY_AND_ASSIGN(FileWriter); +}; + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_FILE_FILE_WRITER_H_ diff --git a/util/file/string_file_writer.cc b/util/file/string_file_writer.cc new file mode 100644 index 00000000..2c2f1f52 --- /dev/null +++ b/util/file/string_file_writer.cc @@ -0,0 +1,137 @@ +// Copyright 2014 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "util/file/string_file_writer.h" + +#include + +#include "base/logging.h" +#include "base/numerics/safe_math.h" +#include "util/numeric/safe_assignment.h" + +namespace crashpad { + +StringFileWriter::StringFileWriter() : string_(), offset_(0) { +} + +StringFileWriter::~StringFileWriter() { +} + +void StringFileWriter::Reset() { + string_.clear(); + offset_ = 0; +} + +bool StringFileWriter::Write(const void* data, size_t size) { + DCHECK(offset_.IsValid()); + + const size_t offset = offset_.ValueOrDie(); + if (offset > string_.size()) { + string_.resize(offset); + } + + base::CheckedNumeric new_offset = offset_; + new_offset += size; + if (!new_offset.IsValid()) { + LOG(ERROR) << "Write(): file too large"; + return false; + } + + string_.replace(offset, size, reinterpret_cast(data), size); + offset_ = new_offset; + + return true; +} + +bool StringFileWriter::WriteIoVec(std::vector* iovecs) { + DCHECK(offset_.IsValid()); + + if (iovecs->empty()) { + LOG(ERROR) << "WriteIoVec(): no iovecs"; + return false; + } + + // Avoid writing anything at all if it would cause an overflow. + base::CheckedNumeric new_offset = offset_; + for (const WritableIoVec& iov : *iovecs) { + new_offset += iov.iov_len; + if (!new_offset.IsValid()) { + LOG(ERROR) << "WriteIoVec(): file too large"; + return false; + } + } + + for (const WritableIoVec& iov : *iovecs) { + if (!Write(iov.iov_base, iov.iov_len)) { + return false; + } + } + +#ifndef NDEBUG + // The interface says that |iovecs| is not sacred, so scramble it to make sure + // that nobody depends on it. + memset(&(*iovecs)[0], 0xa5, sizeof((*iovecs)[0]) * iovecs->size()); +#endif + + return true; +} + +off_t StringFileWriter::Seek(off_t offset, int whence) { + DCHECK(offset_.IsValid()); + + size_t base_offset; + + switch (whence) { + case SEEK_SET: + base_offset = 0; + break; + + case SEEK_CUR: + base_offset = offset_.ValueOrDie(); + break; + + case SEEK_END: + base_offset = string_.size(); + break; + + default: + LOG(ERROR) << "Seek(): invalid whence " << whence; + return -1; + } + + off_t offset_offt; + if (!AssignIfInRange(&offset_offt, base_offset)) { + LOG(ERROR) << "Seek(): base_offset " << base_offset << " invalid for off_t"; + return -1; + } + + base::CheckedNumeric new_offset(offset_offt); + new_offset += offset; + if (!new_offset.IsValid()) { + LOG(ERROR) << "Seek(): new_offset invalid"; + return -1; + } + + if (!AssignIfInRange(&offset_offt, new_offset.ValueOrDie())) { + LOG(ERROR) << "Seek(): new_offset " << new_offset.ValueOrDie() + << " invalid for size_t"; + return -1; + } + + offset_ = offset_offt; + + return offset_.ValueOrDie(); +} + +} // namespace crashpad diff --git a/util/file/string_file_writer.h b/util/file/string_file_writer.h new file mode 100644 index 00000000..4b4002bd --- /dev/null +++ b/util/file/string_file_writer.h @@ -0,0 +1,70 @@ +// Copyright 2014 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_UTIL_FILE_STRING_FILE_WRITER_H_ +#define CRASHPAD_UTIL_FILE_STRING_FILE_WRITER_H_ + +#include + +#include + +#include "base/basictypes.h" +#include "base/numerics/safe_math.h" +#include "util/file/file_writer.h" + +namespace crashpad { + +//! \brief A file writer backed by a virtual file, as opposed to a file on disk +//! or other operating system file descriptor-based file. +//! +//! The virtual file is a buffer in memory. This class is convenient for use +//! with other code that normally expects to write files, when it is impractical +//! or inconvenient to write a file. It is expected that tests, in particular, +//! will benefit from using this class. +class StringFileWriter : public FileWriterInterface { + public: + StringFileWriter(); + ~StringFileWriter(); + + //! \brief Returns a string containing the virtual file’s contents. + const std::string& string() const { return string_; } + + //! \brief Resets the virtual file’s contents to be empty, and resets its file + //! position to `0`. + void Reset(); + + // FileWriterInterface: + virtual bool Write(const void* data, size_t size) override; + virtual bool WriteIoVec(std::vector* iovecs) override; + virtual off_t Seek(off_t offset, int whence) override; + + private: + //! \brief The virtual file’s contents. + std::string string_; + + //! \brief The file offset of the virtual file. + //! + //! \note This is stored in a `size_t` to match the characteristics of + //! #string_, the `std::string` used to store the virtual file’s contents. + //! This type will have different characteristics than the `off_t` used to + //! report file offsets. The implementation must take care when converting + //! between these distinct types. + base::CheckedNumeric offset_; + + DISALLOW_COPY_AND_ASSIGN(StringFileWriter); +}; + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_FILE_STRING_FILE_WRITER_H_ diff --git a/util/file/string_file_writer_test.cc b/util/file/string_file_writer_test.cc new file mode 100644 index 00000000..f0ec72f7 --- /dev/null +++ b/util/file/string_file_writer_test.cc @@ -0,0 +1,378 @@ +// Copyright 2014 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "util/file/string_file_writer.h" + +#include +#include + +#include "gtest/gtest.h" + +namespace { + +using namespace crashpad; + +TEST(StringFileWriter, EmptyFile) { + StringFileWriter writer; + EXPECT_TRUE(writer.string().empty()); + EXPECT_EQ(0, writer.Seek(0, SEEK_CUR)); + EXPECT_TRUE(writer.Write("", 0)); + EXPECT_TRUE(writer.string().empty()); + EXPECT_EQ(0, writer.Seek(0, SEEK_CUR)); +} + +TEST(StringFileWriter, OneByteFile) { + StringFileWriter writer; + + EXPECT_TRUE(writer.Write("a", 1)); + EXPECT_EQ(1u, writer.string().size()); + EXPECT_EQ("a", writer.string()); + EXPECT_EQ(1, writer.Seek(0, SEEK_CUR)); + + EXPECT_EQ(0, writer.Seek(0, SEEK_SET)); + EXPECT_TRUE(writer.Write("b", 1)); + EXPECT_EQ(1u, writer.string().size()); + EXPECT_EQ("b", writer.string()); + EXPECT_EQ(1, writer.Seek(0, SEEK_CUR)); + + EXPECT_EQ(0, writer.Seek(0, SEEK_SET)); + EXPECT_TRUE(writer.Write("\0", 1)); + EXPECT_EQ(1u, writer.string().size()); + EXPECT_EQ('\0', writer.string()[0]); + EXPECT_EQ(1, writer.Seek(0, SEEK_CUR)); +} + +TEST(StringFileWriter, Reset) { + StringFileWriter writer; + + EXPECT_TRUE(writer.Write("abc", 3)); + EXPECT_EQ(3u, writer.string().size()); + EXPECT_EQ("abc", writer.string()); + EXPECT_EQ(3, writer.Seek(0, SEEK_CUR)); + + writer.Reset(); + EXPECT_TRUE(writer.string().empty()); + EXPECT_EQ(0, writer.Seek(0, SEEK_CUR)); + + EXPECT_TRUE(writer.Write("de", 2)); + EXPECT_EQ(2u, writer.string().size()); + EXPECT_EQ("de", writer.string()); + EXPECT_EQ(2, writer.Seek(0, SEEK_CUR)); + + writer.Reset(); + EXPECT_TRUE(writer.string().empty()); + EXPECT_EQ(0, writer.Seek(0, SEEK_CUR)); + + EXPECT_TRUE(writer.Write("fghi", 4)); + EXPECT_EQ(4u, writer.string().size()); + EXPECT_EQ("fghi", writer.string()); + EXPECT_EQ(4, writer.Seek(0, SEEK_CUR)); + + writer.Reset(); + EXPECT_TRUE(writer.string().empty()); + EXPECT_EQ(0, writer.Seek(0, SEEK_CUR)); + + // Test resetting after a sparse write. + EXPECT_EQ(1, writer.Seek(1, SEEK_SET)); + EXPECT_TRUE(writer.Write("j", 1)); + EXPECT_EQ(2u, writer.string().size()); + EXPECT_EQ(std::string("\0j", 2), writer.string()); + EXPECT_EQ(2, writer.Seek(0, SEEK_CUR)); + + writer.Reset(); + EXPECT_TRUE(writer.string().empty()); + EXPECT_EQ(0, writer.Seek(0, SEEK_CUR)); +} + +TEST(StringFileWriter, WriteInvalid) { + StringFileWriter writer; + + EXPECT_EQ(0, writer.Seek(0, SEEK_CUR)); + + EXPECT_FALSE(writer.Write( + "", static_cast(std::numeric_limits::max()) + 1)); + EXPECT_TRUE(writer.string().empty()); + EXPECT_EQ(0, writer.Seek(0, SEEK_CUR)); + + EXPECT_TRUE(writer.Write("a", 1)); + EXPECT_FALSE(writer.Write( + "", static_cast(std::numeric_limits::max()))); + EXPECT_EQ(1u, writer.string().size()); + EXPECT_EQ("a", writer.string()); + EXPECT_EQ(1, writer.Seek(0, SEEK_CUR)); +} + +TEST(StringFileWriter, WriteIoVec) { + StringFileWriter writer; + + std::vector iovecs; + WritableIoVec iov; + iov.iov_base = ""; + iov.iov_len = 0; + iovecs.push_back(iov); + EXPECT_TRUE(writer.WriteIoVec(&iovecs)); + EXPECT_TRUE(writer.string().empty()); + EXPECT_EQ(0, writer.Seek(0, SEEK_CUR)); + + iovecs.clear(); + iov.iov_base = "a"; + iov.iov_len = 1; + iovecs.push_back(iov); + EXPECT_TRUE(writer.WriteIoVec(&iovecs)); + EXPECT_EQ(1u, writer.string().size()); + EXPECT_EQ("a", writer.string()); + EXPECT_EQ(1, writer.Seek(0, SEEK_CUR)); + + iovecs.clear(); + iovecs.push_back(iov); + EXPECT_TRUE(writer.WriteIoVec(&iovecs)); + EXPECT_EQ(2u, writer.string().size()); + EXPECT_EQ("aa", writer.string()); + EXPECT_EQ(2, writer.Seek(0, SEEK_CUR)); + + iovecs.clear(); + iovecs.push_back(iov); + iov.iov_base = "bc"; + iov.iov_len = 2; + iovecs.push_back(iov); + EXPECT_TRUE(writer.WriteIoVec(&iovecs)); + EXPECT_EQ(5u, writer.string().size()); + EXPECT_EQ("aaabc", writer.string()); + EXPECT_EQ(5, writer.Seek(0, SEEK_CUR)); + + EXPECT_TRUE(writer.Write("def", 3)); + EXPECT_EQ(8u, writer.string().size()); + EXPECT_EQ("aaabcdef", writer.string()); + EXPECT_EQ(8, writer.Seek(0, SEEK_CUR)); + + iovecs.clear(); + iov.iov_base = "ghij"; + iov.iov_len = 4; + iovecs.push_back(iov); + iov.iov_base = "klmno"; + iov.iov_len = 5; + iovecs.push_back(iov); + EXPECT_TRUE(writer.WriteIoVec(&iovecs)); + EXPECT_EQ(17u, writer.string().size()); + EXPECT_EQ("aaabcdefghijklmno", writer.string()); + EXPECT_EQ(17, writer.Seek(0, SEEK_CUR)); + + writer.Reset(); + EXPECT_TRUE(writer.string().empty()); + EXPECT_EQ(0, writer.Seek(0, SEEK_CUR)); + + iovecs.clear(); + iov.iov_base = "abcd"; + iov.iov_len = 4; + iovecs.resize(16, iov); + EXPECT_TRUE(writer.WriteIoVec(&iovecs)); + EXPECT_EQ(64u, writer.string().size()); + EXPECT_EQ("abcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd", + writer.string()); + EXPECT_EQ(64, writer.Seek(0, SEEK_CUR)); +} + +TEST(StringFileWriter, WriteIoVecInvalid) { + StringFileWriter writer; + + std::vector iovecs; + EXPECT_FALSE(writer.WriteIoVec(&iovecs)); + EXPECT_TRUE(writer.string().empty()); + EXPECT_EQ(0, writer.Seek(0, SEEK_CUR)); + + WritableIoVec iov; + EXPECT_EQ(1, writer.Seek(1, SEEK_CUR)); + iov.iov_base = "a"; + iov.iov_len = std::numeric_limits::max(); + iovecs.push_back(iov); + EXPECT_FALSE(writer.WriteIoVec(&iovecs)); + EXPECT_TRUE(writer.string().empty()); + EXPECT_EQ(1, writer.Seek(0, SEEK_CUR)); + + iovecs.clear(); + iov.iov_base = "a"; + iov.iov_len = 1; + iovecs.push_back(iov); + iov.iov_len = std::numeric_limits::max() - 1; + iovecs.push_back(iov); + EXPECT_FALSE(writer.WriteIoVec(&iovecs)); + EXPECT_TRUE(writer.string().empty()); + EXPECT_EQ(1, writer.Seek(0, SEEK_CUR)); +} + +TEST(StringFileWriter, Seek) { + StringFileWriter writer; + + EXPECT_TRUE(writer.Write("abcd", 4)); + EXPECT_EQ(4u, writer.string().size()); + EXPECT_EQ("abcd", writer.string()); + EXPECT_EQ(4, writer.Seek(0, SEEK_CUR)); + + EXPECT_EQ(0, writer.Seek(0, SEEK_SET)); + EXPECT_TRUE(writer.Write("efgh", 4)); + EXPECT_EQ(4u, writer.string().size()); + EXPECT_EQ("efgh", writer.string()); + EXPECT_EQ(4, writer.Seek(0, SEEK_CUR)); + + EXPECT_EQ(0, writer.Seek(0, SEEK_SET)); + EXPECT_TRUE(writer.Write("ijk", 3)); + EXPECT_EQ(4u, writer.string().size()); + EXPECT_EQ("ijkh", writer.string()); + EXPECT_EQ(3, writer.Seek(0, SEEK_CUR)); + + EXPECT_EQ(0, writer.Seek(0, SEEK_SET)); + EXPECT_TRUE(writer.Write("lmnop", 5)); + EXPECT_EQ(5u, writer.string().size()); + EXPECT_EQ("lmnop", writer.string()); + EXPECT_EQ(5, writer.Seek(0, SEEK_CUR)); + + EXPECT_EQ(1, writer.Seek(1, SEEK_SET)); + EXPECT_TRUE(writer.Write("q", 1)); + EXPECT_EQ(5u, writer.string().size()); + EXPECT_EQ("lqnop", writer.string()); + EXPECT_EQ(2, writer.Seek(0, SEEK_CUR)); + + EXPECT_EQ(1, writer.Seek(-1, SEEK_CUR)); + EXPECT_TRUE(writer.Write("r", 1)); + EXPECT_EQ(5u, writer.string().size()); + EXPECT_EQ("lrnop", writer.string()); + EXPECT_EQ(2, writer.Seek(0, SEEK_CUR)); + + EXPECT_TRUE(writer.Write("s", 1)); + EXPECT_EQ(5u, writer.string().size()); + EXPECT_EQ("lrsop", writer.string()); + EXPECT_EQ(3, writer.Seek(0, SEEK_CUR)); + + EXPECT_EQ(2, writer.Seek(-1, SEEK_CUR)); + EXPECT_TRUE(writer.Write("t", 1)); + EXPECT_EQ(5u, writer.string().size()); + EXPECT_EQ("lrtop", writer.string()); + EXPECT_EQ(3, writer.Seek(0, SEEK_CUR)); + + EXPECT_EQ(4, writer.Seek(-1, SEEK_END)); + EXPECT_TRUE(writer.Write("u", 1)); + EXPECT_EQ(5u, writer.string().size()); + EXPECT_EQ("lrtou", writer.string()); + EXPECT_EQ(5, writer.Seek(0, SEEK_CUR)); + + EXPECT_EQ(0, writer.Seek(-5, SEEK_END)); + EXPECT_TRUE(writer.Write("v", 1)); + EXPECT_EQ(5u, writer.string().size()); + EXPECT_EQ("vrtou", writer.string()); + EXPECT_EQ(1, writer.Seek(0, SEEK_CUR)); + + EXPECT_EQ(5, writer.Seek(0, SEEK_END)); + EXPECT_TRUE(writer.Write("w", 1)); + EXPECT_EQ(6u, writer.string().size()); + EXPECT_EQ("vrtouw", writer.string()); + EXPECT_EQ(6, writer.Seek(0, SEEK_CUR)); + + EXPECT_EQ(8, writer.Seek(2, SEEK_END)); + EXPECT_EQ(6u, writer.string().size()); + EXPECT_EQ("vrtouw", writer.string()); + + EXPECT_EQ(6, writer.Seek(0, SEEK_END)); + EXPECT_TRUE(writer.Write("x", 1)); + EXPECT_EQ(7u, writer.string().size()); + EXPECT_EQ("vrtouwx", writer.string()); + EXPECT_EQ(7, writer.Seek(0, SEEK_CUR)); +} + +TEST(StringFileWriter, SeekSparse) { + StringFileWriter writer; + + EXPECT_EQ(3, writer.Seek(3, SEEK_SET)); + EXPECT_TRUE(writer.string().empty()); + EXPECT_EQ(3, writer.Seek(0, SEEK_CUR)); + + EXPECT_TRUE(writer.Write("abc", 3)); + EXPECT_EQ(6u, writer.string().size()); + EXPECT_EQ(std::string("\0\0\0abc", 6), writer.string()); + EXPECT_EQ(6, writer.Seek(0, SEEK_CUR)); + + EXPECT_EQ(9, writer.Seek(3, SEEK_END)); + EXPECT_EQ(6u, writer.string().size()); + EXPECT_EQ(9, writer.Seek(0, SEEK_CUR)); + EXPECT_TRUE(writer.Write("def", 3)); + EXPECT_EQ(12u, writer.string().size()); + EXPECT_EQ(std::string("\0\0\0abc\0\0\0def", 12), writer.string()); + EXPECT_EQ(12, writer.Seek(0, SEEK_CUR)); + + EXPECT_EQ(7, writer.Seek(-5, SEEK_END)); + EXPECT_EQ(12u, writer.string().size()); + EXPECT_EQ(7, writer.Seek(0, SEEK_CUR)); + EXPECT_TRUE(writer.Write("g", 1)); + EXPECT_EQ(12u, writer.string().size()); + EXPECT_EQ(std::string("\0\0\0abc\0g\0def", 12), writer.string()); + EXPECT_EQ(8, writer.Seek(0, SEEK_CUR)); + + EXPECT_EQ(15, writer.Seek(7, SEEK_CUR)); + EXPECT_EQ(12u, writer.string().size()); + EXPECT_EQ(15, writer.Seek(0, SEEK_CUR)); + EXPECT_TRUE(writer.Write("hij", 3)); + EXPECT_EQ(18u, writer.string().size()); + EXPECT_EQ(std::string("\0\0\0abc\0g\0def\0\0\0hij", 18), writer.string()); + EXPECT_EQ(18, writer.Seek(0, SEEK_CUR)); + + EXPECT_EQ(1, writer.Seek(-17, SEEK_CUR)); + EXPECT_EQ(18u, writer.string().size()); + EXPECT_EQ(1, writer.Seek(0, SEEK_CUR)); + EXPECT_TRUE(writer.Write("k", 1)); + EXPECT_EQ(18u, writer.string().size()); + EXPECT_EQ(std::string("\0k\0abc\0g\0def\0\0\0hij", 18), writer.string()); + EXPECT_EQ(2, writer.Seek(0, SEEK_CUR)); + + EXPECT_TRUE(writer.Write("l", 1)); + EXPECT_TRUE(writer.Write("mnop", 4)); + EXPECT_EQ(18u, writer.string().size()); + EXPECT_EQ(std::string("\0klmnopg\0def\0\0\0hij", 18), writer.string()); + EXPECT_EQ(7, writer.Seek(0, SEEK_CUR)); +} + +TEST(StringFileWriter, SeekInvalid) { + StringFileWriter writer; + + EXPECT_EQ(0, writer.Seek(0, SEEK_CUR)); + EXPECT_EQ(1, writer.Seek(1, SEEK_SET)); + EXPECT_EQ(1, writer.Seek(0, SEEK_CUR)); + EXPECT_LT(writer.Seek(-1, SEEK_SET), 0); + EXPECT_EQ(1, writer.Seek(0, SEEK_CUR)); + EXPECT_LT(writer.Seek(std::numeric_limits::min(), SEEK_SET), 0); + EXPECT_EQ(1, writer.Seek(0, SEEK_CUR)); + EXPECT_LT(writer.Seek(std::numeric_limits::min(), SEEK_SET), 0); + EXPECT_EQ(1, writer.Seek(0, SEEK_CUR)); + EXPECT_TRUE(writer.string().empty()); + + COMPILE_ASSERT(SEEK_SET != 3 && SEEK_CUR != 3 && SEEK_END != 3, + three_must_be_invalid_for_whence); + EXPECT_LT(writer.Seek(0, 3), 0); + + writer.Reset(); + EXPECT_EQ(0, writer.Seek(0, SEEK_CUR)); + EXPECT_TRUE(writer.string().empty()); + + const off_t kMaxOffset = + std::min(static_cast(std::numeric_limits::max()), + static_cast(std::numeric_limits::max())); + + EXPECT_EQ(kMaxOffset, writer.Seek(kMaxOffset, SEEK_SET)); + EXPECT_EQ(kMaxOffset, writer.Seek(0, SEEK_CUR)); + EXPECT_LT(writer.Seek(1, SEEK_CUR), 0); + + EXPECT_EQ(1, writer.Seek(1, SEEK_SET)); + EXPECT_EQ(1, writer.Seek(0, SEEK_CUR)); + EXPECT_LT(writer.Seek(kMaxOffset, SEEK_CUR), 0); +} + +} // namespace diff --git a/util/numeric/safe_assignment.h b/util/numeric/safe_assignment.h new file mode 100644 index 00000000..05e0b2d9 --- /dev/null +++ b/util/numeric/safe_assignment.h @@ -0,0 +1,44 @@ +// Copyright 2014 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_UTIL_NUMERIC_SAFE_ASSIGNMENT_H_ +#define CRASHPAD_UTIL_NUMERIC_SAFE_ASSIGNMENT_H_ + +#include "base/numerics/safe_conversions.h" + +namespace crashpad { + +//! \brief Performs an assignment if it can be done safely, and signals if it +//! cannot be done safely. +//! +//! \param[out] destination A pointer to the variable to be assigned to. +//! \param[in] source The value to assign. +//! +//! \return `true` if \a source is in the range supported by the type of \a +//! *destination, with the assignment to \a *destination having been +//! performed. `false` if the assignment cannot be completed safely because +//! \a source is outside of this range. +template +bool AssignIfInRange(Destination* destination, Source source) { + if (!base::IsValueInRangeForNumericType(source)) { + return false; + } + + *destination = source; + return true; +} + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_NUMERIC_SAFE_ASSIGNMENT_H_ diff --git a/util/util.gyp b/util/util.gyp new file mode 100644 index 00000000..298a7398 --- /dev/null +++ b/util/util.gyp @@ -0,0 +1,55 @@ +# Copyright 2014 The Crashpad Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +{ + 'targets': [ + { + 'target_name': 'util', + 'type': 'static_library', + 'dependencies': [ + '../compat/compat.gyp:compat', + '../third_party/mini_chromium/mini_chromium/base/base.gyp:base', + ], + 'include_dirs': [ + '..', + '<(INTERMEDIATE_DIR)', + ], + 'sources': [ + 'file/fd_io.cc', + 'file/fd_io.h', + 'file/file_writer.cc', + 'file/file_writer.h', + 'file/string_file_writer.cc', + 'file/string_file_writer.h', + ], + }, + { + 'target_name': 'util_test', + 'type': 'executable', + 'dependencies': [ + 'util', + '../compat/compat.gyp:compat', + '../third_party/gtest/gtest.gyp:gtest', + '../third_party/mini_chromium/mini_chromium/base/base.gyp:base', + ], + 'include_dirs': [ + '..', + ], + 'sources': [ + '../third_party/gtest/gtest/src/gtest_main.cc', + 'file/string_file_writer_test.cc', + ], + }, + ], +}