static void Main() { int expectedNumberOfNodes = 1000; MemoryGraph memoryGraph = new MemoryGraph(expectedNumberOfNodes); GrowableArray <NodeIndex> tempForChildren = new GrowableArray <NodeIndex>(); // We can make a new Node index NodeIndex newNodeIdx = memoryGraph.CreateNode(); NodeIndex childIdx = memoryGraph.CreateNode(); // NodeTypeIndex newNodeType = memoryGraph.CreateType("MyChild"); memoryGraph.SetNode(childIdx, newType, 100, tempForChildren); memoryGraph.AllowReading(); // Serialize to a file memoryGraph.WriteAsBinaryFile("file.gcHeap"); // Can unserialize easily. // var readBackIn = MemoryGraph.ReadFromBinaryFile("file.gcHeap"); }
private NodeTypeIndex GetNodeTypeIndex(ProfilerTypeID typeId) { var typeIdasInt = (int)typeId; NodeTypeIndex ret = NodeTypeIndex.Invalid; if (typeIdasInt >= m_profilerTypeToNodeType.Count) { int prevSize = m_profilerTypeToNodeType.Count; int newSize = typeIdasInt + 100; m_profilerTypeToNodeType.Count = newSize; for (int i = prevSize; i < newSize; i++) { m_profilerTypeToNodeType[i] = NodeTypeIndex.Invalid; } } else { ret = m_profilerTypeToNodeType[typeIdasInt]; } if (ret == NodeTypeIndex.Invalid) { ProfilerType profilerType = m_clrProfilerParser.GetTypeById(typeId); ret = CreateType(profilerType.name); m_profilerTypeToNodeType[typeIdasInt] = ret; // TODO FIX NOW don't allocate every time GetType(ret, AllocTypeNodeStorage()).ModuleName = profilerType.ModuleName; } return(ret); }
private NodeIndex AddLineData(ref LineData lineData) { NodeIndex nodeIndex = m_graph.GetNodeIndex(lineData.Offset); NodeTypeIndex nodeType = GetType(lineData.Kind + " " + lineData.Name, lineData.Size); m_graph.SetNode(nodeIndex, nodeType, lineData.Size, lineData.Children); return(nodeIndex); }
private void SetTypePriorities(string priorityPats) { if (m_typePriorities == null) { m_typePriorities = new float[(int)m_graph.NodeTypeIndexLimit]; } string[] priorityPatArray = priorityPats.Split(';'); Regex[] priorityRegExArray = new Regex[priorityPatArray.Length]; float[] priorityArray = new float[priorityPatArray.Length]; for (int i = 0; i < priorityPatArray.Length; i++) { var m = Regex.Match(priorityPatArray[i], @"(.*)->(-?\d+.?\d*)"); if (!m.Success) { if (string.IsNullOrWhiteSpace(priorityPatArray[i])) { continue; } throw new ApplicationException("Priority pattern " + priorityPatArray[i] + " is not of the form Pat->Num."); } var dotNetRegEx = FilterStackSource.ToDotNetRegEx(m.Groups[1].Value.Trim()); priorityRegExArray[i] = new Regex(dotNetRegEx, RegexOptions.IgnoreCase); priorityArray[i] = float.Parse(m.Groups[2].Value); } // Assign every type index a priority in m_typePriorities based on if they match a pattern. NodeType typeStorage = m_graph.AllocTypeNodeStorage(); for (NodeTypeIndex typeIdx = 0; typeIdx < m_graph.NodeTypeIndexLimit; typeIdx++) { var type = m_graph.GetType(typeIdx, typeStorage); var fullName = type.Name; for (int regExIdx = 0; regExIdx < priorityRegExArray.Length; regExIdx++) { var priorityRegEx = priorityRegExArray[regExIdx]; if (priorityRegEx == null) { continue; } var m = priorityRegEx.Match(fullName); if (m.Success) { m_typePriorities[(int)typeIdx] = priorityArray[regExIdx]; // m_log.WriteLine("Type {0} assigned priority {1:f3}", fullName, priorityArray[regExIdx]); break; } } } }
/// <summary> /// Reads the NodeTypes element /// </summary> private static void ReadNodeTypesFromXml(XmlReader reader, MemoryGraph graph) { Debug.Assert(reader.NodeType == XmlNodeType.Element); var inputDepth = reader.Depth; reader.Read(); // Advance to children while (inputDepth < reader.Depth) { if (reader.NodeType == XmlNodeType.Element) { switch (reader.Name) { case "NodeType": { NodeTypeIndex readTypeIndex = (NodeTypeIndex)FetchInt(reader, "Index", -1); int size = FetchInt(reader, "Size"); string typeName = reader.GetAttribute("Name"); string moduleName = reader.GetAttribute("Module"); if (typeName == null) { throw new ApplicationException("NodeType element does not have a Name attribute"); } if (readTypeIndex == NodeTypeIndex.Invalid) { throw new ApplicationException("NodeType element does not have a Index attribute."); } if (readTypeIndex != 0 || typeName != "UNDEFINED") { NodeTypeIndex typeIndex = graph.CreateType(typeName, moduleName, size); if (readTypeIndex != typeIndex) { throw new ApplicationException("NodeType Indexes do not start at 1 and increase consecutively."); } } reader.Skip(); } break; default: Debug.WriteLine("Skipping unknown element {0}", reader.Name); reader.Skip(); break; } } else if (!reader.Read()) { break; } } }
public override StackSourceFrameIndex GetFrameIndex(StackSourceCallStackIndex callStackIndex) { NodeIndex nodeIndex = (NodeIndex)callStackIndex; // Orphan node support if (nodeIndex == m_graph.NodeIndexLimit) { return((StackSourceFrameIndex)m_graph.NodeTypeIndexLimit); } NodeTypeIndex typeIndex = m_graph.GetNode(nodeIndex, m_nodeStorage).TypeIndex; return((StackSourceFrameIndex)typeIndex); }
public static MemoryGraph Create(string dllPath, SymbolReader symbolReader) { var ret = new MemoryGraph(1000); string pdbPath = symbolReader.FindSymbolFilePathForModule(dllPath); symbolReader.Log.WriteLine("Got PDB path {0}", pdbPath); NativeSymbolModule module = symbolReader.OpenNativeSymbolFile(pdbPath); List <Symbol> symbols = new List <Symbol>(); AddAllChildren(symbols, module.GlobalSymbol); symbols.Sort(); /****** Make a graph out of the symbols ******/ // Put all nodes under this root. var rootChildren = new GrowableArray <NodeIndex>(1000); // Create a node for each symbol uint lastRVA = 0; string lastName = "Header"; var empty = new GrowableArray <NodeIndex>(); foreach (var symbol in symbols) { var symRVA = symbol.RVA; int lastSize = (int)symRVA - (int)lastRVA; NodeTypeIndex typeIdx = ret.CreateType(lastName, null, lastSize); NodeIndex nodeIdx = ret.CreateNode(); ret.SetNode(nodeIdx, typeIdx, lastSize, empty); rootChildren.Add(nodeIdx); lastName = symbol.Name; lastRVA = symRVA; } // TODO FIX NOW dropping the last symbol. // Create the root node. NodeIndex rootIdx = ret.CreateNode(); NodeTypeIndex rootTypeIdx = ret.CreateType("METHODS"); ret.SetNode(rootIdx, rootTypeIdx, 0, rootChildren); ret.RootIndex = rootIdx; ret.AllowReading(); return(ret); }
public override string GetFrameName(StackSourceFrameIndex frameIndex, bool verboseName) { NodeTypeIndex typeIndex = (NodeTypeIndex)frameIndex; // Orphan node support if (typeIndex == m_graph.NodeTypeIndexLimit) { return("[not reachable from roots]"); } var type = m_graph.GetType(typeIndex, m_typeStorage); var moduleName = type.ModuleName; var ret = type.Name; if (moduleName != null) { if (verboseName) { int length = moduleName.Length - 4; if ((length >= 0) && (moduleName[length] == '.')) { moduleName = moduleName.Substring(0, length); } } else { moduleName = System.IO.Path.GetFileNameWithoutExtension(moduleName); } if (moduleName.Length == 0) { moduleName = "?"; } ret = moduleName + "!" + ShortenNameSpaces(type); } // TODO FIX NOW remove priority // ret += " " + m_typePriorities[(int)type.Index].ToString("f1"); // TODO FIX NOW hack for CLRProfiler comparison // ret = Regex.Replace(ret, @" *\[\]", "[]"); // ret = Regex.Replace(ret, @"`\d+", ""); return(ret); }
/// <summary> /// Reads the Nodes Element /// </summary> private static void ReadNodesFromXml(XmlReader reader, MemoryGraph graph) { Debug.Assert(reader.NodeType == XmlNodeType.Element); var inputDepth = reader.Depth; reader.Read(); // Advance to children var children = new GrowableArray <NodeIndex>(1000); var typeStorage = graph.AllocTypeNodeStorage(); while (inputDepth < reader.Depth) { if (reader.NodeType == XmlNodeType.Element) { switch (reader.Name) { case "Node": { NodeIndex readNodeIndex = (NodeIndex)FetchInt(reader, "Index", -1); NodeTypeIndex typeIndex = (NodeTypeIndex)FetchInt(reader, "TypeIndex", -1); int size = FetchInt(reader, "Size"); if (readNodeIndex == NodeIndex.Invalid) { throw new ApplicationException("Node element does not have a Index attribute."); } if (typeIndex == NodeTypeIndex.Invalid) { throw new ApplicationException("Node element does not have a TypeIndex attribute."); } // TODO FIX NOW very inefficient. Use ReadValueChunk and FastStream to make more efficient. children.Clear(); var body = reader.ReadElementContentAsString(); foreach (var num in Regex.Split(body, @"\s+")) { if (num.Length > 0) { children.Add((NodeIndex)int.Parse(num)); } } if (size == 0) { size = graph.GetType(typeIndex, typeStorage).Size; } // TODO should probably just reserve node index 0 to be an undefined object? NodeIndex nodeIndex = 0; if (readNodeIndex != 0) { nodeIndex = graph.CreateNode(); } if (readNodeIndex != nodeIndex) { throw new ApplicationException("Node Indexes do not start at 0 or 1 and increase consecutively."); } graph.SetNode(nodeIndex, typeIndex, size, children); } break; default: Debug.WriteLine("Skipping unknown element {0}", reader.Name); reader.Skip(); break; } } else if (!reader.Read()) { break; } } }
public PdbScopeMemoryGraph(string pdbScopeFile) : base(10000) { var children = new GrowableArray <NodeIndex>(1000); Dictionary <string, NodeTypeIndex> knownTypes = new Dictionary <string, NodeTypeIndex>(1000); XmlReaderSettings settings = new XmlReaderSettings() { IgnoreWhitespace = true, IgnoreComments = true }; using (XmlReader reader = XmlReader.Create(pdbScopeFile, settings)) { int foundBestRoot = int.MaxValue; // it is zero when when we find the best root. Address imageBase = 0; uint sizeOfImageHeader = 0; Address lastAddress = 0; int badValues = 0; Queue <Section> sections = new Queue <Section>(); Address prevAddr = 0; Address expectedAddr = 0; RootIndex = NodeIndex.Invalid; NodeIndex firstNodeIndex = NodeIndex.Invalid; while (reader.Read()) { if (reader.NodeType == XmlNodeType.Element) { switch (reader.Name) { case "Section": { Section section = new Section(); section.Start = Address.Parse(reader.GetAttribute("Start")); section.Size = uint.Parse(reader.GetAttribute("Size")); section.Name = reader.GetAttribute("Name"); sections.Enqueue(section); lastAddress = Math.Max(lastAddress, section.EndRoundedUpToPage); } break; case "Module": if (imageBase == 0) { imageBase = Address.Parse(reader.GetAttribute("Base")); sizeOfImageHeader = 1024; // We are using the file size number NodeIndex nodeIndex = GetNodeIndex(imageBase); NodeTypeIndex typeIndex = CreateType("Image Header"); children.Clear(); SetNode(nodeIndex, typeIndex, (int)sizeOfImageHeader, children); expectedAddr = imageBase + 0x1000; DebugWriteLine("Loading Module Map table used to decode $N symbol prefixes."); string dllFilePath = reader.GetAttribute("FilePath"); if (dllFilePath != null) { LoadModuleMap(dllFilePath, pdbScopeFile); } else { DebugWriteLine("Could not find path to original DLL being analyzed."); } if (m_moduleMap != null) { DebugWriteLine("Loaded Module Map of " + m_moduleMap.Count + " Project N style IL modules to unmangled $N_ prefixes."); } else { DebugWriteLine("Warning: No Module Map Found: $N_ prefixes will not be unmangled."); } } break; case "ObjectTypes": case "Type": case "Dicectory": case "Sections": case "PdbscopeReport": case "Symbols": case "SourceFiles": case "File": break; case "Symbol": string addrStr = reader.GetAttribute("addr"); Address addr; if (addrStr != null && Address.TryParse(addrStr, NumberStyles.AllowHexSpecifier, null, out addr)) { if (addr < lastAddress) { // Get Size string sizeStr = reader.GetAttribute("size"); uint size = 0; if (sizeStr != null) { uint.TryParse(sizeStr, out size); } // Get Children children.Clear(); string to = reader.GetAttribute("to"); if (to != null) { GetChildrenForAddresses(ref children, to); } // Get Name, make a type out of it string name; NodeTypeIndex typeIndex = GetTypeForXmlElement(knownTypes, reader, size, out name); // Currently PdbScope files have extra information lines where it shows the different generic instantiations associated // with a given symbol. These ways have the same address as the previous entry and have no size (size will be 0) so // we filter these lines out with the following condition. if (prevAddr != addr || size != 0) { prevAddr = addr; if (addr < expectedAddr) { DebugWriteLine(string.Format("Got Address {0:x} which is less than the expected address {1:x}. Discarding {2}", addr, expectedAddr, name)); badValues++; if (50 < badValues) { throw new ApplicationException("Too many cases where the addresses were not ascending in the file"); } continue; // discard } /*** We want to make sure we account for all bytes, so log when we see gaps ***/ // If we don't match see if it is because of section boundary. if (addr != expectedAddr) { EmitNodesForGaps(sections, expectedAddr, addr); } expectedAddr = addr + size; NodeIndex nodeIndex = GetNodeIndex((Address)addr); SetNode(nodeIndex, typeIndex, (int)size, children); // See if this is a good root if (foundBestRoot != 0 && name != null) { if (name == "RHBinder__ShimExeMain") { RootIndex = nodeIndex; foundBestRoot = 0; } else if (0 < foundBestRoot && name.Contains("ILT$Main")) { RootIndex = nodeIndex; foundBestRoot = 1; } else if (1 < foundBestRoot & name.Contains("DllMainCRTStartup")) { RootIndex = nodeIndex; foundBestRoot = 1; } else if (2 < foundBestRoot & name.Contains("Main")) { RootIndex = nodeIndex; foundBestRoot = 2; } } // Remember first node. if (firstNodeIndex == NodeIndex.Invalid) { firstNodeIndex = nodeIndex; } } } else { DebugWriteLine(string.Format("Warning Discarding Symbol node {0:x} outside the last address in the image {1:x}", addr, lastAddress)); } } else { DebugWriteLine("Error: symbol without addr"); } break; default: DebugWriteLine(string.Format("Skipping unknown element {0}", reader.Name)); break; } } } EmitNodesForGaps(sections, expectedAddr, lastAddress); if (RootIndex == NodeIndex.Invalid) { RootIndex = firstNodeIndex; } DebugWriteLine(string.Format("Image Base {0:x} LastAddress {1:x}", imageBase, lastAddress)); DebugWriteLine(string.Format("Total Virtual Size {0} ({0:x})", lastAddress - imageBase)); DebugWriteLine(string.Format("Total File Size {0} ({0:x})", TotalSize)); } AllowReading(); }
/// <summary> /// This demo shows you how to create a a graph of memory and turn it into a stackSource with MemoryGraphStackSource. /// </summary> public void DemoMemoryGraph() { // Make a custom stack source that was created out of nothing. InternStackSouce is your friend here. MemoryGraph myGraph = new MemoryGraph(1000); // Create a memory graph out of 'nothing'. In the example below we create a graph where the root which points at // 'base' which has a bunch of children, each of which have a child that points back to the base node (thus it has lots of cycles) var baseIdx = myGraph.GetNodeIndex(0); // Define the NAME (index) for the of the graph (but we have not defined what is in it) GrowableArray <NodeIndex> children = new GrowableArray <NodeIndex>(1); // This array is reused again and again for each child. GrowableArray <NodeIndex> rootChildren = new GrowableArray <NodeIndex>(100); // This is used to create the children of the root; //Here I make up a graph of memory addresses at made up locations for (Address objAddr = 0x100000; objAddr < 0x200000; objAddr += 0x10000) { NodeIndex nodeIdx = myGraph.GetNodeIndex(objAddr); // Create the name (node index) for the child // Make a type for the child. In this case we make a new type for each node, normally you keep these in a interning table so that // every distinct type name has exactly one type index. Interning is not STRICTLLY needed, but the representation assumes that // there are many more nodes than there are types. NodeTypeIndex nodeTypeIdx = myGraph.CreateType("MyType_" + objAddr.ToString(), "MyModule"); // Create a list of children for this node in this case, each node has exactly one child, which is the root node. children.Clear(); children.Add(baseIdx); // Actually define the node with the given name (nodeIdx), type, size (100 in our case) and children; myGraph.SetNode(nodeIdx, nodeTypeIdx, 100, children); rootChildren.Add(nodeIdx); // Remember this node name as belonging to the things that the root points at. } // At this point we have everything we need to define the base node, do it here. myGraph.SetNode(baseIdx, myGraph.CreateType("[Base]"), 0, rootChildren); // Create a root node that points at the base node. myGraph.RootIndex = myGraph.GetNodeIndex(1); children.Clear(); children.Add(baseIdx); myGraph.SetNode(myGraph.RootIndex, myGraph.CreateType("[ROOT]"), 0, children); // Note that the raw graph APIs force you to know all the children of a particular node before you can define // the node. There is a class call MemoryNodeBuilder which makes this a bit nicer in that you can incrementally // add children to nodes and then 'finalize' them all at once at the end. Ideally, however you don't have many // nodes that need MemoryNodeBuilder as they are more expensive to build. // So far, the graph is in 'write mode' where only creation APIs are allowed. Change to 'Read Mode' which no writes // are allowed by read APIs are allowed. (We may lift this restriction at some point). myGraph.AllowReading(); // I can dump it as XML to look at it. using (var writer = File.CreateText("MyGraph.dump.xml")) { myGraph.WriteXml(writer); } // I can write the graph out as a file and read it back in later. // myGraph.WriteAsBinaryFile("myGraph.gcGraph"); // var roundTrip = MemoryGraph.ReadFromBinaryFile("myGraph.gcGraph"); // OK we have a graph, turn it into a stack source so that I can view it. MemoryGraphStackSource myStackSource = new MemoryGraphStackSource(myGraph, LogFile); // Create a Stacks class (which remembers all the Filters, Scaling policy and other things the viewer wants. Stacks stacks = new Stacks(myStackSource); // If you wanted to, you can change the filtering ... stacks.Filter.GroupRegExs = ""; stacks.Filter.MinInclusiveTimePercent = ""; // And view it. OpenStackViewer(stacks); }
public GCHeapGraph(GCHeap heap) : base((int)heap.NumberOfObjects) { // TODO is basically nodes that have not had 'SetNode' done to them. Thus the node index has been // allocated, but we have not processed the defintion of the node. // This list should NEVER contain duplicates (you should test if you already processed the node before // adding to this queue. Queue <NodeIndex> toDo = new Queue <NodeIndex>(); // Create the root node. var root = new MemoryNodeBuilder(this, "[root]"); // Create categories for the roots. var nodeForKind = new MemoryNodeBuilder[((int)GCRootKind.Max) + 1]; var otherRoots = root.FindOrCreateChild("[other roots]"); nodeForKind[(int)GCRootKind.LocalVar] = otherRoots.FindOrCreateChild("[unknown local vars]"); for (int i = 0; i < nodeForKind.Length; i++) { if (nodeForKind[i] == null) { nodeForKind[i] = otherRoots.FindOrCreateChild("[" + ((GCRootKind)i).ToString().ToLower() + " handles]"); } } var unreachable = root.FindOrCreateChild("[unreachable but not yet collected]"); foreach (var gcRoot in heap.Roots) { if (gcRoot.HeapReference == 0) { continue; } // TODO FIX NOW condition should not be needed, should be able to assert it. if (!heap.IsInHeap(gcRoot.HeapReference)) { continue; } Debug.Assert(heap.IsInHeap(gcRoot.HeapReference)); // Make a node for it, and notice if the root is already in the heap. var lastLimit = NodeIndexLimit; var newNodeIdx = GetNodeIndex(gcRoot.HeapReference); var nodeForGcRoot = nodeForKind[(int)gcRoot.Kind]; if (gcRoot.Type != null) { var prefix = "other"; if (gcRoot.Kind == GCRootKind.StaticVar) { prefix = "static"; } else if (gcRoot.Kind == GCRootKind.LocalVar) { prefix = "local"; } var appDomainNode = root.FindOrCreateChild("[appdomain " + gcRoot.AppDomainName + "]"); var varKindNode = appDomainNode.FindOrCreateChild("[" + prefix + " vars]"); var moduleName = Path.GetFileNameWithoutExtension(gcRoot.Type.ModuleFilePath); var moduleNode = varKindNode.FindOrCreateChild("[" + prefix + " vars " + moduleName + "]"); var typeNode = moduleNode.FindOrCreateChild("[" + prefix + " vars " + gcRoot.Type.Name + "]"); var name = gcRoot.Type.Name + "+" + gcRoot.Name + " [" + prefix + " var]"; nodeForGcRoot = typeNode.FindOrCreateChild(name, gcRoot.Type.ModuleFilePath); // TODO REMOVE // if (nodeForGcRoot.Size == 0) // nodeForGcRoot.Size = 4; } // Give the user a bit more information about runtime internal handles if (gcRoot.Kind == GCRootKind.Pinning) { var pinnedType = heap.GetObjectType(gcRoot.HeapReference); if (pinnedType.Name == "System.Object []") { // Traverse pinned object arrays a bit 'manually' here so we tell the users they are likely handles. var handleArray = new MemoryNodeBuilder( this, "[likely runtime object array handle table]", pinnedType.ModuleFilePath, newNodeIdx); handleArray.Size = pinnedType.GetSize(gcRoot.HeapReference); nodeForGcRoot.AddChild(handleArray); pinnedType.EnumerateRefsOfObjectCarefully(gcRoot.HeapReference, delegate(Address childRef, int childFieldOffset) { // Poor man's visited bit. If we did not need to add a node, then we have visited it. var childLastLimit = NodeIndexLimit; var childNodeIdx = GetNodeIndex(childRef); if (childNodeIdx < childLastLimit) // Already visited, simply add the child and move one { handleArray.AddChild(childNodeIdx); return; } var childType = heap.GetObjectType(childRef); if (childType.Name == "System.String") { // TODO FIX NOW: Only want to morph the name if something else does not point at it. var literal = new MemoryNodeBuilder( this, "[likely string literal]", childType.ModuleFilePath, childNodeIdx); literal.Size = childType.GetSize(childRef); handleArray.AddChild(literal); } else { handleArray.AddChild(childNodeIdx); toDo.Enqueue(childNodeIdx); } }); continue; // we are done processing this node. } } nodeForGcRoot.AddChild(newNodeIdx); if (newNodeIdx >= lastLimit) // have not been visited. { toDo.Enqueue(newNodeIdx); } } root.AllocateTypeIndexes(); // Create the necessary types. int memoryTypesStart = (int)NodeTypeIndexLimit; foreach (var type in heap.Types) { NodeTypeIndex nodeType = CreateType(type.Name, type.ModuleFilePath); Debug.Assert((int)nodeType == (int)type.Index + memoryTypesStart); } var children = new GrowableArray <NodeIndex>(); while (toDo.Count > 0) { var nodeIndex = toDo.Dequeue(); var objRef = GetAddress(nodeIndex); children.Clear(); GCHeapType objType = heap.GetObjectType(objRef); objType.EnumerateRefsOfObjectCarefully(objRef, delegate(Address childRef, int childFieldOffset) { Debug.Assert(heap.IsInHeap(childRef)); var lastLimit = NodeIndexLimit; var childNodeIdx = GetNodeIndex(childRef); children.Add(childNodeIdx); // Poor man's visited bit. If the index we just asked for is a new node, put it in the work queue if (childNodeIdx >= lastLimit) { toDo.Enqueue(childNodeIdx); } }); int objSize = objType.GetSize(objRef); Debug.Assert(objSize > 0); SetNode(nodeIndex, (NodeTypeIndex)((int)objType.Index + memoryTypesStart), objSize, children); } long unreachableSize = (heap.TotalSize - TotalSize); unreachable.Size = (int)unreachableSize; if (unreachable.Size != unreachableSize) { unreachable.Size = int.MaxValue; // TODO not correct on overflow } // We are done! RootIndex = root.Build(); AllowReading(); }