|
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml;
using Microsoft.Build.Framework; // ITaskItem
using Microsoft.Build.Utilities; // Task
namespace ILLink.Tasks
{
public class CreateRuntimeRootILLinkDescriptorFile : Task
{
/// <summary>
/// The path to namespace.h.
/// </summary>
[Required]
public ITaskItem NamespaceFilePath { get; set; }
/// <summary>
/// The path to mscorlib.h.
/// </summary>
[Required]
public ITaskItem MscorlibFilePath { get; set; }
/// <summary>
/// The path to cortypeinfo.h.
/// </summary>
[Required]
public ITaskItem CortypeFilePath { get; set; }
/// <summary>
/// The path to rexcep.h.
/// </summary>
[Required]
public ITaskItem RexcepFilePath { get; set; }
/// <summary>
/// The path to ILLinkTrim.xml.
/// </summary>
[Required]
public ITaskItem ILLinkTrimXmlFilePath { get; set; }
public ITaskItem[] DefineConstants { get; set; }
/// <summary>
/// The path to the file to generate.
/// </summary>
[Required]
public ITaskItem RuntimeRootDescriptorFilePath { get; set; }
sealed class ClassMembers
{
public bool keepAllFields;
public HashSet<string> methods;
public HashSet<string> fields;
}
/// <summary>
/// Helper utility to track feature switch macros in header file
/// This type is used in a dictionary as a key
/// </summary>
readonly struct FeatureSwitchMembers : IEquatable<FeatureSwitchMembers>
{
public string Feature { get; }
public string FeatureValue { get; }
public string FeatureDefault { get; }
// Unique value to track the key
private readonly string _key;
public FeatureSwitchMembers (string feature, string featureValue, string featureDefault)
{
Feature = feature;
FeatureValue = featureValue;
FeatureDefault = featureDefault;
// Use a separator that is not going to be in any of the strings to ensure uniqueness
_key = feature + ',' + featureValue + ',' + featureDefault;
}
public override int GetHashCode ()
{
return _key.GetHashCode ();
}
public override bool Equals (object obj)
=> obj is FeatureSwitchMembers inst && Equals (inst);
public bool Equals (FeatureSwitchMembers fsm)
=> fsm._key == _key;
}
readonly Dictionary<string, string> namespaceDictionary = new Dictionary<string, string> ();
readonly Dictionary<string, string> classIdsToClassNames = new Dictionary<string, string> ();
readonly Dictionary<string, ClassMembers> classNamesToClassMembers = new Dictionary<string, ClassMembers> ();
readonly Dictionary<FeatureSwitchMembers, Dictionary<string, ClassMembers>> featureSwitchMembers = new ();
readonly HashSet<string> defineConstants = new HashSet<string> (StringComparer.Ordinal);
public override bool Execute ()
{
var namespaceFilePath = NamespaceFilePath.ItemSpec;
if (!File.Exists (namespaceFilePath)) {
Log.LogError ($"File '{namespaceFilePath}' doesn't exist.");
return false;
}
var mscorlibFilePath = MscorlibFilePath.ItemSpec;
if (!File.Exists (mscorlibFilePath)) {
Log.LogError ($"File '{mscorlibFilePath}' doesn't exist.");
return false;
}
var cortypeFilePath = CortypeFilePath.ItemSpec;
if (!File.Exists (cortypeFilePath)) {
Log.LogError ($"File '{cortypeFilePath}' doesn't exist.");
return false;
}
var rexcepFilePath = RexcepFilePath.ItemSpec;
if (!File.Exists (rexcepFilePath)) {
Log.LogError ($"File '{rexcepFilePath}' doesn't exist.");
return false;
}
InitializeDefineConstants ();
var iLLinkTrimXmlFilePath = ILLinkTrimXmlFilePath.ItemSpec;
if (!File.Exists (iLLinkTrimXmlFilePath)) {
Log.LogError ($"File '{iLLinkTrimXmlFilePath}' doesn't exist.");
return false;
}
ProcessNamespaces (namespaceFilePath);
ProcessMscorlib (mscorlibFilePath);
ProcessCoreTypes (cortypeFilePath);
ProcessExceptionTypes (rexcepFilePath);
OutputXml (iLLinkTrimXmlFilePath, RuntimeRootDescriptorFilePath.ItemSpec);
return true;
}
void ProcessNamespaces (string namespaceFile)
{
string[] namespaces = File.ReadAllLines (namespaceFile);
// Process definitions of the form
// #define g_SystemNS "System"
// from namespace.h
foreach (string namespaceDef in namespaces) {
if (namespaceDef.StartsWith ("#define")) {
char[] separators = { '"', ' ', '\t' };
string[] namespaceDefElements = namespaceDef.Split (separators, StringSplitOptions.RemoveEmptyEntries);
int startIndex = "g_".Length;
// E.g., if namespaceDefElements [1] is "g_RuntimeNS", lhs is "Runtime".
string lhs = namespaceDefElements[1].Substring (startIndex, namespaceDefElements[1].LastIndexOf ('N') - startIndex);
if (namespaceDefElements.Length == 3) {
// E.G., #define g_SystemNS "System"
// "System" --> "System"
namespaceDictionary[lhs] = namespaceDefElements[2];
} else {
// E.g., #define g_RuntimeNS g_SystemNS ".Runtime"
// "Runtime" --> "System.Runtime"
string prefix = namespaceDefElements[2].Substring (startIndex, namespaceDefElements[2].LastIndexOf ('N') - startIndex);
namespaceDictionary[lhs] = namespaceDictionary[prefix] + namespaceDefElements[3];
}
}
}
}
void ProcessMscorlib (string typeFile)
{
string[] types = File.ReadAllLines (typeFile);
DefineTracker defineTracker = new DefineTracker (defineConstants, Log, typeFile);
string classId;
FeatureSwitchMembers? currentFeatureSwitch = null;
foreach (string def in types) {
if (defineTracker.ProcessLine (def) || !defineTracker.IsActiveSection)
continue;
//We will handle BEGIN_ILLINK_FEATURE_SWITCH and END_ILLINK_FEATURE_SWITCH
if (def.StartsWith ("BEGIN_ILLINK_FEATURE_SWITCH")) {
if (currentFeatureSwitch.HasValue) {
Log.LogError ($"Could not figure out feature switch status in '{typeFile}' for line {def}.");
} else {
char[] separators = { ',', '(', ')', ' ', '\t', '/' };
string[] featureSwitchElements = def.Split (separators, StringSplitOptions.RemoveEmptyEntries);
if (featureSwitchElements.Length != 4) {
Log.LogError ($"BEGIN_ILLINK_FEATURE_SWITCH is not formatted correctly '{typeFile}' for line {def}.");
return;
}
currentFeatureSwitch = new FeatureSwitchMembers (featureSwitchElements[1], featureSwitchElements[2], featureSwitchElements[3]);
if (!featureSwitchMembers.ContainsKey (currentFeatureSwitch.Value)) {
featureSwitchMembers.Add (currentFeatureSwitch.Value, new Dictionary<string, ClassMembers> ());
}
}
}
if (def.StartsWith ("END_ILLINK_FEATURE_SWITCH")) {
if (!currentFeatureSwitch.HasValue) {
Log.LogError ($"Could not figure out feature switch status in '{typeFile}' for line {def}.");
} else {
currentFeatureSwitch = null;
}
}
string[] defElements = null;
if (def.StartsWith ("DEFINE_") || def.StartsWith ("// DEFINE_")) {
char[] separators = { ',', '(', ')', ' ', '\t', '/' };
defElements = def.Split (separators, StringSplitOptions.RemoveEmptyEntries);
}
if (def.StartsWith ("DEFINE_CLASS(") || def.StartsWith ("// DEFINE_CLASS(")) {
// E.g., DEFINE_CLASS(APP_DOMAIN, System, AppDomain)
classId = defElements[1]; // APP_DOMAIN
string classNamespace = defElements[2]; // System
string className = defElements[3]; // AppDomain
AddClass (classNamespace, className, classId, false, currentFeatureSwitch);
} else if (def.StartsWith ("DEFINE_CLASS_U(")) {
// E.g., DEFINE_CLASS_U(System, AppDomain, AppDomainBaseObject)
string classNamespace = defElements[1]; // System
string className = defElements[2]; // AppDomain
classId = defElements[3]; // AppDomainBaseObject
// For these classes the sizes of managed and unmanaged classes and field offsets
// are compared so we need to preserve all fields.
const bool keepAllFields = true;
AddClass (classNamespace, className, classId, keepAllFields, currentFeatureSwitch);
} else if (def.StartsWith ("DEFINE_FIELD(")) {
// E.g., DEFINE_FIELD(ACCESS_VIOLATION_EXCEPTION, IP, _ip)
classId = defElements[1]; // ACCESS_VIOLATION_EXCEPTION
string fieldName = defElements[3]; // _ip
AddField (fieldName, classId, currentFeatureSwitch);
} else if (def.StartsWith ("DEFINE_METHOD(")) {
// E.g., DEFINE_METHOD(APP_DOMAIN, ON_ASSEMBLY_LOAD, OnAssemblyLoadEvent, IM_Assembly_RetVoid)
string methodName = defElements[3]; // OnAssemblyLoadEvent
classId = defElements[1]; // APP_DOMAIN
AddMethod (methodName, classId, null, null, currentFeatureSwitch);
} else if (def.StartsWith ("DEFINE_PROPERTY(") || def.StartsWith ("DEFINE_STATIC_PROPERTY(")) {
// E.g., DEFINE_PROPERTY(ARRAY, LENGTH, Length, Int)
// or DEFINE_STATIC_PROPERTY(THREAD, CURRENT_THREAD, CurrentThread, Thread)
string propertyName = defElements[3]; // Length or CurrentThread
classId = defElements[1]; // ARRAY or THREAD
AddMethod ("get_" + propertyName, classId, null, null, currentFeatureSwitch);
} else if (def.StartsWith ("DEFINE_SET_PROPERTY(")) {
// E.g., DEFINE_SET_PROPERTY(THREAD, UI_CULTURE, CurrentUICulture, CultureInfo)
string propertyName = defElements[3]; // CurrentUICulture
classId = defElements[1]; // THREAD
AddMethod ("get_" + propertyName, classId, null, null, currentFeatureSwitch);
AddMethod ("set_" + propertyName, classId, null, null, currentFeatureSwitch);
}
}
}
public void ProcessCoreTypes (string corTypeFile)
{
string[] corTypes = File.ReadAllLines (corTypeFile);
DefineTracker defineTracker = new DefineTracker (defineConstants, Log, corTypeFile);
foreach (string def in corTypes) {
if (defineTracker.ProcessLine (def) || !defineTracker.IsActiveSection)
continue;
// E.g., TYPEINFO(ELEMENT_TYPE_VOID, "System", "Void", 0, TYPE_GC_NONE, false, true, false, false, false) // 0x01
if (def.StartsWith ("TYPEINFO(")) {
char[] separators = { ',', '(', ')', '"', ' ', '\t' };
string[] defElements = def.Split (separators, StringSplitOptions.RemoveEmptyEntries);
string classId = null;
string classNamespace = defElements[2]; // System
string className = defElements[3]; // Void
AddClass (classNamespace, className, classId);
}
}
}
public void ProcessExceptionTypes (string excTypeFile)
{
string[] excTypes = File.ReadAllLines (excTypeFile);
DefineTracker defineTracker = new DefineTracker (defineConstants, Log, excTypeFile);
foreach (string def in excTypes) {
if (defineTracker.ProcessLine (def) || !defineTracker.IsActiveSection)
continue;
// E.g., DEFINE_EXCEPTION(g_InteropNS, MarshalDirectiveException, false, COR_E_MARSHALDIRECTIVE)
if (def.StartsWith ("DEFINE_EXCEPTION(")) {
char[] separators = { ',', '(', ')', ' ', '\t' };
string[] defElements = def.Split (separators, StringSplitOptions.RemoveEmptyEntries);
string classId = null;
string classNamespace = defElements[1]; // g_InteropNS
string className = defElements[2]; // MarshalDirectiveException
AddClass (classNamespace, className, classId);
AddMethod (".ctor", classId, classNamespace, className);
}
}
}
void OutputXml (string iLLinkTrimXmlFilePath, string outputFileName)
{
XmlDocument doc = new XmlDocument ();
using (var sr = new StreamReader (iLLinkTrimXmlFilePath)) {
using XmlReader reader = XmlReader.Create (sr, new XmlReaderSettings () { XmlResolver = null });
doc.Load (reader);
}
XmlElement linkerNode = doc["linker"];
if (featureSwitchMembers.Count > 0) {
foreach ((var fs, var members) in featureSwitchMembers.Select (kv => (kv.Key, kv.Value))) {
if (members.Count == 0)
continue;
// <assembly fullname="System.Private.CoreLib" feature="System.Diagnostics.Tracing.EventSource.IsSupported" featurevalue="true" featuredefault="true">
XmlElement featureAssemblyNode = doc.CreateElement ("assembly");
XmlAttribute featureAssemblyFullName = doc.CreateAttribute ("fullname");
featureAssemblyFullName.Value = "System.Private.CoreLib";
featureAssemblyNode.Attributes.Append (featureAssemblyFullName);
XmlAttribute featureName = doc.CreateAttribute ("feature");
featureName.Value = fs.Feature;
featureAssemblyNode.Attributes.Append (featureName);
XmlAttribute featureValue = doc.CreateAttribute ("featurevalue");
featureValue.Value = fs.FeatureValue;
featureAssemblyNode.Attributes.Append (featureValue);
XmlAttribute featureDefault = doc.CreateAttribute ("featuredefault");
featureDefault.Value = fs.FeatureDefault;
featureAssemblyNode.Attributes.Append (featureDefault);
foreach (var type in members) {
AddXmlTypeNode (doc, featureAssemblyNode, type.Key, type.Value);
}
linkerNode.AppendChild (featureAssemblyNode);
}
}
XmlNode assemblyNode = linkerNode["assembly"];
foreach (var type in classNamesToClassMembers) {
AddXmlTypeNode (doc, assemblyNode, type.Key, type.Value);
}
doc.Save (outputFileName);
}
static void AddXmlTypeNode (XmlDocument doc, XmlNode assemblyNode, string typeName, ClassMembers members)
{
XmlElement typeNode = doc.CreateElement ("type");
XmlAttribute typeFullName = doc.CreateAttribute ("fullname");
typeFullName.Value = typeName;
typeNode.Attributes.Append (typeFullName);
// We need to keep everyting in System.Runtime.InteropServices.WindowsRuntime and
// System.Threading.Volatile.
if (!typeName.StartsWith ("System.Runtime.InteropServices.WindowsRuntime") &&
!typeName.StartsWith ("System.Threading.Volatile")) {
if (members.keepAllFields) {
XmlAttribute preserve = doc.CreateAttribute ("preserve");
preserve.Value = "fields";
typeNode.Attributes.Append (preserve);
} else if ((members.fields == null) && (members.methods == null)) {
XmlAttribute preserve = doc.CreateAttribute ("preserve");
preserve.Value = "nothing";
typeNode.Attributes.Append (preserve);
}
if (!members.keepAllFields && (members.fields != null)) {
foreach (string field in members.fields) {
XmlElement fieldNode = doc.CreateElement ("field");
XmlAttribute fieldName = doc.CreateAttribute ("name");
fieldName.Value = field;
fieldNode.Attributes.Append (fieldName);
typeNode.AppendChild (fieldNode);
}
}
if (members.methods != null) {
foreach (string method in members.methods) {
XmlElement methodNode = doc.CreateElement ("method");
XmlAttribute methodName = doc.CreateAttribute ("name");
methodName.Value = method;
methodNode.Attributes.Append (methodName);
typeNode.AppendChild (methodNode);
}
}
}
assemblyNode.AppendChild (typeNode);
}
void AddClass (string classNamespace, string className, string classId, bool keepAllFields = false, FeatureSwitchMembers? featureSwitch = null)
{
string fullClassName = GetFullClassName (classNamespace, className);
if (fullClassName != null) {
if ((classId != null) && (classId != "NoClass")) {
classIdsToClassNames[classId] = fullClassName;
}
ClassMembers members;
if (!featureSwitch.HasValue) {
if (!classNamesToClassMembers.TryGetValue (fullClassName, out members)) {
members = new ClassMembers ();
classNamesToClassMembers[fullClassName] = members;
}
} else {
Dictionary<string, ClassMembers> currentFeatureSwitchMembers = featureSwitchMembers[featureSwitch.Value];
if (!currentFeatureSwitchMembers.TryGetValue (fullClassName, out members)) {
members = new ClassMembers ();
currentFeatureSwitchMembers[fullClassName] = members;
}
}
members.keepAllFields |= keepAllFields;
}
}
void AddField (string fieldName, string classId, FeatureSwitchMembers? featureSwitch = null)
{
string className = classIdsToClassNames[classId];
ClassMembers members;
if (!featureSwitch.HasValue) {
members = classNamesToClassMembers[className];
} else {
members = featureSwitchMembers[featureSwitch.Value][className];
}
members.fields ??= new HashSet<string> ();
members.fields.Add (fieldName);
}
void AddMethod (string methodName, string classId, string classNamespace = null, string className = null, FeatureSwitchMembers? featureSwitch = null)
{
string fullClassName;
if (classId != null) {
fullClassName = classIdsToClassNames[classId];
} else {
fullClassName = GetFullClassName (classNamespace, className);
}
ClassMembers members;
if (!featureSwitch.HasValue) {
members = classNamesToClassMembers[fullClassName];
} else {
members = featureSwitchMembers[featureSwitch.Value][fullClassName];
}
members.methods ??= new HashSet<string> ();
members.methods.Add (methodName);
}
string GetFullClassName (string classNamespace, string className)
{
string prefixToRemove = "g_";
if (classNamespace.StartsWith (prefixToRemove)) {
classNamespace = classNamespace.Substring (prefixToRemove.Length);
}
string suffixToRemove = "NS";
if (classNamespace.EndsWith (suffixToRemove)) {
classNamespace = classNamespace.Substring (0, classNamespace.Length - suffixToRemove.Length);
}
if ((classNamespace == "NULL") && (className == "NULL")) {
return null;
}
if (!namespaceDictionary.ContainsKey (classNamespace)) {
Log.LogError ($"Unknown namespace '{classNamespace}'.");
}
// Convert from the System.Reflection/CoreCLR nested type name format to the IL/Cecil format.
string classNameWithCecilNestedFormat = className.Replace ('+', '/');
return namespaceDictionary[classNamespace] + "." + classNameWithCecilNestedFormat;
}
void InitializeDefineConstants ()
{
if (DefineConstants is null)
return;
foreach (var item in DefineConstants)
defineConstants.Add (item.ItemSpec.Trim ());
}
sealed class DefineTracker
{
readonly HashSet<string> defineConstants;
readonly TaskLoggingHelper log;
readonly string filePath;
readonly Stack<bool?> activeSections = new Stack<bool?> ();
int linenum;
public DefineTracker (HashSet<string> defineConstants, TaskLoggingHelper log, string filePath)
{
this.defineConstants = defineConstants;
this.log = log;
this.filePath = filePath;
}
public bool ProcessLine (string line)
{
linenum++;
int ifdefLength = 0;
bool negativeIfDef = false;
if (line.StartsWith ("#ifdef ")) {
ifdefLength = 7;
} else if (line.StartsWith ("#ifndef ")) {
ifdefLength = 8;
negativeIfDef = true;
} else if (line.StartsWith ("#if ")) {
ifdefLength = 4;
}
if (ifdefLength > 0) {
if (activeSections.Count > 0 && activeSections.Peek () != true) {
activeSections.Push (null);
} else {
string defineName = line.Substring (ifdefLength);
int commentIndex = defineName.IndexOf ('/');
if (commentIndex >= 0)
defineName = defineName.Substring (0, commentIndex);
defineName = defineName.Trim ();
if (defineConstants.Contains (defineName))
activeSections.Push (!negativeIfDef);
else
activeSections.Push (negativeIfDef);
}
return true;
}
if (line.StartsWith ("#else")) {
if (activeSections.Count == 0) {
log.LogError ($"Could not figure out ifdefs in '{filePath}' around line {linenum}.");
} else {
bool? activeSection = activeSections.Pop ();
if (activeSection == null)
activeSections.Push (null);
else
activeSections.Push (!activeSection.Value);
}
return true;
}
if (line.StartsWith ("#endif")) {
if (activeSections.Count == 0)
log.LogError ($"Could not figure out ifdefs in '{filePath}' around line {linenum}.");
else
activeSections.Pop ();
return true;
}
return false;
}
public bool IsActiveSection {
get => activeSections.Count == 0 || activeSections.Peek () == true;
}
}
}
}
|