From 3906023ade7b9f995a16893c4e99f4f256318ff3 Mon Sep 17 00:00:00 2001 From: Ryan Malloy Date: Mon, 23 Feb 2026 14:25:43 -0700 Subject: [PATCH] Harden v0.11.0 constructors: NaN/Inf guards, expanded error path tests - Add validate_orbital_elements_args() with isnan/isinf checks for all 7 propagation parameters (epoch, q, e, inc, omega, node, tp); h_mag and g_slope exempt (NaN is valid sentinel for "unknown magnitude") - Deduplicate validation between make_orbital_elements() and _deg() - Update SQL COMMENTs to clarify geometric vs apparent coordinates - Add NaN/Inf rejection tests (q, e, epoch, Inf inclination) - Add NaN H/G acceptance test (sentinel value) - Expand error path coverage to all 4 moon families + negative body_id - All 20 regression suites pass --- sql/pg_orrery--0.10.0--0.11.0.sql | 8 +-- sql/pg_orrery--0.11.0.sql | 8 +-- src/orbital_elements_type.c | 78 ++++++++++++++++++++------- test/expected/v011_features.out | 89 ++++++++++++++++++++++++++++++- test/sql/v011_features.sql | 85 ++++++++++++++++++++++++++++- 5 files changed, 240 insertions(+), 28 deletions(-) diff --git a/sql/pg_orrery--0.10.0--0.11.0.sql b/sql/pg_orrery--0.10.0--0.11.0.sql index 9d006da..8c69401 100644 --- a/sql/pg_orrery--0.10.0--0.11.0.sql +++ b/sql/pg_orrery--0.10.0--0.11.0.sql @@ -33,19 +33,19 @@ COMMENT ON FUNCTION make_orbital_elements_deg(float8,float8,float8,float8,float8 CREATE FUNCTION galilean_equatorial(int4, timestamptz) RETURNS equatorial AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION galilean_equatorial(int4, timestamptz) IS - 'Geocentric RA/Dec of a Galilean moon (0=Io, 1=Europa, 2=Ganymede, 3=Callisto). L1.2 theory + VSOP87.'; + 'Geometric geocentric RA/Dec of a Galilean moon (0=Io, 1=Europa, 2=Ganymede, 3=Callisto). L1.2 theory + VSOP87. No light-time or aberration correction.'; CREATE FUNCTION saturn_moon_equatorial(int4, timestamptz) RETURNS equatorial AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION saturn_moon_equatorial(int4, timestamptz) IS - 'Geocentric RA/Dec of a Saturn moon (0=Mimas..7=Hyperion). TASS17 theory + VSOP87.'; + 'Geometric geocentric RA/Dec of a Saturn moon (0=Mimas..7=Hyperion). TASS17 theory + VSOP87. No light-time or aberration correction.'; CREATE FUNCTION uranus_moon_equatorial(int4, timestamptz) RETURNS equatorial AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION uranus_moon_equatorial(int4, timestamptz) IS - 'Geocentric RA/Dec of a Uranus moon (0=Miranda..4=Oberon). GUST86 theory + VSOP87.'; + 'Geometric geocentric RA/Dec of a Uranus moon (0=Miranda..4=Oberon). GUST86 theory + VSOP87. No light-time or aberration correction.'; CREATE FUNCTION mars_moon_equatorial(int4, timestamptz) RETURNS equatorial AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION mars_moon_equatorial(int4, timestamptz) IS - 'Geocentric RA/Dec of a Mars moon (0=Phobos, 1=Deimos). MarsSat theory + VSOP87.'; + 'Geometric geocentric RA/Dec of a Mars moon (0=Phobos, 1=Deimos). MarsSat theory + VSOP87. No light-time or aberration correction.'; diff --git a/sql/pg_orrery--0.11.0.sql b/sql/pg_orrery--0.11.0.sql index 5690540..1dab626 100644 --- a/sql/pg_orrery--0.11.0.sql +++ b/sql/pg_orrery--0.11.0.sql @@ -1372,19 +1372,19 @@ COMMENT ON FUNCTION make_orbital_elements_deg(float8,float8,float8,float8,float8 CREATE FUNCTION galilean_equatorial(int4, timestamptz) RETURNS equatorial AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION galilean_equatorial(int4, timestamptz) IS - 'Geocentric RA/Dec of a Galilean moon (0=Io, 1=Europa, 2=Ganymede, 3=Callisto). L1.2 theory + VSOP87.'; + 'Geometric geocentric RA/Dec of a Galilean moon (0=Io, 1=Europa, 2=Ganymede, 3=Callisto). L1.2 theory + VSOP87. No light-time or aberration correction.'; CREATE FUNCTION saturn_moon_equatorial(int4, timestamptz) RETURNS equatorial AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION saturn_moon_equatorial(int4, timestamptz) IS - 'Geocentric RA/Dec of a Saturn moon (0=Mimas..7=Hyperion). TASS17 theory + VSOP87.'; + 'Geometric geocentric RA/Dec of a Saturn moon (0=Mimas..7=Hyperion). TASS17 theory + VSOP87. No light-time or aberration correction.'; CREATE FUNCTION uranus_moon_equatorial(int4, timestamptz) RETURNS equatorial AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION uranus_moon_equatorial(int4, timestamptz) IS - 'Geocentric RA/Dec of a Uranus moon (0=Miranda..4=Oberon). GUST86 theory + VSOP87.'; + 'Geometric geocentric RA/Dec of a Uranus moon (0=Miranda..4=Oberon). GUST86 theory + VSOP87. No light-time or aberration correction.'; CREATE FUNCTION mars_moon_equatorial(int4, timestamptz) RETURNS equatorial AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; COMMENT ON FUNCTION mars_moon_equatorial(int4, timestamptz) IS - 'Geocentric RA/Dec of a Mars moon (0=Phobos, 1=Deimos). MarsSat theory + VSOP87.'; + 'Geometric geocentric RA/Dec of a Mars moon (0=Phobos, 1=Deimos). MarsSat theory + VSOP87. No light-time or aberration correction.'; diff --git a/src/orbital_elements_type.c b/src/orbital_elements_type.c index e644c78..0afac76 100644 --- a/src/orbital_elements_type.c +++ b/src/orbital_elements_type.c @@ -371,6 +371,64 @@ oe_period_years(PG_FUNCTION_ARGS) } +/* + * Shared validation for make_orbital_elements() and make_orbital_elements_deg(). + * + * Rejects NaN/Inf in the 7 parameters that feed the propagation pipeline. + * h_mag and g_slope are exempt: NaN is a valid sentinel for "unknown". + */ +static void +validate_orbital_elements_args(double epoch, double q, double e, + double ang1, double ang2, double ang3, + double tp) +{ + if (isnan(epoch) || isinf(epoch)) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("epoch must be finite: %g", epoch))); + + if (isnan(q) || isinf(q)) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("perihelion distance must be finite: %g", q))); + + if (q <= 0.0) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("perihelion distance must be positive: %.6f", q))); + + if (isnan(e) || isinf(e)) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("eccentricity must be finite: %g", e))); + + if (e < 0.0) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("eccentricity must be non-negative: %.6f", e))); + + if (isnan(ang1) || isinf(ang1)) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("inclination must be finite: %g", ang1))); + + if (isnan(ang2) || isinf(ang2)) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("argument of perihelion must be finite: %g", ang2))); + + if (isnan(ang3) || isinf(ang3)) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("longitude of ascending node must be finite: %g", ang3))); + + if (isnan(tp) || isinf(tp)) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("time of perihelion must be finite: %g", tp))); +} + + /* ================================================================ * make_orbital_elements(epoch, q, e, inc_rad, omega_rad, Omega_rad, tp, H, G) * @@ -392,15 +450,7 @@ make_orbital_elements(PG_FUNCTION_ARGS) double h_mag = PG_GETARG_FLOAT8(7); double g_slope = PG_GETARG_FLOAT8(8); - if (q <= 0.0) - ereport(ERROR, - (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), - errmsg("perihelion distance must be positive: %.6f", q))); - - if (e < 0.0) - ereport(ERROR, - (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), - errmsg("eccentricity must be non-negative: %.6f", e))); + validate_orbital_elements_args(epoch, q, e, inc, arg_peri, raan, tp); result = (pg_orbital_elements *) palloc(sizeof(pg_orbital_elements)); result->epoch = epoch; @@ -439,15 +489,7 @@ make_orbital_elements_deg(PG_FUNCTION_ARGS) double h_mag = PG_GETARG_FLOAT8(7); double g_slope = PG_GETARG_FLOAT8(8); - if (q <= 0.0) - ereport(ERROR, - (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), - errmsg("perihelion distance must be positive: %.6f", q))); - - if (e < 0.0) - ereport(ERROR, - (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), - errmsg("eccentricity must be non-negative: %.6f", e))); + validate_orbital_elements_args(epoch, q, e, inc_deg, omega_deg, Omega_deg, tp); result = (pg_orbital_elements *) palloc(sizeof(pg_orbital_elements)); result->epoch = epoch; diff --git a/test/expected/v011_features.out b/test/expected/v011_features.out index 9d1cb5c..6bfbe2f 100644 --- a/test/expected/v011_features.out +++ b/test/expected/v011_features.out @@ -211,7 +211,58 @@ ORDER BY moon_id; (2 rows) -- ============================================================ --- Test 12: galilean_equatorial error — invalid body_id +-- Test 12: NaN rejection in constructors +-- NaN passes IEEE 754 comparison guards silently; must be caught explicitly +-- ============================================================ +DO $$ +BEGIN + PERFORM make_orbital_elements(2460400.5, 'NaN'::float8, 0.5, 0, 0, 0, 2460400.5, 0, 0); + RAISE EXCEPTION 'should have failed'; +EXCEPTION WHEN numeric_value_out_of_range THEN + RAISE NOTICE 'make_oe_nan_q: correctly rejected'; +END; +$$; +NOTICE: make_oe_nan_q: correctly rejected +DO $$ +BEGIN + PERFORM make_orbital_elements_deg(2460400.5, 1.0, 'NaN'::float8, 0, 0, 0, 2460400.5, 0, 0); + RAISE EXCEPTION 'should have failed'; +EXCEPTION WHEN numeric_value_out_of_range THEN + RAISE NOTICE 'make_oe_nan_e: correctly rejected'; +END; +$$; +NOTICE: make_oe_nan_e: correctly rejected +DO $$ +BEGIN + PERFORM make_orbital_elements('NaN'::float8, 1.0, 0.5, 0, 0, 0, 2460400.5, 0, 0); + RAISE EXCEPTION 'should have failed'; +EXCEPTION WHEN numeric_value_out_of_range THEN + RAISE NOTICE 'make_oe_nan_epoch: correctly rejected'; +END; +$$; +NOTICE: make_oe_nan_epoch: correctly rejected +DO $$ +BEGIN + PERFORM make_orbital_elements(2460400.5, 1.0, 0.5, 'Infinity'::float8, 0, 0, 2460400.5, 0, 0); + RAISE EXCEPTION 'should have failed'; +EXCEPTION WHEN numeric_value_out_of_range THEN + RAISE NOTICE 'make_oe_inf_inc: correctly rejected'; +END; +$$; +NOTICE: make_oe_inf_inc: correctly rejected +-- ============================================================ +-- Test 13: NaN in H/G is allowed (sentinel for "unknown") +-- ============================================================ +SELECT 'nan_h_g_ok' AS test, + oe_h_mag(make_orbital_elements(2460400.5, 1.0, 0.5, 0, 0, 0, 2460400.5, + 'NaN'::float8, 'NaN'::float8)) AS h_mag_is_nan; + test | h_mag_is_nan +------------+-------------- + nan_h_g_ok | NaN +(1 row) + +-- ============================================================ +-- Test 14: error paths for all four moon families + negative body_id -- ============================================================ DO $$ BEGIN @@ -222,3 +273,39 @@ EXCEPTION WHEN numeric_value_out_of_range THEN END; $$; NOTICE: galilean_eq_invalid: correctly rejected +DO $$ +BEGIN + PERFORM galilean_equatorial(-1, '2024-06-15 12:00:00+00'); + RAISE EXCEPTION 'should have failed'; +EXCEPTION WHEN numeric_value_out_of_range THEN + RAISE NOTICE 'galilean_eq_negative: correctly rejected'; +END; +$$; +NOTICE: galilean_eq_negative: correctly rejected +DO $$ +BEGIN + PERFORM saturn_moon_equatorial(8, '2024-06-15 12:00:00+00'); + RAISE EXCEPTION 'should have failed'; +EXCEPTION WHEN numeric_value_out_of_range THEN + RAISE NOTICE 'saturn_eq_invalid: correctly rejected'; +END; +$$; +NOTICE: saturn_eq_invalid: correctly rejected +DO $$ +BEGIN + PERFORM uranus_moon_equatorial(5, '2024-06-15 12:00:00+00'); + RAISE EXCEPTION 'should have failed'; +EXCEPTION WHEN numeric_value_out_of_range THEN + RAISE NOTICE 'uranus_eq_invalid: correctly rejected'; +END; +$$; +NOTICE: uranus_eq_invalid: correctly rejected +DO $$ +BEGIN + PERFORM mars_moon_equatorial(2, '2024-06-15 12:00:00+00'); + RAISE EXCEPTION 'should have failed'; +EXCEPTION WHEN numeric_value_out_of_range THEN + RAISE NOTICE 'mars_eq_invalid: correctly rejected'; +END; +$$; +NOTICE: mars_eq_invalid: correctly rejected diff --git a/test/sql/v011_features.sql b/test/sql/v011_features.sql index fe5c5e3..62fbda3 100644 --- a/test/sql/v011_features.sql +++ b/test/sql/v011_features.sql @@ -169,7 +169,54 @@ FROM generate_series(0, 1) AS moon_id, ORDER BY moon_id; -- ============================================================ --- Test 12: galilean_equatorial error — invalid body_id +-- Test 12: NaN rejection in constructors +-- NaN passes IEEE 754 comparison guards silently; must be caught explicitly +-- ============================================================ +DO $$ +BEGIN + PERFORM make_orbital_elements(2460400.5, 'NaN'::float8, 0.5, 0, 0, 0, 2460400.5, 0, 0); + RAISE EXCEPTION 'should have failed'; +EXCEPTION WHEN numeric_value_out_of_range THEN + RAISE NOTICE 'make_oe_nan_q: correctly rejected'; +END; +$$; + +DO $$ +BEGIN + PERFORM make_orbital_elements_deg(2460400.5, 1.0, 'NaN'::float8, 0, 0, 0, 2460400.5, 0, 0); + RAISE EXCEPTION 'should have failed'; +EXCEPTION WHEN numeric_value_out_of_range THEN + RAISE NOTICE 'make_oe_nan_e: correctly rejected'; +END; +$$; + +DO $$ +BEGIN + PERFORM make_orbital_elements('NaN'::float8, 1.0, 0.5, 0, 0, 0, 2460400.5, 0, 0); + RAISE EXCEPTION 'should have failed'; +EXCEPTION WHEN numeric_value_out_of_range THEN + RAISE NOTICE 'make_oe_nan_epoch: correctly rejected'; +END; +$$; + +DO $$ +BEGIN + PERFORM make_orbital_elements(2460400.5, 1.0, 0.5, 'Infinity'::float8, 0, 0, 2460400.5, 0, 0); + RAISE EXCEPTION 'should have failed'; +EXCEPTION WHEN numeric_value_out_of_range THEN + RAISE NOTICE 'make_oe_inf_inc: correctly rejected'; +END; +$$; + +-- ============================================================ +-- Test 13: NaN in H/G is allowed (sentinel for "unknown") +-- ============================================================ +SELECT 'nan_h_g_ok' AS test, + oe_h_mag(make_orbital_elements(2460400.5, 1.0, 0.5, 0, 0, 0, 2460400.5, + 'NaN'::float8, 'NaN'::float8)) AS h_mag_is_nan; + +-- ============================================================ +-- Test 14: error paths for all four moon families + negative body_id -- ============================================================ DO $$ BEGIN @@ -179,3 +226,39 @@ EXCEPTION WHEN numeric_value_out_of_range THEN RAISE NOTICE 'galilean_eq_invalid: correctly rejected'; END; $$; + +DO $$ +BEGIN + PERFORM galilean_equatorial(-1, '2024-06-15 12:00:00+00'); + RAISE EXCEPTION 'should have failed'; +EXCEPTION WHEN numeric_value_out_of_range THEN + RAISE NOTICE 'galilean_eq_negative: correctly rejected'; +END; +$$; + +DO $$ +BEGIN + PERFORM saturn_moon_equatorial(8, '2024-06-15 12:00:00+00'); + RAISE EXCEPTION 'should have failed'; +EXCEPTION WHEN numeric_value_out_of_range THEN + RAISE NOTICE 'saturn_eq_invalid: correctly rejected'; +END; +$$; + +DO $$ +BEGIN + PERFORM uranus_moon_equatorial(5, '2024-06-15 12:00:00+00'); + RAISE EXCEPTION 'should have failed'; +EXCEPTION WHEN numeric_value_out_of_range THEN + RAISE NOTICE 'uranus_eq_invalid: correctly rejected'; +END; +$$; + +DO $$ +BEGIN + PERFORM mars_moon_equatorial(2, '2024-06-15 12:00:00+00'); + RAISE EXCEPTION 'should have failed'; +EXCEPTION WHEN numeric_value_out_of_range THEN + RAISE NOTICE 'mars_eq_invalid: correctly rejected'; +END; +$$;