fix: close Θ-W7-T1 round-2 leftovers — NaN guard placement, comment attribution, CLAUDE.md export
Moves the NaN/Inf finiteness check to addSegment where the UB-causing cast actually happens, makes strokeBounds reject an interior non-finite point instead of swallowing it, corrects a LICE_GetPixel misattribution, and documents rasterRowOffset.
This commit is contained in:
+18
-14
@@ -56,13 +56,7 @@ void StrokeCanvas::extendRow(int y, int x0, int x1) {
|
||||
}
|
||||
|
||||
void StrokeCanvas::addPiece(float ax, float ay, float bx, float by, float halfWidth) {
|
||||
// Every current caller feeds bounded geometry, but this is a pure module: a NaN/Inf
|
||||
// coordinate would otherwise reach static_cast<int> below, which is UB rather than a
|
||||
// clipped no-op.
|
||||
if (!(std::isfinite(ax) && std::isfinite(ay) && std::isfinite(bx) && std::isfinite(by) &&
|
||||
std::isfinite(halfWidth))) {
|
||||
return;
|
||||
}
|
||||
// addSegment (the only caller) guarantees finite inputs before this point.
|
||||
const float reach = halfWidth + 0.5f; // beyond this the coverage is 0
|
||||
const float dx = bx - ax;
|
||||
const float dy = by - ay;
|
||||
@@ -104,6 +98,14 @@ void StrokeCanvas::addPiece(float ax, float ay, float bx, float by, float halfWi
|
||||
|
||||
void StrokeCanvas::addSegment(float ax, float ay, float bx, float by, float halfWidth) {
|
||||
if (bounds_.empty() || halfWidth <= 0.0f) return;
|
||||
// Must guard here, not in addPiece: len2 below goes NaN/Inf on a bad input too, so the
|
||||
// `len2 <= kMaxPieceLen^2` comparison is false either way (NaN compares false against
|
||||
// anything) and the pieces-count cast a few lines down is reached as UB regardless of which
|
||||
// branch is taken.
|
||||
if (!(std::isfinite(ax) && std::isfinite(ay) && std::isfinite(bx) && std::isfinite(by) &&
|
||||
std::isfinite(halfWidth))) {
|
||||
return;
|
||||
}
|
||||
const float dx = bx - ax;
|
||||
const float dy = by - ay;
|
||||
const float len2 = dx * dx + dy * dy;
|
||||
@@ -125,20 +127,22 @@ void StrokeCanvas::addSegment(float ax, float ay, float bx, float by, float half
|
||||
}
|
||||
|
||||
Rect strokeBounds(const StrokePoint* pts, std::size_t count, float halfWidth, const Rect& clip) {
|
||||
if (pts == nullptr || count == 0 || halfWidth <= 0.0f || clip.empty()) return Rect{};
|
||||
if (pts == nullptr || count == 0 || halfWidth <= 0.0f || clip.empty() ||
|
||||
!std::isfinite(halfWidth)) {
|
||||
return Rect{};
|
||||
}
|
||||
if (!(std::isfinite(pts[0].x) && std::isfinite(pts[0].y))) return Rect{};
|
||||
float minX = pts[0].x, maxX = pts[0].x, minY = pts[0].y, maxY = pts[0].y;
|
||||
for (std::size_t i = 1; i < count; ++i) {
|
||||
// Checked per-point, not via isfinite(minX/maxX) after the reduction: std::min/max
|
||||
// against NaN silently returns the OTHER (finite) operand, so a NaN anywhere but pts[0]
|
||||
// would otherwise vanish from the reduction instead of rejecting the stroke.
|
||||
if (!(std::isfinite(pts[i].x) && std::isfinite(pts[i].y))) return Rect{};
|
||||
minX = (std::min)(minX, pts[i].x);
|
||||
maxX = (std::max)(maxX, pts[i].x);
|
||||
minY = (std::min)(minY, pts[i].y);
|
||||
maxY = (std::max)(maxY, pts[i].y);
|
||||
}
|
||||
// Same NaN/Inf guard as addPiece: an unbounded coordinate must clip to nothing, not reach
|
||||
// the static_cast<int> below as UB.
|
||||
if (!(std::isfinite(minX) && std::isfinite(maxX) && std::isfinite(minY) &&
|
||||
std::isfinite(maxY) && std::isfinite(halfWidth))) {
|
||||
return Rect{};
|
||||
}
|
||||
const float reach = halfWidth + 0.5f;
|
||||
// Same tightened box as addPiece (see its comment): a pixel only takes ink when its centre
|
||||
// is within `reach`, so this is [ceil(min-reach-0.5), floor(max+reach+0.5)) rather than the
|
||||
|
||||
Reference in New Issue
Block a user