File: System\Xml\Xsl\QIL\QilValidationVisitor.cs
Project: ndp\fx\src\XmlUtils\System.Data.SqlXml.csproj (System.Data.SqlXml)
//------------------------------------------------------------------------------
// <copyright file="QilValidationVisitor.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
// <owner current="true" primary="true">Microsoft</owner>
//------------------------------------------------------------------------------
 
using System.Collections;
using System.Diagnostics;
 
namespace System.Xml.Xsl.Qil {
    using Res = System.Xml.Utils.Res;
 
    /// <summary>A 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 class QilValidationVisitor : QilScopedVisitor {
        private SubstitutionList subs = new SubstitutionList();
        private QilTypeChecker typeCheck = new QilTypeChecker();
 
        //-----------------------------------------------
        // Entry
        //-----------------------------------------------
 
        [Conditional("DEBUG")]
        public static void Validate(QilNode node) {
            Debug.Assert(node != null);
            new QilValidationVisitor().VisitAssumeReference(node);
        }
 
        protected QilValidationVisitor() {}
 
    #if DEBUG
        protected Hashtable allNodes        = new ObjectHashtable();
        protected Hashtable parents         = new ObjectHashtable();
        protected Hashtable 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 = this.typeCheck.Check(parent);
 
                    // 
                    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 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 = Res.GetString(Res.Qil_Validation, message);
 
        #if QIL_TRACE_NODE_CREATION
            message += " ["+ n.NodeId + " (" + n.NodeType.ToString("G") + ")]";
        #endif
 
            string s = n.Annotation as string;
            if (s != null) {
                message = s + "\n" + message;
            }
            n.Annotation = message;
            Debug.Assert(false, message);
        }
    }
}