// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace IncludeTool { /// /// Stores information about a circular include path. All headers processed by the tool should form a direct hierarchy, whereby one /// header always includes another. If two headers include each other, we can't reason about what the IWYU derivation for each should /// be in case they're each included by different files. /// class IncludeCycle { /// /// Array of files in this cycle /// public SourceFile[] Files; /// /// List of observed starting points for this cycle, along with a counter for its number of appearances. /// public List> StartingPoints; /// /// Constructor. /// /// List of files in this cycle public IncludeCycle(IEnumerable InFiles) { Files = InFiles.ToArray(); StartingPoints = new List>(); StartingPoints.Add(new KeyValuePair(Files[0], 1)); } /// /// Adds a new starting point for this cycle. If the starting point already exists, increases the counter for number of times it's been seen. /// /// The first file in the cycle. public void AddStartingPoint(SourceFile File) { // Increment the counter for this starting point for(int Idx = 0; ; Idx++) { if(Idx == StartingPoints.Count) { StartingPoints.Add(new KeyValuePair(File, 1)); break; } else if(StartingPoints[Idx].Key == File) { StartingPoints[Idx] = new KeyValuePair(StartingPoints[Idx].Key, StartingPoints[Idx].Value + 1); break; } } // Find the new largest starting point int LargestIdx = 0; for(int Idx = 1; Idx < StartingPoints.Count; Idx++) { if(StartingPoints[Idx].Value > StartingPoints[LargestIdx].Value) { LargestIdx = Idx; } } // If it's not the same as the current one, reorder the sequence SourceFile NewStartingPoint = StartingPoints[LargestIdx].Key; if(NewStartingPoint != Files[0]) { int Offset = Array.IndexOf(Files, NewStartingPoint); SourceFile[] NewFiles = new SourceFile[Files.Length]; for(int Idx = 0; Idx < Files.Length; Idx++) { NewFiles[Idx] = Files[(Offset + Idx) % Files.Length]; } Files = NewFiles; } } /// /// Checks whether an include cycle matches another include cycle, ignoring the start location. /// /// The cycle to compare against /// True if the cycles are identical, false otherwise public bool Matches(IncludeCycle Other) { if(Other.Files.Length != Files.Length) { return false; } int Offset = Array.IndexOf(Other.Files, Files[0]); if(Offset == -1) { return false; } for(int Idx = 0; Idx < Files.Length; Idx++) { if(Files[Idx] != Other.Files[(Offset + Idx) % Files.Length]) { return false; } } return true; } /// /// Returns a hash code for the current cycle, independent of the cycle's starting point. /// /// Hash code for the cycle public override int GetHashCode() { int HashCode = 0; for(int Idx = 0; Idx < Files.Length; Idx++) { // Note: we deliberately do not mutate the hash code in any way that is not commutative if we start // at any point in the array. Summing doesn't provide a huge amount of entropy, but it's safe. HashCode += Files[Idx].Location.GetHashCode(); } return HashCode; } /// /// Creates a human-readable representation of the cycle /// /// String representation of the cycle public override string ToString() { return String.Join(" -> ", Files.Concat(Files).Take(Files.Length + 1).Select(x => x.Location.GetFileName())); } } /// /// Functions to find include cycles /// static class IncludeCycles { /// /// Finds all include cycles from the given set of source files /// /// Sequence of source files to check /// List of cycles public static List FindAll(IEnumerable Files) { List Cycles = new List(); foreach(SourceFile File in Files) { FindCyclesRecursive(File, new List(), new HashSet(), Cycles); } return Cycles; } /// /// Recurses through the given file and all of its includes, attempting to identify cycles. /// /// The file to search through /// Current include stack of the preprocessor /// Set of files that have already been checked for cycles /// List which receives any cycles that are found static void FindCyclesRecursive(SourceFile File, List Includes, HashSet VisitedFiles, List Cycles) { // Check if this include forms a cycle int IncludeIdx = Includes.IndexOf(File); if(IncludeIdx != -1) { IncludeCycle NewCycle = new IncludeCycle(Includes.Skip(IncludeIdx)); for(int Idx = 0;;Idx++) { if(Idx == Cycles.Count) { Cycles.Add(NewCycle); break; } else if(Cycles[Idx].Matches(NewCycle)) { Cycles[Idx].AddStartingPoint(NewCycle.Files[0]); break; } } } // If we haven't already looked from cycles from this include, search now if(!VisitedFiles.Contains(File)) { VisitedFiles.Add(File); Includes.Add(File); foreach(PreprocessorMarkup Markup in File.Markup) { if(Markup.Type == PreprocessorMarkupType.Include && Markup.IncludedFile != null) { SourceFile IncludedFile = Markup.IncludedFile; if(!IncludedFile.Flags.HasFlag(SourceFileFlags.External)) { FindCyclesRecursive(Markup.IncludedFile, Includes, VisitedFiles, Cycles); } } } Includes.RemoveAt(Includes.Count - 1); } } } }