From 3a8db49573f6ff79bbb55bc98aafab7b5bd68ef1 Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Fri, 15 Mar 2024 14:29:47 +0000 Subject: [PATCH] C++: Add tests for 'cpp/iterator-to-expired-container'. NOTE: This is with the yet-to-be-merged changes to the extractor and IR generation. --- .../IteratorToExpiredContainer.expected | 10 + .../CWE-416/IteratorToExpiredContainer.qlref | 1 + .../query-tests/Security/CWE/CWE-416/test.cpp | 719 ++++++++++++++++++ 3 files changed, 730 insertions(+) create mode 100644 cpp/ql/test/experimental/query-tests/Security/CWE/CWE-416/IteratorToExpiredContainer.expected create mode 100644 cpp/ql/test/experimental/query-tests/Security/CWE/CWE-416/IteratorToExpiredContainer.qlref create mode 100644 cpp/ql/test/experimental/query-tests/Security/CWE/CWE-416/test.cpp diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-416/IteratorToExpiredContainer.expected b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-416/IteratorToExpiredContainer.expected new file mode 100644 index 00000000000..39588f865be --- /dev/null +++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-416/IteratorToExpiredContainer.expected @@ -0,0 +1,10 @@ +| test.cpp:680:30:680:30 | call to operator[] | This object is destroyed before $@ is called. | test.cpp:680:17:680:17 | call to begin | call to begin | +| test.cpp:680:30:680:30 | call to operator[] | This object is destroyed before $@ is called. | test.cpp:680:17:680:17 | call to end | call to end | +| test.cpp:683:31:683:32 | call to at | This object is destroyed before $@ is called. | test.cpp:683:17:683:17 | call to begin | call to begin | +| test.cpp:683:31:683:32 | call to at | This object is destroyed before $@ is called. | test.cpp:683:17:683:17 | call to end | call to end | +| test.cpp:689:16:689:28 | temporary object | This object is destroyed before $@ is called. | test.cpp:689:30:689:34 | call to begin | call to begin | +| test.cpp:689:45:689:57 | temporary object | This object is destroyed before $@ is called. | test.cpp:689:59:689:61 | call to end | call to end | +| test.cpp:702:26:702:26 | call to operator[] | This object is destroyed before $@ is called. | test.cpp:703:18:703:22 | call to begin | call to begin | +| test.cpp:702:26:702:26 | call to operator[] | This object is destroyed before $@ is called. | test.cpp:703:35:703:37 | call to end | call to end | +| test.cpp:716:35:716:47 | temporary object | This object is destroyed before $@ is called. | test.cpp:716:16:716:16 | call to begin | call to begin | +| test.cpp:716:35:716:47 | temporary object | This object is destroyed before $@ is called. | test.cpp:716:16:716:16 | call to end | call to end | diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-416/IteratorToExpiredContainer.qlref b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-416/IteratorToExpiredContainer.qlref new file mode 100644 index 00000000000..5f86bb26ff0 --- /dev/null +++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-416/IteratorToExpiredContainer.qlref @@ -0,0 +1 @@ +experimental/Security/CWE/CWE-416/IteratorToExpiredContainer.ql diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-416/test.cpp b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-416/test.cpp new file mode 100644 index 00000000000..c035fe203a8 --- /dev/null +++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-416/test.cpp @@ -0,0 +1,719 @@ + +typedef unsigned long size_t; + +template +struct remove_const { typedef T type; }; + +template +struct remove_const { typedef T type; }; + +// `remove_const_t` removes any `const` specifier from `T` +template +using remove_const_t = typename remove_const::type; + +template +struct remove_reference { typedef T type; }; + +template +struct remove_reference { typedef T type; }; + +template +struct remove_reference { typedef T type; }; + +// `remove_reference_t` removes any `&` from `T` +template +using remove_reference_t = typename remove_reference::type; + +template +struct decay_impl { + typedef T type; +}; + +template +struct decay_impl { + typedef T* type; +}; + +template +using decay_t = typename decay_impl>::type; + + +namespace std +{ + template constexpr T&& forward(remove_reference_t& t) noexcept; + template constexpr T&& forward(remove_reference_t&& t) noexcept; +} + +// --- iterator --- + +namespace std { + struct ptrdiff_t; + + template struct iterator_traits; + + template + struct iterator { + typedef Category iterator_category; + + iterator(); + iterator(iterator > const &other); // non-const -> const conversion constructor + + iterator &operator++(); + iterator operator++(int); + iterator &operator--(); + iterator operator--(int); + bool operator==(iterator other) const; + bool operator!=(iterator other) const; + reference_type operator*() const; + pointer_type operator->() const; + iterator operator+(int); + iterator operator-(int); + iterator &operator+=(int); + iterator &operator-=(int); + int operator-(iterator); + reference_type operator[](int); + }; + + struct input_iterator_tag {}; + struct forward_iterator_tag : public input_iterator_tag {}; + struct bidirectional_iterator_tag : public forward_iterator_tag {}; + struct random_access_iterator_tag : public bidirectional_iterator_tag {}; + + struct output_iterator_tag {}; + + template + class back_insert_iterator { + protected: + Container* container = nullptr; + public: + using iterator_category = output_iterator_tag; + using value_type = void; + using difference_type = ptrdiff_t; + using pointer = void; + using reference = void; + using container_type = Container; + constexpr back_insert_iterator() noexcept = default; + constexpr explicit back_insert_iterator(Container& x); + back_insert_iterator& operator=(const typename Container::value_type& value); + back_insert_iterator& operator=(typename Container::value_type&& value); + back_insert_iterator& operator*(); + back_insert_iterator& operator++(); + back_insert_iterator operator++(int); + }; + + template + constexpr back_insert_iterator back_inserter(Container& x) { + return back_insert_iterator(x); + } + + template + class front_insert_iterator { + protected: + Container* container = nullptr; + public: + using iterator_category = output_iterator_tag; + using value_type = void; + using difference_type = ptrdiff_t; + using pointer = void; + using reference = void; + using container_type = Container; + constexpr front_insert_iterator() noexcept = default; + constexpr explicit front_insert_iterator(Container& x); + constexpr front_insert_iterator& operator=(const typename Container::value_type& value); + constexpr front_insert_iterator& operator=(typename Container::value_type&& value); + constexpr front_insert_iterator& operator*(); + constexpr front_insert_iterator& operator++(); + constexpr front_insert_iterator operator++(int); + }; + template + constexpr front_insert_iterator front_inserter(Container& x) { + return front_insert_iterator(x); + } +} + +// --- string --- + +namespace std +{ + template struct char_traits; + + typedef size_t streamsize; + + template class allocator { + public: + allocator() throw(); + typedef size_t size_type; + }; + + template, class Allocator = allocator > + class basic_string { + public: + using value_type = charT; + using reference = value_type&; + using const_reference = const value_type&; + typedef typename Allocator::size_type size_type; + static const size_type npos = -1; + + explicit basic_string(const Allocator& a = Allocator()); + basic_string(const charT* s, const Allocator& a = Allocator()); + template basic_string(InputIterator begin, InputIterator end, const Allocator& a = Allocator()); + + const charT* c_str() const; + charT* data() noexcept; + size_t length() const; + + typedef std::iterator iterator; + typedef std::iterator const_iterator; + + iterator begin(); + iterator end(); + const_iterator begin() const; + const_iterator end() const; + const_iterator cbegin() const; + const_iterator cend() const; + + void push_back(charT c); + + const charT& front() const; + charT& front(); + const charT& back() const; + charT& back(); + + const_reference operator[](size_type pos) const; + reference operator[](size_type pos); + const_reference at(size_type n) const; + reference at(size_type n); + template basic_string& operator+=(const T& t); + basic_string& operator+=(const charT* s); + basic_string& append(const basic_string& str); + basic_string& append(const charT* s); + basic_string& append(size_type n, charT c); + template basic_string& append(InputIterator first, InputIterator last); + basic_string& assign(const basic_string& str); + basic_string& assign(size_type n, charT c); + template basic_string& assign(InputIterator first, InputIterator last); + basic_string& insert(size_type pos, const basic_string& str); + basic_string& insert(size_type pos, size_type n, charT c); + basic_string& insert(size_type pos, const charT* s); + iterator insert(const_iterator p, size_type n, charT c); + template iterator insert(const_iterator p, InputIterator first, InputIterator last); + basic_string& replace(size_type pos1, size_type n1, const basic_string& str); + basic_string& replace(size_type pos1, size_type n1, size_type n2, charT c); + size_type copy(charT* s, size_type n, size_type pos = 0) const; + void clear() noexcept; + basic_string substr(size_type pos = 0, size_type n = npos) const; + void swap(basic_string& s) noexcept/*(allocator_traits::propagate_on_container_swap::value || allocator_traits::is_always_equal::value)*/; + }; + + template basic_string operator+(const basic_string& lhs, const basic_string& rhs); + template basic_string operator+(const basic_string& lhs, const charT* rhs); + + typedef basic_string string; +} + +// --- istring / ostream / stringstream --- + +namespace std +{ + template > + class basic_istream /*: virtual public basic_ios - not needed for this test */ { + public: + using char_type = charT; + using int_type = int; //typename traits::int_type; + + basic_istream& operator>>(int& n); + + int_type get(); + basic_istream& get(char_type& c); + basic_istream& get(char_type* s, streamsize n); + int_type peek(); + basic_istream& read (char_type* s, streamsize n); + streamsize readsome(char_type* s, streamsize n); + basic_istream& putback(char_type c); + basic_istream& unget(); + + basic_istream& getline(char_type* s, streamsize n); + basic_istream& getline(char_type* s, streamsize n, char_type delim); + }; + + template basic_istream& operator>>(basic_istream&, charT*); + template basic_istream& operator>>(basic_istream& is, basic_string& str); + + template basic_istream& getline(basic_istream& is, basic_string& str, charT delim); + template basic_istream& getline(basic_istream& is, basic_string& str); + + template > + class basic_ostream /*: virtual public basic_ios - not needed for this test */ { + public: + typedef charT char_type; + + basic_ostream& operator<<(int n); + + basic_ostream& put(char_type c); + basic_ostream& write(const char_type* s, streamsize n); + basic_ostream& flush(); + }; + + template basic_ostream& operator<<(basic_ostream&, const charT*); + template basic_ostream& operator<<(basic_ostream& os, const basic_string& str); + + template> + class basic_iostream : public basic_istream, public basic_ostream { + public: + }; + + template, class Allocator = allocator> + class basic_stringstream : public basic_iostream { + public: + explicit basic_stringstream(/*ios_base::openmode which = ios_base::out|ios_base::in - not needed for this test*/); + explicit basic_stringstream( const basic_string& str/*, ios_base::openmode which = ios_base::out | ios_base::in*/); + basic_stringstream(const basic_stringstream& rhs) = delete; + basic_stringstream(basic_stringstream&& rhs); + basic_stringstream& operator=(const basic_stringstream& rhs) = delete; + basic_stringstream& operator=(basic_stringstream&& rhs); + + void swap(basic_stringstream& rhs); + + basic_string str() const; + void str(const basic_string& str); + }; + + typedef basic_istream istream; + typedef basic_ostream ostream; + extern istream cin; + extern ostream cout; + + using stringstream = basic_stringstream; +} + +// --- vector --- + +namespace std { + template> + class vector { + public: + using value_type = T; + using reference = value_type&; + using const_reference = const value_type&; + using size_type = unsigned int; + using iterator = std::iterator; + using const_iterator = std::iterator; + + vector() noexcept(noexcept(Allocator())); + vector(const std::vector&); + explicit vector(const Allocator&) noexcept; + explicit vector(size_type n, const Allocator& = Allocator()); + vector(size_type n, const T& value, const Allocator& = Allocator()); + template vector(InputIterator first, InputIterator last, const Allocator& = Allocator()); + // use of `iterator_category` makes sure InputIterator is (probably) an iterator, and not an `int` or + // similar that should match a different overload (SFINAE). + ~vector(); + + vector& operator=(const vector& x); + vector& operator=(vector&& x) noexcept/*(allocator_traits::propagate_on_container_move_assignment::value || allocator_traits::is_always_equal::value)*/; + template void assign(InputIterator first, InputIterator last); + // use of `iterator_category` makes sure InputIterator is (probably) an iterator, and not an `int` or + // similar that should match a different overload (SFINAE). + void assign(size_type n, const T& u); + + iterator begin() noexcept; + const_iterator begin() const noexcept; + iterator end() noexcept; + const_iterator end() const noexcept; + + size_type size() const noexcept; + + reference operator[](size_type n); + const_reference operator[](size_type n) const; + const_reference at(size_type n) const; + reference at(size_type n); + reference front(); + const_reference front() const; + reference back(); + const_reference back() const; + + T* data() noexcept; + const T* data() const noexcept; + + void push_back(const T& x); + void push_back(T&& x); + + iterator insert(const_iterator position, const T& x); + iterator insert(const_iterator position, T&& x); + iterator insert(const_iterator position, size_type n, const T& x); + template iterator insert(const_iterator position, InputIterator first, InputIterator last); + + template iterator emplace (const_iterator position, Args&&... args); + template void emplace_back (Args&&... args); + + void swap(vector&) noexcept/*(allocator_traits::propagate_on_container_swap::value || allocator_traits::is_always_equal::value)*/; + + void clear() noexcept; + }; +} + +// --- make_shared / make_unique --- + +namespace std { + template + class shared_ptr { + public: + shared_ptr() noexcept; + explicit shared_ptr(T*); + shared_ptr(const shared_ptr&) noexcept; + template shared_ptr(const shared_ptr&) noexcept; + template shared_ptr(shared_ptr&&) noexcept; + + shared_ptr& operator=(const shared_ptr&) noexcept; + shared_ptr& operator=(shared_ptr&&) noexcept; + + T& operator*() const noexcept; + T* operator->() const noexcept; + + T* get() const noexcept; + }; + + template + class unique_ptr { + public: + constexpr unique_ptr() noexcept; + explicit unique_ptr(T*) noexcept; + unique_ptr(unique_ptr&&) noexcept; + + unique_ptr& operator=(unique_ptr&&) noexcept; + + T& operator*() const; + T* operator->() const noexcept; + + T* get() const noexcept; + }; + + template unique_ptr make_unique(Args&&...); + + template shared_ptr make_shared(Args&&...); +} + +// --- pair --- + +namespace std { + template + struct pair { + typedef T1 first_type; + typedef T2 second_type; + + T1 first; + T2 second; + pair(); + pair(const T1& x, const T2& y); + template pair(const pair &p); + + void swap(pair& p) /*noexcept(...)*/; + }; + + template constexpr pair, decay_t> make_pair(T1&& x, T2&& y) { + return pair, decay_t>(std::forward(x), std::forward(y)); + } +} + +// --- map --- + +namespace std { + template struct less; + + template, class Allocator = allocator>> + class map { + public: + using key_type = Key; + using mapped_type = T; + using value_type = pair; + using iterator = std::iterator; + using const_iterator = std::iterator; + + map(); + map(const map& x); + map(map&& x); + ~map(); + + map& operator=(const map& x); + map& operator=(map&& x) /*noexcept(allocator_traits::is_always_equal::value && is_nothrow_move_assignable_v)*/; + + iterator begin() noexcept; + const_iterator begin() const noexcept; + iterator end() noexcept; + const_iterator end() const noexcept; + + T& operator[](const key_type& x); + T& operator[](key_type&& x); + T& at(const key_type& x); + const T& at(const key_type& x) const; + + template pair emplace(Args&&... args); + template iterator emplace_hint(const_iterator position, Args&&... args); + + pair insert(const value_type& x); + pair insert(value_type&& x); + iterator insert(const_iterator position, const value_type& x); + iterator insert(const_iterator position, value_type&& x); + + template pair try_emplace(const key_type& k, Args&&... args); + template pair try_emplace(key_type&& k, Args&&... args); + template iterator try_emplace(const_iterator hint, const key_type& k, Args&&... args); + template iterator try_emplace(const_iterator hint, key_type&& k, Args&&... args); + template pair insert_or_assign(const key_type& k, M&& obj); + template pair insert_or_assign(key_type&& k, M&& obj); + template iterator insert_or_assign(const_iterator hint, const key_type& k, M&& obj); + template iterator insert_or_assign(const_iterator hint, key_type&& k, M&& obj); + + iterator erase(iterator position); + iterator erase(const_iterator position); + iterator erase(const_iterator first, const_iterator last); + void swap(map&) /*noexcept(/*==allocator_traits::is_always_equal::value && is_nothrow_swappable_v)*/; + void clear() noexcept; + + template void merge(map& source); + template void merge(map&& source); + + iterator find(const key_type& x); + const_iterator find(const key_type& x) const; + + iterator lower_bound(const key_type& x); + const_iterator lower_bound(const key_type& x) const; + iterator upper_bound(const key_type& x); + const_iterator upper_bound(const key_type& x) const; + + pair equal_range(const key_type& x); + pair equal_range(const key_type& x) const; + }; + + template struct hash; + template struct equal_to; + + template, class Pred = equal_to, class Allocator = allocator>> + class unordered_map { + public: + using key_type = Key; + using mapped_type = T; + using value_type = pair; + using iterator = std::iterator; + using const_iterator = std::iterator; + + unordered_map(); + unordered_map(const unordered_map&); + unordered_map(unordered_map&&); + ~unordered_map(); + + unordered_map& operator=(const unordered_map&); + unordered_map& operator=(unordered_map&&) /*noexcept(allocator_traits::is_always_equal::value && is_nothrow_move_assignable_v && is_nothrow_move_assignable_v)*/; + + iterator begin() noexcept; + const_iterator begin() const noexcept; + iterator end() noexcept; + const_iterator end() const noexcept; + + mapped_type& operator[](const key_type& k); + mapped_type& operator[](key_type&& k); + mapped_type& at(const key_type& k); + const mapped_type& at(const key_type& k) const; + + template pair emplace(Args&&... args); + template iterator emplace_hint(const_iterator position, Args&&... args); + + pair insert(const value_type& obj); + pair insert(value_type&& obj); + iterator insert(const_iterator hint, const value_type& obj); + iterator insert(const_iterator hint, value_type&& obj); + + template pair try_emplace(const key_type& k, Args&&... args); + template pair try_emplace(key_type&& k, Args&&... args); + template iterator try_emplace(const_iterator hint, const key_type& k, Args&&... args); + template iterator try_emplace(const_iterator hint, key_type&& k, Args&&... args); + template pair insert_or_assign(const key_type& k, M&& obj); + template pair insert_or_assign(key_type&& k, M&& obj); + template iterator insert_or_assign(const_iterator hint, const key_type& k, M&& obj); + template iterator insert_or_assign(const_iterator hint, key_type&& k, M&& obj); + + iterator erase(iterator position); + iterator erase(const_iterator position); + iterator erase(const_iterator first, const_iterator last); + void swap(unordered_map&) /*noexcept(allocator_traits::is_always_equal::value && is_nothrow_swappable_v && is_nothrow_swappable_v)*/; + void clear() noexcept; + + template void merge(unordered_map& source); + template void merge(unordered_map&& source); + + iterator find(const key_type& k); + const_iterator find(const key_type& k) const; + + pair equal_range(const key_type& k); + pair equal_range(const key_type& k) const; + }; +}; + +// --- set --- + +namespace std { + template, class Allocator = allocator> + class set { + public: + using key_type = Key; + using value_type = Key; + using size_type = size_t; + using allocator_type = Allocator; + using iterator = std::iterator; + using const_iterator = std::iterator; + + set(); + set(const set& x); + set(set&& x); + template set(InputIterator first, InputIterator last/*, const Compare& comp = Compare(), const Allocator& = Allocator()*/); + ~set(); + + set& operator=(const set& x); + set& operator=(set&& x) noexcept/*(allocator_traits::is_always_equal::value && is_nothrow_move_assignable_v)*/; + + iterator begin() noexcept; + const_iterator begin() const noexcept; + iterator end() noexcept; + const_iterator end() const noexcept; + + template pair emplace(Args&&... args); + template iterator emplace_hint(const_iterator position, Args&&... args); + pair insert(const value_type& x); + pair insert(value_type&& x); + iterator insert(const_iterator position, const value_type& x); + iterator insert(const_iterator position, value_type&& x); + template void insert(InputIterator first, InputIterator last); + + iterator erase(iterator position); + iterator erase(const_iterator position); + iterator erase(const_iterator first, const_iterator last); + void swap(set&) noexcept/*(allocator_traits::is_always_equal::value && is_nothrow_swappable_v)*/; + void clear() noexcept; + + template void merge(set& source); + template void merge(set&& source); + + iterator find(const key_type& x); + const_iterator find(const key_type& x) const; + + iterator lower_bound(const key_type& x); + const_iterator lower_bound(const key_type& x) const; + iterator upper_bound(const key_type& x); + const_iterator upper_bound(const key_type& x) const; + pair equal_range(const key_type& x); + pair equal_range(const key_type& x) const; + }; + + template, class Pred = equal_to, class Allocator = allocator> + class unordered_set { + public: + using key_type = Key; + using value_type = Key; + using hasher = Hash; + using key_equal = Pred; + using allocator_type = Allocator; + using size_type = size_t; + using iterator = std::iterator; + using const_iterator = std::iterator; + + unordered_set(); + unordered_set(const unordered_set&); + unordered_set(unordered_set&&); + template unordered_set(InputIterator f, InputIterator l, size_type n = 0/*, const hasher& hf = hasher(), const key_equal& eql = key_equal(), const allocator_type& a = allocator_type()*/); + ~unordered_set(); + + unordered_set& operator=(const unordered_set&); + unordered_set& operator=(unordered_set&&) noexcept/*(allocator_traits::is_always_equal::value && is_nothrow_move_assignable_v && is_nothrow_move_assignable_v)*/; + + iterator begin() noexcept; + const_iterator begin() const noexcept; + iterator end() noexcept; + const_iterator end() const noexcept; + + template pair emplace(Args&&... args); + template iterator emplace_hint(const_iterator position, Args&&... args); + pair insert(const value_type& obj); + pair insert(value_type&& obj); + iterator insert(const_iterator hint, const value_type& obj); + iterator insert(const_iterator hint, value_type&& obj); + template void insert(InputIterator first, InputIterator last); + + iterator erase(iterator position); + iterator erase(const_iterator position); + iterator erase(const_iterator first, const_iterator last); + void swap(unordered_set&) noexcept/*(allocator_traits::is_always_equal::value && is_nothrow_swappable_v && is_nothrow_swappable_v)*/; + void clear() noexcept; + + template void merge(unordered_set& source); + template void merge(unordered_set&& source); + + iterator find(const key_type& k); + const_iterator find(const key_type& k) const; + pair equal_range(const key_type& k); + pair equal_range(const key_type& k) const; + }; +} + +std::vector> returnValue(); +std::vector>& returnRef(); + +std::vector> external_by_value(std::vector>); + +std::vector> external_by_const_ref(const std::vector>&); + +// *: Will be detected once extract destruction of unnamed temporaries and generate IR for them + +const std::vector>& return_self_by_ref(const std::vector>& v) { + return v; +} + +std::vector> return_self_by_value(const std::vector>& v) { + return v; +} + +void test() { + for (auto x : returnValue()) {} // GOOD + for (auto x : returnValue()[0]) {} // BAD [NOT DETECTED] (see *) + for (auto x : external_by_value(returnValue())) {} // GOOD + for (auto x : external_by_const_ref(returnValue())) {} // GOOD + for (auto x : returnValue().at(0)) {} // BAD [NOT DETECTED] (see *) + + for (auto x : returnRef()) {} // GOOD + for (auto x : returnRef()[0]) {} // GOOD + for (auto x : returnRef().at(0)) {} // GOOD + + for(auto it = returnValue().begin(); it != returnValue().end(); ++it) {} // BAD + + { + auto v = returnValue(); + for(auto it = v.begin(); it != v.end(); ++it) {} // GOOD + } + + { + auto&& v = returnValue(); + for(auto it = v.begin(); it != v.end(); ++it) {} // GOOD + } + + { + auto&& v = returnValue()[0]; + for(auto it = v.begin(); it != v.end(); ++it) {} // BAD [NOT DETECTED] (see *) + } + + { + auto&& v = returnRef(); + for(auto it = v.begin(); it != v.end(); ++it) {} // GOOD + } + + { + auto&& v = returnRef()[0]; + for(auto it = v.begin(); it != v.end(); ++it) {} // GOOD + } + + for (auto x : return_self_by_ref(returnValue())) {} // BAD [NOT DETECTED] (see *) + + for (auto x : return_self_by_value(returnValue())) {} // GOOD +}