fix(schema): resolve the declared type through the element-ref chain for the list/union xsi:type check; invalid-instances-accepted 78 to 66 (#181, #147)
The list/union xsi:type derivation check decided whether the declared type admits a list or union substitute from a single type-table lookup, so it never reached the concrete type of an element declared by reference (`<xs:element ref="x">`): such an element's list/union xsi:type was wrongly accepted. declaredAdmitsAnySimpleType now resolves the declared type through the full element-ref / type-reference chain (resolvedDeclaredType, the type-returning companion to resolvedDeclaredTypeName) to the concrete .simple/.complex before deciding. A list or union xsi:type on a ref'd element whose declaration resolves to a more specific atomic type is now rejected; the chain is cycle-bounded and falls through to suppress (under-reject) when it cannot resolve, so a ur-typed, list/union-typed, or unresolvable declared type still admits the substitute. invalid-instances-accepted 78 to 66, no false positive (valid-instances-rejected and valid-schemas-rejected held at 0), no other bucket moved. An adversarial review exercised ref chains to anySimpleType/anyType/untyped declarations, named and aliased list/union declarations, a deep alias chain, cross-namespace imported refs, substitution-group heads, atomic restriction substitutes, and a self- referential alias cycle, and found no over-rejection.
This commit is contained in:
parent
05a0aa4305
commit
c80659d80d
@ -21,7 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
### Fixed
|
||||
|
||||
- XSTS invalid-instances-accepted (#181, #147): an `xsi:type` naming a list or union simple type is now rejected unless the element's declared type is a ur-type (`anySimpleType`/`anyType`). A list or union type derives only from `anySimpleType` (XSD Part 2), so it cannot validly substitute for an element of any more specific atomic or complex type (`cvc-elt.4.3.2.1`); naming one on, say, an `xs:int`-declared element is invalid. A genuinely derived (restriction) substitute, a list/union under an `anySimpleType` declaration, and a declared type that is itself a list or union are all left alone, so no valid instance is newly rejected. This widening became safe once the derivation backbone was keyed by namespaced identity (#186); the conservative atomic/built-in not-derived cases remain under-rejected pending the full type-identity relation. invalid-instances-accepted 81 → 78, no false positive (valid-instances-rejected and valid-schemas-rejected held at 0), no other bucket moved.
|
||||
- XSTS invalid-instances-accepted (#181, #147): an `xsi:type` naming a list or union simple type is now rejected unless the element's declared type is a ur-type (`anySimpleType`/`anyType`). A list or union type derives only from `anySimpleType` (XSD Part 2), so it cannot validly substitute for an element of any more specific atomic or complex type (`cvc-elt.4.3.2.1`); naming one on, say, an `xs:int`-declared element is invalid. A genuinely derived (restriction) substitute, a list/union under an `anySimpleType` declaration, and a declared type that is itself a list or union are all left alone, so no valid instance is newly rejected. The declared type is resolved through the element-ref / type-reference chain, so the check also fires for an element reached via `<xs:element ref>` whose global declaration is atomic. This widening became safe once the derivation backbone was keyed by namespaced identity (#186); the conservative atomic/built-in not-derived cases remain under-rejected pending the full type-identity relation. invalid-instances-accepted 81 → 66, no false positive (valid-instances-rejected and valid-schemas-rejected held at 0), no other bucket moved.
|
||||
|
||||
- XSTS invalid-instances-accepted (#186, #147): the instance-validity derivation and substitution tables are now keyed by namespaced identity (`{ns}local`) instead of bare local name, so two same-local-name types or elements in different namespaces no longer collide. `DerivationTables` keeps its bare-keyed fields for the schema-validity machinery (Particle-Valid-Restriction and the consistency rules) and gains parallel namespaced fields built in the same pass; only the instance consumers (the `xsi:type` blocked/not-derived/abstract checks, the substitution-group closure, and `referenceParticle`) read the namespaced tables, reducing a `type:{ns}local` reference, a bare name, or an element ref to a `{ns}local` key. A key miss stays permissive (no constraint), so the re-keying cannot over-reject; the single-document corpus behaviour is unchanged. With the namespaced backbone in place, a schema's `blockDefault` now supplies the `{disallowed substitutions}` (`block`) of every element and complex type that states none (own `block` overrides; `block=""` clears; intersected with the methods each component admits), without one namespace's defaults shadowing another's. invalid-instances-accepted 89 → 81, no false positive (valid-instances-rejected and valid-schemas-rejected held at 0; an adversarial review exercised same- and cross-namespace substitutions, no-targetNamespace keys, transitive substitution groups, and blockDefault override cases), no other bucket moved.
|
||||
|
||||
|
||||
@ -52,22 +52,31 @@ extension PureXML.Schema.ComplexValidator {
|
||||
/// admitting, so the list/union check stays silent rather than risk a false
|
||||
/// positive on an inline or unknown declared type.
|
||||
private func declaredAdmitsAnySimpleType(_ declared: PureXML.Schema.ElementType) -> Bool {
|
||||
switch declared {
|
||||
let resolved: PureXML.Schema.ElementType? = switch declared {
|
||||
case .simple, .complex: declared
|
||||
case let .typeReference(reference): resolvedDeclaredType(reference)
|
||||
}
|
||||
switch resolved {
|
||||
case let .simple(type): return type.isAnySimpleType || Self.isListOrUnion(type)
|
||||
case let .complex(type): return Self.isUrType(type)
|
||||
case let .typeReference(reference):
|
||||
guard case let .simple(type)? = Self.resolveNamedType(reference, in: types) else {
|
||||
if case .complex? = Self.resolveNamedType(reference, in: types) { return urReference(reference) }
|
||||
return true
|
||||
}
|
||||
return type.isAnySimpleType || Self.isListOrUnion(type)
|
||||
default: return true
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether a reference resolving to a complex type is the complex ur-type.
|
||||
private func urReference(_ reference: String) -> Bool {
|
||||
guard case let .complex(type)? = Self.resolveNamedType(reference, in: types) else { return false }
|
||||
return Self.isUrType(type)
|
||||
/// Follows an element-ref / type-reference chain (`element:foo` -> concrete type)
|
||||
/// to the concrete declared type (a `.simple` or `.complex`), or nil when the
|
||||
/// chain does not resolve. The name-returning ``resolvedDeclaredTypeName`` walks
|
||||
/// the same chain for the backbone key.
|
||||
private func resolvedDeclaredType(_ reference: String) -> PureXML.Schema.ElementType? {
|
||||
var key = reference
|
||||
var resolved = types[key] ?? Self.resolveNamedType(key, in: types)
|
||||
var steps = 0
|
||||
while case let .typeReference(next)? = resolved, steps <= types.count {
|
||||
key = next
|
||||
resolved = types[key] ?? Self.resolveNamedType(key, in: types)
|
||||
steps += 1
|
||||
}
|
||||
return resolved
|
||||
}
|
||||
|
||||
/// Whether a simple type is a list or union (deriving only from `anySimpleType`).
|
||||
|
||||
@ -40,4 +40,22 @@ struct SchemaListUnionXsiTypeTests {
|
||||
#expect(try schema(declaredType: "xs:anySimpleType").validate(doc("myList", "1 2")).isEmpty)
|
||||
#expect(try schema(declaredType: "xs:anySimpleType").validate(doc("myUnion", "x")).isEmpty)
|
||||
}
|
||||
|
||||
/// The declared type is resolved through the element-ref chain: a list/union
|
||||
/// xsi:type on an element reached via `<xs:element ref>` (whose global
|
||||
/// declaration is atomic) is still rejected.
|
||||
@Test("a list/union xsi:type on a ref'd atomic element is rejected")
|
||||
func test_listUnionOnReferencedAtomicRejected() throws {
|
||||
let schema = try PureXML.Schema.Document("""
|
||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
||||
<xs:element name="root"><xs:complexType><xs:sequence>
|
||||
<xs:element ref="leaf"/>
|
||||
</xs:sequence></xs:complexType></xs:element>
|
||||
<xs:element name="leaf" type="xs:int"/>
|
||||
<xs:simpleType name="myList"><xs:list itemType="xs:int"/></xs:simpleType>
|
||||
</xs:schema>
|
||||
""")
|
||||
let xml = #"<root><leaf xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="myList">1 2</leaf></root>"#
|
||||
#expect(try !schema.validate(xml).isEmpty)
|
||||
}
|
||||
}
|
||||
|
||||
@ -150,13 +150,13 @@ struct XSTSSuiteTests {
|
||||
private let knownSchemaValidRejected = 0
|
||||
private let knownSchemaInvalidAccepted = 69
|
||||
private let knownInstanceValidRejected = 0
|
||||
/// Instance bucket: xsi:type must derive from the declared type (133 -> 107);
|
||||
/// block="substitution" bars members even for an untyped head (107 -> 101); ur-type
|
||||
/// `xsd:anyType` wildcards are `lax` (101 -> 95); identity fields compare in the decimal
|
||||
/// value space (95 -> 94); attribute wildcard is the INTERSECTION of its sources (94 -> 89);
|
||||
/// `blockDefault` supplies a missing `block` after re-keying derivation by namespace
|
||||
/// (89 -> 81); a list/union xsi:type is not derived from a non-ur type (81 -> 78).
|
||||
private let knownInstanceInvalidAccepted = 78
|
||||
/// Instance bucket: xsi:type must derive from the declared type (133 -> 107); block="substitution"
|
||||
/// bars members even for an untyped head (107 -> 101); ur-type `xsd:anyType` wildcards are `lax`
|
||||
/// (101 -> 95); identity fields compare in the decimal value space (95 -> 94); attribute wildcard
|
||||
/// is the INTERSECTION of its sources (94 -> 89); `blockDefault` supplies a missing `block` after
|
||||
/// re-keying derivation by namespace (89 -> 81); a list/union xsi:type is not derived from a
|
||||
/// non-ur declared type, resolved through an element-ref chain (81 -> 66).
|
||||
private let knownInstanceInvalidAccepted = 66
|
||||
|
||||
@Test("Every XSTS case behaves: compile, reject, validate, invalidate")
|
||||
func test_suite() throws {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user