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.
This commit is contained in:
Joseph Doherty
2026-07-07 12:38:39 -04:00
parent 384dbd7d36
commit 9cad9ed0fc
375 changed files with 1899 additions and 2493 deletions
@@ -32,8 +32,8 @@ namespace ZB.MOM.WW.OtOpcUa.Core.Scripting;
/// </para>
/// <para>
/// <b>Lifecycle:</b> compiled scripts hold a collectible
/// <see cref="System.Runtime.Loader.AssemblyLoadContext"/> per evaluator
/// (Core.Scripting-008 fix). <see cref="Clear"/> disposes every materialised
/// <see cref="System.Runtime.Loader.AssemblyLoadContext"/> per evaluator.
/// <see cref="Clear"/> disposes every materialised
/// evaluator before dropping its dictionary entry so the emitted assemblies are
/// eligible for GC immediately after a publish. <see cref="Dispose"/> drops the
/// cache itself for graceful server shutdown.
@@ -75,7 +75,7 @@ public sealed class CompiledScriptCache<TContext, TResult> : IDisposable
// value reference, so if two threads race the same bad source both observe
// the same faulted Lazy and both reach this catch, and a concurrent retry
// re-added a fresh Lazy under the same key between the two removals, the
// second removal does NOT evict the in-flight retry. (Core.Scripting-006.)
// second removal does NOT evict the in-flight retry.
_cache.TryRemove(new KeyValuePair<string, Lazy<ScriptEvaluator<TContext, TResult>>>(key, lazy));
throw;
}
@@ -88,28 +88,28 @@ public sealed class CompiledScriptCache<TContext, TResult> : IDisposable
/// Drop every cached compile. Used on config generation publish + tests.
/// Disposes each materialised evaluator before removing it so its collectible
/// <see cref="System.Runtime.Loader.AssemblyLoadContext"/> unloads and the
/// emitted script assembly becomes eligible for GC (Core.Scripting-008).
/// emitted script assembly becomes eligible for GC.
/// </summary>
/// <remarks>
/// Safe to call after <see cref="Dispose"/> — the operation is idempotent.
/// <see cref="Dispose"/> sets <c>_disposed = true</c> before invoking this
/// method (so callers see the post-Dispose guard on <see cref="GetOrCompile"/>),
/// but this method itself MUST run to completion so the Dispose-triggered
/// drain actually unloads every materialised evaluator's ALC. (Core.Scripting-016
/// uncovered this — a previous Clear-aborts-when-disposed guard silently
/// skipped the entire drain on Dispose, leaving emitted assemblies rooted.)
/// drain actually unloads every materialised evaluator's ALC (a previous
/// Clear-aborts-when-disposed guard silently skipped the entire drain on
/// Dispose, leaving emitted assemblies rooted).
/// </remarks>
public void Clear()
{
// Snapshot (key, value) pairs and remove with the value-scoped
// TryRemove(KeyValuePair<,>) overload — same shape as the
// Core.Scripting-006 fix in GetOrCompile's catch block. A concurrent
// fix in GetOrCompile's catch block. A concurrent
// GetOrCompile re-add that hashes to the same key between our snapshot
// and the TryRemove inserts a *different* Lazy reference; the value-
// scoped removal sees the mismatch and leaves the fresh entry intact
// (instead of evicting + disposing it while the concurrent caller
// still holds it). The fresh evaluator and its ALC stay live for the
// concurrent caller. (Core.Scripting-014.)
// concurrent caller.
foreach (var entry in _cache.ToArray())
{
if (_cache.TryRemove(entry))
@@ -8,8 +8,8 @@ namespace ZB.MOM.WW.OtOpcUa.Core.Scripting;
/// <summary>
/// Parses a script's source text + extracts every <c>ctx.GetTag("literal")</c> and
/// <c>ctx.SetVirtualTag("literal", ...)</c> call. Outputs the static dependency set
/// the virtual-tag engine uses to build its change-trigger subscription graph (Phase
/// 7 plan decision #7 — AST inference, operator doesn't maintain a separate list).
/// the virtual-tag engine uses to build its change-trigger subscription graph (AST
/// inference, operator doesn't maintain a separate list).
/// </summary>
/// <remarks>
/// <para>
@@ -29,7 +29,7 @@ namespace ZB.MOM.WW.OtOpcUa.Core.Scripting;
/// extractor while still having a working script. Calls with the same method name on
/// a different receiver (<c>other.GetTag("X")</c>) are explicitly ignored so that
/// scripts defining local helper types with matching names do not produce spurious
/// dependencies. (Core.Scripting-004.)
/// dependencies.
/// </para>
/// </remarks>
public static class DependencyExtractor
@@ -39,6 +39,7 @@ public static class DependencyExtractor
/// paths, or a list of rejection messages if non-literal paths were used.
/// </summary>
/// <param name="scriptSource">The script source code to analyze.</param>
/// <returns>The inferred read/write tag dependencies, plus any non-literal-path rejections.</returns>
public static DependencyExtractionResult Extract(string scriptSource)
{
if (string.IsNullOrWhiteSpace(scriptSource))
@@ -81,7 +82,6 @@ public static class DependencyExtractor
// field). Calls with the same method name on a different receiver (e.g.
// someHelper.GetTag("X")) are ignored — not picking them up avoids spurious
// dependencies when scripts define local types with matching method names.
// (Core.Scripting-004.)
if (node.Expression is MemberAccessExpressionSyntax member
&& member.Expression is IdentifierNameSyntax receiver
&& receiver.Identifier.ValueText == "ctx")
@@ -113,7 +113,7 @@ public static class DependencyExtractor
// rather than StringLiteralToken. Checking the expression kind
// (StringLiteralExpression) covers all token kinds Roslyn assigns to literal
// strings, so a """raw""" path is harvested rather than mis-rejected as a
// dynamic path. (Core.Scripting-005.)
// dynamic path.
if (pathArg is not LiteralExpressionSyntax literal
|| !literal.IsKind(SyntaxKind.StringLiteralExpression))
{
@@ -17,13 +17,13 @@ namespace ZB.MOM.WW.OtOpcUa.Core.Scripting;
/// </summary>
/// <remarks>
/// <para>
/// Deny-list is the authoritative Phase 7 plan decision #6 set:
/// Deny-list is the authoritative Phase 7 plan set:
/// <c>System.IO</c>, <c>System.Net</c>, <c>System.Diagnostics</c>,
/// <c>System.Reflection</c>, <c>System.Threading.Thread</c>,
/// <c>System.Threading.Tasks</c> (scripts are synchronous predicates — no
/// legitimate need to start background tasks; a <c>Task.Run</c> fan-out outlives
/// the evaluation timeout entirely), <c>System.Runtime.InteropServices</c>,
/// <c>Microsoft.Win32</c>. (Core.Scripting-003.)
/// <c>Microsoft.Win32</c>.
/// </para>
/// <para>
/// Deny-list prefix match. <c>System.Net</c> catches <c>System.Net.Http</c>,
@@ -45,7 +45,7 @@ namespace ZB.MOM.WW.OtOpcUa.Core.Scripting;
/// <c>Activator.CreateInstance</c> is a reflection-equivalent escape; <c>GC</c>
/// and <c>AppDomain</c> expose process-wide control. Legitimate <c>System</c>
/// types (<c>Math</c>, <c>String</c>, <c>Convert</c>, <c>DateTime</c>, …) are not
/// on the list and stay usable. (Core.Scripting-001.)
/// on the list and stay usable.
/// </para>
/// </remarks>
public static class ForbiddenTypeAnalyzer
@@ -69,14 +69,13 @@ public static class ForbiddenTypeAnalyzer
// ForbiddenFullTypeNames instead so the check actually fires.
"System.Threading.Tasks", // Task.Run / Parallel — scripts are synchronous predicates
// and have no legitimate need to start background work;
// a Task fan-out outlives the evaluation timeout entirely
// (Core.Scripting-003).
// a Task fan-out outlives the evaluation timeout entirely.
"System.Runtime.InteropServices",
"System.Runtime.Loader", // AssemblyLoadContext + AssemblyDependencyResolver —
// arbitrary DLL load into the host process
// (Core.Scripting-012). Namespace-prefix rather than
// type-granular so future BCL additions to this
// namespace are denied by default.
// arbitrary DLL load into the host process.
// Namespace-prefix rather than type-granular so
// future BCL additions to this namespace are
// denied by default.
"Microsoft.Win32", // registry
];
@@ -104,15 +103,14 @@ public static class ForbiddenTypeAnalyzer
/// per-evaluation timeout; denied type-granularly because its containing
/// namespace is <c>System.Threading</c> (shared with allowed types like
/// <c>CancellationToken</c>), so a namespace-prefix rule cannot reach it
/// without blocking unrelated types. (Core.Scripting-010.)</item>
/// without blocking unrelated types.</item>
/// <item><c>System.Threading.ThreadPool</c> / <c>System.Threading.Timer</c> —
/// background-work vectors that outlive the per-evaluation timeout; same
/// threat as <c>Task.Run</c> that Core.Scripting-003 closed. (Core.Scripting-012.)</item>
/// threat as <c>Task.Run</c>.</item>
/// <item><c>System.Runtime.CompilerServices.Unsafe</c> — exposes raw type-
/// reinterpretation (<c>As&lt;TFrom,TTo&gt;</c>, <c>As&lt;T&gt;(object)</c>)
/// that bypasses the CLR type system without requiring an <c>unsafe</c> context at
/// the call site; enables type-confusion and managed heap corruption.
/// (Core.Scripting-017.)</item>
/// the call site; enables type-confusion and managed heap corruption.</item>
/// </list>
/// </remarks>
public static readonly IReadOnlyList<string> ForbiddenFullTypeNames =
@@ -124,18 +122,15 @@ public static class ForbiddenTypeAnalyzer
// System.Threading.Thread lives in the System.Threading namespace (shared with
// CancellationToken, SemaphoreSlim, etc.), so a namespace-prefix deny-list cannot
// target it without blocking those legitimate types. Denied type-granularly here.
// (Core.Scripting-010.)
"System.Threading.Thread",
// Core.Scripting-012 — broadening the references list to the BCL trusted-platform-
// assemblies set (Core.Scripting-008 follow-up) re-exposed two background-work
// vectors the original deny-list missed. Both live in System.Threading (shared
// with allowed sync primitives like CancellationToken / SemaphoreSlim), so they
// must be denied type-granularly:
// Broadening the references list to the BCL trusted-platform-assemblies set
// re-exposed two background-work vectors the original deny-list missed. Both live
// in System.Threading (shared with allowed sync primitives like CancellationToken /
// SemaphoreSlim), so they must be denied type-granularly:
//
// System.Threading.ThreadPool — QueueUserWorkItem / UnsafeQueueUserWorkItem
// re-introduce the background-fanout threat
// Core.Scripting-003 closed against
// System.Threading.Tasks.
// closed against System.Threading.Tasks.
// System.Threading.Timer — Timer(callback, …) schedules unbounded work
// that outlives the per-evaluation timeout.
//
@@ -144,14 +139,14 @@ public static class ForbiddenTypeAnalyzer
// namespace are denied by default.
"System.Threading.ThreadPool",
"System.Threading.Timer",
// Core.Scripting-017 — System.Runtime.CompilerServices.Unsafe exposes raw
// type-reinterpretation (Unsafe.As<TFrom, TTo>, Unsafe.As<T>(object)) that
// bypasses the CLR type system without requiring an 'unsafe' compilation context
// at the call site. In a script, calling Unsafe.As<string>(someObject) can corrupt
// managed heap references and cause type-confusion in downstream virtual-tag
// consumers. Type-granular (not namespace-prefix) because System.Runtime.CompilerServices
// also contains harmless compile-time-only types (CallerMemberNameAttribute,
// MethodImplAttribute, MethodImplOptions) that scripts may legitimately reference.
// System.Runtime.CompilerServices.Unsafe exposes raw type-reinterpretation
// (Unsafe.As<TFrom, TTo>, Unsafe.As<T>(object)) that bypasses the CLR type system
// without requiring an 'unsafe' compilation context at the call site. In a script,
// calling Unsafe.As<string>(someObject) can corrupt managed heap references and
// cause type-confusion in downstream virtual-tag consumers. Type-granular (not
// namespace-prefix) because System.Runtime.CompilerServices also contains harmless
// compile-time-only types (CallerMemberNameAttribute, MethodImplAttribute,
// MethodImplOptions) that scripts may legitimately reference.
"System.Runtime.CompilerServices.Unsafe",
];
@@ -176,7 +171,7 @@ public static class ForbiddenTypeAnalyzer
/// declares it, even though the receiver type <c>System.Type</c> is allowed).
/// </para>
/// <para>
/// Pass (2) — the Core.Scripting-002 fix — resolves the <em>type</em> of every
/// Pass (2) resolves the <em>type</em> of every
/// <c>TypeSyntax</c> node via <c>GetTypeInfo</c>. The old walker only inspected
/// the four node kinds above, so a forbidden type named through
/// <c>typeof(System.IO.File)</c>, a generic argument
@@ -223,7 +218,7 @@ public static class ForbiddenTypeAnalyzer
break;
}
// Pass (2) — type-reference surface (Core.Scripting-002). Every TypeSyntax
// Pass (2) — type-reference surface. Every TypeSyntax
// resolves to the type it names, regardless of the syntactic form that
// introduced it (typeof operand, cast type, generic argument, default(T)
// operand, array element type, is/as pattern type, declared local type).
@@ -275,10 +270,10 @@ public static class ForbiddenTypeAnalyzer
var typeName = typeSymbol.ToDisplayString();
// The broadened walk (Core.Scripting-002) resolves both GetSymbolInfo and
// GetTypeInfo on every node, so the same forbidden reference can be hit several
// times. Dedupe on span + type so the operator sees one rejection per offending
// reference, not a noisy pile of identical messages.
// The broadened walk resolves both GetSymbolInfo and GetTypeInfo on every node,
// so the same forbidden reference can be hit several times. Dedupe on span + type
// so the operator sees one rejection per offending reference, not a noisy pile of
// identical messages.
if (rejections.Any(r => r.Span == span && r.TypeName == typeName))
return;
@@ -298,9 +293,9 @@ public static class ForbiddenTypeAnalyzer
}
// Type-granular deny-list — dangerous types that live in the allow-listed
// System namespace and so cannot be caught by ForbiddenNamespacePrefixes
// (Core.Scripting-001). Matched on the full type name; OriginalDefinition
// unwraps any generic construction before naming.
// System namespace and so cannot be caught by ForbiddenNamespacePrefixes.
// Matched on the full type name; OriginalDefinition unwraps any generic
// construction before naming.
var fullTypeName = typeSymbol.OriginalDefinition.ToDisplayString(
SymbolDisplayFormat.FullyQualifiedFormat.WithGlobalNamespaceStyle(
SymbolDisplayGlobalNamespaceStyle.Omitted));
@@ -18,9 +18,9 @@ namespace ZB.MOM.WW.OtOpcUa.Core.Scripting;
/// Scripts are wrapped in a synthesized <c>CompiledScript.Run(globals)</c> method
/// and compiled via <see cref="CSharpCompilation"/> into a regular .NET assembly
/// that is loaded into a <b>collectible</b>
/// <see cref="AssemblyLoadContext"/>. The collectible ALC is the fix for
/// Core.Scripting-008: per-publish recompile accretion was previously unbounded
/// because Roslyn's <c>CSharpScript.CreateDelegate</c> emits into the default ALC
/// <see cref="AssemblyLoadContext"/>. The collectible ALC fixes unbounded
/// per-publish recompile accretion: previously
/// Roslyn's <c>CSharpScript.CreateDelegate</c> emitted into the default ALC
/// (non-collectible); now <see cref="Dispose"/> unloads the entire ALC and the
/// emitted assembly becomes eligible for GC.
/// </para>
@@ -38,8 +38,8 @@ namespace ZB.MOM.WW.OtOpcUa.Core.Scripting;
/// </para>
/// <para>
/// Runtime exceptions thrown from user code propagate unwrapped. The virtual-tag
/// engine catches them per-tag and maps to <c>BadInternalError</c> quality
/// per Phase 7 decision #11; this layer doesn't swallow anything so tests can
/// engine catches them per-tag and maps to <c>BadInternalError</c> quality;
/// this layer doesn't swallow anything so tests can
/// assert on the original exception type.
/// </para>
/// <para>
@@ -66,6 +66,7 @@ public sealed class ScriptEvaluator<TContext, TResult> : IDisposable
/// <summary>Compiles user script source into an evaluator.</summary>
/// <param name="scriptSource">The user script source code to compile.</param>
/// <returns>A ready-to-run <see cref="ScriptEvaluator{TContext, TResult}"/> wrapping the compiled script.</returns>
public static ScriptEvaluator<TContext, TResult> Compile(string scriptSource)
{
if (scriptSource is null) throw new ArgumentNullException(nameof(scriptSource));
@@ -78,7 +79,7 @@ public sealed class ScriptEvaluator<TContext, TResult> : IDisposable
var wrapperSource = BuildWrapperSource(scriptSource, sandbox.Imports);
var syntaxTree = CSharpSyntaxTree.ParseText(wrapperSource);
// Step 1a — defend against wrapper-source injection (Core.Scripting-013).
// Step 1a — defend against wrapper-source injection.
// A script body of `return 0; } public static int Evil() { return 0;` would
// close the synthesized `Run` method early, declare a sibling `Evil` method
// inside the synthesized `CompiledScript` class, and leave the wrapper's
@@ -173,6 +174,7 @@ public sealed class ScriptEvaluator<TContext, TResult> : IDisposable
/// <summary>Runs the script against an already-constructed context.</summary>
/// <param name="context">The script context.</param>
/// <param name="ct">Cancellation token for the operation.</param>
/// <returns>A task carrying the script's result.</returns>
public Task<TResult> RunAsync(TContext context, CancellationToken ct = default)
{
if (_disposed) throw new ObjectDisposedException(nameof(ScriptEvaluator<TContext, TResult>));
@@ -215,7 +217,7 @@ public sealed class ScriptEvaluator<TContext, TResult> : IDisposable
/// exactly one member — the <c>Run</c> method. Anything else (a sibling
/// method, nested class, additional class in the namespace, free-floating
/// top-level statement) means the user source closed the synthesized braces
/// early and injected its own declarations. (Core.Scripting-013.)
/// early and injected its own declarations.
/// </summary>
private static void EnforceSingleRunMember(SyntaxTree syntaxTree)
{
@@ -344,7 +346,7 @@ public sealed class ScriptEvaluator<TContext, TResult> : IDisposable
// emits as `global::Ns.Outer<T>.Inner<U>` — valid C#. The pre-fix code
// used `IndexOf('`')` to find the FIRST backtick and truncated the
// entire name there, silently dropping the rest of the nested-generic
// closed args. (Core.Scripting-015.)
// closed args.
var rawName = t.GetGenericTypeDefinition().FullName!.Replace('+', '.');
var allArgs = t.GetGenericArguments();
var segments = rawName.Split('.');
@@ -403,7 +405,7 @@ internal sealed class ScriptAssemblyLoadContext : AssemblyLoadContext
/// reports compile-time errors against the wrapper source. Mirrors the
/// <c>Microsoft.CodeAnalysis.Scripting.CompilationErrorException</c> from the legacy
/// <c>CSharpScript</c> path so callers (engines + the Admin test-harness) keep the
/// same catch site after the Core.Scripting-008 rewrite.
/// same catch site after the rewrite.
/// </summary>
public sealed class CompilationErrorException : Exception
{
@@ -5,7 +5,7 @@ namespace ZB.MOM.WW.OtOpcUa.Core.Scripting;
/// <summary>
/// Factory for the compile-time sandbox every user script is built against.
/// Implements Phase 7 plan decision #6 (read-only sandbox) by whitelisting only the
/// Enforces a read-only sandbox by whitelisting only the
/// assemblies + namespaces the script API needs; no <c>System.IO</c>, no
/// <c>System.Net</c>, no <c>System.Diagnostics.Process</c>, no
/// <c>System.Reflection</c>. Attempts to reference those types in a script fail at
@@ -18,8 +18,8 @@ namespace ZB.MOM.WW.OtOpcUa.Core.Scripting;
/// <c>mscorlib</c> / <c>System.Runtime</c> — this class overrides that with an
/// explicit minimal allow-list. The list is the same regardless of whether
/// <see cref="ScriptEvaluator{TContext, TResult}"/> uses the legacy
/// <c>CSharpScript</c> path or the collectible-<c>AssemblyLoadContext</c> path
/// (Core.Scripting-008): both go through <see cref="Build"/>.
/// <c>CSharpScript</c> path or the collectible-<c>AssemblyLoadContext</c> path:
/// both go through <see cref="Build"/>.
/// </para>
/// <para>
/// Namespaces pre-imported so scripts don't have to write <c>using</c> clauses:
@@ -43,6 +43,7 @@ public static class ScriptSandbox
/// to resolve <c>ctx.GetTag(...)</c> calls.
/// </summary>
/// <param name="contextType">The concrete script context type to use for compilation.</param>
/// <returns>The sandbox configuration (assembly references + default imports) to compile the script against.</returns>
public static SandboxConfig Build(Type contextType)
{
if (contextType is null) throw new ArgumentNullException(nameof(contextType));
@@ -74,8 +75,8 @@ public static class ScriptSandbox
// type by FQN — including the ones we forbid (HttpClient, File, Process,
// Registry, etc.). Letting those types resolve at compile is intentional: the
// hard security gate is ForbiddenTypeAnalyzer in step 3 of the compile pipeline
// (Core.Scripting-001 / -002 established the analyzer must be the sole gate
// because type forwarding makes any references-list-only restriction porous).
// (the analyzer must be the sole gate because type forwarding makes any
// references-list-only restriction porous).
// The references list now serves only as scoping hygiene — out-of-band BCL
// surface (operator-authored hosting helpers, third-party packages, app code)
// is not on the list and stays unreachable.
@@ -118,7 +119,7 @@ public static class ScriptSandbox
string.Equals(name, "mscorlib.dll", StringComparison.Ordinal) ||
// Microsoft.Win32.Registry isn't a System.* DLL but the analyzer's
// Microsoft.Win32 deny-list relies on the type being resolvable so it
// can identify + reject it (Core.Scripting-001 / -002). Add the one
// can identify + reject it. Add the one
// DLL we need rather than broadening to Microsoft.* (which would also
// pull in compilers, build tooling, etc.).
string.Equals(name, "Microsoft.Win32.Registry.dll", StringComparison.Ordinal))
@@ -90,7 +90,7 @@ public sealed class TimedScriptEvaluator<TContext, TResult>
// When both fire at nearly the same time, WaitAsync observes them in
// non-deterministic order, so a cancel that arrives a few µs after the
// timeout still reaches here as TimeoutException. Re-check the token so
// the guarantee holds regardless of race ordering. (Core.Scripting-007.)
// the guarantee holds regardless of race ordering.
if (ct.IsCancellationRequested)
throw new OperationCanceledException(ct);
throw new ScriptTimeoutException(Timeout);
@@ -101,7 +101,7 @@ public sealed class TimedScriptEvaluator<TContext, TResult>
/// <summary>
/// Thrown when a script evaluation exceeds its configured timeout. The virtual-tag
/// engine (Stream B) catches this + maps the owning tag's quality to
/// <c>BadInternalError</c> per Phase 7 plan decision #11, logging a structured
/// <c>BadInternalError</c> per the Phase 7 plan, logging a structured
/// warning with the offending script name so operators can locate + fix it.
/// </summary>
public sealed class ScriptTimeoutException : Exception