mirror of
https://github.com/storytold/effectcraft.git
synced 2026-11-11 06:01:23 +00:00
address review: escaped and distinct header bytes in the row-object estimate; null-prototype descriptors and a one-pass join in the Function guard
This commit is contained in:
@@ -138,9 +138,10 @@ pub fn footage_data_json(f: &effectcraft_project::Footage) -> Option<String> {
|
||||
Some((h, b)) => (h.clone(), b.to_vec()),
|
||||
None => (vec![], vec![]),
|
||||
};
|
||||
// Every row object repeats every header name: a long header over many rows is far
|
||||
// more than the text itself.
|
||||
let keys: usize = header.iter().map(|k| k.len() + 4).sum();
|
||||
// Every row object repeats every distinct header name (a repeated name keeps its last
|
||||
// column), as serialized: a long header over many rows is far more than the text itself.
|
||||
let distinct: std::collections::HashSet<&String> = header.iter().collect();
|
||||
let keys: usize = distinct.iter().map(|k| serde_json::to_string(k).map_or(k.len() * 6 + 2, |j| j.len()) + 2).sum();
|
||||
if keys.saturating_mul(body.len()) > MAX_DATA_JSON_BYTES {
|
||||
return None;
|
||||
}
|
||||
|
||||
@@ -1205,10 +1205,13 @@ function nearestKey(t) { return thisProperty.nearestKey(t); }
|
||||
(function () {
|
||||
var nest = __nest, stack = __stack, apply = Reflect.apply, construct = Reflect.construct, str = String;
|
||||
var define = Object.defineProperty, describe = Object.getOwnPropertyDescriptor, proto = Object.getPrototypeOf;
|
||||
// The original `join`, kept before the guard below replaces it. Descriptors below have a null
|
||||
// prototype: one that inherits `get`/`set` from a changed `Object.prototype` would be refused.
|
||||
var join = Array.prototype.join;
|
||||
// Replace `obj[name]` keeping its attributes.
|
||||
function replace(obj, name, f) {
|
||||
var d = describe(obj, name);
|
||||
define(obj, name, { value: f, writable: d.writable, enumerable: d.enumerable, configurable: true });
|
||||
define(obj, name, { __proto__: null, value: f, writable: d.writable, enumerable: d.enumerable, configurable: true });
|
||||
}
|
||||
function guard(obj, name) {
|
||||
var f = obj[name];
|
||||
@@ -1232,19 +1235,20 @@ function nearestKey(t) { return thisProperty.nearestKey(t); }
|
||||
// in helpers (`push`, `slice`, `join`, `String`) can be replaced by the expression, and
|
||||
// must not make the check see other text than the constructor gets.
|
||||
var w = function () {
|
||||
var n = arguments.length, args = [], params = '';
|
||||
var n = arguments.length, args = [], heads = [];
|
||||
for (var i = 0; i < n; i++) {
|
||||
var s = str(arguments[i]);
|
||||
if (typeof s !== 'string') s = '';
|
||||
define(args, i, { value: s, writable: true, enumerable: true, configurable: true });
|
||||
if (i < n - 1) params = i ? params + ',' + s : s;
|
||||
define(args, i, { __proto__: null, value: s, writable: true, enumerable: true, configurable: true });
|
||||
if (i < n - 1) define(heads, i, { __proto__: null, value: s, writable: true, enumerable: true, configurable: true });
|
||||
}
|
||||
stack();
|
||||
nest(params);
|
||||
// Joined in one pass: adding each piece to a growing string copies it every time.
|
||||
nest(apply(join, heads, [',']));
|
||||
nest(n ? args[n - 1] : '');
|
||||
return construct(F, args);
|
||||
};
|
||||
define(w, 'name', { value: F.name, configurable: true });
|
||||
define(w, 'name', { __proto__: null, value: F.name, configurable: true });
|
||||
w.prototype = F.prototype;
|
||||
replace(F.prototype, 'constructor', w);
|
||||
return w;
|
||||
|
||||
@@ -2114,3 +2114,22 @@ fn delimited_data_row_objects_are_bounded() {
|
||||
};
|
||||
assert!(crate::host::footage_data_json(&f).unwrap().contains("\"a\":1"));
|
||||
}
|
||||
|
||||
/// The row-object estimate counts serialized (escaped) header bytes and each distinct name once.
|
||||
#[test]
|
||||
fn delimited_data_estimate_counts_escaped_and_distinct_headers() {
|
||||
let data = |text: String| effectcraft_project::Footage {
|
||||
path: "/d.csv".into(),
|
||||
kind: effectcraft_project::FootageKind::Data,
|
||||
data: Some(text),
|
||||
..Default::default()
|
||||
};
|
||||
// A control character serializes as six bytes: 200 KiB of them over 100 rows is ~120 MiB.
|
||||
let mut text = format!("{}\n", "\u{1}".repeat(200 << 10));
|
||||
text.push_str(&"1\n".repeat(100));
|
||||
assert!(crate::host::footage_data_json(&data(text)).is_none());
|
||||
// A name repeated across many columns is one key per row object.
|
||||
let mut text = format!("{}\n", vec!["x"; 100].join(","));
|
||||
text.push_str(&"1\n".repeat(140_000));
|
||||
assert!(crate::host::footage_data_json(&data(text)).is_some());
|
||||
}
|
||||
|
||||
@@ -69,6 +69,12 @@ fn eval_and_function_still_work() {
|
||||
assert_eq!(f.num("Object.getPrototypeOf(function* () { yield 7; }).constructor('yield 7')().next().value", 0.0), 7.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn function_constructor_ignores_changes_to_object_prototype() {
|
||||
let f = fx();
|
||||
assert_eq!(f.num("Object.prototype.get = 1; Object.prototype.set = 1; Function('a', 'b', 'return a + b')(2, 3)", 0.0), 5.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deep_array_result_is_an_error_or_text_not_a_crash() {
|
||||
// The result itself: detaching it used to `toString` it past depth 4.
|
||||
|
||||
Reference in New Issue
Block a user