Commit Graph

12 Commits

Author SHA1 Message Date
Joseph Doherty 9e8b5b0ea7 fix(scripting): stop unbounded script recursion from killing the server (lmxopcua#779)
Reproduced first, out of process (ScriptRecursionContainmentTests re-launches
the xunit v3 test assembly to run one recursive script through
ScriptEvaluator + ScriptRunGuard, so the runner never executes one): before
this change every shape — local function, mutual local functions,
self-referencing Func<>, anonymous self-application, recursion through LINQ,
an expression-bodied void lambda, an anonymous method — ended the child with
exit 134 (SIGABRT, "Stack overflow."). A StackOverflowException cannot be
caught, so in production one script takes the whole node down.

Fix: ScriptEvaluator.Compile now rewrites the validated wrapper
(ScriptStackGuardInjector, after ForbiddenTypeAnalyzer) so the script body and
every lambda, anonymous method and local function starts with
ScriptStackGuard.Check(), which throws ScriptStackExhaustedException when
RuntimeHelpers.TryEnsureSufficientExecutionStack reports the stack nearly
spent. The script then fails like any throwing script (Bad, script log) and
the process lives.

Why a runtime check, not an analyzer rule: recursion is not decidable from
syntax (anonymous self-application never names itself), and bounded recursion
must keep working. Expression-bodied lambdas/local functions become blocks
(`return e;` or `e;`, chosen from the bound symbol, so the same overload
binds); expression-tree and async lambdas are left alone. Limits (documented
on ScriptStackGuard and in docs/VirtualTags.md): depth depends on the thread's
stack size; a recursion that never re-enters script code is not seen; a script
may catch the exception, which only unwinds it.

Tests: ScriptRecursionContainmentTests (7 shapes, child process each; all 7
fail with exit 134 when the rewrite is disabled), ScriptStackGuardInjectionTests
(bounded recursion and every rewritten lambda/local-function shape keep their
value; the expression-tree case fails if those lambdas are rewritten).

Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV
2026-11-09 17:44:35 -05:00
Joseph Doherty 919009c5ef fix(ablegacy): stamp health from the injectable Clock; drop the redundant per-project TreatWarningsAsErrors (lmxopcua#768)
AbLegacyDriver stamped LastSuccessfulRead (init, read, write), the read's sample
timestamps and host-state change times from raw DateTime.UtcNow; only the backoff
path read the injectable Clock. All of them now read Clock(), so
NativePcccBridgeStatusHealthTests drives a manual clock (init at T0, read at T1)
and asserts LastSuccessfulRead == T1 exactly instead of spin-waiting for the wall
clock to pass the init stamp (#767). Without the driver change the 6 tests fail
(init stamp is wall-clock, not T0).

Directory.Build.props sets TreatWarningsAsErrors for every project since #766, so
the 75 per-csproj copies (64 src + 11 tests) are removed and the props comment
says it is now the only place. tools/mem-probe keeps its explicit opt-out.
Release build: 0 warnings, 0 errors; a planted CS0168 still fails the build in a
src project and in a test project that used to carry its own copy.

Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV
2026-11-09 13:40:07 -05:00
Joseph Doherty d7c0135d0b docs(scripting): ctx.CancellationToken also fires on caller cancellation
Review m-6. The XML doc said the token is cancelled on timeout only.
ScriptRunGuard links it to the caller's token
(CreateLinkedTokenSource(ct)), so it is also cancelled on shutdown or
when the evaluating actor stops mid-run. The doc now says so.

Refs #731 #734
Claude-Session: https://claude.ai/code/session_01Ub9hGmYLQvn9XETzw7CMsT
2026-10-22 15:15:49 -04:00
Joseph Doherty d2cc071517 feat(scripting): expose the run token to scripts as ctx.CancellationToken
ScriptContext.CancellationToken is backed by an AsyncLocal that
ScriptEvaluator.RunAsync scopes around the compiled delegate, so it is
exactly the token ScriptRunGuard cancels on timeout. A polling script
can now end its own abandoned run and free the slot. AsyncLocal rather
than a field because the scripted-alarm engine reuses one context per
alarm: an orphan keeps seeing its own cancelled token. The scoping
entry point is internal (IVT to Core.Scripting) so a script cannot
replace its own token.

Editor parity: ScriptAnalysisService dot-completion now walks the
receiver's base chain (instance members only, stopping before
System.Object). Before this, a member declared only on ScriptContext
compiled and published but never completed. DiagnoseTests pins the
member as allow-listed; CompletionTests pins the completion.

Refs #731 #734
Claude-Session: https://claude.ai/code/session_01Ub9hGmYLQvn9XETzw7CMsT
2026-10-22 14:31:03 -04:00
Joseph Doherty a8703c6afe refactor(virtualtags): retire dormant VirtualTagEngine/Source/Definition + TimerTriggerScheduler (#603)
TimerTriggerScheduler was welded to the engine and has no production consumer.

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-08 06:51:22 -04:00
Joseph Doherty 9cad9ed0fc docs(xmldoc): fill missing XML docs + strip tracking-ID comments across src
v2-ci / build (push) Failing after 41s
v2-ci / unit-tests (tests/Core/ZB.MOM.WW.OtOpcUa.Cluster.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests) (push) Has been skipped
Adds <summary>/<param>/<returns>/<inheritdoc> where missing and removes
project bookkeeping IDs (task/tracking refs) from shipped code comments,
so the docs read cleanly and CommentChecker is quiet except for known
false positives (PLC/protocol terms, event/IEqualityComparer inheritdoc).
Doc/comment-only; no logic changed; solution builds clean.
2026-07-07 12:38:39 -04:00
Joseph Doherty 98b27fc1b6 fix(code-review): resolve Batch 1 open findings (AdminUI auth, AlarmHistorian dispose guards, docs)
- AdminUI-001: gate Script editor pages at Administrator,Designer + loosen ScriptAnalysis backend to match
- AdminUI-004: explicit [Authorize] on FleetStatus/Alert/ScriptLog hubs
- Core.AlarmHistorian-014: ObjectDisposedException guards on GetStatus/RetryDeadLettered (+ regression test)
- Core.Scripting.Abstractions-004/-007: Deadband tolerance doc + stale ScriptedAlarms.md path
- Host-003: correct config-overlay precedence in ServiceHosting.md
- Configuration-014: LdapGroupRoleMapping collation-dependency doc
- Driver.TwinCAT.Contracts-002: Structure enum doc (discovery-only sentinel)
2026-06-20 22:30:33 -04:00
Joseph Doherty 145b06bec9 review(Core.Scripting.Abstractions): refresh stale Phase7 labels + document {{equip}}
First review at 7286d320. Five Low doc fixes (BadNodeIdUnknown comment parity, three stale
Phase7 labels -> design-doc cites, {{equip}} token doc on GetTag/SetVirtualTag). Deadband
NaN/negative-tolerance (004) + a stale docs path (007) left Open.
2026-06-19 11:06:56 -04:00
Joseph Doherty b73ce75402 harden(vtag): exclude backslash from passthrough capture + parity tests (A review) 2026-06-07 15:31:54 -04:00
Joseph Doherty 08d7477860 feat(vtag): passthrough fast-path skips Roslyn for mirror scripts (A) 2026-06-07 15:26:20 -04:00
Joseph Doherty 1827c51c42 refactor(scripting): clarify sandbox-pin invariant + add RootNamespace (A0 review) 2026-06-07 15:16:14 -04:00
Joseph Doherty 56cac39216 refactor(scripting): extract script-callable types into Roslyn-free Core.Scripting.Abstractions (A0) 2026-06-07 15:10:00 -04:00