Address sanitizer helped me find a double deletion I had causes drawing the browswer. But it reports something similar anytime you insert a module, even if using only fundamental modules. I have tooltips disabled, but they appear in the sanitizer dump.
It would be super nice to be able to use address sanitizer, esp since I am unable to run valgrind.
Here is some output, inserting fundamental VCO:
```=================================================================
==7092==ERROR: AddressSanitizer: heap-use-after-free on address 0x60300018a698 at pc 0x55dedc477897 bp 0x7ffe72439750 sp 0x7ffe72439740
READ of size 8 at 0x60300018a698 thread T0
#0 0x55dedc477896 in std::_List_iterator
#1 0x55dedc477216 in std::reverse_iterator
#2 0x55dedc473ca2 in void rack::widget::Widget::recursePositionEvent
#3 0x55dedc472b1b in rack::widget::Widget::onButton(rack::event::Button const&) include/widget/Widget.hpp:150
#4 0x55dedc472e6a in rack::widget::OpaqueWidget::onButton(rack::event::Button const&) include/widget/OpaqueWidget.hpp:21
#5 0x55dedc45e195 in rack::event::State::handleButton(rack::math::Vec, int, int, int) src/event.cpp:129
#6 0x55dedc443055 in mouseButtonCallback src/window.cpp:120
#7 0x55dedc5a5cd0 in processEvent /home/bruce/v1/dep/glfw/src/x11_window.c:1391
#8 0x55dedc5a5cd0 in _glfwPlatformPollEvents /home/bruce/v1/dep/glfw/src/x11_window.c:2698
#9 0x55dedc4459f9 in rack::Window::run() src/window.cpp:335
#10 0x55dedc4675a7 in main src/main.cpp:186
#11 0x7fdfa1a3fb6a in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x26b6a)
#12 0x55dedc2e3fa9 in _start (/home/bruce/v1/Rack+0x26efa9)
0x60300018a698 is located 8 bytes inside of 24-byte region [0x60300018a690,0x60300018a6a8)
freed by thread T0 here:
#0 0x7fdfa29600ff in operator delete(void) (/lib/x86_64-linux-gnu/libasan.so.5+0x10f0ff)
#1 0x55dedc50575b in __gnu_cxx::new_allocator
#2 0x55dedc503b5d in std::allocator_traits
#3 0x55dedc500a87 in std::__cxx11::_List_base
#4 0x55dedc5652c0 in std::__cxx11::list
#5 0x55dedc564c8a in std::__cxx11::list
#6 0x55dedc562437 in rack::widget::Widget::removeChild(rack::widget::Widget) src/widget/Widget.cpp:119
#7 0x55dedc4f6b59 in rack::app::ModelBox::setTooltip(rack::ui::Tooltip) (/home/bruce/v1/Rack+0x481b59)
#8 0x55dedc4f7450 in rack::app::ModelBox::onHide(rack::event::Hide const&) src/app/ModuleBrowser.cpp:273
#9 0x55dedc476fb1 in void rack::widget::Widget::recurseEvent
#10 0x55dedc472deb in rack::widget::Widget::onHide(rack::event::Hide const&) include/widget/Widget.hpp:193
#11 0x55dedc476fb1 in void rack::widget::Widget::recurseEvent
#12 0x55dedc472deb in rack::widget::Widget::onHide(rack::event::Hide const&) include/widget/Widget.hpp:193
#13 0x55dedc476fb1 in void rack::widget::Widget::recurseEvent
#14 0x55dedc472deb in rack::widget::Widget::onHide(rack::event::Hide const&) include/widget/Widget.hpp:193
#15 0x55dedc476fb1 in void rack::widget::Widget::recurseEvent
#16 0x55dedc472deb in rack::widget::Widget::onHide(rack::event::Hide const&) include/widget/Widget.hpp:193
#17 0x55dedc476fb1 in void rack::widget::Widget::recurseEvent
#18 0x55dedc472deb in rack::widget::Widget::onHide(rack::event::Hide const&) include/widget/Widget.hpp:193
#19 0x55dedc476fb1 in void rack::widget::Widget::recurseEvent
#20 0x55dedc472deb in rack::widget::Widget::onHide(rack::event::Hide const&) include/widget/Widget.hpp:193
#21 0x55dedc561104 in rack::widget::Widget::hide() src/widget/Widget.cpp:45
#22 0x55dedc4f4c07 in chooseModel src/app/ModuleBrowser.cpp:97
#23 0x55dedc4f6d97 in rack::app::ModelBox::onButton(rack::event::Button const&) (/home/bruce/v1/Rack+0x481d97)
#24 0x55dedc473c2b in void rack::widget::Widget::recursePositionEvent
#25 0x55dedc472b1b in rack::widget::Widget::onButton(rack::event::Button const&) include/widget/Widget.hpp:150
#26 0x55dedc473c2b in void rack::widget::Widget::recursePositionEvent
#27 0x55dedc472b1b in rack::widget::Widget::onButton(rack::event::Button const&) include/widget/Widget.hpp:150
#28 0x55dedc473c2b in void rack::widget::Widget::recursePositionEvent
#29 0x55dedc472b1b in rack::widget::Widget::onButton(rack::event::Button const&) include/widget/Widget.hpp:150
previously allocated by thread T0 here:
#0 0x7fdfa295f17f in operator new(unsigned long) (/lib/x86_64-linux-gnu/libasan.so.5+0x10e17f)
#1 0x55dedc54e8f9 in __gnu_cxx::new_allocator
#2 0x55dedc54e429 in std::allocator_traits
#3 0x55dedc54dbf4 in std::__cxx11::_List_base
#4 0x55dedc54d0fb in std::_List_node
#6 0x55dedc5649cf in std::__cxx11::list
#7 0x55dedc561d1a in rack::widget::Widget::addChild(rack::widget::Widget) src/widget/Widget.cpp:91
#8 0x55dedc4f6c60 in rack::app::ModelBox::setTooltip(rack::ui::Tooltip) (/home/bruce/v1/Rack+0x481c60)
#9 0x55dedc4f72e3 in rack::app::ModelBox::onEnter(rack::event::Enter const&) src/app/ModuleBrowser.cpp:264
#10 0x55dedc45cb33 in rack::event::State::setHovered(rack::widget::Widget*) src/event.cpp:28
#11 0x55dedc45f2d4 in rack::event::State::handleHover(rack::math::Vec, rack::math::Vec) src/event.cpp:216
#12 0x55dedc44360f in cursorPosCallback src/window.cpp:152
#13 0x55dedc445b34 in rack::Window::run() src/window.cpp:342
#14 0x55dedc4675a7 in main src/main.cpp:186
#15 0x7fdfa1a3fb6a in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x26b6a)
```
Admittedly this was under Ubuntu 19, which isn't supported. But I doubt that is the cause of it.
BTW: here is how I modified the makefiles to run this:
```
bruce@ubuntu:~/v1$ git diff
diff --git a/Makefile b/Makefile
index c7d1922..7ca5e26 100644
--- a/Makefile
+++ b/Makefile
@@ -20,7 +20,7 @@ ifdef ARCH_LIN
SOURCES += dep/osdialog/osdialog_gtk2.c
build/dep/osdialog/osdialog_gtk2.c.o: FLAGS += $(shell pkg-config --cflags gtk+-2.0)
- LDFLAGS += -rdynamic \
+ LDFLAGS += -fsanitize=address -rdynamic \
dep/lib/libglfw3.a dep/lib/libGLEW.a dep/lib/libjansson.a dep/lib/libspeexdsp.a dep/lib/libzip.a dep/lib/libz.a dep/lib/librtmidi.a dep/lib/librtaudio.a dep/lib/libcurl.a dep/lib/libssl.a dep/lib/libcrypto.a \
-lpthread -lGL -ldl -lX11 -lasound -ljack \
$(shell pkg-config --libs gtk+-2.0)
@@ -37,7 +37,7 @@ endif
ifdef ARCH_WIN
SOURCES += dep/osdialog/osdialog_win.c
- LDFLAGS += -Wl,--export-all-symbols,--out-implib,libRack.a -mwindows \
+ LDFLAGS += -fsanitize=address -Wl,--export-all-symbols,--out-implib,libRack.a -mwindows \
dep/lib/libglew32.a dep/lib/libglfw3.a dep/lib/libjansson.a dep/lib/libspeexdsp.a dep/lib/libzip.a dep/lib/libz.a dep/lib/libcurl.a dep/lib/libssl.a dep/lib/libcrypto.a dep/lib/librtaudio.a dep/lib/librtmidi.a \
-lpthread -lopengl32 -lgdi32 -lws2_32 -lcomdlg32 -lole32 -ldsound -lwinmm -lksuser -lshlwapi -lmfplat -lmfuuid -lwmcodecdspuuid -ldbghelp
TARGET := Rack.exe
diff --git a/compile.mk b/compile.mk
index 81c55f3..1e78303 100644
--- a/compile.mk
+++ b/compile.mk
@@ -12,7 +12,7 @@ FLAGS += -MMD -MP
FLAGS += -g
-FLAGS += -O3 -march=nocona -funsafe-math-optimizations
+FLAGS += -O0 -march=nocona -funsafe-math-optimizations -fsanitize=address
FLAGS += -Wall -Wextra -Wno-unused-parameter
(END)```
I can't reproduce this. Could you send a rough patch that solves your crash?
Be happy to provide more info, not sure what you are asking for. This was inserting one of the fundamental module into an empty rack. Of course it doesn't crash in a real build, just with this address sanitizer build. It did this inserting any plugin.
I can reproduce this on mac.
Seems like the tooltip widget can remove itself from its parent's children list. So the iterator in recurseEvent and recursePositionEvent can be freed by the child's event handler.
Advancing the iterator after Widget* child = *it; in those functions rather than on the for loop line fixes it for me.
That sounds exactly like what I was seeing - excellent. After you fix that are you able to use rack with the sanitizer? @AndrewBelt do you have enough to go on?
Yep, I tried adding a bunch of fundamental modules and connecting them together without any failures. Then I tried adding my own module and it caught a read from null, so it's proving useful.
I'm going to guess that tooltips need to be enabled to see this issue.
As in the "View -> Parameter tooltips" option? The failure happens whether the option is enabled or not. The module browser uses a tooltip to display the description for each module when you hover over it.
Also, since I'm on a mac I'm using the clang address sanitizer rather than the GCC one, but the failure appears to be the same.
I can reproduce on macOS with clang's address sanitizer.
@iamscottmoyers change fixes that for me.
note that "View -> Parameter tooltips" was disabled for me.
after the iterator change, no issues, no crashes, no warnings.
Is this bug still present? After to many others verified this issue I have not gone to the trouble of trying it again. But it would be extremely useful to be able to run asan.
I just tried running with -fsanitize=address on the latest revision (966cf1c) and the bug is still present.
The patch @jwakely added (9d3deb9) fixes it. I've been running with the same fix locally since the end of August now and not had any issues with it.
I can create a pull request for the patch if wanted.
ok, tx for the info. @AndrewBelt, would you consider accepting that patch so we can use asan without having to monkey patch rack?
@jwakely Can you explain your patch? I can't tell how it's functionally different than what it was before.
I'm sure @jwakely can explain it, but I originally debugged the issue so can explain what's going on.
In the current code the iterator is advanced at the end of the for loop.
If child is a tooltip widget it's possible that the child's event handler can remove itself from the children list, which invalidates the iterator.
In the @jwakely patch the iterator is advanced before the child handler is called, so if the handler removes the child from the list the iterator has already been advanced so no invalid access is made.
This is the section of code that can remove the child from its parents list whilst iterating over it.
https://github.com/VCVRack/Rack/blob/966cf1c5b4a3ee0768a923437d132d09df6efcc8/src/app/ModuleBrowser.cpp#L220-L225
I'm still not really sure what's going on here. In the first recursePositionEvent iteration caused by onButton, it points to the Tooltip. It then goes onto the second iteration, setting it to the BrowserOverlay. Within (child->*f)(e2);, the Tooltip is deleted. I would assume that it is still valid in this case, because it's pointing to the BrowserOverlay, not the Tooltip. Can anyone give me insight?
I'm still happy to implement your fix because it allows children to delete themselves from within event handlers, but I don't understand how it fixes this tooltip use-after-free issue, because it's not a child deleting itself but a child deleting a widget that has already been iterated over.
Regardless, I've added a fix in f8ef68c. This does not rely on event propagation implementation details. I have decided not to adopt your patch because I want to continue to consider "delete self within an event handler" as undefined behavior.
Thanks! I've confirmed f8ef68c fixes this issue.
I'll copy in an address sanitizer run I got just before you committed the fix in case anyone wants to investigate the real reason it failed.
I see what you mean about the tooltip being deleted after we've advanced the iterator. It could be that the tooltip is also a child further up the tree that hasn't been advanced yet, I'm not familiar enough with the code to know if widgets can be part of multiple lists. The tooltip delete actually occurs at a much deeper nesting level than the invalid read occurs at.
=================================================================
==47157==ERROR: AddressSanitizer: heap-use-after-free on address 0x6030002064d0 at pc 0x0001003eee4d bp 0x7ffeef8efba0 sp 0x7ffeef8efb98
READ of size 8 at 0x6030002064d0 thread T0
#0 0x1003eee4c in std::__1::__list_iterator<rack::widget::Widget*, void*>::operator--() list:393
#1 0x1003edf3a in std::__1::reverse_iterator<std::__1::__list_iterator<rack::widget::Widget*, void*> >::operator++(int) iterator:692
#2 0x1003f5060 in void rack::widget::Widget::recursePositionEvent<void (rack::widget::Widget::*)(rack::event::Button const&), rack::event::Button>(void (rack::widget::Widget::*)(rack::event::Button const&), rack::event::Button const&) Widget.hpp:124
#3 0x1003f4610 in rack::widget::Widget::onButton(rack::event::Button const&) Widget.hpp:150
#4 0x1003f3f06 in rack::widget::OpaqueWidget::onButton(rack::event::Button const&) OpaqueWidget.hpp:21
#5 0x100377417 in rack::event::State::handleButton(rack::math::Vec, int, int, int) event.cpp:129
#6 0x1003c4d90 in rack::mouseButtonCallback(GLFWwindow*, int, int, int) window.cpp:122
#7 0x7fff385e8936 in -[NSWindow(NSEventRouting) _handleMouseDownEvent:isDelayedEvent:] (AppKit:x86_64+0x24f936)
#8 0x7fff3851f1a5 in -[NSWindow(NSEventRouting) _reallySendEvent:isDelayedEvent:] (AppKit:x86_64+0x1861a5)
#9 0x7fff3851e666 in -[NSWindow(NSEventRouting) sendEvent:] (AppKit:x86_64+0x185666)
#10 0x7fff383bde4a in -[NSApplication(NSEvent) sendEvent:] (AppKit:x86_64+0x24e4a)
#11 0x1006ff0f6 in _glfwPlatformPollEvents cocoa_window.m:1389
#12 0x1003c6e6b in rack::Window::run() window.cpp:341
#13 0x1003953a4 in main main.cpp:186
#14 0x100310033 in start (Rack:x86_64+0x100002033)
0x6030002064d0 is located 0 bytes inside of 24-byte region [0x6030002064d0,0x6030002064e8)
freed by thread T0 here:
#0 0x101057b42 in wrap__ZdlPv (libclang_rt.asan_osx_dynamic.dylib:x86_64h+0x6db42)
#1 0x1003554c4 in std::__1::_DeallocateCaller::__do_call(void*) new:320
#2 0x1003554a8 in std::__1::_DeallocateCaller::__do_deallocate_handle_size(void*, unsigned long) new:278
#3 0x100681af0 in std::__1::_DeallocateCaller::__do_deallocate_handle_size_align(void*, unsigned long, unsigned long) new:248
#4 0x100681ac4 in std::__1::__libcpp_deallocate(void*, unsigned long, unsigned long) new:326
#5 0x100681a7d in std::__1::allocator<std::__1::__list_node<rack::widget::Widget*, void*> >::deallocate(std::__1::__list_node<rack::widget::Widget*, void*>*, unsigned long) memory:1817
#6 0x100681904 in std::__1::allocator_traits<std::__1::allocator<std::__1::__list_node<rack::widget::Widget*, void*> > >::deallocate(std::__1::allocator<std::__1::__list_node<rack::widget::Widget*, void*> >&, std::__1::__list_node<rack::widget::Widget*, void*>*, unsigned long) memory:1555
#7 0x10067ed7a in std::__1::list<rack::widget::Widget*, std::__1::allocator<rack::widget::Widget*> >::erase(std::__1::__list_const_iterator<rack::widget::Widget*, void*>) list:1826
#8 0x10067e67b in rack::widget::Widget::removeChild(rack::widget::Widget*) Widget.cpp:119
#9 0x10046fb88 in rack::app::ModelBox::setTooltip(rack::ui::Tooltip*) ModuleBrowser.cpp:222
#10 0x10046e9f6 in rack::app::ModelBox::onHide(rack::event::Hide const&) ModuleBrowser.cpp:273
#11 0x1003f7f3d in void rack::widget::Widget::recurseEvent<void (rack::widget::Widget::*)(rack::event::Hide const&), rack::event::Hide>(void (rack::widget::Widget::*)(rack::event::Hide const&), rack::event::Hide const&) Widget.hpp:117
#12 0x1003eb460 in rack::widget::Widget::onHide(rack::event::Hide const&) Widget.hpp:193
#13 0x1003f7f3d in void rack::widget::Widget::recurseEvent<void (rack::widget::Widget::*)(rack::event::Hide const&), rack::event::Hide>(void (rack::widget::Widget::*)(rack::event::Hide const&), rack::event::Hide const&) Widget.hpp:117
#14 0x1003eb460 in rack::widget::Widget::onHide(rack::event::Hide const&) Widget.hpp:193
#15 0x1003f7f3d in void rack::widget::Widget::recurseEvent<void (rack::widget::Widget::*)(rack::event::Hide const&), rack::event::Hide>(void (rack::widget::Widget::*)(rack::event::Hide const&), rack::event::Hide const&) Widget.hpp:117
#16 0x1003eb460 in rack::widget::Widget::onHide(rack::event::Hide const&) Widget.hpp:193
#17 0x1003f7f3d in void rack::widget::Widget::recurseEvent<void (rack::widget::Widget::*)(rack::event::Hide const&), rack::event::Hide>(void (rack::widget::Widget::*)(rack::event::Hide const&), rack::event::Hide const&) Widget.hpp:117
#18 0x1003eb460 in rack::widget::Widget::onHide(rack::event::Hide const&) Widget.hpp:193
#19 0x1003f7f3d in void rack::widget::Widget::recurseEvent<void (rack::widget::Widget::*)(rack::event::Hide const&), rack::event::Hide>(void (rack::widget::Widget::*)(rack::event::Hide const&), rack::event::Hide const&) Widget.hpp:117
#20 0x1003eb460 in rack::widget::Widget::onHide(rack::event::Hide const&) Widget.hpp:193
#21 0x1003f7f3d in void rack::widget::Widget::recurseEvent<void (rack::widget::Widget::*)(rack::event::Hide const&), rack::event::Hide>(void (rack::widget::Widget::*)(rack::event::Hide const&), rack::event::Hide const&) Widget.hpp:117
#22 0x1003eb460 in rack::widget::Widget::onHide(rack::event::Hide const&) Widget.hpp:193
#23 0x100679077 in rack::widget::Widget::hide() Widget.cpp:45
#24 0x10046f83d in rack::app::chooseModel(rack::plugin::Model*) ModuleBrowser.cpp:97
#25 0x10046e0d8 in rack::app::ModelBox::onButton(rack::event::Button const&) ModuleBrowser.cpp:239
#26 0x1003f500b in void rack::widget::Widget::recursePositionEvent<void (rack::widget::Widget::*)(rack::event::Button const&), rack::event::Button>(void (rack::widget::Widget::*)(rack::event::Button const&), rack::event::Button const&) Widget.hpp:139
#27 0x1003f4610 in rack::widget::Widget::onButton(rack::event::Button const&) Widget.hpp:150
#28 0x1003f500b in void rack::widget::Widget::recursePositionEvent<void (rack::widget::Widget::*)(rack::event::Button const&), rack::event::Button>(void (rack::widget::Widget::*)(rack::event::Button const&), rack::event::Button const&) Widget.hpp:139
#29 0x1003f4610 in rack::widget::Widget::onButton(rack::event::Button const&) Widget.hpp:150
previously allocated by thread T0 here:
#0 0x101057542 in wrap__Znwm (libclang_rt.asan_osx_dynamic.dylib:x86_64h+0x6d542)
#1 0x1004e4968 in std::__1::__libcpp_allocate(unsigned long, unsigned long) new:239
#2 0x1004e48b1 in std::__1::allocator<std::__1::__list_node<rack::widget::Widget*, void*> >::allocate(unsigned long, void const*) memory:1814
#3 0x1004e47f0 in std::__1::allocator_traits<std::__1::allocator<std::__1::__list_node<rack::widget::Widget*, void*> > >::allocate(std::__1::allocator<std::__1::__list_node<rack::widget::Widget*, void*> >&, unsigned long) memory:1547
#4 0x1004e4210 in std::__1::list<rack::widget::Widget*, std::__1::allocator<rack::widget::Widget*> >::__allocate_node(std::__1::allocator<std::__1::__list_node<rack::widget::Widget*, void*> >&) list:1108
#5 0x1004e383d in std::__1::list<rack::widget::Widget*, std::__1::allocator<rack::widget::Widget*> >::push_back(rack::widget::Widget* const&) list:1610
#6 0x10067d703 in rack::widget::Widget::addChild(rack::widget::Widget*) Widget.cpp:91
#7 0x10046fcd7 in rack::app::ModelBox::setTooltip(rack::ui::Tooltip*) ModuleBrowser.cpp:228
#8 0x10046e7f3 in rack::app::ModelBox::onEnter(rack::event::Enter const&) ModuleBrowser.cpp:264
#9 0x1003753e1 in rack::event::State::setHovered(rack::widget::Widget*) event.cpp:28
#10 0x100378a73 in rack::event::State::handleHover(rack::math::Vec, rack::math::Vec) event.cpp:216
#11 0x1003c9831 in rack::cursorPosCallback(GLFWwindow*, double, double) window.cpp:154
#12 0x1003c7222 in rack::Window::run() window.cpp:354
#13 0x1003953a4 in main main.cpp:186
#14 0x100310033 in start (Rack:x86_64+0x100002033)
SUMMARY: AddressSanitizer: heap-use-after-free list:393 in std::__1::__list_iterator<rack::widget::Widget*, void*>::operator--()
Shadow bytes around the buggy address:
0x1c0600040c40: fd fd fd fa fa fa fd fd fd fd fa fa fd fd fd fd
0x1c0600040c50: fa fa fd fd fd fd fa fa fd fd fd fa fa fa fd fd
0x1c0600040c60: fd fa fa fa fd fd fd fd fa fa fd fd fd fa fa fa
0x1c0600040c70: fd fd fd fa fa fa fd fd fd fa fa fa fd fd fd fd
0x1c0600040c80: fa fa fd fd fd fa fa fa fd fd fd fa fa fa fd fd
=>0x1c0600040c90: fd fa fa fa fd fd fd fd fa fa[fd]fd fd fa fa fa
0x1c0600040ca0: fd fd fd fa fa fa fd fd fd fd fa fa fd fd fd fa
0x1c0600040cb0: fa fa fd fd fd fd fa fa fd fd fd fd fa fa fd fd
0x1c0600040cc0: fd fd fa fa fd fd fd fa fa fa fd fd fd fa fa fa
0x1c0600040cd0: fd fd fd fd fa fa fd fd fd fa fa fa fd fd fd fa
0x1c0600040ce0: fa fa fd fd fd fa fa fa fd fd fd fa fa fa fd fd
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
Shadow gap: cc
==47157==ABORTING
Abort trap: 6
I would assume that it is still valid in this case, because it's pointing to the BrowserOverlay, not the Tooltip. Can anyone give me insight?
I think I've figured it out. children is a std::list so when the Tooltip is deleted it only invalidates iterators that reference it. However, the recursePositionEvent iterator is a std::reverse_iterator. Reverse iterators internally store an iterator to the element next to the one they refer to (https://en.cppreference.com/w/cpp/iterator/reverse_iterator). So if *it is one after the Tooltip then it internally stores an iterator referencing Tooltip. The it++ would then use the invalidated iterator to move on from the BrowserOverlay.
Okay, makes sense. It would make more sense to implement std::list's reverse_iteator as its own class that just calls list = list->prev in its operator++(), but it seems to be done that way for generality because it makes more sense with data structures like std::vector.
@AndrewBelt I find it hard to argue with that. And not just list::reverse_iterator. Every container's reverse iterator would have less confusing semantics if implemented by hand for each container, and std::reverse_iterator didn't exist. The advantages of the confusing std::reverse_iterarator semantics are not having to implement (const and non-const) reverse iterators for every container, and being consistently confusing.