Class SharedStateMutationOutsideLockOrError

java.lang.Object
org.ek9lang.compiler.common.RuleSupport
org.ek9lang.compiler.common.TypedSymbolAccess
org.ek9lang.compiler.phase5.SharedStateMutationOutsideLockOrError
All Implemented Interfaces:
Consumer<EK9Parser.AssignmentStatementContext>

final class SharedStateMutationOutsideLockOrError extends TypedSymbolAccess implements Consumer<EK9Parser.AssignmentStatementContext>
The principal data-race check (slice 1, step 7): every mutation of shared state inside a function/method marked CONCURRENT_ENTRY_POINT must occur inside a MutexLock.enter(...) callback.

"Shared state" in this foundation pass means a field of the enclosing aggregate — a write to this.<field> (or the bare-identifier shorthand) from inside a Component method, dynamic-class method, or other method that is transitively reachable from an HTTP service operation, TCP/Signals handler, or async stream stage. Subsequent passes will widen the rule to non-pure calls on shared receivers and capture-write through closures.

Exemptions:

  • MutexKey body — the lambda passed to enter()/tryEnter(). Writes here are by definition inside the lock.
  • Constructor body — never carries the marker (Step 6/6b skip constructors). The object hasn't escaped yet so its fields aren't shared; E08250 forbids early publication.

A local that refers to shared state (counter <- shared.total) is that state under another name: recordReference(ReferenceAssignment) marks it (REFERS_TO_SHARED_STATE, followed through further locals), an in-place mutation of it (counter += 1, counter :=: 5) or a write to a field through it is a write to shared state. Re-pointing the local (counter := 5) is not.

Every in-place mutation is seen, whatever statement applies it: an assignment's in-place operators (+= -= *= /= :=: :^: :~:), the separate x++ / x-- statement, and the | a stream termination or tee in applies to its target (mutatedInPlaceOrError(ISymbol, Token)). A target whose type is Stdout or Stderr is exempt (isStandardOutput(ISymbol)).

Beside a signal handler (design §13.11 S2c, option A, Steve 2026-11-02): code that runs on the program's own thread while a signal handler can run on the dispatcher (RunsBesideSignalHandlers) is held to the same rule for what a signal handler can reach - a property of a signal-reached type (IsSignalReached) written in its own method or through an object of the type, or the very value a handler captured changed in place - wherever it is written, before register or after. Two fixes: build the state complete when it is made (construction is exempt), or hold it in a MutexLock.

Part of EK9's compile-time data-race detection (slice 1, model rule §3.5). Error E08251 SHARED_STATE_MUTATION_OUTSIDE_LOCK.

  • Constructor Details

  • Method Details

    • capturedBySignalHandlers

      void capturedBySignalHandlers(Set<ISymbol> captured)
      The variables of this source that a signal handler captures (identity), for the beside-a-signal-handler rule.
    • accept

      public void accept(EK9Parser.AssignmentStatementContext ctx)
      Specified by:
      accept in interface Consumer<EK9Parser.AssignmentStatementContext>
    • acceptIncrementOrDecrement

      void acceptIncrementOrDecrement(EK9Parser.StatementContext ctx)
      The statement x++ / x--: a separate grammar alternative from an assignment, but the same in-place mutation as x += 1 (Integer._inc mutates the object, it stores nothing).
    • acceptStreamTarget

      void acceptStreamTarget(EK9Parser.PipelinePartContext target)
      The target of a stream termination (> target, >> target) or of a tee in target: each item is piped in with the target's | operator, which mutates the target in place.
    • recordReference

      void recordReference(ReferenceAssignment reference)
      Mark a local that a declaration or assignment makes refer to shared state - a field of the enclosing aggregate, a capture, 'this', or a local already marked - directly or through a chain of field accesses. A chain ending in a call is not followed (the call may return a fresh value).