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
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
- Keep 'sanitized' on every parameter the overridden declaration marks 'sanitized'
- This applies to methods, trait methods, functions ('is' a super function), dispatcher handlers and text locales
- 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'
- 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: