E07930: cannot assign from sanitized parameter - creates hidden copy semantics

Category: E07xxx Full resolution · Reported in phase: FULL_RESOLUTION · Classification: CANNOT_ASSIGN_FROM_SANITIZED · All error codes

What went wrong

Direct assignment FROM a sanitized parameter creates hidden aliasing. When you write 'local <- sanitizedParam', both variables point to the SAME sanitized copy - mutations to one affect the other.

Worked examples

Compilable examples demonstrating this error and the correct fix:

  • ek9 -q 662 (How do I safely work with sanitized parameters without al...)
  • ek9 -q 686 (Must an override keep the 'sanitized' modifier of its par...)
  • ek9 -q 665 (How do sanitized parameters work with web service content...)

Rationale

A 'sanitized' parameter is the receiving function's own cleaned copy: on entry the function replaces it with a copy (or an unset String for a threat), so the caller's String is never shared. Direct assignment then creates hidden aliasing of that copy: after 'alias <- data', both names refer to the SAME String, so 'alias += " mod"' also changes 'data'.

This defeats the purpose of defensive copying and creates confusing aliasing semantics that are invisible at the code level.

Hidden aliasing through operators

CRITICAL: Operators that RETURN one of their operands (not a new value) can also create hidden aliases to sanitized parameters:

  • '<?' and '>?' (comparison coalescing, min/max) return the smaller/larger operand (the left one on a tie)
  • '?:' (coalesce, Elvis) returns the left operand if set, otherwise the right
  • a ternary returns one of its two arms

Guards in control flow

Guards in if, switch, and other control flow statements also create aliases: a declaration guard ('if local <- input', 'switch local <- input') and an assignment pre-flow ('if local := input then local?') each make 'local' the same String as the sanitized parameter. The [EXAMPLE] section below has every one of these forms, each failing with this error, and the fixes.

Pure context constraints

In a 'pure' context, :=: and :~: are NOT available (they mutate). Your options are limited to:

  1. New declaration of a copy: local <- String(input)
  2. Direct use without assignment
  3. Pass to another pure function
  4. String concatenation (creates new string)

How to fix it

  1. USE: local <- String(input) - works in ALL contexts including pure
  2. USE: Direct use, pass to function, or concatenation
  3. NON-PURE ONLY: :=: or :~: on initialized variables

See also

  • E07920: sanitized in wrong location
  • E07930: assignment from sanitized (this error)
  • E07940: sanitized dropped in an override

https://ek9.io/sanitized.html https://ek9.io/forAI.json#$.sanitizedTypes

Examples

Every example is compiled by the EK9 compiler: the first fails exactly as captioned, the others fix it.

Fails with E07930: cannot assign from sanitized parameter - creates hidden copy semantics

#!ek9
defines module error.e07930.bad

  defines function

    badDeclarationFromSanitized()
      -> input as sanitized String
      <- result as String?

      //ERROR: Direct declaration assignment from sanitized parameter
      local <- input

      //Use local to avoid NOT_REFERENCED error
      result: local

    badReassignmentFromSanitized()
      -> input as sanitized String
      <- result as String?

      local <- "initial"
      //ERROR: Reassignment from sanitized parameter
      local := input

      result: local

    badGuardedAssignmentFromSanitized()
      -> input as sanitized String
      <- result as String?

      local <- String()
      //ERROR: Assign-if-unset from sanitized parameter
      local :=? input

      result: local

    okDirectUse()
      -> input as sanitized String
      <- result as String?

      //OK: Using sanitized parameter directly is fine
      result: input

    okExplicitCopy()
      -> input as sanitized String
      <- result as String?

      //OK: Explicit copy makes semantics clear
      local <- String(input)
      result: local

    okPassToAnotherFunction()
      -> input as sanitized String
      <- result as String?

      //OK: Passing to another function is fine
      result: processData(input)

    processData()
      -> data as String
      <- result as String?
      result: data

    // ... operator-variants

    <?-
      BAD: Guard in if statement could create alias to sanitized parameter.
      The guard uses '<-' which is an assignment.
    -?>
    badGuardInIfFromSanitized()
      -> input as sanitized String
      <- result as String?

      //ERROR: Guard creates alias to sanitized parameter
      if local <- input
        result: local
      else
        result: "empty"

    <?-
      BAD: Guard in switch statement could create alias to sanitized parameter.
    -?>
    badGuardInSwitchFromSanitized()
      -> input as sanitized String
      <- result as String?

      //ERROR: Guard creates alias to sanitized parameter
      switch local <- input
        case "test"
          result: local
        default
          result: "other"

    <?-
      BAD: An assignment pre-flow (:=) could create alias to sanitized parameter.
      The ':=' assigns to an EXISTING variable; 'then local?' then gates on it being set.
    -?>
    badGuardExpressionFromSanitized()
      -> input as sanitized String
      <- result as String?

      //Declare local first (required for the ':=' pre-flow)
      local <- String()

      //ERROR: Guard creates alias to sanitized parameter
      if local := input then local?
        result: local
      else
        result: "empty"

Fixed: Copy it, or use it directly

#!ek9
defines module error.e07930.good

  defines function

    //A new String made from the sanitized one
    process() as pure
      -> input as sanitized String
      <- rtn as String?

      localCopy <- String(input)
      rtn: "Processed: " + localCopy

    //Use it directly in an expression
    query() as pure
      -> input as sanitized String
      <- rtn as String: "Query: " + input

Fixed: Outside pure code, copy or merge into an initialised variable

#!ek9
defines module error.e07930.good.nonpure

  defines function

    //Copy INTO an initialised variable
    useCopyOperator()
      -> input as sanitized String
      <- rtn as String: String()
      rtn :=: String(input)

    //Merge INTO an initialised variable
    useMergeOperator()
      -> input as sanitized String
      <- rtn as String: "Base: "
      rtn :~: String(input)

    //Use it directly, or pass it on
    useDirectly()
      -> input as sanitized String
      stdout <- Stdout()
      stdout.println(input)

Questions and answers

These Q&A entries show E07930 and its fix: