Skip to content

fix(sgl): guard zero-length lines, NaN fractions, and degenerate segments in linear referencing - #865

Open
connerkup wants to merge 3 commits into
duckdb:v1.5-variegatafrom
connerkup:fix/linearref-zero-length-nan-and-hang
Open

fix(sgl): guard zero-length lines, NaN fractions, and degenerate segments in linear referencing#865
connerkup wants to merge 3 commits into
duckdb:v1.5-variegatafrom
connerkup:fix/linearref-zero-length-nan-and-hang

Conversation

@connerkup

Copy link
Copy Markdown

Summary

Fixes three interconnected edge-case bugs in the SGL linear referencing routines (sgl::linestring::interpolate, sgl::linestring::interpolate_points, and sgl::linestring::substring):

  1. Infinite loop / thread hang in ST_LineInterpolatePoints:
    When invoked with repeat=true on a zero-length linestring (e.g. LINESTRING(5 5, 5 5)), actual_length evaluates to 0.0. In the multi-point accumulation loop, next_target is incremented by frac * actual_length = 0.0. The condition while (total_length + segment_length >= next_target) (0.0 >= 0.0) never terminates, causing continuous heap allocation and freezing the worker thread.
  2. Division by zero / NaN coordinate corruption in ST_LineSubstring:
    When a linestring contains degenerate zero-length segments (e.g. leading identical vertices like LINESTRING(1 1, 1 1, 2 2) at start_fraction = 0.0 or a fully collapsed line LINESTRING(5 5, 5 5)), segment_length is 0.0. Evaluating sfrac = remaining / segment_length performs 0.0 / 0.0, injecting IEEE 754 NaN coordinates into the geometry (LINESTRING (nan nan, 1 1, 1.5 1.5)). This also results in inverted bounding box extents (min_x = +1.79e308, max_x = -1.79e308).
  3. Division by zero in ST_LineInterpolatePoint on collapsed lines:
    On a zero-length linestring LINESTRING(5 5, 5 5), linestring::interpolate computed sfrac = 0.0 / 0.0 = NaN, returning POINT EMPTY instead of returning the coordinate POINT (5 5).
  4. NaN input handling:
    Guarded beg_frac and end_frac against NaN inputs, preventing fall-through that previously emitted a silently corrupted vertex at (0, 0).

Changes

  • src/sgl/sgl.cpp:
    • In linestring::interpolate: early return with vertex_array if actual_length == 0; skip zero-length segments (segment_length == 0); reject NaN fractions; fall back to terminal vertex on floating-point precision edge.
    • In linestring::interpolate_points: early return with POINT if actual_length == 0 or if (frac * actual_length) <= 0.0 (avoiding infinite loops on zero-length and subnormal underflow); skip zero-length segments; reject NaN fractions.
    • In linestring::substring: early return with vertex_array if total_length == 0; reject NaN fractions; safely clamp beg and end without division when encountering degenerate segments where segment_length == 0.
  • src/sgl/sgl_test.cpp:
    • Added comprehensive unit tests and adversarial suites covering 2D/3D/4D (Z, M, ZM), degenerate lines, duplicate leading/middle vertices, NaN/Inf fractions, negative values, and underflow. Tested clean with AddressSanitizer and UndefinedBehaviorSanitizer (-fsanitize=address,undefined).
  • test/sql/geometry/st_lineinterpolatepoint.test:
    • Added SQL regression tests for ST_LineSubstring, ST_LineInterpolatePoint, and ST_LineInterpolatePoints on degenerate lines and duplicate vertices.

…erencing

- In linestring::interpolate and linestring::interpolate_points, guard against zero-length linestrings (actual_length == 0) and zero-length segments, eliminating a divide-by-zero that produced NaN/POINT EMPTY and an infinite loop hang in interpolate_points when repeat=true.
- In linestring::substring, return early on zero-length linestrings and safely clamp/skip zero-length segments during beg/end point traversal, preventing 0/0 division that corrupted vertices to NaN and produced inverted bounding boxes.
- Add comprehensive unit tests in sgl_test.cpp and SQL regression tests in st_lineinterpolatepoint.test.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant