From a01c87059b37117f6e2a8060fea964ee493be9eb Mon Sep 17 00:00:00 2001 From: Mark Mentovai Date: Wed, 10 Sep 2014 17:30:21 -0400 Subject: [PATCH] Add symbolic_constants_posix and its test. This includes the functions SignalToString() and StringToSignal(). TEST=util_test SymbolicConstantsPOSIX.* R=rsesek@chromium.org Review URL: https://codereview.chromium.org/565453002 --- util/misc/symbolic_constants_common.h | 132 ++++++++++ util/posix/symbolic_constants_posix.cc | 166 +++++++++++++ util/posix/symbolic_constants_posix.h | 48 ++++ util/posix/symbolic_constants_posix_test.cc | 253 ++++++++++++++++++++ util/util.gyp | 4 + 5 files changed, 603 insertions(+) create mode 100644 util/misc/symbolic_constants_common.h create mode 100644 util/posix/symbolic_constants_posix.cc create mode 100644 util/posix/symbolic_constants_posix.h create mode 100644 util/posix/symbolic_constants_posix_test.cc diff --git a/util/misc/symbolic_constants_common.h b/util/misc/symbolic_constants_common.h new file mode 100644 index 00000000..fe4a3ba8 --- /dev/null +++ b/util/misc/symbolic_constants_common.h @@ -0,0 +1,132 @@ +// 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_MISC_SYMBOLIC_CONSTANTS_COMMON_H_ +#define CRASHPAD_UTIL_MISC_SYMBOLIC_CONSTANTS_COMMON_H_ + +//! \file +//! +//! \anchor symbolic_constant_terminology +//! Symbolic constant terminology +//! ============================= +//!
+//!
Family
+//!
A group of related symbolic constants. Typically, within a single +//! family, one function will be used to transform a numeric value to a +//! string equivalent, and another will perform the inverse operation. +//! Families include POSIX signals and Mach exception masks.
+//!
Full name
+//!
The normal symbolic name used for a constant. For example, in the +//! family of POSIX signals, the strings `"SIGHUP"` and `"SIGSEGV"` are +//! full names.
+//!
Short name
+//!
An abbreviated form of symbolic name used for a constant. Short names +//! vary between families, but are commonly constructed by removing a +//! common prefix from full names. For example, in the family of POSIX +//! signals, the prefix is `SIG`, and short names include `"HUP"` and +//! `"SEGV"`.
+//!
Numeric string
+//!
A string that does not contain a full or short name, but contains a +//! numeric value that can be interpreted as a symbolic constant. For +//! example, in the family of POSIX signals, `SIGKILL` generally has value +//! `9`, so the numeric string `"9"` would be interpreted equivalently to +//! `"SIGKILL"`.
+//!
+ +namespace crashpad { + +//! \brief Options for various `*ToString` functions in `symbolic_constants_*` +//! files. +//! +//! \sa \ref symbolic_constant_terminology "Symbolic constant terminology" +enum SymbolicConstantToStringOptionBits { + //! \brief Return the full name for a given constant. + //! + //! \attention API consumers should provide this value when desired, but + //! should provide only one of kUseFullName and ::kUseShortName. Because + //! kUseFullName is valueless, implementers should check for the absence + //! of ::kUseShortName instead. + kUseFullName = 0 << 0, + + //! \brief Return the short name for a given constant. + kUseShortName = 1 << 0, + + //! \brief If no symbolic name is known for a given constant, return an empty + //! string. + //! + //! \attention API consumers should provide this value when desired, but + //! should provide only one of kUnknownIsEmpty and ::kUnknownIsNumeric. + //! Because kUnknownIsEmpty is valueless, implementers should check for + //! the absence of ::kUnknownIsNumeric instead. + kUnknownIsEmpty = 0 << 1, + + //! \brief If no symbolic name is known for a given constant, return a numeric + //! string. + //! + //! The numeric format used will vary by family, but will be appropriate to + //! the family. Families whose values are typically constructed as bitfields + //! will generally use a hexadecimal format, and other families will generally + //! use a signed or unsigned decimal format. + kUnknownIsNumeric = 1 << 1, + + //! \brief Use `|` to combine values in a bitfield. + //! + //! For families whose values may be constructed as bitfields, allow + //! conversion to strings containing multiple individual components treated as + //! being combined by a bitwise “or” operation. An example family of constants + //! that behaves this way is the suite of Mach exception masks. For constants + //! that are not constructed as bitfields, or constants that are only + //! partially constructed as bitfields, this option has no effect. + kUseOr = 1 << 2, +}; + +//! \brief A bitfield containing values of #SymbolicConstantToStringOptionBits. +typedef unsigned int SymbolicConstantToStringOptions; + +//! \brief Options for various `StringTo*` functions in `symbolic_constants_*` +//! files. +//! +//! Not every `StringTo*` function will implement each of these options. See +//! function-specific documentation for details. +//! +//! \sa \ref symbolic_constant_terminology "Symbolic constant terminology" +enum StringToSymbolicConstantOptionBits { + //! \brief Allow conversion from a string containing a symbolic constant by + //! its full name. + kAllowFullName = 1 << 0, + + //! \brief Allow conversion from a string containing a symbolic constant by + //! its short name. + kAllowShortName = 1 << 1, + + //! \brief Allow conversion from a numeric string. + kAllowNumber = 1 << 2, + + //! \brief Allow `|` to combine values in a bitfield. + //! + //! For families whose values may be constructed as bitfields, allow + //! conversion of strings containing multiple individual components treated as + //! being combined by a bitwise “or” operation. An example family of constants + //! that behaves this way is the suite of Mach exception masks. For constants + //! that are not constructed as bitfields, or constants that are only + //! partially constructed as bitfields, this option has no effect. + kAllowOr = 1 << 3, +}; + +//! \brief A bitfield containing values of #StringToSymbolicConstantOptionBits. +typedef unsigned int StringToSymbolicConstantOptions; + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_MISC_SYMBOLIC_CONSTANTS_COMMON_H_ diff --git a/util/posix/symbolic_constants_posix.cc b/util/posix/symbolic_constants_posix.cc new file mode 100644 index 00000000..6846c5d2 --- /dev/null +++ b/util/posix/symbolic_constants_posix.cc @@ -0,0 +1,166 @@ +// 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/posix/symbolic_constants_posix.h" + +#include + +#include "base/basictypes.h" +#include "base/strings/stringprintf.h" +#include "util/stdlib/string_number_conversion.h" + +namespace { + +const char* kSignalNames[] = { + NULL, + +#if defined(OS_MACOSX) + // sed -Ene 's/^#define[[:space:]]SIG([[:alnum:]]+)[[:space:]]+[[:digit:]]{1,2}([[:space:]]|$).*/ "\1",/p' + // /usr/include/sys/signal.h + // and fix up by removing the entry for SIGPOLL. + "HUP", + "INT", + "QUIT", + "ILL", + "TRAP", + "ABRT", + "EMT", + "FPE", + "KILL", + "BUS", + "SEGV", + "SYS", + "PIPE", + "ALRM", + "TERM", + "URG", + "STOP", + "TSTP", + "CONT", + "CHLD", + "TTIN", + "TTOU", + "IO", + "XCPU", + "XFSZ", + "VTALRM", + "PROF", + "WINCH", + "INFO", + "USR1", + "USR2", +#elif defined(OS_LINUX) + // sed -Ene 's/^#define[[:space:]]SIG([[:alnum:]]+)[[:space:]]+[[:digit:]]{1,2}([[:space:]]|$).*/ "\1",/p' + // /usr/include/asm-generic/signal.h + // and fix up by removing SIGIOT, SIGLOST, SIGUNUSED, and SIGRTMIN. + "HUP", + "INT", + "QUIT", + "ILL", + "TRAP", + "ABRT", + "BUS", + "FPE", + "KILL", + "USR1", + "SEGV", + "USR2", + "PIPE", + "ALRM", + "TERM", + "STKFLT", + "CHLD", + "CONT", + "STOP", + "TSTP", + "TTIN", + "TTOU", + "URG", + "XCPU", + "XFSZ", + "VTALRM", + "PROF", + "WINCH", + "IO", + "PWR", + "SYS", +#endif +}; +#if defined(OS_LINUX) +// NSIG is 64 to account for real-time signals. +COMPILE_ASSERT(arraysize(kSignalNames) == 32, kSignalNames_length); +#else +COMPILE_ASSERT(arraysize(kSignalNames) == NSIG, kSignalNames_length); +#endif + +const char kSigPrefix[] = "SIG"; + +} // namespace + +namespace crashpad { + +std::string SignalToString(int signal, + SymbolicConstantToStringOptions options) { + const char* signal_name = + static_cast(signal) < arraysize(kSignalNames) + ? kSignalNames[signal] + : NULL; + if (!signal_name) { + if (options & kUnknownIsNumeric) { + return base::StringPrintf("%d", signal); + } + return std::string(); + } + + if (options & kUseShortName) { + return std::string(signal_name); + } + return base::StringPrintf("%s%s", kSigPrefix, signal_name); +} + +bool StringToSignal(const base::StringPiece& string, + StringToSymbolicConstantOptions options, + int* signal) { + if ((options & kAllowFullName) || (options & kAllowShortName)) { + bool can_match_full = + (options & kAllowFullName) && + string.substr(0, strlen(kSigPrefix)).compare(kSigPrefix) == 0; + base::StringPiece short_string = + can_match_full ? string.substr(strlen(kSigPrefix)) : string; + for (int index = 0; + index < static_cast(arraysize(kSignalNames)); + ++index) { + const char* signal_name = kSignalNames[index]; + if (!signal_name) { + continue; + } + if (can_match_full && short_string.compare(signal_name) == 0) { + *signal = index; + return true; + } + if ((options & kAllowShortName) && string.compare(signal_name) == 0) { + *signal = index; + return true; + } + } + } + + if (options & kAllowNumber) { + return StringToNumber(string, signal); + } + + return false; +} + +} // namespace crashpad diff --git a/util/posix/symbolic_constants_posix.h b/util/posix/symbolic_constants_posix.h new file mode 100644 index 00000000..4fb4a358 --- /dev/null +++ b/util/posix/symbolic_constants_posix.h @@ -0,0 +1,48 @@ +// 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_POSIX_SYMBOLIC_CONSTANTS_POSIX_H_ +#define CRASHPAD_UTIL_POSIX_SYMBOLIC_CONSTANTS_POSIX_H_ + +#include + +#include "base/strings/string_piece.h" +#include "util/misc/symbolic_constants_common.h" + +namespace crashpad { + +//! \brief Converts a POSIX signal value to a textual representation. +//! +//! \param[in] signal The signal value to convert. +//! \param[in] options Options affecting the conversion. ::kUseOr is ignored. +//! For ::kUnknownIsNumeric, the format is `"%d"`. +//! +//! \return The converted string. +std::string SignalToString(int signal, SymbolicConstantToStringOptions options); + +//! \brief Converts a string to its corresponding POSIX signal value. +//! +//! \param[in] string The string to convert. +//! \param[in] options Options affecting the conversion. ::kAllowOr is ignored. +//! \param[out] signal The converted POSIX signal value. +//! +//! \return `true` on success, `false` if \a string could not be converted as +//! requested. +bool StringToSignal(const base::StringPiece& string, + StringToSymbolicConstantOptions options, + int* signal); + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_POSIX_SYMBOLIC_CONSTANTS_POSIX_H_ diff --git a/util/posix/symbolic_constants_posix_test.cc b/util/posix/symbolic_constants_posix_test.cc new file mode 100644 index 00000000..7cceaefd --- /dev/null +++ b/util/posix/symbolic_constants_posix_test.cc @@ -0,0 +1,253 @@ +// 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/posix/symbolic_constants_posix.h" + +#include + +#include "base/basictypes.h" +#include "base/strings/string_piece.h" +#include "base/strings/stringprintf.h" +#include "gtest/gtest.h" + +namespace { + +using namespace crashpad; + +const struct { + int signal; + const char* full_name; + const char* short_name; +} kSignalTestData[] = { + {SIGABRT, "SIGABRT", "ABRT"}, + {SIGALRM, "SIGALRM", "ALRM"}, + {SIGBUS, "SIGBUS", "BUS"}, + {SIGCHLD, "SIGCHLD", "CHLD"}, + {SIGCONT, "SIGCONT", "CONT"}, + {SIGFPE, "SIGFPE", "FPE"}, + {SIGHUP, "SIGHUP", "HUP"}, + {SIGILL, "SIGILL", "ILL"}, + {SIGINT, "SIGINT", "INT"}, + {SIGIO, "SIGIO", "IO"}, + {SIGKILL, "SIGKILL", "KILL"}, + {SIGPIPE, "SIGPIPE", "PIPE"}, + {SIGPROF, "SIGPROF", "PROF"}, + {SIGQUIT, "SIGQUIT", "QUIT"}, + {SIGSEGV, "SIGSEGV", "SEGV"}, + {SIGSTOP, "SIGSTOP", "STOP"}, + {SIGSYS, "SIGSYS", "SYS"}, + {SIGTERM, "SIGTERM", "TERM"}, + {SIGTRAP, "SIGTRAP", "TRAP"}, + {SIGTSTP, "SIGTSTP", "TSTP"}, + {SIGTTIN, "SIGTTIN", "TTIN"}, + {SIGTTOU, "SIGTTOU", "TTOU"}, + {SIGURG, "SIGURG", "URG"}, + {SIGUSR1, "SIGUSR1", "USR1"}, + {SIGUSR2, "SIGUSR2", "USR2"}, + {SIGVTALRM, "SIGVTALRM", "VTALRM"}, + {SIGWINCH, "SIGWINCH", "WINCH"}, + {SIGXCPU, "SIGXCPU", "XCPU"}, +#if defined(OS_MACOSX) + {SIGEMT, "SIGEMT", "EMT"}, + {SIGINFO, "SIGINFO", "INFO"}, +#elif defined(OS_LINUX) + {SIGPWR, "SIGPWR", "PWR"}, + {SIGSTKFLT, "SIGSTKFLT", "STKFLT"}, +#endif +}; + +// If expect is NULL, the conversion is expected to fail. If expect is empty, +// the conversion is expected to succeed, but the precise returned string value +// is unknown. Otherwise, the conversion is expected to succeed, and expect +// contains the precise expected string value to be returned. +// +// Only set kUseFullName or kUseShortName when calling this. Other options are +// exercised directly by this function. +void TestSignalToStringOnce(int value, + const char* expect, + SymbolicConstantToStringOptions options) { + std::string actual = SignalToString(value, options | kUnknownIsEmpty); + std::string actual_numeric = + SignalToString(value, options | kUnknownIsNumeric); + if (expect) { + if (expect[0] == '\0') { + EXPECT_FALSE(actual.empty()) << "signal " << value; + } else { + EXPECT_EQ(expect, actual) << "signal " << value; + } + EXPECT_EQ(actual, actual_numeric) << "signal " << value; + } else { + EXPECT_TRUE(actual.empty()) << "signal " << value << ", actual " << actual; + EXPECT_FALSE(actual_numeric.empty()) + << "signal " << value << ", actual_numeric " << actual_numeric; + } +} + +void TestSignalToString(int value, + const char* expect_full, + const char* expect_short) { + { + SCOPED_TRACE("full_name"); + TestSignalToStringOnce(value, expect_full, kUseFullName); + } + + { + SCOPED_TRACE("short_name"); + TestSignalToStringOnce(value, expect_short, kUseShortName); + } +} + +TEST(SymbolicConstantsPOSIX, SignalToString) { + for (size_t index = 0; index < arraysize(kSignalTestData); ++index) { + SCOPED_TRACE(base::StringPrintf("index %zu", index)); + TestSignalToString(kSignalTestData[index].signal, + kSignalTestData[index].full_name, + kSignalTestData[index].short_name); + } + +#if defined(OS_LINUX) + // NSIG is 64 to account for real-time signals. + const int kSignalCount = 32; +#else + const int kSignalCount = NSIG; +#endif + + for (int signal = 0; signal < kSignalCount + 8; ++signal) { + SCOPED_TRACE(base::StringPrintf("signal %d", signal)); + if (signal > 0 && signal < kSignalCount) { + TestSignalToString(signal, "", ""); + } else { + TestSignalToString(signal, NULL, NULL); + } + } +} + +void TestStringToSignal(const base::StringPiece& string, + StringToSymbolicConstantOptions options, + bool expect_result, + int expect_value) { + int actual_value; + bool actual_result = StringToSignal(string, options, &actual_value); + if (expect_result) { + EXPECT_TRUE(actual_result) << "string " << string << ", options " << options + << ", signal " << expect_value; + if (actual_result) { + EXPECT_EQ(expect_value, actual_value) << "string " << string + << ", options " << options; + } + } else { + EXPECT_FALSE(actual_result) << "string " << string << ", options " + << options << ", signal " << actual_value; + } +} + +TEST(SymbolicConstantsPOSIX, StringToSignal) { + const StringToSymbolicConstantOptions kOptions[] = { + 0, + kAllowFullName, + kAllowShortName, + kAllowFullName | kAllowShortName, + kAllowNumber, + kAllowFullName | kAllowNumber, + kAllowShortName | kAllowNumber, + kAllowFullName | kAllowShortName | kAllowNumber, + }; + + for (size_t option_index = 0; option_index < arraysize(kOptions); + ++option_index) { + SCOPED_TRACE(base::StringPrintf("option_index %zu", option_index)); + StringToSymbolicConstantOptions options = kOptions[option_index]; + for (size_t index = 0; index < arraysize(kSignalTestData); ++index) { + SCOPED_TRACE(base::StringPrintf("index %zu", index)); + int signal = kSignalTestData[index].signal; + { + SCOPED_TRACE("full_name"); + TestStringToSignal(kSignalTestData[index].full_name, + options, + options & kAllowFullName, + signal); + } + { + SCOPED_TRACE("short_name"); + TestStringToSignal(kSignalTestData[index].short_name, + options, + options & kAllowShortName, + signal); + } + { + SCOPED_TRACE("number"); + std::string number_string = base::StringPrintf("%d", signal); + TestStringToSignal( + number_string, options, options & kAllowNumber, signal); + } + } + + const char* const kNegativeTestData[] = { + "SIGHUP ", + " SIGINT", + "QUIT ", + " ILL", + "SIGSIGTRAP", + "SIGABRTRON", + "FPES", + "SIGGARBAGE", + "random", + "", + }; + + for (size_t index = 0; index < arraysize(kNegativeTestData); ++index) { + SCOPED_TRACE(base::StringPrintf("index %zu", index)); + TestStringToSignal(kNegativeTestData[index], options, false, 0); + } + + const struct { + const char* string; + size_t length; + } kNULTestData[] = { +#define NUL_TEST_DATA(string) { string, arraysize(string) - 1 } + NUL_TEST_DATA("\0SIGBUS"), + NUL_TEST_DATA("SIG\0BUS"), + NUL_TEST_DATA("SIGB\0US"), + NUL_TEST_DATA("SIGBUS\0"), + NUL_TEST_DATA("\0BUS"), + NUL_TEST_DATA("BUS\0"), + NUL_TEST_DATA("B\0US"), + NUL_TEST_DATA("\0002"), + NUL_TEST_DATA("2\0"), + NUL_TEST_DATA("1\0002"), +#undef NUL_TEST_DATA + }; + + for (size_t index = 0; index < arraysize(kNULTestData); ++index) { + SCOPED_TRACE(base::StringPrintf("index %zu", index)); + base::StringPiece string(kNULTestData[index].string, + kNULTestData[index].length); + TestStringToSignal(string, options, false, 0); + } + } + + // Ensure that a NUL is not required at the end of the string. + { + SCOPED_TRACE("trailing_NUL_full"); + TestStringToSignal( + base::StringPiece("SIGBUST", 6), kAllowFullName, true, SIGBUS); + } + { + SCOPED_TRACE("trailing_NUL_short"); + TestStringToSignal( + base::StringPiece("BUST", 3), kAllowShortName, true, SIGBUS); + } +} + +} // namespace diff --git a/util/util.gyp b/util/util.gyp index 7e242edb..0a8aaa0f 100644 --- a/util/util.gyp +++ b/util/util.gyp @@ -73,6 +73,7 @@ 'misc/initialization_state_dcheck.h', 'misc/scoped_forbid_return.cc', 'misc/scoped_forbid_return.h', + 'misc/symbolic_constants_common.h', 'misc/uuid.cc', 'misc/uuid.h', 'numeric/checked_range.h', @@ -80,6 +81,8 @@ 'numeric/safe_assignment.h', 'posix/process_util.h', 'posix/process_util_mac.cc', + 'posix/symbolic_constants_posix.cc', + 'posix/symbolic_constants_posix.h', 'stdlib/cxx.h', 'stdlib/objc.h', 'stdlib/pointer_container.h', @@ -199,6 +202,7 @@ 'numeric/checked_range_test.cc', 'numeric/in_range_cast_test.cc', 'posix/process_util_test.cc', + 'posix/symbolic_constants_posix_test.cc', 'stdlib/string_number_conversion_test.cc', 'stdlib/strlcpy_test.cc', 'stdlib/strnlen_test.cc',