179 lines
6.6 KiB
C#
179 lines
6.6 KiB
C#
//------------------------------------------------------------------------------
|
|
// <copyright file="QilValidationVisitor.cs" company="Microsoft">
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// </copyright>
|
|
// <owner current="true" primary="true">[....]</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);
|
|
}
|
|
}
|
|
}
|