Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extract classpath from symbol names for per-class/per-namespace statistics #26

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
181 changes: 164 additions & 17 deletions SymbolSort.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
using System.Runtime.InteropServices;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using Dia2Lib;

// Most of the interop with msdia90.dll can be generated automatically
Expand Down Expand Up @@ -181,8 +182,10 @@ class Symbol
public int rva_end;
public string name;
public string short_name;
public string raw_name; //decorated symbol name
public string source_filename;
public string section;
public string[] classpath;
public SymbolFlags flags = 0;
};

Expand Down Expand Up @@ -321,6 +324,125 @@ private static string ExtractGroupedSubstrings(string name, char groupBegin, cha
return ungrouped_name;
}

private class MainClassPathGetter
{
private static string[] splitByColons(string x)
{
return x.Split(new string[] { "::" }, StringSplitOptions.None);
}

private static string allowedSpecials = @"<=>,\[\]()!~^&|+\-*\/%" + "$";
private static string reClassWord = @"[\w " + allowedSpecials + "]+";
private static string reClassPath = String.Format(@"({0}::)*{0}", reClassWord);
private static Regex regexClassPath = new Regex("^" + reClassPath + "$", RegexOptions.Compiled);
private static string reLocalEnd = @".*"; //@"(`.+'|[\w]+(\$0)?)";
private static Regex regexFuncLocalVar = new Regex(String.Format(@"^`({0})'::`[\d]+'::{1}$", reClassPath, reLocalEnd));

//extracts the classpath (i.e. namespaces::classes::method) from undecorated symbol name
//input symbol must be stripped of return value and parameters (output from undname.exe with some flags)
//function may return null if symbol format is unknown
public static string[] Run(string short_name)
{
//(all string constaints)
if (short_name == "`string'")
return new string[] { short_name };

// Array<SharedPtr<Curve>, Allocator<SharedPtr<Curve>>>::Buffer::capacity"
// std::_Error_objects<int>::_System_object$initializer$
if (regexClassPath.IsMatch(short_name))
return splitByColons(short_name);

// std::bad_alloc `RTTI Type Descriptor'
const string rttiDescr = " `RTTI Type Descriptor'";
if (short_name.EndsWith(rttiDescr))
{
string[] res = Run(short_name.Substring(0, short_name.Length - rttiDescr.Length));
if (res == null) return null;
return res.Concat(new string[] { rttiDescr.Substring(1) }).ToArray();
}

// `CustomHeap::~CustomHeap'::`1'::dtor$0
// `std::basic_string<char,std::char_traits<char>,std::allocator<char> >::_Copy'::`1'::catch$0
// `CustomHeap<ShapeImpl>::instance'::`2'::some_var
// `HeapWrap < ShapeImpl >::Stub::get'::`7'::`local static guard'
// `HeapWrap<ShapeImpl>::Stub::get'::`7'::`dynamic atexit destructor for 'g_myHeap''
// `Mesh::projectPoints'::`13'::$S1
// `GroupElement::getNumElements'::`2'::MyCounter::`vftable'
if (regexFuncLocalVar.IsMatch(short_name))
return Run(regexFuncLocalVar.Match(short_name).Groups[1].Value);

// `dynamic initializer for 'BoundingBox::Invalid''
// `dynamic initializer for 'std::_Error_objects<int>::_System_object''
// std::`dynamic initializer for '_Tuple_alloc''
// UniquePtr<Service>::`scalar deleting destructor'
if (short_name.EndsWith("'"))
{
int backtickPos = short_name.IndexOf('`');
if (backtickPos >= 0)
{
string prefix = short_name.Substring(0, backtickPos);
string quoted = short_name.Substring(backtickPos + 1, short_name.Length - backtickPos - 2);
if (quoted.Count(c => c == '\'') == 2)
{
int left = quoted.IndexOf('\'');
int right = quoted.LastIndexOf('\'');
quoted = quoted.Substring(left + 1, right - left - 1);
}
string[] quotedWords = Run(quoted);
if (quotedWords == null)
return null;
string[] prefixWords = splitByColons(prefix);
return prefixWords.Take(prefixWords.Length - 1).Concat(quotedWords).ToArray();
}
}

//Console.WriteLine(short_name);
return null;
}
}

private static string[] RunUndName(string[] symbols, uint flags)
{
//write all symbols to temporary file
string inFName = Path.GetTempFileName();
var inWriter = new StreamWriter(inFName);
foreach (string s in symbols)
inWriter.WriteLine(s);
inWriter.Close();

//run undname.exe on the file
var arguments = String.Format("0x{0:X} {1}", flags, inFName);
var process = new Process
{
StartInfo = new ProcessStartInfo("undname", arguments)
{
UseShellExecute = false,
RedirectStandardOutput = true,
CreateNoWindow = true,
}
};

try
{
process.Start();
string output = process.StandardOutput.ReadToEnd();
System.Threading.Thread.Sleep(50); //just to be sure
Debug.Assert(process.HasExited);

//postprocess output
string[] lines = output.Split(new string[] { "\r\n" }, StringSplitOptions.None);
if (lines.Length > symbols.Length && lines.Skip(symbols.Length).All(x => x == ""))
lines = lines.Take(symbols.Length).ToArray();
Debug.Assert(lines.Length == symbols.Length);

return lines;
}
catch
{
return new string[symbols.Length];
}
}

private static string[] SplitIntoCmdArgs(string text)
{
//replace spaces inside quotes
Expand Down Expand Up @@ -560,7 +682,7 @@ private static void ReadSymbolsFromNM(List<Symbol> symbols, string inFilename, I

private static Regex ReadSymbolsFromCOMDAT_regexName = new Regex(@"\n[ \t]*([^ \t]+)[ \t]+name", RegexOptions.Compiled);
private static Regex ReadSymbolsFromCOMDAT_regexSize = new Regex(@"\n[ \t]*([A-Za-z0-9]+)[ \t]+size of raw data", RegexOptions.Compiled);
private static Regex ReadSymbolsFromCOMDAT_regexCOMDAT = new Regex(@"\n[ \t]*COMDAT; sym= \""([^\n\""]+)", RegexOptions.Compiled);
private static Regex ReadSymbolsFromCOMDAT_regexCOMDAT = new Regex(@"\n[ \t]*COMDAT; sym= \""([^\n\""]+)\"" \(([^\n()]+)\)", RegexOptions.Compiled);
private static void ReadSymbolsFromCOMDAT(List<Symbol> symbols, string inFilename)
{
Regex regexName = ReadSymbolsFromCOMDAT_regexName;
Expand Down Expand Up @@ -612,6 +734,7 @@ record += ln;

m = regexCOMDAT.Match(record);
symbol.name = m.Groups[1].Value;
symbol.raw_name = m.Groups[2].Value;
if (symbol.name != "")
{
symbol.rva_start = 0;
Expand Down Expand Up @@ -1334,38 +1457,33 @@ private static void WriteSourceStatsList(TextWriter writer, IEnumerable<KeyValue
writer.WriteLine();
}

private static void DumpFolderStats(TextWriter writer, List<Symbol> symbolList, int maxCount, bool showDifferences, List<RegexReplace> pathReplacements)
private static void DumpFolderStats(TextWriter writer, List<Symbol> symbolList, int maxCount, bool showDifferences, Func<Symbol, string[]> pathFunc, string separator)
{
Dictionary<string, SymbolSourceStats> sourceStats = new Dictionary<string, SymbolSourceStats>();
int childCount = 0;
foreach (Symbol s in symbolList)
{
string filename = s.source_filename;
filename = PerformRegexReplacements(filename, pathReplacements);
for ( ; ; )
string[] parts = pathFunc(s);
for (int k = parts.Length; k > 0; k--)
{
SymbolSourceStats stat;
if (sourceStats.ContainsKey(filename))
string currentname = String.Join(separator, parts, 0, k);
if (sourceStats.ContainsKey(currentname))
{
stat = sourceStats[filename];
stat = sourceStats[currentname];
}
else
{
stat = new SymbolSourceStats();
stat.count = 0;
stat.size = 0;
stat.singleChild = false;
sourceStats.Add(filename, stat);
sourceStats.Add(currentname, stat);
}
stat.count += s.count;
stat.size += s.size;
stat.singleChild = (stat.count == childCount);
childCount = stat.count;

int searchPos = filename.LastIndexOf('\\');
if (searchPos < 0)
break;
filename = filename.Remove(searchPos);
}
}

Expand All @@ -1376,9 +1494,6 @@ private static void DumpFolderStats(TextWriter writer, List<Symbol> symbolList,
return s1.Value.size - s0.Value.size;
} );

writer.WriteLine("File Contributions");
writer.WriteLine("--------------------------------------");

if (showDifferences)
{
writer.WriteLine("Increases in Size");
Expand Down Expand Up @@ -1919,7 +2034,39 @@ static void Main(string[] args)
}

Console.WriteLine("Building folder stats...");
DumpFolderStats(writer, symbols, opts.maxCount, opts.differenceFiles.Any(), opts.pathReplacements);
writer.WriteLine("File Contributions");
writer.WriteLine("--------------------------------------");
DumpFolderStats(writer, symbols, opts.maxCount, opts.differenceFiles.Any(),
delegate(Symbol s)
{
string path = PerformRegexReplacements(s.source_filename, opts.pathReplacements);
return path.Split("/\\".ToCharArray());
}, "\\");

Console.WriteLine("Building class and namespace stats...");
writer.WriteLine("Namespaces and classes Contributions");
writer.WriteLine("--------------------------------------");
string[] easyUndecoratedNames = RunUndName(symbols.Select(s => s.raw_name).ToArray(), 0x29FFF);
for (int i = 0; i < symbols.Count; i++)
{
string n = easyUndecoratedNames[i];
if (n != null)
{
n = ExtractGroupedSubstrings(n, '<', '>', "T");
string[] parts = MainClassPathGetter.Run(n);
symbols[i].classpath = parts;
}
}
DumpFolderStats(writer, symbols, opts.maxCount, opts.differenceFiles.Any(),
delegate (Symbol s)
{
string[] parts = s.classpath;
if (parts == null || parts.Length == 0) {
return new string[] { "[unknown]" };
}
parts = new string[] { "." }.Concat(parts.Take(parts.Length - 1)).ToArray();
return parts;
}, "::");

Console.WriteLine("Computing section stats...");
writer.WriteLine("Merged Sections / Types");
Expand Down