├── .gitignore ├── .vscode ├── launch.json └── tasks.json ├── CODE_OF_CONDUCT.md ├── LICENSE ├── Microsoft.FrozenObjects.sln ├── README.md ├── SECURITY.md ├── nuget.config ├── samples └── Basic │ ├── FrozenObjectTest.csproj │ ├── FrozenObjectTest.sln │ ├── Program.cs │ └── nuget.config ├── src ├── Deserializer.cs ├── Microsoft.FrozenObjects.csproj ├── Microsoft.FrozenObjects.nuspec ├── NativeMethods.cs └── Serializer.cs ├── tests ├── ComplexTypes.cs ├── Microsoft.FrozenObjects.Tests.csproj ├── SupportCode │ ├── DictionarySlim.cs │ ├── HashHelpers.cs │ ├── Marvin.OrdinalIgnoreCase.cs │ ├── Marvin.cs │ ├── ReadOnlyDictionarySlim.cs │ └── Utf16Utility.cs └── UnitTests.cs └── tools ├── Library.cs └── Microsoft.FrozenObjects.BuildTools.csproj /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015/2017 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # Visual Studio 2017 auto generated files 33 | Generated\ Files/ 34 | 35 | # MSTest test Results 36 | [Tt]est[Rr]esult*/ 37 | [Bb]uild[Ll]og.* 38 | 39 | # NUNIT 40 | *.VisualState.xml 41 | TestResult.xml 42 | 43 | # Build Results of an ATL Project 44 | [Dd]ebugPS/ 45 | [Rr]eleasePS/ 46 | dlldata.c 47 | 48 | # Benchmark Results 49 | BenchmarkDotNet.Artifacts/ 50 | 51 | # .NET Core 52 | project.lock.json 53 | project.fragment.lock.json 54 | artifacts/ 55 | **/Properties/launchSettings.json 56 | 57 | # StyleCop 58 | StyleCopReport.xml 59 | 60 | # Files built by Visual Studio 61 | *_i.c 62 | *_p.c 63 | *_i.h 64 | *.ilk 65 | *.meta 66 | *.obj 67 | *.iobj 68 | *.pch 69 | *.pdb 70 | *.ipdb 71 | *.pgc 72 | *.pgd 73 | *.rsp 74 | *.sbr 75 | *.tlb 76 | *.tli 77 | *.tlh 78 | *.tmp 79 | *.tmp_proj 80 | *.log 81 | *.vspscc 82 | *.vssscc 83 | .builds 84 | *.pidb 85 | *.svclog 86 | *.scc 87 | 88 | # Chutzpah Test files 89 | _Chutzpah* 90 | 91 | # Visual C++ cache files 92 | ipch/ 93 | *.aps 94 | *.ncb 95 | *.opendb 96 | *.opensdf 97 | *.sdf 98 | *.cachefile 99 | *.VC.db 100 | *.VC.VC.opendb 101 | 102 | # Visual Studio profiler 103 | *.psess 104 | *.vsp 105 | *.vspx 106 | *.sap 107 | 108 | # Visual Studio Trace Files 109 | *.e2e 110 | 111 | # TFS 2012 Local Workspace 112 | $tf/ 113 | 114 | # Guidance Automation Toolkit 115 | *.gpState 116 | 117 | # ReSharper is a .NET coding add-in 118 | _ReSharper*/ 119 | *.[Rr]e[Ss]harper 120 | *.DotSettings.user 121 | 122 | # JustCode is a .NET coding add-in 123 | .JustCode 124 | 125 | # TeamCity is a build add-in 126 | _TeamCity* 127 | 128 | # DotCover is a Code Coverage Tool 129 | *.dotCover 130 | 131 | # AxoCover is a Code Coverage Tool 132 | .axoCover/* 133 | !.axoCover/settings.json 134 | 135 | # Visual Studio code coverage results 136 | *.coverage 137 | *.coveragexml 138 | 139 | # NCrunch 140 | _NCrunch_* 141 | .*crunch*.local.xml 142 | nCrunchTemp_* 143 | 144 | # MightyMoose 145 | *.mm.* 146 | AutoTest.Net/ 147 | 148 | # Web workbench (sass) 149 | .sass-cache/ 150 | 151 | # Installshield output folder 152 | [Ee]xpress/ 153 | 154 | # DocProject is a documentation generator add-in 155 | DocProject/buildhelp/ 156 | DocProject/Help/*.HxT 157 | DocProject/Help/*.HxC 158 | DocProject/Help/*.hhc 159 | DocProject/Help/*.hhk 160 | DocProject/Help/*.hhp 161 | DocProject/Help/Html2 162 | DocProject/Help/html 163 | 164 | # Click-Once directory 165 | publish/ 166 | 167 | # Publish Web Output 168 | *.[Pp]ublish.xml 169 | *.azurePubxml 170 | # Note: Comment the next line if you want to checkin your web deploy settings, 171 | # but database connection strings (with potential passwords) will be unencrypted 172 | *.pubxml 173 | *.publishproj 174 | 175 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 176 | # checkin your Azure Web App publish settings, but sensitive information contained 177 | # in these scripts will be unencrypted 178 | PublishScripts/ 179 | 180 | # NuGet Packages 181 | *.nupkg 182 | # The packages folder can be ignored because of Package Restore 183 | **/[Pp]ackages/* 184 | # except build/, which is used as an MSBuild target. 185 | !**/[Pp]ackages/build/ 186 | # Uncomment if necessary however generally it will be regenerated when needed 187 | #!**/[Pp]ackages/repositories.config 188 | # NuGet v3's project.json files produces more ignorable files 189 | *.nuget.props 190 | *.nuget.targets 191 | 192 | # Microsoft Azure Build Output 193 | csx/ 194 | *.build.csdef 195 | 196 | # Microsoft Azure Emulator 197 | ecf/ 198 | rcf/ 199 | 200 | # Windows Store app package directories and files 201 | AppPackages/ 202 | BundleArtifacts/ 203 | Package.StoreAssociation.xml 204 | _pkginfo.txt 205 | *.appx 206 | 207 | # Visual Studio cache files 208 | # files ending in .cache can be ignored 209 | *.[Cc]ache 210 | # but keep track of directories ending in .cache 211 | !*.[Cc]ache/ 212 | 213 | # Others 214 | ClientBin/ 215 | ~$* 216 | *~ 217 | *.dbmdl 218 | *.dbproj.schemaview 219 | *.jfm 220 | *.pfx 221 | *.publishsettings 222 | orleans.codegen.cs 223 | 224 | # Including strong name files can present a security risk 225 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 226 | #*.snk 227 | 228 | # Since there are multiple workflows, uncomment next line to ignore bower_components 229 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 230 | #bower_components/ 231 | 232 | # RIA/Silverlight projects 233 | Generated_Code/ 234 | 235 | # Backup & report files from converting an old project file 236 | # to a newer Visual Studio version. Backup files are not needed, 237 | # because we have git ;-) 238 | _UpgradeReport_Files/ 239 | Backup*/ 240 | UpgradeLog*.XML 241 | UpgradeLog*.htm 242 | ServiceFabricBackup/ 243 | *.rptproj.bak 244 | 245 | # SQL Server files 246 | *.mdf 247 | *.ldf 248 | *.ndf 249 | 250 | # Business Intelligence projects 251 | *.rdl.data 252 | *.bim.layout 253 | *.bim_*.settings 254 | *.rptproj.rsuser 255 | 256 | # Microsoft Fakes 257 | FakesAssemblies/ 258 | 259 | # GhostDoc plugin setting file 260 | *.GhostDoc.xml 261 | 262 | # Node.js Tools for Visual Studio 263 | .ntvs_analysis.dat 264 | node_modules/ 265 | 266 | # Visual Studio 6 build log 267 | *.plg 268 | 269 | # Visual Studio 6 workspace options file 270 | *.opt 271 | 272 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 273 | *.vbw 274 | 275 | # Visual Studio LightSwitch build output 276 | **/*.HTMLClient/GeneratedArtifacts 277 | **/*.DesktopClient/GeneratedArtifacts 278 | **/*.DesktopClient/ModelManifest.xml 279 | **/*.Server/GeneratedArtifacts 280 | **/*.Server/ModelManifest.xml 281 | _Pvt_Extensions 282 | 283 | # Paket dependency manager 284 | .paket/paket.exe 285 | paket-files/ 286 | 287 | # FAKE - F# Make 288 | .fake/ 289 | 290 | # JetBrains Rider 291 | .idea/ 292 | *.sln.iml 293 | 294 | # CodeRush 295 | .cr/ 296 | 297 | # Python Tools for Visual Studio (PTVS) 298 | __pycache__/ 299 | *.pyc 300 | 301 | # Cake - Uncomment if you are using it 302 | # tools/** 303 | # !tools/packages.config 304 | 305 | # Tabs Studio 306 | *.tss 307 | 308 | # Telerik's JustMock configuration file 309 | *.jmconfig 310 | 311 | # BizTalk build output 312 | *.btp.cs 313 | *.btm.cs 314 | *.odx.cs 315 | *.xsd.cs 316 | 317 | # OpenCover UI analysis results 318 | OpenCover/ 319 | 320 | # Azure Stream Analytics local run output 321 | ASALocalRun/ 322 | 323 | # MSBuild Binary and Structured Log 324 | *.binlog 325 | 326 | # NVidia Nsight GPU debugger configuration file 327 | *.nvuser 328 | 329 | # MFractors (Xamarin productivity tool) working folder 330 | .mfractor/ 331 | 332 | Serializer/native/Microsoft.FrozenObjects.Serializer.Native.so 333 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to find out which attributes exist for C# debugging 3 | // Use hover for the description of the existing attributes 4 | // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": ".NET Core Launch (console)", 9 | "type": "coreclr", 10 | "request": "launch", 11 | "preLaunchTask": "build", 12 | // If you have changed target frameworks, make sure to update the program path. 13 | "program": "${workspaceFolder}/Tests/bin/Debug/netcoreapp3.0/Microsoft.FrozenObjects.Tests.dll", 14 | "args": [], 15 | "cwd": "${workspaceFolder}/Tests", 16 | // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console 17 | "console": "internalConsole", 18 | "stopAtEntry": false 19 | }, 20 | { 21 | "name": ".NET Core Attach", 22 | "type": "coreclr", 23 | "request": "attach", 24 | "processId": "${command:pickProcess}" 25 | } 26 | ] 27 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "build", 6 | "command": "dotnet", 7 | "type": "process", 8 | "args": [ 9 | "build", 10 | "${workspaceFolder}/Tests/Microsoft.FrozenObjects.Tests.csproj" 11 | ], 12 | "problemMatcher": "$tsc" 13 | }, 14 | { 15 | "label": "publish", 16 | "command": "dotnet", 17 | "type": "process", 18 | "args": [ 19 | "publish", 20 | "${workspaceFolder}/Tests/Microsoft.FrozenObjects.Tests.csproj" 21 | ], 22 | "problemMatcher": "$tsc" 23 | }, 24 | { 25 | "label": "watch", 26 | "command": "dotnet", 27 | "type": "process", 28 | "args": [ 29 | "watch", 30 | "run", 31 | "${workspaceFolder}/Tests/Microsoft.FrozenObjects.Tests.csproj" 32 | ], 33 | "problemMatcher": "$tsc" 34 | } 35 | ] 36 | } -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Microsoft Open Source Code of Conduct 2 | 3 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 4 | 5 | Resources: 6 | 7 | - [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) 8 | - [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) 9 | - Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Microsoft Corporation. All rights reserved. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE 22 | -------------------------------------------------------------------------------- /Microsoft.FrozenObjects.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29025.244 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.FrozenObjects", "src\Microsoft.FrozenObjects.csproj", "{26376C2E-D488-4D04-8332-B0CFC806B56B}" 7 | ProjectSection(ProjectDependencies) = postProject 8 | {09DE73E9-2A44-43FD-B869-1EFFE311E8BB} = {09DE73E9-2A44-43FD-B869-1EFFE311E8BB} 9 | EndProjectSection 10 | EndProject 11 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.FrozenObjects.BuildTools", "tools\Microsoft.FrozenObjects.BuildTools.csproj", "{09DE73E9-2A44-43FD-B869-1EFFE311E8BB}" 12 | EndProject 13 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.FrozenObjects.Tests", "tests\Microsoft.FrozenObjects.Tests.csproj", "{5C2D82E1-E77C-4938-9B9F-807E33666E34}" 14 | EndProject 15 | Global 16 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 17 | Debug|Any CPU = Debug|Any CPU 18 | Release|Any CPU = Release|Any CPU 19 | EndGlobalSection 20 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 21 | {26376C2E-D488-4D04-8332-B0CFC806B56B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 22 | {26376C2E-D488-4D04-8332-B0CFC806B56B}.Debug|Any CPU.Build.0 = Debug|Any CPU 23 | {26376C2E-D488-4D04-8332-B0CFC806B56B}.Release|Any CPU.ActiveCfg = Release|Any CPU 24 | {26376C2E-D488-4D04-8332-B0CFC806B56B}.Release|Any CPU.Build.0 = Release|Any CPU 25 | {09DE73E9-2A44-43FD-B869-1EFFE311E8BB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 26 | {09DE73E9-2A44-43FD-B869-1EFFE311E8BB}.Debug|Any CPU.Build.0 = Debug|Any CPU 27 | {09DE73E9-2A44-43FD-B869-1EFFE311E8BB}.Release|Any CPU.ActiveCfg = Release|Any CPU 28 | {09DE73E9-2A44-43FD-B869-1EFFE311E8BB}.Release|Any CPU.Build.0 = Release|Any CPU 29 | {5C2D82E1-E77C-4938-9B9F-807E33666E34}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 30 | {5C2D82E1-E77C-4938-9B9F-807E33666E34}.Debug|Any CPU.Build.0 = Debug|Any CPU 31 | {5C2D82E1-E77C-4938-9B9F-807E33666E34}.Release|Any CPU.ActiveCfg = Release|Any CPU 32 | {5C2D82E1-E77C-4938-9B9F-807E33666E34}.Release|Any CPU.Build.0 = Release|Any CPU 33 | EndGlobalSection 34 | GlobalSection(SolutionProperties) = preSolution 35 | HideSolutionNode = FALSE 36 | EndGlobalSection 37 | GlobalSection(ExtensibilityGlobals) = postSolution 38 | SolutionGuid = {C2C32CB7-4450-4293-B5F0-888F94FB999D} 39 | EndGlobalSection 40 | EndGlobal 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Frozen Objects 2 | 3 | [![Build status](https://dev.azure.com/ms/FrozenObjects/_apis/build/status/FrozenObjects)](https://dev.azure.com/ms/FrozenObjects/_build/latest?definitionId=175) 4 | 5 | Serialize an object graph to frozen objects 6 | 7 | # Contributing 8 | 9 | This project welcomes contributions and suggestions. Most contributions require you to agree to a 10 | Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us 11 | the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com. 12 | 13 | When you submit a pull request, a CLA bot will automatically determine whether you need to provide 14 | a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions 15 | provided by the bot. You will only need to do this once across all repos using our CLA. 16 | 17 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 18 | For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or 19 | contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Security 4 | 5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). 6 | 7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below. 8 | 9 | ## Reporting Security Issues 10 | 11 | **Please do not report security vulnerabilities through public GitHub issues.** 12 | 13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report). 14 | 15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey). 16 | 17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc). 18 | 19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 20 | 21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 22 | * Full paths of source file(s) related to the manifestation of the issue 23 | * The location of the affected source code (tag/branch/commit or direct URL) 24 | * Any special configuration required to reproduce the issue 25 | * Step-by-step instructions to reproduce the issue 26 | * Proof-of-concept or exploit code (if possible) 27 | * Impact of the issue, including how an attacker might exploit the issue 28 | 29 | This information will help us triage your report more quickly. 30 | 31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs. 32 | 33 | ## Preferred Languages 34 | 35 | We prefer all communications to be in English. 36 | 37 | ## Policy 38 | 39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd). 40 | 41 | 42 | -------------------------------------------------------------------------------- /nuget.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /samples/Basic/FrozenObjectTest.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | netcoreapp3.1 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /samples/Basic/FrozenObjectTest.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29102.190 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FrozenObjectTest", "FrozenObjectTest.csproj", "{68D11345-A9A2-452B-86D2-9B5D89541677}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {68D11345-A9A2-452B-86D2-9B5D89541677}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {68D11345-A9A2-452B-86D2-9B5D89541677}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {68D11345-A9A2-452B-86D2-9B5D89541677}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {68D11345-A9A2-452B-86D2-9B5D89541677}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {CF58AF40-D83F-4A53-BE85-61217BEA6178} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /samples/Basic/Program.cs: -------------------------------------------------------------------------------- 1 | namespace FrozenObjectTest 2 | { 3 | using System; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Reflection; 7 | using static Microsoft.FrozenObjects.Serializer; 8 | 9 | class Program 10 | { 11 | static void Main(string[] args) 12 | { 13 | const string dll = "foo.dll"; 14 | const string bin = "foo.bin"; 15 | const string @namespace = "MyNamespace"; 16 | const string type = "MyType"; 17 | const string method = "DeserializeMethodName"; 18 | 19 | const string obj1 = "Hello World"; 20 | SerializeObject(obj1, bin, dll, @namespace, type, method, new Version(1, 0, 0, 0)); 21 | 22 | var obj2 = Assembly.Load(File.ReadAllBytes(dll)).GetTypes().Single(t => t.FullName == $"{@namespace}.{type}").GetMethod(method).Invoke(null, new object[] { bin }); 23 | 24 | if (Equals(obj1, obj2)) 25 | { 26 | Console.WriteLine("Hooray"); 27 | } 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /samples/Basic/nuget.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/Deserializer.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | namespace Microsoft.FrozenObjects 5 | { 6 | using System; 7 | using System.IO; 8 | using System.Runtime.CompilerServices; 9 | using System.Runtime.InteropServices; 10 | using static NativeMethods; 11 | 12 | public static unsafe class Deserializer 13 | { 14 | public static object Deserialize(RuntimeTypeHandle[] runtimeTypeHandles, string blobPath) 15 | { 16 | var extraSpace = IntPtr.Size + IntPtr.Size; // 1 extra IntPtr for segmentHandle and the other for size of the allocation (needed on Linux) 17 | var length = 0L; 18 | byte* buffer; 19 | 20 | using (var fs = new FileStream(blobPath, FileMode.Open, FileAccess.Read)) 21 | { 22 | length = fs.Length; 23 | var chunkSize = Math.Min(64 * 1024, (int)length); // 64KB is a reasonable size 24 | 25 | buffer = AllocateMemory(length + extraSpace) + extraSpace; // so that buffer points to the real start of the frozen segment. 26 | 27 | var span = new Span(buffer, chunkSize); 28 | var read = 0L; 29 | int chunk; 30 | 31 | while ((chunk = fs.Read(span)) > 0) 32 | { 33 | read += chunk; 34 | 35 | if (read == length) 36 | { 37 | break; 38 | } 39 | 40 | span = new Span(buffer + read, Math.Min(chunkSize, (int)(length - read))); 41 | } 42 | } 43 | 44 | var retVal = DeserializeInner(runtimeTypeHandles, buffer, length); 45 | 46 | // -InPtr.Size - IntPtr.Size is for size (we need it in Linux) 47 | Marshal.WriteIntPtr((IntPtr)buffer, 0 - IntPtr.Size - IntPtr.Size, (IntPtr)(length + extraSpace)); 48 | 49 | // -IntPtr.Size is for segment handle 50 | Marshal.WriteIntPtr((IntPtr)buffer, 0 - IntPtr.Size, InternalHelpers.RegisterFrozenSegment((IntPtr)buffer, (IntPtr)length)); 51 | 52 | return retVal; 53 | } 54 | 55 | public static void UnloadFrozenObject(object o) 56 | { 57 | var optr = Marshal.ReadIntPtr((IntPtr)Unsafe.AsPointer(ref o)); 58 | var baseAddress = optr - IntPtr.Size - IntPtr.Size - IntPtr.Size; // 3 because optr is actually pointing to MT* 59 | var length = Marshal.ReadIntPtr(baseAddress); 60 | var segmentHandle = Marshal.ReadIntPtr(baseAddress, IntPtr.Size); 61 | 62 | InternalHelpers.UnregisterFrozenSegment(segmentHandle); 63 | FreeMemory(baseAddress, length); 64 | } 65 | 66 | [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] 67 | private static object DeserializeInner(RuntimeTypeHandle[] runtimeTypeHandles, byte* buffer, long length) 68 | { 69 | byte* objectId = buffer + IntPtr.Size; 70 | 71 | while (objectId < buffer + length) 72 | { 73 | IntPtr typeHandle = IntPtr.Size == 8 ? runtimeTypeHandles[(int)*(long*)objectId].Value : runtimeTypeHandles[*(int*)objectId].Value; 74 | bool isArray = ((long)typeHandle & 0x2) == 0x2; 75 | 76 | var mt = (MethodTable*)typeHandle; 77 | if (isArray) 78 | { 79 | mt = IntPtr.Size == 8 ? (MethodTable*)*(long*)(typeHandle + 6) : (MethodTable*)*(int*)(typeHandle + 6); // TODO: Is this correct for 32-bit? 80 | } 81 | 82 | long objectSize = mt->BaseSize; 83 | var flags = mt->Flags; 84 | bool hasComponentSize = (flags & 0x80000000) == 0x80000000; 85 | 86 | if (hasComponentSize) 87 | { 88 | var numComponents = (long)*(int*)(objectId + IntPtr.Size); 89 | objectSize += numComponents * mt->ComponentSize; 90 | } 91 | 92 | bool containsPointerOrCollectible = (flags & 0x10000000) == 0x10000000 || (flags & 0x1000000) == 0x1000000; 93 | if (containsPointerOrCollectible) 94 | { 95 | var entries = *(int*)((byte*)mt - IntPtr.Size); 96 | if (entries < 0) 97 | { 98 | entries -= entries; 99 | } 100 | 101 | var slots = 1 + entries * 2; 102 | 103 | var gcdesc = new GCDesc(buffer, (byte*)mt - (slots * IntPtr.Size), slots * IntPtr.Size); 104 | 105 | if (IntPtr.Size == 8) 106 | { 107 | gcdesc.FixupObject64(objectId, objectSize); 108 | } 109 | else 110 | { 111 | gcdesc.FixupObject32(objectId, objectSize); 112 | } 113 | } 114 | 115 | if (IntPtr.Size == 8) 116 | { 117 | *(long*)objectId = (long)mt; 118 | } 119 | else 120 | { 121 | *(int*)objectId = (int)mt; 122 | } 123 | 124 | objectId += objectSize + Padding(objectSize, IntPtr.Size); 125 | } 126 | 127 | var tmp = buffer + IntPtr.Size; 128 | return Unsafe.Read(&tmp); 129 | } 130 | 131 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 132 | private static int Padding(long num, int align) 133 | { 134 | return (int)(0 - num & (align - 1)); 135 | } 136 | 137 | [StructLayout(LayoutKind.Explicit)] 138 | internal struct MethodTable 139 | { 140 | [FieldOffset(0)] 141 | public ushort ComponentSize; 142 | 143 | [FieldOffset(0)] 144 | public uint Flags; 145 | 146 | [FieldOffset(4)] 147 | public int BaseSize; 148 | } 149 | 150 | internal unsafe struct GCDesc 151 | { 152 | private readonly byte* @base; 153 | 154 | private readonly IntPtr data; 155 | 156 | private readonly int size; 157 | 158 | public GCDesc(byte* @base, byte* data, int size) 159 | { 160 | this.@base = @base; 161 | this.data = new IntPtr(data); 162 | this.size = size; 163 | } 164 | 165 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 166 | public int GetNumSeries() 167 | { 168 | return (int)Marshal.ReadIntPtr(this.data + this.size - IntPtr.Size); 169 | } 170 | 171 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 172 | public int GetHighestSeries() 173 | { 174 | return this.size - IntPtr.Size * 3; 175 | } 176 | 177 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 178 | public int GetLowestSeries() 179 | { 180 | return this.size - ComputeSize(this.GetNumSeries()); 181 | } 182 | 183 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 184 | public int GetSeriesSize(int curr) 185 | { 186 | return (int)Marshal.ReadIntPtr(this.data + curr); 187 | } 188 | 189 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 190 | public ulong GetSeriesOffset(int curr) 191 | { 192 | return (ulong)Marshal.ReadIntPtr(this.data + curr + IntPtr.Size); 193 | } 194 | 195 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 196 | public uint GetPointers(int curr, int i) 197 | { 198 | int offset = i * IntPtr.Size; 199 | return IntPtr.Size == 8 ? (uint)Marshal.ReadInt32(this.data + curr + offset) : (uint)Marshal.ReadInt16(this.data + curr + offset); 200 | } 201 | 202 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 203 | public uint GetSkip(int curr, int i) 204 | { 205 | int offset = i * IntPtr.Size + IntPtr.Size / 2; 206 | return IntPtr.Size == 8 ? (uint)Marshal.ReadInt32(this.data + curr + offset) : (uint)Marshal.ReadInt16(this.data + curr + offset); 207 | } 208 | 209 | public void FixupObject64(byte* addr, long size) 210 | { 211 | var series = this.GetNumSeries(); 212 | var highest = this.GetHighestSeries(); 213 | var curr = highest; 214 | 215 | if (series > 0) 216 | { 217 | var lowest = this.GetLowestSeries(); 218 | do 219 | { 220 | var ptr = addr + this.GetSeriesOffset(curr); 221 | var stop = ptr + this.GetSeriesSize(curr) + size; 222 | 223 | while (ptr < stop) 224 | { 225 | var ret = *(ulong*)ptr; 226 | if (ret != 0) 227 | { 228 | *(ulong*)ptr = (ulong)(this.@base + ret); 229 | } 230 | 231 | ptr += 8; 232 | } 233 | 234 | curr -= 8 * 2; 235 | } while (curr >= lowest); 236 | } 237 | else 238 | { 239 | var ptr = addr + this.GetSeriesOffset(curr); 240 | while (ptr < addr + size - 8) 241 | { 242 | for (int i = 0; i > series; i--) 243 | { 244 | var nptrs = this.GetPointers(curr, i); 245 | var skip = this.GetSkip(curr, i); 246 | 247 | var stop = ptr + nptrs * (ulong)8; 248 | do 249 | { 250 | var ret = *(ulong*)ptr; 251 | if (ret != 0) 252 | { 253 | *(ulong*)ptr = (ulong)(this.@base + ret); 254 | } 255 | 256 | ptr += 8; 257 | } while (ptr < stop); 258 | 259 | ptr += skip; 260 | } 261 | } 262 | } 263 | } 264 | 265 | public void FixupObject32(byte* addr, long size) 266 | { 267 | var series = this.GetNumSeries(); 268 | var highest = this.GetHighestSeries(); 269 | var curr = highest; 270 | 271 | if (series > 0) 272 | { 273 | var lowest = this.GetLowestSeries(); 274 | do 275 | { 276 | var ptr = addr + this.GetSeriesOffset(curr); 277 | var stop = ptr + this.GetSeriesSize(curr) + size; 278 | 279 | while (ptr < stop) 280 | { 281 | var ret = *(uint*)ptr; 282 | if (ret != 0) 283 | { 284 | *(uint*)ptr = (uint)(this.@base + ret); 285 | } 286 | 287 | ptr += 4; 288 | } 289 | 290 | curr -= 4 * 2; 291 | } while (curr >= lowest); 292 | } 293 | else 294 | { 295 | var ptr = addr + this.GetSeriesOffset(curr); 296 | while (ptr < addr + size - 4) 297 | { 298 | for (int i = 0; i > series; i--) 299 | { 300 | var nptrs = this.GetPointers(curr, i); 301 | var skip = this.GetSkip(curr, i); 302 | 303 | var stop = ptr + nptrs * (ulong)4; 304 | do 305 | { 306 | var ret = *(uint*)ptr; 307 | if (ret != 0) 308 | { 309 | *(uint*)ptr = (uint)(this.@base + ret); 310 | } 311 | 312 | ptr += 4; 313 | } while (ptr < stop); 314 | 315 | ptr += skip; 316 | } 317 | } 318 | } 319 | } 320 | 321 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 322 | private static int ComputeSize(int series) 323 | { 324 | return IntPtr.Size + series * IntPtr.Size * 2; 325 | } 326 | } 327 | } 328 | } -------------------------------------------------------------------------------- /src/Microsoft.FrozenObjects.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.1 5 | true 6 | Embedded 7 | true 8 | 7.3 9 | $(MSBuildThisFileDirectory)/$(BaseIntermediateOutputPath)/$(Configuration)/$(TargetFramework)/Microsoft.FrozenObjects.InternalCalls.dll 10 | 11 | 12 | 13 | $(InternalCallsOutputPath) 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/Microsoft.FrozenObjects.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Microsoft.FrozenObjects 5 | $version$ 6 | Microsoft and Contributors 7 | Microsoft and Contributors 8 | true 9 | MIT 10 | https://github.com/microsoft/FrozenObjects 11 | Deserializer for Frozen Objects. 12 | Copyright © Microsoft 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/NativeMethods.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | namespace Microsoft.FrozenObjects 5 | { 6 | using System; 7 | using System.Runtime.InteropServices; 8 | 9 | internal static class NativeMethods 10 | { 11 | public static void FreeMemory(IntPtr baseAddress, IntPtr length) 12 | { 13 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) 14 | { 15 | VirtualFree(baseAddress, IntPtr.Zero, FreeType.Release); 16 | } 17 | else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) || RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) 18 | { 19 | munmap(baseAddress, length); 20 | } 21 | else 22 | { 23 | throw new PlatformNotSupportedException(); 24 | } 25 | } 26 | 27 | public static unsafe byte* AllocateMemory(long allocationSize) 28 | { 29 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) 30 | { 31 | byte* buffer = (byte*)VirtualAlloc(IntPtr.Zero, (IntPtr)allocationSize, AllocationType.Reserve | AllocationType.Commit, MemoryProtection.ReadWrite); 32 | 33 | return buffer; 34 | } 35 | else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) 36 | { 37 | byte* buffer = (byte*)mmap(IntPtr.Zero, (IntPtr)allocationSize, MemoryMappedProtections.PROT_READ | MemoryMappedProtections.PROT_WRITE, (int)(MemoryMappedFlags.MAP_PRIVATE | MemoryMappedFlags.MAP_ANONYMOUS), -1, IntPtr.Zero); 38 | 39 | if (buffer == (void*)-1) 40 | { 41 | buffer = (byte*)0; 42 | } 43 | 44 | return buffer; 45 | } 46 | else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) 47 | { 48 | byte* buffer = (byte*)mmap(IntPtr.Zero, (IntPtr)allocationSize, MemoryMappedProtections.PROT_READ | MemoryMappedProtections.PROT_WRITE, (int)(MemoryMappedFlagsDarwin.MAP_PRIVATE | MemoryMappedFlagsDarwin.MAP_ANONYMOUS), -1, IntPtr.Zero); 49 | 50 | if (buffer == (void*)-1) 51 | { 52 | buffer = (byte*)0; 53 | } 54 | 55 | return buffer; 56 | } 57 | else 58 | { 59 | throw new PlatformNotSupportedException(); 60 | } 61 | } 62 | 63 | [DllImport("kernel32.dll", SetLastError = true)] 64 | private static extern IntPtr VirtualAlloc(IntPtr baseAddress, IntPtr size, AllocationType allocationType, MemoryProtection memoryProtection); 65 | 66 | [DllImport("kernel32.dll", SetLastError = true)] 67 | private static extern bool VirtualFree(IntPtr baseAddress, IntPtr size, FreeType freeType); 68 | 69 | [DllImport("libc", EntryPoint = "mmap", SetLastError = true)] 70 | private static extern IntPtr mmap(IntPtr addr, IntPtr length, MemoryMappedProtections prot, int flags, int fd, IntPtr offset); 71 | 72 | [DllImport("libc", EntryPoint = "munmap", SetLastError = true)] 73 | private static extern int munmap(IntPtr addr, IntPtr length); 74 | } 75 | 76 | [Flags] 77 | internal enum AllocationType 78 | { 79 | Commit = 0x1000, 80 | Reserve = 0x2000, 81 | Decommit = 0x4000, 82 | Release = 0x8000, 83 | Reset = 0x80000, 84 | Physical = 0x400000, 85 | TopDown = 0x100000, 86 | WriteWatch = 0x200000, 87 | LargePages = 0x20000000 88 | } 89 | 90 | [Flags] 91 | internal enum MemoryProtection 92 | { 93 | Execute = 0x10, 94 | ExecuteRead = 0x20, 95 | ExecuteReadWrite = 0x40, 96 | ExecuteWriteCopy = 0x80, 97 | NoAccess = 0x01, 98 | ReadOnly = 0x02, 99 | ReadWrite = 0x04, 100 | WriteCopy = 0x08, 101 | GuardModifierflag = 0x100, 102 | NoCacheModifierflag = 0x200, 103 | WriteCombineModifierflag = 0x400 104 | } 105 | 106 | [Flags] 107 | internal enum FreeType 108 | { 109 | Decommit = 0x4000, 110 | Release = 0x8000, 111 | } 112 | 113 | [Flags] 114 | internal enum MemoryMappedProtections 115 | { 116 | PROT_NONE = 0x0, 117 | PROT_READ = 0x1, 118 | PROT_WRITE = 0x2, 119 | PROT_EXEC = 0x4 120 | } 121 | 122 | [Flags] 123 | internal enum MemoryMappedFlags 124 | { 125 | MAP_SHARED = 0x01, 126 | MAP_PRIVATE = 0x02, 127 | MAP_ANONYMOUS = 0x20, 128 | } 129 | 130 | [Flags] 131 | internal enum MemoryMappedFlagsDarwin 132 | { 133 | MAP_SHARED = 0x01, 134 | MAP_PRIVATE = 0x02, 135 | MAP_ANONYMOUS = 0x1000, 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/Serializer.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | namespace Microsoft.FrozenObjects 5 | { 6 | using System; 7 | using System.Collections; 8 | using System.Collections.Generic; 9 | using System.Collections.Immutable; 10 | using System.IO; 11 | using System.Reflection; 12 | using System.Reflection.Metadata; 13 | using System.Reflection.Metadata.Ecma335; 14 | using System.Reflection.PortableExecutable; 15 | using System.Runtime.CompilerServices; 16 | using System.Runtime.InteropServices; 17 | using static NativeMethods; 18 | 19 | public static class Serializer 20 | { 21 | public static void SerializeObject(object o, string outputDataPath, string outputAssemblyFilePath, string outputNamespace, string typeName, string methodName, Version version, Type methodType = null, byte[] privateKeyOpt = null) 22 | { 23 | Dictionary typeTokenMap = SerializeDataAndBuildTypeTokenMap(o, outputDataPath); 24 | 25 | (HashSet allAssemblies, HashSet allTypes) = ComputeAssemblyAndTypeClosure(typeTokenMap); 26 | 27 | MetadataBuilder metadataBuilder = CreateMetadataBuilder(Path.GetFileName(outputAssemblyFilePath), Path.GetFileNameWithoutExtension(outputAssemblyFilePath), version); 28 | 29 | Dictionary assemblyReferenceHandleMap = AccumulateAssemblyReferenceHandles(metadataBuilder, allAssemblies); 30 | Dictionary uniqueTypeRefMap = AccumulateTypeReferenceHandles(metadataBuilder, assemblyReferenceHandleMap, allTypes); 31 | Dictionary typeToTypeSpecMap = AccumulateTypeSpecificationHandles(metadataBuilder, uniqueTypeRefMap, typeTokenMap); 32 | 33 | SerializeCompanionAssembly(metadataBuilder, outputAssemblyFilePath, outputNamespace, typeName, methodName, typeTokenMap, typeToTypeSpecMap, privateKeyOpt); 34 | } 35 | 36 | [MethodImpl(MethodImplOptions.NoInlining)] 37 | private static unsafe void WriteObjectGraphToDisk(byte* stackAllocatedData, Stream stream, Dictionary serializedObjectMap, Queue objectQueue, RuntimeTypeHandleDictionary mtTokenMap, ref long lastReservedObjectEnd) 38 | { 39 | long nextObjectHeaderFilePointer = 0; 40 | 41 | while (objectQueue.Count != 0) 42 | { 43 | object o = objectQueue.Dequeue(); 44 | ref TypeInfo typeInfo = ref mtTokenMap.GetOrAddValueRef(Type.GetTypeHandle(o)); 45 | 46 | long mtToken; 47 | 48 | if (typeInfo != null) 49 | { 50 | mtToken = typeInfo.MTToken; 51 | } 52 | else 53 | { 54 | mtToken = mtTokenMap.Count - 1; 55 | typeInfo = GetObjectInfo(o, mtToken); 56 | } 57 | 58 | long hashCode = (RuntimeHelpers.GetHashCode(o) & 0x0fffffff) | (1 << 27) | (1 << 26); 59 | 60 | fixed (byte* data = &GetRawData(o)) 61 | { 62 | MethodTable* mt; 63 | if (IntPtr.Size == 8) 64 | { 65 | *(long*)stackAllocatedData = hashCode; 66 | *(long*)(stackAllocatedData + 8) = mtToken; 67 | stream.Write(new ReadOnlySpan(stackAllocatedData, 16)); 68 | mt = (MethodTable*)*(long*)(data - IntPtr.Size); 69 | } 70 | else 71 | { 72 | *(int*)stackAllocatedData = (int)hashCode; 73 | *(int*)(stackAllocatedData + 4) = (int)mtToken; 74 | stream.Write(new ReadOnlySpan(stackAllocatedData, 8)); 75 | mt = (MethodTable*)*(int*)(data - IntPtr.Size); 76 | } 77 | 78 | uint flags = mt->Flags; 79 | bool hasComponentSize = (flags & 0x80000000) == 0x80000000; 80 | 81 | long objectSize = mt->BaseSize; 82 | 83 | if (hasComponentSize) 84 | { 85 | var numComponents = (long)*(int*)data; 86 | objectSize += numComponents * mt->ComponentSize; 87 | } 88 | 89 | var remainingSize = objectSize - IntPtr.Size - IntPtr.Size; // because we have already written the object header and MT Token 90 | 91 | if (remainingSize <= int.MaxValue) 92 | { 93 | stream.Write(new ReadOnlySpan(data, (int)remainingSize)); 94 | } 95 | else 96 | { 97 | WriteLargeObject(stream, data, remainingSize); 98 | } 99 | 100 | bool containsPointersOrCollectible = (flags & 0x10000000) == 0x10000000 || (flags & 0x1000000) == 0x1000000; 101 | if (containsPointersOrCollectible) 102 | { 103 | var gcdesc = new GCDesc(typeInfo.GCDescData, typeInfo.GCDescSize); 104 | gcdesc.EnumerateObject(data, objectSize, serializedObjectMap, objectQueue, stream, nextObjectHeaderFilePointer, ref lastReservedObjectEnd); 105 | } 106 | 107 | nextObjectHeaderFilePointer += objectSize + Padding(objectSize, IntPtr.Size); 108 | stream.Seek(nextObjectHeaderFilePointer, SeekOrigin.Begin); 109 | } 110 | } 111 | } 112 | 113 | [MethodImpl(MethodImplOptions.NoInlining)] 114 | private static unsafe void WriteLargeObject(Stream stream, byte* data, long remainingSize) 115 | { 116 | var buffer = data + remainingSize; 117 | 118 | while (remainingSize != 0) 119 | { 120 | var chunkSize = Math.Min(64 * 1024, (int)remainingSize); // 64KB is a reasonable size 121 | stream.Write(new ReadOnlySpan(buffer - remainingSize, chunkSize)); 122 | remainingSize -= chunkSize; 123 | } 124 | } 125 | 126 | /// 127 | /// Responsible for serializing the object graph ( to 128 | /// 129 | /// 130 | /// Loads the native shared library that implements walking the object graph using GCDesc. The reason the functionality 131 | /// is in a native library is because we do a managed calli into this library as a mechanism to supress GC while we serialize 132 | /// the object graph to disk. 133 | /// 134 | /// Each serialized object has a placeholder token written in place of the MethodTable*. 135 | /// 136 | /// A tuple of each unique type we encounter and its placeholder token are returned from the native library. We in turn convert 137 | /// this data into a Dictionary`[System.Type, System.Int32], so that the remainder of the work can be done using System.Reflection 138 | /// 139 | /// So to summarize, we write the object graph to disk and get all unique types in the form of a Dictionary`[System.Type, System.Int32] 140 | /// 141 | /// Next, please read . 142 | /// 143 | /// object to serialize 144 | /// file path for the serialized object graph 145 | private static unsafe Dictionary SerializeDataAndBuildTypeTokenMap(object root, string outputDataPath) 146 | { 147 | var serializedObjectMap = new Dictionary(); 148 | var mtTokenMap = new RuntimeTypeHandleDictionary(100); 149 | var objectQueue = new Queue(); 150 | 151 | objectQueue.Enqueue(root); 152 | 153 | long lastReservedObjectEnd = GetObjectSize(root); 154 | lastReservedObjectEnd += Padding(lastReservedObjectEnd, IntPtr.Size); 155 | 156 | byte* stackAllocatedData = stackalloc byte[16]; 157 | 158 | using (var stream = new ResizableMemoryStream()) 159 | { 160 | WriteObjectGraphToDisk(stackAllocatedData, stream, serializedObjectMap, objectQueue, mtTokenMap, ref lastReservedObjectEnd); 161 | 162 | using (var fs = new FileStream(outputDataPath, FileMode.Create, FileAccess.Write)) 163 | { 164 | stream.Seek(0, SeekOrigin.Begin); 165 | stream.CopyTo(fs, 8192); 166 | } 167 | } 168 | 169 | var typeTokenMap = new Dictionary(mtTokenMap.Count); 170 | 171 | foreach (var entry in mtTokenMap) 172 | { 173 | typeTokenMap.Add(Type.GetTypeFromHandle(entry.Key), (int)entry.Value.MTToken); 174 | } 175 | 176 | return typeTokenMap; 177 | } 178 | 179 | /// 180 | /// Responsible for finding the closure of all Assembly and Types reachable from the original 181 | /// set of Types that were part of the input object graph. 182 | /// 183 | /// 184 | /// 185 | /// We first put all the types we saw in the object walk into a queue, then find all the base types, and 186 | /// if the types were nested, the heirarchy of those types. If the type was generic then their arguments, which 187 | /// in turn go find all theirs, so on and so forth. 188 | /// 189 | /// Ultimately the goal of this function is to get all the types and assemblies that could be needed when encoding the signatures. 190 | /// 191 | /// It is quite possible that some of these types may not require to be encoded, and that's ok. 192 | /// 193 | /// Next, please read . 194 | /// 195 | /// The unique set of types that were in the input object graph 196 | private static Tuple, HashSet> ComputeAssemblyAndTypeClosure(Dictionary typeTokenMap) 197 | { 198 | var typeQueue = new Queue(); 199 | 200 | foreach (var entry in typeTokenMap) 201 | { 202 | typeQueue.Enqueue(entry.Key); 203 | } 204 | 205 | var allTypes = new HashSet(); 206 | var allAssemblies = new HashSet(); 207 | 208 | while (typeQueue.Count != 0) 209 | { 210 | var type = typeQueue.Peek(); 211 | if (!allTypes.Contains(type)) 212 | { 213 | if (type.IsPointer || type.IsByRef || type.IsByRefLike || type.IsCOMObject) // || type.IsCollectible ?? 214 | { 215 | throw new NotSupportedException(); 216 | } 217 | 218 | if (type.IsArray) 219 | { 220 | typeQueue.Enqueue(type.GetElementType()); 221 | } 222 | else 223 | { 224 | var declaringType = type.DeclaringType; 225 | if (declaringType != null) 226 | { 227 | typeQueue.Enqueue(declaringType); 228 | } 229 | 230 | var baseType = type.BaseType; 231 | if (baseType != null) 232 | { 233 | typeQueue.Enqueue(baseType); 234 | } 235 | 236 | if (type.IsGenericType) 237 | { 238 | var typeArgs = type.GenericTypeArguments; 239 | for (int i = 0; i < typeArgs.Length; ++i) 240 | { 241 | typeQueue.Enqueue(typeArgs[i]); 242 | } 243 | } 244 | } 245 | 246 | var typeAssembly = type.Assembly; 247 | if (!allAssemblies.Contains(typeAssembly)) 248 | { 249 | allAssemblies.Add(typeAssembly); 250 | } 251 | 252 | allTypes.Add(type); 253 | } 254 | 255 | typeQueue.Dequeue(); 256 | } 257 | 258 | return new Tuple, HashSet>(allAssemblies, allTypes); 259 | } 260 | 261 | /// 262 | /// Responsible for taking all the unique Assembly objects and just blindly writing an AssemblyRef into the metadata 263 | /// 264 | /// 265 | /// 266 | /// Nothing particularly interesting here, please read 267 | /// 268 | /// 269 | /// the metadata builder 270 | /// unique set of assemblies encountered in our closure walk 271 | private static Dictionary AccumulateAssemblyReferenceHandles(MetadataBuilder metadataBuilder, HashSet allAssemblies) 272 | { 273 | var assemblyReferenceHandleMap = new Dictionary(); 274 | 275 | foreach (var assembly in allAssemblies) 276 | { 277 | var assemblyName = assembly.GetName(); 278 | var assemblyNameStringHandle = metadataBuilder.GetOrAddString(assemblyName.Name); 279 | 280 | var publicKeyTokenBlobHandle = default(BlobHandle); 281 | var publicKeyToken = assemblyName.GetPublicKeyToken(); 282 | if (publicKeyToken != null) 283 | { 284 | publicKeyTokenBlobHandle = metadataBuilder.GetOrAddBlob(publicKeyToken); 285 | } 286 | 287 | StringHandle cultureStringHandle = default; 288 | if (assemblyName.CultureName != null) 289 | { 290 | cultureStringHandle = metadataBuilder.GetOrAddString(assemblyName.CultureName); 291 | } 292 | 293 | assemblyReferenceHandleMap.Add(assembly, metadataBuilder.AddAssemblyReference(assemblyNameStringHandle, assemblyName.Version, cultureStringHandle, publicKeyTokenBlobHandle, default, default)); 294 | } 295 | 296 | return assemblyReferenceHandleMap; 297 | } 298 | 299 | /// 300 | /// Responsible for taking all the unique Type objects and putting the TypeRefs into the metadata 301 | /// 302 | /// 303 | /// 304 | /// A couple of interesting things to point out: 305 | /// 306 | /// (1) Notice that nested types are handled by putting them into this typeList, and then the list in walked in reverse order 307 | /// so that all the TypeRefs can be found by subsequent dictionary lookups. 308 | /// 309 | /// (2) The resolutionScope is taken care of by checking if there is a declaring type or not, and by (1) we'll ensure that we always do it 310 | /// an order that those dictionary lookups can be satisfied. And if you're a nested type your namespace must be empty. System.Reflection 311 | /// doesn't return an empty namespace, so we had to do some work there. 312 | /// 313 | /// (3) Note that we need to get ensure we're working with the generic type definition, otherwise we'll serialize the same typerefs multiple times 314 | /// and fail PE Verification. 315 | /// 316 | /// After this function completes we can be certain that all typerefs & assemblyrefs needed for the next phase, encoding of the typespec signature 317 | /// are a dictionary lookup away. 318 | /// 319 | /// Please read 320 | /// 321 | /// 322 | /// 323 | /// 324 | /// 325 | private static Dictionary AccumulateTypeReferenceHandles(MetadataBuilder metadataBuilder, Dictionary assemblyReferenceHandleMap, HashSet allTypes) 326 | { 327 | var uniqueTypeRefMap = new Dictionary(); 328 | 329 | foreach (var type in allTypes) 330 | { 331 | // If we don't skip arrays here we end up adding bogus TypeRefs that don't actually exist. 332 | // e.g. System.Int32[] which is just a name of a plain type, and of course doesn't exist. 333 | if (type.IsArray) 334 | { 335 | continue; 336 | } 337 | 338 | var typeList = new List { type }; 339 | { 340 | var tmp = type; 341 | while (tmp.DeclaringType != null) 342 | { 343 | typeList.Add(tmp.DeclaringType); 344 | tmp = tmp.DeclaringType; 345 | } 346 | } 347 | 348 | for (int i = typeList.Count - 1; i > -1; --i) 349 | { 350 | var t = typeList[i]; 351 | if (t.IsGenericType) 352 | { 353 | t = t.GetGenericTypeDefinition(); 354 | } 355 | 356 | if (!uniqueTypeRefMap.ContainsKey(t)) 357 | { 358 | var declaringType = t.DeclaringType; 359 | var resolutionScope = declaringType == null ? (EntityHandle)assemblyReferenceHandleMap[t.Assembly] : (EntityHandle)uniqueTypeRefMap[declaringType]; 360 | 361 | var @namespace = default(StringHandle); 362 | if (declaringType == null && !string.IsNullOrEmpty(t.Namespace)) 363 | { 364 | @namespace = metadataBuilder.GetOrAddString(t.Namespace); 365 | } 366 | 367 | uniqueTypeRefMap.Add(t, metadataBuilder.AddTypeReference(resolutionScope, @namespace, metadataBuilder.GetOrAddString(t.Name))); 368 | } 369 | } 370 | } 371 | 372 | return uniqueTypeRefMap; 373 | } 374 | 375 | /// 376 | /// Responsible for encoding the typespec signatures for each of the unique types we encountered in the input object graph. 377 | /// 378 | /// 379 | /// 380 | /// At this point we have all the pieces needed to encoder a TypeSpec signature for each unique type we saw in the object graph. 381 | /// 382 | /// We have to use the short-form signatures for some of those primitive type codes, but besides that we just handle the ECMA-335 II.23.2.12 Type 383 | /// production rules except for the stuff we know we don't want or can't encounter on a heap. 384 | /// 385 | /// This includes: 386 | /// 387 | /// FNPTR MethodDefOrRefSig 388 | /// MVAR number 389 | /// VAR number 390 | /// 391 | /// In addition to that I don't think we want these: 392 | /// 393 | /// PTR Type 394 | /// PTR VOID 395 | /// 396 | /// You can see all the stuff we handle here, 397 | /// and next on the reading list is 398 | /// 399 | /// 400 | /// 401 | /// the metadata builder 402 | /// dictionary of all possible types 403 | /// the actual unique set of types we encountered 404 | /// 405 | private static Dictionary AccumulateTypeSpecificationHandles(MetadataBuilder metadataBuilder, Dictionary uniqueTypeRefMap, Dictionary typeTokenMap) 406 | { 407 | var primitiveTypeCodeMap = new Dictionary 408 | { 409 | { typeof(bool), PrimitiveTypeCode.Boolean }, 410 | { typeof(char), PrimitiveTypeCode.Char }, 411 | { typeof(sbyte), PrimitiveTypeCode.SByte }, 412 | { typeof(byte), PrimitiveTypeCode.Byte }, 413 | { typeof(short), PrimitiveTypeCode.Int16 }, 414 | { typeof(ushort), PrimitiveTypeCode.UInt16 }, 415 | { typeof(int), PrimitiveTypeCode.Int32 }, 416 | { typeof(uint), PrimitiveTypeCode.UInt32 }, 417 | { typeof(long), PrimitiveTypeCode.Int64 }, 418 | { typeof(ulong), PrimitiveTypeCode.UInt64 }, 419 | { typeof(float), PrimitiveTypeCode.Single }, 420 | { typeof(double), PrimitiveTypeCode.Double }, 421 | { typeof(string), PrimitiveTypeCode.String }, 422 | { typeof(IntPtr), PrimitiveTypeCode.IntPtr }, // We don't really want these, except for that custom MethodInfo support 423 | { typeof(UIntPtr), PrimitiveTypeCode.UIntPtr }, // We don't really want these, except for that custom MethodInfo support 424 | { typeof(object), PrimitiveTypeCode.Object } 425 | }; 426 | 427 | var typeToTypeSpecMap = new Dictionary(); 428 | foreach (var type in typeTokenMap.Keys) 429 | { 430 | var blobBuilder = new BlobBuilder(); 431 | var encoder = new BlobEncoder(blobBuilder).TypeSpecificationSignature(); 432 | 433 | HandleType(type, ref encoder, primitiveTypeCodeMap, uniqueTypeRefMap); 434 | 435 | typeToTypeSpecMap.Add(type, metadataBuilder.AddTypeSpecification(metadataBuilder.GetOrAddBlob(blobBuilder))); 436 | } 437 | 438 | return typeToTypeSpecMap; 439 | } 440 | 441 | /** 442 | * Type ::= 443 | * BOOLEAN | CHAR | I1 | U1 | I2 | U2 | I4 | U4 | I8 | U8 | R4 | R8 | I | U | OBJECT | STRING 444 | * | ARRAY Type ArrayShape 445 | * | SZARRAY Type 446 | * | GENERICINST (CLASS | VALUETYPE) TypeRefOrSpecEncoded GenArgCount Type* 447 | * | (CLASS | VALUETYPE) TypeRefOrSpecEncoded 448 | */ 449 | private static void HandleType(Type type, ref SignatureTypeEncoder encoder, Dictionary primitiveTypeCodeMap, Dictionary uniqueTypeRefMap) 450 | { 451 | if (primitiveTypeCodeMap.TryGetValue(type, out var primitiveTypeCode)) 452 | { 453 | // BOOLEAN | CHAR | I1 | U1 | I2 | U2 | I4 | U4 | I8 | U8 | R4 | R8 | I | U | OBJECT | STRING 454 | encoder.PrimitiveType(primitiveTypeCode); 455 | } 456 | else if (type.IsVariableBoundArray) 457 | { 458 | // ARRAY Type ArrayShape 459 | HandleArray(ref encoder); 460 | } 461 | else if (type.IsSZArray) 462 | { 463 | // SZARRAY Type 464 | HandleSZArray(ref encoder); 465 | } 466 | else if (type.IsConstructedGenericType) 467 | { 468 | // GENERICINST (CLASS | VALUETYPE) TypeRefOrSpecEncoded GenArgCount Type* 469 | HandleGenericInst(ref encoder); 470 | } 471 | else if (type.IsPointer || type.IsCollectible || type.IsCOMObject) 472 | { 473 | // what other things can be on the heap but are not caught by this check? 474 | throw new NotSupportedException(); 475 | } 476 | else 477 | { 478 | // CLASS TypeRefOrSpecEncoded | VALUETYPE TypeRefOrSpecEncoded 479 | encoder.Type(uniqueTypeRefMap[type], type.IsValueType); 480 | } 481 | 482 | void HandleSZArray(ref SignatureTypeEncoder e) 483 | { 484 | e.SZArray(); 485 | HandleType(type.GetElementType(), ref e, primitiveTypeCodeMap, uniqueTypeRefMap); 486 | } 487 | 488 | void HandleArray(ref SignatureTypeEncoder e) 489 | { 490 | e.Array(out var elementTypeEncoder, out var arrayShapeEncoder); 491 | HandleType(type.GetElementType(), ref elementTypeEncoder, primitiveTypeCodeMap, uniqueTypeRefMap); 492 | 493 | var rank = type.GetArrayRank(); 494 | var arr = new int[rank]; 495 | var imm = ImmutableArray.Create(arr); 496 | 497 | arrayShapeEncoder.Shape(rank, ImmutableArray.Empty, imm); // just so we match what the C# compiler generates for mdarrays 498 | } 499 | 500 | void HandleGenericInst(ref SignatureTypeEncoder e) 501 | { 502 | var genericTypeArguments = type.GenericTypeArguments; 503 | var genericTypeArgumentsEncoder = e.GenericInstantiation(uniqueTypeRefMap[type.GetGenericTypeDefinition()], genericTypeArguments.Length, type.IsValueType); 504 | 505 | for (int i = 0; i < genericTypeArguments.Length; ++i) 506 | { 507 | var genericTypeArgumentEncoder = genericTypeArgumentsEncoder.AddArgument(); 508 | HandleType(genericTypeArguments[i], ref genericTypeArgumentEncoder, primitiveTypeCodeMap, uniqueTypeRefMap); 509 | } 510 | } 511 | } 512 | 513 | /// 514 | /// Responsible for serializing the MethodDefinition that allows the consumer to load the associated blob. 515 | /// 516 | /// 517 | /// 518 | /// Generates a method whose signature is `object MethodName(string filePath);` 519 | /// 520 | /// The file path is the path of the blob associated with this assembly. 521 | /// 522 | /// The method that this piece of code generates has the glue required for the deserialization to work. Essentially it allocates 523 | /// an array of RuntimeTypeHandles, and the index of the array is the the placeholder token we stuffed into the serialized object we wrote to disk. 524 | /// 525 | /// At deserialization time, the user calls this method which does an `ldtoken TypeSpec` for each of the unique types we saw at serialization time. 526 | /// And then the deserializer uses this information to do the fixups as it deserializes using GCDesc. 527 | /// 528 | /// 529 | /// the metadata builder 530 | /// the file path of the assembly 531 | /// the namespace of the assembly 532 | /// the type of the method 533 | /// the name of the method 534 | /// the type to token place holder map 535 | /// the type to typespec map 536 | /// optional key 537 | private static void SerializeCompanionAssembly(MetadataBuilder metadataBuilder, string outputAssemblyFilePath, string outputNamespace, string typeName, string methodName, Dictionary typeTokenMap, Dictionary typeToTypeSpecMap, byte[] privateKeyOpt) 538 | { 539 | var netstandardAssemblyRef = metadataBuilder.AddAssemblyReference(metadataBuilder.GetOrAddString("netstandard"), new Version(2, 0, 0, 0), default, metadataBuilder.GetOrAddBlob(new byte[] { 0xCC, 0x7B, 0x13, 0xFF, 0xCD, 0x2D, 0xDD, 0x51 }), default, default); 540 | var systemObjectTypeRef = metadataBuilder.AddTypeReference(netstandardAssemblyRef, metadataBuilder.GetOrAddString("System"), metadataBuilder.GetOrAddString("Object")); 541 | 542 | var frozenObjectSerializerAssemblyRef = metadataBuilder.AddAssemblyReference( 543 | name: metadataBuilder.GetOrAddString("Microsoft.FrozenObjects"), 544 | version: new Version(1, 0, 0, 0), 545 | culture: default, 546 | publicKeyOrToken: default, 547 | flags: default, 548 | hashValue: default); 549 | 550 | var runtimeTypeHandleObjectRef = metadataBuilder.AddTypeReference( 551 | netstandardAssemblyRef, 552 | metadataBuilder.GetOrAddString("System"), 553 | metadataBuilder.GetOrAddString("RuntimeTypeHandle")); 554 | 555 | var deserializerRef = metadataBuilder.AddTypeReference( 556 | frozenObjectSerializerAssemblyRef, 557 | metadataBuilder.GetOrAddString("Microsoft.FrozenObjects"), 558 | metadataBuilder.GetOrAddString("Deserializer")); 559 | 560 | var ilBuilder = new BlobBuilder(); 561 | 562 | var frozenObjectDeserializerSignature = new BlobBuilder(); 563 | 564 | new BlobEncoder(frozenObjectDeserializerSignature). 565 | MethodSignature(). 566 | Parameters(2, 567 | returnType => returnType.Type().Object(), 568 | parameters => 569 | { 570 | parameters.AddParameter().Type().SZArray().Type(runtimeTypeHandleObjectRef, true); 571 | parameters.AddParameter().Type().String(); 572 | }); 573 | 574 | var deserializeMemberRef = metadataBuilder.AddMemberReference( 575 | deserializerRef, 576 | metadataBuilder.GetOrAddString("Deserialize"), 577 | metadataBuilder.GetOrAddBlob(frozenObjectDeserializerSignature)); 578 | 579 | var mainSignature = new BlobBuilder(); 580 | 581 | new BlobEncoder(mainSignature). 582 | MethodSignature(). 583 | Parameters(1, returnType => returnType.Type().Object(), parameters => 584 | { 585 | parameters.AddParameter().Type().String(); 586 | }); 587 | 588 | var codeBuilder = new BlobBuilder(); 589 | 590 | var il = new InstructionEncoder(codeBuilder); 591 | il.LoadConstantI4(typeTokenMap.Count); 592 | il.OpCode(ILOpCode.Newarr); 593 | il.Token(runtimeTypeHandleObjectRef); 594 | 595 | foreach (var entry in typeTokenMap) 596 | { 597 | il.OpCode(ILOpCode.Dup); 598 | il.LoadConstantI4(entry.Value); 599 | il.OpCode(ILOpCode.Ldtoken); 600 | il.Token(typeToTypeSpecMap[entry.Key]); 601 | il.OpCode(ILOpCode.Stelem); 602 | il.Token(runtimeTypeHandleObjectRef); 603 | } 604 | 605 | il.LoadArgument(0); 606 | il.OpCode(ILOpCode.Call); 607 | il.Token(deserializeMemberRef); 608 | il.OpCode(ILOpCode.Ret); 609 | 610 | var methodBodyStream = new MethodBodyStreamEncoder(ilBuilder); 611 | 612 | int mainBodyOffset = methodBodyStream.AddMethodBody(il); 613 | codeBuilder.Clear(); 614 | 615 | var mainMethodDef = metadataBuilder.AddMethodDefinition( 616 | MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig, 617 | MethodImplAttributes.IL | MethodImplAttributes.Managed, 618 | metadataBuilder.GetOrAddString(methodName), 619 | metadataBuilder.GetOrAddBlob(mainSignature), 620 | mainBodyOffset, 621 | parameterList: default); 622 | 623 | metadataBuilder.AddTypeDefinition( 624 | default, 625 | default, 626 | metadataBuilder.GetOrAddString(""), 627 | baseType: default, 628 | fieldList: MetadataTokens.FieldDefinitionHandle(1), 629 | methodList: mainMethodDef); 630 | 631 | metadataBuilder.AddTypeDefinition( 632 | TypeAttributes.Class | TypeAttributes.Public | TypeAttributes.Abstract | TypeAttributes.AutoLayout | TypeAttributes.AnsiClass | TypeAttributes.Sealed | TypeAttributes.BeforeFieldInit, 633 | metadataBuilder.GetOrAddString(outputNamespace), 634 | metadataBuilder.GetOrAddString(typeName), 635 | systemObjectTypeRef, 636 | fieldList: MetadataTokens.FieldDefinitionHandle(1), 637 | methodList: mainMethodDef); 638 | 639 | using (var fs = new FileStream(outputAssemblyFilePath, FileMode.Create, FileAccess.Write)) 640 | { 641 | WritePEImage(fs, metadataBuilder, ilBuilder, privateKeyOpt); 642 | } 643 | } 644 | 645 | private static MetadataBuilder CreateMetadataBuilder(string outputModuleName, string outputAssemblyName, Version version) 646 | { 647 | var metadataBuilder = new MetadataBuilder(); 648 | metadataBuilder.AddModule(0, metadataBuilder.GetOrAddString(outputModuleName), metadataBuilder.GetOrAddGuid(Guid.NewGuid()), default, default); 649 | metadataBuilder.AddAssembly(metadataBuilder.GetOrAddString(outputAssemblyName), version, default, default, default, AssemblyHashAlgorithm.Sha1); 650 | return metadataBuilder; 651 | } 652 | 653 | private static void WritePEImage(Stream peStream, MetadataBuilder metadataBuilder, BlobBuilder ilBuilder, byte[] privateKeyOpt, Blob mvidFixup = default) 654 | { 655 | var peBuilder = new ManagedPEBuilder(new PEHeaderBuilder(imageCharacteristics: Characteristics.ExecutableImage | Characteristics.Dll), new MetadataRootBuilder(metadataBuilder), ilBuilder, entryPoint: default, flags: CorFlags.ILOnly | (privateKeyOpt != null ? CorFlags.StrongNameSigned : 0), deterministicIdProvider: null); 656 | 657 | var peBlob = new BlobBuilder(); 658 | 659 | var contentId = peBuilder.Serialize(peBlob); 660 | 661 | if (!mvidFixup.IsDefault) 662 | { 663 | new BlobWriter(mvidFixup).WriteGuid(contentId.Guid); 664 | } 665 | 666 | peBlob.WriteContentTo(peStream); 667 | } 668 | 669 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 670 | private static unsafe TypeInfo GetObjectInfo(object obj, long mtToken) 671 | { 672 | var typeInfo = new TypeInfo { MTToken = mtToken }; 673 | var mt = Unsafe.Add(ref Unsafe.As(ref GetRawData(obj)), -1); 674 | uint flags = ((MethodTable*)mt)->Flags; 675 | 676 | bool containsPointersOrCollectible = (flags & 0x10000000) == 0x10000000 || (flags & 0x1000000) == 0x1000000; 677 | 678 | if (containsPointersOrCollectible) 679 | { 680 | int entries = *(int*)(mt - IntPtr.Size); 681 | if (entries < 0) 682 | { 683 | entries -= entries; 684 | } 685 | 686 | int slots = 1 + entries * 2; 687 | 688 | typeInfo.GCDescData = new IntPtr((byte*)mt - (slots * IntPtr.Size)); 689 | typeInfo.GCDescSize = slots * IntPtr.Size; 690 | } 691 | 692 | return typeInfo; 693 | } 694 | 695 | private static unsafe long GetObjectSize(object obj) 696 | { 697 | var mt = (MethodTable*)Unsafe.Add(ref Unsafe.As(ref GetRawData(obj)), -1); 698 | uint flags = mt->Flags; 699 | bool hasComponentSize = (flags & 0x80000000) == 0x80000000; 700 | 701 | long objectSize = mt->BaseSize; 702 | 703 | if (hasComponentSize) 704 | { 705 | var numComponents = (long)Unsafe.As(ref GetRawData(obj)); 706 | objectSize += numComponents * mt->ComponentSize; 707 | } 708 | 709 | return objectSize; 710 | } 711 | 712 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 713 | private static ref byte GetRawData(object o) 714 | { 715 | return ref Unsafe.As(o).Data; 716 | } 717 | 718 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 719 | private static int Padding(long num, int align) 720 | { 721 | return (int)(0 - num & (align - 1)); 722 | } 723 | 724 | [StructLayout(LayoutKind.Explicit)] 725 | private struct MethodTable 726 | { 727 | [FieldOffset(0)] 728 | public ushort ComponentSize; 729 | 730 | [FieldOffset(0)] 731 | public uint Flags; 732 | 733 | [FieldOffset(4)] 734 | public uint BaseSize; 735 | } 736 | 737 | private readonly unsafe ref struct GCDesc 738 | { 739 | private readonly IntPtr data; 740 | 741 | private readonly int size; 742 | 743 | public GCDesc(IntPtr data, int size) 744 | { 745 | this.data = data; 746 | this.size = size; 747 | } 748 | 749 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 750 | public int GetNumSeries() 751 | { 752 | return (int)Marshal.ReadIntPtr(this.data + this.size - IntPtr.Size); 753 | } 754 | 755 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 756 | public int GetHighestSeries() 757 | { 758 | return this.size - IntPtr.Size * 3; 759 | } 760 | 761 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 762 | public int GetLowestSeries() 763 | { 764 | return this.size - ComputeSize(this.GetNumSeries()); 765 | } 766 | 767 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 768 | public int GetSeriesSize(int curr) 769 | { 770 | return (int)Marshal.ReadIntPtr(this.data + curr); 771 | } 772 | 773 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 774 | public ulong GetSeriesOffset(int curr) 775 | { 776 | return (ulong)Marshal.ReadIntPtr(this.data + curr + IntPtr.Size); 777 | } 778 | 779 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 780 | public uint GetPointers(int curr, int i) 781 | { 782 | int offset = i * IntPtr.Size; 783 | return IntPtr.Size == 8 ? (uint)Marshal.ReadInt32(this.data + curr + offset) : (uint)Marshal.ReadInt16(this.data + curr + offset); 784 | } 785 | 786 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 787 | public uint GetSkip(int curr, int i) 788 | { 789 | int offset = i * IntPtr.Size + IntPtr.Size / 2; 790 | return IntPtr.Size == 8 ? (uint)Marshal.ReadInt32(this.data + curr + offset) : (uint)Marshal.ReadInt16(this.data + curr + offset); 791 | } 792 | 793 | public void EnumerateObject(byte* o, long objectSize, Dictionary serializedObjectMap, Queue objectQueue, Stream stream, long objectHeaderStart, ref long lastReservedObjectEnd) 794 | { 795 | int series = this.GetNumSeries(); 796 | int highest = this.GetHighestSeries(); 797 | int curr = highest; 798 | 799 | if (series > 0) 800 | { 801 | int lowest = this.GetLowestSeries(); 802 | do 803 | { 804 | ulong offset = this.GetSeriesOffset(curr); 805 | ulong stop = offset + (ulong)(this.GetSeriesSize(curr) + objectSize); 806 | 807 | while (offset < stop) 808 | { 809 | EachObjectReference(o, (int)offset, serializedObjectMap, objectQueue, stream, objectHeaderStart, ref lastReservedObjectEnd); 810 | offset += (uint)IntPtr.Size; 811 | } 812 | 813 | curr -= IntPtr.Size * 2; 814 | } while (curr >= lowest); 815 | } 816 | else 817 | { 818 | ulong offset = this.GetSeriesOffset(curr); 819 | while (offset < (ulong)(objectSize - IntPtr.Size)) 820 | { 821 | for (int i = 0; i > series; i--) 822 | { 823 | uint nptrs = this.GetPointers(curr, i); 824 | uint skip = this.GetSkip(curr, i); 825 | 826 | ulong stop = offset + nptrs * (ulong)IntPtr.Size; 827 | do 828 | { 829 | EachObjectReference(o, (int)offset, serializedObjectMap, objectQueue, stream, objectHeaderStart, ref lastReservedObjectEnd); 830 | offset += (ulong)IntPtr.Size; 831 | } while (offset < stop); 832 | 833 | offset += skip; 834 | } 835 | } 836 | } 837 | } 838 | 839 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 840 | private static int ComputeSize(int series) 841 | { 842 | return IntPtr.Size + series * IntPtr.Size * 2; 843 | } 844 | 845 | [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] 846 | private static void EachObjectReference(byte* o, int fieldOffset, Dictionary serializedObjectMap, Queue objectQueue, Stream stream, long objectHeaderStart, ref long lastReservedObjectEnd) 847 | { 848 | ref var objectReference = ref Unsafe.AsRef(o + fieldOffset - IntPtr.Size); 849 | if (objectReference == null) 850 | { 851 | return; 852 | } 853 | 854 | if (!serializedObjectMap.TryGetValue(objectReference, out var objectReferenceDiskOffset)) 855 | { 856 | objectReferenceDiskOffset = lastReservedObjectEnd + IntPtr.Size; // + IntPtr.Size because object references point to the MT* 857 | var objectSize = GetObjectSize(objectReference); 858 | lastReservedObjectEnd += objectSize + Padding(objectSize, IntPtr.Size); 859 | 860 | serializedObjectMap.Add(objectReference, objectReferenceDiskOffset); 861 | objectQueue.Enqueue(objectReference); 862 | } 863 | 864 | stream.Seek(objectHeaderStart + IntPtr.Size + fieldOffset, SeekOrigin.Begin); 865 | stream.Write(new ReadOnlySpan(&objectReferenceDiskOffset, IntPtr.Size)); 866 | } 867 | } 868 | 869 | private class RawData 870 | { 871 | public byte Data; 872 | } 873 | 874 | private class TypeInfo 875 | { 876 | public long MTToken; 877 | 878 | public IntPtr GCDescData; 879 | 880 | public int GCDescSize; 881 | } 882 | 883 | private class RuntimeTypeHandleDictionary 884 | { 885 | private int count; // 0-based index into this.entries of head of free chain: -1 means empty 886 | 887 | private int freeList = -1; // 1-based index into this.entries; 0 means empty 888 | 889 | private int[] buckets; 890 | 891 | private Entry[] entries; 892 | 893 | public Entry[] Entries 894 | { 895 | get { return this.entries; } 896 | } 897 | 898 | public int[] Buckets 899 | { 900 | get { return this.buckets; } 901 | } 902 | 903 | public struct Entry 904 | { 905 | public RuntimeTypeHandle key; 906 | 907 | public TypeInfo value; 908 | 909 | public int next; 910 | } 911 | 912 | public RuntimeTypeHandleDictionary(int capacity) 913 | { 914 | if (capacity < 2) 915 | { 916 | capacity = 2; 917 | } 918 | 919 | int PowerOf2(int v) 920 | { 921 | if ((v & (v - 1)) == 0) 922 | { 923 | return v; 924 | } 925 | 926 | int i = 2; 927 | 928 | while (i < v) 929 | { 930 | i <<= 1; 931 | } 932 | 933 | return i; 934 | } 935 | 936 | capacity = PowerOf2(capacity); 937 | this.buckets = new int[capacity]; 938 | this.entries = new Entry[capacity]; 939 | } 940 | 941 | public int Count => this.count; 942 | 943 | public ref TypeInfo GetOrAddValueRef(RuntimeTypeHandle key) 944 | { 945 | Entry[] e = this.entries; 946 | 947 | int bucketIndex = key.GetHashCode() & (this.buckets.Length - 1); 948 | for (int i = this.buckets[bucketIndex] - 1; (uint)i < (uint)e.Length; i = e[i].next) 949 | { 950 | if (key.Value == e[i].key.Value) 951 | { 952 | return ref e[i].value; 953 | } 954 | } 955 | 956 | return ref AddKey(key, bucketIndex); 957 | } 958 | 959 | [MethodImpl(MethodImplOptions.NoInlining)] 960 | private ref TypeInfo AddKey(RuntimeTypeHandle key, int bucketIndex) 961 | { 962 | Entry[] e = this.entries; 963 | int entryIndex; 964 | if (this.freeList != -1) 965 | { 966 | entryIndex = this.freeList; 967 | this.freeList = -3 - e[this.freeList].next; 968 | } 969 | else 970 | { 971 | if (this.count == e.Length || e.Length == 1) 972 | { 973 | e = Resize(); 974 | bucketIndex = key.GetHashCode() & (this.buckets.Length - 1); 975 | // entry indexes were not changed by Resize 976 | } 977 | entryIndex = this.count; 978 | } 979 | 980 | e[entryIndex].key = key; 981 | e[entryIndex].next = this.buckets[bucketIndex] - 1; 982 | this.buckets[bucketIndex] = entryIndex + 1; 983 | this.count++; 984 | return ref e[entryIndex].value; 985 | } 986 | 987 | private Entry[] Resize() 988 | { 989 | int c = this.count; 990 | int newSize = this.entries.Length * 2; 991 | if ((uint)newSize > (uint)int.MaxValue) // uint cast handles overflow 992 | { 993 | throw new InvalidOperationException("Overflow"); 994 | } 995 | 996 | var e = new Entry[newSize]; 997 | Array.Copy(this.entries, 0, e, 0, c); 998 | 999 | var newBuckets = new int[e.Length]; 1000 | while (c-- > 0) 1001 | { 1002 | int bucketIndex = e[c].key.GetHashCode() & (newBuckets.Length - 1); 1003 | e[c].next = newBuckets[bucketIndex] - 1; 1004 | newBuckets[bucketIndex] = c + 1; 1005 | } 1006 | 1007 | this.buckets = newBuckets; 1008 | this.entries = e; 1009 | 1010 | return e; 1011 | } 1012 | 1013 | public Enumerator GetEnumerator() => new Enumerator(this); 1014 | 1015 | public struct Enumerator : IEnumerator> 1016 | { 1017 | private readonly RuntimeTypeHandleDictionary dictionary; 1018 | 1019 | private int index; 1020 | 1021 | private int count; 1022 | 1023 | private KeyValuePair current; 1024 | 1025 | internal Enumerator(RuntimeTypeHandleDictionary dictionary) 1026 | { 1027 | this.dictionary = dictionary; 1028 | this.index = 0; 1029 | this.count = this.dictionary.count; 1030 | this.current = default; 1031 | } 1032 | 1033 | public bool MoveNext() 1034 | { 1035 | if (this.count == 0) 1036 | { 1037 | this.current = default; 1038 | return false; 1039 | } 1040 | 1041 | this.count--; 1042 | 1043 | while (this.dictionary.entries[this.index].next < -1) 1044 | { 1045 | this.index++; 1046 | } 1047 | 1048 | this.current = new KeyValuePair(this.dictionary.entries[this.index].key, this.dictionary.entries[this.index++].value); 1049 | return true; 1050 | } 1051 | 1052 | public KeyValuePair Current => this.current; 1053 | 1054 | object IEnumerator.Current => this.current; 1055 | 1056 | void IEnumerator.Reset() 1057 | { 1058 | this.index = 0; 1059 | this.count = this.dictionary.count; 1060 | this.current = default; 1061 | } 1062 | 1063 | public void Dispose() 1064 | { 1065 | } 1066 | } 1067 | } 1068 | internal sealed class ResizableMemoryStream : Stream 1069 | { 1070 | private unsafe byte* nativeMemory; 1071 | 1072 | private long position; 1073 | 1074 | private long length; 1075 | 1076 | private long capacity; 1077 | 1078 | public override bool CanRead => throw new NotSupportedException(); 1079 | 1080 | public override bool CanSeek => throw new NotSupportedException(); 1081 | 1082 | public override bool CanWrite => throw new NotSupportedException(); 1083 | 1084 | public override long Length => throw new NotSupportedException(); 1085 | 1086 | public override long Position 1087 | { 1088 | get => throw new NotSupportedException(); 1089 | set => throw new NotSupportedException(); 1090 | } 1091 | 1092 | public override void CopyTo(Stream destination, int bufferSize) 1093 | { 1094 | if (this.position > 0) 1095 | { 1096 | throw new ArgumentException("Only supports CopyTo when position is 0"); 1097 | } 1098 | 1099 | this.position = this.length; 1100 | 1101 | long bytesCopied = 0; 1102 | while (bytesCopied < this.length) 1103 | { 1104 | unsafe 1105 | { 1106 | var bytesToCopy = (int)Math.Min(this.length - bytesCopied, bufferSize); 1107 | var span = new ReadOnlySpan(this.nativeMemory + bytesCopied, bytesToCopy); 1108 | destination.Write(span); 1109 | bytesCopied += bytesToCopy; 1110 | } 1111 | } 1112 | } 1113 | 1114 | public override long Seek(long offset, SeekOrigin loc) 1115 | { 1116 | if (loc != SeekOrigin.Begin) 1117 | { 1118 | throw new ArgumentException(nameof(loc)); 1119 | } 1120 | 1121 | this.position = offset; 1122 | 1123 | return this.position; 1124 | } 1125 | 1126 | public override void Write(ReadOnlySpan buffer) 1127 | { 1128 | long i = this.position + buffer.Length; 1129 | 1130 | if (i > this.length) 1131 | { 1132 | if (i > this.capacity) 1133 | { 1134 | this.EnsureCapacity(i); 1135 | } 1136 | 1137 | this.length = i; 1138 | } 1139 | 1140 | unsafe 1141 | { 1142 | fixed (byte* ptr = &buffer.GetPinnableReference()) 1143 | { 1144 | Buffer.MemoryCopy(ptr, this.nativeMemory + this.position, this.capacity, buffer.Length); 1145 | } 1146 | } 1147 | 1148 | this.position = i; 1149 | } 1150 | 1151 | public override void Close() 1152 | { 1153 | unsafe 1154 | { 1155 | FreeMemory((IntPtr)this.nativeMemory, (IntPtr)this.capacity); 1156 | } 1157 | } 1158 | 1159 | private void EnsureCapacity(long value) 1160 | { 1161 | if (value > this.capacity) 1162 | { 1163 | long newCapacity = Math.Max(value, 64 * 1024); 1164 | 1165 | if (newCapacity < this.capacity * 2) 1166 | { 1167 | newCapacity = this.capacity * 2; 1168 | } 1169 | 1170 | this.SetCapacity(newCapacity); 1171 | } 1172 | } 1173 | 1174 | private unsafe void SetCapacity(long value) 1175 | { 1176 | if (value < this.length) 1177 | { 1178 | throw new ArgumentOutOfRangeException(nameof(value)); 1179 | } 1180 | 1181 | var source = this.nativeMemory; 1182 | 1183 | if (value != this.capacity) 1184 | { 1185 | if (value > 0) 1186 | { 1187 | byte* buf = (byte*)0; 1188 | try 1189 | { 1190 | buf = AllocateMemory(value); 1191 | if (this.length > 0) 1192 | { 1193 | Buffer.MemoryCopy(source, buf, value, this.length); 1194 | } 1195 | } 1196 | catch (Exception) 1197 | { 1198 | if (buf != (byte*)0) 1199 | { 1200 | FreeMemory((IntPtr)buf, (IntPtr)value); 1201 | } 1202 | 1203 | throw; 1204 | } 1205 | 1206 | this.nativeMemory = buf; 1207 | } 1208 | else 1209 | { 1210 | this.nativeMemory = (byte*)0; 1211 | } 1212 | 1213 | if (source != (byte*)0) 1214 | { 1215 | FreeMemory((IntPtr)source, (IntPtr)this.capacity); 1216 | } 1217 | 1218 | this.capacity = value; 1219 | } 1220 | } 1221 | 1222 | public override void Flush() => throw new NotSupportedException(); 1223 | 1224 | public override int Read(byte[] buffer, int offset, int count) => throw new NotSupportedException(); 1225 | 1226 | public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException(); 1227 | 1228 | public override void WriteByte(byte value) => throw new NotSupportedException(); 1229 | 1230 | public override int ReadByte() => throw new NotSupportedException(); 1231 | 1232 | public override int Read(Span buffer) => throw new NotSupportedException(); 1233 | 1234 | public override void SetLength(long value) => throw new NotSupportedException(); 1235 | } 1236 | } 1237 | } -------------------------------------------------------------------------------- /tests/ComplexTypes.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | namespace Microsoft.FrozenObjects.UnitTests 5 | { 6 | using System.Collections.Generic; 7 | using Microsoft.Collections.Extensions; 8 | 9 | public enum LongEnum : long 10 | { 11 | Min = long.MinValue, 12 | Max = long.MaxValue 13 | } 14 | 15 | public enum IntEnum : int 16 | { 17 | Min = int.MinValue, 18 | Max = int.MaxValue 19 | } 20 | 21 | public enum UIntEnum : uint 22 | { 23 | Min = uint.MinValue, 24 | Max = uint.MaxValue 25 | } 26 | 27 | public class OuterStruct 28 | { 29 | public struct GenericValueTypeWithReferences 30 | { 31 | public string A; 32 | public byte B; 33 | public T C; 34 | public T[] D; 35 | public string E; 36 | } 37 | } 38 | 39 | public class GenericBaseClassForThings 40 | { 41 | public List BaseA; 42 | public int BaseB; 43 | public LongEnum BaseC; 44 | public string BaseD; 45 | } 46 | 47 | public class OuterClass 48 | { 49 | public struct FooStruct 50 | { 51 | public class GenericReferenceTypeWithInheritance : GenericBaseClassForThings 52 | { 53 | public T A; 54 | public K[] B; 55 | public V[] C; 56 | public V D; 57 | public X Y; 58 | public Recursive R; 59 | public Circular S; 60 | } 61 | } 62 | } 63 | 64 | public class Recursive 65 | { 66 | public Recursive Field; 67 | 68 | public string Data; 69 | } 70 | 71 | public class Bar 72 | { 73 | public Circular Foo; 74 | } 75 | 76 | public class Circular 77 | { 78 | public Bar Foo; 79 | } 80 | } -------------------------------------------------------------------------------- /tests/Microsoft.FrozenObjects.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.1 5 | true 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /tests/SupportCode/DictionarySlim.cs: -------------------------------------------------------------------------------- 1 | // Licensed to the .NET Foundation under one or more agreements. 2 | // The .NET Foundation licenses this file to you under the MIT license. 3 | // See the LICENSE file in the project root for more information. 4 | 5 | using System; 6 | using System.Collections; 7 | using System.Collections.Generic; 8 | using System.Diagnostics; 9 | using System.Runtime.CompilerServices; 10 | 11 | namespace Microsoft.Collections.Extensions 12 | { 13 | public class DictionarySlim : IReadOnlyCollection> 14 | { 15 | // We want to initialize without allocating arrays. We also want to avoid null checks. 16 | // Array.Empty would give divide by zero in modulo operation. So we use static one element arrays. 17 | // The first add will cause a resize replacing these with real arrays of three elements. 18 | // Arrays are wrapped in a class to avoid being duplicated for each 19 | private static readonly Entry[] InitialEntries = new Entry[1]; 20 | private int _count; 21 | // 0-based index into _entries of head of free chain: -1 means empty 22 | private int _freeList = -1; 23 | // 1-based index into _entries; 0 means empty 24 | private int[] _buckets; 25 | private Entry[] _entries; 26 | 27 | public Entry[] Entries 28 | { 29 | get { return this._entries; } 30 | } 31 | 32 | public int[] Buckets 33 | { 34 | get { return this._buckets; } 35 | } 36 | 37 | public struct Entry 38 | { 39 | public string key; 40 | public string value; 41 | // 0-based index of next entry in chain: -1 means end of chain 42 | // also encodes whether this entry _itself_ is part of the free list by changing sign and subtracting 3, 43 | // so -2 means end of free list, -3 means index 0 but on free list, -4 means index 1 but on free list, etc. 44 | public int next; 45 | } 46 | 47 | /// 48 | /// Construct with default capacity. 49 | /// 50 | public DictionarySlim() 51 | { 52 | _buckets = HashHelpers.SizeOneIntArray; 53 | _entries = InitialEntries; 54 | } 55 | 56 | /// 57 | /// Construct with at least the specified capacity for 58 | /// entries before resizing must occur. 59 | /// 60 | /// Requested minimum capacity 61 | public DictionarySlim(int capacity) 62 | { 63 | if (capacity < 2) 64 | capacity = 2; // 1 would indicate the dummy array 65 | capacity = HashHelpers.PowerOf2(capacity); 66 | _buckets = new int[capacity]; 67 | _entries = new Entry[capacity]; 68 | } 69 | 70 | /// 71 | /// Count of entries in the dictionary. 72 | /// 73 | public int Count => _count; 74 | 75 | /// 76 | /// Clears the dictionary. Note that this invalidates any active enumerators. 77 | /// 78 | public void Clear() 79 | { 80 | _count = 0; 81 | _freeList = -1; 82 | _buckets = HashHelpers.SizeOneIntArray; 83 | _entries = InitialEntries; 84 | } 85 | 86 | // Not safe for concurrent _reads_ (at least, if either of them add) 87 | // For concurrent reads, prefer TryGetValue(key, out value) 88 | /// 89 | /// Gets the value for the specified key, or, if the key is not present, 90 | /// adds an entry and returns the value by ref. This makes it possible to 91 | /// add or update a value in a single look up operation. 92 | /// 93 | /// Key to look for 94 | /// Reference to the new or existing value 95 | public ref string GetOrAddValueRef(string key) 96 | { 97 | Entry[] entries = _entries; 98 | int bucketIndex = GetHashCodeCompat(key) & (_buckets.Length - 1); 99 | for (int i = _buckets[bucketIndex] - 1; 100 | (uint)i < (uint)entries.Length; i = entries[i].next) 101 | { 102 | if (StringComparer.OrdinalIgnoreCase.Compare(key, entries[i].key) == 0) 103 | return ref entries[i].value; 104 | } 105 | 106 | return ref AddKey(key, bucketIndex); 107 | } 108 | 109 | [MethodImpl(MethodImplOptions.NoInlining)] 110 | private ref string AddKey(string key, int bucketIndex) 111 | { 112 | Entry[] entries = _entries; 113 | int entryIndex; 114 | if (_freeList != -1) 115 | { 116 | entryIndex = _freeList; 117 | _freeList = -3 - entries[_freeList].next; 118 | } 119 | else 120 | { 121 | if (_count == entries.Length || entries.Length == 1) 122 | { 123 | entries = Resize(); 124 | bucketIndex = GetHashCodeCompat(key) & (_buckets.Length - 1); 125 | // entry indexes were not changed by Resize 126 | } 127 | entryIndex = _count; 128 | } 129 | 130 | entries[entryIndex].key = key; 131 | entries[entryIndex].next = _buckets[bucketIndex] - 1; 132 | _buckets[bucketIndex] = entryIndex + 1; 133 | _count++; 134 | return ref entries[entryIndex].value; 135 | } 136 | 137 | private Entry[] Resize() 138 | { 139 | Debug.Assert(_entries.Length == _count || _entries.Length == 1); // We only copy _count, so if it's longer we will miss some 140 | int count = _count; 141 | int newSize = _entries.Length * 2; 142 | if ((uint)newSize > (uint)int.MaxValue) // uint cast handles overflow 143 | throw new InvalidOperationException("Overflow"); 144 | 145 | var entries = new Entry[newSize]; 146 | Array.Copy(_entries, 0, entries, 0, count); 147 | 148 | var newBuckets = new int[entries.Length]; 149 | while (count-- > 0) 150 | { 151 | int bucketIndex = GetHashCodeCompat(entries[count].key) & (newBuckets.Length - 1); 152 | entries[count].next = newBuckets[bucketIndex] - 1; 153 | newBuckets[bucketIndex] = count + 1; 154 | } 155 | 156 | _buckets = newBuckets; 157 | _entries = entries; 158 | 159 | return entries; 160 | } 161 | 162 | /// 163 | /// Gets an enumerator over the dictionary 164 | /// 165 | public Enumerator GetEnumerator() => new Enumerator(this); // avoid boxing 166 | 167 | /// 168 | /// Gets an enumerator over the dictionary 169 | /// 170 | IEnumerator> IEnumerable>.GetEnumerator() => 171 | new Enumerator(this); 172 | 173 | /// 174 | /// Gets an enumerator over the dictionary 175 | /// 176 | IEnumerator IEnumerable.GetEnumerator() => new Enumerator(this); 177 | 178 | /// 179 | /// Enumerator 180 | /// 181 | public struct Enumerator : IEnumerator> 182 | { 183 | private readonly DictionarySlim _dictionary; 184 | private int _index; 185 | private int _count; 186 | private KeyValuePair _current; 187 | 188 | internal Enumerator(DictionarySlim dictionary) 189 | { 190 | _dictionary = dictionary; 191 | _index = 0; 192 | _count = _dictionary._count; 193 | _current = default; 194 | } 195 | 196 | /// 197 | /// Move to next 198 | /// 199 | public bool MoveNext() 200 | { 201 | if (_count == 0) 202 | { 203 | _current = default; 204 | return false; 205 | } 206 | 207 | _count--; 208 | 209 | while (_dictionary._entries[_index].next < -1) 210 | _index++; 211 | 212 | _current = new KeyValuePair( 213 | _dictionary._entries[_index].key, 214 | _dictionary._entries[_index++].value); 215 | return true; 216 | } 217 | 218 | /// 219 | /// Get current value 220 | /// 221 | public KeyValuePair Current => _current; 222 | 223 | object IEnumerator.Current => _current; 224 | 225 | void IEnumerator.Reset() 226 | { 227 | _index = 0; 228 | _count = _dictionary._count; 229 | _current = default; 230 | } 231 | 232 | /// 233 | /// Dispose the enumerator 234 | /// 235 | public void Dispose() { } 236 | } 237 | 238 | [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] 239 | private static unsafe int GetHashCodeCompat(string content) 240 | { 241 | fixed (char* c = content) 242 | { 243 | return Marvin.ComputeHash32OrdinalIgnoreCase(ref *c, content.Length, 0xDEAD, 0xBEEF); 244 | } 245 | } 246 | } 247 | } -------------------------------------------------------------------------------- /tests/SupportCode/HashHelpers.cs: -------------------------------------------------------------------------------- 1 | // Licensed to the .NET Foundation under one or more agreements. 2 | // The .NET Foundation licenses this file to you under the MIT license. 3 | // See the LICENSE file in the project root for more information. 4 | 5 | using System; 6 | using System.Diagnostics; 7 | 8 | namespace Microsoft.Collections.Extensions 9 | { 10 | internal static partial class HashHelpers 11 | { 12 | internal static int PowerOf2(int v) 13 | { 14 | if ((v & (v - 1)) == 0) return v; 15 | int i = 2; 16 | while (i < v) i <<= 1; 17 | return i; 18 | } 19 | 20 | // must never be written to 21 | internal static readonly int[] SizeOneIntArray = new int[1]; 22 | 23 | public const int HashCollisionThreshold = 100; 24 | 25 | // This is the maximum prime smaller than Array.MaxArrayLength 26 | public const int MaxPrimeArrayLength = 0x7FEFFFFD; 27 | 28 | public const int HashPrime = 101; 29 | 30 | // Table of prime numbers to use as hash table sizes. 31 | // A typical resize algorithm would pick the smallest prime number in this array 32 | // that is larger than twice the previous capacity. 33 | // Suppose our Hashtable currently has capacity x and enough elements are added 34 | // such that a resize needs to occur. Resizing first computes 2x then finds the 35 | // first prime in the table greater than 2x, i.e. if primes are ordered 36 | // p_1, p_2, ..., p_i, ..., it finds p_n such that p_n-1 < 2x < p_n. 37 | // Doubling is important for preserving the asymptotic complexity of the 38 | // hashtable operations such as add. Having a prime guarantees that double 39 | // hashing does not lead to infinite loops. IE, your hash function will be 40 | // h1(key) + i*h2(key), 0 <= i < size. h2 and the size must be relatively prime. 41 | // We prefer the low computation costs of higher prime numbers over the increased 42 | // memory allocation of a fixed prime number i.e. when right sizing a HashSet. 43 | public static readonly int[] primes = { 44 | 3, 7, 11, 17, 23, 29, 37, 47, 59, 71, 89, 107, 131, 163, 197, 239, 293, 353, 431, 521, 631, 761, 919, 45 | 1103, 1327, 1597, 1931, 2333, 2801, 3371, 4049, 4861, 5839, 7013, 8419, 10103, 12143, 14591, 46 | 17519, 21023, 25229, 30293, 36353, 43627, 52361, 62851, 75431, 90523, 108631, 130363, 156437, 47 | 187751, 225307, 270371, 324449, 389357, 467237, 560689, 672827, 807403, 968897, 1162687, 1395263, 48 | 1674319, 2009191, 2411033, 2893249, 3471899, 4166287, 4999559, 5999471, 7199369 }; 49 | 50 | public static bool IsPrime(int candidate) 51 | { 52 | if ((candidate & 1) != 0) 53 | { 54 | int limit = (int)Math.Sqrt(candidate); 55 | for (int divisor = 3; divisor <= limit; divisor += 2) 56 | { 57 | if ((candidate % divisor) == 0) 58 | return false; 59 | } 60 | return true; 61 | } 62 | return (candidate == 2); 63 | } 64 | 65 | public static int GetPrime(int min) 66 | { 67 | if (min < 0) 68 | throw new ArgumentException("Hashtable's capacity overflowed and went negative. Check load factor, capacity and the current size of the table."); 69 | 70 | for (int i = 0; i < primes.Length; i++) 71 | { 72 | int prime = primes[i]; 73 | if (prime >= min) 74 | return prime; 75 | } 76 | 77 | //outside of our predefined table. 78 | //compute the hard way. 79 | for (int i = (min | 1); i < int.MaxValue; i += 2) 80 | { 81 | if (IsPrime(i) && ((i - 1) % HashPrime != 0)) 82 | return i; 83 | } 84 | return min; 85 | } 86 | 87 | // Returns size of hashtable to grow to. 88 | public static int ExpandPrime(int oldSize) 89 | { 90 | int newSize = 2 * oldSize; 91 | 92 | // Allow the hashtables to grow to maximum possible size (~2G elements) before encountering capacity overflow. 93 | // Note that this check works even when _items.Length overflowed thanks to the (uint) cast 94 | if ((uint)newSize > MaxPrimeArrayLength && MaxPrimeArrayLength > oldSize) 95 | { 96 | Debug.Assert(MaxPrimeArrayLength == GetPrime(MaxPrimeArrayLength), "Invalid MaxPrimeArrayLength"); 97 | return MaxPrimeArrayLength; 98 | } 99 | 100 | return GetPrime(newSize); 101 | } 102 | } 103 | } -------------------------------------------------------------------------------- /tests/SupportCode/Marvin.OrdinalIgnoreCase.cs: -------------------------------------------------------------------------------- 1 | // Licensed to the .NET Foundation under one or more agreements. 2 | // The .NET Foundation licenses this file to you under the MIT license. 3 | // See the LICENSE file in the project root for more information. 4 | 5 | using System.Buffers; 6 | using System.Diagnostics; 7 | using System.Runtime.InteropServices; 8 | using System.Text; 9 | using System.Runtime.CompilerServices; 10 | 11 | namespace System 12 | { 13 | internal static partial class Marvin 14 | { 15 | /// 16 | /// Compute a Marvin OrdinalIgnoreCase hash and collapse it into a 32-bit hash. 17 | /// n.b. is specified as char count, not byte count. 18 | /// 19 | public static int ComputeHash32OrdinalIgnoreCase(ref char data, int count, uint p0, uint p1) 20 | { 21 | uint ucount = (uint)count; // in chars 22 | IntPtr byteOffset = IntPtr.Zero; // in bytes 23 | uint tempValue; 24 | 25 | // We operate on 32-bit integers (two chars) at a time. 26 | 27 | while (ucount >= 2) 28 | { 29 | tempValue = Unsafe.ReadUnaligned(ref Unsafe.As(ref Unsafe.AddByteOffset(ref data, byteOffset))); 30 | if (!Utf16Utility.AllCharsInUInt32AreAscii(tempValue)) 31 | { 32 | goto NotAscii; 33 | } 34 | p0 += Utf16Utility.ConvertAllAsciiCharsInUInt32ToUppercase(tempValue); 35 | Block(ref p0, ref p1); 36 | 37 | byteOffset += 4; 38 | ucount -= 2; 39 | } 40 | 41 | // We have either one char (16 bits) or zero chars left over. 42 | Debug.Assert(ucount < 2); 43 | 44 | if (ucount > 0) 45 | { 46 | tempValue = Unsafe.AddByteOffset(ref data, byteOffset); 47 | if (tempValue > 0x7Fu) 48 | { 49 | goto NotAscii; 50 | } 51 | 52 | // addition is written with -0x80u to allow fall-through to next statement rather than jmp past it 53 | p0 += Utf16Utility.ConvertAllAsciiCharsInUInt32ToUppercase(tempValue) + (0x800000u - 0x80u); 54 | } 55 | p0 += 0x80u; 56 | 57 | Block(ref p0, ref p1); 58 | Block(ref p0, ref p1); 59 | 60 | return (int)(p1 ^ p0); 61 | 62 | NotAscii: 63 | Debug.Assert(0 <= ucount && ucount <= Int32.MaxValue); // this should fit into a signed int 64 | return ComputeHash32OrdinalIgnoreCaseSlow(ref Unsafe.AddByteOffset(ref data, byteOffset), (int)ucount, p0, p1); 65 | } 66 | 67 | private static unsafe int ComputeHash32OrdinalIgnoreCaseSlow(ref char data, int count, uint p0, uint p1) 68 | { 69 | Debug.Assert(count > 0); 70 | 71 | char[] borrowedArr = null; 72 | Span scratch = (uint)count <= 64 ? stackalloc char[64] : (borrowedArr = ArrayPool.Shared.Rent(count)); 73 | 74 | int charsWritten = new ReadOnlySpan(Unsafe.AsPointer(ref data), count).ToUpperInvariant(scratch); 75 | Debug.Assert(charsWritten == count); // invariant case conversion should involve simple folding; preserve code unit count 76 | 77 | // Slice the array to the size returned by ToUpperInvariant. 78 | // Multiplication below may overflow, that's fine since it's going to an unsigned integer. 79 | int hash = ComputeHash32(ref Unsafe.As(ref MemoryMarshal.GetReference(scratch)), charsWritten * 2, p0, p1); 80 | 81 | // Return the borrowed array if necessary. 82 | if (borrowedArr != null) 83 | { 84 | ArrayPool.Shared.Return(borrowedArr); 85 | } 86 | 87 | return hash; 88 | } 89 | } 90 | } -------------------------------------------------------------------------------- /tests/SupportCode/Marvin.cs: -------------------------------------------------------------------------------- 1 | // Licensed to the .NET Foundation under one or more agreements. 2 | // The .NET Foundation licenses this file to you under the MIT license. 3 | // See the LICENSE file in the project root for more information. 4 | 5 | using System.Diagnostics; 6 | using System.Runtime.CompilerServices; 7 | using System.Runtime.InteropServices; 8 | 9 | namespace System 10 | { 11 | internal static partial class Marvin 12 | { 13 | /// 14 | /// Compute a Marvin hash and collapse it into a 32-bit hash. 15 | /// 16 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 17 | public static int ComputeHash32(ReadOnlySpan data, ulong seed) => ComputeHash32(ref MemoryMarshal.GetReference(data), data.Length, (uint)seed, (uint)(seed >> 32)); 18 | 19 | /// 20 | /// Compute a Marvin hash and collapse it into a 32-bit hash. 21 | /// 22 | public static int ComputeHash32(ref byte data, int count, uint p0, uint p1) 23 | { 24 | uint ucount = (uint)count; 25 | IntPtr byteOffset = IntPtr.Zero; 26 | 27 | while (ucount >= 8) 28 | { 29 | p0 += Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref data, byteOffset)); 30 | Block(ref p0, ref p1); 31 | 32 | p0 += Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref data, byteOffset + 4)); 33 | Block(ref p0, ref p1); 34 | 35 | byteOffset += 8; 36 | ucount -= 8; 37 | } 38 | 39 | switch (ucount) 40 | { 41 | case 4: 42 | p0 += Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref data, byteOffset)); 43 | Block(ref p0, ref p1); 44 | goto case 0; 45 | 46 | case 0: 47 | p0 += 0x80u; 48 | break; 49 | 50 | case 5: 51 | p0 += Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref data, byteOffset)); 52 | byteOffset += 4; 53 | Block(ref p0, ref p1); 54 | goto case 1; 55 | 56 | case 1: 57 | p0 += 0x8000u | Unsafe.AddByteOffset(ref data, byteOffset); 58 | break; 59 | 60 | case 6: 61 | p0 += Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref data, byteOffset)); 62 | byteOffset += 4; 63 | Block(ref p0, ref p1); 64 | goto case 2; 65 | 66 | case 2: 67 | p0 += 0x800000u | Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref data, byteOffset)); 68 | break; 69 | 70 | case 7: 71 | p0 += Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref data, byteOffset)); 72 | byteOffset += 4; 73 | Block(ref p0, ref p1); 74 | goto case 3; 75 | 76 | case 3: 77 | p0 += 0x80000000u | (((uint)(Unsafe.AddByteOffset(ref data, byteOffset + 2))) << 16) | (uint)(Unsafe.ReadUnaligned(ref Unsafe.AddByteOffset(ref data, byteOffset))); 78 | break; 79 | 80 | default: 81 | Debug.Fail("Should not get here."); 82 | break; 83 | } 84 | 85 | Block(ref p0, ref p1); 86 | Block(ref p0, ref p1); 87 | 88 | return (int)(p1 ^ p0); 89 | } 90 | 91 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 92 | private static void Block(ref uint rp0, ref uint rp1) 93 | { 94 | uint p0 = rp0; 95 | uint p1 = rp1; 96 | 97 | p1 ^= p0; 98 | p0 = _rotl(p0, 20); 99 | 100 | p0 += p1; 101 | p1 = _rotl(p1, 9); 102 | 103 | p1 ^= p0; 104 | p0 = _rotl(p0, 27); 105 | 106 | p0 += p1; 107 | p1 = _rotl(p1, 19); 108 | 109 | rp0 = p0; 110 | rp1 = p1; 111 | } 112 | 113 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 114 | private static uint _rotl(uint value, int shift) 115 | { 116 | // This is expected to be optimized into a single rol (or ror with negated shift value) instruction 117 | return (value << shift) | (value >> (32 - shift)); 118 | } 119 | } 120 | } -------------------------------------------------------------------------------- /tests/SupportCode/ReadOnlyDictionarySlim.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | namespace Microsoft.Collections.Extensions 5 | { 6 | using System; 7 | using System.Collections; 8 | using System.Collections.Generic; 9 | using System.Runtime.InteropServices; 10 | 11 | [StructLayout(LayoutKind.Sequential)] 12 | public sealed unsafe class ReadOnlyDictionarySlim : IReadOnlyCollection> 13 | { 14 | private readonly int[] _buckets; 15 | private readonly Entry[] _entries; 16 | 17 | internal struct Entry 18 | { 19 | public string key; 20 | public string value; 21 | public int next; 22 | } 23 | 24 | internal ReadOnlyDictionarySlim(IntPtr addr, int[] buckets, Entry[] entries) 25 | { 26 | this._buckets = buckets; 27 | this._entries = entries; 28 | } 29 | 30 | public int Count => this._entries.Length; 31 | 32 | public bool TryGetValue(string key, out string value) 33 | { 34 | var entries = this._entries; 35 | var buckets = this._buckets; 36 | 37 | for (int i = buckets[Marvin.ComputeHash32OrdinalIgnoreCase(ref MemoryMarshal.GetReference(key.AsSpan()), key.Length, 0xDEAD, 0xBEEF) & (buckets.Length - 1)] - 1; (uint)i < (uint)entries.Length; i = entries[i].next) 38 | { 39 | if (StringComparer.OrdinalIgnoreCase.Compare(key, entries[i].key) == 0) 40 | { 41 | value = entries[i].value; 42 | return true; 43 | } 44 | } 45 | 46 | value = default; 47 | return false; 48 | } 49 | 50 | public Enumerator GetEnumerator() => new Enumerator(this); // avoid boxing 51 | 52 | IEnumerator> IEnumerable>.GetEnumerator() => new Enumerator(this); 53 | 54 | IEnumerator IEnumerable.GetEnumerator() => new Enumerator(this); 55 | 56 | public struct Enumerator : IEnumerator> 57 | { 58 | private readonly ReadOnlyDictionarySlim _dictionary; 59 | private int _index; 60 | private int _count; 61 | private KeyValuePair _current; 62 | 63 | internal Enumerator(ReadOnlyDictionarySlim dictionary) 64 | { 65 | _dictionary = dictionary; 66 | _index = 0; 67 | _count = _dictionary._entries.Length; 68 | _current = default; 69 | } 70 | 71 | public bool MoveNext() 72 | { 73 | if (_count == 0) 74 | { 75 | _current = default; 76 | return false; 77 | } 78 | 79 | _count--; 80 | 81 | var entries = _dictionary._entries; 82 | 83 | while (entries[_index].next < -1) 84 | _index++; 85 | 86 | _current = new KeyValuePair(entries[_index].key, entries[_index].value); 87 | 88 | _index++; 89 | return true; 90 | } 91 | 92 | public KeyValuePair Current => _current; 93 | 94 | object IEnumerator.Current => _current; 95 | 96 | void IEnumerator.Reset() 97 | { 98 | _index = 0; 99 | _count = _dictionary._entries.Length; 100 | _current = default; 101 | } 102 | 103 | public void Dispose() 104 | { 105 | } 106 | } 107 | } 108 | } -------------------------------------------------------------------------------- /tests/SupportCode/Utf16Utility.cs: -------------------------------------------------------------------------------- 1 | // Licensed to the .NET Foundation under one or more agreements. 2 | // The .NET Foundation licenses this file to you under the MIT license. 3 | // See the LICENSE file in the project root for more information. 4 | 5 | using System.Runtime.CompilerServices; 6 | using System.Diagnostics; 7 | 8 | namespace System.Text 9 | { 10 | internal static partial class Utf16Utility 11 | { 12 | /// 13 | /// Returns true iff the UInt32 represents two ASCII UTF-16 characters in machine endianness. 14 | /// 15 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 16 | internal static bool AllCharsInUInt32AreAscii(uint value) 17 | { 18 | return (value & ~0x007F_007Fu) == 0; 19 | } 20 | 21 | /// 22 | /// Returns true iff the UInt64 represents four ASCII UTF-16 characters in machine endianness. 23 | /// 24 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 25 | internal static bool AllCharsInUInt64AreAscii(ulong value) 26 | { 27 | return (value & ~0x007F_007F_007F_007Ful) == 0; 28 | } 29 | 30 | /// 31 | /// Given a UInt32 that represents two ASCII UTF-16 characters, returns the invariant 32 | /// lowercase representation of those characters. Requires the input value to contain 33 | /// two ASCII UTF-16 characters in machine endianness. 34 | /// 35 | /// 36 | /// This is a branchless implementation. 37 | /// 38 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 39 | internal static uint ConvertAllAsciiCharsInUInt32ToLowercase(uint value) 40 | { 41 | // ASSUMPTION: Caller has validated that input value is ASCII. 42 | Debug.Assert(AllCharsInUInt32AreAscii(value)); 43 | 44 | // the 0x80 bit of each word of 'lowerIndicator' will be set iff the word has value >= 'A' 45 | uint lowerIndicator = value + 0x0080_0080u - 0x0041_0041u; 46 | 47 | // the 0x80 bit of each word of 'upperIndicator' will be set iff the word has value > 'Z' 48 | uint upperIndicator = value + 0x0080_0080u - 0x005B_005Bu; 49 | 50 | // the 0x80 bit of each word of 'combinedIndicator' will be set iff the word has value >= 'A' and <= 'Z' 51 | uint combinedIndicator = (lowerIndicator ^ upperIndicator); 52 | 53 | // the 0x20 bit of each word of 'mask' will be set iff the word has value >= 'A' and <= 'Z' 54 | uint mask = (combinedIndicator & 0x0080_0080u) >> 2; 55 | 56 | return value ^ mask; // bit flip uppercase letters [A-Z] => [a-z] 57 | } 58 | 59 | /// 60 | /// Given a UInt32 that represents two ASCII UTF-16 characters, returns the invariant 61 | /// uppercase representation of those characters. Requires the input value to contain 62 | /// two ASCII UTF-16 characters in machine endianness. 63 | /// 64 | /// 65 | /// This is a branchless implementation. 66 | /// 67 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 68 | internal static uint ConvertAllAsciiCharsInUInt32ToUppercase(uint value) 69 | { 70 | // ASSUMPTION: Caller has validated that input value is ASCII. 71 | Debug.Assert(AllCharsInUInt32AreAscii(value)); 72 | 73 | // the 0x80 bit of each word of 'lowerIndicator' will be set iff the word has value >= 'a' 74 | uint lowerIndicator = value + 0x0080_0080u - 0x0061_0061u; 75 | 76 | // the 0x80 bit of each word of 'upperIndicator' will be set iff the word has value > 'z' 77 | uint upperIndicator = value + 0x0080_0080u - 0x007B_007Bu; 78 | 79 | // the 0x80 bit of each word of 'combinedIndicator' will be set iff the word has value >= 'a' and <= 'z' 80 | uint combinedIndicator = (lowerIndicator ^ upperIndicator); 81 | 82 | // the 0x20 bit of each word of 'mask' will be set iff the word has value >= 'a' and <= 'z' 83 | uint mask = (combinedIndicator & 0x0080_0080u) >> 2; 84 | 85 | return value ^ mask; // bit flip lowercase letters [a-z] => [A-Z] 86 | } 87 | 88 | /// 89 | /// Given a UInt32 that represents two ASCII UTF-16 characters, returns true iff 90 | /// the input contains one or more lowercase ASCII characters. 91 | /// 92 | /// 93 | /// This is a branchless implementation. 94 | /// 95 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 96 | internal static bool UInt32ContainsAnyLowercaseAsciiChar(uint value) 97 | { 98 | // ASSUMPTION: Caller has validated that input value is ASCII. 99 | Debug.Assert(AllCharsInUInt32AreAscii(value)); 100 | 101 | // the 0x80 bit of each word of 'lowerIndicator' will be set iff the word has value >= 'a' 102 | uint lowerIndicator = value + 0x0080_0080u - 0x0061_0061u; 103 | 104 | // the 0x80 bit of each word of 'upperIndicator' will be set iff the word has value > 'z' 105 | uint upperIndicator = value + 0x0080_0080u - 0x007B_007Bu; 106 | 107 | // the 0x80 bit of each word of 'combinedIndicator' will be set iff the word has value >= 'a' and <= 'z' 108 | uint combinedIndicator = (lowerIndicator ^ upperIndicator); 109 | 110 | return (combinedIndicator & 0x0080_0080u) != 0; 111 | } 112 | 113 | /// 114 | /// Given a UInt32 that represents two ASCII UTF-16 characters, returns true iff 115 | /// the input contains one or more uppercase ASCII characters. 116 | /// 117 | /// 118 | /// This is a branchless implementation. 119 | /// 120 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 121 | internal static bool UInt32ContainsAnyUppercaseAsciiChar(uint value) 122 | { 123 | // ASSUMPTION: Caller has validated that input value is ASCII. 124 | Debug.Assert(AllCharsInUInt32AreAscii(value)); 125 | 126 | // the 0x80 bit of each word of 'lowerIndicator' will be set iff the word has value >= 'A' 127 | uint lowerIndicator = value + 0x0080_0080u - 0x0041_0041u; 128 | 129 | // the 0x80 bit of each word of 'upperIndicator' will be set iff the word has value > 'Z' 130 | uint upperIndicator = value + 0x0080_0080u - 0x005B_005Bu; 131 | 132 | // the 0x80 bit of each word of 'combinedIndicator' will be set iff the word has value >= 'A' and <= 'Z' 133 | uint combinedIndicator = (lowerIndicator ^ upperIndicator); 134 | 135 | return (combinedIndicator & 0x0080_0080u) != 0; 136 | } 137 | 138 | /// 139 | /// Given two UInt32s that represent two ASCII UTF-16 characters each, returns true iff 140 | /// the two inputs are equal using an ordinal case-insensitive comparison. 141 | /// 142 | /// 143 | /// This is a branchless implementation. 144 | /// 145 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 146 | internal static bool UInt32OrdinalIgnoreCaseAscii(uint valueA, uint valueB) 147 | { 148 | // ASSUMPTION: Caller has validated that input values are ASCII. 149 | Debug.Assert(AllCharsInUInt32AreAscii(valueA)); 150 | Debug.Assert(AllCharsInUInt32AreAscii(valueB)); 151 | 152 | // a mask of all bits which are different between A and B 153 | uint differentBits = valueA ^ valueB; 154 | 155 | // the 0x80 bit of each word of 'lowerIndicator' will be set iff the word has value < 'A' 156 | uint lowerIndicator = valueA + 0x0100_0100u - 0x0041_0041u; 157 | 158 | // the 0x80 bit of each word of 'upperIndicator' will be set iff (word | 0x20) has value > 'z' 159 | uint upperIndicator = (valueA | 0x0020_0020u) + 0x0080_0080u - 0x007B_007Bu; 160 | 161 | // the 0x80 bit of each word of 'combinedIndicator' will be set iff the word is *not* [A-Za-z] 162 | uint combinedIndicator = lowerIndicator | upperIndicator; 163 | 164 | // Shift all the 0x80 bits of 'combinedIndicator' into the 0x20 positions, then set all bits 165 | // aside from 0x20. This creates a mask where all bits are set *except* for the 0x20 bits 166 | // which correspond to alpha chars (either lower or upper). For these alpha chars only, the 167 | // 0x20 bit is allowed to differ between the two input values. Every other char must be an 168 | // exact bitwise match between the two input values. In other words, (valueA & mask) will 169 | // convert valueA to uppercase, so (valueA & mask) == (valueB & mask) answers "is the uppercase 170 | // form of valueA equal to the uppercase form of valueB?" (Technically if valueA has an alpha 171 | // char in the same position as a non-alpha char in valueB, or vice versa, this operation will 172 | // result in nonsense, but it'll still compute as inequal regardless, which is what we want ultimately.) 173 | // The line below is a more efficient way of doing the same check taking advantage of the XOR 174 | // computation we performed at the beginning of the method. 175 | 176 | return (((combinedIndicator >> 2) | ~0x0020_0020u) & differentBits) == 0; 177 | } 178 | 179 | /// 180 | /// Given two UInt64s that represent four ASCII UTF-16 characters each, returns true iff 181 | /// the two inputs are equal using an ordinal case-insensitive comparison. 182 | /// 183 | /// 184 | /// This is a branchless implementation. 185 | /// 186 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 187 | internal static bool UInt64OrdinalIgnoreCaseAscii(ulong valueA, ulong valueB) 188 | { 189 | // ASSUMPTION: Caller has validated that input values are ASCII. 190 | Debug.Assert(AllCharsInUInt64AreAscii(valueA)); 191 | Debug.Assert(AllCharsInUInt64AreAscii(valueB)); 192 | 193 | // the 0x80 bit of each word of 'lowerIndicator' will be set iff the word has value >= 'A' 194 | ulong lowerIndicator = valueA + 0x0080_0080_0080_0080ul - 0x0041_0041_0041_0041ul; 195 | 196 | // the 0x80 bit of each word of 'upperIndicator' will be set iff (word | 0x20) has value <= 'z' 197 | ulong upperIndicator = (valueA | 0x0020_0020_0020_0020ul) + 0x0100_0100_0100_0100ul - 0x007B_007B_007B_007Bul; 198 | 199 | // the 0x20 bit of each word of 'combinedIndicator' will be set iff the word is [A-Za-z] 200 | ulong combinedIndicator = (0x0080_0080_0080_0080ul & lowerIndicator & upperIndicator) >> 2; 201 | 202 | // Convert both values to lowercase (using the combined indicator from the first value) 203 | // and compare for equality. It's possible that the first value will contain an alpha character 204 | // where the second value doesn't (or vice versa), and applying the combined indicator will 205 | // create nonsensical data, but the comparison would have failed anyway in this case so it's 206 | // a safe operation to perform. 207 | // 208 | // This 64-bit method is similar to the 32-bit method, but it performs the equivalent of convert-to- 209 | // lowercase-then-compare rather than convert-to-uppercase-and-compare. This particular operation 210 | // happens to be faster on x64. 211 | 212 | return (valueA | combinedIndicator) == (valueB | combinedIndicator); 213 | } 214 | } 215 | } -------------------------------------------------------------------------------- /tests/UnitTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | namespace Microsoft.FrozenObjects.UnitTests 5 | { 6 | using System; 7 | using System.Collections.Generic; 8 | using System.IO; 9 | using System.Reflection; 10 | using Microsoft.Collections.Extensions; 11 | using Xunit; 12 | using static Deserializer; 13 | using static Serializer; 14 | 15 | public class UnitTests 16 | { 17 | [Fact] 18 | public void BasicTest() 19 | { 20 | var dict = new DictionarySlim(); 21 | ref var value = ref dict.GetOrAddValueRef("test"); 22 | value = "value"; 23 | 24 | var tmpPath = Path.GetTempPath(); 25 | var basicdll = Path.Combine(tmpPath, "basic.dll"); 26 | var basicbin = Path.Combine(tmpPath, "basic.bin"); 27 | 28 | SerializeObject(dict, basicbin, basicdll, "xyz", "abc", "m", new Version(1, 0, 0, 0), null); 29 | 30 | var asm = Assembly.Load(File.ReadAllBytes(basicdll)); 31 | var types = asm.GetTypes(); 32 | foreach (var type in types) 33 | { 34 | if (type.FullName == "xyz.abc") 35 | { 36 | foreach (var method in type.GetMethods()) 37 | { 38 | if (method.Name == "m") 39 | { 40 | var obj = method.Invoke(null, new object[] { basicbin } ); 41 | Assert.IsType(dict.GetType(), obj); 42 | UnloadFrozenObject(obj); 43 | break; 44 | } 45 | } 46 | } 47 | } 48 | 49 | File.Delete(basicdll); 50 | File.Delete(basicbin); 51 | } 52 | 53 | [Fact] 54 | public void MDArrayTest() 55 | { 56 | int[,] arr = new int[1,1]; 57 | arr[0, 0] = 1; 58 | 59 | var tmpPath = Path.GetTempPath(); 60 | var mdarraydll = Path.Combine(tmpPath, "mdarray.dll"); 61 | var mdarraybin = Path.Combine(tmpPath, "mdarray.bin"); 62 | 63 | SerializeObject(arr, mdarraybin, mdarraydll, "xyz", "abc", "m", new Version(1, 0, 0, 0)); 64 | 65 | var asm = Assembly.Load(File.ReadAllBytes(mdarraydll)); 66 | var types = asm.GetTypes(); 67 | foreach (var type in types) 68 | { 69 | if (type.FullName == "xyz.abc") 70 | { 71 | foreach (var method in type.GetMethods()) 72 | { 73 | if (method.Name == "m") 74 | { 75 | var obj = method.Invoke(null, new object[] { mdarraybin }); 76 | Assert.IsType(arr.GetType(), obj); 77 | UnloadFrozenObject(obj); 78 | break; 79 | } 80 | } 81 | } 82 | } 83 | 84 | File.Delete(mdarraydll); 85 | File.Delete(mdarraybin); 86 | } 87 | 88 | [Fact] 89 | public void ObjectTest() 90 | { 91 | object o = new object(); 92 | 93 | var tmpPath = Path.GetTempPath(); 94 | var objectdll = Path.Combine(tmpPath, "object.dll"); 95 | var objectbin = Path.Combine(tmpPath, "object.bin"); 96 | 97 | SerializeObject(o, objectbin, objectdll, "xyz", "abc", "m", new Version(1, 0, 0, 0)); 98 | 99 | var asm = Assembly.Load(File.ReadAllBytes(objectdll)); 100 | var types = asm.GetTypes(); 101 | foreach (var type in types) 102 | { 103 | if (type.FullName == "xyz.abc") 104 | { 105 | foreach (var method in type.GetMethods()) 106 | { 107 | if (method.Name == "m") 108 | { 109 | var obj = method.Invoke(null, new object[] { objectbin }); 110 | Assert.IsType(o.GetType(), obj); 111 | UnloadFrozenObject(obj); 112 | break; 113 | } 114 | } 115 | } 116 | } 117 | 118 | File.Delete(objectdll); 119 | File.Delete(objectbin); 120 | } 121 | 122 | [Fact] 123 | public void SameArrayObjectTest() 124 | { 125 | int[][] arrarr = new int[9][]; 126 | int[] arr = { 1, 2, 3, 4, 5}; 127 | int[] arr2 = { 1, 2, 3, 4, 5 }; 128 | arrarr[0] = arr; 129 | arrarr[1] = arr; 130 | arrarr[2] = arr; 131 | arrarr[3] = arr; 132 | arrarr[4] = arr2; 133 | arrarr[5] = arr; 134 | arrarr[6] = arr; 135 | arrarr[7] = arr2; 136 | arrarr[8] = arr; 137 | 138 | var tmpPath = Path.GetTempPath(); 139 | var objectdll = Path.Combine(tmpPath, "samearrayobject.dll"); 140 | var objectbin = Path.Combine(tmpPath, "samearrayobject.bin"); 141 | 142 | SerializeObject(arrarr, objectbin, objectdll, "xyz", "abc", "m", new Version(1, 0, 0, 0)); 143 | 144 | var asm = Assembly.Load(File.ReadAllBytes(objectdll)); 145 | var types = asm.GetTypes(); 146 | foreach (var type in types) 147 | { 148 | if (type.FullName == "xyz.abc") 149 | { 150 | foreach (var method in type.GetMethods()) 151 | { 152 | if (method.Name == "m") 153 | { 154 | var obj = method.Invoke(null, new object[] { objectbin }); 155 | UnloadFrozenObject(obj); 156 | break; 157 | } 158 | } 159 | } 160 | } 161 | 162 | File.Delete(objectdll); 163 | File.Delete(objectbin); 164 | } 165 | 166 | [Fact] 167 | public void ComplicatedObjectTest1() 168 | { 169 | var complicatedObject = new OuterClass.FooStruct.GenericReferenceTypeWithInheritance, string, int[], OuterStruct.GenericValueTypeWithReferences>(); 170 | complicatedObject.R = new Recursive(); 171 | complicatedObject.R.Data = "Food"; 172 | complicatedObject.R.Field = complicatedObject.R; 173 | 174 | complicatedObject.Y = new List { "foo", "bar", "baz" }; 175 | 176 | complicatedObject.A = "A"; 177 | complicatedObject.B = new int[2][]; 178 | complicatedObject.B[0] = new int[5]; 179 | complicatedObject.B[0][0] = 1; 180 | complicatedObject.B[0][1] = 2; 181 | complicatedObject.B[0][2] = 3; 182 | complicatedObject.B[0][3] = 4; 183 | complicatedObject.B[0][4] = -1; 184 | 185 | complicatedObject.B[1] = new int[4]; 186 | 187 | complicatedObject.B[1][0] = int.MaxValue; 188 | complicatedObject.B[1][1] = 1; 189 | complicatedObject.B[1][2] = int.MinValue; 190 | complicatedObject.B[1][3] = 3; 191 | 192 | complicatedObject.C = new OuterStruct.GenericValueTypeWithReferences[2]; 193 | complicatedObject.C[0].A = "A"; 194 | complicatedObject.C[0].B = byte.MaxValue; 195 | complicatedObject.C[0].C = new long[2,3]; 196 | complicatedObject.C[0].C[0, 0] = long.MinValue; 197 | complicatedObject.C[0].C[0, 1] = long.MaxValue; 198 | complicatedObject.C[0].C[0, 2] = long.MinValue; 199 | complicatedObject.C[0].C[1, 0] = long.MaxValue; 200 | complicatedObject.C[0].C[1, 1] = long.MinValue; 201 | complicatedObject.C[0].C[1, 2] = long.MaxValue; 202 | 203 | complicatedObject.C[0].D = new long[1][,]; 204 | complicatedObject.C[0].D[0] = new long[1,2]; 205 | complicatedObject.C[0].D[0][0, 0] = 25; 206 | complicatedObject.C[0].D[0][0, 1] = 30; 207 | complicatedObject.C[0].E = "E"; 208 | 209 | complicatedObject.S = new Circular(); 210 | complicatedObject.S.Foo = new Bar(); 211 | complicatedObject.S.Foo.Foo = complicatedObject.S; 212 | 213 | complicatedObject.BaseA = new List(); 214 | 215 | var sameDict = new DictionarySlim(); 216 | { 217 | ref string value = ref sameDict.GetOrAddValueRef("key"); 218 | value = "value"; 219 | 220 | ref string value2 = ref sameDict.GetOrAddValueRef("key2"); 221 | value2 = "value2"; 222 | 223 | ref string value3 = ref sameDict.GetOrAddValueRef("key3"); 224 | value3 = "value3"; 225 | } 226 | 227 | complicatedObject.BaseA.Add(sameDict); 228 | complicatedObject.BaseA.Add(sameDict); 229 | 230 | var otherDict = new DictionarySlim(); 231 | { 232 | ref string value = ref otherDict.GetOrAddValueRef("key"); 233 | value = "value"; 234 | 235 | ref string value2 = ref otherDict.GetOrAddValueRef("key2"); 236 | value2 = "value2"; 237 | 238 | ref string value3 = ref otherDict.GetOrAddValueRef("key3"); 239 | value3 = "value3"; 240 | } 241 | 242 | complicatedObject.BaseA.Add(otherDict); 243 | complicatedObject.BaseB = 3000; 244 | complicatedObject.BaseC = LongEnum.Max - 1; 245 | complicatedObject.BaseD = "BaseD"; 246 | 247 | var tmpPath = Path.GetTempPath(); 248 | var dll = Path.Combine(tmpPath, "complicatedObject.dll"); 249 | var bin = Path.Combine(tmpPath, "complicatedObject.bin"); 250 | 251 | SerializeObject(complicatedObject, bin, dll, "xyz", "abc", "m", new Version(1, 0, 0, 0)); 252 | 253 | var asm = Assembly.Load(File.ReadAllBytes(dll)); 254 | var types = asm.GetTypes(); 255 | foreach (var type in types) 256 | { 257 | if (type.FullName == "xyz.abc") 258 | { 259 | foreach (var method in type.GetMethods()) 260 | { 261 | if (method.Name == "m") 262 | { 263 | var obj = (OuterClass.FooStruct.GenericReferenceTypeWithInheritance, string, int[], OuterStruct.GenericValueTypeWithReferences>)method.Invoke(null, new object[] { bin }); 264 | UnloadFrozenObject(obj); 265 | break; 266 | } 267 | } 268 | } 269 | } 270 | 271 | File.Delete(dll); 272 | File.Delete(bin); 273 | } 274 | } 275 | } -------------------------------------------------------------------------------- /tools/Library.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | namespace Microsoft.FrozenObjects.BuildTools 5 | { 6 | using System; 7 | using System.IO; 8 | using System.Reflection; 9 | using System.Reflection.Metadata; 10 | using System.Reflection.Metadata.Ecma335; 11 | using System.Reflection.PortableExecutable; 12 | 13 | internal static class Program 14 | { 15 | public static int Main(string[] args) 16 | { 17 | if (args.Length != 1) 18 | { 19 | Console.WriteLine("Usage: Microsoft.FrozenObjects.BuildTools.exe OutputAssemblyPath"); 20 | return -1; 21 | } 22 | 23 | var outputAssemblyPath = args[0]; 24 | CreateInternalCallsAssembly(Path.GetFileName(outputAssemblyPath), Path.GetFileNameWithoutExtension(outputAssemblyPath), new Version(1, 0, 0, 0), outputAssemblyPath); 25 | return 0; 26 | } 27 | 28 | public static void CreateInternalCallsAssembly(string outputModuleName, string outputAssemblyName, Version version, string outputAssemblyFilePath) 29 | { 30 | var metadataBuilder = new MetadataBuilder(); 31 | metadataBuilder.AddModule(0, metadataBuilder.GetOrAddString(outputModuleName), metadataBuilder.GetOrAddGuid(Guid.NewGuid()), default, default); 32 | var assemblyHandle = metadataBuilder.AddAssembly(metadataBuilder.GetOrAddString(outputAssemblyName), version, default, default, default, AssemblyHashAlgorithm.Sha1); 33 | metadataBuilder.AddTypeDefinition(default, default, metadataBuilder.GetOrAddString(""), default, MetadataTokens.FieldDefinitionHandle(1), MetadataTokens.MethodDefinitionHandle(1)); 34 | 35 | var netstandardAssemblyRef = metadataBuilder.AddAssemblyReference(metadataBuilder.GetOrAddString("netstandard"), new Version(2, 0, 0, 0), default, metadataBuilder.GetOrAddBlob(new byte[] { 0xCC, 0x7B, 0x13, 0xFF, 0xCD, 0x2D, 0xDD, 0x51 }), default, default); 36 | var attributeTypeRef = metadataBuilder.AddTypeReference(netstandardAssemblyRef, metadataBuilder.GetOrAddString("System"), metadataBuilder.GetOrAddString("Attribute")); 37 | var assemblyNameField = CreateAssemblyNameField(metadataBuilder); 38 | var systemObjectTypeRef = metadataBuilder.AddTypeReference(netstandardAssemblyRef, metadataBuilder.GetOrAddString("System"), metadataBuilder.GetOrAddString("Object")); 39 | 40 | var codeBuilder = new BlobBuilder(); 41 | 42 | var spcAssemblyRef = metadataBuilder.AddAssemblyReference(metadataBuilder.GetOrAddString("System.Private.CoreLib"), new Version(4, 0, 0, 0), default, metadataBuilder.GetOrAddBlob(new byte[] { 0x7C, 0xEC, 0x85, 0xD7, 0xBE, 0xA7, 0x79, 0x8E }), default, default); 43 | var gcTypeRef = metadataBuilder.AddTypeReference(spcAssemblyRef, metadataBuilder.GetOrAddString("System"), metadataBuilder.GetOrAddString("GC")); 44 | 45 | var createRegisterFrozenSegmentMethodDefinitionHandle = CreateRegisterFrozenSegmentMethod(metadataBuilder, codeBuilder, CreateRegisterFrozenSegmentMemberReferenceHandle(metadataBuilder, gcTypeRef)); 46 | CreateUnregisterFrozenSegmentMethod(metadataBuilder, codeBuilder, CreateUnregisterFrozenSegmentMemberReferenceHandle(metadataBuilder, gcTypeRef)); 47 | metadataBuilder.AddTypeDefinition(TypeAttributes.Public | TypeAttributes.Abstract | TypeAttributes.AutoLayout | TypeAttributes.AnsiClass | TypeAttributes.Sealed | TypeAttributes.BeforeFieldInit, metadataBuilder.GetOrAddString("Microsoft.FrozenObjects"), metadataBuilder.GetOrAddString("InternalHelpers"), systemObjectTypeRef, assemblyNameField, createRegisterFrozenSegmentMethodDefinitionHandle); 48 | 49 | var ignoresAccessChecksToAttributeConstructor = CreateIgnoresAccessChecksToAttributeConstructorMethod(metadataBuilder, codeBuilder, CreateAttributeConstructorMemberRef(metadataBuilder, attributeTypeRef), assemblyNameField); 50 | metadataBuilder.AddCustomAttribute(assemblyHandle, ignoresAccessChecksToAttributeConstructor, metadataBuilder.GetOrAddBlob(new byte[] { 0x01, 0x00, 0x16, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6D, 0x2E, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x2E, 0x43, 0x6F, 0x72, 0x65, 0x4C, 0x69, 0x62, 0x00, 0x00 })); 51 | 52 | var ignoresAccessChecksToAttributeTypeDefinitionHandle = metadataBuilder.AddTypeDefinition(TypeAttributes.Public | TypeAttributes.AutoLayout | TypeAttributes.AnsiClass | TypeAttributes.BeforeFieldInit, metadataBuilder.GetOrAddString("System.Runtime.CompilerServices"), metadataBuilder.GetOrAddString("IgnoresAccessChecksToAttribute"), attributeTypeRef, assemblyNameField, ignoresAccessChecksToAttributeConstructor); 53 | metadataBuilder.AddCustomAttribute(ignoresAccessChecksToAttributeTypeDefinitionHandle, CreateAttributeUsageAttributeMemberRef(metadataBuilder, netstandardAssemblyRef), metadataBuilder.GetOrAddBlob(new byte[] { 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x54, 0x02, 0x0D, 0x41, 0x6C, 0x6C, 0x6F, 0x77, 0x4D, 0x75, 0x6C, 0x74, 0x69, 0x70, 0x6C, 0x65, 0x01 })); 54 | metadataBuilder.AddMethodSemantics(CreateIgnoresAccessChecksToAttributeAssemblyNameProperty(metadataBuilder, ignoresAccessChecksToAttributeTypeDefinitionHandle), MethodSemanticsAttributes.Getter, CreateIgnoresAccessChecksToAttributeGetAssemblyNameMethod(metadataBuilder, codeBuilder, assemblyNameField)); 55 | 56 | using (var fs = new FileStream(outputAssemblyFilePath, FileMode.Create, FileAccess.Write)) 57 | { 58 | WritePEImage(fs, metadataBuilder, codeBuilder); 59 | } 60 | } 61 | 62 | private static MemberReferenceHandle CreateRegisterFrozenSegmentMemberReferenceHandle(MetadataBuilder metadataBuilder, TypeReferenceHandle gcTypeRef) 63 | { 64 | var signatureBuilder = new BlobBuilder(); 65 | 66 | new BlobEncoder(signatureBuilder). 67 | MethodSignature(). 68 | Parameters(2, 69 | returnType => returnType.Type().IntPtr(), 70 | parameters => 71 | { 72 | parameters.AddParameter().Type().IntPtr(); 73 | parameters.AddParameter().Type().IntPtr(); 74 | }); 75 | 76 | return metadataBuilder.AddMemberReference(gcTypeRef, metadataBuilder.GetOrAddString("_RegisterFrozenSegment"), metadataBuilder.GetOrAddBlob(signatureBuilder)); 77 | } 78 | 79 | private static MemberReferenceHandle CreateUnregisterFrozenSegmentMemberReferenceHandle(MetadataBuilder metadataBuilder, TypeReferenceHandle gcTypeRef) 80 | { 81 | var signatureBuilder = new BlobBuilder(); 82 | 83 | new BlobEncoder(signatureBuilder). 84 | MethodSignature(). 85 | Parameters(1, 86 | returnType => returnType.Void(), 87 | parameters => 88 | { 89 | parameters.AddParameter().Type().IntPtr(); 90 | }); 91 | 92 | return metadataBuilder.AddMemberReference(gcTypeRef, metadataBuilder.GetOrAddString("_UnregisterFrozenSegment"), metadataBuilder.GetOrAddBlob(signatureBuilder)); 93 | } 94 | 95 | private static MethodDefinitionHandle CreateRegisterFrozenSegmentMethod(MetadataBuilder metadataBuilder, BlobBuilder codeBuilder, MemberReferenceHandle registerFrozenSegmentMemberReferenceHandle) 96 | { 97 | codeBuilder.Align(4); 98 | 99 | var ilBuilder = new BlobBuilder(); 100 | var il = new InstructionEncoder(ilBuilder); 101 | 102 | il.LoadArgument(0); 103 | il.LoadArgument(1); 104 | il.OpCode(ILOpCode.Call); 105 | il.Token(registerFrozenSegmentMemberReferenceHandle); 106 | il.OpCode(ILOpCode.Ret); 107 | 108 | var methodBodyStream = new MethodBodyStreamEncoder(codeBuilder); 109 | int bodyOffset = methodBodyStream.AddMethodBody(il); 110 | ilBuilder.Clear(); 111 | 112 | var signatureBuilder = new BlobBuilder(); 113 | 114 | new BlobEncoder(signatureBuilder). 115 | MethodSignature(). 116 | Parameters(2, 117 | returnType => returnType.Type().IntPtr(), 118 | parameters => 119 | { 120 | parameters.AddParameter().Type().IntPtr(); 121 | parameters.AddParameter().Type().IntPtr(); 122 | }); 123 | 124 | return metadataBuilder.AddMethodDefinition(MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.Static, MethodImplAttributes.IL | MethodImplAttributes.Managed, metadataBuilder.GetOrAddString("RegisterFrozenSegment"), metadataBuilder.GetOrAddBlob(signatureBuilder), bodyOffset, parameterList: default); 125 | } 126 | 127 | private static void CreateUnregisterFrozenSegmentMethod(MetadataBuilder metadataBuilder, BlobBuilder codeBuilder, MemberReferenceHandle unregisterFrozenSegmentMemberReferenceHandle) 128 | { 129 | codeBuilder.Align(4); 130 | 131 | var ilBuilder = new BlobBuilder(); 132 | var il = new InstructionEncoder(ilBuilder); 133 | 134 | il.LoadArgument(0); 135 | il.OpCode(ILOpCode.Call); 136 | il.Token(unregisterFrozenSegmentMemberReferenceHandle); 137 | il.OpCode(ILOpCode.Ret); 138 | 139 | var methodBodyStream = new MethodBodyStreamEncoder(codeBuilder); 140 | int bodyOffset = methodBodyStream.AddMethodBody(il); 141 | ilBuilder.Clear(); 142 | 143 | var signatureBuilder = new BlobBuilder(); 144 | 145 | new BlobEncoder(signatureBuilder). 146 | MethodSignature(). 147 | Parameters(1, 148 | returnType => returnType.Void(), 149 | parameters => 150 | { 151 | parameters.AddParameter().Type().IntPtr(); 152 | }); 153 | 154 | metadataBuilder.AddMethodDefinition(MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.Static, MethodImplAttributes.IL | MethodImplAttributes.Managed, metadataBuilder.GetOrAddString("UnregisterFrozenSegment"), metadataBuilder.GetOrAddBlob(signatureBuilder), bodyOffset, parameterList: default); 155 | } 156 | 157 | private static MemberReferenceHandle CreateAttributeConstructorMemberRef(MetadataBuilder metadataBuilder, TypeReferenceHandle attributeTypeRef) 158 | { 159 | var signatureBuilder = new BlobBuilder(); 160 | 161 | new BlobEncoder(signatureBuilder). 162 | MethodSignature(SignatureCallingConvention.Default, 0, true). 163 | Parameters(0, 164 | returnType => returnType.Void(), 165 | parameters => 166 | { 167 | }); 168 | 169 | return metadataBuilder.AddMemberReference(attributeTypeRef, metadataBuilder.GetOrAddString(".ctor"), metadataBuilder.GetOrAddBlob(signatureBuilder)); 170 | } 171 | 172 | private static MemberReferenceHandle CreateAttributeUsageAttributeMemberRef(MetadataBuilder metadataBuilder, AssemblyReferenceHandle netstandardAssemblyRef) 173 | { 174 | var attributeTargetsTypeRef = metadataBuilder.AddTypeReference(netstandardAssemblyRef, metadataBuilder.GetOrAddString("System"), metadataBuilder.GetOrAddString("AttributeTargets")); 175 | var attributeUsageAttributeTypeRef = metadataBuilder.AddTypeReference(netstandardAssemblyRef, metadataBuilder.GetOrAddString("System"), metadataBuilder.GetOrAddString("AttributeUsageAttribute")); 176 | 177 | var signatureBuilder = new BlobBuilder(); 178 | 179 | new BlobEncoder(signatureBuilder). 180 | MethodSignature(SignatureCallingConvention.Default, 0, true). 181 | Parameters(1, 182 | returnType => returnType.Void(), 183 | parameters => 184 | { 185 | parameters.AddParameter().Type().Type(attributeTargetsTypeRef, true); 186 | }); 187 | 188 | return metadataBuilder.AddMemberReference(attributeUsageAttributeTypeRef, metadataBuilder.GetOrAddString(".ctor"), metadataBuilder.GetOrAddBlob(signatureBuilder)); 189 | } 190 | 191 | private static FieldDefinitionHandle CreateAssemblyNameField(MetadataBuilder metadataBuilder) 192 | { 193 | var signatureBuilder = new BlobBuilder(); 194 | 195 | new BlobEncoder(signatureBuilder).FieldSignature().String(); 196 | 197 | return metadataBuilder.AddFieldDefinition(FieldAttributes.Private | FieldAttributes.InitOnly, metadataBuilder.GetOrAddString("assemblyName"), metadataBuilder.GetOrAddBlob(signatureBuilder)); 198 | } 199 | 200 | private static PropertyDefinitionHandle CreateIgnoresAccessChecksToAttributeAssemblyNameProperty(MetadataBuilder metadataBuilder, TypeDefinitionHandle typeDefinitionHandle) 201 | { 202 | var signatureBuilder = new BlobBuilder(); 203 | 204 | new BlobEncoder(signatureBuilder). 205 | PropertySignature(true). 206 | Parameters(0, 207 | returnType => returnType.Type().String(), 208 | parameters => 209 | { 210 | }); 211 | 212 | var propertyDefinitionHandle = metadataBuilder.AddProperty(PropertyAttributes.None, metadataBuilder.GetOrAddString("AssemblyName"), metadataBuilder.GetOrAddBlob(signatureBuilder)); 213 | 214 | metadataBuilder.AddPropertyMap(typeDefinitionHandle, propertyDefinitionHandle); 215 | 216 | return propertyDefinitionHandle; 217 | } 218 | 219 | private static MethodDefinitionHandle CreateIgnoresAccessChecksToAttributeConstructorMethod(MetadataBuilder metadataBuilder, BlobBuilder codeBuilder, MemberReferenceHandle attributeConstructor, FieldDefinitionHandle assemblyNameField) 220 | { 221 | codeBuilder.Align(4); 222 | 223 | var ilBuilder = new BlobBuilder(); 224 | var il = new InstructionEncoder(ilBuilder); 225 | 226 | il.LoadArgument(0); 227 | il.OpCode(ILOpCode.Dup); 228 | il.OpCode(ILOpCode.Call); 229 | il.Token(attributeConstructor); 230 | il.LoadArgument(1); 231 | il.OpCode(ILOpCode.Stfld); 232 | il.Token(assemblyNameField); 233 | il.OpCode(ILOpCode.Ret); 234 | 235 | var methodBodyStream = new MethodBodyStreamEncoder(codeBuilder); 236 | int mainBodyOffset = methodBodyStream.AddMethodBody(il); 237 | ilBuilder.Clear(); 238 | 239 | var signatureBuilder = new BlobBuilder(); 240 | 241 | new BlobEncoder(signatureBuilder). 242 | MethodSignature(SignatureCallingConvention.Default, 0, true). 243 | Parameters(1, 244 | returnType => returnType.Void(), 245 | parameters => 246 | { 247 | parameters.AddParameter().Type().String(); 248 | }); 249 | 250 | return metadataBuilder.AddMethodDefinition(MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName, MethodImplAttributes.IL | MethodImplAttributes.Managed, metadataBuilder.GetOrAddString(".ctor"), metadataBuilder.GetOrAddBlob(signatureBuilder), mainBodyOffset, parameterList: default); 251 | } 252 | 253 | private static MethodDefinitionHandle CreateIgnoresAccessChecksToAttributeGetAssemblyNameMethod(MetadataBuilder metadataBuilder, BlobBuilder codeBuilder, FieldDefinitionHandle assemblyNameField) 254 | { 255 | codeBuilder.Align(4); 256 | 257 | var ilBuilder = new BlobBuilder(); 258 | var il = new InstructionEncoder(ilBuilder); 259 | 260 | il.LoadArgument(0); 261 | il.OpCode(ILOpCode.Ldfld); 262 | il.Token(assemblyNameField); 263 | il.OpCode(ILOpCode.Ret); 264 | 265 | var methodBodyStream = new MethodBodyStreamEncoder(codeBuilder); 266 | int bodyOffset = methodBodyStream.AddMethodBody(il); 267 | ilBuilder.Clear(); 268 | 269 | var signatureBuilder = new BlobBuilder(); 270 | 271 | new BlobEncoder(signatureBuilder). 272 | MethodSignature(SignatureCallingConvention.Default, 0, true). 273 | Parameters(0, 274 | returnType => returnType.Type().String(), 275 | parameters => 276 | { 277 | }); 278 | 279 | return metadataBuilder.AddMethodDefinition(MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName, MethodImplAttributes.IL | MethodImplAttributes.Managed, metadataBuilder.GetOrAddString("get_AssemblyName"), metadataBuilder.GetOrAddBlob(signatureBuilder), bodyOffset, parameterList: default); 280 | } 281 | 282 | private static void WritePEImage(Stream peStream, MetadataBuilder metadataBuilder, BlobBuilder ilBuilder, Blob mvidFixup = default, byte[] privateKeyOpt = null) 283 | { 284 | var peBuilder = new ManagedPEBuilder(new PEHeaderBuilder(imageCharacteristics: Characteristics.ExecutableImage | Characteristics.Dll), new MetadataRootBuilder(metadataBuilder), ilBuilder, entryPoint: default, flags: CorFlags.ILOnly | (privateKeyOpt != null ? CorFlags.StrongNameSigned : 0)); 285 | 286 | var peBlob = new BlobBuilder(); 287 | 288 | var contentId = peBuilder.Serialize(peBlob); 289 | 290 | if (!mvidFixup.IsDefault) 291 | { 292 | new BlobWriter(mvidFixup).WriteGuid(contentId.Guid); 293 | } 294 | 295 | peBlob.WriteContentTo(peStream); 296 | } 297 | } 298 | } -------------------------------------------------------------------------------- /tools/Microsoft.FrozenObjects.BuildTools.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.1 5 | Exe 6 | 7 | 8 | --------------------------------------------------------------------------------