Completing the anonymization of types

In the previous step I implemented the assignment of unique persisted numbers for all public types (C# classes and structs) in a program. This was then used for the dubious (and optional) purpose of "anonymizing" the type names in the output module.

In this step I complete that process by making the necessary modifications to the referencing of these now-anonymized type names. This involved rewriting some of the stuff that was shown in that article, so there'll be a certain amount of repetition in this article.

Note: The code shown below is somewhat obsolete. A newer version is available for download - see this article.


See the previous article for a complete (and now obsolete) listing of this source file. Here are some of the changes made in this iteration.

Around line 98:

// Dictionary of the YacksProjectInfo objects that have been read for this and other projects private readonly Dictionary<string, YacksProjectInfo> _projectInfoDictionary = new Dictionary<string, YacksProjectInfo>();

Around line 250:

/// <summary> /// Method to get a persisted type number for a type defined in some project other than the /// current project. The module number for the project is also returned. /// </summary> /// <param name="projectName">name of project as used when YacksProjectInfo was added to metadata, /// for example "Merlinia.CommonClasses.MArrays"</param> /// <param name="fullyQualifiedTypeName">name starts with "Merlinia" and is maybe "mangled"</param> /// <returns>module number or zero if something wrong, /// type number 1 - 9999, or -1 if something wrong</returns> internal (int ModuleNumber, int TypeNumber) GetTypeNumber(string projectName, string fullyQualifiedTypeName) { // Read the YacksProjectInfo object for the assembly if it exists in the // YacksProjectDirectory database (if it is open) YacksProjectInfo projectInfo = ReadProjectInfo(projectName); if (projectInfo != null) { // Get the YacksTypeInfo for the specified type, return type number if OK YacksTypeInfo typeInfo = projectInfo.GetTypeInfo(fullyQualifiedTypeName); if (typeInfo != null) return (projectInfo.ModuleNumber, typeInfo.TypeNumber); } // Error return return (0, -1); } /// <summary> /// Method to read a YacksProjectInfo object for an assembly, if it exists in the database. /// This makes use of a Dictionary{} to keep track of the YacksProjectInfo objects that have /// already been read. /// </summary> private YacksProjectInfo ReadProjectInfo(string projectName) { YacksProjectInfo projectInfo; if (!_projectInfoDictionary.TryGetValue(projectName, out projectInfo)) { projectInfo = _yacksProjects?.FindOne(Query.EQ(nameof(YacksProjectInfo.ProjectName), projectName)); if (projectInfo != null) _projectInfoDictionary.Add(projectName, projectInfo); } return projectInfo; }


See the previous article for a complete (and now obsolete) listing of this source file. One method has been added to the end of the class:

/// <summary> /// Method to find a specified YacksTypeInfo object if it's on TypeInfoList list (if it /// exists). ///TODO: Change from List{} to Dictionary{} to optimize this? /// </summary> /// <param name="fullyQualifiedTypeName">name starts with "Merlinia" and is maybe "mangled"</param> /// <returns>YacksTypeInfo object or null</returns> public YacksTypeInfo GetTypeInfo(string fullyQualifiedTypeName) { return TypeInfoList?.Find((YacksTypeInfo x) => x.TypeName == fullyQualifiedTypeName); }


This source file is part of the CodeAnalysis project in Roslyn. In the Visual Studio Solution Explorer it can be found under CodeAnalysis - PEWriter.

Compared with the previous steps another method has been modified. This is at the end of the PopulateTypeRefTableRows() method, and was at line 2847 for the (modified) revision of Roslyn that I was working with.

INamespaceTypeReference namespaceTypeRef = typeRef.AsNamespaceTypeReference; if (namespaceTypeRef == null) { throw ExceptionUtilities.UnexpectedValue(typeRef); } //Yacks08: May need to resolve anonymized type name if emitting anonymized module GetTypeNameReference(namespaceTypeRef, out resolutionScope, out name, out @namespace); /* removed for Yacks08 resolutionScope = this.GetResolutionScopeHandle(namespaceTypeRef.GetUnit(Context)); string mangledTypeName = GetMangledName(namespaceTypeRef); name = this.GetStringHandleForNameAndCheckLength(mangledTypeName, namespaceTypeRef); @namespace = this.GetStringHandleForNamespaceAndCheckLength(namespaceTypeRef, mangledTypeName); */ } metadata.AddTypeReference( resolutionScope: resolutionScope, @namespace: @namespace, name: name); } }


This is a source file which has been added to the CodeAnalysis project. Two methods previously shown have been modified and two additional methods have been added.

/// <summary> /// Method to either "anonymize" a type name (C# class or struct name) if applicable and /// possible, or else to do standard processing to emit the type definition. (Some of the code /// in this method is copied from original code in the /// MetadataWriter.PopulateTypeDefTableRows() method.) /// </summary> private void AnonymizeTypeName(INamedTypeDefinition typeDef, TypeAttributes typeAttributes, out StringHandle namespaceHandle, out StringHandle typeNameHandle) { // Test if applicable to "anonymize" the type name INamespaceTypeDefinition namespaceType = typeDef.AsNamespaceTypeDefinition(Context); string mangledTypeName = GetMangledName(typeDef); if (namespaceType != null && EmittingAnonymized(namespaceType.NamespaceName)) { // Get the persisted Yacks metadata type number for this type if possible, or for non- // public types get a non-persisted type number TypeAttributes twoBits = typeAttributes & TypeAttributes.NestedPrivate; bool isPublic = twoBits != 0 && twoBits != TypeAttributes.NestedPrivate; YacksCompilation yacksCompilation = module.CommonCompilation._YacksCompilation; int typeNumber = isPublic ? yacksCompilation.GetTypeNumber( namespaceType.NamespaceName + "." + mangledTypeName) : yacksCompilation.GetNonPersistedTypeNumber(); if (typeNumber != -1) { // The namespace name and type name get "anonymized". All namespace names are reduced // to just "Merlinia". See comments on GetHandleForAnonymizedTypeName() re the type // names. namespaceHandle = metadata.GetOrAddString(YacksCompilation.CMerlinia); typeNameHandle = GetHandleForAnonymizedTypeName( module.CommonCompilation._YacksCompilation.ModuleNumber, typeNumber); return; } } // Type name does not get anonymized - do standard processing namespaceHandle = (namespaceType != null) ? GetStringHandleForNamespaceAndCheckLength(namespaceType, mangledTypeName) : default(StringHandle); typeNameHandle = GetStringHandleForNameAndCheckLength(mangledTypeName, typeDef); } /// <summary> /// Method to process emitting references to type names. Special processing is needed if an /// anonymized module is being emitted, and it is referencing types in another anonymized /// module. (Some of the code in this method is copied from original code in the /// MetadataWriter.PopulateTypeRefTableRows() method.) /// </summary> private void GetTypeNameReference(INamespaceTypeReference namespaceTypeRef, out EntityHandle resolutionScopeHandle, out StringHandle typeNameHandle, out StringHandle namespaceHandle) { // Some common processing ... IUnitReference unitReference = namespaceTypeRef.GetUnit(Context); string mangledTypeName = GetMangledName(namespaceTypeRef); string namespaceName = namespaceTypeRef.NamespaceName; resolutionScopeHandle = this.GetResolutionScopeHandle(unitReference); // Test if applicable to "anonymize" the type name reference if (EmittingAnonymized(namespaceName)) { // Get the persisted type number for this type from Yacks metadata database if possible (int moduleNumber, int typeNumber) = module.CommonCompilation._YacksCompilation.GetTypeNumber(unitReference.Name, namespaceName + "." + mangledTypeName); if (typeNumber != -1) { // The namespace name and type name have been "anonymized", and must be referenced as // such. The namespace name was reduced to just "Merlinia". See comments on // GetHandleForAnonymizedTypeName() re the type name. typeNameHandle = GetHandleForAnonymizedTypeName(moduleNumber, typeNumber); namespaceHandle = metadata.GetOrAddString(YacksCompilation.CMerlinia); return; } } // The type name has not been anonymized - do standard processing typeNameHandle = this.GetStringHandleForNameAndCheckLength(mangledTypeName, namespaceTypeRef); namespaceHandle = this.GetStringHandleForNamespaceAndCheckLength(namespaceTypeRef, mangledTypeName); } /// <summary> /// Method to format an anonymized type name and then to add it to the PE module #Strings /// stream and return the "handle" for the string. An anonymized type name is "Tnnnn$mmmm", /// where nnnn is the project number and mmmm is the type number within the project. /// </summary> private StringHandle GetHandleForAnonymizedTypeName(int moduleNumber, int typeNumber) { return metadata.GetOrAddString("T" + moduleNumber.ToString("D" + YacksCompilation.CModuleNumberLength, CultureInfo.InvariantCulture) + "$" + typeNumber.ToString("D" + YacksCompilation.CTypeNumberLength, CultureInfo.InvariantCulture)); } /// <summary> /// Method to test if Yacks modifications are in effect and if an "anonymized" module is /// currently being built and if the namespace name starts with "Merlinia" or "Test" (but not /// "Merlinia.Yacks"). /// </summary> private bool EmittingAnonymized(string namespaceName) { if (!EmittingAnonymized()) return false; if (!(namespaceName.StartsWith(YacksCompilation.CMerlinia, StringComparison.Ordinal) || namespaceName.StartsWith("Test", StringComparison.Ordinal))) return false; if (namespaceName.StartsWith(YacksCompilation.CMerliniaYacks, StringComparison.Ordinal)) return false; return true; }


As a very simple test I compiled a program TestDynamicBitArrays, which tests parts of the Merlinia.CommonClasses.MArrays library assembly. Both the non-anonymized and the anonymized versions of the two programs worked.

These screen shots (with highlighting added) show the TypeRef metadata for the non-anonymized and the anonymized versions of the TestDynamicBitArrays program as displayed by JetBrains dotPeek.

ModRos 8 Snap1

ModRos 8 Snap2

You must login to post a comment.
Loading comment... The comment will be refreshed after 00:00.

Be the first to comment.