using System; using System.Collections.Generic; using System.IO; using System.IO.Ports; using System.Linq; using System.Reflection; namespace CruncherSharp { public class SymbolInfo { public string Name { get; private set; } public string TypeName { get; set; } public ulong Size { get; set; } public ulong? NewSize { get; set; } public uint EndPadding { get; set; } public uint Padding => (uint)(EndPadding + Members.Sum(info => info.PaddingBefore)); // This is the local (intrinsic) padding public uint? MinAlignment { get; set; } public uint PaddingZonesCount => (uint)((EndPadding > 0 ? 1 : 0) + Members.Sum(info => info.PaddingBefore > 0 ? 1 : 0)); public uint? TotalPadding { get; set; } // Includes padding from base classes and members public ulong? PotentialSaving { get; set; } public ulong NumInstances { get; set; } public ulong TotalCount { get; set; } public ulong LowerMemPool { get; set; } public ulong CurrentMemPool { get; set; } public ulong? NewMemPool { get; set; } public bool IsAbstract { get; set; } public bool IsTemplate { get; set; } public bool IsImportedFromCSV { get; set; } public List Members { get; set; } public List Functions { get; set; } public List DerivedClasses { get; set; } public SymbolInfo(string name, string typeName, ulong size, List MemPools) { Name = name; TypeName = typeName; Size = size; NewSize = null; EndPadding = 0; LowerMemPool = 0; CurrentMemPool = 0; NewMemPool = null; MinAlignment = null; TotalPadding = null; PotentialSaving = null; Members = new List(); Functions = null; IsAbstract = false; IsImportedFromCSV = false; if (Name.Contains("<") && Name.Contains(">")) IsTemplate = true; SetMemPools(MemPools); } public void AddMember(SymbolMemberInfo member) { Members.Add(member); } public void AddFunction(SymbolFunctionInfo function) { if (Functions == null) { Functions = new List(); } Functions.Add(function); if (function.IsPure) { IsAbstract = true; } } public void SetMemPools(List MemPools) { uint previousMemPool = 0; foreach (var memPool in MemPools) { if (Size > memPool) { previousMemPool = memPool; continue; } CurrentMemPool = memPool; LowerMemPool = previousMemPool; break; } if (CurrentMemPool == 0) { CurrentMemPool = Size; } SetNewMemPools(MemPools); } public void SetNewMemPools(List MemPools) { if (!NewSize.HasValue) return; foreach (var memPool in MemPools) { if (NewSize > memPool) { continue; } NewMemPool = memPool; return; } NewMemPool = NewSize; } private bool ComputeOffsetCollision(int index) { return index > 0 && Members[index].Offset == Members[index - 1].Offset; } public bool HasVtable { get { foreach (var member in Members) { if (member.Category == SymbolMemberInfo.MemberCategory.VTable) { return true; } } return false; } } public bool HasBaseClass { get { foreach (var member in Members) { if (member.Category == SymbolMemberInfo.MemberCategory.Base) { return true; } } return false; } } public bool HasUnusedVTable() { return HasVtable && !HasBaseClassWithVTable() && DerivedClasses == null; } public bool HasBaseClassWithVTable() { foreach (var member in Members) { if (member.Category == SymbolMemberInfo.MemberCategory.Base) { if (member.TypeInfo == null) { continue; } if (member.TypeInfo.HasVtable) return true; } } return false; } // https://randomascii.wordpress.com/2013/12/01/vc-2013-class-layout-change-and-wasted-space/ public bool HasMSVCExtraPadding() { if (HasBaseClassWithVTable()) return false; if (!HasVtable) return false; if (Members.Count < 2) return false; return Members[1].Size < 16 && Members[1].Offset == 16; } public bool HasMSVCEmptyBaseClass { get { var n = 1; while (n + 2 < Members.Count) { if (Members[n - 1].IsBase && Members[n].IsBase && Members[n].Size == 1 && Members[n].Offset > Members[n - 1].Offset && Members[n + 1].Offset > Members[n].Offset) return true; n++; } return false; } } public void SortAndCalculate() { // Sort members by offset, recompute padding. // Sorting is usually not needed (for data fields), but sometimes base class order is wrong. Members.Sort(SymbolMemberInfo.CompareOffsets); for (int i = 0; i < Members.Count; ++i) { var member = Members[i]; member.AlignWithPrevious = ComputeOffsetCollision(i); member.PaddingBefore = ComputePadding(i); member.BitPaddingAfter = ComputeBitPadding(i); } EndPadding = ComputeEndPadding(); } private uint ComputePadding(int index) { if (index < 1 || index > Members.Count) { return 0; } if (index < Members.Count && Members[index].AlignWithPrevious) { return 0; } int previousIndex = index - 1; ulong biggestSize = Members[previousIndex].Size; while (Members[previousIndex].AlignWithPrevious && previousIndex > 0) { previousIndex--; if (biggestSize < Members[previousIndex].Size) biggestSize = Members[previousIndex].Size; } ulong currentOffset = index > Members.Count - 1 ? Size : Members[index].Offset; ulong previousEnd = Members[previousIndex].Offset + biggestSize; return currentOffset > previousEnd ? (uint)(currentOffset - previousEnd) : 0u; } private uint ComputeBitPadding(int index) { if (index > Members.Count) { return 0; } if (!Members[index].BitField) { return 0; } if (index + 1 < Members.Count) { if (Members[index + 1].BitField && Members[index + 1].Offset == Members[index].Offset) return 0; } return (uint)(8 * Members[index].Size) - (Members[index].BitPosition + Members[index].BitSize); } private uint ComputeEndPadding() { return ComputePadding(Members.Count); } public uint ComputeTotalPadding() { if (TotalPadding.HasValue) { return TotalPadding.Value; } TotalPadding = (uint)((uint)Padding + Members.Sum(info => { if (info.AlignWithPrevious) return 0; if (info.Category == SymbolMemberInfo.MemberCategory.Member) return 0; if (info.TypeInfo == this) return 0; // avoid infinite loops if (info.TypeInfo == null) { return 0; } return info.TypeInfo.ComputeTotalPadding(); })); return TotalPadding.Value; } public ulong ComputePotentialSaving(SymbolAnalyzer symbolAnalyzer) { if (PotentialSaving.HasValue) { return PotentialSaving.Value; } if (Name == "UObjectBase") { PotentialSaving = 0; } //List BitsRemaining = new List(); uint BitsRemaining = 0; foreach (var member in Members) { if (member.Category != SymbolMemberInfo.MemberCategory.Member) { continue; } if (member.Size == 1 &&member.TypeName.StartsWith("bool")) { if (member.BitField) { BitsRemaining += member.BitPaddingAfter; } else if (BitsRemaining == 0) { BitsRemaining = 7u; } else { BitsRemaining--; member.PotentialSaving = 1u; } } else if (member.Size == 4 && member.TypeName.StartsWith("enum ")) { if (!symbolAnalyzer.ConfigEnum.ContainsKey(member.TypeName.Remove(0, 5))) { member.PotentialSaving = 1u; } } } PotentialSaving = 0; if (EndPadding < 16) { uint InternalPadding = (uint)Members.Sum(info => (long)info.PaddingBefore); InternalPadding += BitsRemaining / 8u; InternalPadding += (uint)(Members.Sum(info => { if (info.AlignWithPrevious) return 0u; if (info.PotentialSaving.HasValue) return info.PotentialSaving; return 0u; })); uint MinAlignment = ComputeMinAlignment(); if (InternalPadding > 0 && (InternalPadding + EndPadding) >= MinAlignment) { PotentialSaving = InternalPadding + EndPadding; PotentialSaving -= (PotentialSaving % MinAlignment); } } if (HasUnusedVTable()) PotentialSaving += 8; PotentialSaving += (ulong)(Members.Sum(info => { if (info.AlignWithPrevious) return 0u; if (info.Category == SymbolMemberInfo.MemberCategory.Member) return 0u; if (info.PotentialSaving.HasValue) return info.PotentialSaving; if (info.TypeName == Name) return 0u; // avoid infinite loops if (info.TypeInfo == null) { return 0u; } return (long)info.Count * (long)info.TypeInfo.ComputePotentialSaving(symbolAnalyzer); })); return PotentialSaving.Value; } public uint ComputeMinAlignment() { if (MinAlignment.HasValue) { return MinAlignment.Value; } if (EndPadding >= 8) { MinAlignment = 16; return MinAlignment.Value; } else if (EndPadding >= 4 || HasVtable) { MinAlignment = 8; } else if (EndPadding >= 2) { MinAlignment = 4; } else if (EndPadding > 0) { MinAlignment = 2; } else { MinAlignment = 1; } foreach (var member in Members) { if (member.MinAlignment > MinAlignment) { MinAlignment = member.MinAlignment; } } return MinAlignment.Value; } public ulong ComputeTotalMempoolUsage() { ulong TotalUsage = CurrentMemPool * NumInstances; if (DerivedClasses != null) { foreach (var derivedClass in DerivedClasses) { TotalUsage += derivedClass.ComputeTotalMempoolUsage(); } } return TotalUsage; } public ulong ComputePotentialTotalSaving() { if (PotentialSaving.HasValue) return ComputePotentialTotalSaving(PotentialSaving.Value); return 0; } private ulong ComputePotentialTotalSaving(ulong Win) { ulong TotalWin = 0; if (LowerMemPool > 0 && PotentialSaving.HasValue && (Size - Win) <= LowerMemPool) TotalWin = (CurrentMemPool - LowerMemPool) * NumInstances; if (DerivedClasses != null) { foreach (var derivedClass in DerivedClasses) { TotalWin += derivedClass.ComputePotentialTotalSaving(Win); } } return TotalWin; } public ulong ComputeTotalMempoolWin() { return ComputeTotalMempoolWin(Size - LowerMemPool); } private ulong ComputeTotalMempoolWin(ulong Win) { ulong TotalWin = 0; if (LowerMemPool > 0 && (Size - Win) <= LowerMemPool) TotalWin = (CurrentMemPool - LowerMemPool) * NumInstances; if (DerivedClasses != null) { foreach (var derivedClass in DerivedClasses) { TotalWin += derivedClass.ComputeTotalMempoolWin(Win); } } return TotalWin; } public long ComputeTotalDelta() { long TotalUsage = ((long)NewSize - (long)Size) * (long)NumInstances; if (DerivedClasses != null) { foreach (var derivedClass in DerivedClasses) { TotalUsage += derivedClass.ComputeTotalDelta(); } } return TotalUsage; } public long ComputeTotalMempoolDelta() { if (!NewMemPool.HasValue) return 0; long TotalUsage = ((long)NewMemPool - (long)CurrentMemPool) * (long)NumInstances; if (DerivedClasses != null) { foreach (var derivedClass in DerivedClasses) { TotalUsage += derivedClass.ComputeTotalMempoolDelta(); } } return TotalUsage; } private bool _BaseClassUpdated = false; public void UpdateBaseClass(SymbolAnalyzer symbolAnalyzer) { if (_BaseClassUpdated) return; _BaseClassUpdated = true; bool _ChildClassUpdated = false; foreach (var member in Members) { if (member.Category == SymbolMemberInfo.MemberCategory.Base) { member.TypeInfo = symbolAnalyzer.FindSymbolInfo(member.TypeName); if (member.TypeInfo != null) { if (member.TypeInfo.DerivedClasses == null) member.TypeInfo.DerivedClasses = new List(); member.TypeInfo.DerivedClasses.Add(this); } } else { if (member.UpdateTypeInfo(symbolAnalyzer)) { _ChildClassUpdated = true; } } } if (_ChildClassUpdated) { SortAndCalculate(); } } public bool IsA(string name, SymbolAnalyzer symbolAnalyzer) { var SymbolInfo = symbolAnalyzer.FindSymbolInfo(name); if (SymbolInfo == null) { return false; } foreach (var member in Members) { if (member.Category == SymbolMemberInfo.MemberCategory.Base) { if (member.TypeInfo != null) { if (member.TypeInfo == SymbolInfo) { return true; } if (member.TypeInfo.IsA(name, symbolAnalyzer)) { return true; } } } } return false; } public void CheckOverride() { foreach (var function in Functions) { if (function.Virtual && function.IsOverloaded == false && function.Category == SymbolFunctionInfo.FunctionCategory.Function && DerivedClasses != null) { foreach(var derivedClass in DerivedClasses) { if (derivedClass.IsOverloadingFunction(function)) { function.IsOverloaded = true; break; } } } } } public void CheckMasking() { foreach (var function in Functions) { if (function.Virtual == false && function.Category == SymbolFunctionInfo.FunctionCategory.Function && DerivedClasses != null) { foreach (var derivedClass in DerivedClasses) { derivedClass.CheckMasking(function); } } } } private void CheckMasking(SymbolFunctionInfo func) { foreach (var function in Functions) { if (function.Virtual == false && function.Name == func.Name) { function.IsMasking = true; if (DerivedClasses != null) { foreach (var derivedClass in DerivedClasses) { derivedClass.CheckMasking(func); } } } } } private bool IsOverloadingFunction(SymbolFunctionInfo func) { foreach (var function in Functions) { if (function.Name == func.Name) return true; } if (DerivedClasses != null) { foreach (var derivedClass in DerivedClasses) { if (derivedClass.IsOverloadingFunction(func)) return true; } } return false; } public void UpdateTotalCount(ulong count) { foreach (var member in Members) { if (member.Category == SymbolMemberInfo.MemberCategory.UDT || member.Category == SymbolMemberInfo.MemberCategory.Base) { if (member.TypeInfo != null) { count *= member.Count; member.TypeInfo.TotalCount += count; member.TypeInfo.UpdateTotalCount(count); } } } } public override string ToString() { var sw = new StringWriter(); sw.WriteLine($"Symbol: {Name}"); sw.WriteLine($"TypeName: {TypeName}"); sw.WriteLine($"Size: {Size}"); sw.WriteLine($"Padding: {Padding}"); sw.WriteLine($"Total padding: {TotalPadding}"); sw.WriteLine("Members:"); sw.WriteLine("-------"); const string PaddingMarker = "****Padding"; foreach (var member in Members) { if (member.PaddingBefore > 0) { var paddingOffset = member.Offset - member.PaddingBefore; sw.WriteLine($"{PaddingMarker,-40} {paddingOffset,5} {member.PaddingBefore,5}"); } sw.WriteLine($"{member.DisplayName,-40} {member.Offset,5} {member.Size,5}"); } if (EndPadding > 0) { var endPaddingOffset = Size - EndPadding; sw.WriteLine($"{PaddingMarker,-40} {endPaddingOffset,5} {EndPadding,5}"); } return sw.ToString(); } } }