E07940: 'sanitized' cannot be dropped - the declaration this one stands in for (an overridden method or function, a dispatcher entry point, or another locale of the text) marks this parameter 'sanitized'; it may be added, never removed

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

What went wrong

A declaration dropped 'sanitized' from a parameter that a declaration it stands in for marks 'sanitized':

  • an overriding method or function, against EVERY parent that declares it (the super class and each trait - a sanitized trait counts even when a plain super or an earlier trait also declares the method)
  • a dispatcher handler, against the dispatcher entry point
  • a text locale, against the other locales of the text

'sanitized' is part of the contract, like 'pure': it may be ADDED to a parameter, but never REMOVED.

Worked examples

Compilable examples demonstrating this error and the correct fix:

  • ek9 -q 663 (Can an override add or drop 'sanitized' on a parameter?)
  • ek9 -q 686 (Must an override keep the 'sanitized' modifier of its par...)

Rationale

A caller holding the super type relies on the super's contract: a handler typed as Base may be a Derived at run time, and Base promised to clean what it is given. If Derived drops 'sanitized', untrusted input reaches code that never cleans it whenever the runtime type is Derived - a security hole hidden behind dispatch.

Adding 'sanitized' is safe: the receiving method or function cleans its own 'sanitized' parameters, so the stricter override cleans its input on every route, including calls through the super type. A subtype may weaken a precondition, never strengthen it (Liskov Substitution Principle).

How to fix it

  1. Keep 'sanitized' on every parameter the overridden declaration marks 'sanitized'
  2. This applies to methods, trait methods, functions ('is' a super function), dispatcher handlers and text locales
  3. A dispatcher handler is reached BEFORE the entry point's own body runs, so it must clean the parameter itself - keep the entry point's 'sanitized'
  4. If you really need a version without cleaning, write a separate method or function rather than an override

See also

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

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 E07940: 'sanitized' cannot be dropped - the declaration this one stands in for (an overridden method or function, a dispatcher entry point, or another locale of the text) marks this parameter 'sanitized'; it may be added, never removed

#!ek9
defines module error.e07940.bad

  defines trait

    PlainCleaner
      clean() as abstract
        -> input as String
        <- result as String?

    CleaningCleaner
      clean() as abstract
        -> input as sanitized String
        <- result as String?

  defines class

    Base as open
      processInput()
        -> input as sanitized String
        <- result as String?
        result: input

      processNormal()
        -> input as String
        <- result as String?
        result: input

    DerivedRemovesSanitized extends Base
      //ERROR: Super has sanitized, override doesn't
      override processInput()
        -> input as String
        <- result as String?
        result: input

    DerivedAddsSanitized extends Base
      //OK: Super doesn't have sanitized, the override adds it
      override processNormal()
        -> input as sanitized String
        <- result as String?
        result: input

    DerivedCorrect extends Base
      //OK: Sanitized matches super
      override processInput()
        -> input as sanitized String
        <- result as String?
        result: "Derived: " + input

      //OK: Non-sanitized matches super
      override processNormal()
        -> input as String
        <- result as String?
        result: "Derived: " + input

    //Test multiple parameters with mixed sanitized status
    MultiParamBase as open
      //First param sanitized, second not
      processBothSanitized()
        ->
          first as sanitized String
          second as sanitized String
        <- result as String?

        result: first + second

      //Neither param sanitized

      processNeitherSanitized()
        ->
          first as String
          second as String
        <- result as String?

        result: first + second

      processTwo()
        ->
          first as sanitized String
          second as String
        <- result as String?

        result: first + second

      //Both params sanitized

    //ERROR: Swapped sanitized - dropping it from first is an error, adding it to second is allowed
    DerivedSwapsSanitized extends MultiParamBase
      override processTwo()
        ->
          first as String
          second as sanitized String
        <- result as String?

        result: first + second

    //ERROR: Removes sanitized from second param only
    DerivedRemovesOneSanitized extends MultiParamBase
      override processBothSanitized()
        ->
          first as sanitized String
          second as String
        <- result as String?

        result: first + second

    //OK: Adds sanitized to first param only
    DerivedAddsOneSanitized extends MultiParamBase
      override processNeitherSanitized()
        ->
          first as sanitized String
          second as String
        <- result as String?

        result: first + second

    //OK: Multiple params all match correctly
    DerivedMultiParamCorrect extends MultiParamBase
      override processBothSanitized()
        ->
          first as sanitized String
          second as sanitized String
        <- result as String?

        result: `Derived: ${first}${second}`

      override processNeitherSanitized()
        ->
          first as String
          second as String
        <- result as String?

        result: `Derived: ${first}${second}`

      override processTwo()
        ->
          first as sanitized String
          second as String
        <- result as String?

        result: `Derived: ${first}${second}`

  defines class

    //OK: a trait implementation may ADD sanitized
    StrictCleaner with trait of PlainCleaner
      override clean()
        -> input as sanitized String
        <- result as String: input

    //ERROR: the trait declares sanitized, this implementation drops it
    LooseCleaner with trait of CleaningCleaner
      override clean()
        -> input as String
        <- result as String: input

  defines function

    cleanBase() as abstract
      -> input as sanitized String
      <- result as String?

    plainBase() as abstract
      -> input as String
      <- result as String?

    //ERROR: the super function declares sanitized, this implementation drops it
    dropsSanitized() is cleanBase
      -> input as String
      <- result as String: input

    //OK: keeps the super function's sanitized
    keepsSanitized() is cleanBase
      -> input as sanitized String
      <- result as String: input

    //OK: an implementation may ADD sanitized - it cleans its own parameter on every route
    addsSanitized() is plainBase
      -> input as sanitized String
      <- result as String: input

Fixed: Keep 'sanitized' (adding it is fine)

#!ek9
defines module error.e07940.good

  defines class

    Base as open
      describe()
        -> label as String
        stdout <- Stdout()
        stdout.println(label)

      processInput()
        -> input as sanitized String
        stdout <- Stdout()
        stdout.println(input)

    Derived extends Base
      //Keeps the super's sanitized
      override describe()
        -> label as sanitized String
        stdout <- Stdout()
        stdout.println(`Derived: ${label}`)

      override processInput()
        -> input as sanitized String
        stdout <- Stdout()
        stdout.println(`Derived: ${input}`)

      //Adds sanitized to a plain super parameter

Questions and answers

These Q&A entries show E07940 and its fix: