diff --git a/util/linux/memory_map_test.cc b/util/linux/memory_map_test.cc index e78cb83a..9258f83e 100644 --- a/util/linux/memory_map_test.cc +++ b/util/linux/memory_map_test.cc @@ -21,6 +21,7 @@ #include #include "base/files/file_path.h" +#include "base/strings/stringprintf.h" #include "gtest/gtest.h" #include "test/errors.h" #include "test/file.h" @@ -28,6 +29,7 @@ #include "test/scoped_temp_dir.h" #include "util/file/file_io.h" #include "util/misc/clock.h" +#include "util/misc/from_pointer_cast.h" #include "util/posix/scoped_mmap.h" namespace crashpad { @@ -45,7 +47,7 @@ TEST(MemoryMap, SelfBasic) { MemoryMap map; ASSERT_TRUE(map.Initialize(getpid())); - auto stack_address = reinterpret_cast(&map); + auto stack_address = FromPointerCast(&map); const MemoryMap::Mapping* mapping = map.FindMapping(stack_address); ASSERT_TRUE(mapping); EXPECT_GE(stack_address, mapping->range.Base()); @@ -53,7 +55,7 @@ TEST(MemoryMap, SelfBasic) { EXPECT_TRUE(mapping->readable); EXPECT_TRUE(mapping->writable); - auto code_address = reinterpret_cast(getpid); + auto code_address = FromPointerCast(getpid); mapping = map.FindMapping(code_address); ASSERT_TRUE(mapping); EXPECT_GE(code_address, mapping->range.Base()); @@ -146,10 +148,10 @@ class MapChildTest : public Multiprocess { } void MultiprocessChild() override { - auto code_address = reinterpret_cast(getpid); + auto code_address = FromPointerCast(getpid); CheckedWriteFile(WritePipeHandle(), &code_address, sizeof(code_address)); - auto stack_address = reinterpret_cast(&code_address); + auto stack_address = FromPointerCast(&code_address); CheckedWriteFile(WritePipeHandle(), &stack_address, sizeof(stack_address)); ScopedMmap mapping; @@ -227,6 +229,8 @@ void ExpectMappings(const MemoryMap& map, size_t num_mappings, size_t mapping_size) { for (size_t index = 0; index < num_mappings; ++index) { + SCOPED_TRACE(base::StringPrintf("index %zu", index)); + auto mapping_address = region_addr + index * mapping_size; const MemoryMap::Mapping* mapping = map.FindMapping(mapping_address); ASSERT_TRUE(mapping); @@ -269,10 +273,12 @@ class MapRunningChildTest : public Multiprocess { LinuxVMAddress region_addr; CheckedReadFileExactly(ReadPipeHandle(), ®ion_addr, sizeof(region_addr)); - // Let the child get back to its work - SleepNanoseconds(1000); - for (int iter = 0; iter < 8; ++iter) { + SCOPED_TRACE(base::StringPrintf("iter %d", iter)); + + // Let the child get back to its work + SleepNanoseconds(1000); + MemoryMap map; ASSERT_TRUE(map.Initialize(ChildPID())); diff --git a/util/linux/process_memory_test.cc b/util/linux/process_memory_test.cc index e00134a7..21377553 100644 --- a/util/linux/process_memory_test.cc +++ b/util/linux/process_memory_test.cc @@ -24,6 +24,7 @@ #include "test/errors.h" #include "test/multiprocess.h" #include "util/file/file_io.h" +#include "util/misc/from_pointer_cast.h" #include "util/posix/scoped_mmap.h" namespace crashpad { @@ -66,7 +67,7 @@ class ReadTest : public TargetProcessTest { ProcessMemory memory; ASSERT_TRUE(memory.Initialize(pid)); - LinuxVMAddress address = reinterpret_cast(region_.get()); + LinuxVMAddress address = FromPointerCast(region_.get()); std::unique_ptr result(new char[region_size_]); // Ensure that the entire region can be read. @@ -123,7 +124,7 @@ TEST(ProcessMemory, ReadForked) { bool ReadCString(const ProcessMemory& memory, const char* pointer, std::string* result) { - return memory.ReadCString(reinterpret_cast(pointer), result); + return memory.ReadCString(FromPointerCast(pointer), result); } bool ReadCStringSizeLimited(const ProcessMemory& memory, @@ -131,7 +132,7 @@ bool ReadCStringSizeLimited(const ProcessMemory& memory, size_t size, std::string* result) { return memory.ReadCStringSizeLimited( - reinterpret_cast(pointer), size, result); + FromPointerCast(pointer), size, result); } const char kConstCharEmpty[] = ""; diff --git a/util/misc/from_pointer_cast.h b/util/misc/from_pointer_cast.h new file mode 100644 index 00000000..3f42447d --- /dev/null +++ b/util/misc/from_pointer_cast.h @@ -0,0 +1,91 @@ +// Copyright 2017 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_MISC_FROM_POINTER_CAST_H_ +#define CRASHPAD_UTIL_MISC_FROM_POINTER_CAST_H_ + +#include + +#include +#include + +namespace crashpad { + +#if DOXYGEN + +//! \brief Casts from a pointer type to an integer. +//! +//! Compared to `reinterpret_cast<>()`, FromPointerCast<>() defines whether a +//! pointer type is sign-extended or zero-extended. Casts to signed integral +//! types are sign-extended. Casts to unsigned integral types are zero-extended. +template +FromPointerCast(const From from) { + return reinterpret_cast(from); +} + +#else // DOXYGEN + +// Cast std::nullptr_t to any pointer type. +// +// In C++14, the nullptr_t check could use std::is_null_pointer::type +// instead of the is_same::type, nullptr_t>::type construct. +template +typename std::enable_if< + std::is_same::type, std::nullptr_t>::value && + std::is_pointer::value, + To>::type +FromPointerCast(const From& from) { + return static_cast(from); +} + +// Cast std::nullptr_t to any integral type. +// +// In C++14, the nullptr_t check could use std::is_null_pointer::type +// instead of the is_same::type, nullptr_t>::type construct. +template +typename std::enable_if< + std::is_same::type, std::nullptr_t>::value && + std::is_integral::value, + To>::type +FromPointerCast(const From& from) { + return reinterpret_cast(from); +} + +// Cast a pointer to any other pointer type. +template +typename std::enable_if::value && + std::is_pointer::value, + To>::type +FromPointerCast(const From from) { + return reinterpret_cast(from); +} + +// Cast a pointer to an integral type. Sign-extend when casting to a signed +// type, zero-extend when casting to an unsigned type. +template +typename std::enable_if::value && + std::is_integral::value, + To>::type +FromPointerCast(const From from) { + return static_cast( + reinterpret_cast::value, + intptr_t, + uintptr_t>::type>(from)); +} + +#endif // DOXYGEN + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_MISC_FROM_POINTER_CAST_H_ diff --git a/util/misc/from_pointer_cast_test.cc b/util/misc/from_pointer_cast_test.cc new file mode 100644 index 00000000..0c90c275 --- /dev/null +++ b/util/misc/from_pointer_cast_test.cc @@ -0,0 +1,190 @@ +// Copyright 2017 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/misc/from_pointer_cast.h" + +#include +#include + +#include + +#include "build/build_config.h" +#include "gtest/gtest.h" + +namespace crashpad { +namespace test { +namespace { + +struct SomeType {}; + +template +class FromPointerCastTest : public testing::Test {}; + +using FromPointerCastTestTypes = testing::Types; + +TYPED_TEST_CASE(FromPointerCastTest, FromPointerCastTestTypes); + +TYPED_TEST(FromPointerCastTest, ToSigned) { + EXPECT_EQ(FromPointerCast(nullptr), 0); + EXPECT_EQ(FromPointerCast(reinterpret_cast(1)), 1); + EXPECT_EQ(FromPointerCast(reinterpret_cast(-1)), -1); + EXPECT_EQ(FromPointerCast(reinterpret_cast( + std::numeric_limits::max())), + static_cast(std::numeric_limits::max())); + EXPECT_EQ(FromPointerCast(reinterpret_cast( + std::numeric_limits::min())), + std::numeric_limits::min()); + EXPECT_EQ(FromPointerCast(reinterpret_cast( + std::numeric_limits::max())), + std::numeric_limits::max()); +} + +TYPED_TEST(FromPointerCastTest, ToUnsigned) { + EXPECT_EQ(FromPointerCast(nullptr), 0u); + EXPECT_EQ(FromPointerCast(reinterpret_cast(1)), 1u); + EXPECT_EQ(FromPointerCast(reinterpret_cast(-1)), + static_cast(-1)); + EXPECT_EQ(FromPointerCast(reinterpret_cast( + std::numeric_limits::max())), + std::numeric_limits::max()); + EXPECT_EQ(FromPointerCast(reinterpret_cast( + std::numeric_limits::min())), + static_cast(std::numeric_limits::min())); + EXPECT_EQ(FromPointerCast(reinterpret_cast( + std::numeric_limits::max())), + static_cast(std::numeric_limits::max())); +} + +// MatchCV::Type adapts YourType to match the +// const/void qualification of CVQualifiedType. +template +struct MatchCV { + private: + using NonCVBase = typename std::remove_cv::type; + + public: + using Type = typename std::conditional< + std::is_const::value, + typename std::conditional::value, + const volatile NonCVBase, + const NonCVBase>::type, + typename std::conditional::value, + volatile NonCVBase, + NonCVBase>::type>::type; +}; + +#if defined(COMPILER_MSVC) && _MSC_VER < 1910 +// gtest under MSVS 2015 (MSC 19.0) doesn’t handle EXPECT_EQ(a, b) when a or b +// is a pointer to a volatile type, because it can’t figure out how to print +// them. +template +typename std::remove_volatile::type>::type* +MaybeRemoveVolatile(const T& value) { + return const_cast::type>::type*>(value); +} +#else // COMPILER_MSVC && _MSC_VER < 1910 +// This isn’t a problem in MSVS 2017 (MSC 19.1) or with other compilers. +template +T MaybeRemoveVolatile(const T& value) { + return value; +} +#endif // COMPILER_MSVC && _MSC_VER < 1910 + +TYPED_TEST(FromPointerCastTest, ToPointer) { + using CVSomeType = + typename MatchCV::type>::Type; + + EXPECT_EQ(MaybeRemoveVolatile(FromPointerCast(nullptr)), + MaybeRemoveVolatile(static_cast(nullptr))); + EXPECT_EQ(MaybeRemoveVolatile( + FromPointerCast(reinterpret_cast(1))), + MaybeRemoveVolatile(reinterpret_cast(1))); + EXPECT_EQ(MaybeRemoveVolatile( + FromPointerCast(reinterpret_cast(-1))), + MaybeRemoveVolatile(reinterpret_cast(-1))); + EXPECT_EQ( + MaybeRemoveVolatile(FromPointerCast( + reinterpret_cast(std::numeric_limits::max()))), + MaybeRemoveVolatile(reinterpret_cast( + std::numeric_limits::max()))); + EXPECT_EQ( + MaybeRemoveVolatile(FromPointerCast( + reinterpret_cast(std::numeric_limits::min()))), + MaybeRemoveVolatile( + reinterpret_cast(std::numeric_limits::min()))); + EXPECT_EQ( + MaybeRemoveVolatile(FromPointerCast( + reinterpret_cast(std::numeric_limits::max()))), + MaybeRemoveVolatile( + reinterpret_cast(std::numeric_limits::max()))); +} + +TEST(FromPointerCast, FromFunctionPointer) { + // These casts should work with or without the & in &malloc. + + EXPECT_NE(FromPointerCast(malloc), 0); + EXPECT_NE(FromPointerCast(&malloc), 0); + + EXPECT_NE(FromPointerCast(malloc), 0u); + EXPECT_NE(FromPointerCast(&malloc), 0u); + + EXPECT_EQ(FromPointerCast(malloc), reinterpret_cast(malloc)); + EXPECT_EQ(FromPointerCast(&malloc), reinterpret_cast(malloc)); + EXPECT_EQ(FromPointerCast(malloc), + reinterpret_cast(malloc)); + EXPECT_EQ(FromPointerCast(&malloc), + reinterpret_cast(malloc)); + EXPECT_EQ(MaybeRemoveVolatile(FromPointerCast(malloc)), + MaybeRemoveVolatile(reinterpret_cast(malloc))); + EXPECT_EQ(MaybeRemoveVolatile(FromPointerCast(&malloc)), + MaybeRemoveVolatile(reinterpret_cast(malloc))); + EXPECT_EQ( + MaybeRemoveVolatile(FromPointerCast(malloc)), + MaybeRemoveVolatile(reinterpret_cast(malloc))); + EXPECT_EQ( + MaybeRemoveVolatile(FromPointerCast(&malloc)), + MaybeRemoveVolatile(reinterpret_cast(malloc))); + + EXPECT_EQ(FromPointerCast(malloc), + reinterpret_cast(malloc)); + EXPECT_EQ(FromPointerCast(&malloc), + reinterpret_cast(malloc)); + EXPECT_EQ(FromPointerCast(malloc), + reinterpret_cast(malloc)); + EXPECT_EQ(FromPointerCast(&malloc), + reinterpret_cast(malloc)); + EXPECT_EQ(MaybeRemoveVolatile(FromPointerCast(malloc)), + MaybeRemoveVolatile(reinterpret_cast(malloc))); + EXPECT_EQ(MaybeRemoveVolatile(FromPointerCast(&malloc)), + MaybeRemoveVolatile(reinterpret_cast(malloc))); + EXPECT_EQ( + MaybeRemoveVolatile(FromPointerCast(malloc)), + MaybeRemoveVolatile(reinterpret_cast(malloc))); + EXPECT_EQ( + MaybeRemoveVolatile(FromPointerCast(&malloc)), + MaybeRemoveVolatile(reinterpret_cast(malloc))); +} + +} // namespace +} // namespace test +} // namespace crashpad diff --git a/util/posix/scoped_mmap.h b/util/posix/scoped_mmap.h index 5b216dee..b0ff3dc0 100644 --- a/util/posix/scoped_mmap.h +++ b/util/posix/scoped_mmap.h @@ -20,6 +20,8 @@ #include #include +#include "util/misc/from_pointer_cast.h" + namespace crashpad { //! \brief Maintains a memory-mapped region created by `mmap()`. @@ -85,7 +87,7 @@ class ScopedMmap { //! a type of the caller’s choosing. template T addr_as() const { - return reinterpret_cast(addr_); + return FromPointerCast(addr_); } //! \brief Returns the size of the memory-mapped region. diff --git a/util/util.gyp b/util/util.gyp index 5a7947a1..e3074e9a 100644 --- a/util/util.gyp +++ b/util/util.gyp @@ -101,6 +101,7 @@ 'misc/clock_mac.cc', 'misc/clock_posix.cc', 'misc/clock_win.cc', + 'misc/from_pointer_cast.h', 'misc/implicit_cast.h', 'misc/initialization_state.h', 'misc/initialization_state_dcheck.cc', diff --git a/util/util_test.gyp b/util/util_test.gyp index 6ee35258..db54a9b0 100644 --- a/util/util_test.gyp +++ b/util/util_test.gyp @@ -63,6 +63,7 @@ 'mach/task_memory_test.cc', 'misc/arraysize_unsafe_test.cc', 'misc/clock_test.cc', + 'misc/from_pointer_cast_test.cc', 'misc/initialization_state_dcheck_test.cc', 'misc/initialization_state_test.cc', 'misc/paths_test.cc',