Commit Graph
54 Commits
Author SHA1 Message Date
tylerjenningsw cca5447e09 Expressions: propertyGroup reaches the kept layer, not a new one 2026-10-29 17:43:30 -04:00
tylerjenningsw 2bd86c6cd8 Merge remote-tracking branch 'origin/main' into fix/paths-and-callable-layers
# Conflicts:
#	crates/expr/src/prelude.js
2026-10-29 17:42:29 -04:00
BFlat 0f2225fe9c Merge pull request #2161 from Barzabel/fix/2124-2125-expr-nesting
expr: bound runtime parser and native recursion depth (#2124, #2125)
2026-10-30 01:19:11 +07:00
Dylan M. Taylor 2fd6c448c5 Refactor expr for strict Clippy checks 2026-10-28 06:09:20 +00:00
tylerjenningsw 95ee400ab2 Expressions: test that a change to Object.prototype doesn't reach the kept layers 2026-10-27 14:04:10 -04:00
tylerjenningsw 2eefced49b Expressions: the kept layers' maps have no prototype 2026-10-27 14:04:10 -04:00
tylerjenningsw e7cf022a7d Expressions: test that the kept layers are bounded 2026-10-27 14:04:10 -04:00
tylerjenningsw ad6b87c60d Expressions: the kept layers are bounded like the runtime's scripts 2026-10-27 14:04:10 -04:00
tylerjenningsw 2f2ebbdedb Expressions: test that deleting or redefining thisLayer and thisComp doesn't reach the next evaluation 2026-10-27 14:04:10 -04:00
tylerjenningsw ecb8dadde5 Expressions: an expression can't delete or redefine thisLayer, thisComp or the other unqualified names 2026-10-27 14:04:10 -04:00
tylerjenningsw d81716bfb6 Expressions: test that an expression can't change the prelude for the next one 2026-10-27 14:04:10 -04:00
tylerjenningsw b2fb5f4e5f Expressions: an expression can't change the prelude for the evaluations after it 2026-10-27 14:04:10 -04:00
tylerjenningsw 0860c7e03d Expressions: an expression can't delete a kept layer's fields 2026-10-27 14:04:10 -04:00
tylerjenningsw f4a4d76f2a Expressions: test that a kept layer's own fields don't carry over to the next evaluation 2026-10-27 14:04:10 -04:00
tylerjenningsw 5c23bda95d Expressions: an expression can't change which layer a kept layer is 2026-10-27 14:04:10 -04:00
tylerjenningsw 754ed2fe84 Expressions: test that a kept layer reads the project again in each evaluation 2026-10-27 14:04:09 -04:00
tylerjenningsw 7c0ba4ef1d Expressions: a layer is made once per runtime and reads its info again in each evaluation 2026-10-27 14:04:09 -04:00
tylerjenningsw ca590aaead Expressions: a layer is made once per evaluation, and only functions are checked for being one
A Layer is now a callable function with its own prototype, which costs
more to make than the plain object it was. __layer(c, l) makes each
layer once per evaluation (kept in __S, which __begin resets), so an
expression naming the same layer many times builds it once. That covers
thisComp.layer(...) in a loop, .parent, a Layer Control's value and
thisLayer.

The checks for a layer (the result, thisComp.layer(layer, offset), the
property proxy) read __isLayer only on functions. Numbers and other
primitives are no longer boxed to look the property up.

Timing (release, Windows, medians of 2000 evaluations, PR vs main):
- one evaluation building one layer: about 22 vs 16 us;
- thisLayer.transform.opacity: about 200 vs 170 us;
- 20000 thisComp.layer(1) lookups in one evaluation: 95 vs 138 ms.
2026-10-27 14:04:09 -04:00
tylerjenningsw 3f88c72a43 Expressions: a layer is a callable property group
In After Effects a layer is a property group: thisLayer("transform"),
thisComp.layer("Other")("effects")("Blur"), thisLayer(1). In
EffectCraft's expression runtime a Layer was a plain object, so those
failed with "TypeError: not a callable function", including the
references the pick whip writes with Compact English off
(thisComp.layer("Other")("effects")("ec.noise.fractal")(...)).

A Layer is now callable like a property group (built as __mkGroup
builds groups), looking up its own properties by match name, name or
index, and keeps its methods, name and index. The places that took a
layer for a plain object (the result reading as the layer's index,
thisComp.layer(layer, offset), a Layer Control's value handed through
the property proxy) accept it.

Test: layers_are_callable_property_groups (fails before).
2026-10-27 14:03:59 -04:00
skylar taylor-barrick 69b9ce2bd1 M0.37: reconcile shared docking and qualify migration follow-up 2026-10-26 06:47:07 +01:00
Dylan M. Taylor f31a6b0aef sonar: optional chaining (S6582) and for-of loops (S4138) in engine-owned JS (#2273)
ExtendScript .jsx files are left alone (ES3, no ?. or for-of).
2026-10-25 19:37:04 -04:00
Dylan M. Taylor 23f11ab7cf expr: bound rewrite expansion and preserve assignment targets (#2195)
* expr: bound rewrite expansion and preserve assignment targets

* expr: bound rewritten output size and rewrite computed assignment targets
2026-10-23 09:14:49 -04:00
Dylan M. Taylor 0871135a6b Merge pull request #2062 from storytold/sonar-fix/js-css-reliability
fix: JS reliability bugs (SonarCloud S1764/S9383/S4275)
2026-10-22 19:27:57 -04:00
Barzabel a854e680f7 test(expr): deep-nesting tests assert the guard's own error (#2124, #2125)
Reflect is not an expression global, so Reflect.construct never reached the
nesting check; the proxy case asserts Proxy is undefined.
2026-10-22 23:00:12 +03:00
Barzabel 8324d93c39 fix(expr): JSON.stringify key lists report cycles; document stack guard coverage (#2124, #2125)
An array replacer now copies each source object once per call, so a cycle stays a cycle and the native check throws TypeError, while shared objects still serialize. Documents which threads evaluate expressions, what the 256 KiB guard covers (tested on a 512 KiB thread), that it does not apply on wasm32, and why eval is indirect.
2026-10-22 21:33:16 +03:00
Barzabel 6686cc75ca fix(expr): bound runtime parser and native recursion depth (#2124, #2125)
eval, Function and the generator/async function constructors now check the nesting of their source like the expression text. join, toLocaleString, flat and JSON.stringify check the run's stack use per level, results nested past the detached levels become [object], and Proxy/Reflect are no longer available to expressions. Each of these overflowed the stack and aborted the app.
2026-10-22 20:28:38 +03:00
Dylan M. Taylor 7983396692 fix: JS reliability bugs (SonarCloud S1764, S9383, S4275)
- scriptui.js: replace the self-comparison NaN check (o.max === o.max) with
  the explicit Number.isNaN(o.max) (S1764).
- sw.js: mark the fire-and-forget cache.put() with void and a .catch() so the
  promise rejection is handled (S9383).
- prelude.js: return __err(...) from the activeCamera getter so every path
  yields a value (S4275); __err always throws, so behavior is unchanged.
2026-10-21 17:20:35 -04:00
NetterSilverandClaude Haiku 5.5 28f97be7ee Fix integer overflow in sampleImage for huge sample points
Found while testing crates/expr/src/sample.rs:102

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-21 02:39:20 +02:00
Gert Pellin 0bdf9ec5e3 Merge branch 'main' into expr-property-group-1619 2026-10-16 14:28:34 +02:00
bflatasticandClaude Opus 5.5 cc138fcb90 Expressions: timeToFrames keeps whole durations whole under float error
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 18:38:56 +07:00
Gert Pellin 71a0f8855a Expressions: find an ancestor's index by uid, not by formatted path 2026-10-16 05:45:47 +02:00
Gert Pellin d347a0efca Expressions: propertyGroup() and a group's propertyIndex (#1619)
The After Effects stagger idiom
  id = thisProperty.propertyGroup(2).propertyIndex;
failed with "not a callable function": the expression object model had
no propertyGroup() and property groups had no propertyIndex.

- Property.propertyGroup(countUp = 1) and PropertyGroup.propertyGroup()
  return the group countUp levels up; from a top-level group they reach
  the layer. A count below 1, not a number, or above the layer is an
  error naming propertyGroup, never a crash.
- PropertyGroup.propertyIndex: the group's 1-based index among its
  siblings (Child requests now carry it).
- One new host request (Ancestor) walks up from the node's full uid
  path, so it also works for the property being evaluated.
- Tests for the stagger expression, effect parameters, reaching the
  layer, other layers' properties and groups and out-of-range counts;
  the fuzzer seeds and tokens include the new members.
2026-10-16 05:31:49 +02:00
sanyamsingh001andCodex 36ebe2eada Honor duration rounding in timeToFrames
Round nonzero duration frame counts away from zero while retaining
absolute-time rounding, default argument selection and numeric coercion.

Cover fractional durations, absolute controls, defaults and coercion.
Sort expected paths in the existing footage test for temporary-directory portability.

Co-authored-by: Codex <codex@openai.com>
2026-10-14 03:21:23 +10:00
Kristian MyramandClaude Haiku 5.5 4d105f02d4 #809: layer.source and a Layer Control's layer work in expressions
layer.source was undefined in expressions, and a layer from a Layer Control had no transform.
Both now work.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
2026-10-12 19:19:11 +02:00
vangel76andClaude Sonnet 5.5 d7a03ef3a7 #786: make the expression budget tests independent of machine load
fanning_out_reads_stop_at_a_budget expects the run-count error, but under heavy load
the 5 s wall-clock budget could fire first (different error). Add a test-only
per-thread time limit and lift it in that test. Wall-clock assertions in the other
new tests are now only generous hang guards (>=10x), the fuzzer's hang deadline is
24x MAX_TIME and the perf bound is 10x looser.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-11 20:52:39 +02:00
vangel76andClaude Opus 5.5 9da66d9063 #786: fuzz expressions; fix the overflows, hangs and panic it found
Problem: expression text is input we don't control, and nothing fuzzed the
expression parser and evaluator. A seeded mutation fuzzer found stack
overflows (a few hundred `?:`/`=>`/`if`, a few thousand `.`/`<`/`()` links,
75 nested template substitutions or regex groups, a chain inside one
template), hangs (an expression reading its own layer's transform nested
2^16 times; fanning-out reads; 10M-iteration loops re-run per read; loops
calling looping functions), an i64 overflow panic in wiggle noise, and NaN
path vertices tripping the rasterizer's clipping assertion.

Fix: a dependency-free seeded fuzzer (crates/expr/src/tests/fuzz.rs: real
AE-style seeds, char/token mutations, worker thread with a 2 MiB stack,
catch_unwind, deadline, env vars for long runs); a weighted nesting
estimate covering chains without brackets and template/regex insides;
cycle detection per (property, time, text); a per-evaluation budget of
20 000 script runs and 5 s (runs are interrupted through boa's budgeted
evaluation; native only); loop limit 1M; wrapping noise lattice indices;
non-finite path results rejected. Each fix has a regression test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 14:40:03 +02:00
Claude 9495810ff5 Tests: the near-plane check tests for the flip, and loopOut compares within float tolerance (#572, #456 follow-up)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QiAJeFttZp83Fh7YuzAR5f
2026-10-10 21:48:52 +00:00
Claude a12242c806 Format the merged community fixes (#561-#566 follow-up)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QiAJeFttZp83Fh7YuzAR5f
2026-10-10 12:49:52 +00:00
Nigel Ihza Firdausy 6ed3d210fb fix(expr): reflect pending TextStyle baseline edits in expression getters (#545) 2026-10-10 15:50:18 +07:00
BFlat 26f577cb1a Merge pull request #6 from storybored379/pr/expr-runtime-stack-leak
Expressions: keep the runtime usable after errors and property reads
2026-10-05 12:10:53 +07:00
Brandon ThomasandClaude Opus 5.5 fc8fea031c Never crash: reject deeply nested expressions instead of overflowing the stack
An expression nested about 60 brackets deep overflowed the stack of a 2 MiB thread in the
JavaScript parser (release and debug; ~200 levels on 8 MiB), which aborts the whole app: it
can't be caught. The array-maths rewrite also nests one call per arithmetic operator, so a long
sum went the same way, and the rewriter itself recursed per bracket. rewrite() now measures the
nesting (brackets plus chains of prefix operators) before and after rewriting and turns an
expression deeper than MAX_NESTING (32) into a RangeError; check_syntax reports the same error.
Also replaces the unreachable!() in the bracket-tree builder with a let-chain, and denies the
panic lints in the crate.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 13:22:36 -04:00
bflatastic 244cee4687 M8.1: expressions keep working on long-lived threads — boa leaves values on its VM stack when an exception unwinds through call frames (every pending host request does), so after tens to hundreds of evaluations that read layers or effects every expression on the thread failed with "maximum stack size"; a run that hits the limit now gets a fresh context and is retried once (regression test: 6000 host-reading evaluations on one thread) 2026-10-04 00:19:22 +07:00
storybored379andClaude Opus 5.5 0ae7a1925a Expressions: keep the runtime usable after errors and property reads
boa leaves the value stack of inner call frames behind when an exception
leaves a script (or a function called from Rust) from a nested call. Every
property read throws the pending-host-request abort from inside a prelude
helper, and so does any helper error (`thisComp.layer("Ghost")`), so the
thread's runtime filled up after ~100 evaluations of a pick-whip
expression: from then on every expression on that thread failed with
"RuntimeLimitError: reached the maximum stack size" and silently fell back
to its keyframed value (e.g. a follower layer stops following after ~90
render_frame calls).

Wrap the script and `__finish` in try/catch and rethrow from their own
frame, which unwinds the stack; backtraces (error line numbers) survive.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-03 09:31:41 -07:00
Brandon ThomasandClaude Opus 5.5 0698b23cd7 M13.7: Save Frame As Photoshop Layers / ProEXR, Watch Folder, Vanishing Point (disabled), Create Nulls From Paths, VR Comp Editor, Help tutorials
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 07:55:04 -04:00
Brandon Thomas 86b8e24417 Merge branch 'main' into worktree-agent-a15cecfec57d5290d
# Conflicts:
#	crates/effects/src/ocio.rs
#	crates/engine/src/commands/stubs.rs
#	docs/parity.md
2026-10-03 02:26:19 -04:00
Brandon ThomasandClaude Opus 5.5 8696d1e9ba M7.7: camera/light rigs and Advanced 3D motion blur, blend modes, track mattes
Layer > Camera > Create Stereo 3D Rig (left/right eye comps with expression-linked eye
cameras, Stereo 3D Controls effect, stereo comp through 3D Glasses), Create Orbit Null,
Create Cameras from 3D Model; Layer > Light > Create Lights from 3D Model
(KHR_lights_punctual), Control Light with Camera, Create Environment Light Background
Layer (equirectangular sky drawn through the camera). glTF cameras and punctual lights
are parsed by effectcraft-model.

Advanced 3D: per-sample motion blur over the comp shutter, and blend modes / track
mattes / Preserve Transparency composited through the 2D path in depth order.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 00:27:47 -04:00
Brandon ThomasandClaude Opus 5.5 a4f32f8df2 Merge main into M13.5 long-tail polish
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 00:25:09 -04:00
Brandon ThomasandClaude Opus 5.5 bbaee1fa0f M13.5: stroke taper/wave/multi-dashes, radial gradient highlight, variable-width mask feather, adaptive motion-blur samples
Shapes (SHP-2): Stroke Taper (pixels/percent start/end length, width, ease) and
Wave (amount, pixels/cycles, wavelength, phase) rendered as a variable-width
outline; Dash 2/Gap 2/Dash 3/Gap 3 (shape.dashes.add/remove, timeline +/-
buttons); radial gradient Highlight Length/Angle as a focal gradient.
Masks (MSK-1): mask feather points on the Mask Path (mask.featherPoint.*,
Mask Feather tool add/drag), per-point signed radius and tension, rendered
from the signed distance to the path; carried through vertex edits.
Motion blur (LYR-10): Adaptive Sample Limit comp setting; 2D layers take one
sample per pixel of shutter travel between Samples Per Frame and the limit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 23:22:53 -04:00
Brandon ThomasandClaude Opus 5.5 6d0d7de922 ANM-5: sampleImage, data footage (JSON/CSV/TSV) with footage().sourceData, thisProject, error list, Expression Language menu
sampleImage renders the sampled layer through the evaluating renderer's footage source
(EvalCtx::footage), cached per thread and frame. File > Import reads .json/.csv/.tsv as data
footage kept in the project. expr.errors collects failing expressions for the error bar;
expr.languageMenu lists the categorised Expression Language menu. Marker keys expose
protectedRegion and cue points; colorDepth, timeToFeetAndFrames, comp.activeCamera.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 22:58:04 -04:00
Brandon ThomasandClaude Opus 5.5 f78435927b M9.9: text style runs + paragraph settings
TextDoc gains character style runs (font, size, colours, tracking, leading,
baseline shift, h/v scale, tsume, faux styles, caps, super/subscript, kerning
metrics/optical/manual, ligatures) and per-paragraph settings (justification
with last-line modes, indents, space before/after, direction, composer,
hanging punctuation), serde-compatible with single-style documents. Layout
handles mixed runs on a line (shared baseline, largest leading), every-line
composer, editing geometry (carets, hit testing, selection quads) and basic
vertical type. Renderer colours per run. Expressions get the sourceText style
API (style, getStyleAt, createStyle, setters with character ranges).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 10:34:13 -04:00