// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. // // This tool exists to transform a high level description of Crst dependencies (i.e. which Crst type may be // acquired before or after other Crst types) into a header file that defines a enum to describe each Crst // type and tables that map type to numerical ranking and a string based name. // // To use the tool, run "csc.exe CrstTypeTool.cs" and run the resulting executable. // // The Crst type definition file is written in a very simple language. Comments begin with '//' and continue // to the end of the line. All remaining tokens after comment removal are simply sequences of non-whitespace // characters separated by whitespace. Keywords are case-insensitive and identifiers (which are always Crst // type names) are case sensitive. The language grammar is given below in EBNF-like form: // // TopLevel ::= CrstDefinition* // // CrstDefinition ::= 'Crst' CrstDependency* 'End' // // CrstDependency ::= 'AcquiredBefore' * // | 'AcquiredAfter' * // | 'SameLevelAs' * // | 'Unordered' // // Crst type names match the CrstType enums used in the source code minus the 'Crst' prefix. For example // CrstAppDomainCache is written as 'AppDomainCache' in the .def file. // // The dependency "A 'AcquiredBefore' B" indicates that CrstA may be legally held while CrstB is acquired. // Similarly "A 'AcquiredAfter' B" indicates that CrstA may be legally acquired while CrstB is held. "A // 'AcquiredBefore' B" is logically equivalent to "B 'AcquiredAfter' A" and authors may enter the dependency // is whichever seems to make the most sense to them (or add both rules if they so desire). // // 'Unordered' indicates that the Crst type does not participate in ranking (there should be very few Crsts // like this and those that are know how to avoid or deal with deadlocks manually). // // 'SameLevelAs' indicates the given Crst type may be acquired alongside any number of instances of the Crst // types indicated. "A 'SameLevel' B" automatically implies "B 'SameLevel' A" so it's not necessary to specify // the dependency both ways though authors can do so if they wish. // // Simple validation of the .def file (over and above syntax checking) is performed by this tool prior to // emitting the header file. This will catch logic errors such as referencing a Crst type that is not // defined or using the 'Unordered' attribute along with any other attribute within a single definition. It // will also catch cycles in the dependency graph (i.e. definitions that logically describe a system where the // Crst types can't be ranked). // using System; using System.Collections.Generic; using System.Text; using System.IO; using System.Text.RegularExpressions; // The main application class containing the program entry point. class CrstTypeTool { // A hash containing every Crst type defined by the input .def file along with its attributes. Keyed by // Crst type name (which is case sensitive and doesn't include the 'Crst' enum prefix). Dictionary m_crsts = new Dictionary(); // The program entry point. public static int Main() { try { // Calculate the filenames of the input and output files. string inputFile = "CrstTypes.def"; string outputFile = "CrstTypes.h"; // A common error is to forget to check out the CrstTypes.h file first. Handle this case specially // so we can give a good error message. if (File.Exists(outputFile) && (File.GetAttributes(outputFile) & FileAttributes.ReadOnly) != 0) { Console.WriteLine(outputFile + " is read-only, you must check it out of TFS/SD first"); return 2; } // Create an instance of our application class to store state in (specifically the collection of // Crst type definitions). CrstTypeTool app = new CrstTypeTool(); // Create a parser for the CrstTypes.def file and run it over the input file (errors are signalled // via exception, in common with all the following steps except validation). new TypeFileParser().ParseFile(inputFile, app.m_crsts); // Validate the collection of Crst type definitions we built up during parsing for common logic // errors and the presence of dependency cycles. False is returned from ValidateCrsts if an error // was detected (an error message will have already been output to the console at this point). if (!app.ValidateCrsts()) return 3; // Perform a topological sort to map each Crst type to a numeric ranking. app.LevelCrsts(); // Emit the new header file containing Crst type definitions and ranking information. app.WriteHeaderFile(outputFile); // If we get here the transformation was successful; inform the user and we're done. Console.WriteLine(outputFile + " successfully updated"); return 0; } catch (TypeFileParser.ParseError pe) { // Syntax errors specific to parsing the input file. Console.WriteLine("ParseError: " + pe.Message); return 4; } catch (Exception e) { // Any other general errors (file I/O problems, out of memory etc.). Console.WriteLine("Unexpected exception:"); Console.WriteLine(e); return 5; } } // Emit the CrstTypes.h output file. void WriteHeaderFile(string fileName) { FileStream stream = new FileStream(fileName, FileMode.Create, FileAccess.Write, FileShare.None); StreamWriter writer = new StreamWriter(stream); // Create a collection based on all the Crst types we've stored in the hash. We do this so we can sort // the Crst types we emit (lexically, based on type name). Dictionary.ValueCollection crstCollection = m_crsts.Values; CrstType[] crsts = new CrstType[crstCollection.Count]; crstCollection.CopyTo(crsts, 0); Array.Sort(crsts); // Emit the header. Contains copyright information, the usual goop to avoid multiple inclusion and a // header comment to discourage direct editing and point the user at the CrstTypes.def file instead // (where all will be explained in greater detail). writer.WriteLine("//"); writer.WriteLine("// Licensed to the .NET Foundation under one or more agreements."); writer.WriteLine("// The .NET Foundation licenses this file to you under the MIT license."); writer.WriteLine("// See the LICENSE file in the project root for more information."); writer.WriteLine("//"); writer.WriteLine(); writer.WriteLine("#ifndef __CRST_TYPES_INCLUDED"); writer.WriteLine("#define __CRST_TYPES_INCLUDED"); writer.WriteLine(); writer.WriteLine("// **** THIS IS AN AUTOMATICALLY GENERATED HEADER FILE -- DO NOT EDIT!!! ****"); writer.WriteLine(); writer.WriteLine("// This file describes the range of Crst types available and their mapping to a numeric level (used by the"); writer.WriteLine("// runtime in debug mode to validate we're deadlock free). To modify these settings edit the"); writer.WriteLine("// file:CrstTypes.def file and run the clr\\bin\\CrstTypeTool utility to generate a new version of this file."); writer.WriteLine(); // Emit the CrstType enum to define a value for each crst type (along with the kNumberOfCrstTypes // constant). writer.WriteLine("// Each Crst type is declared as a value in the following CrstType enum."); writer.WriteLine("enum CrstType"); writer.WriteLine("{"); for (int i = 0; i < crsts.Length; i++) writer.WriteLine(" Crst" + crsts[i].Name + " = " + i.ToString() + ","); writer.WriteLine(" kNumberOfCrstTypes = " + crsts.Length.ToString()); writer.WriteLine("};"); writer.WriteLine(); // This is the end of the regular part of the header included by most files. writer.WriteLine("#endif // __CRST_TYPES_INCLUDED"); writer.WriteLine(); // There is a second section of the header intended for inclusion only by vm\Crst.cpp. This contains // some data tables used to map crst type to rank or name. We could instead define two separate // headers, but on the whole it seems simpler to do it this way. writer.WriteLine("// Define some debug data in one module only -- vm\\crst.cpp."); writer.WriteLine("#if defined(__IN_CRST_CPP) && defined(_DEBUG)"); writer.WriteLine(); // Emit the crst type to rank mapping table. writer.WriteLine("// An array mapping CrstType to level."); writer.WriteLine("int g_rgCrstLevelMap[] ="); writer.WriteLine("{"); foreach (CrstType crst in crsts) writer.WriteLine(" " + crst.Level + ",\t\t\t// Crst" + crst.Name); writer.WriteLine("};"); writer.WriteLine(); // Emit the crst type to name mapping table. writer.WriteLine("// An array mapping CrstType to a stringized name."); writer.WriteLine("LPCSTR g_rgCrstNameMap[] ="); writer.WriteLine("{"); foreach (CrstType crst in crsts) writer.WriteLine(" \"Crst" + crst.Name + "\","); writer.WriteLine("};"); writer.WriteLine(); // Emit the constant Crst.cpp uses to record an unordered rank. writer.WriteLine("// Define a special level constant for unordered locks."); writer.WriteLine("#define CRSTUNORDERED (-1)"); writer.WriteLine(); // Emit a couple of inline helpers to map type to rank or name (and validate the type while they're at // it). writer.WriteLine("// Define inline helpers to map Crst types to names and levels."); writer.WriteLine("inline static int GetCrstLevel(CrstType crstType)"); writer.WriteLine("{"); writer.WriteLine(" LIMITED_METHOD_CONTRACT;"); writer.WriteLine(" _ASSERTE(crstType >= 0 && crstType < kNumberOfCrstTypes);"); writer.WriteLine(" return g_rgCrstLevelMap[crstType];"); writer.WriteLine("}"); writer.WriteLine("inline static LPCSTR GetCrstName(CrstType crstType)"); writer.WriteLine("{"); writer.WriteLine(" LIMITED_METHOD_CONTRACT;"); writer.WriteLine(" _ASSERTE(crstType >= 0 && crstType < kNumberOfCrstTypes);"); writer.WriteLine(" return g_rgCrstNameMap[crstType];"); writer.WriteLine("}"); writer.WriteLine(); // And that's the end of the second section of the header file. writer.WriteLine("#endif // defined(__IN_CRST_CPP) && defined(_DEBUG)"); writer.Close(); stream.Close(); } // Peform checking of the Crst type definitions we've read just read. Various forms of logic error are // scanned for including cycles in the dependency graph. Returns true if no errors are found. If false is // returned a descriptive error message will have already been written to the console. bool ValidateCrsts() { // Look at each Crst type definition in turn. foreach (CrstType crst in m_crsts.Values) { // Catch Crst types that are referenced but never defined. if (!crst.Defined) { Console.WriteLine(String.Format("Error: CrstType 'Crst{0}' is referenced without being defined", crst.Name)); return false; } // Catch the use of the 'Unordered' attribute alongside the 'AcquiredBefore' attribute (which // indicates an ordering). if (crst.Level == CrstType.CrstUnordered && (crst.AcquiredBeforeList.Count > 0 || crst.Group != null)) { Console.WriteLine(String.Format("Error: CrstType 'Crst{0}' is declared as both unordered and acquired before 'Crst{1}'", crst.Name, crst.AcquiredBeforeList[0].Name)); return false; } // Catch the use of the 'Unordered' attribute alongside the 'SameLevelAs' attribute (which // indicates an ordering). if (crst.Level == CrstType.CrstUnordered && crst.Group != null) { Console.WriteLine(String.Format("Error: CrstType 'Crst{0}' is declared as both unordered and in the same level as another CrstType", crst.Name)); return false; } // Catch the simple cycle where the Crst type depends on itself. if (crst.AcquiredBeforeList.Contains(crst)) { Console.WriteLine(String.Format("Error: CrstType 'Crst{0}' is declared as being acquired before itself", crst.Name)); return false; } // Look for deeper cycles using a recursive algorithm in 'FindCycle()'. List cycleList = new List(); if (FindCycle(crst, crst, cycleList)) { Console.WriteLine(String.Format("Error: CrstType 'Crst{0}' is involved in a dependency cycle with the following CrstTypes:", crst.Name)); foreach (CrstType cycleCrst in cycleList) Console.WriteLine(String.Format(" Crst{0}", cycleCrst.Name)); return false; } } // Perform normalization of each set of Crst types that are included in the same group (i.e. have a // 'SameLevelAs' relationship). Normalization means that each Crst type in a group will have exactly // the same set of dependency rules as all the others. CrstTypeGroup.NormalizeAllRules(); // The normalization process could have introduced cycles in the dependency graph so run the cycle // detection pass again. We do separate passes like this since normalizing can lead to less intuitive // error messages if a cycle is found: so if the cycle exists before normalization takes place we want // to generate an error message then. foreach (CrstType crst in m_crsts.Values) { List cycleList = new List(); if (FindCycle(crst, crst, cycleList)) { Console.WriteLine(String.Format("Error: CrstType 'Crst{0}' is involved in a dependency cycle with the following CrstTypes:", crst.Name)); foreach (CrstType cycleCrst in cycleList) Console.WriteLine(String.Format(" Crst{0}", cycleCrst)); Console.WriteLine("Note that the cycle was detected only after 'SameLevelAs' processing was performed so some CrstType dependencies are implied by peer CrstTypes"); return false; } } return true; } // Recursively determine if a cycle exists in the Crst type dependency graph rooted at the 'rootCrst' // type. The 'currCrst' indicates the next dependency to be examined (it will be the same as the // 'rootCrst' when we're first called). The 'cycleList' argument contains a list of Crst types we've // already examined in this branch of the algorithm and serves both to avoid checking the same node twice // and to provide a list of the involved Crst types should a cycle be detected. // Note that this algorithm is not designed to detect general cycles in the graph, only those that involve // the 'rootCrst' directly. This is somewhat inefficient but gives us a simple way to generate clear error // messages. bool FindCycle(CrstType rootCrst, CrstType currCrst, List cycleList) { // Add the current Crst type to the list of those we've seen. cycleList.Add(currCrst); // Look through all the dependencies of the current Crst type. foreach (CrstType childCrst in currCrst.AcquiredBeforeList) { // If we find a reference back to the root Crst type then we've found a cycle. Start backing out // from the recursion (keeping the list of nodes we visited intact) by returning true. if (childCrst == rootCrst) return true; // Otherwise iterate over the dependencies of the current node and for each one that we haven't // already seen and recursively extend the search. if (!cycleList.Contains(childCrst)) if (FindCycle(rootCrst, childCrst, cycleList)) return true; } // Didn't find any cycles involving the root and this node; remove this node from the potential cycle // list and return up to our caller indicating such. cycleList.RemoveAt(cycleList.Count - 1); return false; } // Topologically sort all the Crsts so we can assign a total ordering to them (in the form of a numeric // ranking). Ranks start from 0 (Crst types that may be acquired at any time) and increment from there // (Crst types that may only be acquired if a lower type is not already held). // **** NOTE: The leveling process is destructive in that we will lose all dependency information from the // Crst type definitions during the course of the algorithm. void LevelCrsts() { // Note that Crst type dependency rules have been normalized (by the input parser) so that all // AcquiredBefore/AcquiredAfter relationships have been reduced to AcquiredBefore relationships (i.e. // any rule of the form "A AcquiredAfter B" has been converted to "B AcquiredBefore A". Any // normalization makes the algorithm easier to program, but a normaliztion to AcquiredBefore // relationships was chosen since it makes it particularly easy to implement an algorithm that assigns // ranks beginning with zero and moving up to an arbitrary level. Any type that doesn't have any // AcquiredBefore dependencies can always be ranked at a lower level than any remaining unranked types // by definition and from this we can derive a simple iterative process to rank all the crst types. // Calculate how many Crst types we have left to rank (some are not included in this step because // they've been marked as 'Unordered' in the input file). int unsorted = 0; foreach (CrstType crst in m_crsts.Values) if (crst.Level == CrstType.CrstUnassigned) unsorted++; // The ranking level we're going to assign to Crst types on the next pass of the algorithm. int currLevel = 0; // Iterate while we still have Crst types left to rank. On each pass we'll assign a rank to those // types that no longer have any dependencies forcing them to have a higher rank and then remove // dependency rules involving those newly ranked types from the remaining types. while (unsorted > 0) { // Record a flag indicating whether we manage to assign a rank to at least one Crst type on this // pass. If we ever fail to do this we've hit a cycle (this is just paranoia, the Crst declaration // validation performed in ValidateCrsts() should have detected such a cycle first). bool madeProgress = false; // If we spot any types that are in a group (SameLevelAs relationship) then we defer assigning a // rank till we've dealt with any non-group types (we wish to always place type groups in their // very own rank else the Crst rank violation detection code won't detect violations between // members of the group and singleton types that happened to be assigned rank on the same pass). List deferredGroups = new List(); // Scan through all the Crst types. foreach (CrstType crst in m_crsts.Values) { // Skip those types that already have a rank assigned. if (crst.Level != CrstType.CrstUnassigned) continue; // We're looking for Crst types that no longer have any types that can be acquired while they // are already held. This indicates that it's safe to assign the current rank to them (since // there are no remaining dependencies that need to be ranked first (i.e. with a lower rank // value than this type). if (crst.AcquiredBeforeList.Count == 0) { if (crst.Group == null) { // If this type is not part of the group we can go and assign the rank right away. crst.Level = currLevel; madeProgress = true; unsorted--; } else if (!deferredGroups.Contains(crst.Group)) // Otherwise we'll defer ranking this group member until all the singletons are // processed. deferredGroups.Add(crst.Group); } } // We've gone through the entire collection of Crst types and assigned the current rank level to // any singleton Crst types that qualify. Now deal with any group members we detected (it's // possible that more than one group qualifies for ranking at this level but we need to be careful // to assign distinct rank values to each group to avoid hiding lock rank violations (since group // members are always allowed to be acquired alongside any other type with the same rank value). // Iterate over each distinct group that we found in this pass. foreach (CrstTypeGroup group in deferredGroups) { // Look at our progress flag here. If it is false then we didn't have any singleton Crst types // ranked at this level and we haven't processed any other groups at this level either. Thus // we can rank this group at the current level. Otherwise at least one type was already ranked // with this level so we need to increment to a new, distinct level to avoid ranking // ambiguity. if (madeProgress) currLevel++; // Iterate through each Crst type that is a member of this group assigning them the (same) // current rank. foreach (CrstType crst in group.Members) { // Double check that each member has the same dependencies (i.e. they should all be empty // by now). There should be no way that this error should ever occur, it's just paranoia // on my part. if (crst.AcquiredBeforeList.Count != 0) throw new Exception("Internal error: SameLevel CrstTypes with differing rulesets"); crst.Level = currLevel; unsorted--; } // Once we've processed at least one group we've made progress this iteration. madeProgress = true; } // If we didn't manage to assign rank to at least one Crst type then we're not going to do any // better next iteration either (because no state was updated in this iteration). This should only // occur in the presence of a dependency cycle and we shouldn't get that here after a successful // call to ValidateCrsts(), so this check is pure paranoia. if (!madeProgress) { Console.WriteLine(String.Format("{0} unsorted remain", unsorted)); throw new Exception("Cycle detected trying to assign level " + currLevel.ToString()); } // Loop through all the unranked Crsts types and remove any AcquiredBefore relationships that // involve types we've already leveled (since those types, by definition, have already been // assigned a lower rank). foreach (CrstType crst in m_crsts.Values) { if (crst.Level != CrstType.CrstUnassigned) continue; List prunedCrsts = crst.AcquiredBeforeList.FindAll(Unleveled); crst.AcquiredBeforeList = prunedCrsts; } // Done with this rank level, move to the next. currLevel++; } } // Predicate method used with List.FindAll() to locate Crst types that haven't had their rank assigned // yet. static bool Unleveled(CrstType crst) { return crst.Level == CrstType.CrstUnassigned; } } // Class used to parse a CrstTypes.def file into a dictionary of Crst type definitions. It uses a simple lexer // that removes comments then forms tokens out of any consecutive non-whitespace characters. An equally simple // recursive descent parser forms Crst instances by parsing the token stream. class TypeFileParser { // Remember the input file name and the dictionary we're meant to populate. string m_typeFileName; Dictionary m_crsts; // Compile regular expressions for detecting comments and tokens in the parser input. Regex m_commentRegex = new Regex(@"//.*"); Regex m_tokenRegex = new Regex(@"^(\s*(\S+)\s*)*"); // Input is lexed into an array of tokens. We record the index of the token being currently parsed. Token[] m_tokens; int m_currToken; // Parse the given file into Crst type definitions and place these definitions in the dictionary provided. // Syntax errors are signalled via ParseError derived exceptions. public void ParseFile(string typeFileName, Dictionary crsts) { m_typeFileName = typeFileName; m_crsts = crsts; // Lex the file into tokens. InitTokenStream(); // Parse the tokens according to the grammar set out at the top of this file. // Loop until we have no further tokens to process. while (!IsEof()) { // Grab the next token. Token token = NextToken(); // We're at the top level, so the token had better be 'Crst'. if (token.Id != KeywordId.Crst) throw new UnexpectedTokenError(token, KeywordId.Crst); // OK, parse the rest of this single Crst type definition. ParseCrst(); } } // Parse a single Crst type definition. void ParseCrst() { // The next token had better be an identifier (the Crst type name). Token token = NextToken(); if (token.Id != KeywordId.Id) throw new UnexpectedTokenError(token, KeywordId.Id); // The Crst instance might already exist in the dictionary (forward references to a Crst type cause // these entries to auto-vivify). But in that case the entry better not be marked as 'Defined' which // would indicate a double declaration. CrstType crst; if (m_crsts.ContainsKey(token.Text)) { crst = m_crsts[token.Text]; if (crst.Defined) throw new ParseError(String.Format("Duplicate definition for CrstType '{0}'", token.Text), token); } else { // Otherwise this Crst type hasn't been seen thus far so we allocate a new instance and add it to // the dictionary. crst = new CrstType(token.Text); m_crsts.Add(crst.Name, crst); } // We're defining, not just referencing this type. crst.Defined = true; // Parse any attributes inside this definition (until we see an 'End' token). bool parsingCrst = true; while (parsingCrst) { // Get the next token. Either some attribute keyword or 'End'. token = NextToken(); List list; switch (token.Id) { case KeywordId.AcquiredBefore: // Simply parse the following list of Crst types into the current type's AcquiredBefore list. ParseList(crst.AcquiredBeforeList); break; case KeywordId.AcquiredAfter: // AcquiredAfter is trickier. To make the ranking algorithm's life easier we actually // normalize all rules to the AcquiredBefore form (see LevelCrsts() for the reasoning). So we // capture the list of Crst types that follow the AcquiredAfter keyword and then append the // current type to the AcquiredBefore list of each type found. list = new List(); ParseList(list); foreach (CrstType priorCrst in list) priorCrst.AcquiredBeforeList.Add(crst); break; case KeywordId.SameLevelAs: // Parse the following list of Crst types them let the CrstTypeGroup class handle the // resulting updates to the type groups we're currently maintaining. See the comments for the // CrstTypeGroup class for more details. list = new List(); ParseList(list); foreach (CrstType sameLevelCrst in list) CrstTypeGroup.Join(crst, sameLevelCrst); break; case KeywordId.Unordered: crst.Level = CrstType.CrstUnordered; break; case KeywordId.End: parsingCrst = false; break; default: throw new UnexpectedTokenError(token, KeywordId.AcquiredBefore, KeywordId.AcquiredAfter, KeywordId.SameLevelAs, KeywordId.Unordered); } } } // Parse a list of Crst type names. Any other token terminates the list (without error and without // consuming that token from the stream). The list of tokens is returned as a list of corresponding // CrstTypes (which are auto-vivified in the output dictionary if they haven't been declared yet). void ParseList(List list) { // Parse tokens until we find a non-indentifier. while (true) { Token token = NextToken(); if (token.Id != KeywordId.Id) { // We found the list terminator. Push the non-identifier token back into the stream for our // caller to parse correctly. UnwindToken(); return; } // Look up or add a new CrstType corresponding to this type name. CrstType crst; if (m_crsts.ContainsKey(token.Text)) crst = m_crsts[token.Text]; else { crst = new CrstType(token.Text); m_crsts[crst.Name] = crst; } // Add the type to the output list we're building. list.Add(crst); } } // Lex the input file into an array of tokens. void InitTokenStream() { StreamReader file = new StreamReader(m_typeFileName); int lineNumber = 1; List tokenList = new List(); // Read the file a line at a time. string line; while ((line = file.ReadLine()) != null) { // Remove comments from the current line. line = m_commentRegex.Replace(line, ""); // Match all contiguous non-whitespace characters as individual tokens. Match match = m_tokenRegex.Match(line); if (match.Success) { // For each token captured build a token instance and record the token text and the file, line // and column at which it was encountered (these latter in order to produce useful syntax // error messages). CaptureCollection cap = match.Groups[2].Captures; for (int i = 0; i < cap.Count; i++) tokenList.Add(new Token(m_typeFileName, cap[i].Value, lineNumber, cap[i].Index)); } lineNumber++; } // Record the list of tokens we captured as an array and reset the index of the next token to be // handled by the parser. m_tokens = tokenList.ToArray(); m_currToken = 0; } // Have we run out of tokens to parse? bool IsEof() { return m_currToken >= m_tokens.Length; } // Get the next token and throw an exception if we ran out. Token NextToken() { if (m_currToken >= m_tokens.Length) throw new UnexpectedEofError(); return m_tokens[m_currToken++]; } // Push the last token parsed back into the stream. void UnwindToken() { if (m_currToken <= 0) throw new InvalidOperationException(); m_currToken--; } // The various keywords we can encounter (plus Id for identifiers, which are currently always Crst type // names). internal enum KeywordId { Id, Crst, End, AcquiredBefore, AcquiredAfter, Unordered, SameLevelAs, } // Class encapsulating a single token captured from the input file. internal class Token { // Hash of keyword text to enum values. static Dictionary s_keywords; // The characters comprising the text of the token from the input file. string m_text; // Where the token was found (for error messages). string m_file; int m_line; int m_column; // The ID of the keyword this token represents (or KeywordId.Id). KeywordId m_id; // Static class initialization. static Token() { // Populate the keyword hash. No sense building complex finite state machines to improve the // efficiency of keyword lexing here since the input file (and keyword set) is never going to be // big enough to justify the extra work. s_keywords = new Dictionary(); s_keywords.Add("crst", KeywordId.Crst); s_keywords.Add("end", KeywordId.End); s_keywords.Add("acquiredbefore", KeywordId.AcquiredBefore); s_keywords.Add("acquiredafter", KeywordId.AcquiredAfter); s_keywords.Add("unordered", KeywordId.Unordered); s_keywords.Add("samelevelas", KeywordId.SameLevelAs); } public Token(string file, string text, int line, int column) { m_file = file; m_text = text; m_line = line; m_column = column; // Map token text to keyword ID. True keywords (not identifiers) are case insensitive so normalize // the text to lower case before performing the keyword hash lookup. string canonName = m_text.ToLower(); if (s_keywords.ContainsKey(canonName)) m_id = s_keywords[canonName]; else m_id = KeywordId.Id; } public string Text {get { return m_text; }} public string Location {get { return String.Format("{0} line {1}, column {2}", m_file, m_line, m_column); }} public KeywordId Id {get { return m_id; }} } // Base class for all syntax errors reported by the parser. internal class ParseError : Exception { // A raw error message. public ParseError(string message) : base(message) {} // An error message tagged with a file, line and column (coming from an error token). public ParseError(string message, Token errorToken) : base(String.Format("{0}: {1}", errorToken.Location, message)) {} // Produce a textual name for the given keyword type. protected static string IdToName(KeywordId id) { if (id == KeywordId.Id) return "a CrstType name"; return String.Format("'{0}'", id.ToString()); } } // Syntax error used when an unexpected token is encountered which further lists the valid tokens that // would otherwise have been accepted. internal class UnexpectedTokenError : ParseError { // Produce an unexpected token message with a file, line and column coming from an error token and // optionally the names of zero or more tokens that would have been accepted. public UnexpectedTokenError(Token errorToken, params KeywordId[] expected) : base(FormatErrorMessage(errorToken, expected)) {} static string FormatErrorMessage(Token errorToken, KeywordId[] expected) { StringBuilder message = new StringBuilder(String.Format("Unexpected token '{0}' at {1}", errorToken.Text, errorToken.Location)); if (expected.Length == 0) { } else if (expected.Length == 1) { message.Append(String.Format("; expected {0}", IdToName(expected[0]))); } else { message.Append("; expected one of "); for (int i = 0; i < expected.Length - 1; i++) message.Append(String.Format("{0}, ", IdToName(expected[i]))); message.Append(IdToName(expected[expected.Length - 1])); } return message.ToString(); } } // Syntax error used when we unexpectedly ran out of tokens. internal class UnexpectedEofError : ParseError { public UnexpectedEofError() : base("Unexpected end of file") {} } } // This class represents an instance of a Crst type. These are unqiuely identified by case-sensitive name (the // same as the enum name used in vm code, minus the 'Crst' prefix). class CrstType : IComparable { // Special level constants used to indicate unordered Crst types or those types we haven't gotten around // to ranking yet. public static readonly int CrstUnordered = -1; public static readonly int CrstUnassigned = -2; // Name of the type, e.g. "AppDomainCache" for the CrstAppDomainCache type. string m_name; // The numeric ranking assigned to this type. Starts as CrstUnassigned and then becomes either // CrstUnordered (while parsing the input file) or a number >= 0 (during LevelCrsts()). int m_level; // List of Crst types that can be legally acquired while this one is held. (AcquiredAfter relationships // are by switching the terms and adding to the second type's AcquiredBefore list). List m_acquiredBeforeCrsts; // Either null if this Crst type is not in (or has not yet been determined to be in) a SameLevelAs // relationship or points to a CrstTypeGroup that records all the sibling types at the same level (that // have been discovered thus far during parsing). CrstTypeGroup m_group; // Set once a definition for this type has been discovered. Used to detect double definitions and types // referenced without definitions. bool m_defined; public CrstType(string name) { m_name = name; m_level = CrstUnassigned; m_acquiredBeforeCrsts = new List(); m_group = null; m_defined = false; } public string Name {get { return m_name; }} public int Level {get { return m_level; } set { m_level = value; }} public List AcquiredBeforeList {get { return m_acquiredBeforeCrsts; } set { m_acquiredBeforeCrsts = value; }} public CrstTypeGroup Group {get { return m_group; } set { m_group = value; }} public bool Defined {get {return m_defined; } set { m_defined = value; }} // Helper used to sort CrstTypes. The sort order is lexical based on the type name. public int CompareTo(object other) { return m_name.CompareTo(((CrstType)other).m_name); } } // Every time a SameLevelAs relationship is used we need to be careful to keep track of the transitive closure // of all types bound in the relationship. That's because such a relationship impacts the other dependency // rules (each member of a SameLevelAs group must behave as though it has exactly the same dependency rules as // all the others). Identifying all the members is tricky because "A SameLevelAs B" and "B SameLevelAs C" // implies "A SameLevelAs C". So we use a separate tracking structure, instances of the CrstTypeGroup type, to // do the bookkeeping for us. Each Crst type belongs to either zero or one CrstTypeGroups. As we find new // SameLevelAs relationships we create new groups, add types to existing groups or merge groups (as previous // distinct groups are merged by the discovery of a SameLevelAs relationship that links them). By the time // parsing has finished we are guaranteed to have discovered all the distinct, disjoint groups and to have // fully populated them with the transitive closure of all related types. We can them normalize all groups // members so they share the same AcquiredBefore relationships. class CrstTypeGroup { // We record every group that has been formed so far. This makes normalizing all groups easier. static List s_groups = new List(); // Crst types that are members of the current group. There are no duplicates in this list. List m_members = new List(); // Declare a SameLevelAs relationship between the two Crst types given. Groups will be assigned, created // or merged as required to maintain our guarantees (each CrstType is a member of at most one group and // all CrstTypes involved in the same transitive closure of a SameLevelAs relationship are members of one // group). public static void Join(CrstType crst1, CrstType crst2) { CrstTypeGroup group; if (crst1 == crst2) { // In this case the type refers to itself. Create a singleton group for this type if it doesn't // already exist. if (crst1.Group == null) { group = new CrstTypeGroup(); group.m_members.Add(crst1); s_groups.Add(group); crst1.Group = group; } } else if (crst1.Group == null && crst2.Group == null) { // Neither types belong to a group already. So we can create a new one and add both types to it. group = new CrstTypeGroup(); group.m_members.Add(crst1); group.m_members.Add(crst2); s_groups.Add(group); crst1.Group = group; crst2.Group = group; } else if (crst1.Group == null) { // The first type doesn't belong to a group yet but the second does. So we can simply add the // first type to the second group. group = crst2.Group; group.m_members.Add(crst1); crst1.Group = group; } else if (crst2.Group == null) { // As for the case above but the group/no-group positions are reversed. group = crst1.Group; group.m_members.Add(crst2); crst2.Group = group; } else if (crst1.Group != crst2.Group) { // Both types belong to different groups so we'll have to merge them. Add the members of group 2 // to group 1 and throw away group 2. group = crst1.Group; CrstTypeGroup absorbGroup = crst2.Group; foreach (CrstType crst in absorbGroup.m_members) { group.m_members.Add(crst); crst.Group = group; } s_groups.Remove(absorbGroup); } // The only case left is when both types are already in the same group and there's no work needed in // this case. } // Normalize all the groups we created during parsing. See below for the definition of normalization. public static void NormalizeAllRules() { foreach (CrstTypeGroup group in s_groups) group.NormalizeRules(); } // Normalize this group. This involves adjusting the AcquiredBefore list of each member to be the union of // all such rules within the group. This step allows us to detect cycles in the dependency graph that // would otherwise remain hidden if we only examined the unnormalized AcquiredBefore rules. void NormalizeRules() { // This local will contain the union of all AcquiredBefore rules. List acquiredBeforeList = new List(); // Iterate through each member of the group. foreach (CrstType crst in m_members) { // Add each AcquiredBefore rule we haven't already seen to the union. foreach (CrstType afterCrst in crst.AcquiredBeforeList) if (!acquiredBeforeList.Contains(afterCrst)) acquiredBeforeList.Add(afterCrst); } // Reset each member's AcquiredBefore list to a copy of the union we calculated. Note it's important // to make a (shallow) copy because the ranking process modifies this list and so a shared copy would // cause unexpected results. foreach (CrstType crst in m_members) crst.AcquiredBeforeList = acquiredBeforeList.GetRange(0, acquiredBeforeList.Count); } public List Members {get { return m_members; }} }