Copilot commented on code in PR #13616:
URL: https://github.com/apache/trafficserver/pull/13616#discussion_r3972321831


##########
include/tsutil/Metrics.h:
##########
@@ -194,15 +252,25 @@ class Metrics
   // Static methods to encapsulate access to the atomic's
   class iterator
   {
+    friend class Metrics;
+
+    /// Tag for the end sentinel, which has no position and reads no storage.
+    struct end_tag {
+    };
+
+    // Only Metrics hands these out, through begin(), end() and find(). A 
caller that could name an
+    // arbitrary position could name an unlisted one, which iteration must 
never visit.
+    explicit iterator(const Metrics &m);
+    iterator(const Metrics &m, IdType pos);
+    iterator(const Metrics &m, end_tag);

Review Comment:
   These constructors were previously publicly usable (notably the 
`iterator(const Metrics&, IdType)` constructor is removed from the public 
section), and are now restricted to `Metrics` via `friend` + private 
constructors. That is a source-level API breaking change for any code 
constructing iterators directly.\n\n**Recommendation (moderate):** If external 
callers are expected to use `Metrics::iterator` (even incidentally), consider 
preserving a safe public construction path (e.g., keep the constructor public 
but ensure it cannot represent an unlisted position by normalizing unlisted 
positions to `end()`), or provide an explicit factory/API that replaces the old 
usage while keeping builds from breaking.



##########
include/tsutil/Metrics.h:
##########
@@ -231,43 +299,81 @@ class Metrics
       return std::make_tuple(name, type, metric->_value.load());
     }
 
+    /** Equality.
+     *
+     * Three way rather than a plain position compare: any exhausted iterator 
equals the end
+     * sentinel, and equals any other exhausted iterator, since two of them 
may have skipped a
+     * different number of unlisted slots. Two live iterators still compare by 
position.
+     *
+     * Two positional iterators may hold different snapshots, so exhaustion 
between them is judged
+     * against the earlier bound. Otherwise a walk could pass its own bound 
while a stop iterator
+     * made later was still live: they would never compare equal and @c 
operator++ could not make
+     * progress. The sentinel keeps its own answer, since its bound is 
meaningless.
+     *
+     * @note Only iterators taken from the same snapshot are meaningfully 
comparable with each
+     *   other. Because exhaustion is a property of an iterator's own bound, 
two taken at different
+     *   times can compare equal to each other while disagreeing about @c end, 
so this is not a
+     *   total equivalence relation and these are not iterators to hand to a 
generic algorithm.
+     *   Use @c end to test for exhaustion.
+     */
     bool
     operator==(const iterator &o) const
     {
-      return _it == o._it && std::addressof(_metrics) == 
std::addressof(o._metrics);
-    }
+      if (std::addressof(_metrics) != std::addressof(o._metrics)) {
+        return false;
+      }
 
-    bool
-    operator!=(const iterator &o) const
-    {
-      return _it != o._it || std::addressof(_metrics) != 
std::addressof(o._metrics);
+      if (_end || o._end) {
+        return at_end() == o.at_end();
+      }
+
+      auto const bound = _bound < o._bound ? _bound : o._bound;
+      bool const a = _it >= bound, b = o._it >= bound;
+
+      if (a || b) {
+        return a && b;
+      }
+      return _it == o._it;
     }

Review Comment:
   `Metrics::iterator` declares `std::input_iterator_tag` but `operator==` is 
explicitly designed (per the new comment) to *not* behave like a 
standard-conforming iterator equality (e.g., two iterators can compare equal 
while one is still dereferenceable under its own bound). This breaks iterator 
requirements and can cause incorrect behavior/UB if the iterator is ever used 
with standard algorithms that assume iterator equality is an equivalence 
relation and that `a == b` implies interchangeable iterator 
state.\n\n**Recommendation (mandatory):** Either (1) adjust the iterator so it 
truly models an input iterator (make `operator==` a real equivalence relation 
and ensure equal iterators have consistent dereference/exhaustion semantics), 
or (2) redesign to avoid presenting this as a standard iterator (e.g., provide 
an explicit snapshot/range view type with well-defined semantics, or otherwise 
prevent/avoid use in generic algorithms).



##########
include/tsutil/Metrics.h:
##########
@@ -194,15 +252,25 @@ class Metrics
   // Static methods to encapsulate access to the atomic's
   class iterator
   {
+    friend class Metrics;
+
+    /// Tag for the end sentinel, which has no position and reads no storage.
+    struct end_tag {
+    };
+
+    // Only Metrics hands these out, through begin(), end() and find(). A 
caller that could name an
+    // arbitrary position could name an unlisted one, which iteration must 
never visit.
+    explicit iterator(const Metrics &m);
+    iterator(const Metrics &m, IdType pos);
+    iterator(const Metrics &m, end_tag);
+
   public:
     using iterator_category = std::input_iterator_tag;

Review Comment:
   `Metrics::iterator` declares `std::input_iterator_tag` but `operator==` is 
explicitly designed (per the new comment) to *not* behave like a 
standard-conforming iterator equality (e.g., two iterators can compare equal 
while one is still dereferenceable under its own bound). This breaks iterator 
requirements and can cause incorrect behavior/UB if the iterator is ever used 
with standard algorithms that assume iterator equality is an equivalence 
relation and that `a == b` implies interchangeable iterator 
state.\n\n**Recommendation (mandatory):** Either (1) adjust the iterator so it 
truly models an input iterator (make `operator==` a real equivalence relation 
and ensure equal iterators have consistent dereference/exhaustion semantics), 
or (2) redesign to avoid presenting this as a standard iterator (e.g., provide 
an explicit snapshot/range view type with well-defined semantics, or otherwise 
prevent/avoid use in generic algorithms).



##########
include/tsutil/Metrics.h:
##########
@@ -194,15 +252,25 @@ class Metrics
   // Static methods to encapsulate access to the atomic's
   class iterator
   {
+    friend class Metrics;

Review Comment:
   These constructors were previously publicly usable (notably the 
`iterator(const Metrics&, IdType)` constructor is removed from the public 
section), and are now restricted to `Metrics` via `friend` + private 
constructors. That is a source-level API breaking change for any code 
constructing iterators directly.\n\n**Recommendation (moderate):** If external 
callers are expected to use `Metrics::iterator` (even incidentally), consider 
preserving a safe public construction path (e.g., keep the constructor public 
but ensure it cannot represent an unlisted position by normalizing unlisted 
positions to `end()`), or provide an explicit factory/API that replaces the old 
usage while keeping builds from breaking.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to