File: System\Xml\Xsl\QIL\QilValidationVisitor.cs
Web Access
Project: src\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);
        }
    }
}