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


##########
src/tsutil/Metrics.cc:
##########
@@ -188,34 +201,39 @@ Metrics::Storage::createSpan(size_t size, 
Metrics::MetricType type, Metrics::IdT
   // On the final blob there is nowhere left to grow, so refuse a span that 
would fill or overflow
   // it rather than letting addBlob() assert. Same intent as the guard in 
create(), and the same
   // cost: some slots of the last blob go unused.
-  if (_cur_blob >= MAX_BLOBS - 1 && _cur_off + size >= MAX_SIZE) {
+  if (_cur_blob.load(std::memory_order_relaxed) >= MAX_BLOBS - 1 && 
_cur_off.load(std::memory_order_relaxed) + size >= MAX_SIZE) {

Review Comment:
   In `createSpan()`, `_cur_off` / `_cur_blob` are loaded multiple times before 
the re-read (and `_cur_off` is loaded twice just for the size-fit checks). 
Caching the initial relaxed loads into locals for the two early guard checks 
would make the logic easier to follow and avoids extra atomic loads. (Keeping 
the existing re-read after `addBlob()` still makes sense.)



##########
include/tsutil/Metrics.h:
##########
@@ -354,15 +359,37 @@ class Metrics
     current() const
     {
       std::lock_guard lock(_mutex);
-      return {_cur_blob, _cur_off};
+      return {_cur_blob.load(std::memory_order_relaxed), 
_cur_off.load(std::memory_order_relaxed)};
     }
 
+    /** Whether @a id names an allocated slot.
+     *
+     * The gate for every id based accessor, since ids from the @c TSStat* API 
are untrusted. An id
+     * qualifies when it is non-negative, its offset is one @c _makeId could 
produce, and its slot
+     * has been handed out.
+     */
     bool
-    valid(IdType id) const
+    _is_allocated(IdType id) const
     {
-      auto [blob, entry] = _splitID(id);
+      if (id < 0) {
+        return false;
+      }
+
+      auto [blob_ix, offset] = _splitID(id);
 
-      return (id >= 0 && ((blob < _cur_blob && entry < MAX_SIZE) || (blob == 
_cur_blob && entry <= _cur_off)));
+      // _cur_blob first: acquiring it also makes visible everything published 
under it.
+      auto const cur_blob = _cur_blob.load(std::memory_order_acquire);
+      auto const cur_off  = _cur_off.load(std::memory_order_acquire);
+
+      // A non-null blob past cur_blob is allocated but not yet published, 
hence <= and < rather
+      // than a test for "not the current blob".
+      return offset < MAX_SIZE && blob_ix <= cur_blob && _blobs[blob_ix] != 
nullptr && (blob_ix < cur_blob || offset < cur_off);
+    }
+
+    bool
+    valid(IdType id) const
+    {
+      return _is_allocated(id);
     }

Review Comment:
   `_is_allocated()` looks like an internal helper (leading underscore naming) 
but is declared in the `public:` section of `Storage`. If this isn’t intended 
as part of the reachable API surface, make it `private` (or at least 
`protected`) and keep `valid()` as the public gate. If it is intended to be 
public, consider renaming it to a non-underscore name consistent with the rest 
of the public methods.



##########
src/tsutil/unit_tests/test_Metrics.cc:
##########
@@ -640,3 +637,116 @@ TEST_CASE("Metrics span lands exactly on a blob 
boundary", "[libtsapi][Metrics]"
   REQUIRE(Metrics::Counter::load(p) == 7);
   REQUIRE(Metrics::Counter::createPtr("span.boundary.after") == p);
 }
+
+TEST_CASE("Metrics malformed id offsets resolve to bad_id", 
"[libtsapi][Metrics]")
+{
+  // An id's offset field is 16 bits but a real offset is below MAX_SIZE, so a 
malformed one must
+  // not index past a blob's arrays. Two blobs are needed for the offset check 
to be what rejects
+  // it; with one, the null blob check would.
+  auto &h = Metrics::hidden_instance();
+
+  for (int i = 0; i < Metrics::MAX_SIZE + 8; ++i) {
+    REQUIRE(Metrics::Counter::createHiddenPtr("f1.fill." + std::to_string(i)) 
!= nullptr);
+  }
+
+  auto const *bad = h.lookup(Metrics::IdType{0}); // the reserved bad_id slot
+  REQUIRE(bad != nullptr);
+
+  // blob 0 is allocated, so the null check does not fire; only the MAX_SIZE 
test stands between
+  // this and atomics[65535].

Review Comment:
   These new test cases mutate the singleton hidden metrics store 
(`hidden_instance()`) by allocating >MAX_SIZE metrics. Because Catch2 does not 
guarantee test execution order, this can make the overall unit test suite 
order-dependent (e.g., later tests may see an unexpectedly large pre-populated 
store or approach store limits). Consider adding a test-only reset/isolated 
Storage instance for these cases, or restructuring so the tests don't depend on 
persistent global state across TEST_CASEs.



##########
src/tsutil/Metrics.cc:
##########
@@ -188,34 +201,39 @@ Metrics::Storage::createSpan(size_t size, 
Metrics::MetricType type, Metrics::IdT
   // On the final blob there is nowhere left to grow, so refuse a span that 
would fill or overflow
   // it rather than letting addBlob() assert. Same intent as the guard in 
create(), and the same
   // cost: some slots of the last blob go unused.
-  if (_cur_blob >= MAX_BLOBS - 1 && _cur_off + size >= MAX_SIZE) {
+  if (_cur_blob.load(std::memory_order_relaxed) >= MAX_BLOBS - 1 && 
_cur_off.load(std::memory_order_relaxed) + size >= MAX_SIZE) {
     if (id) {
       *id = 0; // Slot 0 is the reserved bad_id.
     }
     return {};
   }
 
   // A span has to be contiguous, so one that does not fit in the current blob 
starts a new one.
-  if (_cur_off + size > MAX_SIZE) {
+  if (_cur_off.load(std::memory_order_relaxed) + size > MAX_SIZE) {
     addBlob();
   }
 
-  Metrics::IdType           span_start = _makeId(_cur_blob, _cur_off, type);
-  Metrics::NamesAndAtomics *blob       = _blobs[_cur_blob].get();
+  // Re-read: addBlob() above may have moved both.
+  auto const cur_blob = _cur_blob.load(std::memory_order_relaxed);
+  auto const cur_off  = _cur_off.load(std::memory_order_relaxed);

Review Comment:
   In `createSpan()`, `_cur_off` / `_cur_blob` are loaded multiple times before 
the re-read (and `_cur_off` is loaded twice just for the size-fit checks). 
Caching the initial relaxed loads into locals for the two early guard checks 
would make the logic easier to follow and avoids extra atomic loads. (Keeping 
the existing re-read after `addBlob()` still makes sense.)



##########
src/tsutil/Metrics.cc:
##########
@@ -188,34 +201,39 @@ Metrics::Storage::createSpan(size_t size, 
Metrics::MetricType type, Metrics::IdT
   // On the final blob there is nowhere left to grow, so refuse a span that 
would fill or overflow
   // it rather than letting addBlob() assert. Same intent as the guard in 
create(), and the same
   // cost: some slots of the last blob go unused.
-  if (_cur_blob >= MAX_BLOBS - 1 && _cur_off + size >= MAX_SIZE) {
+  if (_cur_blob.load(std::memory_order_relaxed) >= MAX_BLOBS - 1 && 
_cur_off.load(std::memory_order_relaxed) + size >= MAX_SIZE) {
     if (id) {
       *id = 0; // Slot 0 is the reserved bad_id.
     }
     return {};
   }
 
   // A span has to be contiguous, so one that does not fit in the current blob 
starts a new one.
-  if (_cur_off + size > MAX_SIZE) {
+  if (_cur_off.load(std::memory_order_relaxed) + size > MAX_SIZE) {

Review Comment:
   In `createSpan()`, `_cur_off` / `_cur_blob` are loaded multiple times before 
the re-read (and `_cur_off` is loaded twice just for the size-fit checks). 
Caching the initial relaxed loads into locals for the two early guard checks 
would make the logic easier to follow and avoids extra atomic loads. (Keeping 
the existing re-read after `addBlob()` still makes sense.)



-- 
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