| File: System\Xml\Xsl\QIL\QilValidationVisitor.cs | Web Access |
| Project: src\runtime\src\libraries\System.Private.Xml\src\System.Private.Xml.csproj (System.Private.Xml) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Collections; using System.Diagnostics; namespace System.Xml.Xsl.Qil { /// <summary>An internal class that validates QilExpression graphs.</summary> /// <remarks> /// QilValidationVisitor traverses the QilExpression graph once to enforce the following constraints: /// <list type="bullet"> /// <item>No circular references</item> /// <item>No duplicate nodes (except for references)</item> /// <item>No out-of-scope references</item> /// <item>Type constraints on operands</item> /// <item>Type constraints on operators</item> /// <item>No null objects (except where allowed)</item> /// <item>No Unknown node types</item> /// </list> /// <p>When an error occurs, it marks the offending node with an annotation and continues checking, /// allowing the detection of multiple errors at once and printing the structure after validation. /// (In the case of circular references, it breaks the loop at the circular reference to allow the graph /// to print correctly.)</p> /// </remarks> /// internal sealed class QilValidationVisitor : QilScopedVisitor { //----------------------------------------------- // Entry //----------------------------------------------- [Conditional("DEBUG")] public static void Validate(QilNode node) { Debug.Assert(node != null); new QilValidationVisitor().VisitAssumeReference(node); } private QilValidationVisitor() { } #if DEBUG private readonly ObjectHashtable allNodes = new ObjectHashtable(); private readonly ObjectHashtable parents = new ObjectHashtable(); private readonly ObjectHashtable scope = new ObjectHashtable(); //----------------------------------------------- // QilVisitor overrides //----------------------------------------------- protected override QilNode VisitChildren(QilNode parent) { if (this.parents.Contains(parent)) { // We have already visited the node that starts the infinite loop, but don't visit its children SetError(parent, "Infinite loop"); } else if (AddNode(parent)) { if (parent.XmlType == null) { SetError(parent, "Type information missing"); } else { XmlQueryType type = QilTypeChecker.Check(parent); // BUGBUG: Hack to account for Xslt compiler type fixups if (!type.IsSubtypeOf(parent.XmlType)) SetError(parent, "Type information was not correctly inferred"); } this.parents.Add(parent, parent); for (int i = 0; i < parent.Count; i++) { if (parent[i] == null) { // Allow parameter name and default value to be null if (parent.NodeType == QilNodeType.Parameter) continue; // Do not allow null anywhere else in the graph else SetError(parent, "Child " + i + " must not be null"); } if (parent.NodeType == QilNodeType.GlobalVariableList || parent.NodeType == QilNodeType.GlobalParameterList || parent.NodeType == QilNodeType.FunctionList) { if (((QilReference)parent[i]).DebugName == null) SetError(parent[i], "DebugName must not be null"); } // If child is a reference, then call VisitReference instead of Visit in order to avoid circular visits. if (IsReference(parent, i)) VisitReference(parent[i]); else Visit(parent[i]); } this.parents.Remove(parent); } return parent; } /// <summary> /// Ensure that the function or iterator reference is already in scope. /// </summary> protected override QilNode VisitReference(QilNode node) { if (!this.scope.Contains(node)) SetError(node, "Out-of-scope reference"); return node; } //----------------------------------------------- // QilScopedVisitor overrides //----------------------------------------------- /// <summary> /// Add an iterator or function to scope if it hasn't been added already. /// </summary> protected override void BeginScope(QilNode node) { if (this.scope.Contains(node)) SetError(node, "Reference already in scope"); else this.scope.Add(node, node); } /// <summary> /// Pop scope. /// </summary> protected override void EndScope(QilNode node) { this.scope.Remove(node); } //----------------------------------------------- // Helper methods //----------------------------------------------- private sealed class ObjectHashtable : Hashtable { protected override bool KeyEquals(object? item, object key) { return item == key; } } private bool AddNode(QilNode n) { if (!this.allNodes.Contains(n)) { this.allNodes.Add(n, n); return true; } else { SetError(n, "Duplicate " + n.NodeType + " node"); return false; } } #endif // DEBUG [Conditional("DEBUG")] internal static void SetError(QilNode n, string message) { message = SR.Format(SR.Qil_Validation, message); #if QIL_TRACE_NODE_CREATION message = "{message} [{n.NodeId} ({n.NodeType:G})]"; #endif if (n.Annotation is string s) { message = $"{s}{Environment.NewLine}{message}"; } n.Annotation = message; Debug.Fail(message); } } }