Index: libcxx/trunk/test/pretty_printers/gdb_pretty_printer_test.py =================================================================== --- libcxx/trunk/test/pretty_printers/gdb_pretty_printer_test.py +++ libcxx/trunk/test/pretty_printers/gdb_pretty_printer_test.py @@ -0,0 +1,112 @@ +#===----------------------------------------------------------------------===## +# +# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +# See https://llvm.org/LICENSE.txt for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +# +#===----------------------------------------------------------------------===## +"""Commands used to automate testing gdb pretty printers. + +This script is part of a larger framework to test gdb pretty printers. It +runs the program, detects test cases, checks them, and prints results. + +See gdb_pretty_printer_test.sh.cpp on how to write a test case. + +""" + +from __future__ import print_function +import re +import gdb + +test_failures = 0 + + +class CheckResult(gdb.Command): + + def __init__(self): + super(CheckResult, self).__init__( + "print_and_compare", gdb.COMMAND_DATA) + + def invoke(self, arg, from_tty): + try: + # Stack frame is: + # 0. StopForDebugger + # 1. ComparePrettyPrintToChars or ComparePrettyPrintToRegex + # 2. TestCase + compare_frame = gdb.newest_frame().older() + testcase_frame = compare_frame.older() + test_loc = testcase_frame.find_sal() + # Use interactive commands in the correct context to get the pretty + # printed version + + value_str = self._get_value_string(compare_frame, testcase_frame) + + # Ignore the convenience variable name and newline + value = value_str[value_str.find("= ") + 2:-1] + gdb.newest_frame().select() + + expectation_val = compare_frame.read_var("expectation") + if "PrettyPrintToRegex" in compare_frame.name(): + check_literal = expectation_val.string() + test_fails = not re.match(check_literal, value) + else: + check_literal_string = expectation_val.string(encoding="utf-8") + check_literal = check_literal_string.encode("utf-8") + test_fails = value != check_literal + + if test_fails: + global test_failures + print("FAIL: " + test_loc.symtab.filename + + ":" + str(test_loc.line)) + print("GDB printed:") + print(" " + value) + print("Value should match:") + print(" " + check_literal) + test_failures += 1 + else: + print("PASS: " + test_loc.symtab.filename + + ":" + str(test_loc.line)) + + except RuntimeError as e: + # At this point, lots of different things could be wrong, so don't try to + # recover or figure it out. Don't exit either, because then it's + # impossible debug the framework itself. + print("FAIL: Something is wrong in the test framework.") + print(str(e)) + test_failures += 1 + + def _get_value_string(self, compare_frame, testcase_frame): + compare_frame.select() + if "ComparePrettyPrint" in compare_frame.name(): + return gdb.execute("p value", to_string=True) + value_str = str(compare_frame.read_var("value")) + clean_expression_str = value_str.strip("'\"") + testcase_frame.select() + return gdb.execute("p " + clean_expression_str, to_string=True) + + +def exit_handler(event=None): + global test_failures + if test_failures: + print("FAILED %d cases" % test_failures) + exit(test_failures) + + +# Start code executed at load time + +# Disable terminal paging +gdb.execute("set height 0") +gdb.execute("set python print-stack full") +test_failures = 0 +CheckResult() +test_bp = gdb.Breakpoint("StopForDebugger") +test_bp.enabled = True +test_bp.silent = True +test_bp.commands = "print_and_compare\ncontinue" +# "run" won't return if the program exits; ensure the script regains control. +gdb.events.exited.connect(exit_handler) +gdb.execute("run") +# If the program didn't exit, something went wrong, but we don't +# know what. Fail on exit. +test_failures += 1 +exit_handler(None) Index: libcxx/trunk/test/pretty_printers/gdb_pretty_printer_test.sh.cpp =================================================================== --- libcxx/trunk/test/pretty_printers/gdb_pretty_printer_test.sh.cpp +++ libcxx/trunk/test/pretty_printers/gdb_pretty_printer_test.sh.cpp @@ -0,0 +1,632 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// UNSUPPORTED: system-windows +// REQUIRES: libcxx_gdb +// +// RUN: %cxx %flags %s -o %t.exe %compile_flags -g %link_flags +// Ensure locale-independence for unicode tests. +// RUN: %libcxx_gdb -nx -batch -iex "set autoload off" -ex "source %libcxx_src_root/utils/gdb/libcxx/printers.py" -ex "python register_libcxx_printer_loader()" -ex "source %libcxx_src_root/test/pretty_printers/gdb_pretty_printer_test.py" %t.exe + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// To write a pretty-printer test: +// +// 1. Declare a variable of the type you want to test +// +// 2. Set its value to something which will test the pretty printer in an +// interesting way. +// +// 3. Call ComparePrettyPrintToChars with that variable, and a "const char*" +// value to compare to the printer's output. +// +// Or +// +// Call ComparePrettyPrintToChars with that variable, and a "const char*" +// *python* regular expression to match against the printer's output. +// The set of special characters in a Python regular expression overlaps +// with a lot of things the pretty printers print--brackets, for +// example--so take care to escape appropriately. +// +// Alternatively, construct a string that gdb can parse as an expression, +// so that printing the value of the expression will test the pretty printer +// in an interesting way. Then, call CompareExpressionPrettyPrintToChars or +// CompareExpressionPrettyPrintToRegex to compare the printer's output. + +// Avoids setting a breakpoint in every-single instantiation of +// ComparePrettyPrintTo*. Also, make sure neither it, nor the +// variables we need present in the Compare functions are optimized +// away. +void StopForDebugger(void *value, void *check) __attribute__((optnone)) { } + +// Prevents the compiler optimizing away the parameter in the caller function. +template +void MarkAsLive(Type &&t) __attribute__((optnone)) { } + +// In all of the Compare(Expression)PrettyPrintTo(Regex/Chars) functions below, +// the python script sets a breakpoint just before the call to StopForDebugger, +// compares the result to the expectation. +// +// The expectation is a literal string to be matched exactly in +// *PrettyPrintToChars functions, and is a python regular expression in +// *PrettyPrintToRegex functions. +// +// In ComparePrettyPrint* functions, the value is a variable of any type. In +// CompareExpressionPrettyPrint functions, the value is a string expression that +// gdb will parse and print the result. +// +// The python script will print either "PASS", or a detailed failure explanation +// along with the line that has invoke the function. The testing will continue +// in either case. + +template void ComparePrettyPrintToChars( + TypeToPrint value, + const char *expectation) { + StopForDebugger(&value, &expectation); +} + +template void ComparePrettyPrintToRegex( + TypeToPrint value, + const char *expectation) { + StopForDebugger(&value, &expectation); +} + +void CompareExpressionPrettyPrintToChars( + std::string value, + const char *expectation) { + StopForDebugger(&value, &expectation); +} + +void CompareExpressionPrettyPrintToRegex( + std::string value, + const char *expectation) { + StopForDebugger(&value, &expectation); +} + +namespace example { + struct example_struct { + int a = 0; + int arr[1000]; + }; +} + +// If enabled, the self test will "fail"--because we want to be sure it properly +// diagnoses tests that *should* fail. Evaluate the output by hand. +void framework_self_test() { +#ifdef FRAMEWORK_SELF_TEST + // Use the most simple data structure we can. + const char a = 'a'; + + // Tests that should pass + ComparePrettyPrintToChars(a, "97 'a'"); + ComparePrettyPrintToRegex(a, ".*"); + + // Tests that should fail. + ComparePrettyPrintToChars(a, "b"); + ComparePrettyPrintToRegex(a, "b"); +#endif +} + +// A simple pass-through allocator to check that we handle CompressedPair +// correctly. +template class UncompressibleAllocator : public std::allocator { + public: + char X; +}; + +void string_test() { + std::string short_string("kdjflskdjf"); + // The display_hint "string" adds quotes the printed result. + ComparePrettyPrintToChars(short_string, "\"kdjflskdjf\""); + + std::basic_string, UncompressibleAllocator> + long_string("mehmet bizim dostumuz agzi kirik testimiz"); + ComparePrettyPrintToChars(long_string, + "\"mehmet bizim dostumuz agzi kirik testimiz\""); +} + +void u16string_test() { + std::u16string test0 = u"Hello World"; + ComparePrettyPrintToChars(test0, "u\"Hello World\""); + std::u16string test1 = u"\U00010196\u20AC\u00A3\u0024"; + ComparePrettyPrintToChars(test1, "u\"\U00010196\u20AC\u00A3\u0024\""); + std::u16string test2 = u"\u0024\u0025\u0026\u0027"; + ComparePrettyPrintToChars(test2, "u\"\u0024\u0025\u0026\u0027\""); + std::u16string test3 = u"mehmet bizim dostumuz agzi kirik testimiz"; + ComparePrettyPrintToChars(test3, + ("u\"mehmet bizim dostumuz agzi kirik testimiz\"")); +} + +void u32string_test() { + std::u32string test0 = U"Hello World"; + ComparePrettyPrintToChars(test0, "U\"Hello World\""); + std::u32string test1 = + U"\U0001d552\U0001d553\U0001d554\U0001d555\U0001d556\U0001d557"; + ComparePrettyPrintToChars( + test1, + ("U\"\U0001d552\U0001d553\U0001d554\U0001d555\U0001d556\U0001d557\"")); + std::u32string test2 = U"\U00004f60\U0000597d"; + ComparePrettyPrintToChars(test2, ("U\"\U00004f60\U0000597d\"")); + std::u32string test3 = U"mehmet bizim dostumuz agzi kirik testimiz"; + ComparePrettyPrintToChars(test3, ("U\"mehmet bizim dostumuz agzi kirik testimiz\"")); +} + +void tuple_test() { + std::tuple test0(2, 3, 4); + ComparePrettyPrintToChars( + test0, + "std::tuple containing = {[1] = 2, [2] = 3, [3] = 4}"); + + std::tuple<> test1; + ComparePrettyPrintToChars( + test1, + "empty std::tuple"); +} + +void unique_ptr_test() { + std::unique_ptr matilda(new std::string("Matilda")); + ComparePrettyPrintToRegex( + std::move(matilda), + R"(std::unique_ptr containing = {__ptr_ = 0x[a-f0-9]+})"); + std::unique_ptr forty_two(new int(42)); + ComparePrettyPrintToRegex(std::move(forty_two), + R"(std::unique_ptr containing = {__ptr_ = 0x[a-f0-9]+})"); + + std::unique_ptr this_is_null; + ComparePrettyPrintToChars(std::move(this_is_null), + R"(std::unique_ptr is nullptr)"); +} + +void bitset_test() { + std::bitset<258> i_am_empty(0); + ComparePrettyPrintToChars(i_am_empty, "std::bitset<258>"); + + std::bitset<0> very_empty; + ComparePrettyPrintToChars(very_empty, "std::bitset<0>"); + + std::bitset<15> b_000001111111100(1020); + ComparePrettyPrintToChars(b_000001111111100, + "std::bitset<15> = {[2] = 1, [3] = 1, [4] = 1, [5] = 1, [6] = 1, " + "[7] = 1, [8] = 1, [9] = 1}"); + + std::bitset<258> b_0_129_132(0); + b_0_129_132[0] = true; + b_0_129_132[129] = true; + b_0_129_132[132] = true; + ComparePrettyPrintToChars(b_0_129_132, + "std::bitset<258> = {[0] = 1, [129] = 1, [132] = 1}"); +} + +void list_test() { + std::list i_am_empty{}; + ComparePrettyPrintToChars(i_am_empty, "std::list is empty"); + + std::list one_two_three {1, 2, 3}; + ComparePrettyPrintToChars(one_two_three, + "std::list with 3 elements = {1, 2, 3}"); + + std::list colors {"red", "blue", "green"}; + ComparePrettyPrintToChars(colors, + R"(std::list with 3 elements = {"red", "blue", "green"})"); +} + +void deque_test() { + std::deque i_am_empty{}; + ComparePrettyPrintToChars(i_am_empty, "std::deque is empty"); + + std::deque one_two_three {1, 2, 3}; + ComparePrettyPrintToChars(one_two_three, + "std::deque with 3 elements = {1, 2, 3}"); + + std::deque bfg; + for (int i = 0; i < 10; ++i) { + example::example_struct current; + current.a = i; + bfg.push_back(current); + } + for (int i = 0; i < 3; ++i) { + bfg.pop_front(); + } + for (int i = 0; i < 3; ++i) { + bfg.pop_back(); + } + ComparePrettyPrintToRegex(bfg, + "std::deque with 4 elements = {" + "{a = 3, arr = {[^}]+}}, " + "{a = 4, arr = {[^}]+}}, " + "{a = 5, arr = {[^}]+}}, " + "{a = 6, arr = {[^}]+}}}"); +} + +void map_test() { + std::map i_am_empty{}; + ComparePrettyPrintToChars(i_am_empty, "std::map is empty"); + + std::map one_two_three; + one_two_three.insert({1, "one"}); + one_two_three.insert({2, "two"}); + one_two_three.insert({3, "three"}); + ComparePrettyPrintToChars(one_two_three, + "std::map with 3 elements = " + R"({[1] = "one", [2] = "two", [3] = "three"})"); + + std::map bfg; + for (int i = 0; i < 4; ++i) { + example::example_struct current; + current.a = 17 * i; + bfg.insert({i, current}); + } + ComparePrettyPrintToRegex(bfg, + R"(std::map with 4 elements = {)" + R"(\[0\] = {a = 0, arr = {[^}]+}}, )" + R"(\[1\] = {a = 17, arr = {[^}]+}}, )" + R"(\[2\] = {a = 34, arr = {[^}]+}}, )" + R"(\[3\] = {a = 51, arr = {[^}]+}}})"); +} + +void multimap_test() { + std::multimap i_am_empty{}; + ComparePrettyPrintToChars(i_am_empty, "std::multimap is empty"); + + std::multimap one_two_three; + one_two_three.insert({1, "one"}); + one_two_three.insert({3, "three"}); + one_two_three.insert({1, "ein"}); + one_two_three.insert({2, "two"}); + one_two_three.insert({2, "zwei"}); + one_two_three.insert({1, "bir"}); + + ComparePrettyPrintToChars(one_two_three, + "std::multimap with 6 elements = " + R"({[1] = "one", [1] = "ein", [1] = "bir", )" + R"([2] = "two", [2] = "zwei", [3] = "three"})"); +} + +void queue_test() { + std::queue i_am_empty; + ComparePrettyPrintToChars(i_am_empty, + "std::queue wrapping = {std::deque is empty}"); + + std::queue one_two_three(std::deque{1, 2, 3}); + ComparePrettyPrintToChars(one_two_three, + "std::queue wrapping = {" + "std::deque with 3 elements = {1, 2, 3}}"); +} + +void priority_queue_test() { + std::priority_queue i_am_empty; + ComparePrettyPrintToChars(i_am_empty, + "std::priority_queue wrapping = {std::vector of length 0, capacity 0}"); + + std::priority_queue one_two_three; + one_two_three.push(11111); + one_two_three.push(22222); + one_two_three.push(33333); + + ComparePrettyPrintToRegex(one_two_three, + R"(std::priority_queue wrapping = )" + R"({std::vector of length 3, capacity 3 = {33333)"); + + ComparePrettyPrintToRegex(one_two_three, ".*11111.*"); + ComparePrettyPrintToRegex(one_two_three, ".*22222.*"); +} + +void set_test() { + std::set i_am_empty; + ComparePrettyPrintToChars(i_am_empty, "std::set is empty"); + + std::set one_two_three {3, 1, 2}; + ComparePrettyPrintToChars(one_two_three, + "std::set with 3 elements = {1, 2, 3}"); + + std::set> prime_pairs { + std::make_pair(3, 5), std::make_pair(5, 7), std::make_pair(3, 5)}; + + ComparePrettyPrintToChars(prime_pairs, + "std::set with 2 elements = {" + "{first = 3, second = 5}, {first = 5, second = 7}}"); +} + +void stack_test() { + std::stack test0; + ComparePrettyPrintToChars(test0, + "std::stack wrapping = {std::deque is empty}"); + test0.push(5); + test0.push(6); + ComparePrettyPrintToChars( + test0, "std::stack wrapping = {std::deque with 2 elements = {5, 6}}"); + std::stack test1; + test1.push(true); + test1.push(false); + ComparePrettyPrintToChars( + test1, + "std::stack wrapping = {std::deque with 2 elements = {true, false}}"); + + std::stack test2; + test2.push("Hello"); + test2.push("World"); + ComparePrettyPrintToChars(test2, + "std::stack wrapping = {std::deque with 2 elements " + "= {\"Hello\", \"World\"}}"); +} + +void multiset_test() { + std::multiset i_am_empty; + ComparePrettyPrintToChars(i_am_empty, "std::multiset is empty"); + + std::multiset one_two_three {"1:one", "2:two", "3:three", "1:one"}; + ComparePrettyPrintToChars(one_two_three, + "std::multiset with 4 elements = {" + R"("1:one", "1:one", "2:two", "3:three"})"); +} + +void vector_test() { + std::vector test0 = {true, false}; + ComparePrettyPrintToChars(test0, + "std::vector of " + "length 2, capacity 64 = {1, 0}"); + for (int i = 0; i < 31; ++i) { + test0.push_back(true); + test0.push_back(false); + } + ComparePrettyPrintToRegex( + test0, + "std::vector of length 64, " + "capacity 64 = {1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, " + "0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, " + "0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0}"); + test0.push_back(true); + ComparePrettyPrintToRegex( + test0, + "std::vector of length 65, " + "capacity 128 = {1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, " + "1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, " + "1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1}"); + + std::vector test1; + ComparePrettyPrintToChars(test1, "std::vector of length 0, capacity 0"); + + std::vector test2 = {5, 6, 7}; + ComparePrettyPrintToChars(test2, + "std::vector of length " + "3, capacity 3 = {5, 6, 7}"); + + std::vector> test3({7, 8}); + ComparePrettyPrintToChars(std::move(test3), + "std::vector of length " + "2, capacity 2 = {7, 8}"); +} + +void set_iterator_test() { + std::set one_two_three {1111, 2222, 3333}; + auto it = one_two_three.find(2222); + MarkAsLive(it); + CompareExpressionPrettyPrintToRegex("it", + R"(std::__tree_const_iterator = {\[0x[a-f0-9]+\] = 2222})"); + + auto not_found = one_two_three.find(1234); + MarkAsLive(not_found); + // Because the end_node is not easily detected, just be sure it doesn't crash. + CompareExpressionPrettyPrintToRegex("not_found", + R"(std::__tree_const_iterator = {\[0x[a-f0-9]+\] = .*})"); +} + +void map_iterator_test() { + std::map one_two_three; + one_two_three.insert({1, "one"}); + one_two_three.insert({2, "two"}); + one_two_three.insert({3, "three"}); + auto it = one_two_three.begin(); + MarkAsLive(it); + CompareExpressionPrettyPrintToRegex("it", + R"(std::__map_iterator = )" + R"({\[0x[a-f0-9]+\] = {first = 1, second = "one"}})"); + + auto not_found = one_two_three.find(7); + MarkAsLive(not_found); + CompareExpressionPrettyPrintToRegex("not_found", + R"(std::__map_iterator = {\[0x[a-f0-9]+\] = end\(\)})"); +} + +void unordered_set_test() { + std::unordered_set i_am_empty; + ComparePrettyPrintToChars(i_am_empty, "std::unordered_set is empty"); + + std::unordered_set numbers {12345, 67890, 222333, 12345}; + numbers.erase(numbers.find(222333)); + ComparePrettyPrintToRegex(numbers, "std::unordered_set with 2 elements = "); + ComparePrettyPrintToRegex(numbers, ".*12345.*"); + ComparePrettyPrintToRegex(numbers, ".*67890.*"); + + std::unordered_set colors {"red", "blue", "green"}; + ComparePrettyPrintToRegex(colors, "std::unordered_set with 3 elements = "); + ComparePrettyPrintToRegex(colors, R"(.*"red".*)"); + ComparePrettyPrintToRegex(colors, R"(.*"blue".*)"); + ComparePrettyPrintToRegex(colors, R"(.*"green".*)"); +} + +void unordered_multiset_test() { + std::unordered_multiset i_am_empty; + ComparePrettyPrintToChars(i_am_empty, "std::unordered_multiset is empty"); + + std::unordered_multiset numbers {12345, 67890, 222333, 12345}; + ComparePrettyPrintToRegex(numbers, + "std::unordered_multiset with 4 elements = "); + ComparePrettyPrintToRegex(numbers, ".*12345.*12345.*"); + ComparePrettyPrintToRegex(numbers, ".*67890.*"); + ComparePrettyPrintToRegex(numbers, ".*222333.*"); + + std::unordered_multiset colors {"red", "blue", "green", "red"}; + ComparePrettyPrintToRegex(colors, + "std::unordered_multiset with 4 elements = "); + ComparePrettyPrintToRegex(colors, R"(.*"red".*"red".*)"); + ComparePrettyPrintToRegex(colors, R"(.*"blue".*)"); + ComparePrettyPrintToRegex(colors, R"(.*"green".*)"); +} + +void unordered_map_test() { + std::unordered_map i_am_empty; + ComparePrettyPrintToChars(i_am_empty, "std::unordered_map is empty"); + + std::unordered_map one_two_three; + one_two_three.insert({1, "one"}); + one_two_three.insert({2, "two"}); + one_two_three.insert({3, "three"}); + ComparePrettyPrintToRegex(one_two_three, + "std::unordered_map with 3 elements = "); + ComparePrettyPrintToRegex(one_two_three, R"(.*\[1\] = "one".*)"); + ComparePrettyPrintToRegex(one_two_three, R"(.*\[2\] = "two".*)"); + ComparePrettyPrintToRegex(one_two_three, R"(.*\[3\] = "three".*)"); +} + +void unordered_multimap_test() { + std::unordered_multimap i_am_empty; + ComparePrettyPrintToChars(i_am_empty, "std::unordered_multimap is empty"); + + std::unordered_multimap one_two_three; + one_two_three.insert({1, "one"}); + one_two_three.insert({2, "two"}); + one_two_three.insert({3, "three"}); + one_two_three.insert({2, "two"}); + ComparePrettyPrintToRegex(one_two_three, + "std::unordered_multimap with 4 elements = "); + ComparePrettyPrintToRegex(one_two_three, R"(.*\[1\] = "one".*)"); + ComparePrettyPrintToRegex(one_two_three, R"(.*\[2\] = "two".*\[2\] = "two")"); + ComparePrettyPrintToRegex(one_two_three, R"(.*\[3\] = "three".*)"); +} + +void unordered_map_iterator_test() { + std::unordered_map ones_to_eights; + ones_to_eights.insert({1, 8}); + ones_to_eights.insert({11, 88}); + ones_to_eights.insert({111, 888}); + + auto ones_to_eights_begin = ones_to_eights.begin(); + MarkAsLive(ones_to_eights_begin); + CompareExpressionPrettyPrintToRegex("ones_to_eights_begin", + R"(std::__hash_map_iterator = {\[1+\] = 8+})"); + + auto not_found = ones_to_eights.find(5); + MarkAsLive(not_found); + CompareExpressionPrettyPrintToRegex("not_found", + R"(std::__hash_map_iterator = end\(\))"); +} + +void unordered_set_iterator_test() { + std::unordered_set ones; + ones.insert(111); + ones.insert(1111); + ones.insert(11111); + + auto ones_begin = ones.begin(); + MarkAsLive(ones_begin); + CompareExpressionPrettyPrintToRegex("ones_begin", + R"(std::__hash_const_iterator = {1+})"); + + auto not_found = ones.find(5); + MarkAsLive(not_found); + CompareExpressionPrettyPrintToRegex("not_found", + R"(std::__hash_const_iterator = end\(\))"); +} + +// Check that libc++ pretty printers do not handle pointers. +void pointer_negative_test() { + int abc = 123; + int *int_ptr = &abc; + // Check that the result is equivalent to "p/r int_ptr" command. + ComparePrettyPrintToRegex(int_ptr, R"(\(int \*\) 0x[a-f0-9]+)"); +} + +void shared_ptr_test() { + // Shared ptr tests while using test framework call another function + // due to which there is one more count for the pointer. Hence, all the + // following tests are testing with expected count plus 1. + std::shared_ptr test0 = std::make_shared(5); + ComparePrettyPrintToRegex( + test0, + R"(std::shared_ptr count 2, weak 0 containing = {__ptr_ = 0x[a-f0-9]+})"); + + std::shared_ptr test1(test0); + ComparePrettyPrintToRegex( + test1, + R"(std::shared_ptr count 3, weak 0 containing = {__ptr_ = 0x[a-f0-9]+})"); + + { + std::weak_ptr test2 = test1; + ComparePrettyPrintToRegex( + test0, + R"(std::shared_ptr count 3, weak 1 containing = {__ptr_ = 0x[a-f0-9]+})"); + } + + ComparePrettyPrintToRegex( + test0, + R"(std::shared_ptr count 3, weak 0 containing = {__ptr_ = 0x[a-f0-9]+})"); + + std::shared_ptr test3; + ComparePrettyPrintToChars(test3, "std::shared_ptr is nullptr"); +} + +void streampos_test() { + std::streampos test0 = 67; + ComparePrettyPrintToChars( + test0, "std::fpos with stream offset:67 with state: {count:0 value:0}"); + std::istringstream input("testing the input stream here"); + std::streampos test1 = input.tellg(); + ComparePrettyPrintToChars( + test1, "std::fpos with stream offset:0 with state: {count:0 value:0}"); + std::unique_ptr buffer(new char[5]); + input.read(buffer.get(), 5); + test1 = input.tellg(); + ComparePrettyPrintToChars( + test1, "std::fpos with stream offset:5 with state: {count:0 value:0}"); +} + +int main(int argc, char* argv[]) { + framework_self_test(); + + string_test(); + + u32string_test(); + tuple_test(); + unique_ptr_test(); + shared_ptr_test(); + bitset_test(); + list_test(); + deque_test(); + map_test(); + multimap_test(); + queue_test(); + priority_queue_test(); + stack_test(); + set_test(); + multiset_test(); + vector_test(); + set_iterator_test(); + map_iterator_test(); + unordered_set_test(); + unordered_multiset_test(); + unordered_map_test(); + unordered_multimap_test(); + unordered_map_iterator_test(); + unordered_set_iterator_test(); + pointer_negative_test(); + streampos_test(); + return 0; +} Index: libcxx/trunk/utils/gdb/libcxx/printers.py =================================================================== --- libcxx/trunk/utils/gdb/libcxx/printers.py +++ libcxx/trunk/utils/gdb/libcxx/printers.py @@ -0,0 +1,992 @@ +#===----------------------------------------------------------------------===## +# +# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +# See https://llvm.org/LICENSE.txt for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +# +#===----------------------------------------------------------------------===## +"""GDB pretty-printers for libc++. + +These should work for objects compiled when _LIBCPP_ABI_UNSTABLE is defined +and when it is undefined. +""" + +from __future__ import print_function + +import re +import gdb + +# One under-documented feature of the gdb pretty-printer API +# is that clients can call any other member of the API +# before they call to_string. +# Therefore all self.FIELDs must be set in the pretty-printer's +# __init__ function. + +_void_pointer_type = gdb.lookup_type("void").pointer() + + +_long_int_type = gdb.lookup_type("unsigned long long") + + +def addr_as_long(addr): + return int(addr.cast(_long_int_type)) + + +# The size of a pointer in bytes. +_pointer_size = _void_pointer_type.sizeof + + +def _remove_cxx_namespace(typename): + """Removed libc++ specific namespace from the type. + + Arguments: + typename(string): A type, such as std::__u::something. + + Returns: + A string without the libc++ specific part, such as std::something. + """ + + return re.sub("std::__.*?::", "std::", typename) + + +def _remove_generics(typename): + """Remove generics part of the type. Assumes typename is not empty. + + Arguments: + typename(string): A type such as std::my_collection. + + Returns: + The prefix up to the generic part, such as std::my_collection. + """ + + match = re.match("^([^<]+)", typename) + return match.group(1) + + +# Some common substitutions on the types to reduce visual clutter (A user who +# wants to see the actual details can always use print/r). +_common_substitutions = [ + ("std::basic_string, std::allocator >", + "std::string"), +] + + +def _prettify_typename(gdb_type): + """Returns a pretty name for the type, or None if no name can be found. + + Arguments: + gdb_type(gdb.Type): A type object. + + Returns: + A string, without type_defs, libc++ namespaces, and common substitutions + applied. + """ + + type_without_typedefs = gdb_type.strip_typedefs() + typename = type_without_typedefs.name or type_without_typedefs.tag or \ + str(type_without_typedefs) + result = _remove_cxx_namespace(typename) + for find_str, subst_str in _common_substitutions: + result = re.sub(find_str, subst_str, result) + return result + + +def _typename_for_nth_generic_argument(gdb_type, n): + """Returns a pretty string for the nth argument of the given type. + + Arguments: + gdb_type(gdb.Type): A type object, such as the one for std::map + n: The (zero indexed) index of the argument to return. + + Returns: + A string for the nth argument, such a "std::string" + """ + element_type = gdb_type.template_argument(n) + return _prettify_typename(element_type) + + +def _typename_with_n_generic_arguments(gdb_type, n): + """Return a string for the type with the first n (1, ...) generic args.""" + + base_type = _remove_generics(_prettify_typename(gdb_type)) + arg_list = [base_type] + template = "%s<" + for i in range(n): + arg_list.append(_typename_for_nth_generic_argument(gdb_type, i)) + template += "%s, " + result = (template[:-2] + ">") % tuple(arg_list) + return result + + +def _typename_with_first_generic_argument(gdb_type): + return _typename_with_n_generic_arguments(gdb_type, 1) + + +class StdTuplePrinter(object): + """Print a std::tuple.""" + + class _Children(object): + """Class to iterate over the tuple's children.""" + + def __init__(self, val): + self.val = val + self.child_iter = iter(self.val["__base_"].type.fields()) + self.count = 0 + + def __iter__(self): + return self + + def next(self): + # child_iter raises StopIteration when appropriate. + field_name = self.child_iter.next() + child = self.val["__base_"][field_name]["__value_"] + self.count += 1 + return ("[%d]" % self.count, child) + + def __init__(self, val): + self.val = val + + def to_string(self): + typename = _remove_generics(_prettify_typename(self.val.type)) + if not self.val.type.fields(): + return "empty %s" % typename + return "%s containing" % typename + + def children(self): + if not self.val.type.fields(): + return iter(()) + return self._Children(self.val) + + +def _get_base_subobject(child_class_value, index=0): + """Returns the object's value in the form of the parent class at index. + + This function effectively casts the child_class_value to the base_class's + type, but the type-to-cast to is stored in the field at index, and once + we know the field, we can just return the data. + + Args: + child_class_value: the value to cast + index: the parent class index + + Raises: + Exception: field at index was not a base-class field. + """ + + field = child_class_value.type.fields()[index] + if not field.is_base_class: + raise Exception("Not a base-class field.") + return child_class_value[field] + + +def _value_of_pair_first(value): + """Convenience for _get_base_subobject, for the common case.""" + return _get_base_subobject(value, 0)["__value_"] + + +class StdStringPrinter(object): + """Print a std::string.""" + + def _get_short_size(self, short_field, short_size): + """Short size depends on both endianness and a compile-time define.""" + + # If the padding field is present after all this indirection, then string + # was compiled with _LIBCPP_ABI_ALTERNATE_STRING_LAYOUT defined. + field = short_field.type.fields()[1].type.fields()[0] + libcpp_abi_alternate_string_layout = field.name and "__padding" in field.name + + # Strictly, this only tells us the current mode, not how libcxx was + # compiled. + libcpp_big_endian = "big endian" in gdb.execute("show endian", + to_string=True) + + # This logical structure closely follows the original code (which is clearer + # in C++). Keep them parallel to make them easier to compare. + if libcpp_abi_alternate_string_layout: + if libcpp_big_endian: + return short_size >> 1 + else: + return short_size + elif libcpp_big_endian: + return short_size + else: + return short_size >> 1 + + def __init__(self, val): + self.val = val + + def to_string(self): + """Build a python string from the data whether stored inline or separately.""" + + value_field = _value_of_pair_first(self.val["__r_"]) + short_field = value_field["__s"] + short_size = short_field["__size_"] + if short_size == 0: + return "" + short_mask = self.val["__short_mask"] + # Counter intuitive to compare the size and short_mask to see if the string + # is long, but that's the way the implementation does it. Note that + # __is_long() doesn't use get_short_size in C++. + is_long = short_size & short_mask + if is_long: + long_field = value_field["__l"] + data = long_field["__data_"] + size = long_field["__size_"] + else: + data = short_field["__data_"] + size = self._get_short_size(short_field, short_size) + if hasattr(data, "lazy_string"): + return data.lazy_string(length=size) + return data.string(length=size) + + def display_hint(self): + return "string" + + +class StdUniquePtrPrinter(object): + """Print a std::unique_ptr.""" + + def __init__(self, val): + self.val = val + self.addr = _value_of_pair_first(self.val["__ptr_"]) + self.pointee_type = self.val.type.template_argument(0) + + def to_string(self): + typename = _remove_generics(_prettify_typename(self.val.type)) + if not self.addr: + return "%s is nullptr" % typename + return ("%s<%s> containing" % + (typename, + _remove_generics(_prettify_typename(self.pointee_type)))) + + def __iter__(self): + if self.addr: + yield "__ptr_", self.addr.cast(self.pointee_type.pointer()) + + def children(self): + return self + + +class StdSharedPointerPrinter(object): + """Print a std::shared_ptr.""" + + def __init__(self, val): + self.val = val + self.addr = self.val["__ptr_"] + + def to_string(self): + """Returns self as a string.""" + typename = _remove_generics(_prettify_typename(self.val.type)) + pointee_type = _remove_generics( + _prettify_typename(self.val.type.template_argument(0))) + if not self.addr: + return "%s is nullptr" % typename + refcount = self.val["__cntrl_"] + if refcount != 0: + usecount = refcount["__shared_owners_"] + 1 + weakcount = refcount["__shared_weak_owners_"] + if usecount == 0: + state = "expired, weak %d" % weakcount + else: + state = "count %d, weak %d" % (usecount, weakcount) + return "%s<%s> %s containing" % (typename, pointee_type, state) + + def __iter__(self): + if self.addr: + yield "__ptr_", self.addr + + def children(self): + return self + + +class StdVectorPrinter(object): + """Print a std::vector.""" + + class _VectorBoolIterator(object): + """Class to iterate over the bool vector's children.""" + + def __init__(self, begin, size, bits_per_word): + self.item = begin + self.size = size + self.bits_per_word = bits_per_word + self.count = 0 + self.offset = 0 + + def __iter__(self): + return self + + def next(self): + """Retrieve the next element.""" + + self.count += 1 + if self.count > self.size: + raise StopIteration + entry = self.item.dereference() + if entry & (1 << self.offset): + outbit = 1 + else: + outbit = 0 + self.offset += 1 + if self.offset >= self.bits_per_word: + self.item += 1 + self.offset = 0 + return ("[%d]" % self.count, outbit) + + class _VectorIterator(object): + """Class to iterate over the non-bool vector's children.""" + + def __init__(self, begin, end): + self.item = begin + self.end = end + self.count = 0 + + def __iter__(self): + return self + + def next(self): + self.count += 1 + if self.item == self.end: + raise StopIteration + entry = self.item.dereference() + self.item += 1 + return ("[%d]" % self.count, entry) + + def __init__(self, val): + """Set val, length, capacity, and iterator for bool and normal vectors.""" + self.val = val + self.typename = _remove_generics(_prettify_typename(val.type)) + begin = self.val["__begin_"] + if self.val.type.template_argument(0).code == gdb.TYPE_CODE_BOOL: + self.typename += "" + self.length = self.val["__size_"] + bits_per_word = self.val["__bits_per_word"] + self.capacity = _value_of_pair_first( + self.val["__cap_alloc_"]) * bits_per_word + self.iterator = self._VectorBoolIterator( + begin, self.length, bits_per_word) + else: + end = self.val["__end_"] + self.length = end - begin + self.capacity = _get_base_subobject( + self.val["__end_cap_"])["__value_"] - begin + self.iterator = self._VectorIterator(begin, end) + + def to_string(self): + return ("%s of length %d, capacity %d" % + (self.typename, self.length, self.capacity)) + + def children(self): + return self.iterator + + def display_hint(self): + return "array" + + +class StdBitsetPrinter(object): + """Print a std::bitset.""" + + def __init__(self, val): + self.val = val + self.n_words = int(self.val["__n_words"]) + self.bits_per_word = int(self.val["__bits_per_word"]) + if self.n_words == 1: + self.values = [int(self.val["__first_"])] + else: + self.values = [int(self.val["__first_"][index]) + for index in range(self.n_words)] + + def to_string(self): + typename = _prettify_typename(self.val.type) + return "%s" % typename + + def _byte_it(self, value): + index = -1 + while value: + index += 1 + will_yield = value % 2 + value /= 2 + if will_yield: + yield index + + def _list_it(self): + for word_index in range(self.n_words): + current = self.values[word_index] + if current: + for n in self._byte_it(current): + yield ("[%d]" % (word_index * self.bits_per_word + n), 1) + + def __iter__(self): + return self._list_it() + + def children(self): + return self + + +class StdDequePrinter(object): + """Print a std::deque.""" + + def __init__(self, val): + self.val = val + self.size = int(_value_of_pair_first(val["__size_"])) + self.start_ptr = self.val["__map_"]["__begin_"] + self.first_block_start_index = int(self.val["__start_"]) + self.node_type = self.start_ptr.type + self.block_size = self._calculate_block_size( + val.type.template_argument(0)) + + def _calculate_block_size(self, element_type): + """Calculates the number of elements in a full block.""" + size = element_type.sizeof + # Copied from struct __deque_block_size implementation of libcxx. + return 4096 / size if size < 256 else 16 + + def _bucket_it(self, start_addr, start_index, end_index): + for i in range(start_index, end_index): + yield i, (start_addr.dereference() + i).dereference() + + def _list_it(self): + """Primary iteration worker.""" + num_emitted = 0 + current_addr = self.start_ptr + start_index = self.first_block_start_index + while num_emitted < self.size: + end_index = min(start_index + self.size - + num_emitted, self.block_size) + for _, elem in self._bucket_it(current_addr, start_index, end_index): + yield "", elem + num_emitted += end_index - start_index + current_addr = gdb.Value(addr_as_long(current_addr) + _pointer_size) \ + .cast(self.node_type) + start_index = 0 + + def to_string(self): + typename = _remove_generics(_prettify_typename(self.val.type)) + if self.size: + return "%s with %d elements" % (typename, self.size) + return "%s is empty" % typename + + def __iter__(self): + return self._list_it() + + def children(self): + return self + + def display_hint(self): + return "array" + + +class StdListPrinter(object): + """Print a std::list.""" + + def __init__(self, val): + self.val = val + size_alloc_field = self.val["__size_alloc_"] + self.size = int(_value_of_pair_first(size_alloc_field)) + dummy_node = self.val["__end_"] + self.nodetype = gdb.lookup_type( + re.sub("__list_node_base", "__list_node", + str(dummy_node.type.strip_typedefs()))).pointer() + self.first_node = dummy_node["__next_"] + + def to_string(self): + typename = _remove_generics(_prettify_typename(self.val.type)) + if self.size: + return "%s with %d elements" % (typename, self.size) + return "%s is empty" % typename + + def _list_iter(self): + current_node = self.first_node + for _ in range(self.size): + yield "", current_node.cast(self.nodetype).dereference()["__value_"] + current_node = current_node.dereference()["__next_"] + + def __iter__(self): + return self._list_iter() + + def children(self): + return self if self.nodetype else iter(()) + + def display_hint(self): + return "array" + + +class StdQueueOrStackPrinter(object): + """Print a std::queue or std::stack.""" + + def __init__(self, val): + self.val = val + self.underlying = val["c"] + + def to_string(self): + typename = _remove_generics(_prettify_typename(self.val.type)) + return "%s wrapping" % typename + + def children(self): + return iter([("", self.underlying)]) + + def display_hint(self): + return "array" + + +class StdPriorityQueuePrinter(object): + """Print a std::priority_queue.""" + + def __init__(self, val): + self.val = val + self.underlying = val["c"] + + def to_string(self): + # TODO(tamur): It would be nice to print the top element. The technical + # difficulty is that, the implementation refers to the underlying + # container, which is a generic class. libstdcxx pretty printers do not + # print the top element. + typename = _remove_generics(_prettify_typename(self.val.type)) + return "%s wrapping" % typename + + def children(self): + return iter([("", self.underlying)]) + + def display_hint(self): + return "array" + + +class RBTreeUtils(object): + """Utility class for std::(multi)map, and std::(multi)set and iterators.""" + + def __init__(self, cast_type, root): + self.cast_type = cast_type + self.root = root + + def left_child(self, node): + result = node.cast(self.cast_type).dereference()["__left_"] + return result + + def right_child(self, node): + result = node.cast(self.cast_type).dereference()["__right_"] + return result + + def parent(self, node): + """Return the parent of node, if it exists.""" + # If this is the root, then from the algorithm's point of view, it has no + # parent. + if node == self.root: + return None + + # We don't have enough information to tell if this is the end_node (which + # doesn't have a __parent_ field), or the root (which doesn't have a parent + # from the algorithm's point of view), so cast_type may not be correct for + # this particular node. Use heuristics. + + # The end_node's left child is the root. Note that when printing interators + # in isolation, the root is unknown. + if self.left_child(node) == self.root: + return None + + parent = node.cast(self.cast_type).dereference()["__parent_"] + # If the value at the offset of __parent_ doesn't look like a valid pointer, + # then assume that node is the end_node (and therefore has no parent). + # End_node type has a pointer embedded, so should have pointer alignment. + if addr_as_long(parent) % _void_pointer_type.alignof: + return None + # This is ugly, but the only other option is to dereference an invalid + # pointer. 0x8000 is fairly arbitrary, but has had good results in + # practice. If there was a way to tell if a pointer is invalid without + # actually dereferencing it and spewing error messages, that would be ideal. + if parent < 0x8000: + return None + return parent + + def is_left_child(self, node): + parent = self.parent(node) + return parent is not None and self.left_child(parent) == node + + def is_right_child(self, node): + parent = self.parent(node) + return parent is not None and self.right_child(parent) == node + + +class AbstractRBTreePrinter(object): + """Abstract super class for std::(multi)map, and std::(multi)set.""" + + def __init__(self, val): + self.val = val + tree = self.val["__tree_"] + self.size = int(_value_of_pair_first(tree["__pair3_"])) + dummy_root = tree["__pair1_"] + root = _value_of_pair_first(dummy_root)["__left_"] + cast_type = self._init_cast_type(val.type) + self.util = RBTreeUtils(cast_type, root) + + def _get_key_value(self, node): + """Subclasses should override to return a list of values to yield.""" + raise NotImplementedError + + def _traverse(self): + """Traverses the binary search tree in order.""" + current = self.util.root + skip_left_child = False + while True: + if not skip_left_child and self.util.left_child(current): + current = self.util.left_child(current) + continue + skip_left_child = False + for key_value in self._get_key_value(current): + yield "", key_value + right_child = self.util.right_child(current) + if right_child: + current = right_child + continue + while self.util.is_right_child(current): + current = self.util.parent(current) + if self.util.is_left_child(current): + current = self.util.parent(current) + skip_left_child = True + continue + break + + def __iter__(self): + return self._traverse() + + def children(self): + return self if self.util.cast_type and self.size > 0 else iter(()) + + def to_string(self): + typename = _remove_generics(_prettify_typename(self.val.type)) + if self.size: + return "%s with %d elements" % (typename, self.size) + return "%s is empty" % typename + + +class StdMapPrinter(AbstractRBTreePrinter): + """Print a std::map or std::multimap.""" + + def _init_cast_type(self, val_type): + map_it_type = gdb.lookup_type( + str(val_type) + "::iterator").strip_typedefs() + tree_it_type = map_it_type.template_argument(0) + node_ptr_type = tree_it_type.template_argument(1) + return node_ptr_type + + def display_hint(self): + return "map" + + def _get_key_value(self, node): + key_value = node.cast(self.util.cast_type).dereference()[ + "__value_"]["__cc"] + return [key_value["first"], key_value["second"]] + + +class StdSetPrinter(AbstractRBTreePrinter): + """Print a std::set.""" + + def _init_cast_type(self, val_type): + set_it_type = gdb.lookup_type( + str(val_type) + "::iterator").strip_typedefs() + node_ptr_type = set_it_type.template_argument(1) + return node_ptr_type + + def display_hint(self): + return "array" + + def _get_key_value(self, node): + key_value = node.cast(self.util.cast_type).dereference()["__value_"] + return [key_value] + + +class AbstractRBTreeIteratorPrinter(object): + """Abstract super class for std::(multi)map, and std::(multi)set iterator.""" + + def _initialize(self, val, typename): + self.typename = typename + self.val = val + self.addr = self.val["__ptr_"] + cast_type = self.val.type.template_argument(1) + self.util = RBTreeUtils(cast_type, None) + if self.addr: + self.node = self.addr.cast(cast_type).dereference() + + def _is_valid_node(self): + if not self.util.parent(self.addr): + return False + return self.util.is_left_child(self.addr) or \ + self.util.is_right_child(self.addr) + + def to_string(self): + if not self.addr: + return "%s is nullptr" % self.typename + return "%s " % self.typename + + def _get_node_value(self, node): + raise NotImplementedError + + def __iter__(self): + addr_str = "[%s]" % str(self.addr) + if not self._is_valid_node(): + yield addr_str, " end()" + else: + yield addr_str, self._get_node_value(self.node) + + def children(self): + return self if self.addr else iter(()) + + +class MapIteratorPrinter(AbstractRBTreeIteratorPrinter): + """Print a std::(multi)map iterator.""" + + def __init__(self, val): + self._initialize(val["__i_"], + _remove_generics(_prettify_typename(val.type))) + + def _get_node_value(self, node): + return node["__value_"]["__cc"] + + +class SetIteratorPrinter(AbstractRBTreeIteratorPrinter): + """Print a std::(multi)set iterator.""" + + def __init__(self, val): + self._initialize(val, _remove_generics(_prettify_typename(val.type))) + + def _get_node_value(self, node): + return node["__value_"] + + +class StdFposPrinter(object): + """Print a std::fpos or std::streampos.""" + + def __init__(self, val): + self.val = val + + def to_string(self): + typename = _remove_generics(_prettify_typename(self.val.type)) + offset = self.val["__off_"] + state = self.val["__st_"] + count = state["__count"] + value = state["__value"]["__wch"] + return "%s with stream offset:%s with state: {count:%s value:%s}" % ( + typename, offset, count, value) + + +class AbstractUnorderedCollectionPrinter(object): + """Abstract super class for std::unordered_(multi)[set|map].""" + + def __init__(self, val): + self.val = val + self.table = val["__table_"] + self.sentinel = self.table["__p1_"] + self.size = int(_value_of_pair_first(self.table["__p2_"])) + node_base_type = self.sentinel.type.template_argument(0) + self.cast_type = node_base_type.template_argument(0) + + def _list_it(self, sentinel_ptr): + next_ptr = _value_of_pair_first(sentinel_ptr)["__next_"] + while str(next_ptr.cast(_void_pointer_type)) != "0x0": + next_val = next_ptr.cast(self.cast_type).dereference() + for key_value in self._get_key_value(next_val): + yield "", key_value + next_ptr = next_val["__next_"] + + def to_string(self): + typename = _remove_generics(_prettify_typename(self.val.type)) + if self.size: + return "%s with %d elements" % (typename, self.size) + return "%s is empty" % typename + + def _get_key_value(self, node): + """Subclasses should override to return a list of values to yield.""" + raise NotImplementedError + + def children(self): + return self if self.cast_type and self.size > 0 else iter(()) + + def __iter__(self): + return self._list_it(self.sentinel) + + +class StdUnorderedSetPrinter(AbstractUnorderedCollectionPrinter): + """Print a std::unordered_(multi)set.""" + + def _get_key_value(self, node): + return [node["__value_"]] + + def display_hint(self): + return "array" + + +class StdUnorderedMapPrinter(AbstractUnorderedCollectionPrinter): + """Print a std::unordered_(multi)map.""" + + def _get_key_value(self, node): + key_value = node["__value_"]["__cc"] + return [key_value["first"], key_value["second"]] + + def display_hint(self): + return "map" + + +class AbstractHashMapIteratorPrinter(object): + """Abstract class for unordered collection iterators.""" + + def _initialize(self, val, addr): + self.val = val + self.typename = _remove_generics(_prettify_typename(self.val.type)) + self.addr = addr + if self.addr: + self.node = self.addr.cast(self.cast_type).dereference() + + def _get_key_value(self): + """Subclasses should override to return a list of values to yield.""" + raise NotImplementedError + + def to_string(self): + if not self.addr: + return "%s = end()" % self.typename + return "%s " % self.typename + + def children(self): + return self if self.addr else iter(()) + + def __iter__(self): + for key_value in self._get_key_value(): + yield "", key_value + + +class StdUnorderedSetIteratorPrinter(AbstractHashMapIteratorPrinter): + """Print a std::(multi)set iterator.""" + + def __init__(self, val): + self.cast_type = val.type.template_argument(0) + self._initialize(val, val["__node_"]) + + def _get_key_value(self): + return [self.node["__value_"]] + + def display_hint(self): + return "array" + + +class StdUnorderedMapIteratorPrinter(AbstractHashMapIteratorPrinter): + """Print a std::(multi)map iterator.""" + + def __init__(self, val): + self.cast_type = val.type.template_argument(0).template_argument(0) + self._initialize(val, val["__i_"]["__node_"]) + + def _get_key_value(self): + key_value = self.node["__value_"]["__cc"] + return [key_value["first"], key_value["second"]] + + def display_hint(self): + return "map" + + +def _remove_std_prefix(typename): + match = re.match("^std::(.+)", typename) + return match.group(1) if match is not None else "" + + +class LibcxxPrettyPrinter(object): + """PrettyPrinter object so gdb-commands like 'info pretty-printers' work.""" + + def __init__(self, name): + super(LibcxxPrettyPrinter, self).__init__() + self.name = name + self.enabled = True + + self.lookup = { + "basic_string": StdStringPrinter, + "string": StdStringPrinter, + "tuple": StdTuplePrinter, + "unique_ptr": StdUniquePtrPrinter, + "shared_ptr": StdSharedPointerPrinter, + "weak_ptr": StdSharedPointerPrinter, + "bitset": StdBitsetPrinter, + "deque": StdDequePrinter, + "list": StdListPrinter, + "queue": StdQueueOrStackPrinter, + "stack": StdQueueOrStackPrinter, + "priority_queue": StdPriorityQueuePrinter, + "map": StdMapPrinter, + "multimap": StdMapPrinter, + "set": StdSetPrinter, + "multiset": StdSetPrinter, + "vector": StdVectorPrinter, + "__map_iterator": MapIteratorPrinter, + "__map_const_iterator": MapIteratorPrinter, + "__tree_iterator": SetIteratorPrinter, + "__tree_const_iterator": SetIteratorPrinter, + "fpos": StdFposPrinter, + "unordered_set": StdUnorderedSetPrinter, + "unordered_multiset": StdUnorderedSetPrinter, + "unordered_map": StdUnorderedMapPrinter, + "unordered_multimap": StdUnorderedMapPrinter, + "__hash_map_iterator": StdUnorderedMapIteratorPrinter, + "__hash_map_const_iterator": StdUnorderedMapIteratorPrinter, + "__hash_iterator": StdUnorderedSetIteratorPrinter, + "__hash_const_iterator": StdUnorderedSetIteratorPrinter, + } + + self.subprinters = [] + for name, subprinter in self.lookup.items(): + # Subprinters and names are used only for the rarely used command "info + # pretty" (and related), so the name of the first data structure it prints + # is a reasonable choice. + if subprinter not in self.subprinters: + subprinter.name = name + self.subprinters.append(subprinter) + + def __call__(self, val): + """Return the pretty printer for a val, if the type is supported.""" + + # Do not handle any type that is not a struct/class. + if val.type.strip_typedefs().code != gdb.TYPE_CODE_STRUCT: + return None + + # Don't attempt types known to be inside libstdcxx. + typename = val.type.name or val.type.tag or str(val.type) + match = re.match("^std::(__.*?)::", typename) + if match is None or match.group(1) in ["__cxx1998", + "__debug", + "__7", + "__g"]: + return None + + # Handle any using declarations or other typedefs. + typename = _prettify_typename(val.type) + if not typename: + return None + without_generics = _remove_generics(typename) + lookup_name = _remove_std_prefix(without_generics) + if lookup_name in self.lookup: + return self.lookup[lookup_name](val) + return None + + +_libcxx_printer_name = "libcxx_pretty_printer" + + +# These are called for every binary object file, which could be thousands in +# certain pathological cases. Limit our pretty printers to the progspace. +def _register_libcxx_printers(event): + progspace = event.new_objfile.progspace + if not getattr(progspace, _libcxx_printer_name, False): + print("Loading libc++ pretty-printers.") + gdb.printing.register_pretty_printer( + progspace, LibcxxPrettyPrinter(_libcxx_printer_name)) + setattr(progspace, _libcxx_printer_name, True) + + +def _unregister_libcxx_printers(event): + progspace = event.progspace + if getattr(progspace, _libcxx_printer_name, False): + for printer in progspace.pretty_printers: + if getattr(printer, "name", "none") == _libcxx_printer_name: + progspace.pretty_printers.remove(printer) + setattr(progspace, _libcxx_printer_name, False) + break + + +def register_libcxx_printer_loader(): + """Register event handlers to load libc++ pretty-printers.""" + gdb.events.new_objfile.connect(_register_libcxx_printers) + gdb.events.clear_objfiles.connect(_unregister_libcxx_printers)