Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions changelog.d/9322-main-gates-after-batch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
### Fixed — three `lint` gates that went red on `main`

- **`shape_descriptor_census.py` matches the #9317 stamp funnel.** The census
asserts the shape descriptor is published before the `ObjectHeader` ShapeId,
and found that write by its literal spelling. #9317 correctly routed every
post-birth publication through
`stamp_object_shape_id_with_carrier_note`, so the inline write is gone and
the assertion could no longer locate it. The ordering itself is unchanged —
`shape_descriptor_ensure_with_holes` still precedes the stamp in
`publish_object_shape_from`. The census and its own inversion self-test now
name the funnel.

- **An unused `Path` import** in `strided_tagged_fill.rs` (#9316), which
`cargo check --workspace --all-targets -D warnings` treats as an error. It
is invisible to a plain `cargo build`, which does not compile test targets.

- **`unrooted_local_shape.py` per-file ceiling** for `perry-ext-mysql2`
(#9319). Its two new tests allocated eight heap values up front and then
pushed them one at a time, leaving every earlier value live across a
`js_array_push` that can move it — the #8217 shape. Each value is now built
inside the iteration that pushes it, and the array is consumed in the
expression that builds it. The file returns to its ceiling of 9 with no
baseline change, and the repository total drops 576 → 567.
62 changes: 36 additions & 26 deletions crates/perry-ext-mysql2/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1634,32 +1634,42 @@ mod tests {
#[test]
fn parameter_extraction_preserves_every_supported_value() {
unsafe {
let short = perry_runtime::JSValue::try_short_string(b"hi")
.expect("two-byte string uses the SSO representation");
let long = alloc_string("long-string");
let date = perry_runtime::date::js_date_new_from_timestamp(1_706_933_106_789.0);
let buffer = perry_ffi::alloc_buffer(&[0, 1, 127, 128, 255]);

let mut array = js_array_alloc(8);
for value in [
JsValue::from_bits(short.bits()),
JsValue::from_string_ptr(long.as_raw()),
JsValue::from_int32(42),
JsValue::from_number(3.25),
JsValue::TRUE,
JsValue::NULL,
JsValue::from_bits(date.to_bits()),
JsValue::from_object_ptr(buffer),
] {
array = js_array_push(array, value);
}

// Each heap value is built inside the iteration that pushes it.
// The eager form -- allocate all eight, then push them one at a
// time -- leaves every earlier value live across a `js_array_push`
// that can move it, which is the #8217 shape.
let expected_date = chrono::NaiveDate::from_ymd_opt(2024, 2, 3)
.unwrap()
.and_hms_milli_opt(4, 5, 6, 789)
.unwrap();
let actual = extract_params_from_jsvalue(JsValue::from_object_ptr(array))
.expect("all supported parameter values must marshal");
// Built and consumed in one expression: no named local holds the
// array across the pushes that can move it.
let actual = extract_params_from_jsvalue(JsValue::from_object_ptr((0..8u32).fold(
js_array_alloc(8),
|acc, slot| {
let value = match slot {
0 => JsValue::from_bits(
perry_runtime::JSValue::try_short_string(b"hi")
.expect("two-byte string uses the SSO representation")
.bits(),
),
1 => JsValue::from_string_ptr(alloc_string("long-string").as_raw()),
2 => JsValue::from_int32(42),
3 => JsValue::from_number(3.25),
4 => JsValue::TRUE,
5 => JsValue::NULL,
6 => JsValue::from_bits(
perry_runtime::date::js_date_new_from_timestamp(1_706_933_106_789.0)
.to_bits(),
),
_ => JsValue::from_object_ptr(perry_ffi::alloc_buffer(&[
0, 1, 127, 128, 255,
])),
};
js_array_push(acc, value)
},
)))
.expect("all supported parameter values must marshal");
assert_eq!(
actual,
vec![
Expand All @@ -1679,8 +1689,9 @@ mod tests {
#[test]
fn parameter_extraction_rejects_values_instead_of_substituting_null() {
unsafe {
let mut undefined_array = js_array_alloc(1);
undefined_array = js_array_push(undefined_array, JsValue::UNDEFINED);
// Nested so the fresh array is never a named local held across the
// push that can move it.
let undefined_array = js_array_push(js_array_alloc(1), JsValue::UNDEFINED);
let error = extract_params_from_jsvalue(JsValue::from_object_ptr(undefined_array))
.expect_err("undefined must never become SQL NULL");
assert_eq!(error, "Bind parameter at index 0 is undefined");
Expand All @@ -1690,8 +1701,7 @@ mod tests {
.expect_err("a non-array params container must be rejected");
assert_eq!(error, "Bind parameters must be an array");

let mut object_array = js_array_alloc(1);
object_array = js_array_push(object_array, object);
let object_array = js_array_push(js_array_alloc(1), object);
let error = extract_params_from_jsvalue(JsValue::from_object_ptr(object_array))
.expect_err("an unsupported parameter must never become SQL NULL");
assert_eq!(error, "Unsupported bind parameter at index 0");
Expand Down
2 changes: 1 addition & 1 deletion crates/perry/tests/strided_tagged_fill.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
//! instead), an out-of-range window (node grows the array), and the plain
//! strided-gap behavior, under normal and forced-evacuation runs.

use std::path::{Path, PathBuf};
use std::path::PathBuf;
use std::process::{Command, Output};

fn perry_bin() -> PathBuf {
Expand Down
9 changes: 7 additions & 2 deletions scripts/shape_descriptor_census.py
Original file line number Diff line number Diff line change
Expand Up @@ -398,10 +398,15 @@ def assert_authority_surfaces(sources: dict[str, str]) -> None:
"by-id descriptor before reverse accelerator",
)
sync = function_body(shapes, "publish_object_shape_from")
# #9317 routed every post-birth ShapeId publication through
# `stamp_object_shape_id_with_carrier_note`, which performs the header
# write and then arms `old_carrier` for a promoted receiver. The header
# write is no longer spelled inline here, so name the funnel: the ordering
# this guards (descriptor authoritative BEFORE the stamp) is unchanged.
assert_before(
sync,
"shape_descriptor_ensure",
"(*obj).parent_class_id = id",
"stamp_object_shape_id_with_carrier_note",
"descriptor before ObjectHeader ShapeId",
)
# #8113 MINT-THEN-STAMP. With `field_count` deleted, the descriptor is the
Expand Down Expand Up @@ -786,7 +791,7 @@ def run_sabotage_selftests(sources: dict[str, str], baseline: dict[str, object])
# #9029 tombstones: the lineage publish carries hole_count, so the
# mint call in publish_object_shape_from is the _with_holes form.
"shape_descriptor_ensure_with_holes(",
"(*obj).parent_class_id = id",
"stamp_object_shape_id_with_carrier_note",
)
inverted_publication[path] = inverted_publication[path].replace(
publication_body, inverted_body, 1
Expand Down
Loading