diff --git a/eng/Subsets.props b/eng/Subsets.props
index 393d13c0c5a77..eca56d981e12c 100644
--- a/eng/Subsets.props
+++ b/eng/Subsets.props
@@ -182,6 +182,7 @@
+
@@ -409,6 +410,11 @@
Test="true" Category="tools"/>
+
+
+
+
diff --git a/eng/native/configurecompiler.cmake b/eng/native/configurecompiler.cmake
index c5fa2f7db8830..3ec98ad9be3cd 100644
--- a/eng/native/configurecompiler.cmake
+++ b/eng/native/configurecompiler.cmake
@@ -60,6 +60,7 @@ if (MSVC)
define_property(TARGET PROPERTY CLR_EH_CONTINUATION INHERITED BRIEF_DOCS "Controls the /guard:ehcont flag presence" FULL_DOCS "Set this property to ON or OFF to indicate if the /guard:ehcont compiler flag should be present")
define_property(TARGET PROPERTY CLR_EH_OPTION INHERITED BRIEF_DOCS "Defines the value of the /EH option" FULL_DOCS "Set this property to one of the valid /EHxx options (/EHa, /EHsc, /EHa-, ...)")
define_property(TARGET PROPERTY MSVC_WARNING_LEVEL INHERITED BRIEF_DOCS "Define the warning level for the /Wn option" FULL_DOCS "Set this property to one of the valid /Wn options (/W0, /W1, /W2, /W3, /W4)")
+ define_property(TARGET PROPERTY SET_SOURCE_CHARSET INHERITED BRIEF_DOCS "Add the /source-charset option" FULL_DOCS "Set this property to add the /source-charset:utf8 option")
set_property(GLOBAL PROPERTY CLR_CONTROL_FLOW_GUARD ON)
@@ -881,7 +882,8 @@ if (MSVC)
add_compile_options($<$:/Zi>) # enable debugging information
add_compile_options($<$:/ZH:SHA_256>) # use SHA256 for generating hashes of compiler processed source files.
- add_compile_options($<$:/source-charset:utf-8>) # Force MSVC to compile source as UTF-8.
+ set_property(GLOBAL PROPERTY SET_SOURCE_CHARSET ON)
+ add_compile_options($<$>,$>:/source-charset:utf-8>) # Force MSVC to compile source as UTF-8.
if (CLR_CMAKE_HOST_ARCH_I386)
add_compile_options($<$:/Gz>)
diff --git a/eng/pipelines/runtime.yml b/eng/pipelines/runtime.yml
index 2e68a43af682e..40995100964d7 100644
--- a/eng/pipelines/runtime.yml
+++ b/eng/pipelines/runtime.yml
@@ -692,9 +692,19 @@ extends:
jobParameters:
timeoutInMinutes: 120
nameSuffix: CLR_Tools_Tests
- buildArgs: -s clr.aot+clr.iltools+libs.sfx+clr.toolstests+tools.cdacreadertests -c $(_BuildConfig) -test
+ buildArgs: -s clr.aot+clr.iltools+libs.sfx+clr.toolstests+tools.cdacreadertests+tools.nativetests -c $(_BuildConfig) -test
enablePublishTestResults: true
testResultsFormat: 'xunit'
+ postBuildSteps:
+ - task: PublishTestResults@2
+ displayName: 'Publish CTest Results'
+ inputs:
+ testResultsFormat: 'CTest'
+ testResultsFiles: '*.ctestxml'
+ searchFolder: '$(Build.SourcesDirectory)/artifacts/TestResults/$(_BuildConfig)'
+ testRunTitle: 'CLR_Tools_Tests-$(_BuildConfig)-ctest'
+ continueOnError: true
+ condition: always()
# We want to run AOT tests when illink changes because there's share code and tests from illink which are used by AOT
condition: >-
or(
diff --git a/src/coreclr/CMakeLists.txt b/src/coreclr/CMakeLists.txt
index 107859ec58dc5..9bab10778da74 100644
--- a/src/coreclr/CMakeLists.txt
+++ b/src/coreclr/CMakeLists.txt
@@ -125,10 +125,17 @@ add_subdirectory(pal/prebuilt/inc)
set(EP_GENERATED_HEADER_PATH "${GENERATED_INCLUDE_DIR}")
include (${CLR_SRC_NATIVE_DIR}/eventpipe/configure.cmake)
+#--------------------------------
+# Include shared native libraries
+#--------------------------------
add_subdirectory(${CLR_SRC_NATIVE_DIR}/containers containers)
add_subdirectory(${CLR_SRC_NATIVE_DIR}/eventpipe eventpipe)
add_subdirectory(${CLR_SRC_NATIVE_DIR}/minipal shared_minipal)
+if (NOT CLR_CROSS_COMPONENTS_BUILD)
+ include(dnmd.cmake)
+endif()
+
if(NOT CLR_CMAKE_HOST_MACCATALYST AND NOT CLR_CMAKE_HOST_IOS AND NOT CLR_CMAKE_HOST_TVOS)
add_subdirectory(debug/debug-pal)
endif()
diff --git a/src/coreclr/build-runtime.cmd b/src/coreclr/build-runtime.cmd
index 89dafc4971588..a82abcba796eb 100644
--- a/src/coreclr/build-runtime.cmd
+++ b/src/coreclr/build-runtime.cmd
@@ -329,6 +329,9 @@ for /f "delims=" %%a in ("-%__RequestedBuildComponents%-") do (
if not "!string:-crosscomponents-=!"=="!string!" (
set __CMakeTarget=!__CMakeTarget! crosscomponents
)
+ if not "!string:-cdac-=!"=="!string!" (
+ set __CMakeTarget=!__CMakeTarget! cdac
+ )
if not "!string:-debug-=!"=="!string!" (
set __CMakeTarget=!__CMakeTarget! debug
)
diff --git a/src/coreclr/components.cmake b/src/coreclr/components.cmake
index 70dd081376f67..492da5140fee0 100644
--- a/src/coreclr/components.cmake
+++ b/src/coreclr/components.cmake
@@ -27,3 +27,6 @@ add_dependencies(runtime hosts)
# The cross-components build is separate, so we don't need to add a dependency on coreclr_misc
add_component(crosscomponents)
+
+# The cdac-components build is separate
+add_component(cdac)
diff --git a/src/coreclr/dnmd.cmake b/src/coreclr/dnmd.cmake
new file mode 100644
index 0000000000000..420f56630248d
--- /dev/null
+++ b/src/coreclr/dnmd.cmake
@@ -0,0 +1,18 @@
+# Include the dnmd project into the CoreCLR build
+include(FetchContent)
+FetchContent_Declare(
+ dnmd
+ SOURCE_DIR "${CLR_SRC_NATIVE_DIR}/dnmd"
+)
+
+set(DNMD_BUILD_TESTS OFF)
+set(DNMD_INSTALL OFF)
+FetchContent_MakeAvailable(dnmd)
+
+set_property(DIRECTORY ${CLR_SRC_NATIVE_DIR}/dnmd PROPERTY CLR_CONTROL_FLOW_GUARD ON)
+
+# Install dnmd for usage by the cdac.
+install_static_library(dnmd cdac cdac)
+install_static_library(dnmd_interfaces_static cdac cdac)
+install(TARGETS dnmd_interfaces DESTINATION cdac COMPONENT cdac)
+add_dependencies(cdac dnmd_interfaces)
diff --git a/src/coreclr/inc/corhdr.h b/src/coreclr/inc/corhdr.h
index b0e257a8469b2..84aa72eba3dce 100644
--- a/src/coreclr/inc/corhdr.h
+++ b/src/coreclr/inc/corhdr.h
@@ -1752,7 +1752,11 @@ typedef enum NativeTypeArrayFlags
//
// Enum used for HFA type recognition.
// Supported across architectures, so that it can be used in altjits and cross-compilation.
+#ifdef __cplusplus
typedef enum CorInfoHFAElemType : unsigned {
+#else
+typedef enum CorInfoHFAElemType {
+#endif
CORINFO_HFA_ELEM_NONE,
CORINFO_HFA_ELEM_FLOAT,
CORINFO_HFA_ELEM_DOUBLE,
diff --git a/src/coreclr/inc/sha1.h b/src/coreclr/inc/sha1.h
deleted file mode 100644
index 1c9c9cead440d..0000000000000
--- a/src/coreclr/inc/sha1.h
+++ /dev/null
@@ -1,49 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-//
-
-#ifndef SHA1_H_
-#define SHA1_H_
-
-// Hasher class, performs no allocation and therefore does not throw or return
-// errors. Usage is as follows:
-// Create an instance (this initializes the hash).
-// Add one or more blocks of input data using AddData().
-// Retrieve the hash using GetHash(). This can be done as many times as desired
-// until the object is destructed. Once a hash is asked for, further AddData
-// calls will be ignored. There is no way to reset object state (simply
-// destroy the object and create another instead).
-
-#define SHA1_HASH_SIZE 20 // Number of bytes output by SHA-1
-
-typedef struct {
- DWORD magic_sha1; // Magic value for A_SHA_CTX
- DWORD awaiting_data[16];
- // Data awaiting full 512-bit block.
- // Length (nbit_total[0] % 512) bits.
- // Unused part of buffer (at end) is zero
- DWORD partial_hash[5];
- // Hash through last full block
- DWORD nbit_total[2];
- // Total length of message so far
- // (bits, mod 2^64)
-} SHA1_CTX;
-
-class SHA1Hash
-{
-private:
- SHA1_CTX m_Context;
- BYTE m_Value[SHA1_HASH_SIZE];
- BOOL m_fFinalized;
-
- void SHA1Init(SHA1_CTX*);
- void SHA1Update(SHA1_CTX*, const BYTE*, const DWORD);
- void SHA1Final(SHA1_CTX*, BYTE* digest);
-
-public:
- SHA1Hash();
- void AddData(BYTE *pbData, DWORD cbData);
- BYTE *GetHash();
-};
-
-#endif // SHA1_H_
diff --git a/src/coreclr/md/runtime/CMakeLists.txt b/src/coreclr/md/runtime/CMakeLists.txt
index 293de03ee8bc4..e3532cca0a560 100644
--- a/src/coreclr/md/runtime/CMakeLists.txt
+++ b/src/coreclr/md/runtime/CMakeLists.txt
@@ -46,15 +46,19 @@ endif (CLR_CMAKE_TARGET_WIN32)
add_library_clr(mdruntime_dac ${MDRUNTIME_SOURCES})
set_target_properties(mdruntime_dac PROPERTIES DAC_COMPONENT TRUE)
target_precompile_headers(mdruntime_dac PRIVATE stdafx.h)
+target_link_libraries(mdruntime_dac PUBLIC minipal)
add_library_clr(mdruntime_wks OBJECT ${MDRUNTIME_SOURCES})
target_compile_definitions(mdruntime_wks PRIVATE FEATURE_METADATA_EMIT_ALL)
target_precompile_headers(mdruntime_wks PRIVATE stdafx.h)
+target_link_libraries(mdruntime_wks PUBLIC minipal)
add_library_clr(mdruntime-dbi ${MDRUNTIME_SOURCES})
set_target_properties(mdruntime-dbi PROPERTIES DBI_COMPONENT TRUE)
target_precompile_headers(mdruntime-dbi PRIVATE stdafx.h)
+target_link_libraries(mdruntime-dbi PUBLIC minipal)
add_library_clr(mdruntime_ppdb ${MDRUNTIME_SOURCES})
target_compile_definitions(mdruntime_ppdb PRIVATE FEATURE_METADATA_EMIT_ALL FEATURE_METADATA_EMIT_PORTABLE_PDB)
target_precompile_headers(mdruntime_ppdb PRIVATE stdafx.h)
+target_link_libraries(mdruntime_ppdb PUBLIC minipal)
diff --git a/src/coreclr/md/runtime/strongnameinternal.cpp b/src/coreclr/md/runtime/strongnameinternal.cpp
index 9198875c99c46..a15ee542589e6 100644
--- a/src/coreclr/md/runtime/strongnameinternal.cpp
+++ b/src/coreclr/md/runtime/strongnameinternal.cpp
@@ -6,7 +6,7 @@
#include "stdafx.h"
#include "strongnameinternal.h"
-#include "sha1.h"
+#include
// Common keys used by libraries we ship are included here.
@@ -317,9 +317,6 @@ HRESULT StrongNameTokenFromPublicKey(BYTE *pbPublicKeyBlob, // [in] pu
return S_OK;
#endif
HRESULT hr = S_OK;
-
- SHA1Hash sha1;
- BYTE *pHash = NULL;
DWORD i;
PublicKeyBlob *pPublicKey = NULL;
DWORD dwHashLenMinusTokenSize = 0;
@@ -338,8 +335,9 @@ HRESULT StrongNameTokenFromPublicKey(BYTE *pbPublicKeyBlob, // [in] pu
}
// Compute a hash over the public key.
- sha1.AddData(pbPublicKeyBlob, cbPublicKeyBlob);
- pHash = sha1.GetHash();
+ BYTE hash[SHA1_HASH_SIZE];
+ minipal_sha1(pbPublicKeyBlob, cbPublicKeyBlob, hash, sizeof(hash));
+
static_assert(SHA1_HASH_SIZE >= StrongNameToken::SIZEOF_TOKEN, "StrongNameToken::SIZEOF_TOKEN must be smaller or equal to the SHA1_HASH_SIZE");
dwHashLenMinusTokenSize = SHA1_HASH_SIZE - StrongNameToken::SIZEOF_TOKEN;
@@ -347,7 +345,7 @@ HRESULT StrongNameTokenFromPublicKey(BYTE *pbPublicKeyBlob, // [in] pu
// low order bytes from a network byte order point of view). Reverse the
// order of these bytes in the output buffer to get host byte order.
for (i = 0; i < StrongNameToken::SIZEOF_TOKEN; i++)
- pToken->m_token[StrongNameToken::SIZEOF_TOKEN - (i + 1)] = pHash[i + dwHashLenMinusTokenSize];
+ pToken->m_token[StrongNameToken::SIZEOF_TOKEN - (i + 1)] = hash[i + dwHashLenMinusTokenSize];
return hr;
}
diff --git a/src/coreclr/utilcode/CMakeLists.txt b/src/coreclr/utilcode/CMakeLists.txt
index ec543e707d718..718990a69169a 100644
--- a/src/coreclr/utilcode/CMakeLists.txt
+++ b/src/coreclr/utilcode/CMakeLists.txt
@@ -34,7 +34,6 @@ set(UTILCODE_COMMON_SOURCES
corimage.cpp
format1.cpp
prettyprintsig.cpp
- sha1.cpp
sigbuilder.cpp
sigparser.cpp
sstring.cpp
diff --git a/src/coreclr/vm/assembly.cpp b/src/coreclr/vm/assembly.cpp
index 8cff98c6d3f8e..561800afe2677 100644
--- a/src/coreclr/vm/assembly.cpp
+++ b/src/coreclr/vm/assembly.cpp
@@ -22,7 +22,7 @@
#include "reflectclasswriter.h"
#include "comdynamic.h"
-#include "sha1.h"
+#include
#include "eeconfig.h"
diff --git a/src/coreclr/vm/dwbucketmanager.hpp b/src/coreclr/vm/dwbucketmanager.hpp
index dec761a97e5ed..b7a8ff4604726 100644
--- a/src/coreclr/vm/dwbucketmanager.hpp
+++ b/src/coreclr/vm/dwbucketmanager.hpp
@@ -86,7 +86,7 @@ DWORD GetCountBucketParamsForEvent(LPCWSTR wzEventName)
#include "dwreport.h"
#include
#include "dbginterface.h"
-#include
+#include
//------------------------------------------------------------------------------
// Description
@@ -1068,11 +1068,11 @@ int BaseBucketParamsManager::CopyStringToBucket(_Out_writes_(targetMaxLength) LP
}
// String didn't fit, so hash it.
- SHA1Hash hash;
- hash.AddData(reinterpret_cast(const_cast(pSource)), (static_cast(u16_strlen(pSource))) * sizeof(WCHAR));
+ BYTE hash[SHA1_HASH_SIZE];
+ minipal_sha1(reinterpret_cast(const_cast(pSource)), (static_cast(u16_strlen(pSource))) * sizeof(WCHAR), hash, sizeof(hash));
// Encode in base32. The hash is a fixed size; we'll accept up to maxLen characters of the encoding.
- BytesToBase32 b32(hash.GetHash(), SHA1_HASH_SIZE);
+ BytesToBase32 b32(hash, SHA1_HASH_SIZE);
targLen = b32.Convert(pTargetParam, targetMaxLength);
pTargetParam[targLen] = W('\0');
diff --git a/src/coreclr/vm/peassembly.cpp b/src/coreclr/vm/peassembly.cpp
index 19cb70194d629..ebc7119b9ef77 100644
--- a/src/coreclr/vm/peassembly.cpp
+++ b/src/coreclr/vm/peassembly.cpp
@@ -20,7 +20,7 @@
#include "../binder/inc/applicationcontext.hpp"
#include "../binder/inc/assemblybindercommon.hpp"
-#include "sha1.h"
+#include
#ifndef DACCESS_COMPILE
diff --git a/src/native/dnmd/.gitignore b/src/native/dnmd/.gitignore
new file mode 100644
index 0000000000000..64c0736681632
--- /dev/null
+++ b/src/native/dnmd/.gitignore
@@ -0,0 +1,5 @@
+artifacts/
+.vs/
+.idea/
+*~
+TestResults/
diff --git a/src/native/dnmd/CMakeLists.txt b/src/native/dnmd/CMakeLists.txt
new file mode 100644
index 0000000000000..462ec683d15fc
--- /dev/null
+++ b/src/native/dnmd/CMakeLists.txt
@@ -0,0 +1,37 @@
+cmake_minimum_required(VERSION 3.20)
+project(dnmd
+ LANGUAGES C)
+
+# Always include our public headers
+include_directories(src/inc)
+
+# Include the metadata API headers provided by CoreCLR
+include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../../coreclr/inc ${CMAKE_CURRENT_SOURCE_DIR}/../../coreclr/pal/prebuilt/inc)
+
+if ("${CMAKE_CURRENT_SOURCE_DIR}" STREQUAL "${CMAKE_SOURCE_DIR}")
+ # This is our root CMakeList.txt, so we need to set up some global settings and include the minipal here.
+ include(../../../eng/native/configurepaths.cmake)
+ include(${CLR_ENG_NATIVE_DIR}/configurecompiler.cmake)
+
+ add_subdirectory(${CLR_SRC_NATIVE_DIR}/minipal minipal)
+endif()
+
+include_directories(${CLR_SRC_NATIVE_DIR})
+
+option(DNMD_BUILD_TESTS "Build tests for dnmd" ON)
+option(DNMD_INSTALL "Install dnmd targets" ON)
+option(DNMD_BUILD_INTERFACES "Build implementations of the metadata interfaces for DNMD" ON)
+option(DNMD_BUILD_TOOLS "Build tools for dnmd" ON)
+
+if (DNMD_BUILD_INTERFACES OR DNMD_BUILD_TESTS OR DNMD_BUILD_TOOLS)
+ enable_language(CXX)
+endif()
+
+add_subdirectory(src/)
+
+
+if (DNMD_BUILD_TESTS)
+ enable_testing()
+
+ add_subdirectory(test/)
+endif()
\ No newline at end of file
diff --git a/src/native/dnmd/build-native.cmd b/src/native/dnmd/build-native.cmd
new file mode 100644
index 0000000000000..9d0747600bb8c
--- /dev/null
+++ b/src/native/dnmd/build-native.cmd
@@ -0,0 +1,114 @@
+@if not defined _echo @echo off
+setlocal
+
+:SetupArgs
+:: Initialize the args that will be passed to cmake
+set "__sourceRootDir=%~dp0"
+:: remove trailing slash
+if %__sourceRootDir:~-1%==\ set "__sourceRootDir=%__sourceRootDir:~0,-1%"
+set "__repoRoot=%__sourceRootDir%\..\..\.."
+:: normalize
+for %%i in ("%__repoRoot%") do set "__repoRoot=%%~fi"
+set "__engNativeDir=%__repoRoot%\eng\native"
+set "__artifactsDir=%__repoRoot%\artifacts"
+set __CMakeBinDir=""
+set __IntermediatesDir=""
+set __BuildArch=x64
+set __BuildTarget="build"
+set __TargetOS=windows
+set CMAKE_BUILD_TYPE=Debug
+set __Ninja=1
+set __ExtraCmakeParams=
+
+:Arg_Loop
+:: Since the native build requires some configuration information before msbuild is called, we have to do some manual args parsing
+if [%1] == [] goto :InitVSEnv
+if /i [%1] == [Release] ( set CMAKE_BUILD_TYPE=Release&&shift&goto Arg_Loop)
+if /i [%1] == [Debug] ( set CMAKE_BUILD_TYPE=Debug&&shift&goto Arg_Loop)
+
+if /i [%1] == [x86] ( set __BuildArch=x86&&shift&goto Arg_Loop)
+if /i [%1] == [x64] ( set __BuildArch=x64&&shift&goto Arg_Loop)
+if /i [%1] == [arm64] ( set __BuildArch=arm64&&shift&goto Arg_Loop)
+if /i [%1] == [wasm] ( set __BuildArch=wasm&&shift&goto Arg_Loop)
+
+if /i [%1] == [outconfig] ( set __outConfig=%2&&shift&&shift&goto Arg_Loop)
+
+if /i [%1] == [browser] ( set __TargetOS=browser&&shift&goto Arg_Loop)
+if /i [%1] == [wasi] ( set __TargetOS=wasi&&shift&goto Arg_Loop)
+
+if /i [%1] == [rebuild] ( set __BuildTarget=rebuild&&shift&goto Arg_Loop)
+
+if /i [%1] == [msbuild] ( set __Ninja=0&&shift&goto Arg_Loop)
+
+if /i [%1] == [-fsanitize] ( set __ExtraCmakeParams=%__ExtraCmakeParams% "-DCLR_CMAKE_ENABLE_SANITIZERS=$2"&&shift&&shift&goto Arg_Loop)
+
+shift
+goto :Arg_Loop
+
+:InitVSEnv
+call "%__engNativeDir%\init-vs-env.cmd" %__BuildArch%
+if NOT [%errorlevel%] == [0] goto :Failure
+
+:: Setup to cmake the native components
+echo Commencing build of native components
+echo.
+
+call "%__engNativeDir%\version\copy_version_files.cmd"
+
+set __ExtraCmakeParams=%__ExtraCmakeParams% "-DCMAKE_BUILD_TYPE=%CMAKE_BUILD_TYPE%"
+
+if [%__outConfig%] == [] set __outConfig=%__TargetOS%.%__BuildArch%.%CMAKE_BUILD_TYPE%
+
+if %__IntermediatesDir% == "" (
+ set "__IntermediatesDir=%__artifactsDir%\obj\dnmd\%__outConfig%"
+)
+if %__Ninja% == 0 (
+ set "__IntermediatesDir=%__IntermediatesDir%\ide"
+)
+set "__IntermediatesDir=%__IntermediatesDir:\=/%"
+
+if %__CMakeBinDir% == "" (
+ set "__CMakeBinDir=%__artifactsDir%\bin\dnmd\%__outConfig%"
+)
+
+:: Check that the intermediate directory exists so we can place our cmake build tree there
+if not exist "%__IntermediatesDir%" md "%__IntermediatesDir%"
+
+:: Write an empty Directory.Build.props/targets to ensure that msbuild doesn't pick up
+:: the repo's root Directory.Build.props/targets.
+set MSBUILD_EMPTY_PROJECT_CONTENT= ^
+ ^^^ ^
+ ^^^
+echo %MSBUILD_EMPTY_PROJECT_CONTENT% > "%__artifactsDir%\obj\dnmd\Directory.Build.props"
+echo %MSBUILD_EMPTY_PROJECT_CONTENT% > "%__artifactsDir%\obj\dnmd\Directory.Build.targets"
+
+:: Regenerate the VS solution
+
+call "%__repoRoot%\eng\native\gen-buildsys.cmd" "%__sourceRootDir%" "%__IntermediatesDir%" %__VSVersion% %__BuildArch% %__TargetOS% %__ExtraCmakeParams%
+if NOT [%errorlevel%] == [0] goto :Failure
+
+:BuildNativeProj
+:: Build the project created by Cmake
+set __generatorArgs=
+if [%__Ninja%] == [1] (
+ set __generatorArgs=
+) else if [%__TargetOS%] == [browser] (
+ set __generatorArgs=
+) else if [%__TargetOS%] == [wasi] (
+ set __generatorArgs=
+) else (
+ set __generatorArgs=/p:Platform=%__BuildArch% /p:PlatformToolset="%__PlatformToolset%" -noWarn:MSB8065
+)
+
+call "%CMakePath%" --build "%__IntermediatesDir%" --target install --config %CMAKE_BUILD_TYPE% -- %__generatorArgs%
+IF ERRORLEVEL 1 (
+ goto :Failure
+)
+
+echo Done building Native components
+exit /B 0
+
+:Failure
+:: Build failed
+echo Failed to generate native component build project!
+exit /b 1
diff --git a/src/native/dnmd/build-native.sh b/src/native/dnmd/build-native.sh
new file mode 100755
index 0000000000000..6b6450987a154
--- /dev/null
+++ b/src/native/dnmd/build-native.sh
@@ -0,0 +1,18 @@
+__ProjectRoot="$(cd "$(dirname "$0")"; pwd -P)"
+__RepoRootDir="$(cd "$__ProjectRoot"/../../..; pwd -P)"
+
+__TargetArch=
+__BuildType=Debug
+__TargetOS=
+__Compiler=clang
+__UseNinja=0
+__SkipConfigure=0
+__PortableBuild=1
+
+source "$__RepoRootDir"/eng/native/build-commons.sh
+
+__ConfigTriplet="$__TargetOS.$__TargetArch.$__BuildType"
+__ArtifactsObjDir="$__RepoRootDir/artifacts/obj"
+__IntermediatesDir="$__ArtifactsObjDir/dnmd/$__ConfigTriplet"
+
+build_native "$__HostOS" "$__HostArch" "$__ProjectRoot" "$__IntermediatesDir" "" "$__CMakeArgs" "DNMD"
diff --git a/src/native/dnmd/configure.cmake b/src/native/dnmd/configure.cmake
new file mode 100644
index 0000000000000..9dbcbea1bdb61
--- /dev/null
+++ b/src/native/dnmd/configure.cmake
@@ -0,0 +1,37 @@
+# Compiler configurations
+
+set(CMAKE_C_STANDARD 11)
+set(CMAKE_C_STANDARD_REQUIRED ON)
+set(CMAKE_C_EXTENSIONS OFF)
+
+set(CMAKE_CXX_STANDARD 14)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+set(CMAKE_CXX_EXTENSIONS OFF)
+
+#
+# Configure CMake for platforms
+#
+if(WIN32)
+ add_compile_definitions(BUILD_WINDOWS=1)
+elseif(APPLE)
+ add_compile_definitions(BUILD_MACOS=1)
+
+ set(CMAKE_BUILD_WITH_INSTALL_NAME_DIR ON)
+ set(CMAKE_INSTALL_NAME_DIR "@rpath")
+ set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE)
+ set(CMAKE_INSTALL_RPATH "@loader_path")
+else()
+ set(CMAKE_INSTALL_RPATH "$ORIGIN")
+ add_compile_definitions(BUILD_UNIX=1)
+endif()
+
+#
+# Agnostic compiler/platform settings
+#
+add_compile_definitions(__STDC_WANT_LIB_EXT1__=1) # https://en.cppreference.com/w/c/error#Bounds_checking
+
+option(DNMD_ENABLE_PROFILING OFF)
+
+if (DNMD_ENABLE_PROFILING AND MSVC)
+ add_link_options(/PROFILE)
+endif()
\ No newline at end of file
diff --git a/src/native/dnmd/dnmd.proj b/src/native/dnmd/dnmd.proj
new file mode 100644
index 0000000000000..8203196fc57bf
--- /dev/null
+++ b/src/native/dnmd/dnmd.proj
@@ -0,0 +1,32 @@
+
+
+ true
+ $(BaseIntermediateOutputPath)/$(TargetOS).$(TargetArchitecture).$(Configuration)
+
+
+
+
+ -cross
+ -ninja
+ -msbuild
+ -portablebuild=false
+ bash ./build-native.sh
+ ./build-native.cmd
+
+
+
+
+
+
+
+
+
+
+ dnmd-$([System.String]::new('%(RecursiveDir)').TrimEnd('\\').TrimEnd('/')).ctestxml
+
+
+
+
+
diff --git a/src/native/dnmd/external/dncp b/src/native/dnmd/external/dncp
new file mode 160000
index 0000000000000..fd609bbbaa4a2
--- /dev/null
+++ b/src/native/dnmd/external/dncp
@@ -0,0 +1 @@
+Subproject commit fd609bbbaa4a2416992460d5558b9c680cc98204
diff --git a/src/native/dnmd/readme.md b/src/native/dnmd/readme.md
new file mode 100644
index 0000000000000..35b291bb699cd
--- /dev/null
+++ b/src/native/dnmd/readme.md
@@ -0,0 +1,58 @@
+# .NET MetaData – DNMD
+
+DNMD represents a suites of tools for manipulating [ECMA-335][ecma_335] defined metadata. It designed to be written in unmanaged code (that is, C/C++) in a modern style. This doesn't mean it is intended to rely on the latest features or libraries. Rather it is written to use canonical C and C++ in a manner that is clear.
+
+DNMD provides the following tools:
+
+- `dnmd` - A static library with no external dependencies that represents the lowest level of reading ECMA-335.
+- `dnmd_interfaces` - A shared library (`.dll`|`.dylib`|`.so`) that consumes `dnmd` and provides higher level .NET APIs. At present the following interfaces are provided:
+ - [`IMetaDataDispenser`][api_dispenser]
+ - [`IMetaDataImport`][api_import] / [`IMetaDataImport2`][api_import2]
+ - [`IMetaDataAssemblyImport`][api_assemblyimport]
+- `dnmd_interfaces_static` - A static library version of `dnmd_interfaces`.
+- `mddump` - Utility for dumping ECMA-335 tables.
+- `mdmerge` - Utility for merging EnC deltas into ECMA-335 tables.
+
+The primary goal of DNMD is to explore the benefits of a rewrite of the metadata APIs in the .NET runtime. The rewrite has the following constraints:
+
+- Must be sharable across any existing .NET runtime implementation.
+- Must be cross-platform with minimal OS abstraction layering.
+- Represent scenarios that are relevant to modern .NET (that is, .NET 6+).
+
+## Requirements (minimum)
+
+- [CMake](https://cmake.org/download/) 3.20
+
+- C11 and C++14 compliant compilers
+
+## Build
+
+> `cmake -S . -B artifacts`
+
+> `cmake --build artifacts --target install`
+
+## Test
+
+The `test/` directory contains all product tests. The native components for
+DNMD should be built first. See the Build section.
+
+Tests can be run using `ctest --test-dir artifacts`.
+
+Testing correctness defers to the current implementation of the relevant interface
+defined in the newest .NET runtime the test finds via normal runtime discovery mechanisms (for example, `IMetaDataImport`).
+The approach is to pass identical arguments to the current implementation and the
+implementation in this repo. The return argument and all out arguments are then
+compared for equality. In some cases pointers are returned so the pointer is dereferenced
+and then hashed.
+
+# Additional Resources
+
+[ECMA-335 specification][ecma_335]
+
+
+[ecma_335]: https://www.ecma-international.org/publications-and-standards/standards/ecma-335/
+
+[api_dispenser]: https://learn.microsoft.com/dotnet/framework/unmanaged-api/metadata/imetadatadispenser-interface
+[api_import]: https://learn.microsoft.com/dotnet/framework/unmanaged-api/metadata/imetadataimport-interface
+[api_import2]: https://learn.microsoft.com/dotnet/framework/unmanaged-api/metadata/imetadataimport2-interface
+[api_assemblyimport]: https://learn.microsoft.com/dotnet/framework/unmanaged-api/metadata/imetadataassemblyimport-interface
\ No newline at end of file
diff --git a/src/native/dnmd/src/CMakeLists.txt b/src/native/dnmd/src/CMakeLists.txt
new file mode 100644
index 0000000000000..1cea1e964a949
--- /dev/null
+++ b/src/native/dnmd/src/CMakeLists.txt
@@ -0,0 +1,29 @@
+# Configure the compiler
+include(../configure.cmake)
+
+include_directories(BEFORE ./inc)
+
+add_subdirectory(dnmd)
+
+if (DNMD_BUILD_INTERFACES)
+ add_subdirectory(interfaces)
+endif()
+
+if (DNMD_BUILD_TOOLS)
+ add_subdirectory(mddump)
+ add_subdirectory(mdmerge)
+endif()
+
+if (DNMD_INSTALL)
+ include(CMakePackageConfigHelpers)
+ configure_package_config_file(config.cmake.in
+ ${CMAKE_CURRENT_BINARY_DIR}/dnmd-config.cmake
+ INSTALL_DESTINATION lib/cmake/dnmd)
+ write_basic_package_version_file(
+ ${CMAKE_CURRENT_BINARY_DIR}/dnmd-config-version.cmake
+ VERSION 1.0.0
+ COMPATIBILITY SameMajorVersion )
+ install(FILES ${CMAKE_CURRENT_BINARY_DIR}/dnmd-config.cmake
+ ${CMAKE_CURRENT_BINARY_DIR}/dnmd-config-version.cmake
+ DESTINATION lib/cmake/dnmd)
+endif()
diff --git a/src/native/dnmd/src/config.cmake.in b/src/native/dnmd/src/config.cmake.in
new file mode 100644
index 0000000000000..8a6d327069b18
--- /dev/null
+++ b/src/native/dnmd/src/config.cmake.in
@@ -0,0 +1,6 @@
+@PACKAGE_INIT@
+
+include("${CMAKE_CURRENT_LIST_DIR}/dnmdlib.cmake")
+include("${CMAKE_CURRENT_LIST_DIR}/dnmdinterfaces.cmake")
+
+check_required_components(dnmd)
diff --git a/src/native/dnmd/src/dnmd/CMakeLists.txt b/src/native/dnmd/src/dnmd/CMakeLists.txt
new file mode 100644
index 0000000000000..4f6ac39441013
--- /dev/null
+++ b/src/native/dnmd/src/dnmd/CMakeLists.txt
@@ -0,0 +1,55 @@
+set(SOURCES
+ access.c
+ bytes.c
+ deltas.c
+ editor.c
+ entry.c
+ query.c
+ streams.c
+ tables.c
+ write.c
+)
+
+set(HEADERS
+ ../inc/dnmd.h
+ ./internal.h
+)
+
+add_library(dnmd
+ STATIC
+ ${SOURCES}
+ ${HEADERS}
+)
+add_library(dnmd_pdb
+ STATIC
+ ${SOURCES}
+ ${HEADERS}
+)
+
+target_compile_definitions(dnmd_pdb PUBLIC DNMD_PORTABLE_PDB)
+target_sources(dnmd_pdb PRIVATE ../inc/dnmd_pdb.h pdb_blobs.c)
+set_target_properties(dnmd_pdb PROPERTIES EXPORT_NAME pdb)
+
+add_library(dnmd::dnmd ALIAS dnmd)
+add_library(dnmd::pdb ALIAS dnmd_pdb)
+
+target_include_directories(dnmd PUBLIC $)
+target_include_directories(dnmd_pdb PUBLIC $)
+
+set_target_properties(dnmd PROPERTIES
+ PUBLIC_HEADER "${CMAKE_CURRENT_SOURCE_DIR}/../inc/dnmd.h;${CMAKE_CURRENT_SOURCE_DIR}/../inc/dnmd.hpp"
+ POSITION_INDEPENDENT_CODE ON)
+
+set_target_properties(dnmd_pdb PROPERTIES
+ PUBLIC_HEADER "${CMAKE_CURRENT_SOURCE_DIR}/../inc/dnmd.h;${CMAKE_CURRENT_SOURCE_DIR}/../inc/dnmd.hpp;${CMAKE_CURRENT_SOURCE_DIR}/../inc/dnmd_pdb.h"
+ POSITION_INDEPENDENT_CODE ON)
+
+if (DNMD_INSTALL)
+ install(TARGETS dnmd dnmd_pdb EXPORT dnmd
+ PUBLIC_HEADER DESTINATION include
+ ARCHIVE DESTINATION lib
+ LIBRARY DESTINATION lib
+ RUNTIME DESTINATION bin)
+
+ install(EXPORT dnmd NAMESPACE dnmd:: FILE dnmdlib.cmake DESTINATION lib/cmake/dnmd)
+endif()
diff --git a/src/native/dnmd/src/dnmd/access.c b/src/native/dnmd/src/dnmd/access.c
new file mode 100644
index 0000000000000..479747f94e604
--- /dev/null
+++ b/src/native/dnmd/src/dnmd/access.c
@@ -0,0 +1,150 @@
+#include "internal.h"
+
+bool create_access_context(mdcursor_t* cursor, col_index_t col_idx, bool make_writable, access_cxt_t* acxt)
+{
+ mdtable_t* table = CursorTable(cursor);
+ if (table == NULL)
+ return false;
+
+ uint32_t row = CursorRow(cursor);
+ if (row == 0 || row > table->row_count)
+ return false;
+
+ uint8_t idx = col_to_index(col_idx, table);
+ assert(idx < table->column_count);
+
+ // Metadata row indexing is 1-based.
+ row--;
+
+ mdtcol_t col = table->column_details[idx];
+
+ uint32_t offset_to_table_data = row * table->row_size_bytes + ExtractOffset(col);
+#ifndef NDEBUG
+ size_t len = (col & mdtc_b2) ? 2 : 4;
+ assert(offset_to_table_data + len <= table->data.size);
+#endif
+
+ acxt->table = table;
+ acxt->data = table->data.ptr + offset_to_table_data;
+ acxt->col_details = col;
+ if (make_writable)
+ {
+ acxt->writable_data = get_writable_table_data(table, make_writable) + offset_to_table_data;
+ }
+ else
+ {
+ acxt->writable_data = NULL;
+ }
+ return true;
+}
+
+bool read_column_data(access_cxt_t* acxt, uint32_t* data)
+{
+ assert(acxt != NULL && acxt->data != NULL && data != NULL);
+
+ uint8_t const* table_data = acxt->data;
+
+ if ((acxt->col_details & mdtc_b4) == mdtc_b4)
+ {
+ size_t len = 4;
+ return read_u32(&table_data, &len, data);
+ }
+ else
+ {
+ size_t len = 2;
+ uint16_t value;
+ if (!read_u16(&table_data, &len, &value))
+ return false;
+
+ *data = value;
+ return true;
+ }
+}
+
+bool write_column_data(access_cxt_t* acxt, uint32_t data)
+{
+ assert(acxt != NULL && acxt->writable_data != NULL);
+ uint8_t* table_data = acxt->writable_data;
+ if ((acxt->col_details & mdtc_b4) == mdtc_b4)
+ {
+ size_t len = 4;
+ return write_u32(&table_data, &len, data);
+ }
+ else
+ {
+ size_t len = 2;
+ return write_u16(&table_data, &len, (uint16_t)data);
+ }
+}
+
+bool create_bulk_access_context(mdcursor_t* cursor, col_index_t col_idx, uint32_t row_count, bulk_access_cxt_t* acxt)
+{
+ assert(acxt != NULL);
+ mdtable_t* table = CursorTable(cursor);
+ if (table == NULL)
+ return false;
+
+ uint32_t row = CursorRow(cursor);
+ if (row == 0 || row > table->row_count)
+ return false;
+
+ uint8_t idx = col_to_index(col_idx, table);
+ assert(idx < table->column_count);
+
+ // Metadata row indexing is 1-based.
+ row--;
+ acxt->table = table;
+ acxt->col_details = table->column_details[idx];
+
+ // Compute the offset into the first row.
+ uint32_t offset = ExtractOffset(acxt->col_details);
+
+ acxt->start = acxt->data = table->data.ptr + (row * table->row_size_bytes) + offset;
+
+ // Compute the beginning of the row after the last valid row.
+ uint32_t last_row = row + row_count;
+ if (last_row > table->row_count)
+ last_row = table->row_count;
+ acxt->end = table->data.ptr + (last_row * table->row_size_bytes);
+
+ // Limit the data read to the width of the column
+ acxt->data_len_col = (acxt->col_details & mdtc_b2) ? 2 : 4;
+ acxt->data_len = acxt->data_len_col;
+
+ // Compute the next row stride. Take the total length and substract
+ // the data length for the column to get at the next row's column.
+ acxt->next_row_stride = table->row_size_bytes - acxt->data_len_col;
+ return true;
+}
+
+bool read_column_data_and_advance(bulk_access_cxt_t* acxt, uint32_t* data)
+{
+ assert(acxt != NULL && data != NULL);
+ *data = 0;
+
+ if ((acxt->col_details & mdtc_b4) == mdtc_b4)
+ {
+ return read_u32(&acxt->data, &acxt->data_len, data);
+ }
+ else
+ {
+ uint16_t value;
+ if (!read_u16(&acxt->data, &acxt->data_len, &value))
+ return false;
+
+ *data = value;
+ return true;
+ }
+}
+
+bool next_row(bulk_access_cxt_t* acxt)
+{
+ assert(acxt != NULL);
+ // We will only traverse correctly if we've already read the column in this row.
+ assert(acxt->data_len == 0);
+ acxt->data += acxt->next_row_stride;
+
+ // Restore the data length of the column data.
+ acxt->data_len = acxt->data_len_col;
+ return acxt->data < acxt->end;
+}
diff --git a/src/native/dnmd/src/dnmd/bytes.c b/src/native/dnmd/src/dnmd/bytes.c
new file mode 100644
index 0000000000000..811afe409c86d
--- /dev/null
+++ b/src/native/dnmd/src/dnmd/bytes.c
@@ -0,0 +1,401 @@
+#include "internal.h"
+
+uint32_t align_to(uint32_t val, uint32_t align)
+{
+ assert(align != 0);
+ return (val + (align - 1)) & ~(align - 1);
+}
+
+// Brian Kernighan's algorithm for counting bits
+size_t count_set_bits(uint64_t val)
+{
+ size_t count = 0;
+ while (val)
+ {
+ val = val & (val - 1);
+ count++;
+ }
+ return count;
+}
+
+bool advance_stream(uint8_t const** data, size_t* data_len, size_t b)
+{
+ assert(data != NULL && data_len != NULL);
+ if (*data_len < b)
+ return false;
+
+ *data += b;
+ *data_len -= b;
+ return true;
+}
+
+bool advance_output_stream(uint8_t** data, size_t* data_len, size_t b)
+{
+ return advance_stream((uint8_t const**)data, data_len, b);
+}
+
+// This is a little-endian format in the physical form.
+static bool read_le(uint8_t const** data, size_t* data_len, void* o, size_t o_size)
+{
+ assert(data != NULL && data_len != NULL && o != NULL);
+ if (*data_len < o_size)
+ return false;
+
+ uint64_t integer;
+ uint8_t const* d = *data;
+ switch (o_size)
+ {
+ case 8:
+ integer = (uint64_t)*d++;
+ integer |= (uint64_t)*d++ << 8;
+ integer |= (uint64_t)*d++ << 16;
+ integer |= (uint64_t)*d++ << 24;
+ integer |= (uint64_t)*d++ << 32;
+ integer |= (uint64_t)*d++ << 40;
+ integer |= (uint64_t)*d++ << 48;
+ integer |= (uint64_t)*d++ << 56;
+ break;
+ case 4:
+ integer = (uint64_t)*d++;
+ integer |= (uint64_t)*d++ << 8;
+ integer |= (uint64_t)*d++ << 16;
+ integer |= (uint64_t)*d++ << 24;
+ break;
+ case 2:
+ integer = (uint64_t)*d++;
+ integer |= (uint64_t)*d++ << 8;
+ break;
+ case 1:
+ integer = (uint64_t)*d++;
+ break;
+ default:
+ return false;
+ }
+
+ memcpy(o, &integer, o_size);
+ *data = d;
+ *data_len -= o_size;
+ return true;
+}
+
+// This is a little-endian format in the physical form.
+static bool write_le(uint8_t** data, size_t* data_len, uint64_t o, size_t o_size_to_write)
+{
+ assert(data != NULL && data_len != NULL);
+ if (*data_len < o_size_to_write)
+ return false;
+
+ uint8_t* d = *data;
+ switch(o_size_to_write)
+ {
+ case 8:
+ *d++ = (uint8_t)o;
+ *d++ = (uint8_t)(o >> 8);
+ *d++ = (uint8_t)(o >> 16);
+ *d++ = (uint8_t)(o >> 24);
+ *d++ = (uint8_t)(o >> 32);
+ *d++ = (uint8_t)(o >> 40);
+ *d++ = (uint8_t)(o >> 48);
+ *d++ = (uint8_t)(o >> 56);
+ break;
+ case 4:
+ *d++ = (uint8_t)o;
+ *d++ = (uint8_t)(o >> 8);
+ *d++ = (uint8_t)(o >> 16);
+ *d++ = (uint8_t)(o >> 24);
+ break;
+ case 2:
+ *d++ = (uint8_t)o;
+ *d++ = (uint8_t)(o >> 8);
+ break;
+ case 1:
+ *d++ = (uint8_t)o;
+ break;
+ default:
+ return false;
+ }
+ *data = d;
+ *data_len -= o_size_to_write;
+ return true;
+}
+
+bool read_u8(uint8_t const** data, size_t* data_len, uint8_t* o)
+{
+ return read_le(data, data_len, o, sizeof(*o));
+}
+
+bool read_i8(uint8_t const** data, size_t* data_len, int8_t* o)
+{
+ return read_le(data, data_len, o, sizeof(*o));
+}
+
+// MSVC doesn't optimize away the implementation on Little-Endian platforms,
+// so manually provide an optimized implementation for MSVC.
+#ifndef _MSC_VER
+bool read_u16(uint8_t const** data, size_t* data_len, uint16_t* o)
+{
+ return read_le(data, data_len, o, sizeof(*o));
+}
+
+bool read_i16(uint8_t const** data, size_t* data_len, int16_t* o)
+{
+ return read_le(data, data_len, o, sizeof(*o));
+}
+
+bool read_u32(uint8_t const** data, size_t* data_len, uint32_t* o)
+{
+ return read_le(data, data_len, o, sizeof(*o));
+}
+
+bool read_i32(uint8_t const** data, size_t* data_len, int32_t* o)
+{
+ return read_le(data, data_len, o, sizeof(*o));
+}
+
+bool read_u64(uint8_t const** data, size_t* data_len, uint64_t* o)
+{
+ return read_le(data, data_len, o, sizeof(*o));
+}
+
+bool read_i64(uint8_t const** data, size_t* data_len, int64_t* o)
+{
+ return read_le(data, data_len, o, sizeof(*o));
+}
+
+#else
+bool read_u16(uint8_t const** data, size_t* data_len, uint16_t* o)
+{
+ if (*data_len < sizeof(*o))
+ return false;
+
+ memcpy(o, *data, sizeof(*o));
+ *data += sizeof(*o);
+ *data_len -= sizeof(*o);
+ return true;
+}
+
+bool read_i16(uint8_t const** data, size_t* data_len, int16_t* o)
+{
+ if (*data_len < sizeof(*o))
+ return false;
+
+ memcpy(o, *data, sizeof(*o));
+ *data += sizeof(*o);
+ *data_len -= sizeof(*o);
+ return true;
+}
+
+bool read_u32(uint8_t const** data, size_t* data_len, uint32_t* o)
+{
+ if (*data_len < sizeof(*o))
+ return false;
+
+ memcpy(o, *data, sizeof(*o));
+ *data += sizeof(*o);
+ *data_len -= sizeof(*o);
+ return true;
+}
+
+bool read_i32(uint8_t const** data, size_t* data_len, int32_t* o)
+{
+ if (*data_len < sizeof(*o))
+ return false;
+
+ memcpy(o, *data, sizeof(*o));
+ *data += sizeof(*o);
+ *data_len -= sizeof(*o);
+ return true;
+}
+
+bool read_u64(uint8_t const** data, size_t* data_len, uint64_t* o)
+{
+ if (*data_len < sizeof(*o))
+ return false;
+
+ memcpy(o, *data, sizeof(*o));
+ *data += sizeof(*o);
+ *data_len -= sizeof(*o);
+ return true;
+}
+
+bool read_i64(uint8_t const** data, size_t* data_len, int64_t* o)
+{
+ if (*data_len < sizeof(*o))
+ return false;
+
+ memcpy(o, *data, sizeof(*o));
+ *data += sizeof(*o);
+ *data_len -= sizeof(*o);
+ return true;
+}
+
+#endif
+
+bool write_u8(uint8_t** data, size_t* data_len, uint8_t o)
+{
+ return write_le(data, data_len, o, sizeof(o));
+}
+
+bool write_i8(uint8_t** data, size_t* data_len, int8_t o)
+{
+ return write_le(data, data_len, o, sizeof(o));
+}
+
+bool write_u16(uint8_t** data, size_t* data_len, uint16_t o)
+{
+ return write_le(data, data_len, o, sizeof(o));
+}
+
+bool write_i16(uint8_t** data, size_t* data_len, int16_t o)
+{
+ return write_le(data, data_len, o, sizeof(o));
+}
+
+bool write_u32(uint8_t** data, size_t* data_len, uint32_t o)
+{
+ return write_le(data, data_len, o, sizeof(o));
+}
+
+bool write_i32(uint8_t** data, size_t* data_len, int32_t o)
+{
+ return write_le(data, data_len, o, sizeof(o));
+}
+
+bool write_u64(uint8_t** data, size_t* data_len, uint64_t o)
+{
+ return write_le(data, data_len, o, sizeof(o));
+}
+
+bool write_i64(uint8_t** data, size_t* data_len, int64_t o)
+{
+ return write_le(data, data_len, o, sizeof(o));
+}
+
+// II.23.2
+// This is a big-endian format in the physical form.
+bool decompress_u32(uint8_t const** data, size_t* data_len, uint32_t* o)
+{
+ assert(data != NULL && data_len != NULL && o != NULL);
+ uint8_t const* s = *data;
+ assert(s != NULL);
+
+ uint32_t val;
+
+ // The valid leading bits are 00, 10, and 110.
+ // All others are invalid.
+ // PERF: Check for 00 vs 10 first as we get better codegen
+ // on Intel/AMD processors (shorter instruction sequences and better branch prediction).
+ if ((*s & 0x80) == 0x00)
+ {
+ if (*data_len < 1)
+ return false;
+
+ *data_len -= 1;
+ val = *s++;
+ }
+ else if ((*s & 0xC0) == 0x80)
+ {
+ if (*data_len < 2)
+ return false;
+
+ *data_len -= 2;
+ val = ((*s++ & 0x3f) << 8);
+ val |= *s++;
+ }
+ else if ((*s & 0xE0) == 0xC0)
+ {
+ if (*data_len < 4)
+ return false;
+
+ *data_len -= 4;
+ val = ((*s++ & 0x1f) << 24);
+ val |= (*s++ << 16);
+ val |= (*s++ << 8);
+ val |= *s++;
+ }
+ else
+ {
+ return false;
+ }
+
+ *o = val;
+ *data = s;
+ return true;
+}
+
+enum {
+ SIGN_MASK_ONEBYTE = 0xffffffc0, // Mask the same size as the missing bits.
+ SIGN_MASK_TWOBYTE = 0xffffe000, // Mask the same size as the missing bits.
+ SIGN_MASK_FOURBYTE = 0xf0000000, // Mask the same size as the missing bits.
+};
+
+// II.23.2
+// This is a big-endian format in the physical form.
+bool decompress_i32(uint8_t const** data, size_t* data_len, int32_t* o)
+{
+ uint32_t unsigned_value;
+ size_t original_data_len = *data_len;
+ if (!decompress_u32(data, data_len, &unsigned_value))
+ return false;
+
+ bool is_signed = (unsigned_value & 1) != 0;
+ unsigned_value >>= 1;
+ if (is_signed)
+ {
+ switch (original_data_len - *data_len)
+ {
+ // Sign extend the value based on the number of bytes used.
+ case 1:
+ unsigned_value |= SIGN_MASK_ONEBYTE;
+ break;
+ case 2:
+ unsigned_value |= SIGN_MASK_TWOBYTE;
+ break;
+ case 4:
+ unsigned_value |= SIGN_MASK_FOURBYTE;
+ break;
+ default:
+ assert(false);
+ return false;
+ }
+ }
+ *o = (int32_t)unsigned_value;
+ return true;
+}
+
+// II.23.2
+// This is a big-endian format in the physical form.
+bool compress_u32(uint32_t data, uint8_t* compressed, size_t* compressed_len)
+{
+ assert(compressed != NULL && compressed_len != NULL);
+ if (data <= 0x7f)
+ {
+ if (*compressed_len < 1)
+ return false;
+ compressed[0] = (uint8_t)data;
+ *compressed_len = 1;
+ }
+ else if (data <= 0x3fff)
+ {
+ if (*compressed_len < 2)
+ return false;
+ compressed[0] = (uint8_t)((data >> 8) | 0x80);
+ compressed[1] = (uint8_t)data;
+ *compressed_len = 2;
+ }
+ else if (data <= 0x1fffffff)
+ {
+ if (*compressed_len < 4)
+ return false;
+ compressed[0] = (uint8_t)((data >> 24) | 0xc0);
+ compressed[1] = (uint8_t)(data >> 16);
+ compressed[2] = (uint8_t)(data >> 8);
+ compressed[3] = (uint8_t)data;
+ *compressed_len = 4;
+ }
+ else
+ {
+ return false;
+ }
+ return true;
+}
diff --git a/src/native/dnmd/src/dnmd/deltas.c b/src/native/dnmd/src/dnmd/deltas.c
new file mode 100644
index 0000000000000..f636592cf7a79
--- /dev/null
+++ b/src/native/dnmd/src/dnmd/deltas.c
@@ -0,0 +1,391 @@
+#include "internal.h"
+
+static bool append_heaps_from_delta(mdcxt_t* cxt, mdcxt_t* delta)
+{
+ assert(delta != NULL);
+
+ if (!append_heap(cxt, delta, mdtc_hstring))
+ return false;
+
+ if (!append_heap(cxt, delta, mdtc_hguid))
+ return false;
+
+ if (!append_heap(cxt, delta, mdtc_hblob))
+ return false;
+
+ if (!append_heap(cxt, delta, mdtc_hus))
+ return false;
+
+ return true;
+}
+
+typedef enum
+{
+ dops_Default = 0,
+ dops_MethodCreate,
+ dops_FieldCreate,
+ dops_ParamCreate,
+ dops_PropertyCreate,
+ dops_EventCreate,
+} delta_ops_t;
+
+#define NO_TOKENS_IN_GROUP (uint32_t)(-1)
+
+// Tokens in the EncMap and EncLog tables may have the high bit set to indicate that they aren't true token references.
+// Deltas produced by CoreCLR, CLR, and ILASM will have this bit set. Roslyn does not utilize this bit.
+// We'll strip this high bit if it is set since we don't need it.
+#define RemoveRecordBit(x) (x & 0x7fffffff)
+
+typedef struct map_table_group__
+{
+ mdcursor_t start;
+ uint32_t count;
+} map_table_group_t;
+
+typedef struct enc_token_map__
+{
+ map_table_group_t map_cur_by_table[MDTABLE_MAX_COUNT];
+} enc_token_map_t;
+
+static bool initialize_token_map(mdtable_t* map, enc_token_map_t* token_map)
+{
+ assert(map != NULL);
+ assert(token_map != NULL);
+ assert(map->table_id == mdtid_ENCMap);
+ for (uint32_t i = 0; i < MDTABLE_MAX_COUNT; ++i)
+ {
+ token_map->map_cur_by_table[i].count = NO_TOKENS_IN_GROUP;
+ }
+
+ // If we don't have any entries in the map table, then we don't have any remapped tokens.
+ // The initialization we've already done by this point is sufficient.
+ if (map->row_count == 0)
+ return true;
+
+ // The EncMap table is grouped by token type and sorted by the order of the rows in the tables in the delta.
+ mdcursor_t map_cur = create_cursor(map, 1);
+
+ mdtable_id_t previous_table_id = mdtid_Unused;
+ for (uint32_t i = 0; i < map->row_count; (void)md_cursor_next(&map_cur), ++i)
+ {
+ mdToken tk;
+ if (!md_get_column_value_as_constant(map_cur, mdtENCMap_Token, &tk))
+ return false;
+
+ mdtable_id_t table_id = ExtractTokenType(RemoveRecordBit(tk));
+
+ if (table_id < mdtid_First || table_id >= mdtid_End)
+ return false;
+
+ if (token_map->map_cur_by_table[table_id].count == NO_TOKENS_IN_GROUP)
+ {
+ token_map->map_cur_by_table[table_id].start = map_cur;
+ token_map->map_cur_by_table[table_id].count = 1;
+ }
+ else if (previous_table_id != table_id)
+ {
+ // If the set of remapped tokens for this table has already been started, then the previous token
+ // must be from the same table as the current token (tokens are grouped by table).
+ return false;
+ }
+ else
+ {
+ token_map->map_cur_by_table[table_id].count++;
+ }
+
+ previous_table_id = table_id;
+ }
+
+ return true;
+}
+
+static bool resolve_token(enc_token_map_t* token_map, mdToken referenced_token, mdhandle_t delta_image, mdcursor_t* row_in_delta)
+{
+ mdtable_id_t type = ExtractTokenType(referenced_token);
+
+ if (type < mdtid_First || type >= mdtid_End)
+ return false;
+
+ uint32_t rid = RidFromToken(referenced_token);
+
+ if (rid == 0)
+ return false;
+
+ // If we don't have any EncMap entries for this table,
+ // then the token in the EncLog is the token we need to look up in the delta image to get the delta info to apply.
+ if (token_map->map_cur_by_table[type].count == NO_TOKENS_IN_GROUP)
+ {
+ return md_token_to_cursor(delta_image, referenced_token, row_in_delta);
+ }
+
+ mdcursor_t map_record = token_map->map_cur_by_table[type].start;
+ for (uint32_t i = 0; i < token_map->map_cur_by_table[type].count; md_cursor_next(&map_record), i++)
+ {
+ mdToken mappedToken;
+ if (!md_get_column_value_as_constant(map_record, mdtENCMap_Token, &mappedToken))
+ return false;
+
+ assert((mdtable_id_t)ExtractTokenType(RemoveRecordBit(mappedToken)) == type);
+ if (RidFromToken(mappedToken) == rid)
+ {
+ return md_token_to_cursor(delta_image, TokenFromRid(i + 1, CreateTokenType(type)), row_in_delta);
+ }
+ }
+
+ // If we have a set of remapped tokens for a table,
+ // we will remap all tokens in the EncLog.
+ return false;
+}
+
+static bool add_list_target_row(mdcursor_t parent, col_index_t list_col)
+{
+ mdcursor_t new_child_record;
+ if (!md_add_new_row_to_list(parent, list_col, &new_child_record))
+ return false;
+
+ // We've added enough information to the new record to make it valid for sorting purposes.
+ // Commit the row add. We'll fill in the rest of the data in the next record in the EncLog.
+ md_commit_row_add(new_child_record);
+
+ return true;
+}
+
+static bool process_log(mdcxt_t* cxt, mdcxt_t* delta)
+{
+ // The EncMap table is grouped by token type and sorted by the order of the rows in the tables in the delta.
+ mdtable_t* map = &delta->tables[mdtid_ENCMap];
+ enc_token_map_t token_map;
+ if (!initialize_token_map(map, &token_map))
+ return false;
+
+ mdtable_t* log = &delta->tables[mdtid_ENCLog];
+ mdcursor_t log_cur = create_cursor(log, 1);
+ delta_ops_t last_op = dops_Default;
+ for (uint32_t i = 0; i < log->row_count; (void)md_cursor_next(&log_cur), ++i)
+ {
+ mdToken tk;
+ uint32_t op;
+ if (!md_get_column_value_as_constant(log_cur, mdtENCLog_Token, &tk)
+ || !md_get_column_value_as_constant(log_cur, mdtENCLog_Op, &op))
+ {
+ return false;
+ }
+
+ tk = RemoveRecordBit(tk);
+
+ switch ((delta_ops_t)op)
+ {
+ case dops_MethodCreate:
+ {
+ if (ExtractTokenType(tk) != mdtid_TypeDef)
+ return false;
+
+ // By the time we're adding a member to a list, the parent should already be in the image.
+ mdcursor_t parent;
+ if (!md_token_to_cursor(cxt, tk, &parent))
+ return false;
+
+ if (!add_list_target_row(parent, mdtTypeDef_MethodList))
+ return false;
+ break;
+ }
+ case dops_FieldCreate:
+ {
+ if (ExtractTokenType(tk) != mdtid_TypeDef)
+ return false;
+
+ // By the time we're adding a member to a list, the parent should already be in the image.
+ mdcursor_t parent;
+ if (!md_token_to_cursor(cxt, tk, &parent))
+ return false;
+
+ if (!add_list_target_row(parent, mdtTypeDef_FieldList))
+ return false;
+ break;
+ }
+ case dops_ParamCreate:
+ {
+ if (ExtractTokenType(tk) != mdtid_MethodDef)
+ return false;
+
+ // By the time we're adding a member to a list, the parent should already be in the image.
+ mdcursor_t parent;
+ if (!md_token_to_cursor(cxt, tk, &parent))
+ return false;
+
+ // We don't use md_add_new_row_to_sorted_list here because we don't know the value of the Sequence column
+ // until we process the next record in the EncLog.
+ // We'll re-sort the list after we process the next entry in the EncLog.
+ if (!add_list_target_row(parent, mdtMethodDef_ParamList))
+ return false;
+ break;
+ }
+ case dops_PropertyCreate:
+ {
+ if (ExtractTokenType(tk) != mdtid_PropertyMap)
+ return false;
+
+ // By the time we're adding a member to a list, the parent should already be in the image.
+ mdcursor_t parent;
+ if (!md_token_to_cursor(cxt, tk, &parent))
+ return false;
+
+ if (!add_list_target_row(parent, mdtPropertyMap_PropertyList))
+ return false;
+ break;
+ }
+ case dops_EventCreate:
+ {
+ if (ExtractTokenType(tk) != mdtid_EventMap)
+ return false;
+
+ // By the time we're adding a member to a list, the parent should already be in the image.
+ mdcursor_t parent;
+ if (!md_token_to_cursor(cxt, tk, &parent))
+ return false;
+
+ if (!add_list_target_row(parent, mdtEventMap_EventList))
+ return false;
+ break;
+ }
+ case dops_Default:
+ {
+ mdtable_id_t table_id = ExtractTokenType(tk);
+
+ if (table_id < mdtid_First || table_id >= mdtid_End)
+ return false;
+
+ uint32_t rid = RidFromToken(tk);
+
+ if (rid == 0)
+ return false;
+
+ // Resolve the token in the delta image that has the data that we need to copy to the base image.
+ mdcursor_t delta_record;
+ if (!resolve_token(&token_map, tk, delta, &delta_record))
+ return false;
+
+ // Try resolving the original token to determine what row we're editing.
+ // We'll try to look up the row in the base image.
+ // If we fail to resolve the original token, then we aren't editing an existing row,
+ // but instead creating a new row.
+ mdcursor_t record_to_edit;
+ bool edit_record = md_token_to_cursor(cxt, tk, &record_to_edit);
+
+ // We can only add rows directly to the end of the table.
+ // TODO: In the future, we could be smarter
+ // and try to insert a row in the middle of a table to preserve sorting.
+ // For some tables that aren't referred to by other tables, such as CustomAttribute,
+ // we could get much better performance by preserving the sorted behavior.
+ // If the runtime doesn't have any dependency on tokens being stable for these tables,
+ // this optimization may reduce the need for maintaining a manual sorting above the metadata layer.
+ if (!edit_record)
+ {
+ mdtable_t* table = &cxt->tables[table_id];
+
+ // If we're adding a row to a table, then the row we're adding must be the next row in the table.
+ // If it's not, then we're missing some row-add operations that should have happened previously.
+ // The ENC Log is invalid.
+ if (table->row_count != (rid - 1))
+ return false;
+
+ if (!md_append_row(cxt, table_id, &record_to_edit))
+ return false;
+ }
+
+ if (!copy_cursor(record_to_edit, delta_record))
+ return false;
+
+ if (!edit_record)
+ md_commit_row_add(record_to_edit);
+
+ if (last_op == dops_ParamCreate)
+ {
+ // If the last operation we did was a "create parameter" operation,
+ // then we need to ensure that the ParamList is sorted by Sequence.
+ // This ordering is not guaranteed by EnC delta producers,
+ // so we need to enforce it ourselves during delta application.
+ mdcursor_t parent;
+ bool success = md_find_cursor_of_range_element(record_to_edit, &parent);
+ assert(success);
+ (void)success;
+
+ if (!sort_list_by_column(parent, mdtMethodDef_ParamList, mdtParam_Sequence))
+ return false;
+ }
+ // TODO: Write to the ENC Log in cxt to record the change.
+ break;
+ }
+ default:
+ assert(!"Unknown delta operation");
+ return false;
+ }
+
+ last_op = (delta_ops_t)op;
+ }
+
+ return true;
+}
+
+bool merge_in_delta(mdcxt_t* cxt, mdcxt_t* delta)
+{
+ assert(cxt != NULL);
+ assert(delta != NULL && (delta->context_flags & mdc_minimal_delta));
+
+ // Validate metadata versions
+ if (cxt->major_ver != delta->major_ver
+ || cxt->minor_ver != delta->minor_ver)
+ {
+ return false;
+ }
+
+ mdcursor_t base_module = create_cursor(&cxt->tables[mdtid_Module], 1);
+ mdcursor_t delta_module = create_cursor(&delta->tables[mdtid_Module], 1);
+
+ mdguid_t base_mvid;
+ if (!md_get_column_value_as_guid(base_module, mdtModule_Mvid, &base_mvid))
+ return false;
+
+ mdguid_t delta_mvid;
+ if (!md_get_column_value_as_guid(delta_module, mdtModule_Mvid, &delta_mvid))
+ return false;
+
+ // MVIDs must match between base and delta images.
+ if (memcmp(&base_mvid, &delta_mvid, sizeof(mdguid_t)) != 0)
+ return false;
+
+ // The EncBaseId of the delta must equal the EncId of the base image.
+ // This ensures that we are applying deltas in order.
+ mdguid_t enc_id;
+ mdguid_t delta_enc_base_id;
+ if (!md_get_column_value_as_guid(base_module, mdtModule_EncId, &enc_id)
+ || !md_get_column_value_as_guid(delta_module, mdtModule_EncBaseId, &delta_enc_base_id)
+ || memcmp(&enc_id, &delta_enc_base_id, sizeof(mdguid_t)) != 0)
+ {
+ return false;
+ }
+
+ // Merge heaps
+ if (!append_heaps_from_delta(cxt, delta))
+ {
+ return false;
+ }
+
+ // Process delta log
+ if (!process_log(cxt, delta))
+ {
+ return false;
+ }
+
+ // Now that we've applied the delta,
+ // update our Enc IDd to match the delta's ID in preparation for the next delta.
+ // We don't want to manipulate the heap sizes, so we'll pull the heap offset directly from the delta and use that
+ // in the base image.
+ uint32_t new_enc_base_id_offset;
+ if (!get_column_value_as_heap_offset(delta_module, mdtModule_EncId, &new_enc_base_id_offset))
+ return false;
+ if (!set_column_value_as_heap_offset(base_module, mdtModule_EncId, new_enc_base_id_offset))
+ return false;
+
+ return true;
+}
diff --git a/src/native/dnmd/src/dnmd/editor.c b/src/native/dnmd/src/dnmd/editor.c
new file mode 100644
index 0000000000000..753dbd5d01c22
--- /dev/null
+++ b/src/native/dnmd/src/dnmd/editor.c
@@ -0,0 +1,848 @@
+#include "dnmd.h"
+#include "internal.h"
+
+typedef struct mdtable_editor__
+{
+ mddata_t data; // If non-null, points to allocated data for the table.
+ mdtable_t* table; // The read-only table that corresponds to this editor.
+} mdtable_editor_t;
+
+typedef struct md_heap_editor__
+{
+ mddata_t heap; // If non-null, points to allocated data for the heap.
+ mdstream_t* stream; // The read-only stream that corresponds to this editor.
+} md_heap_editor_t;
+
+typedef struct mdeditor__
+{
+ mdcxt_t* cxt; // Non-null is indication of complete initialization
+
+ // Metadata heaps - II.24.2.2
+ md_heap_editor_t strings_heap;
+ md_heap_editor_t guid_heap;
+ md_heap_editor_t blob_heap;
+ md_heap_editor_t user_string_heap;
+ md_heap_editor_t pdb_heap;
+
+ // Metadata tables - II.22
+ mdtable_editor_t* tables;
+} mdeditor_t;
+
+static mdeditor_t* get_editor(mdcxt_t* cxt)
+{
+ if (cxt->editor != NULL)
+ return cxt->editor;
+
+ assert(cxt->editor == NULL);
+ // If we haven't edited yet, initialize the table editor.
+ size_t editor_mem = align_to(sizeof(mdeditor_t), sizeof(void*));
+ size_t table_editor_mem = MDTABLE_MAX_COUNT * align_to(sizeof(mdtable_editor_t), sizeof(void*));
+
+ size_t total_mem = editor_mem + table_editor_mem;
+ uint8_t* mem = (uint8_t*)alloc_mdmem(cxt, total_mem);
+ if (mem == NULL)
+ return NULL;
+
+ // Zero out the memory
+ memset(mem, 0, total_mem);
+
+ // Configure the editor object
+ mdeditor_t* editor = (mdeditor_t*)mem;
+ // Point the read-only view of the heaps at the image heaps.
+ editor->strings_heap.stream = &cxt->strings_heap;
+ editor->guid_heap.stream = &cxt->guid_heap;
+ editor->blob_heap.stream = &cxt->blob_heap;
+ editor->user_string_heap.stream = &cxt->user_string_heap;
+#ifdef DNMD_PORTABLE_PDB
+ editor->pdb_heap.stream = &cxt->pdb;
+#endif // DNMD_PORTABLE_PDB
+
+ mem += editor_mem;
+ editor->tables = (mdtable_editor_t*)mem;
+
+ // Update each table editor to point to its table view.
+ for (mdtable_id_t id = mdtid_First; id < mdtid_End; ++id)
+ {
+ editor->tables[id].table = &cxt->tables[id];
+ }
+
+ // Connect the editor and context.
+ editor->cxt = cxt;
+ cxt->editor = editor;
+ return editor;
+}
+
+bool create_and_fill_indirect_table(mdcxt_t* cxt, mdtable_id_t original_table, mdtable_id_t indirect_table)
+{
+ mdeditor_t* editor = get_editor(cxt);
+ if (editor == NULL)
+ return false;
+
+ // We should only call into here if we don't already have an indirection table.
+ mdtable_t* target_table = editor->tables[indirect_table].table;
+ assert(target_table->cxt == NULL);
+
+ if (!initialize_new_table_details(cxt, indirect_table, target_table))
+ return false;
+
+ target_table->cxt = editor->cxt;
+ // Assert that the indirection table has exactly one column that points back at the original table.
+ // The width can be either a short or wide column.
+ assert(target_table->column_count == 1 && (target_table->column_details[0] & ~mdtc_widthmask) == (InsertTable(original_table) | mdtc_idx_table));
+
+ // If we're allocating an indirection table, then we're about to add new rows to the original table.
+ // Allocate more space than we need for the rows we're copying over to be able to handle adding new rows.
+ size_t allocation_space = target_table->row_size_bytes * editor->tables[original_table].table->row_count * 2;
+ void* mem = alloc_mdmem(editor->cxt, allocation_space);
+ if (mem == NULL)
+ return false;
+
+ editor->tables[indirect_table].data.ptr = mem;
+ editor->tables[indirect_table].data.size = allocation_space;
+ target_table->data.ptr = editor->tables[indirect_table].data.ptr;
+ target_table->data.size = target_table->row_size_bytes * editor->tables[original_table].table->row_count;
+
+ // The indirection table will initially have each row pointing to the matching row in the original table.
+ uint8_t* table_data = editor->tables[indirect_table].data.ptr;
+ size_t table_len = editor->tables[indirect_table].data.size;
+ for (uint32_t i = 0; i < editor->tables[original_table].table->row_count; i++)
+ {
+ if (target_table->column_details[0] & mdtc_b2)
+ {
+ assert(i + 1 <= UINT16_MAX);
+ write_u16(&table_data, &table_len, (uint16_t)i + 1);
+ }
+ else
+ write_u32(&table_data, &table_len, i + 1);
+ }
+
+ target_table->row_count = editor->tables[original_table].table->row_count;
+ target_table->cxt = editor->cxt;
+ return true;
+}
+
+uint8_t* get_writable_table_data(mdtable_t* table, bool make_writable)
+{
+ mdeditor_t* editor = get_editor(table->cxt);
+ if (editor == NULL)
+ return NULL;
+
+ mddata_t* table_data = &editor->tables[table->table_id].data;
+
+ if (table_data->ptr == NULL && make_writable)
+ {
+ // If we're trying to get writable data for a table that has not been edited,
+ // then we need to allocate space for it and copy the contents for editing.
+ // TODO: Should we allocate more space than the table currently uses to ensure
+ // immediate table growth doesn't require a realloc?
+ void* mem = alloc_mdmem(table->cxt, table->data.size);
+ if (mem == NULL)
+ return NULL;
+ table_data->ptr = mem;
+ if (table_data->ptr == NULL)
+ return NULL;
+ table_data->size = table->data.size;
+ memcpy(table_data->ptr, table->data.ptr, table->data.size);
+ table->data.ptr = table_data->ptr;
+ }
+
+ return table_data->ptr;
+}
+
+// Copy a row from one table to another.
+// The rows must have an identical number of columns and the columns must have the same definition other than column width.
+// This function does not ensure that the destination table is still sorted after the copy, so this should only be used in cases
+// where a table will keep the same sort order after the copy (such as resizing a table).
+static bool copy_row(uint8_t** dest, size_t* dest_len, mdtcol_t const* dest_cols, uint8_t const** src, size_t* src_len, mdtcol_t const* src_cols, uint8_t num_cols)
+{
+ for (uint8_t col_index = 0; col_index < num_cols; col_index++)
+ {
+ // The source and destination column details can only differ by storage width.
+ assert((src_cols[col_index] & ~mdtc_widthmask) == (dest_cols[col_index] & ~mdtc_widthmask));
+
+ uint32_t data = 0;
+
+ if (src_cols[col_index] & mdtc_b2)
+ {
+ if (!read_u16(src, src_len, (uint16_t*)&data))
+ return false;
+ }
+ else
+ {
+ if (!read_u32(src, src_len, &data))
+ return false;
+ }
+
+ if (dest_cols[col_index] & mdtc_b2)
+ {
+ if (!write_u16(dest, dest_len, (uint16_t)data))
+ return false;
+ }
+ else
+ {
+ if (!write_u32(dest, dest_len, data))
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static bool set_column_size_for_max_row_count(mdeditor_t* editor, mdtable_t* table, mdtable_id_t updated_table, mdtcol_t updated_heap, uint32_t new_max_row_count)
+{
+ assert(table->column_count <= MDTABLE_MAX_COLUMN_COUNT);
+ assert((mdtid_First <= updated_table && updated_table <= mdtid_End) || (updated_table == mdtid_Unused && (ExtractHeapType(updated_heap) != 0)));
+ mdtcol_t new_column_details[MDTABLE_MAX_COLUMN_COUNT];
+
+ uint32_t initial_row_count;
+ if (updated_table != mdtid_Unused)
+ {
+ initial_row_count = editor->tables[updated_table].table->row_count;
+ }
+ else if (updated_heap == mdtc_hguid)
+ {
+ mdstream_t* stream = get_heap_by_id(table->cxt, updated_heap);
+ initial_row_count = (uint32_t)(stream->size / sizeof(mdguid_t));
+ }
+ else
+ {
+ initial_row_count = (uint32_t)get_heap_by_id(table->cxt, updated_heap)->size;
+ // If we are resizing a heap, we'll ensure that the flag on the context for large heaps is consistent.
+ // This makes saving easier by requiring minimal reprocessing of the heaps at save time.
+ mdcxt_flag_t large_heap_flag = get_large_heap_flag(updated_heap);
+ if (large_heap_flag != 0)
+ {
+ if ((editor->cxt->context_flags & large_heap_flag) == large_heap_flag
+ && new_max_row_count <= UINT16_MAX)
+ {
+ editor->cxt->context_flags &= ~large_heap_flag;
+ }
+ else if ((editor->cxt->context_flags & large_heap_flag) == 0
+ && new_max_row_count > UINT16_MAX)
+ {
+ editor->cxt->context_flags |= large_heap_flag;
+ }
+ }
+ }
+
+ for (uint8_t col_index = 0; col_index < table->column_count; col_index++)
+ {
+ mdtcol_t col_details = table->column_details[col_index];
+ new_column_details[col_index] = col_details;
+
+ uint32_t initial_max_column_value, new_max_column_value;
+ if ((col_details & mdtc_idx_table) == mdtc_idx_table && ExtractTable(col_details) == updated_table)
+ {
+ initial_max_column_value = initial_row_count;
+ new_max_column_value = new_max_row_count;
+ }
+ else if ((col_details & mdtc_idx_coded) == mdtc_idx_coded && updated_table != mdtid_Unused && is_coded_index_target(col_details, updated_table))
+ {
+ bool composed = compose_coded_index(TokenFromRid(initial_row_count, CreateTokenType(updated_table)), col_details, &initial_max_column_value);
+ assert(composed);
+ (void)composed;
+ composed = compose_coded_index(TokenFromRid(new_max_row_count, CreateTokenType(updated_table)), col_details, &new_max_column_value);
+ assert(composed);
+ (void)composed;
+ }
+ else if ((col_details & (mdtc_idx_heap)) == mdtc_idx_heap && ExtractHeapType(col_details) == updated_heap)
+ {
+ initial_max_column_value = initial_row_count;
+ new_max_column_value = new_max_row_count;
+ }
+ else
+ {
+ continue;
+ }
+
+ if ((col_details & mdtc_b2) && new_max_column_value > UINT16_MAX)
+ {
+ new_column_details[col_index] = (col_details & ~mdtc_b2) | mdtc_b4;
+ }
+ else if ((col_details & mdtc_b4) && new_max_column_value <= UINT16_MAX)
+ {
+ new_column_details[col_index] = (col_details & ~mdtc_b4) | mdtc_b2;
+ }
+ }
+
+ // We want to make sure that we can store as many rows as the current table can in our new allocation.
+ size_t table_data_size = editor->tables[table->table_id].data.ptr != NULL ? editor->tables[table->table_id].data.size : table->data.size;
+ size_t max_original_rows_in_size = table_data_size / table->row_size_bytes;
+
+ uint8_t new_row_size = 0;
+ for (uint8_t col_index = 0; col_index < table->column_count; col_index++)
+ {
+ new_row_size += (new_column_details[col_index] & mdtc_b2) == mdtc_b2 ? 2 : 4;
+ }
+
+ // If the row size is the same, then we didn't have to change any column sizes.
+ // We are either only expanding or reducing, so we can't end up at the same size with changed column sizes.
+ if (new_row_size == table->row_size_bytes)
+ return true;
+
+ size_t new_allocation_size = max_original_rows_in_size * new_row_size;
+
+ void* mem = alloc_mdmem(editor->cxt, new_allocation_size);
+ if (mem == NULL)
+ return false;
+ uint8_t* new_data_blob = mem;
+
+ // Go through all of the columns of each row and copy them to the new memory for the table
+ // in their correct size.
+ uint8_t const* table_data = table->data.ptr;
+ size_t table_data_length = table->data.size;
+ uint8_t* new_table_data = new_data_blob;
+ size_t new_table_data_length = new_allocation_size;
+ for (uint32_t i = 0; i < table->row_count; i++)
+ {
+ if (!copy_row(&new_table_data, &new_table_data_length, new_column_details, &table_data, &table_data_length, table->column_details, table->column_count))
+ return false;
+ }
+
+ // Update the public view of the table to have the new schema and point to the new data.
+ table->row_size_bytes = new_row_size;
+ table->data.ptr = new_data_blob;
+ table->data.size = (size_t)table->row_count * table->row_size_bytes;
+ memcpy(table->column_details, new_column_details, sizeof(mdtcol_t) * table->column_count);
+
+ // Update the table's corresponding editor to point to the newly-allocated memory,
+ // and free the previous allocation if necessary.
+ if (editor->tables[table->table_id].data.ptr != NULL)
+ free_mdmem(editor->cxt, editor->tables[table->table_id].data.ptr);
+
+ editor->tables[table->table_id].data.ptr = new_data_blob;
+ editor->tables[table->table_id].data.size = new_allocation_size;
+
+ return true;
+}
+
+static bool update_table_references_for_shifted_rows(mdeditor_t* editor, mdtable_id_t updated_table, uint32_t changed_row_start, int64_t shift)
+{
+ assert(updated_table != mdtid_Unused);
+ // Make sure we aren't shifting into negative row ids or shifting above the max row id. That isn't legal.
+ assert(changed_row_start + shift > 0 && changed_row_start + shift < 0x00ffffff);
+ for (mdtable_id_t table_id = mdtid_First; table_id < mdtid_End; table_id++)
+ {
+ mdtable_t* table = &editor->cxt->tables[table_id];
+ if (table->cxt == NULL) // This table is not used in the current image
+ continue;
+
+ // Update all columns in the table that can refer to the updated table
+ // to be the correct width for the updated table's new size.
+ if (!set_column_size_for_max_row_count(editor, table, updated_table, mdtc_none, (uint32_t)(table->row_count + shift)))
+ return false;
+
+ for (uint8_t i = 0; i < table->column_count; i++)
+ {
+ mdtcol_t col_details = table->column_details[i];
+ if (((col_details & mdtc_idx_table) == mdtc_idx_table && ExtractTable(col_details) == updated_table)
+ || ((col_details & mdtc_idx_coded) == mdtc_idx_coded && is_coded_index_target(col_details, updated_table)))
+ {
+ // We've found a column that will need updating.
+ mdcursor_t c = create_cursor(table, 1);
+ update_shifted_row_references(&c, table->row_count, i, updated_table, changed_row_start, (uint32_t)(changed_row_start + shift));
+ }
+ }
+ }
+ return true;
+}
+
+static bool allocate_more_editable_space(mdcxt_t* cxt, mddata_t* editable_data, mdcdata_t* data, size_t minimum_size)
+{
+ size_t new_size = minimum_size > data->size * 2 ? minimum_size : data->size * 2;
+ void* new_ptr;
+ if (editable_data->ptr != NULL)
+ {
+ void* mem = alloc_mdmem(cxt, new_size);
+ if (mem == NULL)
+ return false;
+ memcpy(mem, data->ptr, data->size);
+ new_ptr = mem;
+ free_mdmem(cxt, editable_data->ptr);
+ }
+ else
+ {
+ void* mem = alloc_mdmem(cxt, new_size);
+ if (mem == NULL)
+ return false;
+ new_ptr = mem;
+ memcpy(new_ptr, data->ptr, data->size);
+ }
+
+ if (new_ptr == NULL)
+ return false;
+ editable_data->ptr = new_ptr;
+ editable_data->size = new_size;
+ data->ptr = new_ptr;
+ // We don't update data->size as the space has been allocated, but it is not in use in the image yet.
+ return true;
+}
+
+bool allocate_new_table(mdcxt_t* cxt, mdtable_id_t table_id)
+{
+ mdeditor_t* editor = get_editor(cxt);
+ if (editor == NULL)
+ return false;
+
+ mdtable_t* table = &cxt->tables[table_id];
+ // We should not be allocating for a newly-initialized table.
+ assert(table->cxt == NULL);
+
+ if (!initialize_new_table_details(cxt, table_id, table))
+ return false;
+
+ table->cxt = cxt;
+ // Allocate some memory for the table.
+ // The number of rows in this allocation is arbitrary.
+ // It may be interesting to change the default depending on the target table.
+ size_t initial_allocation_size = table->row_size_bytes * 20;
+ // The initial table has a size 0 as it has no rows.
+ table->data.size = 0;
+ uint8_t* table_data = alloc_mdmem(cxt, initial_allocation_size);
+ if (table_data == NULL)
+ {
+ table->cxt = NULL;
+ return false;
+ }
+ table->data.ptr = cxt->editor->tables[table_id].data.ptr = table_data;
+ cxt->editor->tables[table_id].data.size = initial_allocation_size;
+ return true;
+}
+
+bool insert_row_into_table(mdcxt_t* cxt, mdtable_id_t table_id, uint32_t row_index, mdcursor_t* new_row)
+{
+ assert(row_index != 0); // Row indexes are 1-based.
+ mdeditor_t* editor = get_editor(cxt);
+ if (editor == NULL)
+ return false;
+
+ mdtable_editor_t* target_table_editor = &editor->tables[table_id];
+ assert(target_table_editor->table->cxt != NULL); // The table should exist in the image before a row is added to it.
+
+ // We do not support adding multiple rows to a table at once. One row must be fully added before another is added.
+ if (target_table_editor->table->is_adding_new_row)
+ return false;
+
+ // We can either insert a row in the middle of a table or directly after the end of the table.
+ if (target_table_editor->table->row_count < (row_index - 1))
+ return false;
+
+ // If we are out of space in our table, then we need to allocate a new table buffer.
+ if (target_table_editor->data.ptr == NULL || target_table_editor->data.size < target_table_editor->table->row_size_bytes * (size_t)(target_table_editor->table->row_count + 1))
+ {
+ if (!allocate_more_editable_space(editor->cxt, &target_table_editor->data, &target_table_editor->table->data, (target_table_editor->table->row_count + 1) * target_table_editor->table->row_size_bytes))
+ return false;
+ }
+
+ size_t next_row_start_offset = target_table_editor->table->row_size_bytes * (size_t)(row_index - 1);
+ size_t last_row_end_offset = target_table_editor->table->row_size_bytes * (size_t)target_table_editor->table->row_count;
+
+ if (next_row_start_offset < last_row_end_offset)
+ {
+ // If we're inserting a row in the middle of the table, then we need to move the rows after it down.
+ memmove(
+ target_table_editor->data.ptr + next_row_start_offset + target_table_editor->table->row_size_bytes,
+ target_table_editor->data.ptr + next_row_start_offset,
+ last_row_end_offset - next_row_start_offset);
+
+ }
+ // Clear the new row.
+ memset(target_table_editor->data.ptr + next_row_start_offset, 0, target_table_editor->table->row_size_bytes);
+
+ // Update table references
+ // We may have columns that are pointing to the row just after the end of the table, so we need to do this in all cases,
+ // not just the "in the middle" case.
+ if (!update_table_references_for_shifted_rows(editor, table_id, row_index, 1))
+ return false;
+
+ target_table_editor->table->data.size += target_table_editor->table->row_size_bytes;
+ target_table_editor->table->row_count++;
+ target_table_editor->table->is_adding_new_row = true;
+
+ *new_row = create_cursor(target_table_editor->table, row_index);
+ return true;
+}
+
+#ifdef DNMD_PORTABLE_PDB
+bool update_referenced_type_system_table_row_count(mdcxt_t* cxt, mdtable_id_t updated_table, uint32_t new_max_row_count)
+{
+ assert(updated_table < mdtid_FirstPdb);
+
+ mdeditor_t* editor = get_editor(cxt);
+ if (editor == NULL)
+ return false;
+
+ md_pdb_t pdb;
+ if (!try_get_pdb(cxt, &pdb))
+ return false;
+
+ if (pdb.type_system_table_rows[updated_table] >= new_max_row_count)
+ return true;
+
+ pdb.type_system_table_rows[updated_table] = new_max_row_count;
+
+ for (mdtable_id_t table_id = mdtid_FirstPdb; table_id < mdtid_End; ++table_id)
+ {
+ mdtable_t* table = &editor->cxt->tables[table_id];
+ if (table->cxt == NULL) // This table is not used in the current image
+ continue;
+
+ // Update all columns in the table that can refer to the updated table
+ // to be the correct width for the updated table's new size.
+ if (!set_column_size_for_max_row_count(editor, table, updated_table, mdtc_none, new_max_row_count))
+ return false;
+ }
+
+ size_t pdb_heap_size = cxt->pdb.size;
+ if (!(pdb.referenced_type_system_tables & (1ULL << updated_table)))
+ {
+ // If we haven't referenced this type system table yet, then we need to allocate space for the row count
+ // and mark that we're referencing it now.
+ pdb_heap_size += sizeof(uint32_t);
+ pdb.referenced_type_system_tables |= (1ULL << updated_table);
+ }
+
+ if (editor->pdb_heap.heap.ptr == NULL || pdb_heap_size < editor->pdb_heap.heap.size)
+ {
+ // If we don't have space for the new row count or we haven't edited the PDB heap yet, then we need to allocate more space.
+ if (!allocate_more_editable_space(editor->cxt, &editor->pdb_heap.heap, editor->pdb_heap.stream, pdb_heap_size))
+ return false;
+ }
+
+ uint8_t* pdb_heap_data = editor->pdb_heap.heap.ptr;
+ size_t pdb_heap_data_length = editor->pdb_heap.heap.size;
+ // We can skip over the PDB ID and the entrypoint token.
+ if (!advance_output_stream(&pdb_heap_data, &pdb_heap_data_length, ARRAY_SIZE(pdb.pdb_id) + sizeof(mdToken)))
+ return false;
+
+ // Write the bitset of referenced type system tables.
+ if (!write_u64(&pdb_heap_data, &pdb_heap_data_length, pdb.referenced_type_system_tables))
+ return false;
+
+ // Now write the row counts for each referenced type system table.
+ size_t n = count_set_bits(pdb.referenced_type_system_tables);
+ uint8_t const* pdb_end = pdb_heap_data + (n * sizeof(uint32_t));
+
+ // Read in all row data defined by the references bits.
+ for (size_t i = 0; i < MDTABLE_MAX_COUNT; ++i)
+ {
+ if (pdb.referenced_type_system_tables & (1ULL << i))
+ {
+ // Read in the row count for referenced tables
+ if (!write_u32(&pdb_heap_data, &pdb_heap_data_length, pdb.type_system_table_rows[i]))
+ return false;
+ }
+ }
+
+ // Validate we wrote the row counts properly
+ if (pdb_heap_data != pdb_end)
+ return false;
+ return true;
+}
+#endif // DNMD_PORTABLE_PDB
+
+static md_heap_editor_t* get_heap_editor_by_id(mdeditor_t* editor, mdtcol_t heap_id)
+{
+ switch (heap_id)
+ {
+ case mdtc_hblob:
+ return &editor->blob_heap;
+ case mdtc_hguid:
+ return &editor->guid_heap;
+ case mdtc_hstring:
+ return &editor->strings_heap;
+ case mdtc_hus:
+ return &editor->user_string_heap;
+ default:
+ return NULL;
+ }
+}
+
+static bool reserve_heap_space(mdeditor_t* editor, uint32_t space_size, mdtcol_t heap_id, bool preserve_offsets, uint32_t* heap_offset)
+{
+ md_heap_editor_t* heap_editor = get_heap_editor_by_id(editor, heap_id);
+ if (heap_editor == NULL)
+ return false;
+
+ if (heap_editor->stream->ptr == NULL)
+ {
+ // Set the default heap size based on likely reasonable sizes for the heaps.
+ // In most images, there won't be more than three guids, so we can start with a small heap in that case.
+ size_t const initial_heap_size = heap_id == mdtc_hguid ? sizeof(mdguid_t) * 3 : 0x100;
+ void* mem = alloc_mdmem(editor->cxt, initial_heap_size);
+ if (mem == NULL)
+ return false;
+
+ heap_editor->stream->ptr = mem;
+ heap_editor->heap.ptr = mem;
+ heap_editor->heap.size = initial_heap_size;
+
+ // The first character in the strings heap must be the '\0' - II.24.2.3
+ // The first character in the user_string and blob heaps must be the 0 - II.24.2.4
+ // The guid heap doesn't start with a 0 byte, but it must be emitted in sizeof(mdguid_t)-based chuncks.
+ // If we are preserving offsets, then we don't initialize the heap, as we will be copying an existing heap that has already been validated and
+ // we must have the exact same offsets as the existing heap to avoid breaking heap references.
+ if (heap_id != mdtc_hguid && !preserve_offsets)
+ {
+ heap_editor->heap.ptr[0] = 0;
+ heap_editor->stream->size = heap_id == mdtc_hguid ? 0 : 1;
+ }
+ }
+
+ *heap_offset = (uint32_t)heap_editor->stream->size;
+
+ if (*heap_offset > UINT32_MAX - space_size)
+ {
+ // The max heap size is 2^32-1, so we don't have space left to allocate.
+ return false;
+ }
+
+
+ uint32_t new_heap_size = *heap_offset + space_size;
+ if (new_heap_size > heap_editor->heap.size)
+ {
+ if (!allocate_more_editable_space(editor->cxt, &heap_editor->heap, heap_editor->stream, new_heap_size))
+ return false;
+ }
+
+ // Update heap references in case the additional used space crosses the boundary for index sizes.
+ uint32_t index_scale = (heap_id == mdtc_hguid ? sizeof(mdguid_t) : 1);
+ assert(heap_editor->stream->size % index_scale == 0);
+ for (mdtable_id_t i = mdtid_First; i < mdtid_End; i++)
+ {
+ mdtable_t* table = &editor->cxt->tables[i];
+ if (table->cxt == NULL) // This table is not used in the current image
+ continue;
+
+ // Update all columns in the table that can refer to the updated heap
+ // to be the correct width for the updated heap's new size.
+ if (!set_column_size_for_max_row_count(editor, table, mdtid_Unused, heap_id, new_heap_size / index_scale))
+ return false;
+ }
+
+ // Now that the new heap size can be referenced, let's update the heap size.
+ heap_editor->stream->size += space_size;
+
+ return true;
+}
+
+uint32_t add_to_string_heap(mdcxt_t* cxt, char const* str)
+{
+ // II.24.2.3 - When the #String heap is present, the first entry is always the empty string (i.e., \0).
+ // II.24.2.2 - Streams need not be there if they are empty.
+ // We can avoid allocating the heap if the only entry is the empty string.
+ // Columns that point to the string heap can be 0 if there is no #String heap.
+ // In that case, they represent the empty string.
+ if (str[0] == '\0')
+ return 0;
+
+ mdeditor_t* editor = get_editor(cxt);
+ if (editor == NULL)
+ return 0;
+
+ // TODO: Deduplicate heap
+ uint32_t str_len = (uint32_t)strlen(str);
+ uint32_t heap_offset;
+ if (!reserve_heap_space(editor, str_len + 1, mdtc_hstring, false, &heap_offset))
+ {
+ return 0;
+ }
+ memcpy((uint8_t*)editor->strings_heap.heap.ptr + heap_offset, str, str_len);
+ ((uint8_t*)editor->strings_heap.heap.ptr)[heap_offset + str_len] = '\0';
+ return heap_offset;
+}
+
+uint32_t add_to_blob_heap(mdcxt_t* cxt, uint8_t const* data, uint32_t length)
+{
+ // II.24.2.4 - When the #Blob heap is present, the first entry is always the empty blob.
+ // II.24.2.2 - Streams need not be there if they are empty.
+ // We can avoid allocating the heap if the only entry is the empty blob.
+ // Columns that point to the blob heap can be 0 if there is no #Blob heap.
+ // In that case, they represent the empty blob.
+ if (length == 0)
+ return 0;
+
+ mdeditor_t* editor = get_editor(cxt);
+ if (editor == NULL)
+ return 0;
+
+ // TODO: Deduplicate heap
+ uint8_t compressed_length[4];
+ size_t compressed_length_size = ARRAY_SIZE(compressed_length);
+ if (!compress_u32(length, compressed_length, &compressed_length_size))
+ return 0;
+
+ uint32_t heap_slot_size = length + (uint32_t)compressed_length_size;
+
+ uint32_t heap_offset;
+ if (!reserve_heap_space(editor, heap_slot_size, mdtc_hblob, false, &heap_offset))
+ {
+ return 0;
+ }
+
+ memcpy(editor->blob_heap.heap.ptr + heap_offset, compressed_length, compressed_length_size);
+ memcpy(editor->blob_heap.heap.ptr + heap_offset + compressed_length_size, data, length);
+ return heap_offset;
+}
+
+uint32_t add_to_user_string_heap(mdcxt_t* cxt, char16_t const* str)
+{
+ uint32_t str_len;
+ uint8_t has_special_char = 0;
+ for (str_len = 0; str[str_len] != (char16_t)0; str_len++)
+ {
+ char16_t c = str[str_len];
+ // II.24.2.4
+ // There is an additional terminal byte which holds a 1 or 0.
+ // The 1 signifies Unicode characters that require handling beyond
+ // that normally provided for 8-bit encoding sets.
+ // This final byte holds the value 1 if and only if any UTF16 character
+ // within the string has any bit set in its top byte,
+ // or its low byte is any of the following: 0x01-0x08, 0x0E–0x1F, 0x27, 0x2D, 0x7F.
+ // Otherwise, it holds 0.
+ if ((c & 0x80) != 0)
+ {
+ has_special_char = 1;
+ }
+ else if (c >= 0x1 && c <= 0x8)
+ {
+ has_special_char = 1;
+ }
+ else if (c >= 0xe && c <= 0x1f)
+ {
+ has_special_char = 1;
+ }
+ else if (c == 0x27)
+ {
+ has_special_char = 1;
+ }
+ else if (c == 0x2d)
+ {
+ has_special_char = 1;
+ }
+ else if (c == 0x7f)
+ {
+ has_special_char = 1;
+ }
+ }
+
+ // II.24.2.4 - When the #US heap is present, the first entry is always the empty blob.
+ // II.24.2.2 - Streams need not be there if they are empty.
+ // We can avoid allocating the heap if the only entry is the empty blob.
+ // Indices into the #US heap can be 0 if there is no #US heap.
+ // In that case, they represent the empty userstring blob.
+ if (str_len == 0)
+ return 0;
+
+ mdeditor_t* editor = get_editor(cxt);
+ if (editor == NULL)
+ return 0;
+
+ // TODO: Deduplicate heap
+
+ // II.24.2.4
+ // Strings in the #US (user string) heap are encoded using 16-bit Unicode encodings.
+ // The count on each string is the number of bytes (not characters) in the string.
+ // Furthermore, there is an additional terminal byte (so all byte counts are odd, not even).
+ size_t us_blob_bytes = str_len * sizeof(char16_t) + 1;
+
+ // The string is too long to represent in the heap.
+ if (us_blob_bytes > INT32_MAX)
+ return 0;
+ uint8_t compressed_length[sizeof(uint32_t)];
+ size_t compressed_length_size = ARRAY_SIZE(compressed_length);
+ if (!compress_u32((uint32_t)us_blob_bytes, compressed_length, &compressed_length_size))
+ return 0;
+
+ uint32_t heap_slot_size = (uint32_t)us_blob_bytes + (uint32_t)compressed_length_size;
+ uint32_t heap_offset;
+ if (!reserve_heap_space(editor, heap_slot_size, mdtc_hus, false, &heap_offset))
+ {
+ return 0;
+ }
+
+ // Copy the compressed blob length into the heap.
+ memcpy(editor->user_string_heap.heap.ptr + heap_offset, compressed_length, compressed_length_size);
+ // Copy the UTF-16-encoded user string into the heap.
+ memcpy(editor->user_string_heap.heap.ptr + heap_offset + compressed_length_size, str, us_blob_bytes - 1);
+
+ // Set the trailing byte.
+ editor->user_string_heap.heap.ptr[heap_offset + compressed_length_size + us_blob_bytes - 1] = has_special_char;
+ return heap_offset;
+}
+
+mdguid_t const empty_guid = { 0 };
+
+uint32_t add_to_guid_heap(mdcxt_t* cxt, mdguid_t guid)
+{
+ mdeditor_t* editor = get_editor(cxt);
+ if (editor == NULL)
+ return 0;
+ // TODO: Deduplicate heap
+ if (memcmp(&guid, &empty_guid, sizeof(mdguid_t)) == 0)
+ return 0;
+
+ uint32_t heap_offset;
+ if (!reserve_heap_space(editor, sizeof(mdguid_t), mdtc_hguid, false, &heap_offset))
+ {
+ return 0;
+ }
+
+ memcpy(editor->guid_heap.heap.ptr + heap_offset, &guid, sizeof(mdguid_t));
+ // II.22 - The Guid heap is an array of GUIDs, each 16 bytes wide. Its
+ // first element is numbered 1, its second 2, and so on.
+ // So, we need to make the offset 1-based and at the scale of the GUID size.
+ return (heap_offset / sizeof(mdguid_t)) + 1;
+}
+
+bool append_heap(mdcxt_t* cxt, mdcxt_t* delta, mdtcol_t heap_id)
+{
+ bool is_minimal_delta = (delta->context_flags & mdc_minimal_delta) == mdc_minimal_delta;
+ mdeditor_t* editor = get_editor(cxt);
+ if (editor == NULL)
+ return false;
+
+ md_heap_editor_t* heap_editor = get_heap_editor_by_id(editor, heap_id);
+ mdstream_t* delta_heap = get_heap_by_id(delta, heap_id);
+ if (delta_heap->size == 0)
+ return true;
+
+ size_t copy_offset;
+ size_t delta_size;
+ if (is_minimal_delta && heap_id != mdtc_hguid)
+ {
+ // If the delta image is a minimal delta and the heap is not the GUID heap, we do a full copy from the delta image.
+ copy_offset = 0;
+ delta_size = delta_heap->size;
+ }
+ else
+ {
+ // Otherwise, we only do a partial copy from the stream starting at the end of the existing heap.
+ copy_offset = heap_editor->stream->size;
+ delta_size = delta_heap->size - copy_offset;
+ }
+
+ if (delta_size > UINT32_MAX)
+ {
+ return false;
+ }
+
+ uint32_t heap_offset;
+ if (!reserve_heap_space(editor, (uint32_t)delta_size, heap_id, true, &heap_offset))
+ {
+ return false;
+ }
+
+ memcpy(heap_editor->heap.ptr + heap_offset, delta_heap->ptr + copy_offset, delta_size);
+
+ return true;
+}
+
+mduserstringcursor_t md_add_userstring_to_heap(mdhandle_t handle, char16_t const* userstring)
+{
+ mdcxt_t* cxt = extract_mdcxt(handle);
+ if (cxt == NULL)
+ return 0;
+
+ return add_to_user_string_heap(cxt, userstring);
+}
diff --git a/src/native/dnmd/src/dnmd/entry.c b/src/native/dnmd/src/dnmd/entry.c
new file mode 100644
index 0000000000000..e7b1516d80a87
--- /dev/null
+++ b/src/native/dnmd/src/dnmd/entry.c
@@ -0,0 +1,1157 @@
+#include "internal.h"
+
+#include
+#include
+
+// mdlib magic number for context
+#define MDLIB_MAGIC_NUMBER 0x3d71b
+
+// Defined in II.24.2.1
+#define METADATA_SIG 0x424A5342
+
+static mdcxt_t* allocate_full_context(mdcxt_t* cxt)
+{
+ // The intent here is to call the allocator once.
+ // Therefore we compute the full size and then call
+ // malloc a single time. The following needs to be
+ // done:
+ // 1. Compute total amount of needed memory:
+ // - sizeof(mdcxt_t)
+ // - table count
+ // - column count for each table
+ // 2. Copy supplied mdcxt_t to the newly allocated one
+ // 3. Determine table array offset
+ // 4. Set table pointer in mdcxt_t
+ // 5. Determine column details array offsets
+ // 6. Set column details array in each table
+ // 7. Return the newly allocated context
+
+ uint32_t table_col_sizes[MDTABLE_MAX_COUNT];
+ uint32_t total_col_size = 0;
+ for (mdtable_id_t id = mdtid_First; id < mdtid_End; ++id)
+ {
+ table_col_sizes[id] = sizeof(mdtcol_t) * get_table_column_count(id);
+ total_col_size += table_col_sizes[id];
+ }
+
+ // Ensure all sections of the allocation are pointer aligned.
+ size_t cxt_mem = align_to(sizeof(mdcxt_t), sizeof(void*));
+ size_t tables_mem = MDTABLE_MAX_COUNT * align_to(sizeof(mdtable_t), sizeof(void*));
+ size_t col_mem = align_to(total_col_size, sizeof(void*));
+
+ size_t total_mem = cxt_mem + tables_mem + col_mem;
+ uint8_t* mem = (uint8_t*)malloc(total_mem);
+ if (mem == NULL)
+ return NULL;
+
+ // Copy passed in state
+ mdcxt_t* pcxt = (mdcxt_t*)mem;
+ mem += cxt_mem;
+ memcpy(pcxt, cxt, sizeof(*cxt));
+ assert(pcxt->tables == NULL);
+ assert(pcxt->mem == NULL);
+
+ // Zero out the remaining memory
+ memset(mem, 0, total_mem - cxt_mem);
+
+ // Update the tables pointer to offset in allocation
+ pcxt->tables = (mdtable_t*)mem;
+ mem += tables_mem;
+
+ // Update each table's column array
+ for (mdtable_id_t id = mdtid_First; id < mdtid_End; ++id)
+ {
+ pcxt->tables[id].column_details = (mdtcol_t*)mem;
+ uint32_t size = table_col_sizes[id];
+ mem += size;
+ }
+
+ assert(mem <= (uint8_t*)(pcxt + total_mem));
+ return pcxt;
+}
+
+bool md_create_handle(void const* data, size_t data_len, mdhandle_t* handle)
+{
+ if (data == NULL || handle == NULL)
+ return false;
+
+ uint8_t const* const base = data;
+ uint8_t const* curr = data;
+ size_t curr_len = data_len;
+
+ // Validate the metadata root is the minimally valid before creating a handle.
+ uint32_t sig;
+ uint32_t ver_buf_count;
+ uint16_t stream_count;
+
+ mdcxt_t cxt;
+ memset(&cxt, 0, sizeof(cxt));
+
+ // Consume header defined in II.24.2.1
+ if (!read_u32(&curr, &curr_len, &sig) || sig != METADATA_SIG)
+ return false;
+
+ if (!read_u16(&curr, &curr_len, &cxt.major_ver)
+ || !read_u16(&curr, &curr_len, &cxt.minor_ver)
+ || !advance_stream(&curr, &curr_len, 4)
+ || !read_u32(&curr, &curr_len, &ver_buf_count))
+ {
+ return false;
+ }
+
+ // The version count is aligned to 4-bytes
+ ver_buf_count = align_to(ver_buf_count, 4);
+ if (ver_buf_count > curr_len)
+ return false;
+
+ // Confirm terminator and consume the version/aligned length
+ cxt.version = (char const*)curr;
+ if (ver_buf_count == 0
+ || cxt.version[ver_buf_count - 1] != '\0'
+ || !advance_stream(&curr, &curr_len, ver_buf_count))
+ {
+ return false;
+ }
+
+ if (!read_u16(&curr, &curr_len, &cxt.flags)
+ || !read_u16(&curr, &curr_len, &stream_count))
+ {
+ return false;
+ }
+
+ // Iterate over the discovered streams
+ uint32_t offset;
+ uint32_t stream_size;
+ uint8_t* name_end;
+ size_t name_len;
+ bool tables_heap_uncompressed = false;
+ for (size_t i = 0; i < stream_count; ++i)
+ {
+ if (!read_u32(&curr, &curr_len, &offset)
+ || !read_u32(&curr, &curr_len, &stream_size))
+ {
+ return false;
+ }
+
+ // Verify the offset is valid for our data size
+ if (offset > data_len)
+ return false;
+
+ // Verify the stream size can fit into available size
+ if (stream_size > data_len - offset)
+ return false;
+
+ // Find the terminating null.
+ name_end = memchr(curr, 0, curr_len);
+ if (name_end == NULL)
+ return false;
+
+ name_len = name_end - curr;
+ if (strncmp((char const*)curr, "#~", name_len) == 0)
+ {
+ cxt.tables_heap.ptr = base + offset;
+ cxt.tables_heap.size = stream_size;
+ tables_heap_uncompressed = false;
+ }
+ // The #- stream is used for images that may have the *Ptr indirection tables.
+ // The indirection tables, as well as the #- stream, are not documented in the ECMA spec.
+ else if (strncmp((char const*)curr, "#-", name_len) == 0)
+ {
+ cxt.tables_heap.ptr = base + offset;
+ cxt.tables_heap.size = stream_size;
+ tables_heap_uncompressed = true;
+ }
+ // The #JTD stream is a marker that the image is a minimal EnC delta, as compared to an image
+ // with the EnC data included. This stream is not documented in the ECMA spec.
+ else if (strncmp((char const*)curr, "#JTD", name_len) == 0)
+ {
+ // The content of the stream is ignored.
+ cxt.context_flags |= mdc_minimal_delta;
+ }
+ else if (strncmp((char const*)curr, "#Strings", name_len) == 0)
+ {
+ cxt.strings_heap.ptr = base + offset;
+ cxt.strings_heap.size = stream_size;
+
+ // Compute the precise size of the string heap by walking back over the trailing null padding.
+ // There may be up to three extra '\0' characters appended for padding.
+ // ENC minimal delta images require the precise size of the base image string heap to be known,
+ // so we trim the trailing padding.
+ uint8_t const* p = cxt.strings_heap.ptr + cxt.strings_heap.size - 1;
+ while (cxt.strings_heap.size >= 2 && p[0] == 0 && p[-1] == 0)
+ {
+ p--;
+ cxt.strings_heap.size--;
+ }
+ }
+ else if (strncmp((char const*)curr, "#Blob", name_len) == 0)
+ {
+ cxt.blob_heap.ptr = base + offset;
+ cxt.blob_heap.size = stream_size;
+ }
+ else if (strncmp((char const*)curr, "#US", name_len) == 0)
+ {
+ cxt.user_string_heap.ptr = base + offset;
+ cxt.user_string_heap.size = stream_size;
+ }
+ else if (strncmp((char const*)curr, "#GUID", name_len) == 0)
+ {
+ cxt.guid_heap.ptr = base + offset;
+ cxt.guid_heap.size = stream_size;
+ }
+#ifdef DNMD_PORTABLE_PDB
+ else if (strncmp((char const*)curr, "#Pdb", name_len) == 0)
+ {
+ cxt.pdb.ptr = base + offset;
+ cxt.pdb.size = stream_size;
+ }
+#endif // DNMD_PORTABLE_PDB
+ else
+ {
+ assert(!"Unknown stream");
+ return false;
+ }
+
+ // Align the string length to 4 bytes.
+ if (!advance_stream(&curr, &curr_len, align_to((uint32_t)(name_len + 1), 4)))
+ return false;
+ }
+
+ // When the #JTD stream is present, the #- stream must be
+ // the stream that contains the metadata tables.
+ if ((bool)(cxt.context_flags & mdc_minimal_delta) && !tables_heap_uncompressed)
+ return false;
+
+ // Header initialization is complete.
+ cxt.magic = MDLIB_MAGIC_NUMBER;
+ cxt.raw_metadata.ptr = data;
+ cxt.raw_metadata.size = data_len;
+
+ // Allocate and initialize a context
+ mdcxt_t* pcxt = allocate_full_context(&cxt);
+ if (pcxt == NULL)
+ return false;
+
+#ifndef NDEBUG
+ memset(&cxt, 0xcc, sizeof(cxt));
+#endif // NDEBUG
+
+ // Initialize the tables in the new context.
+ if (!initialize_tables(pcxt))
+ {
+ free(pcxt);
+ return false;
+ }
+
+ // Move the constructed context to the allocated one.
+ *handle = pcxt;
+ return true;
+}
+
+// Initialize the minimal set of tables required for a valid metadata image.
+// Every image must have a row in the Module table
+// for module identity information
+// and a row in the TypeDef table for the global type.
+static bool initialize_minimal_table_rows(mdcxt_t* cxt)
+{
+ // Add the Module row for module identity
+ mdcursor_t module_cursor;
+ if (!md_append_row(cxt, mdtid_Module, &module_cursor))
+ return false;
+
+ // Set the Generation to 0
+ uint32_t generation = 0;
+ if (!md_set_column_value_as_constant(module_cursor, mdtModule_Generation, generation))
+ return false;
+
+ // Use the 0 index to specify the NULL guid as the guids for the image.
+ uint32_t guid_heap_offset = 0;
+ if (!set_column_value_as_heap_offset(module_cursor, mdtModule_Mvid, guid_heap_offset)
+ || !set_column_value_as_heap_offset(module_cursor, mdtModule_EncBaseId, guid_heap_offset)
+ || !set_column_value_as_heap_offset(module_cursor, mdtModule_EncId, guid_heap_offset))
+ {
+ return false;
+ }
+
+ char const* name = "";
+ if (!md_set_column_value_as_utf8(module_cursor, mdtModule_Name, name))
+ return false;
+
+ // Mark that we're done adding the Module row.
+ md_commit_row_add(module_cursor);
+
+ // Add a row for the global type.
+ mdcursor_t global_type_cursor;
+ if (!md_append_row(cxt, mdtid_TypeDef, &global_type_cursor))
+ return false;
+
+ uint32_t flags = 0;
+ if (!md_set_column_value_as_constant(global_type_cursor, mdtTypeDef_Flags, flags))
+ return false;
+
+ char const* global_type_name = ""; // Defined in ECMA-335 II.10.8
+ if (!md_set_column_value_as_utf8(global_type_cursor, mdtTypeDef_TypeName, global_type_name))
+ return false;
+
+ char const* namespace = "";
+ if (!md_set_column_value_as_utf8(global_type_cursor, mdtTypeDef_TypeNamespace, namespace))
+ return false;
+
+ mdToken nil_typedef = CreateTokenType(mdtid_TypeDef);
+ if (!md_set_column_value_as_token(global_type_cursor, mdtTypeDef_Extends, nil_typedef))
+ return false;
+
+ // Mark that we're done adding the TypeDef row.
+ md_commit_row_add(global_type_cursor);
+
+ return true;
+}
+
+mdhandle_t md_create_new_handle()
+{
+ mdcxt_t cxt;
+
+ memset(&cxt, 0, sizeof(mdcxt_t));
+ cxt.magic = MDLIB_MAGIC_NUMBER;
+ cxt.context_flags = mdc_none;
+ cxt.major_ver = 1;
+ cxt.minor_ver = 1;
+ cxt.flags = 0;
+ cxt.version = "v4.0.30319";
+ cxt.editor = NULL;
+ cxt.mem = NULL;
+
+ // Allocate and initialize a full context
+ // with the correctly-sized trailing memory.
+ mdcxt_t* pcxt = allocate_full_context(&cxt);
+ if (pcxt == NULL)
+ return NULL;
+
+ if (!initialize_minimal_table_rows(pcxt))
+ {
+ free(pcxt);
+ return NULL;
+ }
+
+ return pcxt;
+}
+
+#ifdef DNMD_PORTABLE_PDB
+mdhandle_t md_create_new_pdb_handle()
+{
+ mdcxt_t cxt;
+
+ memset(&cxt, 0, sizeof(mdcxt_t));
+ cxt.magic = MDLIB_MAGIC_NUMBER;
+ cxt.context_flags = mdc_none;
+ cxt.major_ver = 1;
+ cxt.minor_ver = 1;
+ cxt.flags = 0;
+ cxt.version = "PDB v1.0";
+ cxt.editor = NULL;
+ cxt.mem = NULL;
+
+ // Allocate and initialize a full context
+ // with the correctly-sized trailing memory.
+ mdcxt_t* pcxt = allocate_full_context(&cxt);
+ if (pcxt == NULL)
+ return NULL;
+
+ return pcxt;
+}
+#endif // DNMD_PORTABLE_PDB
+
+bool md_apply_delta(mdhandle_t handle, mdhandle_t delta_handle)
+{
+ mdcxt_t* base = extract_mdcxt(handle);
+ if (base == NULL)
+ return false;
+
+ mdcxt_t* delta = extract_mdcxt(delta_handle);
+ if (delta == NULL)
+ return false;
+
+ // Verify the supplied delta is actually a delta file
+ bool result = false;
+ if (delta->context_flags & mdc_minimal_delta)
+ result = merge_in_delta(base, delta);
+
+ return result;
+}
+
+typedef struct mdmem__
+{
+ struct mdmem__* next;
+ size_t size;
+ uint8_t data[];
+} mdmem_t;
+
+void md_destroy_handle(mdhandle_t handle)
+{
+ mdcxt_t* cxt = extract_mdcxt(handle);
+ if (cxt == NULL)
+ return;
+
+ mdmem_t* tmp;
+ mdmem_t* curr = cxt->mem;
+ while(curr != NULL)
+ {
+ tmp = curr->next;
+ free(curr);
+ curr = tmp;
+ }
+
+ free(cxt);
+}
+
+bool md_validate(mdhandle_t handle)
+{
+ mdcxt_t* cxt = extract_mdcxt(handle);
+ if (cxt == NULL)
+ return false;
+
+ return validate_guid_heap(cxt)
+ && validate_strings_heap(cxt)
+ && validate_user_string_heap(cxt)
+ && validate_blob_heap(cxt)
+ && validate_tables(cxt);
+}
+
+static bool dump_table_rows(mdtable_t* table)
+{
+ if (table->row_count == 0)
+ {
+ printf("Empty table\n");
+ }
+ else
+ {
+ printf("Table %u (0x%x) rows: %u\n", table->table_id, table->table_id, table->row_count);
+ }
+
+ char const* str;
+ mdguid_t guid;
+ uint8_t const* blob;
+ uint32_t blob_len;
+ uint32_t constant;
+ mduserstring_t user_string;
+ mdToken tk;
+
+#ifdef DEBUG_TABLE_COLUMN_LOOKUP
+ uint16_t const embedded_tid = ((uint16_t)table->table_id) << 8;
+#define IDX(x) (embedded_tid | x)
+#else
+#define IDX(x) x
+#endif
+
+ // Create a cursor to the first row.
+ mdcursor_t cursor = create_cursor(table, 1);
+
+ // The maximum known column count is 9, so hard coding the array to that.
+ bool to_get[] = { true, true, true, true, true, true, true, true, true };
+ assert(table->column_count <= ARRAY_SIZE(to_get));
+ uint32_t raw_values[ARRAY_SIZE(to_get)];
+
+#define IF_NOT_REPORT_RAW(exp) if (!(exp)) { printf("Invalid (%u) [%#x]|", j, raw_values[j]); continue; }
+#define IF_INVALID_BLOB_REPORT_RAW(parse_fn, handle_or_cursor, blob_type, result_buf, result_buf_len) \
+ { \
+ result_buf = NULL; \
+ md_blob_parse_result_t result = parse_fn(handle_or_cursor, blob, blob_len, result_buf, &result_buf_len); \
+ if (result == mdbpr_InvalidBlob) { printf("Invalid PDB Blob (" blob_type ") Offset: %zu (len: %u) [%#x]|", (blob - table->cxt->blob_heap.ptr), blob_len, raw_values[j]); continue; } \
+ assert(result == mdbpr_InsufficientBuffer); \
+ result_buf = malloc(result_buf_len); \
+ if (result_buf == NULL) { printf("Ran out of memory when parsing PDB blob.\n"); return false; } \
+ result = parse_fn(handle_or_cursor, blob, blob_len, result_buf, &result_buf_len); \
+ if (result == mdbpr_InvalidBlob) { printf("Invalid PDB Blob (" blob_type ") Offset: %zu (len: %u) [%#x]|", (blob - table->cxt->blob_heap.ptr), blob_len, raw_values[j]); free(result_buf); continue; } \
+ assert(result == mdbpr_Success); \
+ }
+
+ for (uint32_t i = 0; i < table->row_count; ++i)
+ {
+ if (!md_get_column_values_raw(cursor, table->column_count, to_get, raw_values))
+ {
+ printf("Failure to retrieve raw column values. Table is corrupted.\n");
+ return false;
+ }
+
+ printf("%4u|", i);
+ for (uint8_t j = 0; j < table->column_count; ++j)
+ {
+ if (table->column_details[j] & mdtc_hstring)
+ {
+ IF_NOT_REPORT_RAW(md_get_column_value_as_utf8(cursor, IDX(j), &str));
+ printf("'%s' [%#x]|", str, raw_values[j]);
+ }
+ else if (table->column_details[j] & mdtc_hguid)
+ {
+ IF_NOT_REPORT_RAW(md_get_column_value_as_guid(cursor, IDX(j), &guid));
+ printf("{%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x} [%#x]|",
+ guid.data1, guid.data2, guid.data3,
+ guid.data4[0], guid.data4[1],
+ guid.data4[2], guid.data4[3],
+ guid.data4[4], guid.data4[5],
+ guid.data4[6], guid.data4[7], raw_values[j]);
+ }
+ else if (table->column_details[j] & mdtc_hblob)
+ {
+ col_index_t col = IDX(j);
+#ifdef DNMD_PORTABLE_PDB
+ if (table->table_id == mdtid_Document && col == mdtDocument_Name)
+ {
+ IF_NOT_REPORT_RAW(md_get_column_value_as_blob(cursor, col, &blob, &blob_len));
+
+ char* document_name;
+ size_t name_len;
+ IF_INVALID_BLOB_REPORT_RAW(md_parse_document_name, table->cxt, "DocumentName", document_name, name_len);
+ printf("DocumentName: '%s' [%#x]|", document_name, raw_values[j]);
+ free(document_name);
+ continue;
+ }
+ else if (table->table_id == mdtid_MethodDebugInformation && col == mdtMethodDebugInformation_SequencePoints)
+ {
+ IF_NOT_REPORT_RAW(md_get_column_value_as_blob(cursor, col, &blob, &blob_len));
+
+ if (blob_len == 0)
+ {
+ printf("Empty SequencePoints: Offset: %zu (len: %u) [%#x]|", (blob - table->cxt->blob_heap.ptr), blob_len, raw_values[j]);
+ continue;
+ }
+
+ md_sequence_points_t* sequence_points;
+ size_t sequence_points_len;
+ IF_INVALID_BLOB_REPORT_RAW(md_parse_sequence_points, cursor, "SequencePoints", sequence_points, sequence_points_len);
+ printf("SequencePoints: LocalSignature 0x%08x (mdToken) ", sequence_points->signature);
+ mdToken document_tok;
+ md_cursor_to_token(sequence_points->document, &document_tok);
+ printf("Document 0x%08x (mdToken) ", document_tok);
+ printf("{ ");
+ bool first = true;
+ for (uint32_t k = 0; k < sequence_points->record_count; ++k)
+ {
+ if (!first)
+ {
+ printf(", ");
+ }
+ first = false;
+ if (sequence_points->records[k].kind == mdsp_DocumentRecord)
+ {
+ printf("document-record: ");
+ md_cursor_to_token(sequence_points->records[k].document.document, &document_tok);
+ printf("0x%08x (mdToken)", document_tok);
+ }
+ else if (sequence_points->records[k].kind == mdsp_HiddenSequencePointRecord)
+ {
+ printf("hidden-sequence-point-record: %u", sequence_points->records[k].hidden_sequence_point.rolling_il_offset);
+ }
+ else if (sequence_points->records[k].kind == mdsp_SequencePointRecord)
+ {
+ printf("sequence-point-record: (%u, %u, %" PRId64 ", %" PRId64 ", %" PRId64 ")",
+ sequence_points->records[k].sequence_point.rolling_il_offset,
+ sequence_points->records[k].sequence_point.delta_lines,
+ sequence_points->records[k].sequence_point.delta_columns,
+ sequence_points->records[k].sequence_point.rolling_start_line,
+ sequence_points->records[k].sequence_point.rolling_start_column);
+ }
+ else
+ {
+ assert(!"Invalid sequence point record kind.");
+ }
+ }
+
+ printf(" } [%#x]|", raw_values[j]);
+
+ free(sequence_points);
+ continue;
+ }
+ else if (table->table_id == mdtid_LocalConstant && col == mdtLocalConstant_Signature)
+ {
+ IF_NOT_REPORT_RAW(md_get_column_value_as_blob(cursor, col, &blob, &blob_len));
+ md_local_constant_sig_t* local_constant_sig;
+ size_t local_constant_sig_len;
+ IF_INVALID_BLOB_REPORT_RAW(md_parse_local_constant_sig, table->cxt, "LocalConstantSig", local_constant_sig, local_constant_sig_len);
+ printf("LocalConstantSig: ");
+ for (uint32_t k = 0; k < local_constant_sig->custom_modifier_count; ++k)
+ {
+ printf("%s(0x%08x) ", local_constant_sig->custom_modifiers[k].required ? "modreq" : "modopt", local_constant_sig->custom_modifiers[k].type);
+ }
+
+ if (local_constant_sig->constant_kind == mdck_PrimitiveConstant)
+ {
+ printf("Primitive: 0x%02x ", local_constant_sig->primitive.type_code);
+ }
+ else if (local_constant_sig->constant_kind == mdck_EnumConstant)
+ {
+ printf("Enum: 0x%02x{0x%08x (mdToken)} ", local_constant_sig->enum_constant.type_code, local_constant_sig->enum_constant.enum_type);
+ }
+ else if (local_constant_sig->constant_kind == mdck_GeneralConstant)
+ {
+ printf("General: 0x%02x{0x%08x (mdToken)} ", local_constant_sig->general.kind, local_constant_sig->general.type);
+ }
+ else
+ {
+ assert(!"Invalid constant kind.");
+ }
+ printf("Value Offset: %zu (len: %zu) [%#x]|", local_constant_sig->value_blob - table->cxt->blob_heap.ptr, local_constant_sig->value_len, raw_values[j]);
+
+ free(local_constant_sig);
+ continue;
+ }
+ else if (table->table_id == mdtid_ImportScope && col == mdtImportScope_Imports)
+ {
+ IF_NOT_REPORT_RAW(md_get_column_value_as_blob(cursor, col, &blob, &blob_len));
+
+ if (blob_len == 0)
+ {
+ printf("Empty Imports: Offset: %zu (len: %u) [%#x]|", (blob - table->cxt->blob_heap.ptr), blob_len, raw_values[j]);
+ continue;
+ }
+
+ md_imports_t* imports;
+ size_t imports_len;
+ IF_INVALID_BLOB_REPORT_RAW(md_parse_imports, table->cxt, "Imports", imports, imports_len);
+ printf("{ ");
+ bool first = true;
+ for (uint32_t k = 0; k < imports->count; ++k)
+ {
+ if (!first)
+ {
+ printf(", ");
+ }
+ first = false;
+ switch (imports->imports[k].kind)
+ {
+ case mdidk_ImportNamespace:
+ printf("ns('%.*s')", imports->imports[k].target_namespace_len, imports->imports[k].target_namespace);
+ break;
+ case mdidk_ImportAssemblyNamespace:
+ printf("ns('%.*s' in 0x%08x (mdToken))", imports->imports[k].target_namespace_len, imports->imports[k].target_namespace, imports->imports[k].assembly);
+ break;
+ case mdidk_ImportType:
+ printf("type(0x%08x (mdToken))", imports->imports[k].target_type);
+ break;
+ case mdidk_ImportXmlNamespace:
+ printf("xml-alias('%.*s' for '%.*s')", imports->imports[k].alias_len, imports->imports[k].alias, imports->imports[k].target_namespace_len, imports->imports[k].target_namespace);
+ break;
+ case mdidk_ImportAssemblyReferenceAlias:
+ printf("import-alias('%.*s')", imports->imports[k].alias_len, imports->imports[k].alias);
+ break;
+ case mdidk_AliasAssemblyReference:
+ printf("alias('%.*s' for 0x%08x (mdToken))", imports->imports[k].alias_len, imports->imports[k].alias, imports->imports[k].assembly);
+ break;
+ case mdidk_AliasNamespace:
+ printf("alias('%.*s' for '%.*s')", imports->imports[k].alias_len, imports->imports[k].alias, imports->imports[k].target_namespace_len, imports->imports[k].target_namespace);
+ break;
+ case mdidk_AliasAssemblyNamespace:
+ printf("alias('%.*s' for '%.*s' in 0x%08x (mdToken))", imports->imports[k].alias_len, imports->imports[k].alias, imports->imports[k].target_namespace_len, imports->imports[k].target_namespace, imports->imports[k].assembly);
+ break;
+ case mdidk_AliasType:
+ printf("alias('%.*s' for 0x%08x (mdToken))", imports->imports[k].alias_len, imports->imports[k].alias, imports->imports[k].target_type);
+ break;
+ default:
+ assert(!"Invalid import kind.");
+ break;
+ }
+ }
+
+ printf(" } [%#x]|", raw_values[j]);
+
+ free(imports);
+ continue;
+ }
+#endif
+ IF_NOT_REPORT_RAW(md_get_column_value_as_blob(cursor, col, &blob, &blob_len));
+ printf("Offset: %zu (len: %u) [%#x]|", (blob - table->cxt->blob_heap.ptr), blob_len, raw_values[j]);
+ }
+ else if (table->column_details[j] & mdtc_hus)
+ {
+ IF_NOT_REPORT_RAW(md_get_column_value_as_userstring(cursor, IDX(j), &user_string));
+ printf("UTF-16 string (%u bytes) [%#x]|", user_string.str_bytes, raw_values[j]);
+ }
+ else if (table->column_details[j] & (mdtc_idx_table | mdtc_idx_coded))
+ {
+ IF_NOT_REPORT_RAW(md_get_column_value_as_token(cursor, IDX(j), &tk));
+ printf("0x%08x (mdToken) [%#x]|", tk, raw_values[j]);
+ }
+ else
+ {
+ assert(table->column_details[j] & mdtc_constant);
+ IF_NOT_REPORT_RAW(md_get_column_value_as_constant(cursor, IDX(j), &constant));
+ printf("0x%08x [%#x]|", constant, raw_values[j]);
+ }
+ }
+ printf("\n");
+ if (!md_cursor_next(&cursor) && i != (table->row_count - 1))
+ return false;
+ }
+ printf("\n");
+#undef IF_NOT_REPORT_RAW
+#undef IF_INVALID_BLOB_REPORT_RAW
+
+ return true;
+}
+
+bool md_dump_tables(mdhandle_t handle, int32_t table_id)
+{
+ mdcxt_t* cxt = extract_mdcxt(handle);
+ if (cxt == NULL)
+ return false;
+
+ for (int32_t i = 0; i < MDTABLE_MAX_COUNT; ++i)
+ {
+ // Check if the user supplied a table to check
+ if (table_id > -1)
+ {
+ if (i < table_id) // Less than, skip.
+ continue;
+ if (i > table_id) // Greater than, done.
+ break;
+ assert(i == table_id);
+ }
+
+ if (!dump_table_rows(&cxt->tables[i]))
+ {
+ printf("Failure in table '%u'\n", i);
+ return false;
+ }
+ }
+
+ return true;
+}
+
+char const* md_get_version_string(mdhandle_t handle)
+{
+ mdcxt_t* cxt = extract_mdcxt(handle);
+ if (cxt == NULL)
+ return NULL;
+ return cxt->version;
+}
+
+mdcxt_t* extract_mdcxt(mdhandle_t md)
+{
+ mdcxt_t* cxt = (mdcxt_t*)md;
+ if (!cxt || cxt->magic != MDLIB_MAGIC_NUMBER)
+ return NULL;
+ return cxt;
+}
+
+void* alloc_mdmem(mdcxt_t* cxt, size_t length)
+{
+ assert(cxt != NULL);
+ mdmem_t* m = (mdmem_t*)malloc(sizeof(mdmem_t) + length);
+ if (m != NULL)
+ {
+ m->next = cxt->mem;
+ m->size = length;
+ cxt->mem = m;
+ return m->data;
+ }
+ return NULL;
+}
+
+void free_mdmem(mdcxt_t* cxt, void* mem)
+{
+ assert(cxt != NULL);
+ if (mem == NULL)
+ return;
+
+ // We need to get back to the mdmem_t header from the start of the block.
+ mdmem_t* m = (mdmem_t*)((char*)mem - offsetof(mdmem_t, data));
+
+ // Remove m from the chain of tracked memory.
+ if (cxt->mem == m)
+ {
+ cxt->mem = m->next;
+ }
+ else
+ {
+ for (mdmem_t* p = cxt->mem; p != NULL; p = p->next)
+ {
+ if (p->next == m)
+ {
+ p->next = m->next;
+ break;
+ }
+ }
+ }
+
+ // Now that we aren't tracking the memory, free it.
+ free(m);
+}
+
+static size_t get_stream_header_and_contents_size(char const* heap_name, size_t heap_size)
+{
+ assert(heap_name != NULL);
+ // II.24.2.2 Stream header
+ size_t const base_stream_header_size =
+ sizeof(uint32_t) // Offset
+ + sizeof(uint32_t) // Size
+ // Name is variable length and calculated below
+ ;
+
+ // Add the size of the stream header
+ // II.24.2.2 Stream name is padded to a 4-byte boundary
+ size_t save_size = base_stream_header_size;
+ save_size += align_to((uint32_t)strlen(heap_name) + 1, 4);
+ // Add the size of the stream itself.
+ // It's not placed directly after the header in the image,
+ // but we might as well account for it here while we're checking
+ // the heap's existence.
+ save_size += heap_size;
+
+ return save_size;
+}
+
+static size_t get_table_stream_size(mdcxt_t* cxt)
+{
+ // II.24.2.6 #~ stream
+ size_t const table_stream_header_size =
+ + sizeof(uint32_t) // Reserved
+ + sizeof(uint8_t) // MajorVersion
+ + sizeof(uint8_t) // MinorVersion
+ + sizeof(uint8_t) // HeapSizes
+ + sizeof(uint8_t) // Reserved
+ + sizeof(uint64_t) // Valid tables
+ + sizeof(uint64_t) // Sorted tables
+ // Rows and Tables entries are both variable length and calculated below
+ ;
+
+ size_t save_size = table_stream_header_size;
+
+ for (uint8_t i = 0; i < MDTABLE_MAX_COUNT; ++i)
+ {
+ if (cxt->tables[i].cxt != NULL && cxt->tables[i].row_count != 0)
+ {
+ save_size += sizeof(uint32_t); // Row count
+ save_size += cxt->tables[i].data.size; // Table data
+ }
+ }
+
+ return save_size;
+}
+
+static size_t get_image_size(mdcxt_t* cxt)
+{
+ if (cxt->editor == NULL)
+ return cxt->raw_metadata.size;
+
+ // II.24.2.1 Metadata Root size
+ size_t const image_header_size =
+ sizeof(uint32_t) // Signature
+ + sizeof(uint16_t) // MajorVersion
+ + sizeof(uint16_t) // MinorVersion
+ + sizeof(uint32_t) // Reserved
+ + sizeof(uint32_t) // Length (of version string)
+ + align_to((uint32_t)strlen(cxt->version) + 1, 4) // Version String
+ + sizeof(uint16_t) // Flags
+ + sizeof(uint16_t) // Streams (number of streams)
+ ;
+
+ size_t save_size = image_header_size;
+
+ if (cxt->blob_heap.size != 0)
+ save_size += get_stream_header_and_contents_size("#Blob", cxt->blob_heap.size);
+ if (cxt->guid_heap.size != 0)
+ save_size += get_stream_header_and_contents_size("#GUID", cxt->guid_heap.size);
+ if (cxt->strings_heap.size != 0)
+ save_size += get_stream_header_and_contents_size("#Strings", align_to((uint32_t)cxt->strings_heap.size, 4));
+ if (cxt->user_string_heap.size != 0)
+ save_size += get_stream_header_and_contents_size("#US", cxt->user_string_heap.size);
+
+ if (cxt->context_flags & mdc_minimal_delta)
+ save_size += get_stream_header_and_contents_size("#JTD", 0);
+
+ // All names of the tables stream are the same length,
+ // so pick the one in the standard.
+ save_size += get_stream_header_and_contents_size("#~", get_table_stream_size(cxt));
+
+ return save_size;
+}
+
+// II.24.2.2 Stream header
+static bool write_stream_header(char const* name, size_t size, mddata_t* offset_space, uint8_t** buffer, size_t* buffer_len)
+{
+ assert(offset_space != NULL);
+ size_t name_len = strlen(name);
+ size_t name_buf_len = align_to((uint32_t)name_len + 1, 4);
+
+ offset_space->ptr = *buffer;
+ offset_space->size = 4;
+
+ if (!advance_output_stream(buffer, buffer_len, 4) // Offset
+ || !write_u32(buffer, buffer_len, (uint32_t)size)) // Size
+ {
+ return false;
+ }
+
+ if (*buffer_len < name_buf_len)
+ return false;
+
+ // Name
+ memcpy(*buffer, name, name_len + 1);
+ advance_output_stream(buffer, buffer_len, name_len + 1);
+ // Pad the name to a 4-byte boundary.
+ advance_output_stream(buffer, buffer_len, name_buf_len - name_len - 1);
+
+ return true;
+}
+
+bool md_write_to_buffer(mdhandle_t handle, uint8_t* buffer, size_t* len)
+{
+ if (len == NULL)
+ return false;
+
+ mdcxt_t* cxt = extract_mdcxt(handle);
+ if (cxt == NULL)
+ return false;
+
+ size_t image_size = get_image_size(cxt);
+ size_t const full_buffer_len = *len;
+
+ // Handle the case where no edits have occurred.
+ // This operation is basically a "copy to new buffer".
+ if (cxt->editor == NULL)
+ {
+ if (buffer == NULL || full_buffer_len < cxt->raw_metadata.size)
+ {
+ *len = cxt->raw_metadata.size;
+ return false;
+ }
+ memcpy(buffer, cxt->raw_metadata.ptr, cxt->raw_metadata.size);
+ return true;
+ }
+
+ if (buffer == NULL || full_buffer_len < image_size)
+ {
+ *len = image_size;
+ return false;
+ }
+
+ uint8_t* const buffer_start = buffer;
+ size_t remaining_buffer_len = full_buffer_len;
+ if (!write_u32(&buffer, &remaining_buffer_len, METADATA_SIG)
+ || !write_u16(&buffer, &remaining_buffer_len, cxt->major_ver)
+ || !write_u16(&buffer, &remaining_buffer_len, cxt->minor_ver)
+ || !write_u32(&buffer, &remaining_buffer_len, 0))
+ {
+ return false;
+ }
+
+ size_t version_str_len = strlen(cxt->version);
+ uint32_t version_buf_len = align_to((uint32_t)version_str_len + 1, 4);
+
+ if (!write_u32(&buffer, &remaining_buffer_len, (uint32_t)version_buf_len))
+ return false;
+
+ if (remaining_buffer_len < version_buf_len)
+ return false;
+
+ memcpy(buffer, cxt->version, version_str_len + 1);
+ // Pad the version string to a 4-byte boundary.
+ memset(buffer + version_str_len + 1, 0, version_buf_len - version_str_len - 1);
+ advance_output_stream(&buffer, &remaining_buffer_len, version_buf_len);
+
+ if (!write_u16(&buffer, &remaining_buffer_len, cxt->flags))
+ return false;
+
+ uint16_t stream_count = 0;
+ if (cxt->blob_heap.size != 0)
+ stream_count++;
+ if (cxt->guid_heap.size != 0)
+ stream_count++;
+ if (cxt->strings_heap.size != 0)
+ stream_count++;
+ if (cxt->user_string_heap.size != 0)
+ stream_count++;
+
+ char const* tables_stream_name = "#~";
+
+ if (cxt->context_flags & mdc_minimal_delta)
+ {
+ tables_stream_name = "#-";
+ stream_count++;
+ }
+
+ uint64_t valid_tables = 0;
+ uint64_t sorted_tables = 0;
+ for (uint8_t i = 0; i < MDTABLE_MAX_COUNT; ++i)
+ {
+ if (cxt->tables[i].cxt != NULL && cxt->tables[i].row_count != 0)
+ {
+ // We don't support saving if we are in the process of adding a new row.
+ if (cxt->tables[i].is_adding_new_row)
+ return false;
+
+ valid_tables |= (1ULL << i);
+ if (cxt->tables[i].is_sorted)
+ sorted_tables |= (1ULL << i);
+
+ // Indirect tables only exist in images that use the uncompresed stream.
+ if (table_is_indirect_table((mdtable_id_t)i))
+ tables_stream_name = "#-";
+ }
+ }
+
+ // The tables stream is always included.
+ stream_count++;
+
+ if (!write_u16(&buffer, &remaining_buffer_len, stream_count))
+ return false;
+
+ mddata_t blob_heap_offset_space = { 0 };
+ mddata_t strings_heap_offset_space = { 0 };
+ mddata_t guid_heap_offset_space = { 0 };
+ mddata_t user_string_heap_offset_space = { 0 };
+ mddata_t tables_heap_offset_space = { 0 };
+#ifdef DNMD_PORTABLE_PDB
+ mddata_t pdb_offset_space = { 0 };
+#endif
+
+ // Write the stream headers.
+ if (cxt->context_flags & mdc_minimal_delta)
+ {
+ mddata_t offset_space;
+ if (!write_stream_header("#JTD", 0, &offset_space, &buffer, &remaining_buffer_len))
+ return false;
+
+ // Set the stream offset to the location of the stream header.
+ // There's no content in this stream, but the offset must be valid.
+ write_u32(&offset_space.ptr, &offset_space.size, (uint32_t)((uint8_t*)offset_space.ptr - buffer_start));
+ }
+
+ if (cxt->strings_heap.size != 0)
+ {
+ // The strings heap should be aligned to 4 bytes.
+ if (!write_stream_header("#Strings", align_to((uint32_t)cxt->strings_heap.size, 4), &strings_heap_offset_space, &buffer, &remaining_buffer_len))
+ return false;
+ }
+
+ if (cxt->blob_heap.size != 0)
+ {
+ if (!write_stream_header("#Blob", cxt->blob_heap.size, &blob_heap_offset_space, &buffer, &remaining_buffer_len))
+ return false;
+ }
+
+ if (cxt->guid_heap.size != 0)
+ {
+ if (!write_stream_header("#GUID", cxt->guid_heap.size, &guid_heap_offset_space, &buffer, &remaining_buffer_len))
+ return false;
+ }
+
+ if (cxt->user_string_heap.size != 0)
+ {
+ if (!write_stream_header("#US", cxt->user_string_heap.size, &user_string_heap_offset_space, &buffer, &remaining_buffer_len))
+ return false;
+ }
+
+#ifdef DNMD_PORTABLE_PDB
+ if (cxt->pdb.size != 0)
+ {
+ if (!write_stream_header("#Pdb", cxt->pdb.size, &pdb_offset_space, &buffer, &remaining_buffer_len))
+ return false;
+ }
+#endif // DNMD_PORTABLE_PDB
+
+ size_t table_stream_size = get_table_stream_size(cxt);
+
+ if (table_stream_size > UINT32_MAX)
+ return false;
+
+ if (!write_stream_header(tables_stream_name, (uint32_t)table_stream_size, &tables_heap_offset_space, &buffer, &remaining_buffer_len))
+ return false;
+
+ // Write the stream data
+ if (cxt->strings_heap.size != 0)
+ {
+ assert(strings_heap_offset_space.ptr != NULL && strings_heap_offset_space.size == 4);
+ write_u32(&strings_heap_offset_space.ptr, &strings_heap_offset_space.size, (uint32_t)(buffer - buffer_start));
+ uint32_t string_heap_size = align_to((uint32_t)cxt->strings_heap.size, 4);
+ if (remaining_buffer_len < string_heap_size)
+ return false;
+ memcpy(buffer, cxt->strings_heap.ptr, cxt->strings_heap.size);
+ memset((uint8_t*)buffer + cxt->strings_heap.size, 0, string_heap_size - cxt->strings_heap.size);
+ advance_output_stream(&buffer, &remaining_buffer_len, string_heap_size);
+ }
+
+ if (cxt->blob_heap.size != 0)
+ {
+ assert(blob_heap_offset_space.ptr != NULL && blob_heap_offset_space.size == 4);
+ write_u32(&blob_heap_offset_space.ptr, &blob_heap_offset_space.size, (uint32_t)(buffer - buffer_start));
+ if (remaining_buffer_len < cxt->blob_heap.size)
+ return false;
+ memcpy(buffer, cxt->blob_heap.ptr, cxt->blob_heap.size);
+ advance_output_stream(&buffer, &remaining_buffer_len, cxt->blob_heap.size);
+ }
+
+ if (cxt->guid_heap.size != 0)
+ {
+ assert(guid_heap_offset_space.ptr != NULL && guid_heap_offset_space.size == 4);
+ write_u32(&guid_heap_offset_space.ptr, &guid_heap_offset_space.size, (uint32_t)(buffer - buffer_start));
+ if (remaining_buffer_len < cxt->guid_heap.size)
+ return false;
+ memcpy(buffer, cxt->guid_heap.ptr, cxt->guid_heap.size);
+ advance_output_stream(&buffer, &remaining_buffer_len, cxt->guid_heap.size);
+ }
+
+ if (cxt->user_string_heap.size != 0)
+ {
+ assert(user_string_heap_offset_space.ptr != NULL && user_string_heap_offset_space.size == 4);
+ write_u32(&user_string_heap_offset_space.ptr, &user_string_heap_offset_space.size, (uint32_t)(buffer - buffer_start));
+ if (remaining_buffer_len < cxt->user_string_heap.size)
+ return false;
+ memcpy(buffer, cxt->user_string_heap.ptr, cxt->user_string_heap.size);
+ advance_output_stream(&buffer, &remaining_buffer_len, cxt->user_string_heap.size);
+ }
+
+#ifdef DNMD_PORTABLE_PDB
+ if (cxt->pdb.size != 0)
+ {
+ assert(pdb_offset_space.ptr != NULL && pdb_offset_space.size == 4);
+ write_u32(&pdb_offset_space.ptr, &pdb_offset_space.size, (uint32_t)(buffer - buffer_start));
+ if (remaining_buffer_len < cxt->pdb.size)
+ return false;
+ memcpy(buffer, cxt->pdb.ptr, cxt->pdb.size);
+ advance_output_stream(&buffer, &remaining_buffer_len, cxt->pdb.size);
+ }
+#endif // DNMD_PORTABLE_PDB
+
+ if (remaining_buffer_len < table_stream_size)
+ return false;
+
+ // Always write the table stream header. This is required for a valid image.
+ assert(tables_heap_offset_space.ptr != NULL && tables_heap_offset_space.size == 4);
+ write_u32(&tables_heap_offset_space.ptr, &tables_heap_offset_space.size, (uint32_t)(buffer - buffer_start));
+ if (!write_u32(&buffer, &remaining_buffer_len, 0) // Reserved
+ || !write_u8(&buffer, &remaining_buffer_len, 2) // MajorVersion
+ || !write_u8(&buffer, &remaining_buffer_len, 0) // MinorVersion
+ || !write_u8(&buffer, &remaining_buffer_len, (uint8_t)(cxt->context_flags & mdc_image_flags & ~mdc_extra_data)) // HeapOffsetSizes, excluding the extra data flag as we don't save it to write out.
+ || !write_u8(&buffer, &remaining_buffer_len, 1) // Reserved
+ || !write_u64(&buffer, &remaining_buffer_len, valid_tables)
+ || !write_u64(&buffer, &remaining_buffer_len, sorted_tables))
+ {
+ return false;
+ }
+
+ if (valid_tables != 0)
+ {
+ for (uint8_t i = 0; i < MDTABLE_MAX_COUNT; ++i)
+ {
+ if (valid_tables & (1ULL << i))
+ {
+ if (!write_u32(&buffer, &remaining_buffer_len, cxt->tables[i].row_count))
+ return false;
+ }
+ }
+
+ for (uint8_t i = 0; i < MDTABLE_MAX_COUNT; ++i)
+ {
+ if (valid_tables & (1ULL << i))
+ {
+ assert (remaining_buffer_len >= cxt->tables[i].data.size);
+ memcpy(buffer, cxt->tables[i].data.ptr, cxt->tables[i].data.size);
+ advance_output_stream(&buffer, &remaining_buffer_len, cxt->tables[i].data.size);
+ }
+ }
+ }
+
+ assert(full_buffer_len - remaining_buffer_len == image_size);
+ return true;
+}
diff --git a/src/native/dnmd/src/dnmd/internal.h b/src/native/dnmd/src/dnmd/internal.h
new file mode 100644
index 0000000000000..129236fa069cb
--- /dev/null
+++ b/src/native/dnmd/src/dnmd/internal.h
@@ -0,0 +1,457 @@
+#ifndef _SRC_DNMD_INTERNAL_H_
+#define _SRC_DNMD_INTERNAL_H_
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#ifdef DNMD_PORTABLE_PDB
+#include
+#endif
+
+// Implementations for missing bounds checking APIs.
+// See https://en.cppreference.com/w/c/error#Bounds_checking
+#if !defined(__STDC_LIB_EXT1__) && !defined(BUILD_WINDOWS)
+typedef size_t rsize_t;
+#endif // !__STDC_LIB_EXT1__
+
+#define ARRAY_SIZE(a) (sizeof(a) / sizeof(*a))
+
+#ifndef NDEBUG
+#define ASSERT_ASSUME(x) assert(x)
+#elif defined(_MSC_VER)
+#define ASSERT_ASSUME(x) __assume(x)
+#elif defined(__clang__)
+#define ASSERT_ASSUME(x) __builtin_assume(x)
+#elif defined(__GNUC__)
+#define ASSERT_ASSUME(x) do { if (!(x)) __builtin_unreachable(); } while (0)
+#else
+#define ASSERT_ASSUME(x) (void)(x)
+#endif
+
+// Mutable data
+typedef struct mddata__
+{
+ uint8_t* ptr;
+ size_t size;
+} mddata_t;
+
+// Const data
+typedef struct mdcdata__
+{
+ uint8_t const* ptr;
+ size_t size;
+} mdcdata_t;
+
+// II.24.2.6 - 64 is the maximum value
+#define MDTABLE_MAX_COUNT ((size_t)mdtid_End)
+static_assert(MDTABLE_MAX_COUNT <= 64, "Specification sets max table count to 64");
+
+#define MDTABLE_MAX_COLUMN_COUNT 9
+
+// Macros for computing token types.
+#define CreateTokenType(tk) (mdToken)(((uint32_t)tk << 24) & 0xff000000)
+#define ExtractTokenType(tk) ((tk >> 24) & 0xff)
+
+// Flags and masks used to embed column details for
+// interpreting table rows.
+typedef enum
+{
+ mdtc_none = 0x00000000,
+
+ // If the value should be taken as-is or used to index more
+ mdtc_constant = 0x00000001,
+ mdtc_idx_heap = 0x00000002, // Index into a heap - see flags below.
+ mdtc_idx_table = 0x00000004, // Index into a table - see mask below.
+ mdtc_idx_coded = 0x00000008, // Coded index - see II.24.2.6.
+ // Value category mask
+ mdtc_categorymask = 0x0000000f,
+
+ // Size of the constant or index
+ mdtc_b2 = 0x00000010, // 2-bytes
+ mdtc_b4 = 0x00000020, // 4-bytes
+ // Width of column flags
+ mdtc_widthmask = 0x00000030,
+
+ //mdtc_unused1 = 0x00000040,
+ //mdtc_unused2 = 0x00000080,
+
+ // Column byte offset into row
+ mdtc_comask = 0x0000ff00, // Mask for storing column offset
+
+ // Table values
+ mdtc_timask = 0x00ff0000, // Mask for storing table index
+
+ // Coded index
+ mdtc_cimask = 0x0f000000, // Mask for storing coded index map index
+
+ // Heap flags
+ mdtc_hguid = 0x10000000, // #GUID
+ mdtc_hstring = 0x20000000, // #Strings
+ mdtc_hus = 0x40000000, // #US
+ mdtc_hblob = 0x80000000, // #Blob
+ mdtc_hmask = 0xf0000000, // Mask for storing heap type
+} mdtcol_t;
+
+// Flags and masks for context details
+typedef enum
+{
+ mdc_none = 0x0000,
+ mdc_large_string_heap = 0x0001,
+ mdc_large_guid_heap = 0x0002,
+ mdc_large_blob_heap = 0x0004,
+ mdc_extra_data = 0x0040,
+ mdc_image_flags = 0xffff,
+ mdc_minimal_delta = 0x00010000,
+} mdcxt_flag_t;
+
+// Macros used to insert/extract the column offset.
+#define InsertOffset(o) ((o << 8) & mdtc_comask)
+#define ExtractOffset(o) ((o & mdtc_comask) >> 8)
+
+// Macros used to insert/extract the table for indexing.
+#define InsertTable(c) ((c << 16) & mdtc_timask)
+#define ExtractTable(c) ((c & mdtc_timask) >> 16)
+
+// Macros used to insert/extract the coded index map index
+#define InsertCodedIndex(s) ((s << 24) & mdtc_cimask)
+#define ExtractCodedIndex(s) ((s & mdtc_cimask) >> 24)
+
+// Macros used to insert/extract the heap type
+#define InsertHeapType(h) ((h) & mdtc_hmask)
+#define ExtractHeapType(h) ((h) & mdtc_hmask)
+
+// Forward declare.
+struct mdcxt__;
+
+typedef struct mdtable__
+{
+ mdcdata_t data;
+ uint32_t row_count;
+ uint8_t row_size_bytes;
+ uint8_t column_count;
+ bool is_sorted : 1;
+ bool is_adding_new_row : 1;
+ uint8_t table_id;
+ struct mdcxt__* cxt; // Non-null is indication of complete initialization
+ mdtcol_t* column_details;
+} mdtable_t;
+
+typedef mdcdata_t mdstream_t;
+
+typedef struct mdmem__ mdmem_t;
+
+typedef struct mdeditor__ mdeditor_t;
+
+typedef struct mdcxt__
+{
+ uint32_t magic; // mdlib magic
+ mdcdata_t raw_metadata; // metadata raw bytes
+ mdeditor_t* editor; // metadata editor
+ mdcxt_flag_t context_flags;
+
+ // Metadata root details - II.24.2.1
+ uint16_t major_ver;
+ uint16_t minor_ver;
+ uint16_t flags;
+ char const* version;
+
+ // Metadata heaps - II.24.2.2
+ mdstream_t strings_heap;
+ mdstream_t guid_heap;
+ mdstream_t blob_heap;
+ mdstream_t user_string_heap;
+ mdstream_t tables_heap;
+#ifdef DNMD_PORTABLE_PDB
+ mdstream_t pdb;
+#endif // DNMD_PORTABLE_PDB
+
+ // Metadata tables - II.22
+ mdtable_t* tables;
+
+ // Additional memory used for dynamic operations
+ mdmem_t* mem;
+} mdcxt_t;
+
+// Extract a context from the mdhandle_t.
+mdcxt_t* extract_mdcxt(mdhandle_t md);
+
+// Allocate and free tracked memory.
+void* alloc_mdmem(mdcxt_t* cxt, size_t length);
+void free_mdmem(mdcxt_t* cxt, void* mem);
+
+// Merge the supplied delta into the context.
+bool merge_in_delta(mdcxt_t* cxt, mdcxt_t* delta);
+
+//
+// Streams
+//
+
+mdstream_t* get_heap_by_id(mdcxt_t* cxt, mdtcol_t heap_id);
+mdcxt_flag_t get_large_heap_flag(mdtcol_t heap_id);
+
+// Strings heap, #Strings - II.24.2.3
+bool try_get_string(mdcxt_t* cxt, size_t offset, char const** str);
+bool validate_strings_heap(mdcxt_t* cxt);
+uint32_t add_to_string_heap(mdcxt_t* cxt, char const* str);
+
+// User strings heap, #US - II.24.2.4
+bool try_get_user_string(mdcxt_t* cxt, size_t offset, mduserstring_t* str, size_t* next_offset);
+bool validate_user_string_heap(mdcxt_t* cxt);
+uint32_t add_to_user_string_heap(mdcxt_t* cxt, char16_t const* str);
+
+// Blob heap, #Blob - II.24.2.4
+bool try_get_blob(mdcxt_t* cxt, size_t offset, uint8_t const** blob, uint32_t* blob_len);
+bool validate_blob_heap(mdcxt_t* cxt);
+uint32_t add_to_blob_heap(mdcxt_t* cxt, uint8_t const* data, uint32_t length);
+
+// GUID heap, #GUID - II.24.2.5
+bool try_get_guid(mdcxt_t* cxt, size_t idx, mdguid_t* guid);
+bool validate_guid_heap(mdcxt_t* cxt);
+uint32_t add_to_guid_heap(mdcxt_t* cxt, mdguid_t guid);
+
+// Table heap, #~ - II.24.2.6
+// Note: This can only be done after all streams have been read in.
+bool initialize_tables(mdcxt_t* cxt);
+bool validate_tables(mdcxt_t* cxt);
+
+// PDB heap, #Pdb - https://github.com/dotnet/runtime/blob/main/docs/design/specs/PortablePdb-Metadata.md#pdb-stream
+typedef struct md_pdb__
+{
+ uint8_t pdb_id[20];
+ mdToken entry_point;
+ uint64_t referenced_type_system_tables;
+ uint32_t type_system_table_rows[MDTABLE_MAX_COUNT];
+} md_pdb_t;
+
+// Interpret in the PDB data stream
+// The md_pdb_t will be fully initialized if "true" is returned.
+bool try_get_pdb(mdcxt_t* cxt, md_pdb_t* pdb);
+
+//
+// Tables
+//
+
+// Coded index collections - II.24.2.6
+typedef enum
+{
+ mdci_TypeDefOrRef,
+ mdci_HasConstant,
+ mdci_HasCustomAttribute,
+ mdci_HasFieldMarshall,
+ mdci_HasDeclSecurity,
+ mdci_MemberRefParent,
+ mdci_HasSemantics,
+ mdci_MethodDefOrRef,
+ mdci_MemberForwarded,
+ mdci_Implementation,
+ mdci_CustomAttributeType,
+ mdci_ResolutionScope,
+ mdci_TypeOrMethodDef,
+#ifdef DNMD_PORTABLE_PDB
+ mdci_HasCustomDebugInformation,
+#endif // DNMD_PORTABLE_PDB
+ mdci_Count
+} md_coded_idx_t;
+
+// Manipulators for coded indices - II.24.2.6
+bool compose_coded_index(mdToken tk, mdtcol_t col_details, uint32_t* coded_index);
+bool decompose_coded_index(uint32_t cidx, mdtcol_t col_details, mdtable_id_t* table_id, uint32_t* table_row);
+bool is_coded_index_target(mdtcol_t col_details, mdtable_id_t table);
+
+// Get the column count for a table.
+uint8_t get_table_column_count(mdtable_id_t id);
+
+// II.22 Metadata logical format tables
+// Sort key info for tables
+
+typedef struct md_key_info__
+{
+ uint8_t index;
+ bool descending;
+} md_key_info_t;
+
+uint8_t get_table_keys(mdtable_id_t id, md_key_info_t const** keys);
+
+// Initialize the supplied table details
+bool initialize_table_details(
+ uint32_t const* all_table_row_counts,
+ mdcxt_flag_t context_flags,
+ mdtable_id_t id,
+ bool is_sorted,
+ mdtable_t* table);
+
+// Given the current table, consume the data stream assuming it contains the rows
+bool consume_table_rows(mdtable_t* table, uint8_t const** data, size_t* data_len);
+
+// Get whether or not the column in the table points into an indirect table
+bool table_is_indirect_table(mdtable_id_t table_id);
+// Get the indirection table for a given table
+mdtable_id_t get_corresponding_indirection_table(mdtable_id_t table_id);
+
+// Cursor manipulation
+
+
+// Internal function used to create a cursor.
+// Limited validation is done for the arguments.
+mdcursor_t create_cursor(mdtable_t* table, uint32_t row);
+
+// We declare these functions as static so they can be included in each translation unit.
+// Some units may not use them, so we ignore the unused function warning.
+#ifdef __GNUC__
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-function"
+#endif
+static mdtable_t* CursorTable(mdcursor_t* c)
+{
+ assert(c != NULL);
+ return (mdtable_t*)c->_reserved1;
+}
+
+static uint32_t CursorRow(mdcursor_t* c)
+{
+ assert(c != NULL);
+ return RidFromToken(c->_reserved2);
+}
+
+static bool CursorNull(mdcursor_t* c)
+{
+ return CursorRow(c) == 0;
+}
+
+static bool CursorEnd(mdcursor_t* c)
+{
+ return (CursorTable(c)->row_count + 1) == CursorRow(c);
+}
+
+static uint8_t col_to_index(col_index_t col_idx, mdtable_t const* table)
+{
+ assert(table != NULL);
+ uint32_t idx = (uint32_t)col_idx;
+#ifdef DEBUG_TABLE_COLUMN_LOOKUP
+ mdtable_id_t tgt_table_id = col_idx >> 8;
+ if (tgt_table_id != table->table_id)
+ {
+ assert(!"Unexpected table/column indexing");
+ return false;
+ }
+ idx = (col_idx & 0xff);
+#else
+ (void)table;
+#endif
+ return (uint8_t)idx;
+}
+
+static col_index_t index_to_col(uint8_t idx, mdtable_id_t table_id)
+{
+#ifdef DEBUG_TABLE_COLUMN_LOOKUP
+ return (col_index_t)((table_id << 8) | idx);
+#else
+ (void)table_id;
+ return (col_index_t)idx;
+#endif
+}
+#ifdef __GNUC__
+#pragma GCC diagnostic pop
+#endif
+
+// Copy data from a cursor to one row to a cursor to another row.
+bool copy_cursor(mdcursor_t dest, mdcursor_t src);
+
+// Single column access
+typedef struct access_cxt__
+{
+ mdtable_t* table;
+ mdtcol_t col_details;
+ uint8_t const* data;
+ uint8_t* writable_data;
+} access_cxt_t;
+
+bool create_access_context(mdcursor_t* cursor, col_index_t col_idx, bool make_writable, access_cxt_t* rcxt);
+bool write_column_data(access_cxt_t* acxt, uint32_t data);
+bool read_column_data(access_cxt_t* acxt, uint32_t* data);
+
+// Raw bulk table access
+typedef struct bulk_access_cxt__
+{
+ mdtable_t* table;
+ mdtcol_t col_details;
+ uint8_t const* start;
+ uint8_t const* data;
+ uint8_t const* end;
+ size_t data_len;
+ uint32_t data_len_col;
+ uint32_t next_row_stride;
+} bulk_access_cxt_t;
+
+bool create_bulk_access_context(mdcursor_t* cursor, col_index_t col_idx, uint32_t row_count, bulk_access_cxt_t* acxt);
+bool read_column_data_and_advance(bulk_access_cxt_t* acxt, uint32_t* data);
+bool next_row(bulk_access_cxt_t* acxt);
+
+// Internal functions used to read/write columns with minimal validation.
+bool get_column_value_as_heap_offset(mdcursor_t c, col_index_t col_idx, uint32_t* offset);
+bool set_column_value_as_heap_offset(mdcursor_t c, col_index_t col_idx, uint32_t offset);
+
+//
+// Manipulation of bits
+//
+
+uint32_t align_to(uint32_t val, uint32_t align);
+size_t count_set_bits(uint64_t val);
+
+//
+// Byte streams
+//
+
+bool advance_stream(uint8_t const** data, size_t* data_len, size_t b);
+
+bool read_u8(uint8_t const** data, size_t* data_len, uint8_t* o);
+bool read_i8(uint8_t const** data, size_t* data_len, int8_t* o);
+bool read_u16(uint8_t const** data, size_t* data_len, uint16_t* o);
+bool read_i16(uint8_t const** data, size_t* data_len, int16_t* o);
+bool read_u32(uint8_t const** data, size_t* data_len, uint32_t* o);
+bool read_i32(uint8_t const** data, size_t* data_len, int32_t* o);
+bool read_u64(uint8_t const** data, size_t* data_len, uint64_t* o);
+bool read_i64(uint8_t const** data, size_t* data_len, int64_t* o);
+
+bool advance_output_stream(uint8_t** data, size_t* data_len, size_t b);
+
+bool write_u8(uint8_t** data, size_t* data_len, uint8_t o);
+bool write_i8(uint8_t** data, size_t* data_len, int8_t o);
+bool write_u16(uint8_t** data, size_t* data_len, uint16_t o);
+bool write_i16(uint8_t** data, size_t* data_len, int16_t o);
+bool write_u32(uint8_t** data, size_t* data_len, uint32_t o);
+bool write_i32(uint8_t** data, size_t* data_len, int32_t o);
+bool write_u64(uint8_t** data, size_t* data_len, uint64_t o);
+bool write_i64(uint8_t** data, size_t* data_len, int64_t o);
+
+// II.23.2
+bool decompress_u32(uint8_t const** data, size_t* data_len, uint32_t* o);
+bool decompress_i32(uint8_t const** data, size_t* data_len, int32_t* o);
+// compressed_len is an in/out parameter. If compress_u32 returns true, then
+// compressed_len is set to the number of bytes written to compressed.
+bool compress_u32(uint32_t data, uint8_t* compressed, size_t* compressed_len);
+
+// Editing
+bool create_and_fill_indirect_table(mdcxt_t* cxt, mdtable_id_t original_table, mdtable_id_t indirect_table);
+bool allocate_new_table(mdcxt_t* cxt, mdtable_id_t table_id);
+uint8_t* get_writable_table_data(mdtable_t* table, bool make_writable);
+bool initialize_new_table_details(mdcxt_t* cxt, mdtable_id_t id, mdtable_t* table);
+int32_t update_shifted_row_references(mdcursor_t* c, uint32_t count, uint8_t col_index, mdtable_id_t updated_table, uint32_t original_starting_table_index, uint32_t new_starting_table_index);
+bool insert_row_into_table(mdcxt_t* cxt, mdtable_id_t table_id, uint32_t row_index, mdcursor_t* new_row);
+#ifdef DNMD_PORTABLE_PDB
+bool update_referenced_type_system_table_row_count(mdcxt_t* cxt, mdtable_id_t updated_table, uint32_t new_max_row_count);
+#endif // DNMD_PORTABLE_PDB
+
+// Sort a row list (like FieldList, MethodList, ParamList, etc.) by the values specified in the given constant column on the target table.
+bool sort_list_by_column(mdcursor_t parent, col_index_t list_col, col_index_t col);
+
+// Add the heap with the specified id from the delta image to the cxt image.
+bool append_heap(mdcxt_t* cxt, mdcxt_t* delta, mdtcol_t heap_id);
+
+extern mdguid_t const empty_guid;
+
+#endif // _SRC_DNMD_INTERNAL_H_
diff --git a/src/native/dnmd/src/dnmd/pdb_blobs.c b/src/native/dnmd/src/dnmd/pdb_blobs.c
new file mode 100644
index 0000000000000..a604dd8835cf6
--- /dev/null
+++ b/src/native/dnmd/src/dnmd/pdb_blobs.c
@@ -0,0 +1,705 @@
+#include "internal.h"
+
+md_blob_parse_result_t md_parse_document_name(mdhandle_t handle, uint8_t const* blob, size_t blob_len, char const* name, size_t* name_len)
+{
+ mdcxt_t* cxt = extract_mdcxt(handle);
+ if (cxt == NULL)
+ return mdbpr_InvalidArgument;
+
+ if (blob == NULL || name_len == NULL)
+ return mdbpr_InvalidArgument;
+
+ // Only support one-character ASCII seperators.
+ // System.Reflection.Metadata.MetadataReader has the same limitation
+ uint8_t separator;
+ if (!read_u8(&blob, &blob_len, &separator))
+ return mdbpr_InvalidBlob;
+
+ if (separator > 0x7f)
+ return mdbpr_InvalidBlob;
+
+ uint8_t* name_current = (uint8_t*)name;
+ size_t remaining_name_len = *name_len;
+ size_t required_len = 0;
+ md_blob_parse_result_t result = mdbpr_Success;
+ bool write_separator = false;
+ while (blob_len > 0)
+ {
+ if (write_separator)
+ {
+ // Add the required space for the separator.
+ // If there is space in the buffer, write the separator.
+ required_len += 1;
+ if (name_current == NULL || remaining_name_len == 0)
+ {
+ result = mdbpr_InsufficientBuffer;
+ }
+ else
+ {
+ write_u8(&name_current, &remaining_name_len, separator);
+ }
+ }
+ write_separator = separator != 0;
+
+ // Get the next part of the path.
+ uint32_t part_offset;
+ if (!decompress_u32(&blob, &blob_len, &part_offset))
+ return mdbpr_InvalidBlob;
+
+ // The part blob is a UTF-8 string that is not null-terminated.
+ const uint8_t* part;
+ uint32_t part_len;
+ if (!try_get_blob(cxt, part_offset, &part, &part_len))
+ return mdbpr_InvalidBlob;
+
+ // Add the required space for the part.
+ // If there is space in the buffer, write the part.
+ required_len += part_len;
+ if (name_current == NULL || remaining_name_len < part_len)
+ {
+ result = mdbpr_InsufficientBuffer;
+ continue;
+ }
+ else
+ {
+ memcpy(name_current, part, part_len);
+ bool success = advance_output_stream(&name_current, &remaining_name_len, part_len);
+ assert(success);
+ (void)success;
+ }
+ }
+
+ // Add the null terminator.
+ required_len++;
+ if (name_current != NULL && remaining_name_len > 0)
+ write_u8(&name_current, &remaining_name_len, 0);
+ else
+ result = mdbpr_InsufficientBuffer;
+
+ *name_len = required_len;
+ return result;
+}
+
+// We only support up to UINT32_MAX - 1 sequence points per method.
+// Technically, the number of supported sequence points in the spec is unbounded.
+// However, the PE format that an ECMA-335 blob is commonly wrapped in
+// can only support up to 4GB files, so we can't possibly have UINT32_MAX - 1 entries
+// in any existing scenario anyway.
+static uint32_t get_num_sequence_points(mdcursor_t method_debug_information, uint8_t const* blob, size_t blob_len)
+{
+ uint32_t num_records = 0;
+ uint32_t ignored;
+ if (!decompress_u32(&blob, &blob_len, &ignored)) // header LocalSignature
+ return UINT32_MAX;
+
+ mdcursor_t document;
+ if (!md_get_column_value_as_cursor(method_debug_information, mdtMethodDebugInformation_Document, &document))
+ return UINT32_MAX;
+
+ if (CursorNull(&document) && !decompress_u32(&blob, &blob_len, &ignored)) // header InitialDocument
+ return UINT32_MAX;
+
+ bool first_record = true;
+ while (blob_len > 0)
+ {
+ if (num_records == UINT32_MAX)
+ return UINT32_MAX;
+ num_records++;
+ uint32_t il_offset;
+ if (!decompress_u32(&blob, &blob_len, &il_offset)) // ILOffset
+ return UINT32_MAX;
+
+ // The first record cannot be a document record
+ if (!first_record && il_offset == 0)
+ {
+ uint32_t document_offset;
+ if (!decompress_u32(&blob, &blob_len, &document_offset)) // Document
+ return UINT32_MAX;
+ }
+ else
+ {
+ // We don't need to check if we need to do an unsigned or signed decompression
+ // as we will always read the same number of bytes and we don't care about the values here
+ // as we're only calculating the number of records.
+ uint32_t delta_lines;
+ if (!decompress_u32(&blob, &blob_len, &delta_lines)) // DeltaLines
+ return UINT32_MAX;
+
+ uint32_t delta_columns;
+ if (!decompress_u32(&blob, &blob_len, &delta_columns)) // DeltaColumns
+ return UINT32_MAX;
+ if (delta_lines != 0 || delta_columns != 0)
+ {
+ uint32_t start_line;
+ if (!decompress_u32(&blob, &blob_len, &start_line)) // StartLine
+ return UINT32_MAX;
+ uint32_t start_column;
+ if (!decompress_u32(&blob, &blob_len, &start_column)) // StartColumn
+ return UINT32_MAX;
+ }
+ }
+
+ first_record = false;
+ }
+
+ return num_records;
+}
+
+md_blob_parse_result_t md_parse_sequence_points(
+ mdcursor_t method_debug_information,
+ uint8_t const* blob,
+ size_t blob_len,
+ md_sequence_points_t* sequence_points,
+ size_t* buffer_len)
+{
+ if (CursorNull(&method_debug_information) || CursorEnd(&method_debug_information))
+ return mdbpr_InvalidArgument;
+
+ if (blob == NULL || buffer_len == NULL)
+ return mdbpr_InvalidArgument;
+
+ uint32_t num_records = get_num_sequence_points(method_debug_information, blob, blob_len);
+
+ if (num_records == UINT32_MAX)
+ return mdbpr_InvalidBlob;
+
+ size_t required_size = sizeof(md_sequence_points_t) + num_records * sizeof(sequence_points->records[0]);
+ if (sequence_points == NULL || *buffer_len < required_size)
+ {
+ *buffer_len = required_size;
+ return mdbpr_InsufficientBuffer;
+ }
+
+ // header LocalSignature
+ if (!decompress_u32(&blob, &blob_len, &sequence_points->signature))
+ return mdbpr_InvalidBlob;
+
+ mdcursor_t document;
+ if (!md_get_column_value_as_cursor(method_debug_information, mdtMethodDebugInformation_Document, &document))
+ return mdbpr_InvalidBlob;
+
+ // Create a "null" cursor to default-initialize the document field.
+ mdcxt_t* cxt = extract_mdcxt(md_extract_handle_from_cursor(method_debug_information));
+ sequence_points->document = create_cursor(&cxt->tables[mdtid_Document], 0);
+
+ // header InitialDocument
+ uint32_t document_rid = 0;
+ if (CursorNull(&document)
+ && !decompress_u32(&blob, &blob_len, &document_rid))
+ {
+ return mdbpr_InvalidBlob;
+ }
+
+ if (document_rid != 0
+ && !md_token_to_cursor(cxt, CreateTokenType(mdtid_Document) | document_rid, &sequence_points->document))
+ {
+ return mdbpr_InvalidBlob;
+ }
+
+ bool seen_non_hidden_sequence_point = false;
+ for (uint32_t i = 0; blob_len > 0 && i < num_records; ++i)
+ {
+ uint32_t il_offset;
+ if (!decompress_u32(&blob, &blob_len, &il_offset)) // ILOffset
+ return mdbpr_InvalidBlob;
+
+ // Check if the method transitioned
+ // into a new source file.
+ if (i != 0 && il_offset == 0)
+ {
+ uint32_t document_row_id;
+ if (!decompress_u32(&blob, &blob_len, &document_row_id)) // Document
+ return mdbpr_InvalidBlob;
+
+ sequence_points->records[i].kind = mdsp_DocumentRecord;
+ if (!md_token_to_cursor(cxt, CreateTokenType(mdtid_Document) | document_row_id, &sequence_points->records[i].document.document))
+ return mdbpr_InvalidBlob;
+
+ continue;
+ }
+
+ uint32_t delta_lines;
+ if (!decompress_u32(&blob, &blob_len, &delta_lines)) // DeltaLines
+ return mdbpr_InvalidBlob;
+
+ int64_t delta_columns;
+ if (delta_lines == 0)
+ {
+ uint32_t raw_delta_columns;
+ if (!decompress_u32(&blob, &blob_len, &raw_delta_columns)) // DeltaColumns
+ return mdbpr_InvalidBlob;
+ delta_columns = raw_delta_columns;
+ }
+ else
+ {
+ int32_t raw_delta_columns;
+ if (!decompress_i32(&blob, &blob_len, &raw_delta_columns)) // DeltaColumns
+ return mdbpr_InvalidBlob;
+ delta_columns = raw_delta_columns;
+ }
+
+ // Check for hidden point
+ if (delta_lines == 0 && delta_columns == 0)
+ {
+ sequence_points->records[i].kind = mdsp_HiddenSequencePointRecord;
+ sequence_points->records[i].hidden_sequence_point.rolling_il_offset = il_offset;
+ continue;
+ }
+
+ int64_t start_line;
+ int64_t start_column;
+ if (!seen_non_hidden_sequence_point)
+ {
+ seen_non_hidden_sequence_point = true;
+ uint32_t start_line_raw;
+ if (!decompress_u32(&blob, &blob_len, &start_line_raw)) // StartLine
+ return mdbpr_InvalidBlob;
+ uint32_t start_column_raw;
+ if (!decompress_u32(&blob, &blob_len, &start_column_raw)) // StartColumn
+ return mdbpr_InvalidBlob;
+ start_line = start_line_raw;
+ start_column = start_column_raw;
+ }
+ else
+ {
+ // If we've seen a non-hidden sequence point,
+ // then the values are compressed signed integers instead of
+ // unsigned integers.
+ int32_t start_line_raw;
+ if (!decompress_i32(&blob, &blob_len, &start_line_raw)) // StartLine
+ return mdbpr_InvalidBlob;
+ int32_t start_column_raw;
+ if (!decompress_i32(&blob, &blob_len, &start_column_raw)) // StartColumn
+ return mdbpr_InvalidBlob;
+ start_line = start_line_raw;
+ start_column = start_column_raw;
+ }
+
+ sequence_points->records[i].kind = mdsp_SequencePointRecord;
+ sequence_points->records[i].sequence_point.rolling_il_offset = il_offset;
+ sequence_points->records[i].sequence_point.delta_lines = delta_lines;
+ sequence_points->records[i].sequence_point.delta_columns = delta_columns;
+ sequence_points->records[i].sequence_point.rolling_start_line = start_line;
+ sequence_points->records[i].sequence_point.rolling_start_column = start_column;
+ }
+
+ if (blob_len != 0)
+ return mdbpr_InvalidBlob;
+
+ sequence_points->record_count = num_records;
+ return mdbpr_Success;
+}
+
+md_blob_parse_result_t md_parse_local_constant_sig(mdhandle_t handle, uint8_t const* blob, size_t blob_len, md_local_constant_sig_t* local_constant_sig, size_t* buffer_len)
+{
+ if (extract_mdcxt(handle) == NULL || blob == NULL || buffer_len == NULL)
+ return mdbpr_InvalidArgument;
+
+ // Walk the custom modifiers portion of the signature to calculate the required buffer space.
+ uint8_t const* custom_modifiers_blob = blob;
+ size_t custom_modifiers_blob_len = blob_len;
+ uint32_t num_custom_modifiers = 0;
+ for (; custom_modifiers_blob_len > 0; ++num_custom_modifiers)
+ {
+ uint32_t element_type;
+ if (!decompress_u32(&custom_modifiers_blob, &custom_modifiers_blob_len, &element_type))
+ return mdbpr_InvalidBlob;
+
+ if (element_type != ELEMENT_TYPE_CMOD_OPT && element_type != ELEMENT_TYPE_CMOD_REQD)
+ break;
+
+ uint32_t cindex;
+ if (!decompress_u32(&custom_modifiers_blob, &custom_modifiers_blob_len, &cindex))
+ return mdbpr_InvalidBlob;
+ }
+
+ size_t required_size = sizeof(md_local_constant_sig_t) + num_custom_modifiers * sizeof(local_constant_sig->custom_modifiers[0]);
+ if (local_constant_sig == NULL || *buffer_len < required_size)
+ {
+ *buffer_len = required_size;
+ return mdbpr_InsufficientBuffer;
+ }
+
+ local_constant_sig->custom_modifier_count = num_custom_modifiers;
+
+ for (uint32_t i = 0; i < num_custom_modifiers; ++i)
+ {
+ uint32_t element_type;
+ if (!decompress_u32(&custom_modifiers_blob, &custom_modifiers_blob_len, &element_type))
+ return mdbpr_InvalidBlob;
+
+ if (element_type != ELEMENT_TYPE_CMOD_OPT && element_type != ELEMENT_TYPE_CMOD_REQD)
+ break;
+
+ local_constant_sig->custom_modifiers[i].required = element_type == ELEMENT_TYPE_CMOD_REQD;
+
+ uint32_t cindex;
+ if (!decompress_u32(&custom_modifiers_blob, &custom_modifiers_blob_len, &cindex))
+ return mdbpr_InvalidBlob;
+
+ mdtable_id_t table;
+ uint32_t row_id;
+ // Technically the spec defines this as a TypeDefOrRefOrSpecEncoded token,
+ // but the implementation of the TypeDefOrRef coded index has the same configuration as the
+ // TypeDefOrRefOrSpec encoding.
+ if (!decompose_coded_index(cindex, mdtc_idx_coded | InsertCodedIndex(mdci_TypeDefOrRef), &table, &row_id))
+ return mdbpr_InvalidBlob;
+
+ local_constant_sig->custom_modifiers[i].type = CreateTokenType(table) | row_id;
+ }
+
+ uint32_t type_code;
+ if (!decompress_u32(&blob, &blob_len, &type_code))
+ return mdbpr_InvalidBlob;
+
+ uint32_t constant_type_index;
+ mdtable_id_t constant_type_table;
+ uint32_t constant_type_row;
+ switch (type_code)
+ {
+ case ELEMENT_TYPE_OBJECT:
+ local_constant_sig->constant_kind = mdck_GeneralConstant;
+ local_constant_sig->general.kind = mdgc_Object;
+ local_constant_sig->general.type = 0;
+ local_constant_sig->value_blob = blob;
+ local_constant_sig->value_len = blob_len;
+ break;
+ case ELEMENT_TYPE_VALUETYPE:
+ local_constant_sig->constant_kind = mdck_GeneralConstant;
+ local_constant_sig->general.kind = mdgc_ValueType;
+ if (!decompress_u32(&blob, &blob_len, &constant_type_index))
+ return mdbpr_InvalidBlob;
+ if (!decompose_coded_index(constant_type_index, mdtc_idx_coded | InsertCodedIndex(mdci_TypeDefOrRef), &constant_type_table, &constant_type_row))
+ return mdbpr_InvalidBlob;
+ local_constant_sig->general.type = CreateTokenType(constant_type_table) | constant_type_row;
+ local_constant_sig->value_blob = blob;
+ local_constant_sig->value_len = blob_len;
+ break;
+ case ELEMENT_TYPE_CLASS:
+ local_constant_sig->constant_kind = mdck_GeneralConstant;
+ local_constant_sig->general.kind = mdgc_Class;
+ if (!decompress_u32(&blob, &blob_len, &constant_type_index))
+ return mdbpr_InvalidBlob;
+ if (!decompose_coded_index(constant_type_index, mdtc_idx_coded | InsertCodedIndex(mdci_TypeDefOrRef), &constant_type_table, &constant_type_row))
+ return mdbpr_InvalidBlob;
+ local_constant_sig->general.type = CreateTokenType(constant_type_table) | constant_type_row;
+ local_constant_sig->value_blob = blob;
+ local_constant_sig->value_len = blob_len;
+ break;
+ // These constants are never enums, so we don't need to skip the content.
+ case ELEMENT_TYPE_R4:
+ if (blob_len != 4)
+ return mdbpr_InvalidBlob;
+ local_constant_sig->constant_kind = mdck_PrimitiveConstant;
+ local_constant_sig->primitive.type_code = (uint8_t)type_code;
+ local_constant_sig->value_blob = blob;
+ local_constant_sig->value_len = blob_len;
+ break;
+ case ELEMENT_TYPE_R8:
+ if (blob_len != 8)
+ return mdbpr_InvalidBlob;
+ local_constant_sig->constant_kind = mdck_PrimitiveConstant;
+ local_constant_sig->primitive.type_code = (uint8_t)type_code;
+ local_constant_sig->value_blob = blob;
+ local_constant_sig->value_len = blob_len;
+ break;
+ case ELEMENT_TYPE_STRING:
+ local_constant_sig->constant_kind = mdck_PrimitiveConstant;
+ local_constant_sig->primitive.type_code = (uint8_t)type_code;
+ local_constant_sig->value_blob = blob;
+ local_constant_sig->value_len = blob_len;
+ break;
+ // These constant types might be enums, so we need to check if there's a TypeDefOrRefOrSpecEncoded value
+ // after the value to determine if the constant is an enum.
+ case ELEMENT_TYPE_BOOLEAN:
+ case ELEMENT_TYPE_CHAR:
+ case ELEMENT_TYPE_I1:
+ case ELEMENT_TYPE_U1:
+ case ELEMENT_TYPE_I2:
+ case ELEMENT_TYPE_U2:
+ case ELEMENT_TYPE_I4:
+ case ELEMENT_TYPE_U4:
+ case ELEMENT_TYPE_I8:
+ case ELEMENT_TYPE_U8:
+ // Save off the value.
+ local_constant_sig->value_blob = blob;
+ local_constant_sig->value_len = blob_len;
+ switch (type_code)
+ {
+ case ELEMENT_TYPE_BOOLEAN:
+ case ELEMENT_TYPE_I1:
+ case ELEMENT_TYPE_U1:
+ {
+ uint8_t dummy;
+ if (!read_u8(&blob, &blob_len, &dummy))
+ return mdbpr_InvalidBlob;
+ break;
+ }
+ case ELEMENT_TYPE_CHAR:
+ case ELEMENT_TYPE_I2:
+ case ELEMENT_TYPE_U2:
+ {
+ uint16_t dummy;
+ if (!read_u16(&blob, &blob_len, &dummy))
+ return mdbpr_InvalidBlob;
+ break;
+ }
+ case ELEMENT_TYPE_I4:
+ case ELEMENT_TYPE_U4:
+ {
+ uint32_t dummy;
+ if (!read_u32(&blob, &blob_len, &dummy))
+ return mdbpr_InvalidBlob;
+ break;
+ }
+ case ELEMENT_TYPE_I8:
+ case ELEMENT_TYPE_U8:
+ {
+ uint64_t dummy;
+ if (!read_u64(&blob, &blob_len, &dummy))
+ return mdbpr_InvalidBlob;
+ break;
+ }
+ default:
+ assert(false);
+ return mdbpr_InvalidArgument;
+ }
+
+ // Check if there is any remaining blob data.
+ if (blob_len == 0)
+ {
+ local_constant_sig->constant_kind = mdck_PrimitiveConstant;
+ local_constant_sig->primitive.type_code = (uint8_t)type_code;
+ }
+ else
+ {
+ // If we have data remaining, then we need to read the enum type.
+ // In this case, we subtract off the rest of the blob length from the value blob length
+ // as it isn't part of the value.
+ local_constant_sig->value_len -= blob_len;
+ if (!decompress_u32(&blob, &blob_len, &constant_type_index)
+ || !decompose_coded_index(constant_type_index, mdtc_idx_coded | InsertCodedIndex(mdci_TypeDefOrRef), &constant_type_table, &constant_type_row))
+ {
+ return mdbpr_InvalidBlob;
+ }
+
+ local_constant_sig->constant_kind = mdck_EnumConstant;
+ local_constant_sig->enum_constant.type_code = (uint8_t)type_code;
+ local_constant_sig->enum_constant.enum_type = CreateTokenType(constant_type_table) | constant_type_row;
+ }
+ break;
+ default:
+ assert(false);
+ return mdbpr_InvalidArgument;
+ }
+ return mdbpr_Success;
+}
+
+// We only support up to UINT32_MAX - 1 imports per Imports blob.
+// Technically, the number of supported imports in the spec is unbounded.
+// However, the PE format that an ECMA-335 blob is commonly wrapped in
+// can only support up to 4GB files, so we can't possibly have UINT32_MAX - 1 entries
+// in any existing scenario anyway.
+static uint32_t get_num_imports(uint8_t const* blob, size_t blob_len)
+{
+ uint32_t num_imports = 0;
+ for (;blob_len > 0 && num_imports < UINT32_MAX; ++num_imports)
+ {
+ uint8_t kind;
+ if (!read_u8(&blob, &blob_len, &kind))
+ return UINT32_MAX;
+
+ uint32_t raw;
+ switch (kind)
+ {
+ case mdidk_ImportNamespace:
+ if (!decompress_u32(&blob, &blob_len, &raw)) // target-namespace
+ return UINT32_MAX;
+ break;
+ case mdidk_ImportAssemblyNamespace:
+ if (!decompress_u32(&blob, &blob_len, &raw)) // target-assembly
+ return UINT32_MAX;
+ if (!decompress_u32(&blob, &blob_len, &raw)) // target-namespace
+ return UINT32_MAX;
+ break;
+ case mdidk_ImportType:
+ if (!decompress_u32(&blob, &blob_len, &raw)) // target-type
+ return UINT32_MAX;
+ break;
+ case mdidk_AliasNamespace:
+ case mdidk_ImportXmlNamespace:
+ if (!decompress_u32(&blob, &blob_len, &raw)) // alias
+ return UINT32_MAX;
+ if (!decompress_u32(&blob, &blob_len, &raw)) // target-namespace
+ return UINT32_MAX;
+ break;
+ case mdidk_ImportAssemblyReferenceAlias:
+ if (!decompress_u32(&blob, &blob_len, &raw)) // alias
+ return UINT32_MAX;
+ break;
+ case mdidk_AliasAssemblyReference:
+ if (!decompress_u32(&blob, &blob_len, &raw)) // alias
+ return UINT32_MAX;
+ if (!decompress_u32(&blob, &blob_len, &raw)) // target-assembly
+ return UINT32_MAX;
+ break;
+ case mdidk_AliasAssemblyNamespace:
+ if (!decompress_u32(&blob, &blob_len, &raw)) // alias
+ return UINT32_MAX;
+ if (!decompress_u32(&blob, &blob_len, &raw)) // target-assembly
+ return UINT32_MAX;
+ if (!decompress_u32(&blob, &blob_len, &raw)) // target-namespace
+ return UINT32_MAX;
+ break;
+ case mdidk_AliasType:
+ if (!decompress_u32(&blob, &blob_len, &raw)) // alias
+ return UINT32_MAX;
+ if (!decompress_u32(&blob, &blob_len, &raw)) // target-type
+ return UINT32_MAX;
+ break;
+ default:
+ return UINT32_MAX;
+ }
+ }
+ return num_imports;
+}
+
+md_blob_parse_result_t md_parse_imports(mdhandle_t handle, uint8_t const* blob, size_t blob_len, md_imports_t* imports, size_t* buffer_len)
+{
+ mdcxt_t* cxt = extract_mdcxt(handle);
+ if (cxt == NULL || blob == NULL || buffer_len == NULL)
+ return mdbpr_InvalidArgument;
+
+ uint32_t num_imports = get_num_imports(blob, blob_len);
+ if (num_imports == UINT32_MAX)
+ return mdbpr_InvalidBlob;
+
+ size_t required_size = sizeof(md_imports_t) + num_imports * sizeof(imports->imports[0]);
+ if (imports == NULL || *buffer_len < required_size)
+ {
+ *buffer_len = required_size;
+ return mdbpr_InsufficientBuffer;
+ }
+
+ imports->count = num_imports;
+ for (uint32_t i = 0; i < num_imports; ++i)
+ {
+ uint8_t kind;
+ if (!read_u8(&blob, &blob_len, &kind))
+ return mdbpr_InvalidBlob;
+
+ // Zero out this import entry.
+ memset(&imports->imports[i], 0, sizeof(imports->imports[i]));
+
+ imports->imports[i].kind = kind;
+ uint32_t raw;
+ switch (kind)
+ {
+ case mdidk_ImportNamespace:
+ if (!decompress_u32(&blob, &blob_len, &raw))
+ return mdbpr_InvalidBlob;
+
+ if (!try_get_blob(cxt, raw, (uint8_t const**)&imports->imports[i].target_namespace, &imports->imports[i].target_namespace_len))
+ return mdbpr_InvalidBlob;
+ break;
+ case mdidk_ImportAssemblyNamespace:
+ if (!decompress_u32(&blob, &blob_len, &raw))
+ return mdbpr_InvalidBlob;
+
+ imports->imports[i].assembly = CreateTokenType(mdtid_AssemblyRef) | raw;
+
+ if (!decompress_u32(&blob, &blob_len, &raw))
+ return mdbpr_InvalidBlob;
+
+ if (!try_get_blob(cxt, raw, (uint8_t const**)&imports->imports[i].target_namespace, &imports->imports[i].target_namespace_len))
+ return mdbpr_InvalidBlob;
+ break;
+ case mdidk_ImportType:
+ {
+ mdtable_id_t table;
+ uint32_t row_id;
+ if (!decompress_u32(&blob, &blob_len, &raw))
+ return mdbpr_InvalidBlob;
+
+ if (!decompose_coded_index(raw, mdtc_idx_coded | InsertCodedIndex(mdci_TypeDefOrRef), &table, &row_id))
+ return mdbpr_InvalidBlob;
+
+ imports->imports[i].target_type = CreateTokenType(table) | row_id;
+ break;
+ }
+ case mdidk_AliasNamespace:
+ case mdidk_ImportXmlNamespace:
+ if (!decompress_u32(&blob, &blob_len, &raw))
+ return mdbpr_InvalidBlob;
+
+ if (!try_get_blob(cxt, raw, (uint8_t const**)&imports->imports[i].alias, &imports->imports[i].alias_len))
+ return mdbpr_InvalidBlob;
+
+ if (!decompress_u32(&blob, &blob_len, &raw))
+ return mdbpr_InvalidBlob;
+
+ if (!try_get_blob(cxt, raw, (uint8_t const**)&imports->imports[i].target_namespace, &imports->imports[i].target_namespace_len))
+ return mdbpr_InvalidBlob;
+ break;
+ case mdidk_ImportAssemblyReferenceAlias:
+ if (!decompress_u32(&blob, &blob_len, &raw))
+ return mdbpr_InvalidBlob;
+
+ if (!try_get_blob(cxt, raw, (uint8_t const**)&imports->imports[i].alias, &imports->imports[i].alias_len))
+ return mdbpr_InvalidBlob;
+ break;
+ case mdidk_AliasAssemblyReference:
+ if (!decompress_u32(&blob, &blob_len, &raw))
+ return mdbpr_InvalidBlob;
+
+ if (!try_get_blob(cxt, raw, (uint8_t const**)&imports->imports[i].alias, &imports->imports[i].alias_len))
+ return mdbpr_InvalidBlob;
+
+ if (!decompress_u32(&blob, &blob_len, &raw))
+ return mdbpr_InvalidBlob;
+
+ imports->imports[i].assembly = CreateTokenType(mdtid_AssemblyRef) | raw;
+ break;
+ case mdidk_AliasAssemblyNamespace:
+ if (!decompress_u32(&blob, &blob_len, &raw))
+ return mdbpr_InvalidBlob;
+
+ if (!try_get_blob(cxt, raw, (uint8_t const**)&imports->imports[i].alias, &imports->imports[i].alias_len))
+ return mdbpr_InvalidBlob;
+
+ if (!decompress_u32(&blob, &blob_len, &raw))
+ return mdbpr_InvalidBlob;
+
+ imports->imports[i].assembly = CreateTokenType(mdtid_AssemblyRef) | raw;
+
+ if (!decompress_u32(&blob, &blob_len, &raw))
+ return mdbpr_InvalidBlob;
+
+ if (!try_get_blob(cxt, raw, (uint8_t const**)&imports->imports[i].target_namespace, &imports->imports[i].target_namespace_len))
+ return mdbpr_InvalidBlob;
+ break;
+ case mdidk_AliasType:
+ {
+ if (!decompress_u32(&blob, &blob_len, &raw))
+ return mdbpr_InvalidBlob;
+
+ if (!try_get_blob(cxt, raw, (uint8_t const**)&imports->imports[i].alias, &imports->imports[i].alias_len))
+ return mdbpr_InvalidBlob;
+
+ mdtable_id_t table;
+ uint32_t row_id;
+ if (!decompress_u32(&blob, &blob_len, &raw))
+ return mdbpr_InvalidBlob;
+
+ if (!decompose_coded_index(raw, mdtc_idx_coded | InsertCodedIndex(mdci_TypeDefOrRef), &table, &row_id))
+ return mdbpr_InvalidBlob;
+
+ imports->imports[i].target_type = CreateTokenType(table) | row_id;
+ break;
+ }
+ default:
+ return mdbpr_InvalidBlob;
+ }
+ }
+ return mdbpr_Success;
+}
diff --git a/src/native/dnmd/src/dnmd/query.c b/src/native/dnmd/src/dnmd/query.c
new file mode 100644
index 0000000000000..20ea7be3bbf71
--- /dev/null
+++ b/src/native/dnmd/src/dnmd/query.c
@@ -0,0 +1,921 @@
+#include "internal.h"
+
+mdcursor_t create_cursor(mdtable_t* table, uint32_t row)
+{
+ assert(table != NULL && row <= (table->row_count + 1));
+ mdcursor_t c;
+ c._reserved1 = (intptr_t)table;
+ c._reserved2 = row;
+ return c;
+}
+
+static mdtable_t* type_to_table(mdcxt_t* cxt, mdtable_id_t table_id)
+{
+ assert(cxt != NULL);
+ assert(0 <= table_id && table_id < MDTABLE_MAX_COUNT);
+ return &cxt->tables[table_id];
+}
+
+bool md_create_cursor(mdhandle_t handle, mdtable_id_t table_id, mdcursor_t* cursor, uint32_t* count)
+{
+ if (count == NULL)
+ return false;
+
+ // Set the token to the first row.
+ // If the table is empty, the call will return false.
+ if (!md_token_to_cursor(handle, (CreateTokenType(table_id) | 1), cursor))
+ return false;
+
+ *count = CursorTable(cursor)->row_count;
+ return true;
+}
+
+static bool cursor_move_no_checks(mdcursor_t* c, int32_t delta)
+{
+ assert(c != NULL);
+
+ mdtable_t* table = CursorTable(c);
+ uint32_t row = CursorRow(c);
+ row += delta;
+ // Indices into tables begin at 1 - see II.22.
+ // They can also point to index n+1, which
+ // indicates the end.
+ if (row == 0 || row > (table->row_count + 1))
+ return false;
+
+ *c = create_cursor(table, row);
+ return true;
+}
+
+bool md_cursor_move(mdcursor_t* c, int32_t delta)
+{
+ if (c == NULL || CursorTable(c) == NULL)
+ return false;
+ return cursor_move_no_checks(c, delta);
+}
+
+bool md_cursor_next(mdcursor_t* c)
+{
+ return md_cursor_move(c, 1);
+}
+
+bool md_token_to_cursor(mdhandle_t handle, mdToken tk, mdcursor_t* c)
+{
+ assert(c != NULL);
+
+ mdcxt_t* cxt = extract_mdcxt(handle);
+ if (cxt == NULL)
+ return false;
+
+ mdtable_id_t table_id = ExtractTokenType(tk);
+ if (table_id >= MDTABLE_MAX_COUNT)
+ return false;
+
+ mdtable_t* table = type_to_table(cxt, table_id);
+
+ // Indices into tables begin at 1 - see II.22.
+ uint32_t row = RidFromToken(tk);
+ if (row == 0 || row > table->row_count)
+ return false;
+
+ *c = create_cursor(table, row);
+ return true;
+}
+
+bool md_cursor_to_token(mdcursor_t c, mdToken* tk)
+{
+ assert(tk != NULL);
+
+ mdtable_t* table = CursorTable(&c);
+ if (table == NULL)
+ {
+ *tk = mdTokenNil;
+ return true;
+ }
+
+ mdToken row = RidFromToken(CursorRow(&c));
+ *tk = CreateTokenType(table->table_id) | row;
+ // We'll allow getting a token for a cursor just passed the end of the table.
+ // These tokens are used in some scenarios to represent an empty list.
+ return (row <= table->row_count + 1);
+}
+
+mdhandle_t md_extract_handle_from_cursor(mdcursor_t c)
+{
+ mdtable_t* table = CursorTable(&c);
+ if (table == NULL)
+ return NULL;
+ return table->cxt;
+}
+
+bool md_walk_user_string_heap(mdhandle_t handle, mduserstringcursor_t* cursor, mduserstring_t* str, uint32_t* offset)
+{
+ mdcxt_t* cxt = extract_mdcxt(handle);
+ if (cxt == NULL)
+ return -1;
+
+ assert(cursor != NULL);
+ *offset = (uint32_t)*cursor;
+ size_t next_offset;
+ if (!try_get_user_string(cxt, *offset, str, &next_offset))
+ return false;
+
+ *cursor = (mduserstringcursor_t)next_offset;
+ return true;
+}
+
+static bool get_column_value_as_token_or_cursor(mdcursor_t* c, uint32_t col_idx, mdToken* tk, mdcursor_t* cursor)
+{
+ assert(c != NULL && (tk != NULL || cursor != NULL));
+
+ access_cxt_t acxt;
+ if (!create_access_context(c, col_idx, false, &acxt))
+ return false;
+
+ // If this isn't an index column, then fail.
+ if (!(acxt.col_details & (mdtc_idx_table | mdtc_idx_coded)))
+ return false;
+
+ uint32_t table_row;
+ mdtable_id_t table_id;
+
+ uint32_t raw;
+
+ if (!read_column_data(&acxt, &raw))
+ return false;
+
+ if (acxt.col_details & mdtc_idx_table)
+ {
+ // The raw value is the row index into the table that
+ // is embedded in the column details.
+ table_row = RidFromToken(raw);
+ table_id = ExtractTable(acxt.col_details);
+ }
+ else
+ {
+ assert(acxt.col_details & mdtc_idx_coded);
+ if (!decompose_coded_index(raw, acxt.col_details, &table_id, &table_row))
+ return false;
+ }
+
+ if (0 > table_id || table_id >= MDTABLE_MAX_COUNT)
+ return false;
+
+ mdtable_t* table;
+ if (tk != NULL)
+ {
+ *tk = CreateTokenType(table_id) | table_row;
+ }
+ else
+ {
+ // Returning a cursor means pointing directly into a table
+ // so we must validate the cursor is valid prior to creation.
+ table = type_to_table(acxt.table->cxt, table_id);
+
+ // Indices into tables begin at 1 - see II.22.
+ // However, tables can contain a row ID of 0 to
+ // indicate "none" or point 1 past the end.
+ if (table_row > table->row_count + 1)
+ return false;
+
+ // Sometimes we can get an index into a table of 0 or 1 past the end
+ // of a table that does not exist. In that case, our table object here
+ // will be completely uninitialized. Set the table id so we can do operations
+ // that need a table id, like creating the table or getting a token.
+ if (table->table_id == 0 && table_id != 0)
+ {
+ assert(table_row == 0 || table_row == 1);
+ table->table_id = table_id;
+ }
+
+ assert(cursor != NULL);
+ *cursor = create_cursor(table, table_row);
+ }
+
+ return true;
+}
+
+bool md_get_column_value_as_token(mdcursor_t c, col_index_t col_idx, mdToken* tk)
+{
+ assert(tk != NULL);
+ return get_column_value_as_token_or_cursor(&c, col_idx, tk, NULL);
+}
+
+bool md_get_column_value_as_cursor(mdcursor_t c, col_index_t col_idx, mdcursor_t* cursor)
+{
+ assert(cursor != NULL);
+ return get_column_value_as_token_or_cursor(&c, col_idx, NULL, cursor);
+}
+
+// Forward declaration
+//#define DNMD_DEBUG_FIND_TOKEN_OF_RANGE_ELEMENT
+#ifdef DNMD_DEBUG_FIND_TOKEN_OF_RANGE_ELEMENT
+static bool _validate_md_find_token_of_range_element(mdcursor_t expected, mdcursor_t begin, uint32_t count);
+#endif // DNMD_DEBUG_FIND_TOKEN_OF_RANGE_ELEMENT
+
+bool md_get_column_value_as_range(mdcursor_t c, col_index_t col_idx, mdcursor_t* cursor, uint32_t* count)
+{
+ assert(cursor != NULL);
+ if (!get_column_value_as_token_or_cursor(&c, col_idx, NULL, cursor))
+ return false;
+
+ // Check if the cursor is null or the end of the table
+ if (CursorNull(cursor) || CursorEnd(cursor))
+ {
+ *count = 0;
+ return true;
+ }
+
+ mdcursor_t nextMaybe = c;
+ // Loop here as we may have a bunch of null cursors in the column.
+ for (;;)
+ {
+ // The cursor into the current table remains valid,
+ // we can safely move it at least one beyond the last.
+ (void)cursor_move_no_checks(&nextMaybe, 1);
+
+ // Check if we are at the end of the current table. If so,
+ // ECMA states we use the remaining rows in the target table.
+ if (CursorEnd(&nextMaybe))
+ {
+ // Add +1 for inclusive count.
+ *count = CursorTable(cursor)->row_count - CursorRow(cursor) + 1;
+ }
+ else
+ {
+ // Examine the current table's next row value to find the
+ // extrema of the target table range.
+ mdcursor_t end;
+ if (!md_get_column_value_as_cursor(nextMaybe, col_idx, &end))
+ return false;
+
+ // The next row is a null cursor, which means we need to
+ // check the next row in the current table.
+ if (CursorNull(&end))
+ continue;
+ *count = CursorRow(&end) - CursorRow(cursor);
+ }
+ break;
+ }
+
+ // Use the results of this function to validate md_find_token_of_range_element()
+#ifdef DNMD_DEBUG_FIND_TOKEN_OF_RANGE_ELEMENT
+ (void)_validate_md_find_token_of_range_element(c, *cursor, *count);
+#endif // DNMD_DEBUG_FIND_TOKEN_OF_RANGE_ELEMENT
+
+ return true;
+}
+
+bool md_get_column_value_as_constant(mdcursor_t c, col_index_t col_idx, uint32_t* constant)
+{
+ assert(constant != NULL);
+
+ access_cxt_t acxt;
+ if (!create_access_context(&c, col_idx, false, &acxt))
+ return false;
+
+ // If this isn't an constant column, then fail.
+ if (!(acxt.col_details & mdtc_constant))
+ return false;
+
+ if (!read_column_data(&acxt, constant))
+ return false;
+
+ return true;
+}
+
+// Set a column value as an existing offset into a heap.
+bool get_column_value_as_heap_offset(mdcursor_t c, col_index_t col_idx, uint32_t* offset)
+{
+ assert(offset != NULL);
+
+ access_cxt_t acxt;
+ if (!create_access_context(&c, col_idx, false, &acxt))
+ return false;
+
+ // If this isn't a heap index column, then fail.
+ if (!(acxt.col_details & mdtc_idx_heap))
+ return false;
+
+ mdstream_t const* heap = get_heap_by_id(acxt.table->cxt, ExtractHeapType(acxt.col_details));
+ if (heap == NULL)
+ return false;
+
+ if (!read_column_data(&acxt, offset))
+ return false;
+
+ return true;
+}
+
+bool md_get_column_value_as_utf8(mdcursor_t c, col_index_t col_idx, char const** str)
+{
+ access_cxt_t acxt;
+ if (!create_access_context(&c, col_idx, false, &acxt))
+ return false;
+
+ // If this isn't a heap index column, then fail.
+ if (!(acxt.col_details & mdtc_hstring))
+ return false;
+
+ uint32_t offset;
+ if (!read_column_data(&acxt, &offset))
+ return false;
+
+ if (!try_get_string(CursorTable(&c)->cxt, offset, str))
+ return false;
+
+ return true;
+}
+
+bool md_get_column_value_as_userstring(mdcursor_t c, col_index_t col_idx, mduserstring_t* string)
+{
+ assert(string != NULL);
+
+ access_cxt_t acxt;
+ if (!create_access_context(&c, col_idx, false, &acxt))
+ return false;
+
+ // If this isn't a heap index column, then fail.
+ if (!(acxt.col_details & mdtc_hus))
+ return false;
+
+ uint32_t offset;
+ if (!read_column_data(&acxt, &offset))
+ return false;
+
+ size_t next_offset;
+ if (!try_get_user_string(CursorTable(&c)->cxt, offset, string, &next_offset))
+ return false;
+
+ return true;
+}
+
+bool md_get_column_value_as_blob(mdcursor_t c, col_index_t col_idx, uint8_t const** blob, uint32_t* blob_len)
+{
+ assert(blob != NULL && blob_len != NULL);
+
+ access_cxt_t acxt;
+ if (!create_access_context(&c, col_idx, false, &acxt))
+ return false;
+
+ // If this isn't a heap index column, then fail.
+ if (!(acxt.col_details & mdtc_hblob))
+ return false;
+
+ uint32_t offset;
+ if (!read_column_data(&acxt, &offset))
+ return false;
+
+ if (!try_get_blob(CursorTable(&c)->cxt, offset, blob, blob_len))
+ return false;
+
+ return true;
+}
+
+bool md_get_column_value_as_guid(mdcursor_t c, col_index_t col_idx, mdguid_t* guid)
+{
+ assert(guid != NULL);
+
+ access_cxt_t acxt;
+ if (!create_access_context(&c, col_idx, false, &acxt))
+ return false;
+
+ // If this isn't a heap index column, then fail.
+ if (!(acxt.col_details & mdtc_hguid))
+ return false;
+
+ uint32_t offset;
+ if (!read_column_data(&acxt, &offset))
+ return false;
+
+ if (!try_get_guid(CursorTable(&c)->cxt, offset, guid))
+ return false;
+
+ return true;
+}
+
+int32_t md_get_many_rows_column_value_as_token(mdcursor_t c, col_index_t col_idx, uint32_t out_length, mdToken* tk)
+{
+ assert(out_length != 0 && tk != NULL);
+
+ bulk_access_cxt_t acxt;
+ if (!create_bulk_access_context(&c, col_idx, out_length, &acxt))
+ return -1;
+
+ // If this isn't an index column, then fail.
+ if (!(acxt.col_details & (mdtc_idx_table | mdtc_idx_coded)))
+ return -1;
+
+ uint32_t table_row;
+ mdtable_id_t table_id;
+
+ uint32_t raw;
+ int32_t read_in = 0;
+ do
+ {
+ if (!read_column_data_and_advance(&acxt, &raw))
+ return -1;
+
+ if (acxt.col_details & mdtc_idx_table)
+ {
+ // The raw value is the row index into the table that
+ // is embedded in the column details.
+ table_row = RidFromToken(raw);
+ table_id = ExtractTable(acxt.col_details);
+ }
+ else
+ {
+ assert(acxt.col_details & mdtc_idx_coded);
+ if (!decompose_coded_index(raw, acxt.col_details, &table_id, &table_row))
+ return -1;
+ }
+
+ if (0 > table_id || table_id >= MDTABLE_MAX_COUNT)
+ return -1;
+
+ tk[read_in] = CreateTokenType(table_id) | table_row;
+ read_in++;
+ } while (out_length > 1 && next_row(&acxt));
+
+ return read_in;
+}
+
+bool md_get_column_values_raw(mdcursor_t c, uint32_t values_length, bool* values_to_get, uint32_t* values_raw)
+{
+ if (values_length > 0
+ && (values_to_get == NULL
+ || values_raw == NULL))
+ {
+ return false;
+ }
+
+ access_cxt_t acxt;
+ for (uint32_t i = 0; i < values_length; ++i)
+ {
+ if (!values_to_get[i])
+ continue;
+
+ // Create access context for the next column value
+ if (!create_access_context(&c, i, false, &acxt))
+ return false;
+
+ if (!read_column_data(&acxt, &values_raw[i]))
+ return false;
+ }
+
+ return true;
+}
+
+typedef struct find_cxt__
+{
+ uint32_t col_offset;
+ uint32_t data_len;
+ mdtcol_t col_details;
+} find_cxt_t;
+
+static bool create_find_context(mdtable_t* table, col_index_t col_idx, find_cxt_t* fcxt)
+{
+ assert(table != NULL && fcxt != NULL);
+
+ uint8_t idx = col_to_index(col_idx, table);
+ assert(idx < MDTABLE_MAX_COLUMN_COUNT);
+ if (idx >= table->column_count)
+ return false;
+
+ mdtcol_t cd = table->column_details[idx];
+ fcxt->col_offset = ExtractOffset(cd);
+ fcxt->data_len = (cd & mdtc_b2) ? 2 : 4;
+ fcxt->col_details = cd;
+ return true;
+}
+
+static int32_t col_compare_2bytes(void const* key, void const* row, void* cxt)
+{
+ assert(key != NULL && row != NULL && cxt != NULL);
+
+ find_cxt_t* fcxt = (find_cxt_t*)cxt;
+ uint8_t const* col_data = (uint8_t const*)row + fcxt->col_offset;
+
+ uint16_t const lhs = *(uint16_t*)key;
+ uint16_t rhs = 0;
+ size_t col_len = fcxt->data_len;
+ ASSERT_ASSUME(col_len == 2);
+ bool success = read_u16(&col_data, &col_len, &rhs);
+ assert(success && col_len == 0);
+ (void)success;
+
+ // 0: equal, < 0: (lhs < rhs), > 0: (lhr > rhs)
+ return lhs - rhs;
+}
+
+static int32_t col_compare_4bytes(void const* key, void const* row, void* cxt)
+{
+ assert(key != NULL && row != NULL && cxt != NULL);
+
+ find_cxt_t* fcxt = (find_cxt_t*)cxt;
+ uint8_t const* col_data = (uint8_t const*)row + fcxt->col_offset;
+
+ uint32_t const lhs = *(uint32_t*)key;
+ uint32_t rhs = 0;
+ size_t col_len = fcxt->data_len;
+ ASSERT_ASSUME(col_len == 4);
+ bool success = read_u32(&col_data, &col_len, &rhs);
+ assert(success && col_len == 0);
+ (void)success;
+
+ // 0: equal, < 0: (lhs < rhs), > 0: (lhr > rhs)
+ return lhs - rhs;
+}
+
+typedef int32_t(*md_bcompare_t)(void const* key, void const* row, void*);
+
+// Define all 2 and 4 byte search functions
+#define SEARCH_COMPARE(...) col_compare_2bytes(__VA_ARGS__)
+#define SEARCH_FUNC_NAME(n) n ## _2bytes
+#include "search.template.h"
+
+#define SEARCH_COMPARE(...) col_compare_4bytes(__VA_ARGS__)
+#define SEARCH_FUNC_NAME(n) n ## _4bytes
+#include "search.template.h"
+
+static void const* cursor_to_row_bytes(mdcursor_t* c)
+{
+ assert(c != NULL && (CursorRow(c) > 0));
+ // Indices into tables begin at 1 - see II.22.
+ return &CursorTable(c)->data.ptr[(CursorRow(c) - 1) * CursorTable(c)->row_size_bytes];
+}
+
+static bool find_row_from_cursor(mdcursor_t begin, col_index_t idx, uint32_t* value, mdcursor_t* cursor)
+{
+ mdtable_t* table = CursorTable(&begin);
+ if (table == NULL || cursor == NULL)
+ return false;
+
+ uint32_t first_row = CursorRow(&begin);
+ // Indices into tables begin at 1 - see II.22.
+ if (first_row == 0 || first_row > table->row_count)
+ return false;
+
+ find_cxt_t fcxt;
+ if (!create_find_context(table, idx, &fcxt))
+ return false;
+
+ // If the value is for a coded index, update the value.
+ if (fcxt.col_details & mdtc_idx_coded)
+ {
+ if (!compose_coded_index(*value, fcxt.col_details, value))
+ return false;
+ }
+
+ // Compute the starting row.
+ void const* starting_row = cursor_to_row_bytes(&begin);
+ // Add +1 for inclusive count - use binary search if sorted, otherwise linear.
+ void const* row_maybe = (table->is_sorted && !table->is_adding_new_row)
+ ? ((fcxt.data_len == 2)
+ ? md_bsearch_2bytes(value, starting_row, (table->row_count - first_row) + 1, table->row_size_bytes, &fcxt)
+ : md_bsearch_4bytes(value, starting_row, (table->row_count - first_row) + 1, table->row_size_bytes, &fcxt))
+ : ((fcxt.data_len == 2)
+ ? md_lsearch_2bytes(value, starting_row, (table->row_count - first_row) + 1, table->row_size_bytes, &fcxt)
+ : md_lsearch_4bytes(value, starting_row, (table->row_count - first_row) + 1, table->row_size_bytes, &fcxt));
+ if (row_maybe == NULL)
+ return false;
+
+ // Compute the found row.
+ // Indices into tables begin at 1 - see II.22.
+ assert(starting_row <= row_maybe);
+ uint32_t row = (uint32_t)(((intptr_t)row_maybe - (intptr_t)starting_row) / table->row_size_bytes) + 1;
+ if (row > table->row_count)
+ return false;
+
+ *cursor = create_cursor(table, row);
+ return true;
+}
+
+bool md_find_row_from_cursor(mdcursor_t begin, col_index_t idx, uint32_t value, mdcursor_t* cursor)
+{
+ return find_row_from_cursor(begin, idx, &value, cursor);
+}
+
+md_range_result_t md_find_range_from_cursor(mdcursor_t begin, col_index_t idx, uint32_t value, mdcursor_t* start, uint32_t* count)
+{
+ // If the table isn't sorted, then a range isn't possible.
+ mdtable_t* table = CursorTable(&begin);
+
+ if (!table->is_sorted || table->is_adding_new_row)
+ return MD_RANGE_NOT_SUPPORTED;
+
+ md_key_info_t const* keys;
+ uint8_t keys_count = get_table_keys(table->table_id, &keys);
+ if (keys_count == 0)
+ return MD_RANGE_NOT_SUPPORTED;
+
+ if (keys[0].index != col_to_index(idx, table))
+ return MD_RANGE_NOT_SUPPORTED;
+
+ // Currently all tables have ascending primary keys.
+ // The algorithm below only works with ascending keys.
+ assert(!keys[0].descending);
+
+ // Look for any instance of the value.
+ mdcursor_t found;
+ if (!find_row_from_cursor(begin, idx, &value, &found))
+ return MD_RANGE_NOT_FOUND;
+
+ int32_t res;
+ find_cxt_t fcxt;
+ // This was already created and validated when the row was found.
+ // We assume the data is still valid.
+ bool success = create_find_context(table, idx, &fcxt);
+ ASSERT_ASSUME(success);
+
+ // A valid value was found, so we are at least within the range.
+ // Now find the extrema.
+ *start = found;
+ mdcursor_t end = found;
+
+ // PERF: Split this into an if/else instead of using a function pointer
+ // to avoid CFG overhead in MSVC.
+ if (fcxt.data_len == 2)
+ {
+ while (cursor_move_no_checks(start, -1))
+ {
+ // Since we are moving backwards in a sorted column,
+ // the value should match or be greater.
+ res = col_compare_2bytes(&value, cursor_to_row_bytes(start), &fcxt);
+ assert(res >= 0);
+ if (res > 0)
+ {
+ // Move forward to the start.
+ (void)cursor_move_no_checks(start, 1);
+ break;
+ }
+ }
+ while (cursor_move_no_checks(&end, 1) && !CursorEnd(&end))
+ {
+ // Since we are moving forwards in a sorted column,
+ // the value should match or be less.
+ res = col_compare_2bytes(&value, cursor_to_row_bytes(&end), &fcxt);
+ assert(res <= 0);
+ if (res < 0)
+ break;
+ }
+ }
+ else
+ {
+ while (cursor_move_no_checks(start, -1))
+ {
+ // Since we are moving backwards in a sorted column,
+ // the value should match or be greater.
+ res = col_compare_4bytes(&value, cursor_to_row_bytes(start), &fcxt);
+ assert(res >= 0);
+ if (res > 0)
+ {
+ // Move forward to the start.
+ (void)cursor_move_no_checks(start, 1);
+ break;
+ }
+ }
+ while (cursor_move_no_checks(&end, 1) && !CursorEnd(&end))
+ {
+ // Since we are moving forwards in a sorted column,
+ // the value should match or be less.
+ res = col_compare_4bytes(&value, cursor_to_row_bytes(&end), &fcxt);
+ assert(res <= 0);
+ if (res < 0)
+ break;
+ }
+ }
+
+ // Compute the row delta
+ *count = CursorRow(&end) - CursorRow(start);
+ return MD_RANGE_FOUND;
+}
+
+#ifdef DNMD_DEBUG_FIND_TOKEN_OF_RANGE_ELEMENT
+// This function is used to validate the mapping logic between
+// md_find_token_of_range_element() and md_get_column_value_as_range().
+static bool _validate_md_find_token_of_range_element(mdcursor_t expected, mdcursor_t begin, uint32_t count)
+{
+#define IF_FALSE_RETURN(exp) { if (!(exp)) assert(false && #exp); return false; }
+ mdToken expected_tk = 0;
+
+ // The expected token is often just where the cursor presently points.
+ // The Event and Property tables need to be queryed for the expected value.
+ switch (CursorTable(&begin)->table_id)
+ {
+ case mdtid_Field:
+ case mdtid_MethodDef:
+ case mdtid_Param:
+#ifdef DNMD_PORTABLE_PDB
+ case mdtid_LocalVariable:
+ case mdtid_LocalConstant:
+#endif // DNMD_PORTABLE_PDB
+ IF_FALSE_RETURN(md_cursor_to_token(expected, &expected_tk));
+ break;
+ case mdtid_Event:
+ IF_FALSE_RETURN(md_get_column_value_as_token(expected, mdtEventMap_Parent, 1, &expected_tk));
+ break;
+ case mdtid_Property:
+ IF_FALSE_RETURN(md_get_column_value_as_token(expected, mdtPropertyMap_Parent, 1, &expected_tk));
+ break;
+ default:
+ IF_FALSE_RETURN(!"Invalid table ID");
+ break;
+ }
+
+ mdToken actual;
+ mdcursor_t curr = begin;
+ for (uint32_t i = 0; i < count; ++i)
+ {
+ IF_FALSE_RETURN(md_find_token_of_range_element(curr, &actual));
+ IF_FALSE_RETURN(expected_tk == actual);
+ IF_FALSE_RETURN(md_cursor_next(&curr));
+ }
+#undef IF_FALSE_RETURN
+
+ return true;
+}
+#endif // DNMD_DEBUG_FIND_TOKEN_OF_RANGE_ELEMENT
+
+static bool find_range_element(mdcursor_t element, mdcursor_t* tgt_cursor)
+{
+ assert(tgt_cursor != NULL);
+ mdtable_t* table = CursorTable(&element);
+ if (table == NULL)
+ return false;
+
+ uint32_t row = CursorRow(&element);
+ mdtable_id_t tgt_table_id;
+ col_index_t tgt_col;
+ switch (table->table_id)
+ {
+ case mdtid_Field:
+ tgt_table_id = mdtid_TypeDef;
+ tgt_col = mdtTypeDef_FieldList;
+ break;
+ case mdtid_MethodDef:
+ tgt_table_id = mdtid_TypeDef;
+ tgt_col = mdtTypeDef_MethodList;
+ break;
+ case mdtid_Param:
+ tgt_table_id = mdtid_MethodDef;
+ tgt_col = mdtMethodDef_ParamList;
+ break;
+ case mdtid_Event:
+ tgt_table_id = mdtid_EventMap;
+ tgt_col = mdtEventMap_EventList;
+ break;
+ case mdtid_Property:
+ tgt_table_id = mdtid_PropertyMap;
+ tgt_col = mdtPropertyMap_PropertyList;
+ break;
+#ifdef DNMD_PORTABLE_PDB
+ case mdtid_LocalVariable:
+ tgt_table_id = mdtid_LocalScope;
+ tgt_col = mdtLocalScope_VariableList;
+ break;
+ case mdtid_LocalConstant:
+ tgt_table_id = mdtid_LocalScope;
+ tgt_col = mdtLocalScope_ConstantList;
+ break;
+#endif // DNMD_PORTABLE_PDB
+ default:
+ return false;
+ }
+
+ mdtable_t* tgt_table = type_to_table(table->cxt, tgt_table_id);
+
+ uint8_t col_index = col_to_index(tgt_col, tgt_table);
+
+ assert((tgt_table->column_details[col_index] & mdtc_idx_table) == mdtc_idx_table);
+
+ // If the column in the target table is not pointing to the starting table,
+ // then it is pointing to the corresponding indirection table.
+ // We need to find the element in the indirection table that points to the cursor
+ // and then find the element in the target table that points to the indirection table.
+ if (table_is_indirect_table(ExtractTable(tgt_table->column_details[col_index])))
+ {
+ mdtable_id_t indir_table_id = ExtractTable(tgt_table->column_details[col_index]);
+ col_index_t indir_col = index_to_col(0, indir_table_id);
+ mdcursor_t indir_table_cursor;
+ uint32_t indir_table_row_count;
+ if (!md_create_cursor(table->cxt, indir_table_id, &indir_table_cursor, &indir_table_row_count))
+ return false;
+
+ mdcursor_t indir_row;
+ if (!find_row_from_cursor(indir_table_cursor, indir_col, &row, &indir_row))
+ return false;
+
+ // Now that we've found the indirection cell, we can look in the target table for the
+ // element that contains the indirection cell in its range.
+ row = CursorRow(&indir_row);
+ }
+
+ find_cxt_t fcxt;
+ if (!create_find_context(tgt_table, tgt_col, &fcxt))
+ return false;
+
+ uint32_t found_row;
+ int32_t last_cmp = (fcxt.data_len == 2)
+ ? mdtable_bsearch_closest_2bytes(&row, tgt_table, &fcxt, &found_row)
+ : mdtable_bsearch_closest_4bytes(&row, tgt_table, &fcxt, &found_row);
+
+ // The three result cases are handled as follows.
+ // If last < 0, then the cursor is greater than the value so we must move back one.
+ // If last == 0, then the cursor matches the value. This could be the first
+ // instance of the value in a run of rows. We are only interested in the
+ // last row with this value.
+ // If last > 0, then the cursor is less than the value and begins the list, use it.
+ mdcursor_t pos;
+ mdcursor_t tmp;
+ mdToken tmp_tk;
+ if (last_cmp < 0)
+ {
+ pos = create_cursor(tgt_table, found_row - 1);
+ }
+ else if (last_cmp == 0)
+ {
+ tmp = create_cursor(tgt_table, found_row);
+ tmp_tk = row;
+ do
+ {
+ pos = tmp;
+ if (!cursor_move_no_checks(&tmp, 1)
+ || !md_get_column_value_as_token(tmp, tgt_col, &tmp_tk))
+ {
+ break;
+ }
+ }
+ while (RidFromToken(tmp_tk) == row);
+ }
+ else
+ {
+ pos = create_cursor(tgt_table, found_row);
+ }
+
+ switch (table->table_id)
+ {
+ case mdtid_Field:
+ case mdtid_MethodDef:
+ case mdtid_Param:
+#ifdef DNMD_PORTABLE_PDB
+ case mdtid_LocalVariable:
+ case mdtid_LocalConstant:
+#endif // DNMD_PORTABLE_PDB
+ *tgt_cursor = pos;
+ return true;
+ case mdtid_Event:
+ return md_get_column_value_as_cursor(pos, mdtEventMap_Parent, tgt_cursor);
+ case mdtid_Property:
+ return md_get_column_value_as_cursor(pos, mdtPropertyMap_Parent, tgt_cursor);
+ default:
+ assert(!"Invalid table ID");
+ return false;
+ }
+}
+
+bool md_find_token_of_range_element(mdcursor_t element, mdToken* tk)
+{
+ if (tk == NULL)
+ return false;
+ mdcursor_t cursor;
+ if (!find_range_element(element, &cursor))
+ return false;
+ return md_cursor_to_token(cursor, tk);
+}
+
+bool md_find_cursor_of_range_element(mdcursor_t element, mdcursor_t* cursor)
+{
+ if (cursor == NULL)
+ return false;
+ return find_range_element(element, cursor);
+}
+
+bool md_resolve_indirect_cursor(mdcursor_t c, mdcursor_t* target)
+{
+ mdtable_t* table = CursorTable(&c);
+ if (table == NULL)
+ return false;
+
+ if (!table_is_indirect_table(table->table_id))
+ {
+ // If the table isn't an indirect table,
+ // we don't need to resolve an indirection from the cursor.
+ // In this case, the original cursor is the target cursor.
+ *target = c;
+ return true;
+ }
+ col_index_t col_idx = index_to_col(0, table->table_id);
+
+ // If the cursor points to the end of the indirection table (just after the last row),
+ // then we'll manually resolve it to the end of the target table.
+ if (CursorEnd(&c))
+ {
+ mdtable_id_t tgt_table_id = ExtractTable(table->column_details[col_idx]);
+ mdtable_t* tgt_table = type_to_table(table->cxt, tgt_table_id);
+ *target = create_cursor(tgt_table, tgt_table->row_count + 1);
+ return true;
+ }
+
+ return md_get_column_value_as_cursor(c, col_idx, target);
+}
diff --git a/src/native/dnmd/src/dnmd/search.template.h b/src/native/dnmd/src/dnmd/search.template.h
new file mode 100644
index 0000000000000..d4b437cdb38ea
--- /dev/null
+++ b/src/native/dnmd/src/dnmd/search.template.h
@@ -0,0 +1,110 @@
+//
+// File containing functions that we templatize using macros
+//
+
+#ifndef SEARCH_COMPARE
+#error Must define SEARCH_COMPARE() macro
+#endif // SEARCH_COMPARE
+
+#ifndef SEARCH_FUNC_NAME
+#error Must define SEARCH_FUNC_NAME(name) macro
+#endif // SEARCH_FUNC_NAME
+
+// Since MSVC doesn't have a C11 compatible bsearch_s, defining one below.
+// Ideally we would use the one in the standard so the signature is designed
+// to match what should eventually exist.
+static void const* SEARCH_FUNC_NAME(md_bsearch)(
+ void const* key,
+ void const* base,
+ rsize_t count,
+ rsize_t element_size,
+ void* cxt)
+{
+ assert(key != NULL && base != NULL);
+ while (count > 0)
+ {
+ void const* row = (uint8_t const*)base + (element_size * (count / 2));
+ int32_t res = SEARCH_COMPARE(key, row, cxt);
+ if (res == 0)
+ return row;
+
+ if (count == 1)
+ {
+ break;
+ }
+ else if (res < 0)
+ {
+ count /= 2;
+ }
+ else
+ {
+ base = row;
+ count -= count / 2;
+ }
+ }
+ return NULL;
+}
+
+static void const* SEARCH_FUNC_NAME(md_lsearch)(
+ void const* key,
+ void const* base,
+ rsize_t count,
+ rsize_t element_size,
+ void* cxt)
+{
+ assert(key != NULL && base != NULL);
+ void const* row = base;
+ for (rsize_t i = 0; i < count; ++i)
+ {
+ int32_t res = SEARCH_COMPARE(key, row, cxt);
+ if (res == 0)
+ return row;
+
+ // Onto the next row.
+ row = (uint8_t const*)row + element_size;
+ }
+ return NULL;
+}
+
+// Modeled after C11's bsearch_s. This API performs a binary search
+// and instead of returning NULL if the value isn't found, the last
+// compare result and row is returned.
+static int32_t SEARCH_FUNC_NAME(mdtable_bsearch_closest)(
+ void const* key,
+ mdtable_t* table,
+ find_cxt_t* fcxt,
+ uint32_t* found_row)
+{
+ assert(table != NULL && found_row != NULL);
+ void const* base = table->data.ptr;
+ rsize_t count = table->row_count;
+ rsize_t element_size = table->row_size_bytes;
+
+ int32_t res = 0;
+ void const* row = base;
+ while (count > 0)
+ {
+ row = (uint8_t const*)base + (element_size * (count / 2));
+ res = SEARCH_COMPARE(key, row, fcxt);
+ if (res == 0 || count == 1)
+ break;
+
+ if (res < 0)
+ {
+ count /= 2;
+ }
+ else
+ {
+ base = row;
+ count -= count / 2;
+ }
+ }
+
+ // Compute the found row.
+ // Indices into tables begin at 1 - see II.22.
+ *found_row = (uint32_t)(((intptr_t)row - (intptr_t)table->data.ptr) / element_size) + 1;
+ return res;
+}
+
+#undef SEARCH_COMPARE
+#undef SEARCH_FUNC_NAME
diff --git a/src/native/dnmd/src/dnmd/streams.c b/src/native/dnmd/src/dnmd/streams.c
new file mode 100644
index 0000000000000..16f303ca0a009
--- /dev/null
+++ b/src/native/dnmd/src/dnmd/streams.c
@@ -0,0 +1,383 @@
+#include "internal.h"
+
+bool try_get_string(mdcxt_t* cxt, size_t offset, char const** str)
+{
+ assert(cxt != NULL && str != NULL);
+
+ mdstream_t* h = &cxt->strings_heap;
+
+ // II.24.2.3 - When the #String heap is present, the first entry is always the empty string (i.e., \0).
+ // II.24.2.2 - Streams need not be there if they are empty.
+ // If the offset into the heap is 0, we can treat that as a "null" index into the heap and return
+ // the empty string.
+ if (h->size == 0 && offset == 0)
+ {
+ *str = "\0"; // II.24.2.3 'The first character must be the '\0' character.
+ return true;
+ }
+
+ if (h->size <= offset)
+ return false;
+
+ *str = (char const*)(h->ptr + offset);
+ return true;
+}
+
+bool validate_strings_heap(mdcxt_t* cxt)
+{
+ assert(cxt != NULL);
+
+ mdstream_t* h = &cxt->strings_heap;
+ if (h->size == 0)
+ return true;
+
+ // The first character must be the '\0' - II.24.2.3
+ if (*(char const*)h->ptr != '\0')
+ return false;
+
+ return true;
+}
+
+bool try_get_user_string(mdcxt_t* cxt, size_t offset, mduserstring_t* str, size_t* next_offset)
+{
+ assert(cxt != NULL && str != NULL && next_offset != NULL);
+ mdstream_t* h = &cxt->user_string_heap;
+ if (h->size <= offset)
+ return false;
+
+ uint8_t const* begin = (uint8_t const*)(h->ptr + offset);
+
+ size_t data_len = h->size - offset;
+ uint32_t byte_count;
+ if (!decompress_u32(&begin, &data_len, &byte_count))
+ return false;
+
+ if (byte_count == 0)
+ {
+ memset(str, 0, sizeof(*str));
+ }
+ else
+ {
+ // II.24.2.4
+ // The count on each string is the number of bytes in the string.
+ // There is an additional terminal byte which holds a 1 or 0.
+ // The 1 signifies Unicode characters that require handling beyond
+ // that normally provided for 8-bit encoding sets.
+ str->str = (char16_t const*)begin;
+ str->str_bytes = byte_count;
+ str->final_byte = begin[byte_count - 1];
+ }
+
+ *next_offset = &begin[byte_count] - h->ptr;
+ return true;
+}
+
+bool validate_user_string_heap(mdcxt_t* cxt)
+{
+ assert(cxt != NULL);
+
+ mdstream_t* h = &cxt->user_string_heap;
+ if (h->size == 0)
+ return true;
+
+ // The first element must be the 0 - II.24.2.4
+ if (*h->ptr != 0)
+ return false;
+
+ return true;
+}
+
+bool try_get_blob(mdcxt_t* cxt, size_t offset, uint8_t const** blob, uint32_t* blob_len)
+{
+ assert(cxt != NULL && blob != NULL && blob_len != NULL);
+
+ mdstream_t* h = &cxt->blob_heap;
+
+ if (h->size == 0 && offset == 0)
+ {
+ // The first element must be the 0 - II.24.2.4
+ *blob = h->ptr;
+ *blob_len = 0;
+ return true;
+ }
+
+ if (h->size <= offset)
+ return false;
+
+ uint8_t const* ptr = (uint8_t const*)(h->ptr + offset);
+
+ size_t data_len = h->size - offset;
+ uint32_t byte_count;
+ if (!decompress_u32(&ptr, &data_len, &byte_count))
+ return false;
+
+ *blob = ptr;
+ *blob_len = byte_count;
+ return true;
+}
+
+bool validate_blob_heap(mdcxt_t* cxt)
+{
+ assert(cxt != NULL);
+
+ mdstream_t* h = &cxt->blob_heap;
+ if (h->size == 0)
+ return true;
+
+ // The first element must be the 0 - II.24.2.4
+ if (*h->ptr != 0)
+ return false;
+
+ return true;
+}
+
+bool try_get_guid(mdcxt_t* cxt, size_t idx, mdguid_t* guid)
+{
+ assert(cxt != NULL && guid != NULL);
+
+ mdstream_t* h = &cxt->guid_heap;
+ size_t count = h->size / sizeof(mdguid_t);
+
+ if (count < idx)
+ return false;
+
+ // The guid heap starts from an index of 1 - see II.22.
+ // However, since there are many zero indices, we permit
+ // the value and return the "null" guid.
+ if (idx == 0)
+ {
+ memset(guid, 0, sizeof(*guid));
+ return true;
+ }
+
+ mdguid_t* guids = (mdguid_t*)h->ptr;
+ *guid = guids[idx - 1];
+ return true;
+}
+
+bool validate_guid_heap(mdcxt_t* cxt)
+{
+ assert(cxt != NULL);
+
+ mdstream_t* h = &cxt->guid_heap;
+ if (h->size % sizeof(mdguid_t) != 0)
+ return false;
+
+ return true;
+}
+
+bool initialize_tables(mdcxt_t* cxt)
+{
+ assert(cxt != NULL);
+
+ mdstream_t* h = &cxt->tables_heap;
+ if (h->size == 0)
+ return false;
+
+ uint8_t const* curr = h->ptr;
+ size_t curr_len = h->size;
+
+ uint8_t maj;
+ uint8_t min;
+ uint8_t heap_sizes;
+ uint8_t reserved;
+ if (!advance_stream(&curr, &curr_len, 4)
+ || !read_u8(&curr, &curr_len, &maj)
+ || !read_u8(&curr, &curr_len, &min)
+ || !read_u8(&curr, &curr_len, &heap_sizes)
+ || !read_u8(&curr, &curr_len, &reserved))
+ {
+ return false;
+ }
+
+ // The bottom byte of the context flags is the heap sizes
+ // flags from the image.
+ // We shouldn't have any of these bits set from our other
+ // initialization logic.
+ assert((cxt->context_flags & mdc_image_flags) == 0);
+ cxt->context_flags |= heap_sizes;
+
+ uint64_t valid_tables;
+ uint64_t sorted_tables;
+ if (!read_u64(&curr, &curr_len, &valid_tables)
+ || !read_u64(&curr, &curr_len, &sorted_tables))
+ {
+ return false;
+ }
+
+ size_t n = count_set_bits(valid_tables);
+ uint8_t const* table_begin = curr + (n * sizeof(uint32_t));
+
+ // We need to collect table row counts first.
+ // This is required because we need row counts to compute
+ // row size when a "coded index" is used - see II.24.2.6.
+ uint64_t valid = valid_tables;
+ uint32_t row_counts[MDTABLE_MAX_COUNT];
+ for (size_t i = 0; i < ARRAY_SIZE(row_counts); ++i)
+ {
+ if (valid & 1)
+ {
+ // Read in the row count for the table
+ if (!read_u32(&curr, &curr_len, &row_counts[i]))
+ return false;
+ }
+ else
+ {
+ row_counts[i] = 0;
+ }
+ valid = valid >> 1;
+ }
+
+ // There is an extra flag that is respected by metadata readers but is not defined in the ECMA spec.
+ // If the 0x40 bit is set, then there is an extra 4-byte integer after the row counts before the table data.
+ // This is refered to as the ExtraData heap size flag in the System.Reflection.Metadata reader.
+ if (cxt->context_flags & mdc_extra_data)
+ {
+ if (!advance_stream(&curr, &curr_len, sizeof(uint32_t)))
+ return false;
+ }
+
+ // Validate we processed the row counts properly
+ if (curr != table_begin)
+ return false;
+
+#ifdef DNMD_PORTABLE_PDB
+ md_pdb_t pdb;
+ if (try_get_pdb(cxt, &pdb))
+ {
+ // Merge in the PDB reference row counts
+ for (size_t i = 0; i < MDTABLE_MAX_COUNT; ++i)
+ row_counts[i] += pdb.type_system_table_rows[i];
+ }
+ else if (cxt->pdb.size != 0)
+ {
+ return false;
+ }
+#endif // DNMD_PORTABLE_PDB
+
+ mdtable_t* table;
+ valid = valid_tables;
+ for (size_t i = 0; valid; ++i)
+ {
+ // If the table is valid, initialize the table
+ if (valid & 1)
+ {
+ table = &cxt->tables[i];
+
+ // Initialize the table
+ if (!initialize_table_details(row_counts, cxt->context_flags, (mdtable_id_t)i, (bool)(sorted_tables & 1), table))
+ return false;
+
+ // Consume the data based on the table details
+ if (!consume_table_rows(table, &curr, &curr_len))
+ return false;
+
+ // Store the context on the table as an indication of fully initialized.
+ table->cxt = cxt;
+ }
+ sorted_tables = sorted_tables >> 1;
+ valid = valid >> 1;
+ }
+
+ return true;
+}
+
+bool validate_tables(mdcxt_t* cxt)
+{
+ assert(cxt != NULL);
+ (void)cxt;
+ // [TODO] Reference ECMA-335 and encode table verification.
+ // [TODO] Validate that tables marked as sorted are actually sorted.
+ // [TODO] Do not allow the *Ptr tables to be present in a compressed table heap.
+ return true;
+}
+
+bool try_get_pdb(mdcxt_t* cxt, md_pdb_t* pdb)
+{
+#ifdef DNMD_PORTABLE_PDB
+ assert(cxt != NULL && pdb != NULL);
+
+ mdstream_t* h = &cxt->pdb;
+ if (h->size == 0)
+ return false;
+
+ uint8_t const* curr = h->ptr;
+ size_t curr_len = h->size;
+
+ uint8_t const* pdb_id_maybe = curr;
+ if (!advance_stream(&curr, &curr_len, ARRAY_SIZE(pdb->pdb_id)))
+ return false;
+
+ memcpy(&pdb->pdb_id, pdb_id_maybe, ARRAY_SIZE(pdb->pdb_id));
+
+ uint64_t tables;
+ if (!read_u32(&curr, &curr_len, &pdb->entry_point)
+ || !read_u64(&curr, &curr_len, &tables))
+ {
+ return false;
+ }
+
+ pdb->referenced_type_system_tables = tables;
+ size_t n = count_set_bits(tables);
+ uint8_t const* pdb_end = curr + (n * sizeof(uint32_t));
+
+ // Read in all row data defined by the references bits.
+ for (size_t i = 0; i < MDTABLE_MAX_COUNT; ++i)
+ {
+ if (tables & 1)
+ {
+ // Read in the row count for referenced tables
+ if (!read_u32(&curr, &curr_len, &pdb->type_system_table_rows[i]))
+ return false;
+ }
+ else
+ {
+ pdb->type_system_table_rows[i] = 0;
+ }
+ tables = tables >> 1;
+ }
+
+ // Validate we processed the row counts properly
+
+ if (curr != pdb_end)
+ return false;
+ return true;
+#else
+ (void)cxt;
+ (void)pdb;
+ return false;
+#endif // !DNMD_PORTABLE_PDB
+}
+
+mdstream_t* get_heap_by_id(mdcxt_t* cxt, mdtcol_t heap_id)
+{
+ assert(cxt != NULL);
+ switch (heap_id)
+ {
+ case mdtc_hblob:
+ return &cxt->blob_heap;
+ case mdtc_hguid:
+ return &cxt->guid_heap;
+ case mdtc_hstring:
+ return &cxt->strings_heap;
+ case mdtc_hus:
+ return &cxt->user_string_heap;
+ default:
+ return NULL;
+ }
+}
+
+mdcxt_flag_t get_large_heap_flag(mdtcol_t heap_id)
+{
+ switch (heap_id)
+ {
+ case mdtc_hblob:
+ return mdc_large_blob_heap;
+ case mdtc_hguid:
+ return mdc_large_guid_heap;
+ case mdtc_hstring:
+ return mdc_large_string_heap;
+ default:
+ return 0;
+ }
+}
\ No newline at end of file
diff --git a/src/native/dnmd/src/dnmd/tables.c b/src/native/dnmd/src/dnmd/tables.c
new file mode 100644
index 0000000000000..ef890e157e0d7
--- /dev/null
+++ b/src/native/dnmd/src/dnmd/tables.c
@@ -0,0 +1,843 @@
+// Never permit debugging the column lookup logic.
+// Communicate this request by defining the following macro.
+#define MDTABLES_BUILD
+#include "internal.h"
+
+// Computed values from II.24.2.6
+static mdtable_id_t const TypeDefOrRef[] = { mdtid_TypeDef, mdtid_TypeRef, mdtid_TypeSpec };
+static mdtable_id_t const HasConstant[] = { mdtid_Field, mdtid_Param, mdtid_Property };
+static mdtable_id_t const HasCustomAttribute[] =
+{
+ mdtid_MethodDef,
+ mdtid_Field,
+ mdtid_TypeRef,
+ mdtid_TypeDef,
+ mdtid_Param,
+ mdtid_InterfaceImpl,
+ mdtid_MemberRef,
+ mdtid_Module,
+ mdtid_DeclSecurity,
+ mdtid_Property,
+ mdtid_Event,
+ mdtid_StandAloneSig,
+ mdtid_ModuleRef,
+ mdtid_TypeSpec,
+ mdtid_Assembly,
+ mdtid_AssemblyRef,
+ mdtid_File,
+ mdtid_ExportedType,
+ mdtid_ManifestResource,
+ mdtid_GenericParam,
+ mdtid_GenericParamConstraint,
+ mdtid_MethodSpec
+};
+static mdtable_id_t const HasFieldMarshall[] = { mdtid_Field, mdtid_Param };
+static mdtable_id_t const HasDeclSecurity[] = { mdtid_TypeDef, mdtid_MethodDef, mdtid_Assembly };
+static mdtable_id_t const MemberRefParent[] = { mdtid_TypeDef, mdtid_TypeRef, mdtid_ModuleRef, mdtid_MethodDef, mdtid_TypeSpec };
+static mdtable_id_t const HasSemantics[] = { mdtid_Event, mdtid_Property };
+static mdtable_id_t const MethodDefOrRef[] = { mdtid_MethodDef, mdtid_MemberRef };
+static mdtable_id_t const MemberForwarded[] = { mdtid_Field, mdtid_MethodDef };
+static mdtable_id_t const Implementation[] = { mdtid_File, mdtid_AssemblyRef, mdtid_ExportedType };
+static mdtable_id_t const CustomAttributeType[] = { mdtid_Unused, mdtid_Unused, mdtid_MethodDef, mdtid_MemberRef, mdtid_Unused };
+static mdtable_id_t const ResolutionScope[] = { mdtid_Module, mdtid_ModuleRef, mdtid_AssemblyRef, mdtid_TypeRef };
+static mdtable_id_t const TypeOrMethodDef[] = { mdtid_TypeDef, mdtid_MethodDef };
+
+#ifdef DNMD_PORTABLE_PDB
+static mdtable_id_t const HasCustomDebugInformation[] =
+{
+ mdtid_MethodDef,
+ mdtid_Field,
+ mdtid_TypeRef,
+ mdtid_TypeDef,
+ mdtid_Param,
+ mdtid_InterfaceImpl,
+ mdtid_MemberRef,
+ mdtid_Module,
+ mdtid_DeclSecurity,
+ mdtid_Property,
+ mdtid_Event,
+ mdtid_StandAloneSig,
+ mdtid_ModuleRef,
+ mdtid_TypeSpec,
+ mdtid_Assembly,
+ mdtid_AssemblyRef,
+ mdtid_File,
+ mdtid_ExportedType,
+ mdtid_ManifestResource,
+ mdtid_GenericParam,
+ mdtid_GenericParamConstraint,
+ mdtid_MethodSpec,
+ mdtid_Document,
+ mdtid_LocalScope,
+ mdtid_LocalVariable,
+ mdtid_LocalConstant,
+ mdtid_ImportScope,
+};
+#endif // DNMD_PORTABLE_PDB
+
+typedef struct
+{
+ // Coded index lookup
+ mdtable_id_t const* lookup;
+ // Coded index lookup length
+ uint8_t const lookup_len;
+ // Number of bits needed to encode lookup index
+ uint8_t const bit_encoding_size;
+} coded_index_entry_t;
+
+static coded_index_entry_t const coded_index_map[] =
+{
+ { TypeDefOrRef, ARRAY_SIZE(TypeDefOrRef), 2},
+ { HasConstant, ARRAY_SIZE(HasConstant), 2},
+ { HasCustomAttribute, ARRAY_SIZE(HasCustomAttribute), 5},
+ { HasFieldMarshall, ARRAY_SIZE(HasFieldMarshall), 1},
+ { HasDeclSecurity, ARRAY_SIZE(HasDeclSecurity), 2 },
+ { MemberRefParent, ARRAY_SIZE(MemberRefParent), 3 },
+ { HasSemantics, ARRAY_SIZE(HasSemantics), 1 },
+ { MethodDefOrRef, ARRAY_SIZE(MethodDefOrRef), 1 },
+ { MemberForwarded, ARRAY_SIZE(MemberForwarded), 1 },
+ { Implementation, ARRAY_SIZE(Implementation), 2 },
+ { CustomAttributeType, ARRAY_SIZE(CustomAttributeType), 3 },
+ { ResolutionScope, ARRAY_SIZE(ResolutionScope), 2 },
+ { TypeOrMethodDef, ARRAY_SIZE(TypeOrMethodDef), 1 },
+#ifdef DNMD_PORTABLE_PDB
+ { HasCustomDebugInformation, ARRAY_SIZE(HasCustomDebugInformation), 5 },
+#endif // DNMD_PORTABLE_PDB
+};
+
+bool compose_coded_index(mdToken tk, mdtcol_t col_details, uint32_t* coded_index)
+{
+ // See II.24.2.6
+ assert(coded_index != NULL);
+
+ // Use the embedded index into the coded index map.
+ size_t ci_idx = ExtractCodedIndex(col_details);
+ assert(ci_idx < ARRAY_SIZE(coded_index_map));
+ coded_index_entry_t const* ci_entry = &coded_index_map[ci_idx];
+
+ // Verify the supplied table type is valid for encoding.
+ mdtable_id_t tgt_table = ExtractTokenType(tk);
+ uint32_t row;
+ for (uint8_t i = 0; i < ci_entry->lookup_len; ++i)
+ {
+ // If the table is valid, construct the coded index.
+ if (ci_entry->lookup[i] == tgt_table)
+ {
+ row = RidFromToken(tk);
+ *coded_index = (row << ci_entry->bit_encoding_size) | (uint32_t)i;
+ return true;
+ }
+ }
+ return false;
+}
+
+bool decompose_coded_index(uint32_t cidx, mdtcol_t col_details, mdtable_id_t* table_id, uint32_t* table_row)
+{
+ // See II.24.2.6
+ assert(table_id != NULL && table_row != NULL);
+
+ // Use the embedded index into the coded index map.
+ size_t ci_idx = ExtractCodedIndex(col_details);
+ assert(ci_idx < ARRAY_SIZE(coded_index_map));
+ coded_index_entry_t const* ci_entry = &coded_index_map[ci_idx];
+
+ // Create a mask to extract the index into the entry.
+ uint32_t code_mask = (1 << ci_entry->bit_encoding_size) - 1;
+ if ((cidx & code_mask) >= ci_entry->lookup_len)
+ return false;
+ *table_id = ci_entry->lookup[cidx & code_mask];
+
+ // Remove the encoded lookup index.
+ *table_row = cidx >> ci_entry->bit_encoding_size;
+ return true;
+}
+
+bool is_coded_index_target(mdtcol_t col_details, mdtable_id_t table)
+{
+ assert(table != mdtid_Unused);
+
+ size_t ci_idx = ExtractCodedIndex(col_details);
+ assert(ci_idx < ARRAY_SIZE(coded_index_map));
+ coded_index_entry_t const* ci_entry = &coded_index_map[ci_idx];
+ for (uint8_t i = 0; i < ci_entry->lookup_len; ++i)
+ {
+ // If the table is valid, construct the coded index.
+ if (ci_entry->lookup[i] == table)
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
+// Look up table for column counts by table ID
+static uint8_t const table_column_counts[] =
+{
+ mdtModule_ColCount,
+ mdtTypeRef_ColCount,
+ mdtTypeDef_ColCount,
+ mdtFieldPtr_ColCount,
+ mdtField_ColCount,
+ mdtMethodPtr_ColCount,
+ mdtMethodDef_ColCount,
+ mdtParamPtr_ColCount,
+ mdtParam_ColCount,
+ mdtInterfaceImpl_ColCount,
+ mdtMemberRef_ColCount,
+ mdtConstant_ColCount,
+ mdtCustomAttribute_ColCount,
+ mdtFieldMarshal_ColCount,
+ mdtDeclSecurity_ColCount,
+ mdtClassLayout_ColCount,
+ mdtFieldLayout_ColCount,
+ mdtStandAloneSig_ColCount,
+ mdtEventMap_ColCount,
+ mdtEventPtr_ColCount,
+ mdtEvent_ColCount,
+ mdtPropertyMap_ColCount,
+ mdtPropertyPtr_ColCount,
+ mdtProperty_ColCount,
+ mdtMethodSemantics_ColCount,
+ mdtMethodImpl_ColCount,
+ mdtModuleRef_ColCount,
+ mdtTypeSpec_ColCount,
+ mdtImplMap_ColCount,
+ mdtFieldRva_ColCount,
+ 2, // ENCLog
+ 1, // ENCMap
+ mdtAssembly_ColCount,
+ 1, // AssemblyProcessor
+ 3, // AssemblyOS
+ mdtAssemblyRef_ColCount,
+ 2, // AssemblyRefProcessor,
+ 4, // AssemblyRefOS,
+ mdtFile_ColCount,
+ mdtExportedType_ColCount,
+ mdtManifestResource_ColCount,
+ mdtNestedClass_ColCount,
+ mdtGenericParam_ColCount,
+ mdtMethodSpec_ColCount,
+ mdtGenericParamConstraint_ColCount,
+#ifdef DNMD_PORTABLE_PDB
+ 0, // Reserved
+ 0, // Reserved
+ 0, // Reserved
+ // https://github.com/dotnet/runtime/blob/main/docs/design/specs/PortablePdb-Metadata.md
+ mdtDocument_ColCount,
+ mdtMethodDebugInformation_ColCount,
+ mdtLocalScope_ColCount,
+ mdtLocalVariable_ColCount,
+ mdtLocalConstant_ColCount,
+ mdtImportScope_ColCount,
+ mdtStateMachineMethod_ColCount,
+ mdtCustomDebugInformation_ColCount,
+#endif // DNMD_PORTABLE_PDB
+};
+static_assert(ARRAY_SIZE(table_column_counts) == MDTABLE_MAX_COUNT, "Table column count must match max count");
+
+uint8_t get_table_column_count(mdtable_id_t id)
+{
+ assert(mdtid_First <= id && id < mdtid_End);
+ return table_column_counts[id];
+}
+
+// II.22 Metadata logical format tables
+// DNMD implements the augments to the metadata logical format in the ECMA-335 spec located at https://github.com/dotnet/runtime/blob/main/docs/design/specs/Ecma-335-Augments.md
+static md_key_info_t const keys_ClassLayout[] = { { mdtClassLayout_Parent, false } };
+static md_key_info_t const keys_Constant[] = { { mdtConstant_Parent, false } };
+static md_key_info_t const keys_CustomAttribute[] = { { mdtCustomAttribute_Parent, false } };
+static md_key_info_t const keys_DeclSecurity[] = { { mdtDeclSecurity_Parent, false } };
+static md_key_info_t const keys_FieldLayout[] = { { mdtFieldLayout_Field, false } };
+static md_key_info_t const keys_FieldMarshal[] = { { mdtFieldMarshal_Parent, false } };
+static md_key_info_t const keys_FieldRva[] = { { mdtFieldRva_Field, false } };
+static md_key_info_t const keys_GenericParam[] = { { mdtGenericParam_Owner, false }, { mdtGenericParam_Number, false } };
+static md_key_info_t const keys_GenericParamConstraint[] = { { mdtGenericParamConstraint_Owner, false } };
+static md_key_info_t const keys_ImplMap[] = { { mdtImplMap_MemberForwarded, false } };
+static md_key_info_t const keys_InterfaceImpl[] = { { mdtInterfaceImpl_Class, false } };
+static md_key_info_t const keys_MethodImpl[] = { { mdtMethodImpl_Class, false } };
+static md_key_info_t const keys_MethodSemantics[] = { { mdtMethodSemantics_Association, false } };
+static md_key_info_t const keys_NestedClass[] = { { mdtNestedClass_NestedClass, false } };
+
+#ifdef DNMD_PORTABLE_PDB
+// https://github.com/dotnet/runtime/blob/main/docs/design/specs/PortablePdb-Metadata.md
+static md_key_info_t const keys_LocalScope[] = { { mdtLocalScope_Method, false }, { mdtLocalScope_StartOffset, false }, { mdtLocalScope_Length, true } };
+static md_key_info_t const keys_StateMachineMethod[] = { { mdtStateMachineMethod_MoveNextMethod, false } };
+static md_key_info_t const keys_CustomDebugInformation[] = { { mdtCustomDebugInformation_Parent, false } };
+#endif
+
+typedef struct
+{
+ md_key_info_t const* keys;
+ uint8_t key_count;
+} keys_info_t;
+
+static keys_info_t const table_keys[] =
+{
+ { NULL, 0 },
+ { NULL, 0 },
+ { NULL, 0 },
+ { NULL, 0 },
+ { NULL, 0 },
+ { NULL, 0 },
+ { NULL, 0 },
+ { NULL, 0 },
+ { NULL, 0 },
+ { keys_InterfaceImpl, ARRAY_SIZE(keys_InterfaceImpl) },
+ { NULL, 0 },
+ { keys_Constant, ARRAY_SIZE(keys_Constant) },
+ { keys_CustomAttribute, ARRAY_SIZE(keys_CustomAttribute) },
+ { keys_FieldMarshal, ARRAY_SIZE(keys_FieldMarshal) },
+ { keys_DeclSecurity, ARRAY_SIZE(keys_DeclSecurity) },
+ { keys_ClassLayout, ARRAY_SIZE(keys_ClassLayout) },
+ { keys_FieldLayout, ARRAY_SIZE(keys_FieldLayout) },
+ { NULL, 0 },
+ { NULL, 0 },
+ { NULL, 0 },
+ { NULL, 0 },
+ { NULL, 0 },
+ { NULL, 0 },
+ { NULL, 0 },
+ { keys_MethodSemantics, ARRAY_SIZE(keys_MethodSemantics) },
+ { keys_MethodImpl, ARRAY_SIZE(keys_MethodImpl) },
+ { NULL, 0 },
+ { NULL, 0 },
+ { keys_ImplMap, ARRAY_SIZE(keys_ImplMap) },
+ { keys_FieldRva, ARRAY_SIZE(keys_FieldRva) },
+ { NULL, 0 }, // ENCLog
+ { NULL, 0 }, // ENCMap
+ { NULL, 0 },
+ { NULL, 0 }, // AssemblyProcessor
+ { NULL, 0 }, // AssemblyOS
+ { NULL, 0 },
+ { NULL, 0 }, // AssemblyRefProcessor,
+ { NULL, 0 }, // AssemblyRefOS,
+ { NULL, 0 },
+ { NULL, 0 },
+ { NULL, 0 },
+ { keys_NestedClass, ARRAY_SIZE(keys_NestedClass) },
+ { keys_GenericParam, ARRAY_SIZE(keys_GenericParam) },
+ { NULL, 0 },
+ { keys_GenericParamConstraint, ARRAY_SIZE(keys_GenericParamConstraint) },
+#ifdef DNMD_PORTABLE_PDB
+ { NULL, 0 }, // Reserved
+ { NULL, 0 }, // Reserved
+ { NULL, 0 }, // Reserved
+ // https://github.com/dotnet/runtime/blob/main/docs/design/specs/PortablePdb-Metadata.md
+ { NULL, 0 },
+ { NULL, 0 },
+ { keys_LocalScope, ARRAY_SIZE(keys_LocalScope) },
+ { NULL, 0 },
+ { NULL, 0 },
+ { NULL, 0 },
+ { keys_StateMachineMethod, ARRAY_SIZE(keys_StateMachineMethod) },
+ { keys_CustomDebugInformation, ARRAY_SIZE(keys_CustomDebugInformation) },
+#endif // DNMD_PORTABLE_PDB
+};
+
+// II.22 Metadata logical format tables
+// Primary and secondary key info for tables
+uint8_t get_table_keys(mdtable_id_t id, md_key_info_t const** keys)
+{
+ assert(mdtid_First <= id && id < mdtid_End);
+ *keys = table_keys[id].keys;
+ return table_keys[id].key_count;
+}
+
+// Compute the row size and embed the offset for
+// each column in the column details.
+static uint32_t compute_row_offsets_size(mdtcol_t* col, size_t col_len)
+{
+ uint32_t c = 0;
+ for (size_t i = 0; i < col_len; i++)
+ {
+ assert((mdtc_b2 | mdtc_b4) & col[i]);
+ col[i] |= InsertOffset(c);
+ c += ((col[i] & mdtc_b2) ? 2 : 4);
+ }
+ return c;
+}
+
+// II.24.2.6
+static mdtcol_t compute_coded_index(bool is_minimal_delta, uint32_t const* row_counts, md_coded_idx_t coded_map_idx)
+{
+ assert(coded_map_idx < ARRAY_SIZE(coded_index_map));
+ assert(coded_map_idx <= ExtractCodedIndex(mdtc_cimask) && "Coded index map index bit encoding exceeded");
+
+ coded_index_entry_t const* entry = &coded_index_map[coded_map_idx];
+
+ uint32_t rc;
+ uint32_t m = 0;
+ for (uint8_t i = 0; i < entry->lookup_len; ++i)
+ {
+ if (entry->lookup[i] != mdtid_Unused)
+ {
+ rc = row_counts[entry->lookup[i]];
+ if (m < rc)
+ m = rc;
+ }
+ }
+ uint32_t max_rows_2b = (uint32_t)1 << (16 - entry->bit_encoding_size);
+ return InsertCodedIndex(coded_map_idx) | mdtc_idx_coded | (m < max_rows_2b && !is_minimal_delta ? mdtc_b2 : mdtc_b4);
+}
+
+static mdtcol_t compute_table_index(bool is_minimal_delta, uint32_t const* row_counts, mdtable_id_t id)
+{
+ assert(row_counts != NULL && (mdtid_First <= id && id < mdtid_End));
+ return InsertTable(id) | (row_counts[id] < (1 << 16) && !is_minimal_delta ? mdtc_b2 : mdtc_b4) | mdtc_idx_table;
+}
+
+static mdtable_id_t get_target_table(uint32_t const* all_table_row_counts, mdtable_id_t direct_table, mdtable_id_t indirect_table)
+{
+ assert(all_table_row_counts != NULL);
+ assert(mdtid_First <= direct_table && direct_table < mdtid_End);
+ assert(mdtid_First <= indirect_table && indirect_table < mdtid_End);
+ return all_table_row_counts[indirect_table] != 0 ? indirect_table : direct_table;
+}
+
+bool initialize_table_details(
+ uint32_t const* all_table_row_counts,
+ mdcxt_flag_t context_flags,
+ mdtable_id_t id,
+ bool is_sorted,
+ mdtable_t* table)
+{
+ assert(all_table_row_counts != NULL && (mdtid_First <= id && id < mdtid_End) && table != NULL);
+ assert(all_table_row_counts[id] != 0 && "Unable to initialize a table with a row count of 0.");
+ if (all_table_row_counts[id] == 0)
+ return false;
+
+ mdtcol_t const string_index = mdtc_idx_heap | mdtc_hstring | (context_flags & mdc_large_string_heap ? mdtc_b4 : mdtc_b2);
+ mdtcol_t const guid_index = mdtc_idx_heap | mdtc_hguid | (context_flags & mdc_large_guid_heap ? mdtc_b4 : mdtc_b2);
+ mdtcol_t const blob_index = mdtc_idx_heap | mdtc_hblob | (context_flags & mdc_large_blob_heap ? mdtc_b4 : mdtc_b2);
+
+ bool is_minimal_delta = (context_flags & mdc_minimal_delta) == mdc_minimal_delta;
+
+ table->row_count = all_table_row_counts[id];
+ table->is_sorted = is_sorted;
+ table->table_id = (uint8_t)id;
+
+#define CODED_INDEX_ARGS(x) is_minimal_delta, all_table_row_counts, (x)
+#define TABLE_INDEX_ARGS(x) is_minimal_delta, all_table_row_counts, (x)
+ switch (id)
+ {
+ case mdtid_Module: // II.22.30
+ table->column_details[mdtModule_Generation] = mdtc_constant | mdtc_b2;
+ table->column_details[mdtModule_Name] = string_index;
+ table->column_details[mdtModule_Mvid] = guid_index;
+ table->column_details[mdtModule_EncId] = guid_index;
+ table->column_details[mdtModule_EncBaseId] = guid_index;
+ assert(mdtModule_ColCount == get_table_column_count(id));
+ break;
+ case mdtid_TypeRef: // II.22.38
+ table->column_details[mdtTypeRef_ResolutionScope] = compute_coded_index(CODED_INDEX_ARGS(mdci_ResolutionScope));
+ table->column_details[mdtTypeRef_TypeName] = string_index;
+ table->column_details[mdtTypeRef_TypeNamespace] = string_index;
+ assert(mdtTypeRef_ColCount == get_table_column_count(id));
+ break;
+ case mdtid_TypeDef: // II.22.37
+ table->column_details[mdtTypeDef_Flags] = mdtc_constant | mdtc_b4;
+ table->column_details[mdtTypeDef_TypeName] = string_index;
+ table->column_details[mdtTypeDef_TypeNamespace] = string_index;
+ table->column_details[mdtTypeDef_Extends] = compute_coded_index(CODED_INDEX_ARGS(mdci_TypeDefOrRef));
+ table->column_details[mdtTypeDef_FieldList] = compute_table_index(TABLE_INDEX_ARGS(get_target_table(all_table_row_counts, mdtid_Field, mdtid_FieldPtr)));
+ table->column_details[mdtTypeDef_MethodList] = compute_table_index(TABLE_INDEX_ARGS(get_target_table(all_table_row_counts, mdtid_MethodDef, mdtid_MethodPtr)));
+ assert(mdtTypeDef_ColCount == get_table_column_count(id));
+ break;
+ case mdtid_FieldPtr: // Not in ECMA
+ table->column_details[mdtFieldPtr_Field] = compute_table_index(TABLE_INDEX_ARGS(mdtid_Field));
+ assert(mdtFieldPtr_ColCount == get_table_column_count(id));
+ break;
+ case mdtid_Field: // II.22.15
+ table->column_details[mdtField_Flags] = mdtc_constant | mdtc_b2;
+ table->column_details[mdtField_Name] = string_index;
+ table->column_details[mdtField_Signature] = blob_index;
+ assert(mdtField_ColCount == get_table_column_count(id));
+ break;
+ case mdtid_MethodPtr: // Not in ECMA
+ table->column_details[mdtMethodPtr_Method] = compute_table_index(TABLE_INDEX_ARGS(mdtid_MethodDef));
+ assert(mdtMethodPtr_ColCount == get_table_column_count(id));
+ break;
+ case mdtid_MethodDef: // II.22.26
+ table->column_details[mdtMethodDef_Rva] = mdtc_constant | mdtc_b4;
+ table->column_details[mdtMethodDef_ImplFlags] = mdtc_constant | mdtc_b2;
+ table->column_details[mdtMethodDef_Flags] = mdtc_constant | mdtc_b2;
+ table->column_details[mdtMethodDef_Name] = string_index;
+ table->column_details[mdtMethodDef_Signature] = blob_index;
+ table->column_details[mdtMethodDef_ParamList] = compute_table_index(TABLE_INDEX_ARGS(get_target_table(all_table_row_counts, mdtid_Param, mdtid_ParamPtr)));
+ assert(mdtMethodDef_ColCount == get_table_column_count(id));
+ break;
+ case mdtid_ParamPtr: // Not in ECMA
+ table->column_details[mdtParamPtr_Param] = compute_table_index(TABLE_INDEX_ARGS(mdtid_Param));
+ assert(mdtParamPtr_ColCount == get_table_column_count(id));
+ break;
+ case mdtid_Param: // II.22.33
+ table->column_details[mdtParam_Flags] = mdtc_constant | mdtc_b2;
+ table->column_details[mdtParam_Sequence] = mdtc_constant | mdtc_b2;
+ table->column_details[mdtParam_Name] = string_index;
+ assert(mdtParam_ColCount == get_table_column_count(id));
+ break;
+ case mdtid_InterfaceImpl: // II.22.23
+ table->column_details[mdtInterfaceImpl_Class] = compute_table_index(TABLE_INDEX_ARGS(mdtid_TypeDef));
+ table->column_details[mdtInterfaceImpl_Interface] = compute_coded_index(CODED_INDEX_ARGS(mdci_TypeDefOrRef));
+ assert(mdtInterfaceImpl_ColCount == get_table_column_count(id));
+ break;
+ case mdtid_MemberRef: // II.22.25
+ table->column_details[mdtMemberRef_Class] = compute_coded_index(CODED_INDEX_ARGS(mdci_MemberRefParent));
+ table->column_details[mdtMemberRef_Name] = string_index;
+ table->column_details[mdtMemberRef_Signature] = blob_index;
+ assert(mdtMemberRef_ColCount == get_table_column_count(id));
+ break;
+ case mdtid_Constant: // II.22.9
+ table->column_details[mdtConstant_Type] = mdtc_constant | mdtc_b2;
+ table->column_details[mdtConstant_Parent] = compute_coded_index(CODED_INDEX_ARGS(mdci_HasConstant));
+ table->column_details[mdtConstant_Value] = blob_index;
+ assert(mdtConstant_ColCount == get_table_column_count(id));
+ break;
+ case mdtid_CustomAttribute: // II.22.10
+ table->column_details[mdtCustomAttribute_Parent] = compute_coded_index(CODED_INDEX_ARGS(mdci_HasCustomAttribute));
+ table->column_details[mdtCustomAttribute_Type] = compute_coded_index(CODED_INDEX_ARGS(mdci_CustomAttributeType));
+ table->column_details[mdtCustomAttribute_Value] = blob_index;
+ assert(mdtCustomAttribute_ColCount == get_table_column_count(id));
+ break;
+ case mdtid_FieldMarshal: // II.22.17
+ table->column_details[mdtFieldMarshal_Parent] = compute_coded_index(CODED_INDEX_ARGS(mdci_HasFieldMarshall));
+ table->column_details[mdtFieldMarshal_NativeType] = blob_index;
+ assert(mdtFieldMarshal_ColCount == get_table_column_count(id));
+ break;
+ case mdtid_DeclSecurity: // II.22.11
+ table->column_details[mdtDeclSecurity_Action] = mdtc_constant | mdtc_b2;
+ table->column_details[mdtDeclSecurity_Parent] = compute_coded_index(CODED_INDEX_ARGS(mdci_HasDeclSecurity));
+ table->column_details[mdtDeclSecurity_PermissionSet] = blob_index;
+ assert(mdtDeclSecurity_ColCount == get_table_column_count(id));
+ break;
+ case mdtid_ClassLayout: // II.22.8
+ table->column_details[mdtClassLayout_PackingSize] = mdtc_constant | mdtc_b2;
+ table->column_details[mdtClassLayout_ClassSize] = mdtc_constant | mdtc_b4;
+ table->column_details[mdtClassLayout_Parent] = compute_table_index(TABLE_INDEX_ARGS(mdtid_TypeDef));
+ assert(mdtClassLayout_ColCount == get_table_column_count(id));
+ break;
+ case mdtid_FieldLayout: // II.22.16
+ table->column_details[mdtFieldLayout_Offset] = mdtc_constant | mdtc_b4;
+ table->column_details[mdtFieldLayout_Field] = compute_table_index(TABLE_INDEX_ARGS(mdtid_Field));
+ assert(mdtFieldLayout_ColCount == get_table_column_count(id));
+ break;
+ case mdtid_StandAloneSig: // II.22.36
+ table->column_details[mdtStandAloneSig_Signature] = blob_index;
+ assert(mdtStandAloneSig_ColCount == get_table_column_count(id));
+ break;
+ case mdtid_EventMap: // II.22.12
+ table->column_details[mdtEventMap_Parent] = compute_table_index(TABLE_INDEX_ARGS(mdtid_TypeDef));
+ table->column_details[mdtEventMap_EventList] = compute_table_index(TABLE_INDEX_ARGS(get_target_table(all_table_row_counts, mdtid_Event, mdtid_EventPtr)));
+ assert(mdtEventMap_ColCount == get_table_column_count(id));
+ break;
+ case mdtid_EventPtr: // Not in ECMA
+ table->column_details[mdtEventPtr_Event] = compute_table_index(TABLE_INDEX_ARGS(mdtid_Event));
+ assert(mdtEventPtr_ColCount == get_table_column_count(id));
+ break;
+ case mdtid_Event:// II.22.13
+ table->column_details[mdtEvent_EventFlags] = mdtc_constant | mdtc_b2;
+ table->column_details[mdtEvent_Name] = string_index;
+ table->column_details[mdtEvent_EventType] = compute_coded_index(CODED_INDEX_ARGS(mdci_TypeDefOrRef));
+ assert(mdtEvent_ColCount == get_table_column_count(id));
+ break;
+ case mdtid_PropertyMap: // II.22.35
+ table->column_details[mdtPropertyMap_Parent] = compute_table_index(TABLE_INDEX_ARGS(mdtid_TypeDef));
+ table->column_details[mdtPropertyMap_PropertyList] = compute_table_index(TABLE_INDEX_ARGS(get_target_table(all_table_row_counts, mdtid_Property, mdtid_PropertyPtr)));
+ assert(mdtPropertyMap_ColCount == get_table_column_count(id));
+ break;
+ case mdtid_PropertyPtr: // Not in ECMA
+ table->column_details[mdtPropertyPtr_Property] = compute_table_index(TABLE_INDEX_ARGS(mdtid_Property));
+ assert(mdtPropertyPtr_ColCount == get_table_column_count(id));
+ break;
+ case mdtid_Property: // II.22.34
+ table->column_details[mdtProperty_Flags] = mdtc_constant | mdtc_b2;
+ table->column_details[mdtProperty_Name] = string_index;
+ table->column_details[mdtProperty_Type] = blob_index;
+ assert(mdtProperty_ColCount == get_table_column_count(id));
+ break;
+ case mdtid_MethodSemantics: // II.22.28
+ table->column_details[mdtMethodSemantics_Semantics] = mdtc_constant | mdtc_b2;
+ table->column_details[mdtMethodSemantics_Method] = compute_table_index(TABLE_INDEX_ARGS(mdtid_MethodDef));
+ table->column_details[mdtMethodSemantics_Association] = compute_coded_index(CODED_INDEX_ARGS(mdci_HasSemantics));
+ assert(mdtMethodSemantics_ColCount == get_table_column_count(id));
+ break;
+ case mdtid_MethodImpl: // II.22.27
+ table->column_details[mdtMethodImpl_Class] = compute_table_index(TABLE_INDEX_ARGS(mdtid_TypeDef));
+ table->column_details[mdtMethodImpl_MethodBody] = compute_coded_index(CODED_INDEX_ARGS(mdci_MethodDefOrRef));
+ table->column_details[mdtMethodImpl_MethodDeclaration] = compute_coded_index(CODED_INDEX_ARGS(mdci_MethodDefOrRef));
+ assert(mdtMethodImpl_ColCount == get_table_column_count(id));
+ break;
+ case mdtid_ModuleRef: // II.22.31
+ table->column_details[mdtModuleRef_Name] = string_index;
+ assert(mdtModuleRef_ColCount == get_table_column_count(id));
+ break;
+ case mdtid_TypeSpec: // II.22.39
+ table->column_details[mdtTypeSpec_Signature] = blob_index;
+ assert(mdtTypeSpec_ColCount == get_table_column_count(id));
+ break;
+ case mdtid_ImplMap: // II.22.22
+ table->column_details[mdtImplMap_MappingFlags] = mdtc_constant | mdtc_b2;
+ table->column_details[mdtImplMap_MemberForwarded] = compute_coded_index(CODED_INDEX_ARGS(mdci_MemberForwarded));
+ table->column_details[mdtImplMap_ImportName] = string_index;
+ table->column_details[mdtImplMap_ImportScope] = compute_table_index(TABLE_INDEX_ARGS(mdtid_ModuleRef));
+ assert(mdtImplMap_ColCount == get_table_column_count(id));
+ break;
+ case mdtid_FieldRva: // II.22.18
+ table->column_details[mdtFieldRva_Rva] = mdtc_constant | mdtc_b4;
+ table->column_details[mdtFieldRva_Field] = compute_table_index(TABLE_INDEX_ARGS(mdtid_Field));
+ assert(mdtFieldRva_ColCount == get_table_column_count(id));
+ break;
+ case mdtid_ENCLog:
+ table->column_details[mdtENCLog_Token] = mdtc_constant | mdtc_b4;
+ table->column_details[mdtENCLog_Op] = mdtc_constant | mdtc_b4;
+ assert(mdtENCLog_ColCount == get_table_column_count(id));
+ break;
+ case mdtid_ENCMap:
+ table->column_details[mdtENCMap_Token] = mdtc_constant | mdtc_b4;
+ assert(mdtENCMap_ColCount == get_table_column_count(id));
+ break;
+ case mdtid_Assembly: // II.22.2
+ table->column_details[mdtAssembly_HashAlgId] = mdtc_constant | mdtc_b4;
+ table->column_details[mdtAssembly_MajorVersion] = mdtc_constant | mdtc_b2;
+ table->column_details[mdtAssembly_MinorVersion] = mdtc_constant | mdtc_b2;
+ table->column_details[mdtAssembly_BuildNumber] = mdtc_constant | mdtc_b2;
+ table->column_details[mdtAssembly_RevisionNumber] = mdtc_constant | mdtc_b2;
+ table->column_details[mdtAssembly_Flags] = mdtc_constant | mdtc_b4;
+ table->column_details[mdtAssembly_PublicKey] = blob_index;
+ table->column_details[mdtAssembly_Name] = string_index;
+ table->column_details[mdtAssembly_Culture] = string_index;
+ assert(mdtAssembly_ColCount == get_table_column_count(id));
+ break;
+ case mdtid_AssemblyProcessor: // II.22.3
+ table->column_details[0] = mdtc_constant | mdtc_b4;
+ assert(1 == get_table_column_count(id));
+ break;
+ case mdtid_AssemblyOS: // II.22.4
+ table->column_details[0] = mdtc_constant | mdtc_b4;
+ table->column_details[1] = mdtc_constant | mdtc_b4;
+ table->column_details[2] = mdtc_constant | mdtc_b4;
+ assert(3 == get_table_column_count(id));
+ break;
+ case mdtid_AssemblyRef: // II.22.5
+ table->column_details[mdtAssemblyRef_MajorVersion] = mdtc_constant | mdtc_b2;
+ table->column_details[mdtAssemblyRef_MinorVersion] = mdtc_constant | mdtc_b2;
+ table->column_details[mdtAssemblyRef_BuildNumber] = mdtc_constant | mdtc_b2;
+ table->column_details[mdtAssemblyRef_RevisionNumber] = mdtc_constant | mdtc_b2;
+ table->column_details[mdtAssemblyRef_Flags] = mdtc_constant | mdtc_b4;
+ table->column_details[mdtAssemblyRef_PublicKeyOrToken] = blob_index;
+ table->column_details[mdtAssemblyRef_Name] = string_index;
+ table->column_details[mdtAssemblyRef_Culture] = string_index;
+ table->column_details[mdtAssemblyRef_HashValue] = blob_index;
+ assert(mdtAssemblyRef_ColCount == get_table_column_count(id));
+ break;
+ case mdtid_AssemblyRefProcessor: // II.22.7
+ table->column_details[0] = mdtc_constant | mdtc_b4;
+ table->column_details[1] = compute_table_index(TABLE_INDEX_ARGS(mdtid_AssemblyRef));
+ assert(2 == get_table_column_count(id));
+ break;
+ case mdtid_AssemblyRefOS: // II.22.6
+ table->column_details[0] = mdtc_constant | mdtc_b4;
+ table->column_details[1] = mdtc_constant | mdtc_b4;
+ table->column_details[2] = mdtc_constant | mdtc_b4;
+ table->column_details[3] = compute_table_index(TABLE_INDEX_ARGS(mdtid_AssemblyRef));
+ assert(4 == get_table_column_count(id));
+ break;
+ case mdtid_File: // II.22.19
+ table->column_details[mdtFile_Flags] = mdtc_constant | mdtc_b4;
+ table->column_details[mdtFile_Name] = string_index;
+ table->column_details[mdtFile_HashValue] = blob_index;
+ assert(mdtFile_ColCount == get_table_column_count(id));
+ break;
+ case mdtid_ExportedType: // II.22.14
+ table->column_details[mdtExportedType_Flags] = mdtc_constant | mdtc_b4;
+ table->column_details[mdtExportedType_TypeDefId] = mdtc_constant | mdtc_b4;
+ table->column_details[mdtExportedType_TypeName] = string_index;
+ table->column_details[mdtExportedType_TypeNamespace] = string_index;
+ table->column_details[mdtExportedType_Implementation] = compute_coded_index(CODED_INDEX_ARGS(mdci_Implementation));
+ assert(mdtExportedType_ColCount == get_table_column_count(id));
+ break;
+ case mdtid_ManifestResource: // II.22.24
+ table->column_details[mdtManifestResource_Offset] = mdtc_constant | mdtc_b4;
+ table->column_details[mdtManifestResource_Flags] = mdtc_constant | mdtc_b4;
+ table->column_details[mdtManifestResource_Name] = string_index;
+ table->column_details[mdtManifestResource_Implementation] = compute_coded_index(CODED_INDEX_ARGS(mdci_Implementation));
+ assert(mdtManifestResource_ColCount == get_table_column_count(id));
+ break;
+ case mdtid_NestedClass: // II.22.32
+ table->column_details[mdtNestedClass_NestedClass] = compute_table_index(TABLE_INDEX_ARGS(mdtid_TypeDef));
+ table->column_details[mdtNestedClass_EnclosingClass] = compute_table_index(TABLE_INDEX_ARGS(mdtid_TypeDef));
+ assert(mdtNestedClass_ColCount == get_table_column_count(id));
+ break;
+ case mdtid_GenericParam: // II.22.20
+ table->column_details[mdtGenericParam_Number] = mdtc_constant | mdtc_b2;
+ table->column_details[mdtGenericParam_Flags] = mdtc_constant | mdtc_b2;
+ table->column_details[mdtGenericParam_Owner] = compute_coded_index(CODED_INDEX_ARGS(mdci_TypeOrMethodDef));
+ table->column_details[mdtGenericParam_Name] = string_index;
+ assert(mdtGenericParam_ColCount == get_table_column_count(id));
+ break;
+ case mdtid_MethodSpec: // II.22.29
+ table->column_details[mdtMethodSpec_Method] = compute_coded_index(CODED_INDEX_ARGS(mdci_MethodDefOrRef));
+ table->column_details[mdtMethodSpec_Instantiation] = blob_index;
+ assert(mdtMethodSpec_ColCount == get_table_column_count(id));
+ break;
+ case mdtid_GenericParamConstraint: // II.22.21
+ table->column_details[mdtGenericParamConstraint_Owner] = compute_table_index(TABLE_INDEX_ARGS(mdtid_GenericParam));
+ table->column_details[mdtGenericParamConstraint_Constraint] = compute_coded_index(CODED_INDEX_ARGS(mdci_TypeDefOrRef));
+ assert(mdtGenericParamConstraint_ColCount == get_table_column_count(id));
+ break;
+
+#ifdef DNMD_PORTABLE_PDB
+ // https://github.com/dotnet/runtime/blob/main/docs/design/specs/PortablePdb-Metadata.md
+ case mdtid_Document:
+ table->column_details[mdtDocument_Name] = blob_index;
+ table->column_details[mdtDocument_HashAlgorithm] = guid_index;
+ table->column_details[mdtDocument_Hash] = blob_index;
+ table->column_details[mdtDocument_Language] = guid_index;
+ assert(mdtDocument_ColCount == get_table_column_count(id));
+ break;
+ case mdtid_MethodDebugInformation:
+ table->column_details[mdtMethodDebugInformation_Document] = compute_table_index(TABLE_INDEX_ARGS(mdtid_Document));
+ table->column_details[mdtMethodDebugInformation_SequencePoints] = blob_index;
+ assert(mdtMethodDebugInformation_ColCount == get_table_column_count(id));
+ break;
+ case mdtid_LocalScope:
+ table->column_details[mdtLocalScope_Method] = compute_table_index(TABLE_INDEX_ARGS(mdtid_MethodDef));
+ table->column_details[mdtLocalScope_ImportScope] = compute_table_index(TABLE_INDEX_ARGS(mdtid_ImportScope));
+ table->column_details[mdtLocalScope_VariableList] = compute_table_index(TABLE_INDEX_ARGS(mdtid_LocalVariable));
+ table->column_details[mdtLocalScope_ConstantList] = compute_table_index(TABLE_INDEX_ARGS(mdtid_LocalConstant));
+ table->column_details[mdtLocalScope_StartOffset] = mdtc_constant | mdtc_b4;
+ table->column_details[mdtLocalScope_Length] = mdtc_constant | mdtc_b4;
+ assert(mdtLocalScope_ColCount == get_table_column_count(id));
+ break;
+ case mdtid_LocalVariable:
+ table->column_details[mdtLocalVariable_Attributes] = mdtc_constant | mdtc_b2;
+ table->column_details[mdtLocalVariable_Index] = mdtc_constant | mdtc_b2;
+ table->column_details[mdtLocalVariable_Name] = string_index;
+ assert(mdtLocalVariable_ColCount == get_table_column_count(id));
+ break;
+ case mdtid_LocalConstant:
+ table->column_details[mdtLocalConstant_Name] = string_index;
+ table->column_details[mdtLocalConstant_Signature] = blob_index;
+ assert(mdtLocalConstant_ColCount == get_table_column_count(id));
+ break;
+ case mdtid_ImportScope:
+ table->column_details[mdtImportScope_Parent] = compute_table_index(TABLE_INDEX_ARGS(mdtid_ImportScope));
+ table->column_details[mdtImportScope_Imports] = blob_index;
+ assert(mdtImportScope_ColCount == get_table_column_count(id));
+ break;
+ case mdtid_StateMachineMethod:
+ table->column_details[mdtStateMachineMethod_MoveNextMethod] = compute_table_index(TABLE_INDEX_ARGS(mdtid_MethodDef));
+ table->column_details[mdtStateMachineMethod_KickoffMethod] = compute_table_index(TABLE_INDEX_ARGS(mdtid_MethodDef));
+ assert(mdtStateMachineMethod_ColCount == get_table_column_count(id));
+ break;
+ case mdtid_CustomDebugInformation:
+ table->column_details[mdtCustomDebugInformation_Parent] = compute_coded_index(CODED_INDEX_ARGS(mdci_HasCustomDebugInformation));
+ table->column_details[mdtCustomDebugInformation_Kind] = guid_index;
+ table->column_details[mdtCustomDebugInformation_Value] = blob_index;
+ assert(mdtCustomDebugInformation_ColCount == get_table_column_count(id));
+ break;
+#endif // DNMD_PORTABLE_PDB
+
+ default:
+ assert(!"Unknown metadata table ID");
+ return false;
+ }
+#undef TABLE_INDEX_ARGS
+#undef CODED_INDEX_ARGS
+
+ // Set the column count
+ table->column_count = get_table_column_count(id);
+ assert(table->column_count != 0);
+ uint32_t size_bytes = compute_row_offsets_size(table->column_details, table->column_count);
+ assert(size_bytes <= UINT8_MAX);
+ table->row_size_bytes = (uint8_t)size_bytes;
+ return true;
+}
+
+bool initialize_new_table_details(
+ mdcxt_t* cxt,
+ mdtable_id_t id,
+ mdtable_t* table
+)
+{
+ assert(table->cxt == NULL);
+ // Use the real table row counts to ensure that when saving, we can
+ // directly write out table memory without any required post-processing.
+ uint32_t table_row_counts[MDTABLE_MAX_COUNT];
+ for (size_t i = 0; i < MDTABLE_MAX_COUNT; i++)
+ {
+ table_row_counts[i] = cxt->tables[i].row_count;
+ }
+
+ // Set the new table's row count temporarily to 1 to ensure that we initialize the table.
+ table_row_counts[id] = 1;
+
+ // We'll treat any new table that has keys as sorted.
+ // We only want to do this for tables with keys as tables without keys
+ // never use the is_sorted bit.
+ md_key_info_t const* keys;
+ uint8_t key_count = get_table_keys(id, &keys);
+ bool has_keys = key_count != 0;
+
+ if (!initialize_table_details(
+ table_row_counts,
+ cxt->context_flags,
+ id,
+ has_keys,
+ table))
+ return false;
+
+ table->row_count = 0;
+ return true;
+}
+
+bool consume_table_rows(mdtable_t* table, uint8_t const** data, size_t* data_len)
+{
+ assert(table != NULL && data != NULL && data_len != NULL);
+ assert(table->row_size_bytes != 0 && "Table with row byte length of 0 is unexpected");
+ if (table->row_count == 0)
+ return true;
+
+ uint8_t const* rows = *data;
+ size_t rows_len = table->row_size_bytes * (size_t)table->row_count;
+ if (!advance_stream(data, data_len, rows_len))
+ return false;
+
+ table->data.ptr = rows;
+ table->data.size = rows_len;
+ return true;
+}
+
+bool table_is_indirect_table(mdtable_id_t table_id)
+{
+ switch (table_id)
+ {
+ case mdtid_FieldPtr:
+ case mdtid_MethodPtr:
+ case mdtid_ParamPtr:
+ case mdtid_EventPtr:
+ case mdtid_PropertyPtr:
+ return true;
+ default:
+ return false;
+ }
+}
+
+mdtable_id_t get_corresponding_indirection_table(mdtable_id_t table_id)
+{
+ switch (table_id)
+ {
+ case mdtid_Field:
+ return mdtid_FieldPtr;
+ case mdtid_MethodDef:
+ return mdtid_MethodPtr;
+ case mdtid_Param:
+ return mdtid_ParamPtr;
+ case mdtid_Event:
+ return mdtid_EventPtr;
+ case mdtid_Property:
+ return mdtid_PropertyPtr;
+ default:
+ return mdtid_Unused;
+ }
+}
\ No newline at end of file
diff --git a/src/native/dnmd/src/dnmd/write.c b/src/native/dnmd/src/dnmd/write.c
new file mode 100644
index 0000000000000..41cd4889eb243
--- /dev/null
+++ b/src/native/dnmd/src/dnmd/write.c
@@ -0,0 +1,1044 @@
+#include "internal.h"
+
+static bool is_row_sorted_with_next_row(md_key_info_t const* keys, uint8_t count_keys, mdtable_id_t table_id, mdcursor_t row, mdcursor_t next_row)
+{
+ // We have a previous row, let's validate that it's sorted.
+ for (uint8_t i = 0; i < count_keys; i++)
+ {
+ col_index_t key_col = index_to_col(keys[i].index, table_id);
+
+ access_cxt_t row_acxt;
+ if (!create_access_context(&row, key_col, false, &row_acxt))
+ return false;
+
+ // Key columns can only be constant, index into a table, or a coded token index.
+ // Heap offset columns cannot be keys.
+ assert(row_acxt.col_details & (mdtc_constant | mdtc_idx_table | mdtc_idx_coded));
+
+ access_cxt_t next_acxt;
+ if (!create_access_context(&next_row, key_col, false, &next_acxt))
+ return false;
+
+ uint32_t row_value;
+ if (!read_column_data(&row_acxt, &row_value))
+ return false;
+
+ uint32_t next_value;
+ if (!read_column_data(&next_acxt, &next_value))
+ return false;
+
+ bool column_sorted = keys[i].descending ? (row_value >= next_value) : (row_value <= next_value);
+ if (!column_sorted)
+ return false;
+ }
+ return true;
+}
+
+static bool set_column_value_as_token_or_cursor(mdcursor_t c, uint32_t col_idx, mdToken const* tk, mdcursor_t const* cursor)
+{
+ access_cxt_t acxt;
+ if (!create_access_context(&c, col_idx, true, &acxt))
+ return false;
+
+ // If we can't write on the underlying table, then fail.
+ if (acxt.writable_data == NULL)
+ return false;
+
+ // If this isn't an index column, then fail.
+ if (!(acxt.col_details & (mdtc_idx_table | mdtc_idx_coded)))
+ return false;
+
+ uint8_t key_count = 0;
+ uint8_t key_idx = UINT8_MAX;
+ md_key_info_t const* keys = NULL;
+ // If we're editing already-existing rows, then we need to validate that we stay sorted.
+ // If we're in the middle of a row-add operation, we'll wait until the add is complete to validate.
+ if (acxt.table->is_sorted && !acxt.table->is_adding_new_row)
+ {
+ // If the table is sorted, then we need to validate that we stay sorted.
+ // We will not check here if a table goes from unsorted to sorted as that would require
+ // significantly more work to validate and is not a correctness issue.
+ key_count = get_table_keys(acxt.table->table_id, &keys);
+ for (uint8_t i = 0; i < key_count; i++)
+ {
+ if (keys[i].index == col_to_index(col_idx, acxt.table))
+ {
+ key_idx = i;
+ break;
+ }
+ }
+ }
+
+ mdToken token;
+ if (tk != NULL)
+ {
+ token = *tk;
+ }
+ else
+ {
+ if (!md_cursor_to_token(*cursor, &token))
+ return false;
+ }
+
+#ifdef DNMD_PORTABLE_PDB
+ {
+ uint32_t table_row = RidFromToken(token);
+ mdtable_id_t table_id = ExtractTokenType(token);
+ if (table_id < mdtid_FirstPdb)
+ {
+ if (!update_referenced_type_system_table_row_count(acxt.table->cxt, table_id, table_row))
+ return false;
+ }
+ }
+#endif
+
+ uint32_t raw;
+ if (acxt.col_details & mdtc_idx_table)
+ {
+ uint32_t table_row = RidFromToken(token);
+ mdtable_id_t table_id = ExtractTokenType(token);
+ // The raw value is the row index into the table that
+ // is embedded in the column details.
+ // Return an error if the provided token does not point to the right table.
+ if (ExtractTable(acxt.col_details) != table_id)
+ return false;
+ raw = table_row;
+ }
+ else
+ {
+ assert(acxt.col_details & mdtc_idx_coded);
+ if (!compose_coded_index(token, acxt.col_details, &raw))
+ return false;
+ }
+
+ if (!write_column_data(&acxt, raw))
+ return false;
+
+ // If the column we are writing to is a key of a sorted column, then we need to validate that it is sorted correctly.
+ // We'll validate against the previous row here and then validate against the next row after we've written all of the columns that we will write.
+ if (key_idx != UINT8_MAX)
+ {
+ assert(keys != NULL && key_idx < key_count);
+ mdcursor_t current_row = c;
+ bool success = md_cursor_next(¤t_row);
+ assert(success);
+ (void)success;
+ mdcursor_t prior_row = current_row;
+ if (md_cursor_move(&prior_row, -1) && !CursorNull(&prior_row))
+ {
+ // If we have a prior row, then we need to check if we're sorted with respect to it.
+ if (!is_row_sorted_with_next_row(keys, key_count, acxt.table->table_id, prior_row, current_row))
+ {
+ // If we're not sorted, then invalidate key_idx to avoid checking if we're sorted for future row writes.
+ // We won't go from unsorted to sorted.
+ acxt.table->is_sorted = false;
+ key_idx = UINT8_MAX;
+ }
+ }
+ }
+
+ // Validate that the last row we wrote is sorted with respect to any following rows.
+ if (key_idx != UINT8_MAX)
+ {
+ assert(keys != NULL && key_idx < key_count);
+ mdcursor_t current_row = c;
+ bool success = md_cursor_next(¤t_row);
+ assert(success);
+ (void)success;
+ mdcursor_t next_row = current_row;
+ if (md_cursor_next(&next_row) && !CursorEnd(&next_row))
+ {
+ // If we have a prior row, then we need to check if we're sorted with respect to it.
+ if (!is_row_sorted_with_next_row(keys, key_count, acxt.table->table_id, current_row, next_row))
+ {
+ // If we're not sorted, then invalidate key_idx to avoid checking if we're sorted for future row writes.
+ // We won't go from unsorted to sorted.
+ acxt.table->is_sorted = false;
+ key_idx = UINT8_MAX;
+ }
+ }
+ }
+
+ return true;
+}
+
+bool md_set_column_value_as_token(mdcursor_t c, col_index_t col, mdToken tk)
+{
+ return set_column_value_as_token_or_cursor(c, col_to_index(col, CursorTable(&c)), &tk, NULL);
+}
+
+bool md_set_column_value_as_cursor(mdcursor_t c, col_index_t col, mdcursor_t cursor)
+{
+ return set_column_value_as_token_or_cursor(c, col_to_index(col, CursorTable(&c)), NULL, &cursor);
+}
+
+bool md_set_column_value_as_constant(mdcursor_t c, col_index_t col_idx, uint32_t constant)
+{
+ access_cxt_t acxt;
+ if (!create_access_context(&c, col_idx, true, &acxt))
+ return false;
+
+ // If this isn't an constant column, then fail.
+ if (!(acxt.col_details & mdtc_constant))
+ return false;
+
+ uint8_t key_count = 0;
+ uint8_t key_idx = UINT8_MAX;
+ md_key_info_t const* keys = NULL;
+ // If we're editing already-existing rows, then we need to validate that we stay sorted.
+ // If we're in the middle of a row-add operation, we'll wait until the add is complete to validate.
+ if (acxt.table->is_sorted && !acxt.table->is_adding_new_row)
+ {
+ // If the table is sorted, then we need to validate that we stay sorted.
+ // We will not check here if a table goes from unsorted to sorted as that would require
+ // significantly more work to validate and is not a correctness issue.
+ key_count = get_table_keys(acxt.table->table_id, &keys);
+ for (uint8_t i = 0; i < key_count; i++)
+ {
+ if (keys[i].index == col_to_index(col_idx, acxt.table))
+ {
+ key_idx = i;
+ break;
+ }
+ }
+ }
+
+ if (!write_column_data(&acxt, constant))
+ return false;
+
+ // If the column we are writing to is a key of a sorted column, then we need to validate that it is sorted correctly.
+ // We'll validate against the previous row here and then validate against the next row after we've written all of the columns that we will write.
+ if (key_idx != UINT8_MAX)
+ {
+ assert(keys != NULL && key_idx < key_count);
+ mdcursor_t current_row = c;
+ bool success = md_cursor_next(¤t_row);
+ assert(success);
+ (void)success;
+ mdcursor_t prior_row = current_row;
+ if (md_cursor_move(&prior_row, -1) && !CursorNull(&prior_row))
+ {
+ // If we have a prior row, then we need to check if we're sorted with respect to it.
+ if (!is_row_sorted_with_next_row(keys, key_count, acxt.table->table_id, prior_row, current_row))
+ {
+ // If we're not sorted, then invalidate key_idx to avoid checking if we're sorted for future row writes.
+ // We won't go from unsorted to sorted.
+ acxt.table->is_sorted = false;
+ key_idx = UINT8_MAX;
+ }
+ }
+ }
+
+ // Validate that the last row we wrote is sorted with respect to any following rows.
+ if (key_idx != UINT8_MAX)
+ {
+ assert(keys != NULL && key_idx < key_count);
+ mdcursor_t current_row = c;
+ bool success = md_cursor_next(¤t_row);
+ assert(success);
+ (void)success;
+ mdcursor_t next_row = current_row;
+ if (md_cursor_move(&next_row, 1) && !CursorEnd(&next_row))
+ {
+ // If we have a prior row, then we need to check if we're sorted with respect to it.
+ if (!is_row_sorted_with_next_row(keys, key_count, acxt.table->table_id, current_row, next_row))
+ {
+ // If we're not sorted, then invalidate key_idx to avoid checking if we're sorted for future row writes.
+ // We won't go from unsorted to sorted.
+ acxt.table->is_sorted = false;
+ key_idx = UINT8_MAX;
+ }
+ }
+ }
+
+ return true;
+}
+
+#ifdef DEBUG_COLUMN_SORTING
+static void validate_column_is_not_key(mdtable_t const* table, col_index_t col_idx)
+{
+ md_key_info_t const* keys = NULL;
+ uint8_t key_count = get_table_keys(table->table_id, &keys);
+ for (uint8_t i = 0; i < key_count; i++)
+ {
+ if (keys[i].index == col_to_index(col_idx, table))
+ assert(!"Sorted columns cannot be heap references");
+ }
+}
+#endif
+
+// Set a column value as an existing offset into a heap.
+bool set_column_value_as_heap_offset(mdcursor_t c, col_index_t col_idx, uint32_t offset)
+{
+ access_cxt_t acxt;
+ if (!create_access_context(&c, col_idx, true, &acxt))
+ return false;
+
+ // If this isn't a heap index column, then fail.
+ if (!(acxt.col_details & mdtc_idx_heap))
+ return false;
+
+ mdstream_t const* heap = get_heap_by_id(acxt.table->cxt, ExtractHeapType(acxt.col_details));
+ if (heap == NULL)
+ return false;
+
+#ifdef DEBUG_COLUMN_SORTING
+ validate_column_is_not_key(acxt.table, col_idx);
+#endif
+
+ if (!write_column_data(&acxt, offset))
+ return false;
+
+ return true;
+}
+
+bool md_set_column_value_as_utf8(mdcursor_t c, col_index_t col_idx, char const* str)
+{
+ access_cxt_t acxt;
+ if (!create_access_context(&c, col_idx, true, &acxt))
+ return false;
+
+ // If this isn't an constant column, then fail.
+ if (!(acxt.col_details & mdtc_hstring))
+ return false;
+
+#ifdef DEBUG_COLUMN_SORTING
+ validate_column_is_not_key(acxt.table, col_idx);
+#endif
+
+ uint32_t heap_offset;
+ heap_offset = add_to_string_heap(acxt.table->cxt, str);
+
+ if (heap_offset == 0 && str[0] != '\0')
+ return false;
+
+ if (!write_column_data(&acxt, heap_offset))
+ return false;
+
+ return true;
+}
+
+// TODO: These functions should not call set_column_value_as_heap_offset.
+bool md_set_column_value_as_blob(mdcursor_t c, col_index_t col_idx, uint8_t const* blob, uint32_t blob_len)
+{
+ access_cxt_t acxt;
+ if (!create_access_context(&c, col_idx, true, &acxt))
+ return false;
+
+ // If this isn't an constant column, then fail.
+ if (!(acxt.col_details & mdtc_hblob))
+ return false;
+
+#ifdef DEBUG_COLUMN_SORTING
+ validate_column_is_not_key(acxt.table, col_idx);
+#endif
+
+ uint32_t heap_offset = add_to_blob_heap(acxt.table->cxt, blob, blob_len);
+
+ if (heap_offset == 0 && blob_len > 0)
+ return false;
+
+ if (!write_column_data(&acxt, heap_offset))
+ return false;
+
+ return true;
+}
+
+bool md_set_column_value_as_guid(mdcursor_t c, col_index_t col_idx, mdguid_t guid)
+{
+ access_cxt_t acxt;
+ if (!create_access_context(&c, col_idx, true, &acxt))
+ return false;
+
+ // If this isn't an constant column, then fail.
+ if (!(acxt.col_details & mdtc_hguid))
+ return false;
+
+#ifdef DEBUG_COLUMN_SORTING
+ validate_column_is_not_key(acxt.table, col_idx);
+#endif
+
+ uint32_t index = add_to_guid_heap(acxt.table->cxt, guid);
+
+ if (index == 0 && memcmp(&guid, &empty_guid, sizeof(mdguid_t)) != 0)
+ return false;
+
+ return set_column_value_as_heap_offset(c, col_idx, index);
+}
+
+bool md_set_column_value_as_userstring(mdcursor_t c, col_index_t col_idx, char16_t const* userstring)
+{
+ access_cxt_t acxt;
+ if (!create_access_context(&c, col_idx, true, &acxt))
+ return false;
+
+ // If this isn't an constant column, then fail.
+ if (!(acxt.col_details & mdtc_hus))
+ return false;
+
+#ifdef DEBUG_COLUMN_SORTING
+ validate_column_is_not_key(acxt.table, col_idx);
+#endif
+
+ uint32_t index = add_to_user_string_heap(CursorTable(&c)->cxt, userstring);
+
+ if (index == 0 && userstring[0] != 0)
+ return false;
+
+ if (!write_column_data(&acxt, index))
+ return false;
+
+ return true;
+}
+
+int32_t update_shifted_row_references(mdcursor_t* c, uint32_t count, uint8_t col_index, mdtable_id_t updated_table, uint32_t original_starting_table_index, uint32_t new_starting_table_index)
+{
+ assert(c != NULL);
+ col_index_t col = index_to_col(col_index, CursorTable(c)->table_id);
+
+ // If this isn't an table or coded index column, then fail.
+ if (!(CursorTable(c)->column_details[col_index] & (mdtc_idx_table | mdtc_idx_coded)))
+ return -1;
+
+ int32_t diff = (int32_t)(new_starting_table_index - original_starting_table_index);
+
+ for (uint32_t i = 0; i < count; i++, md_cursor_next(c))
+ {
+ mdToken tk;
+ if (!md_get_column_value_as_token(*c, col, &tk))
+ return -1;
+
+ if ((mdtable_id_t)ExtractTokenType(tk) == updated_table)
+ {
+ uint32_t rid = RidFromToken(tk);
+ if (rid >= original_starting_table_index)
+ {
+ rid += diff;
+ tk = TokenFromRid(rid, CreateTokenType(updated_table));
+ if (!md_set_column_value_as_token(*c, col, tk))
+ return -1;
+ }
+ }
+ }
+
+ return count;
+}
+
+static bool col_points_to_list(mdcursor_t* c, col_index_t col_index)
+{
+ assert(c != NULL);
+
+ switch (CursorTable(c)->table_id)
+ {
+ case mdtid_TypeDef:
+ return col_index == mdtTypeDef_FieldList || col_index == mdtTypeDef_MethodList;
+ case mdtid_PropertyMap:
+ return col_index == mdtPropertyMap_PropertyList;
+ case mdtid_EventMap:
+ return col_index == mdtEventMap_EventList;
+ case mdtid_MethodDef:
+ return col_index == mdtMethodDef_ParamList;
+#ifdef DNMD_PORTABLE_PDB
+ case mdtid_LocalScope:
+ return col_index == mdtLocalScope_VariableList || col_index == mdtLocalScope_ConstantList;
+#endif // DNMD_PORTABLE_PDB
+ }
+ return false;
+}
+
+static bool copy_cursor_column(mdcursor_t dest, mdcursor_t src, col_index_t idx)
+{
+ uint32_t column_value;
+ mdtable_t* table = CursorTable(&src);
+ mdtable_t* dest_table = CursorTable(&dest);
+ switch (table->column_details[idx] & mdtc_categorymask)
+ {
+ case mdtc_constant:
+ if (!md_get_column_value_as_constant(src, idx, &column_value))
+ return false;
+ break;
+ case mdtc_idx_coded:
+ case mdtc_idx_table:
+ if (!md_get_column_value_as_token(src, idx, &column_value))
+ return false;
+ break;
+ case mdtc_idx_heap:
+ if (!get_column_value_as_heap_offset(src, idx, &column_value))
+ return false;
+ break;
+ default:
+ assert(!"Unknown category");
+ return false;
+ }
+
+ switch (dest_table->column_details[idx] & mdtc_categorymask)
+ {
+ case mdtc_constant:
+ if (!md_set_column_value_as_constant(dest, idx, column_value))
+ return false;
+ break;
+ case mdtc_idx_coded:
+ case mdtc_idx_table:
+ if (!md_set_column_value_as_token(dest, idx, column_value))
+ return false;
+ break;
+ case mdtc_idx_heap:
+ if (set_column_value_as_heap_offset(dest, idx, column_value))
+ return false;
+ break;
+ default:
+ assert(!"Unknown category");
+ return false;
+ }
+ return true;
+}
+
+static bool set_column_as_end_of_table_cursor(mdcursor_t c, col_index_t col_idx)
+{
+ mdtable_t* table = CursorTable(&c);
+ assert((table->column_details[col_to_index(col_idx, table)] & mdtc_categorymask) == mdtc_idx_table);
+ mdtable_id_t target_table_id = ExtractTable(table->column_details[col_to_index(col_idx, table)]);
+ mdtable_t* target_table = &table->cxt->tables[target_table_id];
+
+ mdcursor_t end_of_table;
+ if (target_table->cxt == NULL)
+ {
+ if (!initialize_new_table_details(table->cxt, target_table_id, target_table))
+ {
+ return false;
+ }
+ end_of_table = create_cursor(target_table, 0);
+ }
+ else
+ {
+ end_of_table = create_cursor(target_table, target_table->row_count + 1);
+ }
+
+ return md_set_column_value_as_cursor(c, col_idx, end_of_table);
+}
+
+static bool initialize_list_columns(mdcursor_t c)
+{
+ // Initialize list columns to one-past the end of the target table.
+ mdtable_t* table = CursorTable(&c);
+ switch (table->table_id)
+ {
+ case mdtid_TypeDef:
+ return set_column_as_end_of_table_cursor(c, mdtTypeDef_FieldList)
+ && set_column_as_end_of_table_cursor(c, mdtTypeDef_MethodList);
+ break;
+ case mdtid_MethodDef:
+ return set_column_as_end_of_table_cursor(c, mdtMethodDef_ParamList);
+ case mdtid_PropertyMap:
+ return set_column_as_end_of_table_cursor(c, mdtPropertyMap_PropertyList);
+ case mdtid_EventMap:
+ return set_column_as_end_of_table_cursor(c, mdtEventMap_EventList);
+ default:
+ break;
+ }
+ return true;
+}
+
+static bool insert_row_cursor_relative(mdcursor_t row, int32_t offset, mdcursor_t* new_row)
+{
+ mdtable_t* table = CursorTable(&row);
+ if (table->cxt == NULL) // We can't turn an insert into a "create table" operation.
+ return false;
+
+ // We don't allow inserting in the middle of tables that have indirection tables.
+ // Inserting into these tables should use md_add_new_row_to_parent_list instead.
+ mdtable_id_t indirect_table_maybe = get_corresponding_indirection_table(table->table_id);
+ if (indirect_table_maybe != mdtid_Unused)
+ return false;
+
+ // We can't insert a row before the first row of a table.
+ assert(offset + (int64_t)CursorRow(&row) >= 0);
+
+ uint32_t new_row_index = CursorRow(&row) + offset;
+
+ if (new_row_index > table->row_count + 1)
+ return false;
+
+ if (!insert_row_into_table(table->cxt, table->table_id, new_row_index, new_row))
+ return false;
+
+ // Now that we have this row, we need to initialize the list columns to the correct values that represent a zero-length list.
+ // If we've inserted a row at the end of the table, we'll initalize the columns to the end-of-table cursor.
+ // If we've inserted a row in the middle of the table, we'll copy the next row's list column values.
+ mdcursor_t next_row = *new_row;
+ if (!md_cursor_next(&next_row) || CursorEnd(&next_row))
+ {
+ return initialize_list_columns(*new_row);
+ }
+
+ for (uint8_t i = 0; i < table->column_count; i++)
+ {
+ col_index_t col = index_to_col(i, table->table_id);
+ if (col_points_to_list(&next_row, col))
+ {
+ if (!copy_cursor_column(*new_row, next_row, col))
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool md_insert_row_before(mdcursor_t row, mdcursor_t* new_row)
+{
+ // Inserting a row before a given cursor means that the new row will point to the same
+ // target as the given cursor.
+ return insert_row_cursor_relative(row, 0, new_row);
+}
+
+bool md_insert_row_after(mdcursor_t row, mdcursor_t* new_row)
+{
+ return insert_row_cursor_relative(row, 1, new_row);
+}
+
+// Append to the end of the table.
+// The table must already exist.
+static bool append_row(mdtable_t* table, mdcursor_t* new_row)
+{
+ assert(table->cxt != NULL);
+
+ if (!insert_row_into_table(table->cxt, table->table_id, table->row_count + 1, new_row))
+ return false;
+
+ return initialize_list_columns(*new_row);
+}
+
+bool md_append_row(mdhandle_t handle, mdtable_id_t table_id, mdcursor_t* new_row)
+{
+ // We don't allow directly appending to tables that have indirection tables.
+ // Inserting into these tables should use md_add_new_row_to_parent_list instead.
+ mdtable_id_t indirect_table_maybe = get_corresponding_indirection_table(table_id);
+ if (indirect_table_maybe != mdtid_Unused)
+ return false;
+
+ mdcxt_t* cxt = extract_mdcxt(handle);
+
+ if (table_id < mdtid_First || table_id > mdtid_End)
+ return false;
+
+ mdtable_t* table = &cxt->tables[table_id];
+
+ if (table->cxt == NULL)
+ {
+ // We should never be allocating a new indirection table through md_append_row.
+ // We should be allocating it in md_add_new_row_to_parent_table when necessary.
+ assert(!table_is_indirect_table(table_id));
+ if (!allocate_new_table(cxt, table_id))
+ return false;
+ }
+
+ return append_row(table, new_row);
+}
+
+static bool add_new_row_to_list(mdcursor_t list_owner, col_index_t list_col, mdcursor_t row_to_insert_before, mdcursor_t* new_row)
+{
+ assert(col_points_to_list(&list_owner, list_col));
+ // Get the range of rows already in the parent's child list.
+ // If we have an indirection table already, we will get back a range in the indirection table here.
+ mdcursor_t range;
+ uint32_t count;
+ if (!md_get_column_value_as_range(list_owner, list_col, &range, &count))
+ return false;
+
+ // Assert that the insertion location is in our range or points to the first row of the next range.
+ // For a zero-length range, row_to_insert_before will be the first row of the next range, so we need to account for that.
+ assert(CursorTable(&range) == CursorTable(&row_to_insert_before));
+ assert(CursorRow(&range) <= CursorRow(&row_to_insert_before) && CursorRow(&row_to_insert_before) <= CursorRow(&range) + (count == 0 ? 1 : count));
+
+ mdcursor_t target_row;
+ // If the range is in an indirection table, we'll normalize our insert to the actual target table.
+ if (!md_resolve_indirect_cursor(row_to_insert_before, &target_row))
+ return false;
+
+ if (CursorTable(&row_to_insert_before) != CursorTable(&target_row))
+ {
+ // In this case, we resolved the indirect cursor, so we must have an indirection table.
+ // We need to append to the target table and then insert a new row in the requested place into the indirection table.
+ if (!append_row(CursorTable(&target_row), new_row))
+ return false;
+
+ mdcursor_t new_indirection_row;
+ if (!md_insert_row_before(row_to_insert_before, &new_indirection_row))
+ return false;
+
+ if (!md_set_column_value_as_cursor(new_indirection_row, index_to_col(0, CursorTable(&row_to_insert_before)->table_id), *new_row))
+ return false;
+
+ if (count == 0 || CursorRow(&range) == CursorRow(&row_to_insert_before))
+ {
+ // If our original count was zero, then this is the first element in the list for this parent.
+ // If the start of our range is the same as the row we're inserting before, then we're inserting at the start of the list.
+ // In both of these cases, we need to update the parent's row column to point to the newly inserted row.
+ // Otherwise, this element would be associated with the entry before the parent row.
+ if (!md_set_column_value_as_cursor(list_owner, list_col, new_indirection_row))
+ return false;
+ }
+
+ md_commit_row_add(new_indirection_row);
+ return true;
+ }
+ else if (CursorEnd(&row_to_insert_before))
+ {
+ // In this case, we don't have an indirection table
+ // and we don't need to create one as we're inserting a row at the end of the table.
+ if (!append_row(CursorTable(&row_to_insert_before), new_row))
+ return false;
+
+ if (count == 0)
+ {
+ // If our original count was zero, then this is the first element in the list for this parent.
+ // We need to update the parent's row column to point to the newly inserted row.
+ // Otherwise, this element would be associated with the entry before the parent row.
+ // We also need to traverse all rows before this row that have the current value of this column,
+ // otherwise the list will be inconsistent.
+ mdcursor_t parent_row = list_owner;
+ mdcursor_t current_cursor_value;
+ if (!md_get_column_value_as_cursor(list_owner, list_col, ¤t_cursor_value))
+ return false;
+
+ while (md_cursor_move(&parent_row, -1))
+ {
+ mdcursor_t prev_cursor_value;
+ if (!md_get_column_value_as_cursor(parent_row, list_col, &prev_cursor_value))
+ return false;
+
+ if (CursorRow(&prev_cursor_value) != CursorRow(¤t_cursor_value))
+ {
+ // We found the last cursor value that doesn't match the current value.
+ // Go back to it.
+ md_cursor_next(&parent_row);
+ break;
+ }
+ }
+
+ for (; CursorRow(&parent_row) <= CursorRow(&list_owner); md_cursor_next(&parent_row))
+ {
+ if (!md_set_column_value_as_cursor(parent_row, list_col, *new_row))
+ return false;
+ }
+ }
+ return true;
+ }
+
+ // In this case, we don't have an indirection table.
+ // We need to create one since the target column is a list column.
+ mdtable_t* target_table = CursorTable(&target_row);
+ mdtable_id_t indirect_table = get_corresponding_indirection_table(target_table->table_id);
+ assert(indirect_table != mdtid_Unused);
+
+ if (!create_and_fill_indirect_table(target_table->cxt, target_table->table_id, indirect_table))
+ return false;
+
+ mdtcol_t* list_col_details = &CursorTable(&list_owner)->column_details[col_to_index(list_col, CursorTable(&list_owner))];
+ // Clear the target column of the table index, so that we can set it to the new indirection table.
+ *list_col_details = (*list_col_details & ~mdtc_timask) | InsertTable(indirect_table);
+
+ // Now that we have created an indirection table, we can insert the row into it.
+ // We need to change our "row to insert before" cursor to point at the indirection table.
+ // Because we just created the indirection table, then we know that each row in the target table corresponds to the same row index
+ // in the indirection table.
+ row_to_insert_before = create_cursor(&target_table->cxt->tables[indirect_table], CursorRow(&range));
+
+ // Now, we can call back into ourselves to do the actual insert.
+ return add_new_row_to_list(list_owner, list_col, row_to_insert_before, new_row);
+}
+
+bool md_add_new_row_to_list(mdcursor_t list_owner, col_index_t list_col, mdcursor_t* new_row)
+{
+ if (!col_points_to_list(&list_owner, list_col))
+ return false;
+
+ // Get the range of rows already in the parent's child list.
+ // If we have an indirection table already, we will get back a range in the indirection table here.
+ mdcursor_t existing_range;
+ uint32_t count;
+ if (!md_get_column_value_as_range(list_owner, list_col, &existing_range, &count))
+ return false;
+
+ if (CursorTable(&existing_range)->cxt == NULL)
+ {
+ // If we don't have a table to add the row to, create one.
+ if (!allocate_new_table(CursorTable(&list_owner)->cxt, CursorTable(&existing_range)->table_id))
+ return false;
+
+ // Now that we have a table, we recreate the "existing range" cursor as the one-past-the-end cursor
+ // This allows us to use the remaining logic unchanged.
+ existing_range = create_cursor(CursorTable(&existing_range), 1);
+ }
+
+ mdcursor_t row_after_range = existing_range;
+ // Move the cursor just past the end of the range. We'll insert a row at the end of the range.
+ if (!md_cursor_move(&row_after_range, count))
+ return false;
+
+ return add_new_row_to_list(list_owner, list_col, row_after_range, new_row);
+}
+
+bool md_add_new_row_to_sorted_list(mdcursor_t list_owner, col_index_t list_col, col_index_t sort_order_col, uint32_t sort_col_value, mdcursor_t* new_row)
+{
+ if (!col_points_to_list(&list_owner, list_col))
+ return false;
+
+ // Get the range of rows already in the parent's child list.
+ // If we have an indirection table already, we will get back a range in the indirection table here.
+ mdcursor_t existing_range;
+ uint32_t count;
+ if (!md_get_column_value_as_range(list_owner, list_col, &existing_range, &count))
+ return false;
+
+ if (CursorTable(&existing_range)->cxt == NULL)
+ {
+ // If we don't have a table to add the row to, create one.
+ if (!allocate_new_table(CursorTable(&list_owner)->cxt, CursorTable(&existing_range)->table_id))
+ return false;
+
+ // Now that we have a table, we recreate the "existing range" cursor as the one-past-the-end cursor
+ // This allows us to use the remaining logic unchanged.
+ existing_range = create_cursor(CursorTable(&existing_range), 1);
+ }
+
+ mdcursor_t row_to_insert_before = existing_range;
+ // Move the cursor to just past the end of the range. If we don't find a place in the middle that we need to insert the row,
+ // we'll insert it here.
+ if (!md_cursor_move(&row_to_insert_before, count))
+ return false;
+
+ // The existing list isn't empty, so we need to find the correct place to insert the new row.
+ if (count > 0)
+ {
+ // In most cases we will be inserting at the end of the list,
+ // so start searching there to make this a little faster.
+ mdcursor_t row_to_check = row_to_insert_before;
+
+ // Move our cursor to the last row in the list and move back one more row each iteration.
+ // This can't return false as we got to row_to_insert_before by moving forward at least one row.
+ for (; md_cursor_move(&row_to_check, -1) && CursorRow(&row_to_check) >= CursorRow(&existing_range);)
+ {
+ // If the range is in an indirection table, we need to normalize to the target table to
+ // get the sort column value.
+ mdcursor_t target_row;
+ if (!md_resolve_indirect_cursor(row_to_check, &target_row))
+ return false;
+
+ uint32_t current_sort_col_value;
+ if (!md_get_column_value_as_constant(target_row, sort_order_col, ¤t_sort_col_value))
+ return false;
+
+ if (current_sort_col_value <= sort_col_value)
+ {
+ // row_to_check is the first row with a sort order less than or equal to the new row.
+ // So we want to insert the new row after this row.
+ // Set row_to_insert_before to the next row to ensure we insert the new row after this row.
+ row_to_insert_before = row_to_check;
+ (void)md_cursor_next(&row_to_insert_before); // We got to row_to_insert_before by moving back from an existing row, so there must be a next row.
+ break;
+ }
+ }
+
+ // If we didn't find a row with a sort order less than or equal to the new row, we want to insert the new row at the beginning of the list.
+ // If our cursor is pointing at the first row, that means that our existing range starts at the first row.
+ if (CursorRow(&row_to_check) == 1 || CursorRow(&row_to_check) < CursorRow(&existing_range))
+ {
+ // We didn't find a row with a sort order less than or equal to the new row.
+ // So we want to insert the new row at the beginning of the list.
+ row_to_insert_before = existing_range;
+ }
+ }
+
+ if (!add_new_row_to_list(list_owner, list_col, row_to_insert_before, new_row))
+ return false;
+
+ // Now that we've added the new column to the list, set the sort order column to the provided value to
+ // ensure the sort is accurate.
+ if (!md_set_column_value_as_constant(*new_row, sort_order_col, sort_col_value))
+ return false;
+
+ return true;
+}
+
+bool copy_cursor(mdcursor_t dest, mdcursor_t src)
+{
+ mdtable_t* table = CursorTable(&src);
+ assert(table->column_count == CursorTable(&dest)->column_count);
+
+ for (uint8_t i = 0; i < table->column_count; i++)
+ {
+ col_index_t col = index_to_col(i, table->table_id);
+ // We don't want to copy over columns that point to lists in other tables.
+ // These columns have very particular behavior and are handled separately by
+ // direct manipulation in the other operations.
+ if (col_points_to_list(&src, col))
+ continue;
+
+ if (!copy_cursor_column(dest, src, col))
+ return false;
+ }
+
+ return true;
+}
+
+static bool validate_row_sorted_within_table(mdcursor_t row)
+{
+ mdtable_t* table = CursorTable(&row);
+ md_key_info_t const* keys;
+ uint8_t count_keys = get_table_keys(table->table_id, &keys);
+ assert(count_keys != 0); // We should only ever have a sorted table for a table with keys.
+
+ mdcursor_t prior_row = row;
+ if (md_cursor_move(&prior_row, -1) && !CursorNull(&prior_row))
+ {
+ if (!is_row_sorted_with_next_row(keys, count_keys, table->table_id, prior_row, row))
+ return false;
+ }
+ mdcursor_t next_row = row;
+ if (!md_cursor_next(&next_row) && !CursorEnd(&next_row))
+ {
+ if (!is_row_sorted_with_next_row(keys, count_keys, table->table_id, row, next_row))
+ return false;
+ }
+
+ return true;
+}
+
+void md_commit_row_add(mdcursor_t row)
+{
+ mdtable_t* table = CursorTable(&row);
+
+ // If this method is called with a zero-initialized cursor,
+ // no-op. This helps make the C++ helper md_added_row_t function more easily.
+ // This also allows users to call this method in all cases, even if the row-add fails.
+ if (table == NULL)
+ return;
+
+ assert(table->is_adding_new_row);
+
+ // If the table was previously sorted,
+ // validate that the current row is sorted with respect to the prior and following rows.
+ if (table->is_sorted)
+ {
+ table->is_sorted = validate_row_sorted_within_table(row);
+ }
+
+ table->is_adding_new_row = false;
+}
+
+bool sort_list_by_column(mdcursor_t parent, col_index_t list_col, col_index_t col)
+{
+ mdcursor_t range;
+ uint32_t count;
+ bool success = md_get_column_value_as_range(parent, list_col, &range, &count);
+ assert(success);
+ (void)success;
+
+ // A one element range is always sorted.
+ if (count == 1)
+ return true;
+
+ void* cursor_order_buffer = malloc((sizeof(mdcursor_t) + sizeof(int32_t)) * count);
+ if (cursor_order_buffer == NULL)
+ return false;
+
+ mdcursor_t* correct_cursor_order = cursor_order_buffer;
+ memset(correct_cursor_order, 0, sizeof(*correct_cursor_order) * count);
+ int32_t* correct_cursor_order_ids = (int32_t*)(((mdcursor_t*)cursor_order_buffer) + count);
+
+ bool need_to_update = false;
+ mdcursor_t list_item = range;
+ int32_t next_index = 0;
+ // Gather cursors to all of the rows in the list,
+ // and put them in the order specified by the column.
+ for (uint32_t i = 0; i < count; i++, md_cursor_next(&list_item))
+ {
+ mdcursor_t target;
+ if (!md_resolve_indirect_cursor(list_item, &target))
+ {
+ free(cursor_order_buffer);
+ return false;
+ }
+
+ uint32_t sequence_number;
+ if (!md_get_column_value_as_constant(target, col, &sequence_number))
+ {
+ free(cursor_order_buffer);
+ assert(!"Failed to read constant column from target cursor");
+ return false;
+ }
+
+ assert(CursorNull(&correct_cursor_order[next_index]));
+ correct_cursor_order[next_index] = target;
+ correct_cursor_order_ids[next_index] = sequence_number;
+
+ // Sequence ids need to be in ascending order to be sorted.
+ if (next_index > 0
+ && correct_cursor_order_ids[next_index - 1] > correct_cursor_order_ids[next_index])
+ {
+ // Do a simple insertion sort as we go.
+ // It's unlikely we'll need to sort,
+ // and even when we do, we'll likely be mostly sorted anyway.
+ for (uint32_t j = next_index - 1; j >= 0; --j)
+ {
+ if (correct_cursor_order_ids[j] > correct_cursor_order_ids[j + 1])
+ {
+ memmove(&correct_cursor_order[j], &correct_cursor_order[j + 1], sizeof(mdcursor_t) * (next_index - j));
+ memmove(&correct_cursor_order_ids[j], &correct_cursor_order_ids[j + 1], sizeof(int32_t) * (next_index - j));
+ correct_cursor_order[j] = target;
+ correct_cursor_order_ids[j] = sequence_number;
+ }
+ }
+ need_to_update = true;
+ }
+
+ ++next_index;
+ }
+
+ // If we are already sorted, we're done.
+ if (!need_to_update)
+ {
+ free(cursor_order_buffer);
+ return true;
+ }
+
+ // If we don't have an indirection table, we need to create one now.
+ mdtable_t* table = CursorTable(&range);
+ if (!table_is_indirect_table(table->table_id))
+ {
+ mdtable_id_t indirect_table = get_corresponding_indirection_table(table->table_id);
+ assert(indirect_table != mdtid_Unused);
+
+ if (!create_and_fill_indirect_table(table->cxt, table->table_id, indirect_table))
+ {
+ free(cursor_order_buffer);
+ assert(!"Failed to create indirection table");
+ return false;
+ }
+
+ mdtcol_t* list_col_details = &CursorTable(&parent)->column_details[col_to_index(list_col, table)];
+ // Clear the target column of the table index, so that we can set it to the new indirection table.
+ *list_col_details = (*list_col_details & ~mdtc_timask) | InsertTable(indirect_table);
+
+ // Now update c to point to the same row in the new indirection table.
+ range = create_cursor(&table->cxt->tables[indirect_table], CursorRow(&range));
+ }
+
+ col_index_t indirect_col = index_to_col(0, CursorTable(&range)->table_id);
+
+ mdcursor_t to_update = range;
+ for (uint32_t i = 0; i < count; i++)
+ {
+ if (!md_set_column_value_as_cursor(to_update, indirect_col, correct_cursor_order[i]))
+ {
+ free(cursor_order_buffer);
+ return false;
+ }
+
+ md_cursor_next(&to_update);
+ }
+
+ free(cursor_order_buffer);
+ return true;
+}
diff --git a/src/native/dnmd/src/inc/dnmd.h b/src/native/dnmd/src/inc/dnmd.h
new file mode 100644
index 0000000000000..bc156ab457caf
--- /dev/null
+++ b/src/native/dnmd/src/inc/dnmd.h
@@ -0,0 +1,556 @@
+#ifndef _SRC_INC_DNMD_H_
+#define _SRC_INC_DNMD_H_
+
+#include
+#include
+#include
+// MacOS doesn't have uchar.h
+#if defined(__has_include)
+#if __has_include()
+#include
+#elif !defined(__cplusplus)
+// When uchar.h isn't available and we're in C, define char16_t as per the C standard.
+typedef uint_least16_t char16_t;
+#endif
+
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef uint32_t mdToken;
+
+typedef struct mdguid__
+{
+ uint32_t data1;
+ uint16_t data2;
+ uint16_t data3;
+ uint8_t data4[8];
+} mdguid_t;
+
+typedef void* mdhandle_t;
+
+// Create a metadata handle that can be used to parse and modify the supplied metadata.
+//
+// The supplied data is expected to be unmoved and available until all
+// handles created with the data have been destroyed.
+// If modifications are made, the data will not be updated in place.
+bool md_create_handle(void const* data, size_t data_len, mdhandle_t* handle);
+
+// Create a new metadata handle for a new image.
+// Returns a handle for the new image, or NULL if the handle could not be created.
+// The image will always be in the v1.1 ECMA-355 metadata format,
+// use the "v4.0.30319" version string,
+// and have an MVID of all zeros.
+mdhandle_t md_create_new_handle();
+
+#ifdef DNMD_PORTABLE_PDB
+// Create a new metadata handle for a new Portable PDB image.
+// Returns a handle for the new image, or NULL if the handle could not be created.
+// The image will always be in the v1.1 metadata format
+// and use the "PDB v1.0" version string.
+mdhandle_t md_create_new_pdb_handle();
+#endif // DNMD_PORTABLE_PDB
+
+// Apply delta data to the current metadata.
+bool md_apply_delta(mdhandle_t handle, mdhandle_t delta_handle);
+
+// Destroy the metadata handle and free all associated memory.
+void md_destroy_handle(mdhandle_t handle);
+
+// Validate the metadata associated with the handle.
+bool md_validate(mdhandle_t handle);
+
+// Write all tables to stdout.
+// Set table_id to '-1' to print out all tables.
+bool md_dump_tables(mdhandle_t handle, int32_t table_id);
+
+char const* md_get_version_string(mdhandle_t handle);
+
+//
+// All tables possible in ECMA-335
+//
+typedef enum
+{
+ mdtid_Unused = -1,
+ mdtid_Module = 0x0,
+ mdtid_TypeRef = 0x01,
+ mdtid_TypeDef = 0x02,
+ mdtid_FieldPtr = 0x03,
+ mdtid_Field = 0x04,
+ mdtid_MethodPtr = 0x05,
+ mdtid_MethodDef = 0x06,
+ mdtid_ParamPtr = 0x07,
+ mdtid_Param = 0x08,
+ mdtid_InterfaceImpl = 0x09,
+ mdtid_MemberRef = 0x0a,
+ mdtid_Constant = 0x0b,
+ mdtid_CustomAttribute = 0x0c,
+ mdtid_FieldMarshal = 0x0d,
+ mdtid_DeclSecurity = 0x0e,
+ mdtid_ClassLayout = 0x0f,
+ mdtid_FieldLayout = 0x10,
+ mdtid_StandAloneSig = 0x11,
+ mdtid_EventMap = 0x12,
+ mdtid_EventPtr = 0x13,
+ mdtid_Event = 0x14,
+ mdtid_PropertyMap = 0x15,
+ mdtid_PropertyPtr = 0x16,
+ mdtid_Property = 0x17,
+ mdtid_MethodSemantics = 0x18,
+ mdtid_MethodImpl = 0x19,
+ mdtid_ModuleRef = 0x1a,
+ mdtid_TypeSpec = 0x1b,
+ mdtid_ImplMap = 0x1c,
+ mdtid_FieldRva = 0x1d,
+ mdtid_ENCLog = 0x1e,
+ mdtid_ENCMap = 0x1f,
+ mdtid_Assembly = 0x20,
+ mdtid_AssemblyProcessor = 0x21,
+ mdtid_AssemblyOS = 0x22,
+ mdtid_AssemblyRef = 0x23,
+ mdtid_AssemblyRefProcessor = 0x24,
+ mdtid_AssemblyRefOS = 0x25,
+ mdtid_File = 0x26,
+ mdtid_ExportedType = 0x27,
+ mdtid_ManifestResource = 0x28,
+ mdtid_NestedClass = 0x29,
+ mdtid_GenericParam = 0x2a,
+ mdtid_MethodSpec = 0x2b,
+ mdtid_GenericParamConstraint = 0x2c,
+
+#ifdef DNMD_PORTABLE_PDB
+ // https://github.com/dotnet/runtime/blob/main/docs/design/specs/PortablePdb-Metadata.md
+ mdtid_Document = 0x30,
+ mdtid_MethodDebugInformation = 0x31,
+ mdtid_LocalScope = 0x32,
+ mdtid_LocalVariable = 0x33,
+ mdtid_LocalConstant = 0x34,
+ mdtid_ImportScope = 0x35,
+ mdtid_StateMachineMethod = 0x36,
+ mdtid_CustomDebugInformation = 0x37,
+#endif // DNMD_PORTABLE_PDB
+
+ mdtid_End,
+ mdtid_First = mdtid_Module,
+#ifdef DNMD_PORTABLE_PDB
+ mdtid_FirstPdb = mdtid_Document,
+#endif // DNMD_PORTABLE_PDB
+} mdtable_id_t;
+
+// Table cursor definition
+typedef struct mdcursor__
+{
+ intptr_t _reserved1;
+ intptr_t _reserved2;
+} mdcursor_t;
+
+// Create a cursor to the first row in a table.
+bool md_create_cursor(mdhandle_t handle, mdtable_id_t table_id, mdcursor_t* cursor, uint32_t* count);
+
+// Move the cursor +/- number of rows.
+bool md_cursor_move(mdcursor_t* c, int32_t delta);
+
+// Move to the next row.
+bool md_cursor_next(mdcursor_t* c);
+
+// Convert between a token and location in metadata tables.
+bool md_token_to_cursor(mdhandle_t handle, mdToken tk, mdcursor_t* c);
+bool md_cursor_to_token(mdcursor_t c, mdToken* tk);
+mdhandle_t md_extract_handle_from_cursor(mdcursor_t c);
+
+// Walk the #US heap. The initial value should be set to 0 or
+// a valid offset into the #US heap - see RidFromToken in corhdr.h.
+typedef intptr_t mduserstringcursor_t;
+
+typedef struct mduserstring__
+{
+ char16_t const* str;
+ uint32_t str_bytes;
+ uint8_t final_byte;
+} mduserstring_t;
+bool md_walk_user_string_heap(mdhandle_t handle, mduserstringcursor_t* cursor, mduserstring_t* str, uint32_t* offset);
+
+// Define to help debug table indexing
+//#define DEBUG_TABLE_COLUMN_LOOKUP
+
+// The MDTABLE_COLUMN macro constructs a table/column ID enumeration.
+//
+// An example (release build):
+// MDTABLE_COLUMN(Assembly, HashAlgId, 0) => mdtAssembly_HashAlgId = 0
+//
+#if defined(DEBUG_TABLE_COLUMN_LOOKUP) && !defined(MDTABLES_BUILD)
+#define MDTABLE_COLUMN(table, col, value) mdt ## table ## _ ## col = ((mdtid_ ## table << 8) | (value))
+#else
+#define MDTABLE_COLUMN(table, col, value) mdt ## table ## _ ## col = (value)
+#endif // DEBUG_TABLE_COLUMN_LOOKUP && !MDTABLES_BUILD
+
+#define MDTABLE_COLUMN_COUNT(table, value) mdt ## table ## _ ## ColCount = (value)
+
+//
+// Column indexes for tables
+//
+typedef enum
+{
+ MDTABLE_COLUMN(Module, Generation, 0),
+ MDTABLE_COLUMN(Module, Name, 1),
+ MDTABLE_COLUMN(Module, Mvid, 2),
+ MDTABLE_COLUMN(Module, EncId, 3),
+ MDTABLE_COLUMN(Module, EncBaseId, 4),
+ MDTABLE_COLUMN_COUNT(Module, 5),
+
+ MDTABLE_COLUMN(TypeRef, ResolutionScope, 0),
+ MDTABLE_COLUMN(TypeRef, TypeName, 1),
+ MDTABLE_COLUMN(TypeRef, TypeNamespace, 2),
+ MDTABLE_COLUMN_COUNT(TypeRef, 3),
+
+ MDTABLE_COLUMN(TypeDef, Flags, 0),
+ MDTABLE_COLUMN(TypeDef, TypeName, 1),
+ MDTABLE_COLUMN(TypeDef, TypeNamespace, 2),
+ MDTABLE_COLUMN(TypeDef, Extends, 3),
+ MDTABLE_COLUMN(TypeDef, FieldList, 4),
+ MDTABLE_COLUMN(TypeDef, MethodList, 5),
+ MDTABLE_COLUMN_COUNT(TypeDef, 6),
+
+ MDTABLE_COLUMN(FieldPtr, Field, 0),
+ MDTABLE_COLUMN_COUNT(FieldPtr, 1),
+
+ MDTABLE_COLUMN(Field, Flags, 0),
+ MDTABLE_COLUMN(Field, Name, 1),
+ MDTABLE_COLUMN(Field, Signature, 2),
+ MDTABLE_COLUMN_COUNT(Field, 3),
+
+ MDTABLE_COLUMN(MethodPtr, Method, 0),
+ MDTABLE_COLUMN_COUNT(MethodPtr, 1),
+
+ MDTABLE_COLUMN(MethodDef, Rva, 0),
+ MDTABLE_COLUMN(MethodDef, ImplFlags, 1),
+ MDTABLE_COLUMN(MethodDef, Flags, 2),
+ MDTABLE_COLUMN(MethodDef, Name, 3),
+ MDTABLE_COLUMN(MethodDef, Signature, 4),
+ MDTABLE_COLUMN(MethodDef, ParamList, 5),
+ MDTABLE_COLUMN_COUNT(MethodDef, 6),
+
+ MDTABLE_COLUMN(ParamPtr, Param, 0),
+ MDTABLE_COLUMN_COUNT(ParamPtr, 1),
+
+ MDTABLE_COLUMN(Param, Flags, 0),
+ MDTABLE_COLUMN(Param, Sequence, 1),
+ MDTABLE_COLUMN(Param, Name, 2),
+ MDTABLE_COLUMN_COUNT(Param, 3),
+
+ MDTABLE_COLUMN(InterfaceImpl, Class, 0),
+ MDTABLE_COLUMN(InterfaceImpl, Interface, 1),
+ MDTABLE_COLUMN_COUNT(InterfaceImpl, 2),
+
+ MDTABLE_COLUMN(MemberRef, Class, 0),
+ MDTABLE_COLUMN(MemberRef, Name, 1),
+ MDTABLE_COLUMN(MemberRef, Signature, 2),
+ MDTABLE_COLUMN_COUNT(MemberRef, 3),
+
+ MDTABLE_COLUMN(Constant, Type, 0),
+ MDTABLE_COLUMN(Constant, Parent, 1),
+ MDTABLE_COLUMN(Constant, Value, 2),
+ MDTABLE_COLUMN_COUNT(Constant, 3),
+
+ MDTABLE_COLUMN(CustomAttribute, Parent, 0),
+ MDTABLE_COLUMN(CustomAttribute, Type, 1),
+ MDTABLE_COLUMN(CustomAttribute, Value, 2),
+ MDTABLE_COLUMN_COUNT(CustomAttribute, 3),
+
+ MDTABLE_COLUMN(FieldMarshal, Parent, 0),
+ MDTABLE_COLUMN(FieldMarshal, NativeType, 1),
+ MDTABLE_COLUMN_COUNT(FieldMarshal, 2),
+
+ MDTABLE_COLUMN(DeclSecurity, Action, 0),
+ MDTABLE_COLUMN(DeclSecurity, Parent, 1),
+ MDTABLE_COLUMN(DeclSecurity, PermissionSet, 2),
+ MDTABLE_COLUMN_COUNT(DeclSecurity, 3),
+
+ MDTABLE_COLUMN(ClassLayout, PackingSize, 0),
+ MDTABLE_COLUMN(ClassLayout, ClassSize, 1),
+ MDTABLE_COLUMN(ClassLayout, Parent, 2),
+ MDTABLE_COLUMN_COUNT(ClassLayout, 3),
+
+ MDTABLE_COLUMN(FieldLayout, Offset, 0),
+ MDTABLE_COLUMN(FieldLayout, Field, 1),
+ MDTABLE_COLUMN_COUNT(FieldLayout, 2),
+
+ MDTABLE_COLUMN(StandAloneSig, Signature, 0),
+ MDTABLE_COLUMN_COUNT(StandAloneSig, 1),
+
+ MDTABLE_COLUMN(EventMap, Parent, 0),
+ MDTABLE_COLUMN(EventMap, EventList, 1),
+ MDTABLE_COLUMN_COUNT(EventMap, 2),
+
+ MDTABLE_COLUMN(EventPtr, Event, 0),
+ MDTABLE_COLUMN_COUNT(EventPtr, 1),
+
+ MDTABLE_COLUMN(Event, EventFlags, 0),
+ MDTABLE_COLUMN(Event, Name, 1),
+ MDTABLE_COLUMN(Event, EventType, 2),
+ MDTABLE_COLUMN_COUNT(Event, 3),
+
+ MDTABLE_COLUMN(PropertyMap, Parent, 0),
+ MDTABLE_COLUMN(PropertyMap, PropertyList, 1),
+ MDTABLE_COLUMN_COUNT(PropertyMap, 2),
+
+ MDTABLE_COLUMN(PropertyPtr, Property, 0),
+ MDTABLE_COLUMN_COUNT(PropertyPtr, 1),
+
+ MDTABLE_COLUMN(Property, Flags, 0),
+ MDTABLE_COLUMN(Property, Name, 1),
+ MDTABLE_COLUMN(Property, Type, 2),
+ MDTABLE_COLUMN_COUNT(Property, 3),
+
+ MDTABLE_COLUMN(MethodSemantics, Semantics, 0),
+ MDTABLE_COLUMN(MethodSemantics, Method, 1),
+ MDTABLE_COLUMN(MethodSemantics, Association, 2),
+ MDTABLE_COLUMN_COUNT(MethodSemantics, 3),
+
+ MDTABLE_COLUMN(MethodImpl, Class, 0),
+ MDTABLE_COLUMN(MethodImpl, MethodBody, 1),
+ MDTABLE_COLUMN(MethodImpl, MethodDeclaration, 2),
+ MDTABLE_COLUMN_COUNT(MethodImpl, 3),
+
+ MDTABLE_COLUMN(ModuleRef, Name, 0),
+ MDTABLE_COLUMN_COUNT(ModuleRef, 1),
+
+ MDTABLE_COLUMN(TypeSpec, Signature, 0),
+ MDTABLE_COLUMN_COUNT(TypeSpec, 1),
+
+ MDTABLE_COLUMN(ImplMap, MappingFlags, 0),
+ MDTABLE_COLUMN(ImplMap, MemberForwarded, 1),
+ MDTABLE_COLUMN(ImplMap, ImportName, 2),
+ MDTABLE_COLUMN(ImplMap, ImportScope, 3),
+ MDTABLE_COLUMN_COUNT(ImplMap, 4),
+
+ MDTABLE_COLUMN(FieldRva, Rva, 0),
+ MDTABLE_COLUMN(FieldRva, Field, 1),
+ MDTABLE_COLUMN_COUNT(FieldRva, 2),
+
+ MDTABLE_COLUMN(ENCLog, Token, 0),
+ MDTABLE_COLUMN(ENCLog, Op, 1),
+ MDTABLE_COLUMN_COUNT(ENCLog, 2),
+
+ MDTABLE_COLUMN(ENCMap, Token, 0),
+ MDTABLE_COLUMN_COUNT(ENCMap, 1),
+
+ MDTABLE_COLUMN(Assembly, HashAlgId, 0),
+ MDTABLE_COLUMN(Assembly, MajorVersion, 1),
+ MDTABLE_COLUMN(Assembly, MinorVersion, 2),
+ MDTABLE_COLUMN(Assembly, BuildNumber, 3),
+ MDTABLE_COLUMN(Assembly, RevisionNumber, 4),
+ MDTABLE_COLUMN(Assembly, Flags, 5),
+ MDTABLE_COLUMN(Assembly, PublicKey, 6),
+ MDTABLE_COLUMN(Assembly, Name, 7),
+ MDTABLE_COLUMN(Assembly, Culture, 8),
+ MDTABLE_COLUMN_COUNT(Assembly, 9),
+
+ MDTABLE_COLUMN(AssemblyRef, MajorVersion, 0),
+ MDTABLE_COLUMN(AssemblyRef, MinorVersion, 1),
+ MDTABLE_COLUMN(AssemblyRef, BuildNumber, 2),
+ MDTABLE_COLUMN(AssemblyRef, RevisionNumber, 3),
+ MDTABLE_COLUMN(AssemblyRef, Flags, 4),
+ MDTABLE_COLUMN(AssemblyRef, PublicKeyOrToken, 5),
+ MDTABLE_COLUMN(AssemblyRef, Name, 6),
+ MDTABLE_COLUMN(AssemblyRef, Culture, 7),
+ MDTABLE_COLUMN(AssemblyRef, HashValue, 8),
+ MDTABLE_COLUMN_COUNT(AssemblyRef, 9),
+
+ MDTABLE_COLUMN(File, Flags, 0),
+ MDTABLE_COLUMN(File, Name, 1),
+ MDTABLE_COLUMN(File, HashValue, 2),
+ MDTABLE_COLUMN_COUNT(File, 3),
+
+ MDTABLE_COLUMN(ExportedType, Flags, 0),
+ MDTABLE_COLUMN(ExportedType, TypeDefId, 1),
+ MDTABLE_COLUMN(ExportedType, TypeName, 2),
+ MDTABLE_COLUMN(ExportedType, TypeNamespace, 3),
+ MDTABLE_COLUMN(ExportedType, Implementation, 4),
+ MDTABLE_COLUMN_COUNT(ExportedType, 5),
+
+ MDTABLE_COLUMN(ManifestResource, Offset, 0),
+ MDTABLE_COLUMN(ManifestResource, Flags, 1),
+ MDTABLE_COLUMN(ManifestResource, Name, 2),
+ MDTABLE_COLUMN(ManifestResource, Implementation, 3),
+ MDTABLE_COLUMN_COUNT(ManifestResource, 4),
+
+ MDTABLE_COLUMN(NestedClass, NestedClass, 0),
+ MDTABLE_COLUMN(NestedClass, EnclosingClass, 1),
+ MDTABLE_COLUMN_COUNT(NestedClass, 2),
+
+ MDTABLE_COLUMN(GenericParam, Number, 0),
+ MDTABLE_COLUMN(GenericParam, Flags, 1),
+ MDTABLE_COLUMN(GenericParam, Owner, 2),
+ MDTABLE_COLUMN(GenericParam, Name, 3),
+ MDTABLE_COLUMN_COUNT(GenericParam, 4),
+
+ MDTABLE_COLUMN(MethodSpec, Method, 0),
+ MDTABLE_COLUMN(MethodSpec, Instantiation, 1),
+ MDTABLE_COLUMN_COUNT(MethodSpec, 2),
+
+ MDTABLE_COLUMN(GenericParamConstraint, Owner, 0),
+ MDTABLE_COLUMN(GenericParamConstraint, Constraint, 1),
+ MDTABLE_COLUMN_COUNT(GenericParamConstraint, 2),
+
+#ifdef DNMD_PORTABLE_PDB
+ // https://github.com/dotnet/runtime/blob/main/docs/design/specs/PortablePdb-Metadata.md
+ MDTABLE_COLUMN(Document, Name, 0),
+ MDTABLE_COLUMN(Document, HashAlgorithm, 1),
+ MDTABLE_COLUMN(Document, Hash, 2),
+ MDTABLE_COLUMN(Document, Language, 3),
+ MDTABLE_COLUMN_COUNT(Document, 4),
+
+ MDTABLE_COLUMN(MethodDebugInformation, Document, 0),
+ MDTABLE_COLUMN(MethodDebugInformation, SequencePoints, 1),
+ MDTABLE_COLUMN_COUNT(MethodDebugInformation, 2),
+
+ MDTABLE_COLUMN(LocalScope, Method, 0),
+ MDTABLE_COLUMN(LocalScope, ImportScope, 1),
+ MDTABLE_COLUMN(LocalScope, VariableList, 2),
+ MDTABLE_COLUMN(LocalScope, ConstantList, 3),
+ MDTABLE_COLUMN(LocalScope, StartOffset, 4),
+ MDTABLE_COLUMN(LocalScope, Length, 5),
+ MDTABLE_COLUMN_COUNT(LocalScope, 6),
+
+ MDTABLE_COLUMN(LocalVariable, Attributes, 0),
+ MDTABLE_COLUMN(LocalVariable, Index, 1),
+ MDTABLE_COLUMN(LocalVariable, Name, 2),
+ MDTABLE_COLUMN_COUNT(LocalVariable, 3),
+
+ MDTABLE_COLUMN(LocalConstant, Name, 0),
+ MDTABLE_COLUMN(LocalConstant, Signature, 1),
+ MDTABLE_COLUMN_COUNT(LocalConstant, 2),
+
+ MDTABLE_COLUMN(ImportScope, Parent, 0),
+ MDTABLE_COLUMN(ImportScope, Imports, 1),
+ MDTABLE_COLUMN_COUNT(ImportScope, 2),
+
+ MDTABLE_COLUMN(StateMachineMethod, MoveNextMethod, 0),
+ MDTABLE_COLUMN(StateMachineMethod, KickoffMethod, 1),
+ MDTABLE_COLUMN_COUNT(StateMachineMethod, 2),
+
+ MDTABLE_COLUMN(CustomDebugInformation, Parent, 0),
+ MDTABLE_COLUMN(CustomDebugInformation, Kind, 1),
+ MDTABLE_COLUMN(CustomDebugInformation, Value, 2),
+ MDTABLE_COLUMN_COUNT(CustomDebugInformation, 3),
+#endif // DNMD_PORTABLE_PDB
+
+} col_index_t;
+
+// Query row's column values
+bool md_get_column_value_as_token(mdcursor_t c, col_index_t col_idx, mdToken* tk);
+bool md_get_column_value_as_cursor(mdcursor_t c, col_index_t col_idx, mdcursor_t* cursor);
+// Resolve the column to a cursor and a range based on the run/list pattern in tables.
+// The run continues to the smaller of:
+// * the last row of the target table
+// * the next run in the target table, found by inspecting the column value of the next row in the current table.
+// See md_find_token_of_range_element() for mapping elements in the other direction.
+bool md_get_column_value_as_range(mdcursor_t c, col_index_t col_idx, mdcursor_t* cursor, uint32_t* count);
+bool md_get_column_value_as_constant(mdcursor_t c, col_index_t col_idx, uint32_t* constant);
+bool md_get_column_value_as_utf8(mdcursor_t c, col_index_t col_idx, char const** str);
+bool md_get_column_value_as_userstring(mdcursor_t c, col_index_t col_idx, mduserstring_t* strings);
+bool md_get_column_value_as_blob(mdcursor_t c, col_index_t col_idx, uint8_t const** blob, uint32_t* blob_len);
+bool md_get_column_value_as_guid(mdcursor_t c, col_index_t col_idx, mdguid_t* guid);
+
+// Read a table or coded index column from multiple rows and return the values as an array of tokens.
+// The number of rows read is returned by the function. A '-1' return value indicates an error.
+int32_t md_get_many_rows_column_value_as_token(mdcursor_t c, col_index_t col_idx, uint32_t out_length, mdToken* tokens);
+
+// Return the raw column values for the row. Unlike the md_get_column_value_as_* APIs, the returned values
+// are in their raw form.
+// Callers should indicate ('true') using the 'values_to_get' collection which columns are desired.
+// Corresponding entries in 'values_raw' will only be set if a 'true' value is set in 'values_to_get'.
+// Note this API was not designed in a performance critical manner and should only be used if necessary.
+// The APIs that retrieve specific columns in their respective formatted forms have been designed for performance
+// and should be preferred whenever possible.
+bool md_get_column_values_raw(mdcursor_t c, uint32_t values_length, bool* values_to_get, uint32_t* values_raw);
+
+// Find a row or range of rows where the supplied column has the expected value.
+// These APIs assume the value to look for is the value in the table, typically record IDs (RID)
+// for tokens. An exception is made for coded indices, which are cumbersome to compute.
+// If the queried column contains a coded index value, the value will be validated and
+// transformed to its coded form for comparison.
+bool md_find_row_from_cursor(mdcursor_t begin, col_index_t idx, uint32_t value, mdcursor_t* cursor);
+
+typedef enum
+{
+ MD_RANGE_FOUND = 0,
+ MD_RANGE_NOT_FOUND = 1,
+ MD_RANGE_NOT_SUPPORTED = 2,
+} md_range_result_t;
+
+md_range_result_t md_find_range_from_cursor(mdcursor_t begin, col_index_t idx, uint32_t value, mdcursor_t* start, uint32_t* count);
+
+// Given a value into a supported table, find the associated parent token.
+// - mdtid_Field
+// - mdtid_MethodDef
+// - mdtid_Param
+// - mdtid_Event
+// - mdtid_Property
+// See md_get_column_value_as_range() for getting the complete range.
+bool md_find_token_of_range_element(mdcursor_t element, mdToken* tk);
+bool md_find_cursor_of_range_element(mdcursor_t element, mdcursor_t* cursor);
+
+// Given a cursor, resolve any indirections to the final cursor or return the original cursor if it does not point to an indirection table.
+// Returns true if the cursor was not an indirect cursor or if the indirection was resolved, or false if the cursor pointed to an invalid indirection table entry.
+bool md_resolve_indirect_cursor(mdcursor_t c, mdcursor_t* target);
+
+// Set row's column values
+// The returned number represents the number of rows updated.
+bool md_set_column_value_as_token(mdcursor_t c, col_index_t col, mdToken tk);
+bool md_set_column_value_as_cursor(mdcursor_t c, col_index_t col, mdcursor_t cursor);
+bool md_set_column_value_as_constant(mdcursor_t c, col_index_t col_idx, uint32_t constant);
+bool md_set_column_value_as_utf8(mdcursor_t c, col_index_t col_idx, char const* str);
+bool md_set_column_value_as_blob(mdcursor_t c, col_index_t col_idx, uint8_t const* blob, uint32_t blob_len);
+bool md_set_column_value_as_guid(mdcursor_t c, col_index_t col_idx, mdguid_t guid);
+bool md_set_column_value_as_userstring(mdcursor_t c, col_index_t col_idx, char16_t const* userstring);
+
+// Create a new row logically before the row specified by the cursor.
+// If the given row is in a table that is a target of a list column, this function will return false.
+// Only md_add_row_to_list can be used to add rows to a table that is a target of a list column.
+// The table is treated as unsorted until md_commit_row_add is called after all columns have been set on the new row.
+bool md_insert_row_before(mdcursor_t row, mdcursor_t* new_row);
+
+// Create a new row after the row specified by the cursor.
+// If the given row is in a table that is a target of a list column, this function will return false.
+// Only md_add_row_to_list can be used to add rows to a table that is a target of a list column.
+// The table is treated as unsorted until md_commit_row_add is called after all columns have been set on the new row.
+bool md_insert_row_after(mdcursor_t row, mdcursor_t* new_row);
+
+// Create a new row at the end of the specified table.
+// If the given row is in a table that is a target of a list column, this function will return false.
+// Only md_add_row_to_list can be used to add rows to a table that is a target of a list column.
+// The table is treated as unsorted until md_commit_row_add is called after all columns have been set on the new row.
+bool md_append_row(mdhandle_t handle, mdtable_id_t table_id, mdcursor_t* new_row);
+
+// Creates a new row in the list for the given cursor specified by the given column.
+// This method accounts for any indirection tables that may need to be created or maintained to ensure that
+// the structure of the list is maintained without moving tokens.
+// The table that new_child_row points to is treated as unsorted until md_commit_row_add is called after all columns have been set on the new row.
+bool md_add_new_row_to_list(mdcursor_t list_owner, col_index_t list_col, mdcursor_t* new_row);
+
+// Creates a new row in the list for the given cursor specified by the given column such that the values of the sort_order_col are maintained in ascending order.
+// This method assumes that the list is currently sorted by the sort_order_col column.
+// This method accounts for any indirection tables that may need to be created or maintained to ensure that
+// the structure of the list is maintained without moving tokens.
+// The table that new_row points to is treated as unsorted until md_commit_row_add is called after all columns have been set on the new row.
+// The new_row row will also have the sort_order_col column initialized to sort_col_value.
+bool md_add_new_row_to_sorted_list(mdcursor_t list_owner, col_index_t list_col, col_index_t sort_order_col, uint32_t sort_col_value, mdcursor_t* new_row);
+
+// Finish the process of adding a row to the cursor's table.
+void md_commit_row_add(mdcursor_t row);
+
+// Add a user string to the #US heap.
+mduserstringcursor_t md_add_userstring_to_heap(mdhandle_t handle, char16_t const* userstring);
+
+// Write the metadata represented by the handle to the supplied buffer.
+// The metadata is always written with the v2.0 table schema.
+bool md_write_to_buffer(mdhandle_t handle, uint8_t* buffer, size_t* len);
+#ifdef __cplusplus
+}
+#endif
+
+#endif // _SRC_INC_DNMD_H_
diff --git a/src/native/dnmd/src/inc/dnmd.hpp b/src/native/dnmd/src/inc/dnmd.hpp
new file mode 100644
index 0000000000000..5a299371f8f94
--- /dev/null
+++ b/src/native/dnmd/src/inc/dnmd.hpp
@@ -0,0 +1,56 @@
+#ifndef _SRC_INC_DNMD_HPP_
+#define _SRC_INC_DNMD_HPP_
+
+#include "dnmd.h"
+#include
+
+struct mdhandle_deleter_t final
+{
+ using pointer = mdhandle_t;
+ void operator()(mdhandle_t handle)
+ {
+ ::md_destroy_handle(handle);
+ }
+};
+
+// C++ lifetime wrapper for mdhandle_t type
+using mdhandle_ptr = std::unique_ptr;
+
+struct md_added_row_t final
+{
+private:
+ mdcursor_t new_row;
+public:
+ md_added_row_t() = default;
+ explicit md_added_row_t(mdcursor_t row) : new_row{ row } {}
+ md_added_row_t(md_added_row_t const& other) = delete;
+ md_added_row_t(md_added_row_t&& other)
+ {
+ *this = std::move(other);
+ }
+
+ md_added_row_t& operator=(md_added_row_t const& other) = delete;
+ md_added_row_t& operator=(md_added_row_t&& other)
+ {
+ new_row = other.new_row;
+ other.new_row = {}; // Clear the other's row so we don't double-commit.
+ return *this;
+ }
+
+ ~md_added_row_t()
+ {
+ md_commit_row_add(new_row);
+ }
+
+ operator mdcursor_t()
+ {
+ return new_row;
+ }
+
+ mdcursor_t* operator&()
+ {
+ return &new_row;
+ }
+};
+
+#endif // _SRC_INC_DNMD_HPP_
diff --git a/src/native/dnmd/src/inc/dnmd_interfaces.hpp b/src/native/dnmd/src/inc/dnmd_interfaces.hpp
new file mode 100644
index 0000000000000..88d4e363166d0
--- /dev/null
+++ b/src/native/dnmd/src/inc/dnmd_interfaces.hpp
@@ -0,0 +1,24 @@
+#ifndef _INC_DNMD_INTERFACES_HPP_
+#define _INC_DNMD_INTERFACES_HPP_
+
+#ifndef DNMD_EXPORT
+#define DNMD_EXPORT
+#endif // !DNMD_EXPORT
+
+// Create a metadata dispenser instance.
+//
+// IMetaDataDispenser - {809C652E-7396-11D2-9771-00A0C9B4D50C}
+extern "C" DNMD_EXPORT
+HRESULT GetDispenser(
+ REFGUID riid,
+ void** ppObj);
+
+// Create a symbol binder instance.
+//
+// ISymUnmanagedBinder - {AA544D42-28CB-11d3-BD22-0000F80849BD}
+extern "C" DNMD_EXPORT
+HRESULT GetSymBinder(
+ REFGUID riid,
+ void** ppObj);
+
+#endif // _INC_DNMD_INTERFACES_HPP_
diff --git a/src/native/dnmd/src/inc/dnmd_pdb.h b/src/native/dnmd/src/inc/dnmd_pdb.h
new file mode 100644
index 0000000000000..eacf55b854926
--- /dev/null
+++ b/src/native/dnmd/src/inc/dnmd_pdb.h
@@ -0,0 +1,139 @@
+#ifndef _SRC_INC_DNMD_PDB_H
+#define _SRC_INC_DNMD_PDB_H
+
+#include
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// Methods to parse specialized blob formats defined in the Portable PDB spec.
+// https://github.com/dotnet/runtime/blob/main/docs/design/specs/PortablePdb-Metadata.md
+
+typedef enum md_blob_parse_result__
+{
+ mdbpr_Success,
+ mdbpr_InvalidBlob,
+ mdbpr_InvalidArgument,
+ mdbpr_InsufficientBuffer
+} md_blob_parse_result_t;
+
+// Parse a DocumentName blob into a UTF-8 string.
+md_blob_parse_result_t md_parse_document_name(mdhandle_t handle, uint8_t const* blob, size_t blob_len, char const* name, size_t* name_len);
+
+// Parse a SequencePoints blob.
+typedef struct md_sequence_points__
+{
+ mdToken signature;
+ mdcursor_t document;
+ uint32_t record_count;
+ struct
+ {
+ enum
+ {
+ mdsp_DocumentRecord,
+ mdsp_SequencePointRecord,
+ mdsp_HiddenSequencePointRecord,
+ } kind;
+ union
+ {
+ struct
+ {
+ mdcursor_t document;
+ } document;
+ struct
+ {
+ uint32_t rolling_il_offset;
+ uint32_t delta_lines;
+ int64_t delta_columns;
+ int64_t rolling_start_line;
+ int64_t rolling_start_column;
+ } sequence_point;
+ struct
+ {
+ uint32_t rolling_il_offset;
+ } hidden_sequence_point;
+ };
+ } records[];
+} md_sequence_points_t;
+md_blob_parse_result_t md_parse_sequence_points(mdcursor_t method_debug_information, uint8_t const* blob, size_t blob_len, md_sequence_points_t* sequence_points, size_t* buffer_len);
+
+// Parse a LocalConstantSig blob.
+typedef struct md_local_constant_sig__
+{
+ enum
+ {
+ mdck_PrimitiveConstant,
+ mdck_EnumConstant,
+ mdck_GeneralConstant
+ } constant_kind;
+
+ union
+ {
+ struct
+ {
+ uint8_t type_code; // ELEMENT_TYPE_* - ECMA-335 II.23.1.16
+ } primitive;
+ struct
+ {
+ uint8_t type_code; // ELEMENT_TYPE_* - ECMA-335 II.23.1.16
+ mdToken enum_type; // See ECMA-335 II.14.3 for Enum restrictions.
+ } enum_constant;
+ struct
+ {
+ enum
+ {
+ mdgc_ValueType,
+ mdgc_Class,
+ mdgc_Object
+ } kind;
+ mdToken type; // TypeDefOrRefOrSpecEncoded - ECMA-335 II.23.2.8
+ } general;
+ };
+
+ uint8_t const* value_blob;
+ size_t value_len;
+
+ uint32_t custom_modifier_count;
+ struct
+ {
+ bool required; // Differentiate modreq vs modopt.
+ mdToken type; // Custom modifier - ECMA-335 II.23.2.7
+ } custom_modifiers[];
+} md_local_constant_sig_t;
+md_blob_parse_result_t md_parse_local_constant_sig(mdhandle_t handle, uint8_t const* blob, size_t blob_len, md_local_constant_sig_t* local_constant_sig, size_t* buffer_len);
+
+// Parse an Imports blob.
+typedef struct md_imports__
+{
+ uint32_t count;
+ struct
+ {
+ enum
+ {
+ mdidk_ImportNamespace = 1,
+ mdidk_ImportAssemblyNamespace = 2,
+ mdidk_ImportType = 3,
+ mdidk_ImportXmlNamespace = 4,
+ mdidk_ImportAssemblyReferenceAlias = 5,
+ mdidk_AliasAssemblyReference = 6,
+ mdidk_AliasNamespace = 7,
+ mdidk_AliasAssemblyNamespace = 8,
+ mdidk_AliasType = 9,
+ } kind;
+ char const* alias;
+ uint32_t alias_len;
+ mdToken assembly;
+ char const* target_namespace;
+ uint32_t target_namespace_len;
+ mdToken target_type;
+ } imports[];
+} md_imports_t;
+md_blob_parse_result_t md_parse_imports(mdhandle_t handle, uint8_t const* blob, size_t blob_len, md_imports_t* imports, size_t* buffer_len);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // _SRC_INC_DNMD_PDB_H
+
diff --git a/src/native/dnmd/src/inc/internal/dnmd_peimage.h b/src/native/dnmd/src/inc/internal/dnmd_peimage.h
new file mode 100644
index 0000000000000..6f71c04d4132d
--- /dev/null
+++ b/src/native/dnmd/src/inc/internal/dnmd_peimage.h
@@ -0,0 +1,172 @@
+#ifndef _SRC_INC_INTERNAL_DNMD_PEIMAGE_H_
+#define _SRC_INC_INTERNAL_DNMD_PEIMAGE_H_
+
+#include
+
+//
+// PE image data structures
+//
+#define IMAGE_DOS_SIGNATURE 0x5A4D // MZ
+
+typedef struct _IMAGE_DOS_HEADER { // DOS .EXE header
+ uint16_t e_magic; // Magic number
+ uint16_t e_cblp; // Bytes on last page of file
+ uint16_t e_cp; // Pages in file
+ uint16_t e_crlc; // Relocations
+ uint16_t e_cparhdr; // Size of header in paragraphs
+ uint16_t e_minalloc; // Minimum extra paragraphs needed
+ uint16_t e_maxalloc; // Maximum extra paragraphs needed
+ uint16_t e_ss; // Initial (relative) SS value
+ uint16_t e_sp; // Initial SP value
+ uint16_t e_csum; // Checksum
+ uint16_t e_ip; // Initial IP value
+ uint16_t e_cs; // Initial (relative) CS value
+ uint16_t e_lfarlc; // File address of relocation table
+ uint16_t e_ovno; // Overlay number
+ uint16_t e_res[4]; // Reserved words
+ uint16_t e_oemid; // OEM identifier (for e_oeminfo)
+ uint16_t e_oeminfo; // OEM information; e_oemid specific
+ uint16_t e_res2[10]; // Reserved words
+ int32_t e_lfanew; // File address of new exe header
+} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
+
+#define IMAGE_DATA_DIRECTORY_DEFINED
+typedef struct _IMAGE_DATA_DIRECTORY {
+ uint32_t VirtualAddress;
+ uint32_t Size;
+} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
+
+#define IMAGE_DEBUG_DIRECTORY_DEFINED
+typedef struct _IMAGE_DEBUG_DIRECTORY {
+ uint32_t Characteristics;
+ uint32_t TimeDateStamp;
+ uint16_t MajorVersion;
+ uint16_t MinorVersion;
+ uint32_t Type;
+ uint32_t SizeOfData;
+ uint32_t AddressOfRawData;
+ uint32_t PointerToRawData;
+} IMAGE_DEBUG_DIRECTORY, *PIMAGE_DEBUG_DIRECTORY;
+
+#define IMAGE_NUMBEROF_DIRECTORY_ENTRIES 16
+
+#define IMAGE_SIZEOF_SHORT_NAME 8
+
+typedef struct _IMAGE_SECTION_HEADER {
+ unsigned char Name[IMAGE_SIZEOF_SHORT_NAME];
+ union {
+ uint32_t PhysicalAddress;
+ uint32_t VirtualSize;
+ } Misc;
+ uint32_t VirtualAddress;
+ uint32_t SizeOfRawData;
+ uint32_t PointerToRawData;
+ uint32_t PointerToRelocations;
+ uint32_t PointerToLinenumbers;
+ uint16_t NumberOfRelocations;
+ uint16_t NumberOfLinenumbers;
+ uint32_t Characteristics;
+} IMAGE_SECTION_HEADER, * PIMAGE_SECTION_HEADER;
+
+#define IMAGE_FILE_MACHINE_AMD64 0x8664 // AMD64 (K8)
+#define IMAGE_FILE_MACHINE_ARM64 0xAA64 // ARM64 Little-Endian
+#define IMAGE_FILE_MACHINE_I386 0x014c // Intel 386.
+#define IMAGE_FILE_MACHINE_ARM 0x01c0 // ARM Little-Endian
+
+typedef struct _IMAGE_FILE_HEADER {
+ uint16_t Machine;
+ uint16_t NumberOfSections;
+ uint32_t TimeDateStamp;
+ uint32_t PointerToSymbolTable;
+ uint32_t NumberOfSymbols;
+ uint16_t SizeOfOptionalHeader;
+ uint16_t Characteristics;
+} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
+
+#define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR 14 // COM Runtime descriptor
+
+typedef struct _IMAGE_OPTIONAL_HEADER {
+ uint16_t Magic;
+ uint8_t MajorLinkerVersion;
+ uint8_t MinorLinkerVersion;
+ uint32_t SizeOfCode;
+ uint32_t SizeOfInitializedData;
+ uint32_t SizeOfUninitializedData;
+ uint32_t AddressOfEntryPoint;
+ uint32_t BaseOfCode;
+ uint32_t BaseOfData;
+ uint32_t ImageBase;
+ uint32_t SectionAlignment;
+ uint32_t FileAlignment;
+ uint16_t MajorOperatingSystemVersion;
+ uint16_t MinorOperatingSystemVersion;
+ uint16_t MajorImageVersion;
+ uint16_t MinorImageVersion;
+ uint16_t MajorSubsystemVersion;
+ uint16_t MinorSubsystemVersion;
+ uint32_t Win32VersionValue;
+ uint32_t SizeOfImage;
+ uint32_t SizeOfHeaders;
+ uint32_t CheckSum;
+ uint16_t Subsystem;
+ uint16_t DllCharacteristics;
+ uint32_t SizeOfStackReserve;
+ uint32_t SizeOfStackCommit;
+ uint32_t SizeOfHeapReserve;
+ uint32_t SizeOfHeapCommit;
+ uint32_t LoaderFlags;
+ uint32_t NumberOfRvaAndSizes;
+ IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
+} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
+
+typedef struct _IMAGE_OPTIONAL_HEADER64 {
+ uint16_t Magic;
+ uint8_t MajorLinkerVersion;
+ uint8_t MinorLinkerVersion;
+ uint32_t SizeOfCode;
+ uint32_t SizeOfInitializedData;
+ uint32_t SizeOfUninitializedData;
+ uint32_t AddressOfEntryPoint;
+ uint32_t BaseOfCode;
+ uint64_t ImageBase;
+ uint32_t SectionAlignment;
+ uint32_t FileAlignment;
+ uint16_t MajorOperatingSystemVersion;
+ uint16_t MinorOperatingSystemVersion;
+ uint16_t MajorImageVersion;
+ uint16_t MinorImageVersion;
+ uint16_t MajorSubsystemVersion;
+ uint16_t MinorSubsystemVersion;
+ uint32_t Win32VersionValue;
+ uint32_t SizeOfImage;
+ uint32_t SizeOfHeaders;
+ uint32_t CheckSum;
+ uint16_t Subsystem;
+ uint16_t DllCharacteristics;
+ uint64_t SizeOfStackReserve;
+ uint64_t SizeOfStackCommit;
+ uint64_t SizeOfHeapReserve;
+ uint64_t SizeOfHeapCommit;
+ uint32_t LoaderFlags;
+ uint32_t NumberOfRvaAndSizes;
+ IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
+} IMAGE_OPTIONAL_HEADER64, *PIMAGE_OPTIONAL_HEADER64;
+
+typedef struct _IMAGE_NT_HEADERS {
+ uint32_t Signature;
+ IMAGE_FILE_HEADER FileHeader;
+} IMAGE_NT_HEADERS, *PIMAGE_NT_HEADERS;
+
+typedef struct _IMAGE_NT_HEADERS64 {
+ uint32_t Signature;
+ IMAGE_FILE_HEADER FileHeader;
+ IMAGE_OPTIONAL_HEADER64 OptionalHeader;
+} IMAGE_NT_HEADERS64, *PIMAGE_NT_HEADERS64;
+
+typedef struct _IMAGE_NT_HEADERS32 {
+ uint32_t Signature;
+ IMAGE_FILE_HEADER FileHeader;
+ IMAGE_OPTIONAL_HEADER32 OptionalHeader;
+} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
+
+#endif // _SRC_INC_INTERNAL_DNMD_PEIMAGE_H_
diff --git a/src/native/dnmd/src/inc/internal/dnmd_platform.hpp b/src/native/dnmd/src/inc/internal/dnmd_platform.hpp
new file mode 100644
index 0000000000000..792ef282c55a0
--- /dev/null
+++ b/src/native/dnmd/src/inc/internal/dnmd_platform.hpp
@@ -0,0 +1,51 @@
+#ifndef _SRC_INC_INTERNAL_DNMD_PLATFORM_HPP_
+#define _SRC_INC_INTERNAL_DNMD_PLATFORM_HPP_
+
+#ifdef BUILD_WINDOWS
+
+#define NOMINMAX
+#include
+
+#define MAIN_CALLCONV __cdecl
+
+#else
+
+#include
+#include "dnmd_peimage.h"
+
+#define MAIN_CALLCONV
+#endif // !BUILD_WINDOWS
+
+// Machine code masks for native (R2R) images
+// See pedecoder.h in CoreCLR
+#define IMAGE_FILE_MACHINE_OS_MASK_APPLE 0x4644
+#define IMAGE_FILE_MACHINE_OS_MASK_FREEBSD 0xADC4
+#define IMAGE_FILE_MACHINE_OS_MASK_LINUX 0x7B79
+#define IMAGE_FILE_MACHINE_OS_MASK_NETBSD 0x1993
+#define IMAGE_FILE_MACHINE_OS_MASK_SUN 0x1992
+
+#include
+#include
+
+#define _HRESULT_TYPEDEF_(_sc) ((HRESULT)_sc)
+
+#include
+#include
+#include
+#include
+
+template
+struct malloc_deleter_t final
+{
+ using pointer = T*;
+ void operator()(T* mem)
+ {
+ ::free((void*)mem);
+ }
+};
+
+// C++ lifetime wrapper for malloc'd memory
+template
+using malloc_ptr = std::unique_ptr>;
+
+#endif // _SRC_INC_INTERNAL_DNMD_PLATFORM_HPP_
diff --git a/src/native/dnmd/src/inc/internal/dnmd_tools_platform.hpp b/src/native/dnmd/src/inc/internal/dnmd_tools_platform.hpp
new file mode 100644
index 0000000000000..e0feeae791017
--- /dev/null
+++ b/src/native/dnmd/src/inc/internal/dnmd_tools_platform.hpp
@@ -0,0 +1,243 @@
+#ifndef _SRC_INC_INTERNAL_DNMD_TOOLS_PLATFORM_HPP_
+#define _SRC_INC_INTERNAL_DNMD_TOOLS_PLATFORM_HPP_
+
+#include
+#include
+#include
+#include
+#include
+
+#include "dnmd_platform.hpp"
+#include "span.hpp"
+
+inline bool create_mdhandle(malloc_span const& buffer, mdhandle_ptr& handle)
+{
+ mdhandle_t h;
+ if (!md_create_handle(buffer.data(), buffer.size(), &h))
+ return false;
+ handle.reset(h);
+ return true;
+}
+
+//
+// PE File functions
+//
+
+inline uint32_t get_file_size(char const* path)
+{
+ uint32_t size_in_uint8_ts = 0;
+#ifdef BUILD_WINDOWS
+ HANDLE handle = ::CreateFileA(path, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, 0, nullptr);
+ if (handle != INVALID_HANDLE_VALUE)
+ {
+ size_in_uint8_ts = ::GetFileSize(handle, nullptr);
+ (void)::CloseHandle(handle);
+ }
+#else
+ struct stat st;
+ int rc = stat(path, &st);
+ if (rc == 0)
+ size_in_uint8_ts = st.st_size;
+#endif // !BUILD_WINDOWS
+
+ return size_in_uint8_ts;
+}
+
+inline PIMAGE_SECTION_HEADER find_section_header(
+ span section_headers,
+ uint32_t rva)
+{
+ for (size_t i = 0; i < section_headers.size(); ++i)
+ {
+ if (section_headers[i].VirtualAddress <= rva
+ && rva < (section_headers[i].VirtualAddress + section_headers[i].SizeOfRawData))
+ {
+ return §ion_headers[i];
+ }
+ }
+
+ return nullptr;
+}
+
+inline bool read_in_file(char const* file, malloc_span& b)
+{
+ // Read in the entire file
+ std::ifstream fd{ file, std::ios::binary | std::ios::in };
+ if (!fd)
+ return false;
+
+ size_t size = get_file_size(file);
+ if (size == 0)
+ return false;
+
+ b = { (uint8_t*)std::malloc(size), size };
+ fd.read((char*)b.data(), b.size());
+ return true;
+}
+
+inline bool write_out_file(char const* file, malloc_span b)
+{
+ // Read in the entire file
+ std::ofstream fd{ file, std::ios::binary | std::ios::out };
+ if (!fd)
+ return false;
+
+ fd.write((char*)b.data(), b.size());
+ return true;
+}
+
+inline bool find_pe_image_bitness(uint16_t machine, uint8_t& bitness)
+{
+#define MAKE_MACHINE_CASE(x) \
+ case ((x) ^ IMAGE_FILE_MACHINE_OS_MASK_APPLE): \
+ case ((x) ^ IMAGE_FILE_MACHINE_OS_MASK_FREEBSD): \
+ case ((x) ^ IMAGE_FILE_MACHINE_OS_MASK_LINUX): \
+ case ((x) ^ IMAGE_FILE_MACHINE_OS_MASK_NETBSD): \
+ case ((x) ^ IMAGE_FILE_MACHINE_OS_MASK_SUN): \
+ case (x)
+
+ switch (machine)
+ {
+ MAKE_MACHINE_CASE(IMAGE_FILE_MACHINE_I386):
+ MAKE_MACHINE_CASE(IMAGE_FILE_MACHINE_ARM):
+ bitness = 32;
+ return true;
+ MAKE_MACHINE_CASE(IMAGE_FILE_MACHINE_AMD64):
+ MAKE_MACHINE_CASE(IMAGE_FILE_MACHINE_ARM64):
+ bitness = 64;
+ return true;
+ default:
+ return false;
+ }
+
+#undef MAKE_MACHINE_CASE
+}
+
+inline bool get_metadata_from_pe(malloc_span& b)
+{
+ if (b.size() < sizeof(IMAGE_DOS_HEADER))
+ return false;
+
+ // [TODO] Handle endian issues with .NET generated PE images
+ // All integers should be read as little-endian.
+ auto dos_header = (PIMAGE_DOS_HEADER)(void*)b.data();
+ bool is_pe = dos_header->e_magic == IMAGE_DOS_SIGNATURE;
+ if (!is_pe)
+ return false;
+
+ // Handle headers that are 32 or 64
+ PIMAGE_SECTION_HEADER tgt_header;
+ PIMAGE_DATA_DIRECTORY dotnet_dir;
+
+ // Section headers begin immediately after the NT_HEADERS.
+ span section_headers;
+
+ if ((size_t)dos_header->e_lfanew > b.size())
+ return false;
+
+ size_t remaining_pe_size = b.size() - dos_header->e_lfanew;
+ uint16_t section_header_count;
+ uint8_t* section_header_begin;
+ auto nt_header_any = (PIMAGE_NT_HEADERS)(b.data() + dos_header->e_lfanew);
+ uint16_t machine = nt_header_any->FileHeader.Machine;
+
+ uint8_t bitness;
+ if (!find_pe_image_bitness(machine, bitness))
+ return false;
+
+ if (bitness == 64)
+ {
+ auto nt_header64 = (PIMAGE_NT_HEADERS64)nt_header_any;
+ if (remaining_pe_size < sizeof(*nt_header64))
+ return false;
+ remaining_pe_size -= sizeof(*nt_header64);
+ section_header_count = nt_header64->FileHeader.NumberOfSections;
+ section_header_begin = (uint8_t*)&nt_header64[1];
+ dotnet_dir = &nt_header64->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR];
+ }
+ else if (bitness == 32)
+ {
+ auto nt_header32 = (PIMAGE_NT_HEADERS32)nt_header_any;
+ if (remaining_pe_size < sizeof(*nt_header32))
+ return false;
+ remaining_pe_size -= sizeof(*nt_header32);
+ section_header_count = nt_header32->FileHeader.NumberOfSections;
+ section_header_begin = (uint8_t*)&nt_header32[1];
+ dotnet_dir = &nt_header32->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR];
+ }
+ else
+ {
+ // Unknown machine type
+ return false;
+ }
+
+ // Doesn't contain a .NET header
+ bool is_dotnet = dotnet_dir->Size != 0;
+ if (!is_dotnet)
+ return false;
+
+ // Compute the maximum space in the PE to validate section header count.
+ if (section_header_count > (remaining_pe_size / sizeof(IMAGE_SECTION_HEADER)))
+ return false;
+
+ remaining_pe_size -= section_header_count * sizeof(IMAGE_SECTION_HEADER);
+
+ section_headers = { (PIMAGE_SECTION_HEADER)section_header_begin, section_header_count };
+
+ tgt_header = find_section_header(section_headers, dotnet_dir->VirtualAddress);
+ if (tgt_header == nullptr)
+ return false;
+
+ // Sanity check
+ if (dotnet_dir->VirtualAddress < tgt_header->VirtualAddress)
+ return false;
+
+ DWORD cor_header_offset = (DWORD)(dotnet_dir->VirtualAddress - tgt_header->VirtualAddress) + tgt_header->PointerToRawData;
+ if (cor_header_offset > b.size() - sizeof(IMAGE_COR20_HEADER))
+ return false;
+
+ auto cor_header = (PIMAGE_COR20_HEADER)(b.data() + cor_header_offset);
+ tgt_header = find_section_header(section_headers, cor_header->MetaData.VirtualAddress);
+ if (tgt_header == nullptr)
+ return false;
+
+ // Sanity check
+ if (cor_header->MetaData.VirtualAddress < tgt_header->VirtualAddress)
+ return false;
+
+ DWORD metadata_offset = (DWORD)(cor_header->MetaData.VirtualAddress - tgt_header->VirtualAddress) + tgt_header->PointerToRawData;
+ if (metadata_offset > b.size())
+ return false;
+
+ void* ptr = (void*)(b.data() + metadata_offset);
+
+ size_t metadata_length = cor_header->MetaData.Size;
+ if (metadata_length > b.size() - metadata_offset)
+ return false;
+
+ // Capture the metadata portion of the image.
+ malloc_span metadata = { (uint8_t*)std::malloc(metadata_length), metadata_length };
+ std::memcpy(metadata.data(), ptr, metadata.size());
+ b = std::move(metadata);
+ return true;
+}
+
+inline bool get_metadata_from_file(malloc_span& b)
+{
+ // Defined in II.24.2.1 - defined in physical uint8_t order
+ std::array const metadata_sig = { 0x42, 0x53, 0x4A, 0x42 };
+
+ if (b.size() < metadata_sig.size())
+ return false;
+
+ // If the header doesn't match, the file is unknown.
+ for (size_t i = 0; i < metadata_sig.size(); ++i)
+ {
+ if (b[i] != metadata_sig[i])
+ return false;
+ }
+
+ return true;
+}
+
+#endif // _SRC_INC_INTERNAL_DNMD_TOOLS_PLATFORM_HPP_
diff --git a/src/native/dnmd/src/inc/internal/span.hpp b/src/native/dnmd/src/inc/internal/span.hpp
new file mode 100644
index 0000000000000..e81e6de3f385d
--- /dev/null
+++ b/src/native/dnmd/src/inc/internal/span.hpp
@@ -0,0 +1,143 @@
+#ifndef _SRC_INC_INTERNAL_SPAN_HPP_
+#define _SRC_INC_INTERNAL_SPAN_HPP_
+
+#include
+#include
+
+template
+class span
+{
+protected:
+ T* _ptr;
+ size_t _size;
+public:
+ span()
+ : _ptr{}
+ , _size{}
+ { }
+
+ span(T* ptr, size_t len)
+ : _ptr{ ptr }, _size{ len }
+ { }
+
+ span(span const & other) = default;
+
+ span& operator=(span&& other) noexcept = default;
+
+ size_t size() const noexcept
+ {
+ return _size;
+ }
+
+ T* data() noexcept
+ {
+ return _ptr;
+ }
+
+ T const* data() const noexcept
+ {
+ return _ptr;
+ }
+
+ T& operator[](size_t idx)
+ {
+ if (_ptr == nullptr)
+ throw std::runtime_error{ "Deref null" };
+ if (idx >= _size)
+ throw std::out_of_range{ "Out of bounds access" };
+ return _ptr[idx];
+ }
+
+ operator span() const
+ {
+ return { _ptr, _size };
+ }
+
+ T* begin() noexcept
+ {
+ return _ptr;
+ }
+
+ T const* cbegin() const noexcept
+ {
+ return _ptr;
+ }
+
+ T* end() noexcept
+ {
+ return _ptr + _size;
+ }
+
+ T const* cend() const noexcept
+ {
+ return _ptr + _size;
+ }
+};
+
+template
+class owning_span final : public span
+{
+public:
+ owning_span() : span{}
+ { }
+
+ owning_span(T* ptr, size_t len)
+ : span{ ptr, len }
+ { }
+
+ owning_span(owning_span&& other) noexcept
+ : span{}
+ {
+ *this = std::move(other);
+ }
+
+ ~owning_span()
+ {
+ Deleter{}(this->_ptr);
+ }
+
+ owning_span& operator=(owning_span&& other) noexcept
+ {
+ if (this->_ptr != nullptr)
+ Deleter{}(this->_ptr);
+
+ this->_ptr = other._ptr;
+ this->_size = other._size;
+ other._ptr = {};
+ other._size = {};
+ return *this;
+ }
+
+ T* release() noexcept
+ {
+ T* tmp = this->_ptr;
+ this->_ptr = {};
+ return tmp;
+ }
+
+ operator owning_span() const
+ {
+ return { this->_ptr, this->_size };
+ }
+};
+
+struct free_deleter final
+{
+ void operator()(void* ptr)
+ {
+ std::free(ptr);
+ }
+};
+
+template
+using malloc_span = owning_span;
+
+template
+span slice(span b, size_t offset)
+{
+ if (offset > b.size())
+ throw std::out_of_range{ "Out of bounds access" };
+ return { b.data() + offset, b.size() - offset };
+}
+
+#endif // _SRC_INC_INTERNAL_SPAN_HPP_
diff --git a/src/native/dnmd/src/interfaces/CMakeLists.txt b/src/native/dnmd/src/interfaces/CMakeLists.txt
new file mode 100644
index 0000000000000..6a2247167044b
--- /dev/null
+++ b/src/native/dnmd/src/interfaces/CMakeLists.txt
@@ -0,0 +1,90 @@
+set(SOURCES
+ ./dispenser.cpp
+ ./symbinder.cpp
+ ./metadataimport.cpp
+ ./metadataemit.cpp
+ ./hcorenum.cpp
+ ./pal.cpp
+ ./signatures.cpp
+ ./importhelpers.cpp
+)
+
+set(HEADERS
+ ../inc/dnmd_interfaces.hpp
+ ../inc/internal/span.hpp
+ ./metadataimportro.hpp
+ ./metadataemit.hpp
+ ./hcorenum.hpp
+ ./controllingiunknown.hpp
+ ./tearoffbase.hpp
+ ./pal.hpp
+ ./dnmdowner.hpp
+ ./signatures.hpp
+ ./importhelpers.hpp
+)
+
+if(NOT MSVC)
+ # Adds global GUID constants.
+ list(APPEND SOURCES ./iids.cpp ./options.cpp)
+endif()
+
+if (WIN32)
+ # Disable "secure CRT" warnings when targeting Windows
+ # as the "secure CRT" is not cross-platform.
+ add_compile_definitions(_CRT_SECURE_NO_WARNINGS)
+endif()
+
+add_library(dnmd_interfaces_static
+ STATIC
+ ${SOURCES}
+ ${HEADERS}
+)
+
+set_target_properties(dnmd_interfaces_static PROPERTIES EXPORT_NAME interfaces_static)
+
+add_library(dnmd::interfaces_static ALIAS dnmd_interfaces_static)
+
+add_library(dnmd_interfaces
+ SHARED
+ ${SOURCES}
+ ${HEADERS}
+)
+
+set_target_properties(dnmd_interfaces PROPERTIES EXPORT_NAME interfaces)
+add_library(dnmd::interfaces ALIAS dnmd_interfaces)
+
+target_include_directories(dnmd_interfaces_static PUBLIC $)
+target_include_directories(dnmd_interfaces PUBLIC $)
+
+target_compile_definitions(dnmd_interfaces_static PRIVATE COM_NO_WINDOWS_H)
+target_compile_definitions(dnmd_interfaces PRIVATE DNMD_BUILD_SHARED COM_NO_WINDOWS_H)
+
+target_link_libraries(dnmd_interfaces_static
+ PUBLIC
+ minipal
+ minipal_com
+ dnmd::dnmd)
+
+target_link_libraries(dnmd_interfaces
+ PRIVATE
+ minipal
+ minipal_com
+ dnmd::dnmd)
+
+if(NOT MSVC)
+ target_link_libraries(dnmd_interfaces_static PUBLIC minipal_comhdrs)
+ target_link_libraries(dnmd_interfaces PRIVATE minipal_comhdrs)
+endif()
+
+set_target_properties(dnmd_interfaces PROPERTIES
+ PUBLIC_HEADER ${CMAKE_CURRENT_SOURCE_DIR}/../inc/dnmd_interfaces.hpp)
+
+if (DNMD_INSTALL)
+ install(TARGETS dnmd_interfaces EXPORT interfaces
+ PUBLIC_HEADER DESTINATION include
+ ARCHIVE DESTINATION lib
+ LIBRARY DESTINATION lib
+ RUNTIME DESTINATION bin)
+
+ install(EXPORT interfaces NAMESPACE dnmd:: FILE dnmdinterfaces.cmake DESTINATION lib/cmake/dnmd)
+endif()
diff --git a/src/native/dnmd/src/interfaces/controllingiunknown.hpp b/src/native/dnmd/src/interfaces/controllingiunknown.hpp
new file mode 100644
index 0000000000000..8561e5ef41439
--- /dev/null
+++ b/src/native/dnmd/src/interfaces/controllingiunknown.hpp
@@ -0,0 +1,71 @@
+#ifndef _SRC_INTERFACES_CONTROLLINGIUNKNOWN_HPP_
+#define _SRC_INTERFACES_CONTROLLINGIUNKNOWN_HPP_
+
+#include "tearoffbase.hpp"
+#include
+#include
+#include
+#include
+#include
+
+class ControllingIUnknown final : public IUnknown
+{
+ std::atomic _refCount{ 1 };
+ std::vector> _tearOffs;
+public:
+ ControllingIUnknown() = default;
+
+ template
+ T* CreateAndAddTearOff(Ts&&... args)
+ {
+ auto tear_off = std::make_unique(this, std::forward(args)...);
+ T* tear_off_ptr = tear_off.get();
+ _tearOffs.push_back(std::move(tear_off));
+ return tear_off_ptr;
+ }
+
+public: // IUnknown
+ virtual HRESULT STDMETHODCALLTYPE QueryInterface(
+ /* [in] */ REFIID riid,
+ /* [iid_is][out] */ _COM_Outptr_ void __RPC_FAR* __RPC_FAR* ppvObject) override
+ {
+ if (ppvObject == nullptr)
+ return E_POINTER;
+
+ if (riid == IID_IUnknown)
+ {
+ *ppvObject = static_cast(this);
+ (void)AddRef();
+ return S_OK;
+ }
+
+ for (std::unique_ptr const& tearOff: _tearOffs)
+ {
+ if (tearOff->TryGetInterfaceOnThis(riid, ppvObject))
+ {
+ (void)AddRef();
+ return S_OK;
+ }
+ }
+
+ *ppvObject = nullptr;
+ return E_NOINTERFACE;
+ }
+
+ virtual ULONG STDMETHODCALLTYPE AddRef(void) override
+ {
+ return ++_refCount;
+ }
+
+ virtual ULONG STDMETHODCALLTYPE Release(void) override
+ {
+ uint32_t c = --_refCount;
+ if (c == 0)
+ {
+ delete this;
+ }
+ return c;
+ }
+};
+
+#endif // _SRC_INTERFACES_CONTROLLINGIUNKNOWN_HPP_
\ No newline at end of file
diff --git a/src/native/dnmd/src/interfaces/dispenser.cpp b/src/native/dnmd/src/interfaces/dispenser.cpp
new file mode 100644
index 0000000000000..e26991564aa2b
--- /dev/null
+++ b/src/native/dnmd/src/interfaces/dispenser.cpp
@@ -0,0 +1,296 @@
+#ifdef DNMD_BUILD_SHARED
+#ifdef _MSC_VER
+#define DNMD_EXPORT __declspec(dllexport)
+#else
+#define DNMD_EXPORT __attribute__((__visibility__("default")))
+#endif // !_MSC_VER
+#endif // DNMD_BUILD_SHARED
+
+#include
+#include "dnmd_interfaces.hpp"
+#include "controllingiunknown.hpp"
+#include "metadataimportro.hpp"
+#include "metadataemit.hpp"
+#include "threadsafe.hpp"
+#include
+
+#include
+
+namespace
+{
+ class MDDispenser final : public TearOffBase
+ {
+ bool _threadSafe;
+ private:
+ minipal::com_ptr CreateExposedObject(minipal::com_ptr unknown, DNMDOwner* owner)
+ {
+ mdhandle_view handle_view{ owner };
+ MetadataEmit* emit = unknown->CreateAndAddTearOff(handle_view);
+ MetadataImportRO* import = unknown->CreateAndAddTearOff(std::move(handle_view));
+ if (!_threadSafe)
+ {
+ return unknown;
+ }
+ minipal::com_ptr threadSafeUnknown;
+ threadSafeUnknown.Attach(new ControllingIUnknown());
+
+ // Define an IDNMDOwner* tear-off here so the thread-safe object can be identified as a DNMD object.
+ (void)threadSafeUnknown->CreateAndAddTearOff(handle_view);
+ (void)threadSafeUnknown->CreateAndAddTearOff>(std::move(unknown), import, emit);
+ // ThreadSafeImportEmit took ownership of owner through unknown.
+ return threadSafeUnknown;
+ }
+
+ protected:
+ virtual bool TryGetInterfaceOnThis(REFIID riid, void** ppvObject) override
+ {
+ if (riid == IID_IMetaDataDispenserEx || riid == IID_IMetaDataDispenser)
+ {
+ *ppvObject = static_cast(this);
+ return true;
+ }
+ return false;
+ }
+
+ public: // IMetaDataDispenser
+ using TearOffBase::TearOffBase;
+
+ STDMETHOD(DefineScope)(
+ REFCLSID rclsid,
+ DWORD dwCreateFlags,
+ REFIID riid,
+ IUnknown** ppIUnk) override
+ {
+ if (rclsid != CLSID_CLR_v2_MetaData)
+ {
+ // DNMD::Interfaces only creating v2 metadata images.
+ return CLDB_E_FILE_OLDVER;
+ }
+
+ if (dwCreateFlags != 0)
+ {
+ return E_INVALIDARG;
+ }
+
+ mdhandle_ptr md_ptr { md_create_new_handle() };
+ if (md_ptr == nullptr)
+ return E_OUTOFMEMORY;
+
+ // Initialize the MVID of the new image.
+ mdcursor_t moduleCursor;
+ if (!md_token_to_cursor(md_ptr.get(), TokenFromRid(1, mdtModule), &moduleCursor))
+ return E_FAIL;
+
+ mdguid_t mvid;
+ if (!minipal_guid_v4_create(&mvid))
+ return E_FAIL;
+
+ if (!md_set_column_value_as_guid(moduleCursor, mdtModule_Mvid, mvid))
+ return E_OUTOFMEMORY;
+
+ minipal::com_ptr obj;
+ obj.Attach(new (std::nothrow) ControllingIUnknown());
+ if (obj == nullptr)
+ return E_OUTOFMEMORY;
+
+ try
+ {
+ DNMDOwner* owner = obj->CreateAndAddTearOff(std::move(md_ptr));
+ return CreateExposedObject(std::move(obj), owner)->QueryInterface(riid, (void**)ppIUnk);
+ }
+ catch(std::bad_alloc const&)
+ {
+ return E_OUTOFMEMORY;
+ }
+ }
+
+ STDMETHOD(OpenScope)(
+ LPCWSTR szScope,
+ DWORD dwOpenFlags,
+ REFIID riid,
+ IUnknown** ppIUnk) override
+ {
+ UNREFERENCED_PARAMETER(szScope);
+ UNREFERENCED_PARAMETER(dwOpenFlags);
+ UNREFERENCED_PARAMETER(riid);
+ UNREFERENCED_PARAMETER(ppIUnk);
+ return E_NOTIMPL;
+ }
+
+ STDMETHOD(OpenScopeOnMemory)(
+ LPCVOID pData,
+ ULONG cbData,
+ DWORD dwOpenFlags,
+ REFIID riid,
+ IUnknown** ppIUnk) override
+ {
+ if (ppIUnk == nullptr)
+ return E_INVALIDARG;
+
+ minipal::cotaskmem_ptr nowOwned;
+ if (dwOpenFlags & ofTakeOwnership)
+ nowOwned.reset((void*)pData);
+
+ malloc_ptr copiedMem;
+ if (dwOpenFlags & ofCopyMemory)
+ {
+ copiedMem.reset(::malloc(cbData));
+ if (copiedMem == nullptr)
+ return E_OUTOFMEMORY;
+
+ // Reassign the newly allocated memory to the param variable.
+ pData = ::memcpy(copiedMem.get(), pData, cbData);
+ }
+
+ mdhandle_t mdhandle;
+ if (!md_create_handle(pData, cbData, &mdhandle))
+ return CLDB_E_FILE_CORRUPT;
+
+ mdhandle_ptr md_ptr{ mdhandle };
+
+ minipal::com_ptr obj;
+ obj.Attach(new (std::nothrow) ControllingIUnknown());
+ if (obj == nullptr)
+ return E_OUTOFMEMORY;
+
+ try
+ {
+ DNMDOwner* owner = obj->CreateAndAddTearOff(std::move(md_ptr), std::move(copiedMem), std::move(nowOwned));
+ mdhandle_view handle_view{ owner };
+
+ if (dwOpenFlags & ofReadOnly)
+ {
+ // If we're read-only, then we don't need to deal with thread safety.
+ (void)obj->CreateAndAddTearOff(std::move(handle_view));
+ return obj->QueryInterface(riid, (void**)ppIUnk);
+ }
+
+ // If we're read-write, go through our helper to create an object that respects all of the options
+ // (as the various options affect writing operations only).
+ return CreateExposedObject(std::move(obj), owner)->QueryInterface(riid, (void**)ppIUnk);
+ }
+ catch(std::bad_alloc const&)
+ {
+ return E_OUTOFMEMORY;
+ }
+ }
+
+ public: // IMetaDataDispenserEx
+ STDMETHOD(SetOption)(
+ REFGUID optionid,
+ VARIANT const *value) override
+ {
+ if (optionid == MetaDataThreadSafetyOptions)
+ {
+ _threadSafe = V_UI4(value) == CorThreadSafetyOptions::MDThreadSafetyOn;
+ return S_OK;
+ }
+ return E_INVALIDARG;
+ }
+
+ STDMETHOD(GetOption)(
+ REFGUID optionid,
+ VARIANT *pvalue) override
+ {
+ if (optionid == MetaDataThreadSafetyOptions)
+ {
+ V_UI4(pvalue) = _threadSafe ? CorThreadSafetyOptions::MDThreadSafetyOn : CorThreadSafetyOptions::MDThreadSafetyOff;
+ return S_OK;
+ }
+ return E_INVALIDARG;
+ }
+
+ STDMETHOD(OpenScopeOnITypeInfo)(
+ ITypeInfo *pITI,
+ DWORD dwOpenFlags,
+ REFIID riid,
+ IUnknown **ppIUnk) override
+ {
+ UNREFERENCED_PARAMETER(pITI);
+ UNREFERENCED_PARAMETER(dwOpenFlags);
+ UNREFERENCED_PARAMETER(riid);
+ UNREFERENCED_PARAMETER(ppIUnk);
+ return E_NOTIMPL;
+ }
+
+ STDMETHOD(GetCORSystemDirectory)(
+ _Out_writes_to_opt_(cchBuffer, *pchBuffer)
+ LPWSTR szBuffer,
+ DWORD cchBuffer,
+ DWORD* pchBuffer) override
+ {
+ UNREFERENCED_PARAMETER(szBuffer);
+ UNREFERENCED_PARAMETER(cchBuffer);
+ UNREFERENCED_PARAMETER(pchBuffer);
+ return E_NOTIMPL;
+ }
+
+ STDMETHOD(FindAssembly)(
+ LPCWSTR szAppBase,
+ LPCWSTR szPrivateBin,
+ LPCWSTR szGlobalBin,
+ LPCWSTR szAssemblyName,
+ LPCWSTR szName,
+ ULONG cchName,
+ ULONG *pcName) override
+ {
+ UNREFERENCED_PARAMETER(szAppBase);
+ UNREFERENCED_PARAMETER(szPrivateBin);
+ UNREFERENCED_PARAMETER(szGlobalBin);
+ UNREFERENCED_PARAMETER(szAssemblyName);
+ UNREFERENCED_PARAMETER(szName);
+ UNREFERENCED_PARAMETER(cchName);
+ UNREFERENCED_PARAMETER(pcName);
+ return E_NOTIMPL;
+ }
+
+ STDMETHOD(FindAssemblyModule)(
+ LPCWSTR szAppBase,
+ LPCWSTR szPrivateBin,
+ LPCWSTR szGlobalBin,
+ LPCWSTR szAssemblyName,
+ LPCWSTR szModuleName,
+ _Out_writes_to_opt_(cchName, *pcName)
+ LPWSTR szName,
+ ULONG cchName,
+ ULONG *pcName) override
+ {
+ UNREFERENCED_PARAMETER(szAppBase);
+ UNREFERENCED_PARAMETER(szPrivateBin);
+ UNREFERENCED_PARAMETER(szGlobalBin);
+ UNREFERENCED_PARAMETER(szAssemblyName);
+ UNREFERENCED_PARAMETER(szModuleName);
+ UNREFERENCED_PARAMETER(szName);
+ UNREFERENCED_PARAMETER(cchName);
+ UNREFERENCED_PARAMETER(pcName);
+ return E_NOTIMPL;
+ }
+ };
+}
+
+extern "C" DNMD_EXPORT
+HRESULT GetDispenser(
+ REFGUID riid,
+ void** ppObj)
+{
+ if (riid != IID_IMetaDataDispenser
+ && riid != IID_IMetaDataDispenserEx)
+ {
+ return E_INVALIDARG;
+ }
+
+ if (ppObj == nullptr)
+ return E_INVALIDARG;
+
+ try
+ {
+ minipal::com_ptr obj;
+ obj.Attach(new ControllingIUnknown());
+ (void)obj->CreateAndAddTearOff();
+ return obj->QueryInterface(riid, (void**)ppObj);
+ }
+ catch(std::bad_alloc const&)
+ {
+ return E_OUTOFMEMORY;
+ }
+}
diff --git a/src/native/dnmd/src/interfaces/dnmdowner.hpp b/src/native/dnmd/src/interfaces/dnmdowner.hpp
new file mode 100644
index 0000000000000..7a6e35a07078f
--- /dev/null
+++ b/src/native/dnmd/src/interfaces/dnmdowner.hpp
@@ -0,0 +1,117 @@
+#ifndef _SRC_INTERFACES_DNMDOWNER_HPP_
+#define _SRC_INTERFACES_DNMDOWNER_HPP_
+
+#include
+#include "tearoffbase.hpp"
+#include "controllingiunknown.hpp"
+
+#include
+#include
+
+#include
+#include
+
+EXTERN_GUID(IID_IDNMDOwner, 0x250ebc02, 0x1a92, 0x4638, 0xaa, 0x6c, 0x3d, 0x0f, 0x98, 0xb3, 0xa6, 0xfb);
+
+// This interface is an IUnknown interface for the purposes of easy discovery.
+struct IDNMDOwner : IUnknown
+{
+ virtual mdhandle_t MetaData() = 0;
+};
+
+class DNMDOwner;
+
+// We use a reference wrapper around the handle to allow the handle to be swapped out.
+// We plan to use swapping to implement table sorting as DNMD itself does not support
+// sorting tables or remapping tokens.
+// This is explicitly a non-owning view as this view will be passed to other tear-offs of the same object,
+// which would otherwise lead to memory leaks.
+class mdhandle_view final
+{
+private:
+ DNMDOwner* _owner;
+public:
+ explicit mdhandle_view(DNMDOwner* owner)
+ : _owner{ owner }
+ {
+ }
+
+ mdhandle_view(mdhandle_view const& other) = default;
+
+ mdhandle_view(mdhandle_view&& other) = default;
+
+ mdhandle_view& operator=(mdhandle_view const& other) = default;
+
+ mdhandle_view& operator=(mdhandle_view&& other) = default;
+
+ mdhandle_t get() const;
+
+ bool operator==(std::nullptr_t) const
+ {
+ return get() == nullptr;
+ }
+ bool operator!=(std::nullptr_t) const
+ {
+ return get() != nullptr;
+ }
+};
+
+inline bool operator==(std::nullptr_t, mdhandle_view const& view)
+{
+ return view == nullptr;
+}
+
+inline bool operator!=(std::nullptr_t, mdhandle_view const& view)
+{
+ return view != nullptr;
+}
+
+class DNMDOwner final : public TearOffBase
+{
+private:
+ mdhandle_ptr _handle;
+ malloc_ptr _malloc_to_free;
+ minipal::cotaskmem_ptr _cotaskmem_to_free;
+
+protected:
+ virtual bool TryGetInterfaceOnThis(REFIID riid, void** ppvObject) override
+ {
+ assert(riid != IID_IUnknown);
+ if (riid == IID_IDNMDOwner)
+ {
+ *ppvObject = static_cast(this);
+ return true;
+ }
+ return false;
+ }
+
+public:
+ DNMDOwner(IUnknown* controllingUnknown, mdhandle_ptr md_ptr)
+ : TearOffBase(controllingUnknown)
+ , _handle{ std::move(md_ptr) }
+ , _malloc_to_free{ nullptr }
+ , _cotaskmem_to_free{ nullptr }
+ { }
+
+ DNMDOwner(IUnknown* controllingUnknown, mdhandle_ptr md_ptr, malloc_ptr mallocMem, minipal::cotaskmem_ptr cotaskmemMem)
+ : TearOffBase(controllingUnknown)
+ , _handle{ std::move(md_ptr) }
+ , _malloc_to_free{ std::move(mallocMem) }
+ , _cotaskmem_to_free{ std::move(cotaskmemMem) }
+ { }
+
+ virtual ~DNMDOwner() noexcept = default;
+
+public: // IDNMDOwner
+ mdhandle_t MetaData() override
+ {
+ return _handle.get();
+ }
+};
+
+inline mdhandle_t mdhandle_view::get() const
+{
+ return _owner->MetaData();
+}
+
+#endif // !_SRC_INTERFACES_DNMDOWNER_HPP_
\ No newline at end of file
diff --git a/src/native/dnmd/src/interfaces/hcorenum.cpp b/src/native/dnmd/src/interfaces/hcorenum.cpp
new file mode 100644
index 0000000000000..79a799d610a28
--- /dev/null
+++ b/src/native/dnmd/src/interfaces/hcorenum.cpp
@@ -0,0 +1,402 @@
+#include "hcorenum.hpp"
+#include
+#include
+
+#define RETURN_IF_FAILED(exp) \
+{ \
+ hr = (exp); \
+ if (FAILED(hr)) \
+ { \
+ return hr; \
+ } \
+}
+
+HRESULT HCORENUMImpl::CreateTableEnum(_In_ uint32_t count, _Out_ HCORENUMImpl** impl) noexcept
+{
+ assert(impl != nullptr && count > 0);
+
+ HCORENUMImpl* enumImpl;
+ enumImpl = (HCORENUMImpl*)::malloc(sizeof(*enumImpl) + (sizeof(enumImpl->_data) * (count - 1)));
+ if (enumImpl == nullptr)
+ return E_OUTOFMEMORY;
+
+ // Immediately set the return.
+ *impl = enumImpl;
+
+ enumImpl->_type = HCORENUMType::Table;
+ enumImpl->_entrySpan = 1;
+ enumImpl->_curr = &enumImpl->_data;
+ enumImpl->_last = enumImpl->_curr;
+
+ // Initialize the linked list of EnumData.
+ EnumData* currInit = enumImpl->_curr;
+ currInit->Next = nullptr;
+
+ // -1 because the initial impl contains one.
+ EnumData* nextMaybe = (EnumData*)&enumImpl[1];
+ for (size_t i = 0; i < (count - 1); ++i)
+ {
+ currInit->Next = nextMaybe;
+ currInit = nextMaybe;
+ currInit->Next = nullptr;
+ enumImpl->_last = currInit;
+ nextMaybe = nextMaybe + 1;
+ }
+
+ return S_OK;
+}
+
+void HCORENUMImpl::InitTableEnum(_Inout_ HCORENUMImpl& impl, _In_ uint32_t index, _In_ mdcursor_t cursor, _In_ uint32_t rows) noexcept
+{
+ assert(impl._type == HCORENUMType::Table);
+ EnumData* currInit = impl._curr;
+
+ // See CreateTableEnum for allocation layout.
+ if (index > 0)
+ {
+ HCORENUMImpl* pImpl = &impl;
+ EnumData* dataBegin = (EnumData*)&pImpl[1]; // Data starts immediately after impl.
+ currInit = (EnumData*)&dataBegin[index - 1];
+ }
+
+ currInit->Table.Current = cursor;
+ currInit->Table.Start = cursor;
+ currInit->ReadIn = 0;
+ currInit->Total = rows;
+}
+
+HRESULT HCORENUMImpl::CreateDynamicEnum(_Out_ HCORENUMImpl** impl, _In_ uint32_t entrySpan) noexcept
+{
+ assert(impl != nullptr && entrySpan > 0);
+
+ HCORENUMImpl* enumImpl;
+ enumImpl = (HCORENUMImpl*)::malloc(sizeof(*enumImpl));
+ if (enumImpl == nullptr)
+ return E_OUTOFMEMORY;
+
+ // Immediately set the return.
+ *impl = enumImpl;
+
+ enumImpl->_type = HCORENUMType::Dynamic;
+ // The page must be a multiple of the entrySpan for reading to be efficient.
+ assert(ARRAY_SIZE(enumImpl->_data.Dynamic.Page) % entrySpan == 0);
+ enumImpl->_entrySpan = entrySpan;
+ ::memset(&enumImpl->_data, 0, sizeof(enumImpl->_data));
+ enumImpl->_curr = &enumImpl->_data;
+ enumImpl->_last = enumImpl->_curr;
+ return S_OK;
+}
+
+HRESULT HCORENUMImpl::AddToDynamicEnum(_Inout_ HCORENUMImpl& impl, uint32_t value) noexcept
+{
+ assert(impl._type == HCORENUMType::Dynamic);
+
+ // Check if we have exhausted the last page
+ EnumData* currData = impl._last;
+ if (currData->Total >= ARRAY_SIZE(currData->Dynamic.Page))
+ {
+ EnumData* newData = (EnumData*)::malloc(sizeof(EnumData));
+ if (newData == nullptr)
+ return E_OUTOFMEMORY;
+
+ ::memset(newData, 0, sizeof(*newData));
+ assert(currData->Next == nullptr);
+ currData->Next = newData;
+ impl._last = newData;
+ currData = impl._last;
+ }
+ currData->Dynamic.Page[currData->Total] = value;
+ currData->Total++;
+ return S_OK;
+}
+
+void HCORENUMImpl::Destroy(_In_ HCORENUMImpl* impl) noexcept
+{
+ assert(impl != nullptr);
+ if (impl->_type == HCORENUMType::Dynamic)
+ {
+ // Delete all allocated pages.
+ EnumData* tmp;
+ EnumData* toDelete = impl->_data.Next;
+ while (toDelete != nullptr)
+ {
+ tmp = toDelete->Next;
+ ::free(toDelete);
+ toDelete = tmp;
+ }
+ }
+
+ ::free(impl);
+}
+
+uint32_t HCORENUMImpl::Count() const noexcept
+{
+ // Accumulate all tables in the enumerator
+ uint32_t count = 0;
+ EnumData const* curr = &_data;
+ do
+ {
+ count += curr->Total;
+ curr = curr->Next;
+ }
+ while (curr != nullptr);
+
+ return count / _entrySpan;
+}
+
+HRESULT HCORENUMImpl::ReadTokens(
+ mdToken rTokens[],
+ ULONG cMax,
+ ULONG* pcTokens) noexcept
+{
+ HRESULT hr;
+ uint32_t tokenCount = 0;
+ if (cMax == 1)
+ {
+ hr = ReadOneToken(rTokens[0], tokenCount);
+ }
+ else
+ {
+ hr = (_type == HCORENUMType::Table)
+ ? ReadTableTokens(rTokens, cMax, tokenCount)
+ : ReadDynamicTokens(rTokens, cMax, tokenCount);
+ }
+
+ if (pcTokens != nullptr)
+ *pcTokens = tokenCount;
+
+ return hr;
+}
+
+HRESULT HCORENUMImpl::ReadTokenPairs(
+ mdToken rTokens1[],
+ mdToken rTokens2[],
+ ULONG cMax,
+ ULONG* pcTokens) noexcept
+{
+ assert(_type == HCORENUMType::Dynamic);
+ assert(rTokens1 != nullptr && rTokens2 != nullptr && pcTokens != nullptr);
+ assert(_entrySpan == 2);
+
+ EnumData* currData = _curr;
+ if (currData == nullptr)
+ return S_FALSE;
+
+ uint32_t count = 0;
+ for (uint32_t i = 0; i < cMax; ++i)
+ {
+ // Check if all values have been read.
+ while (currData->ReadIn == currData->Total)
+ {
+ currData = currData->Next;
+ // Check next link in enumerator list
+ if (currData == nullptr)
+ goto Done;
+ _curr = currData;
+ }
+
+ assert(((currData->Total - currData->ReadIn) % 2) == 0);
+ rTokens1[count] = currData->Dynamic.Page[currData->ReadIn++];
+ rTokens2[count] = currData->Dynamic.Page[currData->ReadIn++];
+ count++;
+ }
+Done:
+ *pcTokens = count;
+ return S_OK;
+}
+
+HRESULT HCORENUMImpl::Reset(_In_ ULONG position) noexcept
+{
+ return (_type == HCORENUMType::Table)
+ ? ResetTableEnum(position)
+ : ResetDynamicEnum(position);
+}
+
+HRESULT HCORENUMImpl::ReadOneToken(mdToken& rToken, uint32_t& count) noexcept
+{
+ EnumData* currData = _curr;
+ while (currData->ReadIn == currData->Total)
+ {
+ currData = currData->Next;
+ // Check next link in enumerator list
+ if (currData == nullptr)
+ return S_FALSE;
+ _curr = currData;
+ }
+
+ if (_type == HCORENUMType::Table)
+ {
+ mdcursor_t current;
+ if (!md_resolve_indirect_cursor(currData->Table.Current, ¤t))
+ return CLDB_E_FILE_CORRUPT;
+
+ if (!md_cursor_to_token(current, &rToken))
+ return S_FALSE;
+
+ (void)md_cursor_next(&currData->Table.Current);
+ }
+ else
+ {
+ rToken = currData->Dynamic.Page[currData->ReadIn];
+ }
+
+ currData->ReadIn++;
+ count = 1;
+ return S_OK;
+}
+
+HRESULT HCORENUMImpl::ReadTableTokens(
+ mdToken rTokens[],
+ uint32_t cMax,
+ uint32_t& tokenCount) noexcept
+{
+ assert(_type == HCORENUMType::Table);
+ assert(rTokens != nullptr);
+
+ EnumData* currData = _curr;
+ if (currData == nullptr)
+ return S_FALSE;
+
+ uint32_t count = 0;
+ for (uint32_t i = 0; i < cMax; ++i)
+ {
+ // Check if all values have been read.
+ while (currData->ReadIn == currData->Total)
+ {
+ currData = currData->Next;
+ // Check next link in enumerator list
+ if (currData == nullptr)
+ goto Done;
+ _curr = currData;
+ }
+
+ mdcursor_t current;
+ if (!md_resolve_indirect_cursor(currData->Table.Current, ¤t))
+ return CLDB_E_FILE_CORRUPT;
+
+ if (!md_cursor_to_token(current, &rTokens[count]))
+ break;
+ count++;
+
+ if (!md_cursor_next(&currData->Table.Current))
+ break;
+ currData->ReadIn++;
+ }
+Done:
+ tokenCount = count;
+ return S_OK;
+}
+
+HRESULT HCORENUMImpl::ReadDynamicTokens(
+ mdToken rTokens[],
+ uint32_t cMax,
+ uint32_t& tokenCount) noexcept
+{
+ assert(_type == HCORENUMType::Dynamic);
+ assert(rTokens != nullptr);
+
+ EnumData* currData = _curr;
+ if (currData == nullptr)
+ return S_FALSE;
+
+ uint32_t count = 0;
+ for (uint32_t i = 0; i < cMax; ++i)
+ {
+ // Check if all values have been read.
+ while (currData->ReadIn == currData->Total)
+ {
+ currData = currData->Next;
+ // Check next link in enumerator list
+ if (currData == nullptr)
+ goto Done;
+ _curr = currData;
+ }
+
+ rTokens[count] = currData->Dynamic.Page[currData->ReadIn];
+ currData->ReadIn++;
+ count++;
+ }
+Done:
+ tokenCount = count;
+ return S_OK;
+}
+
+HRESULT HCORENUMImpl::ResetTableEnum(_In_ uint32_t position) noexcept
+{
+ assert(_type == HCORENUMType::Table);
+
+ mdcursor_t newStart;
+ uint32_t newReadIn;
+ bool reset = false;
+ EnumData* currData = &_data;
+ while (currData != nullptr)
+ {
+ newStart = currData->Table.Start;
+ if (reset)
+ {
+ // Reset the enumerator state
+ newReadIn = 0;
+ }
+ else if (position < currData->Total)
+ {
+ // The current enumerator contains the position
+ if (!md_cursor_move(&newStart, position))
+ return E_INVALIDARG;
+ newReadIn = position;
+ reset = true;
+
+ // Update the current state of the enumerator
+ _curr = currData;
+ }
+ else
+ {
+ // The current enumerator is consumed based on position
+ position -= currData->Total;
+ if (!md_cursor_move(&newStart, currData->Total))
+ return E_INVALIDARG;
+ newReadIn = currData->Total;
+ }
+
+ currData->Table.Current = newStart;
+ currData->ReadIn = newReadIn;
+ currData = currData->Next;
+ }
+
+ return S_OK;
+}
+
+HRESULT HCORENUMImpl::ResetDynamicEnum(_In_ uint32_t position) noexcept
+{
+ assert(_type == HCORENUMType::Dynamic);
+
+ uint32_t newReadIn;
+ bool reset = false;
+ EnumData* currData = &_data;
+ while (currData != nullptr)
+ {
+ if (reset)
+ {
+ // Reset the enumerator state
+ newReadIn = 0;
+ }
+ else if (position < currData->Total)
+ {
+ newReadIn = position;
+ reset = true;
+
+ // Update the current state of the enumerator
+ _curr = currData;
+ }
+ else
+ {
+ // The current enumerator is consumed based on position
+ position -= currData->Total;
+ newReadIn = currData->Total;
+ }
+
+ currData->ReadIn = newReadIn;
+ currData = currData->Next;
+ }
+
+ return S_OK;
+}
diff --git a/src/native/dnmd/src/interfaces/hcorenum.hpp b/src/native/dnmd/src/interfaces/hcorenum.hpp
new file mode 100644
index 0000000000000..a5b312f91e63f
--- /dev/null
+++ b/src/native/dnmd/src/interfaces/hcorenum.hpp
@@ -0,0 +1,102 @@
+#ifndef _SRC_INTERFACES_HCORENUM_HPP_
+#define _SRC_INTERFACES_HCORENUM_HPP_
+
+#include
+
+enum class HCORENUMType : uint32_t
+{
+ Table = 1, Dynamic
+};
+
+// Represents a singly linked list or dynamic uint32_t array enumerator
+class HCORENUMImpl final
+{
+ HCORENUMType _type;
+ uint32_t _entrySpan; // The number of entries equal to a single unit.
+
+ struct EnumData final
+ {
+ union
+ {
+ // Enumerate for tables
+ struct
+ {
+ mdcursor_t Current;
+ mdcursor_t Start;
+ } Table;
+
+ // Enumerate for dynamic uint32_t array
+ struct
+ {
+ uint32_t Page[16];
+ } Dynamic;
+ };
+
+ uint32_t ReadIn;
+ uint32_t Total;
+ EnumData* Next;
+ };
+
+ EnumData _data;
+ EnumData* _curr;
+ EnumData* _last;
+
+public: // static
+ // Lifetime operations
+ static HRESULT CreateTableEnum(_In_ uint32_t count, _Out_ HCORENUMImpl** impl) noexcept;
+ static void InitTableEnum(_Inout_ HCORENUMImpl& impl, _In_ uint32_t index, _In_ mdcursor_t cursor, _In_ uint32_t rows) noexcept;
+
+ // If multiple values represent a single entry, the "entrySpan" argument
+ // can be used to indicate the count for a single entry.
+ static HRESULT CreateDynamicEnum(_Out_ HCORENUMImpl** impl, _In_ uint32_t entrySpan = 1) noexcept;
+ static HRESULT AddToDynamicEnum(_Inout_ HCORENUMImpl& impl, uint32_t value) noexcept;
+
+ static void Destroy(_In_ HCORENUMImpl* impl) noexcept;
+
+public: // instance
+ // Get the total items for this enumeration
+ uint32_t Count() const noexcept;
+
+ // Read in the tokens for this enumeration
+ HRESULT ReadTokens(
+ mdToken rTokens[],
+ ULONG cMax,
+ ULONG* pcTokens) noexcept;
+
+ HRESULT ReadTokenPairs(
+ mdToken rTokens1[],
+ mdToken rTokens2[],
+ ULONG cMax,
+ ULONG* pcTokens) noexcept;
+
+ // Reset the enumeration to a specific position
+ HRESULT Reset(_In_ ULONG position) noexcept;
+
+private:
+ HRESULT ReadOneToken(mdToken& rToken, uint32_t& count) noexcept;
+ HRESULT ReadTableTokens(
+ mdToken rTokens[],
+ uint32_t cMax,
+ uint32_t& tokenCount) noexcept;
+ HRESULT ReadDynamicTokens(
+ mdToken rTokens[],
+ uint32_t cMax,
+ uint32_t& tokenCount) noexcept;
+
+ HRESULT ResetTableEnum(_In_ uint32_t position) noexcept;
+ HRESULT ResetDynamicEnum(_In_ uint32_t position) noexcept;
+};
+
+struct HCORENUMImplDeleter final
+{
+ using pointer = HCORENUMImpl*;
+ void operator()(HCORENUMImpl* mem)
+ {
+ HCORENUMImpl::Destroy(mem);
+ }
+};
+
+// C++ lifetime wrapper for HCORENUMImpl memory
+using HCORENUMImpl_ptr = std::unique_ptr;
+
+#endif // _SRC_INTERFACES_HCORENUM_HPP_
\ No newline at end of file
diff --git a/src/native/dnmd/src/interfaces/iids.cpp b/src/native/dnmd/src/interfaces/iids.cpp
new file mode 100644
index 0000000000000..44e0ef9f2efb0
--- /dev/null
+++ b/src/native/dnmd/src/interfaces/iids.cpp
@@ -0,0 +1,35 @@
+#include
+#include
+
+#define MINIPAL_COM_DEFINE_GUID
+#include
+
+#define MIDL_DEFINE_GUID(name,l,w1,w2,b1,b2,b3,b4,b5,b6,b7,b8) \
+ EXTERN_GUID(name,l,w1,w2,b1,b2,b3,b4,b5,b6,b7,b8)
+
+MIDL_DEFINE_GUID(GUID_NULL, 0x00000000, 0x0000, 0x0000, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
+
+// Define the IMetaDataImport IID here - cor.h provides the declaration.
+MIDL_DEFINE_GUID(IID_IMetaDataDispenser,0x809c652e,0x7396,0x11d2,0x97,0x71,0x00,0xa0,0xc9,0xb4,0xd5,0x0c);
+MIDL_DEFINE_GUID(IID_IMetaDataDispenserEx, 0x31bcfce2, 0xdafb, 0x11d2, 0x9f, 0x81, 0x0, 0xc0, 0x4f, 0x79, 0xa0, 0xa3);
+MIDL_DEFINE_GUID(IID_IMetaDataImport,0x7dac8207,0xd3ae,0x4c75,0x9b,0x67,0x92,0x80,0x1a,0x49,0x7d,0x44);
+MIDL_DEFINE_GUID(IID_IMetaDataImport2,0xfce5efa0,0x8bba,0x4f8e,0xa0,0x36,0x8f,0x20,0x22,0xb0,0x84,0x66);
+MIDL_DEFINE_GUID(IID_IMetaDataAssemblyImport,0xee62470b,0xe94b,0x424e,0x9b,0x7c,0x2f,0x00,0xc9,0x24,0x9f,0x93);
+MIDL_DEFINE_GUID(IID_IMetaDataEmit, 0xba3fee4c, 0xecb9, 0x4e41, 0x83, 0xb7, 0x18, 0x3f, 0xa4, 0x1c, 0xd8, 0x59);
+MIDL_DEFINE_GUID(IID_IMetaDataEmit2, 0xf5dd9950, 0xf693, 0x42e6, 0x83, 0xe, 0x7b, 0x83, 0x3e, 0x81, 0x46, 0xa9);
+MIDL_DEFINE_GUID(IID_IMetaDataAssemblyEmit, 0x211ef15b, 0x5317, 0x4438, 0xb1, 0x96, 0xde, 0xc8, 0x7b, 0x88, 0x76, 0x93);
+
+// Define the ISymUnmanaged* IIDs here - corsym.h provides the declaration.
+MIDL_DEFINE_GUID(IID_ISymUnmanagedBinder, 0xaa544d42, 0x28cb, 0x11d3, 0xbd, 0x22, 0x00, 0x00, 0xf8, 0x08, 0x49, 0xbd);
+
+// Define option IIDs here - cor.h provides the declaration.
+MIDL_DEFINE_GUID(MetaDataThreadSafetyOptions, 0xf7559806, 0xf266, 0x42ea, 0x8c, 0x63, 0xa, 0xdb, 0x45, 0xe8, 0xb2, 0x34);
+MIDL_DEFINE_GUID(CLSID_CLR_v2_MetaData, 0xefea471a, 0x44fd, 0x4862, 0x92, 0x92, 0xc, 0x58, 0xd4, 0x6e, 0x1f, 0x3a);
+
+// objidl.idl
+MIDL_DEFINE_GUID(IID_IUnknown, 0x00000000, 0x0000, 0x0000, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46);
+MIDL_DEFINE_GUID(IID_ISequentialStream, 0x0c733a30, 0x2a1c, 0x11ce, 0xad, 0xe5, 0x00, 0xaa, 0x00, 0x44, 0x77, 0x3d);
+MIDL_DEFINE_GUID(IID_IStream, 0x0000000c, 0x0000, 0x0000, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46);
+
+// Define an IID for our own marker interface
+MIDL_DEFINE_GUID(IID_IDNMDOwner, 0x250ebc02, 0x1a92, 0x4638, 0xaa, 0x6c, 0x3d, 0x0f, 0x98, 0xb3, 0xa6, 0xfb);
diff --git a/src/native/dnmd/src/interfaces/importhelpers.cpp b/src/native/dnmd/src/interfaces/importhelpers.cpp
new file mode 100644
index 0000000000000..f5d292105f70f
--- /dev/null
+++ b/src/native/dnmd/src/interfaces/importhelpers.cpp
@@ -0,0 +1,1818 @@
+#include "importhelpers.hpp"
+#include "signatures.hpp"
+#include "pal.hpp"
+#include
+#include
+#include
+#include
+#include
+#include
+
+// Macros from wincrypt.h that we need avaliable on all platforms
+// for strong-name parsing.
+// Get the class (hash, signature, encryption, etc) of an algorithm from an ALG_ID
+#define GET_ALG_CLASS(x) (x & (7 << 13))
+// Get the sub-identifier of an algorithm (like SHA1) from an ALG_ID
+#define GET_ALG_SID(x) (x & (511))
+
+#define ALG_CLASS_SIGNATURE (1 << 13)
+#define ALG_CLASS_HASH (4 << 13)
+
+#define ALG_SID_SHA1 4
+
+// Blob definitions from wincrypt.h
+#define PUBLICKEYBLOB 0x6
+
+#define RETURN_IF_FAILED(exp) \
+{ \
+ hr = (exp); \
+ if (FAILED(hr)) \
+ { \
+ return hr; \
+ } \
+}
+
+namespace
+{
+ HRESULT GetMvid(mdhandle_t image, mdguid_t* mvid)
+ {
+ mdcursor_t c;
+ uint32_t count;
+ if (!md_create_cursor(image, mdtid_Module, &c, &count))
+ return CLDB_E_FILE_CORRUPT;
+
+ if (!md_get_column_value_as_guid(c, mdtModule_Mvid, mvid))
+ return CLDB_E_FILE_CORRUPT;
+
+ return S_OK;
+ }
+
+ CorTokenType GetTokenTypeFromCursor(mdcursor_t cursor)
+ {
+ mdToken token = mdTokenNil;
+ if (!md_cursor_to_token(cursor, &token))
+ assert(false);
+
+ return (CorTokenType)TypeFromToken(token);
+ }
+
+ // The strong name token is the last 8 bytes of the SHA1 hash of the public key.
+ // See II.6.3
+ constexpr size_t StrongNameTokenSize = 8;
+
+ using StrongNameToken = std::array;
+
+ namespace StrongNameKeys
+ {
+ // The byte values of the real public keys and their corresponding tokens
+ // for assemblies the .NET SDK ships.
+ // These blobs allow us to skip the token calculation for these assemblies.
+ // Each of these keys corresponds to the public key in a file in the .NET Arcade SDK.
+
+ // The byte values of the ECMA pseudo public key and its token.
+ // Arcade SDK StrongNameKeyId: ECMA
+ // See II.6.2.1.3 for the definition of this key.
+ uint8_t const EcmaPublicKey[] = { 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0 };
+ StrongNameToken const EcmaToken = { 0xb7, 0x7a, 0x5c, 0x56, 0x19, 0x34, 0xe0, 0x89 };
+
+ // Arcade SDK StrongNameKeyId: Microsoft
+ uint8_t const Microsoft[] =
+ {
+ 0x00,0x24,0x00,0x00,0x04,0x80,0x00,0x00,0x94,0x00,0x00,0x00,0x06,0x02,0x00,0x00,
+ 0x00,0x24,0x00,0x00,0x52,0x53,0x41,0x31,0x00,0x04,0x00,0x00,0x01,0x00,0x01,0x00,
+ 0x07,0xd1,0xfa,0x57,0xc4,0xae,0xd9,0xf0,0xa3,0x2e,0x84,0xaa,0x0f,0xae,0xfd,0x0d,
+ 0xe9,0xe8,0xfd,0x6a,0xec,0x8f,0x87,0xfb,0x03,0x76,0x6c,0x83,0x4c,0x99,0x92,0x1e,
+ 0xb2,0x3b,0xe7,0x9a,0xd9,0xd5,0xdc,0xc1,0xdd,0x9a,0xd2,0x36,0x13,0x21,0x02,0x90,
+ 0x0b,0x72,0x3c,0xf9,0x80,0x95,0x7f,0xc4,0xe1,0x77,0x10,0x8f,0xc6,0x07,0x77,0x4f,
+ 0x29,0xe8,0x32,0x0e,0x92,0xea,0x05,0xec,0xe4,0xe8,0x21,0xc0,0xa5,0xef,0xe8,0xf1,
+ 0x64,0x5c,0x4c,0x0c,0x93,0xc1,0xab,0x99,0x28,0x5d,0x62,0x2c,0xaa,0x65,0x2c,0x1d,
+ 0xfa,0xd6,0x3d,0x74,0x5d,0x6f,0x2d,0xe5,0xf1,0x7e,0x5e,0xaf,0x0f,0xc4,0x96,0x3d,
+ 0x26,0x1c,0x8a,0x12,0x43,0x65,0x18,0x20,0x6d,0xc0,0x93,0x34,0x4d,0x5a,0xd2,0x93
+ };
+
+ StrongNameToken const MicrosoftToken = {0xb0,0x3f,0x5f,0x7f,0x11,0xd5,0x0a,0x3a};
+
+ // Arcade SDK StrongNameKeyId: SilverlightPlatform
+ uint8_t const SilverlightPlatform[] =
+ {
+ 0x00,0x24,0x00,0x00,0x04,0x80,0x00,0x00,0x94,0x00,0x00,0x00,0x06,0x02,0x00,0x00,
+ 0x00,0x24,0x00,0x00,0x52,0x53,0x41,0x31,0x00,0x04,0x00,0x00,0x01,0x00,0x01,0x00,
+ 0x8d,0x56,0xc7,0x6f,0x9e,0x86,0x49,0x38,0x30,0x49,0xf3,0x83,0xc4,0x4b,0xe0,0xec,
+ 0x20,0x41,0x81,0x82,0x2a,0x6c,0x31,0xcf,0x5e,0xb7,0xef,0x48,0x69,0x44,0xd0,0x32,
+ 0x18,0x8e,0xa1,0xd3,0x92,0x07,0x63,0x71,0x2c,0xcb,0x12,0xd7,0x5f,0xb7,0x7e,0x98,
+ 0x11,0x14,0x9e,0x61,0x48,0xe5,0xd3,0x2f,0xba,0xab,0x37,0x61,0x1c,0x18,0x78,0xdd,
+ 0xc1,0x9e,0x20,0xef,0x13,0x5d,0x0c,0xb2,0xcf,0xf2,0xbf,0xec,0x3d,0x11,0x58,0x10,
+ 0xc3,0xd9,0x06,0x96,0x38,0xfe,0x4b,0xe2,0x15,0xdb,0xf7,0x95,0x86,0x19,0x20,0xe5,
+ 0xab,0x6f,0x7d,0xb2,0xe2,0xce,0xef,0x13,0x6a,0xc2,0x3d,0x5d,0xd2,0xbf,0x03,0x17,
+ 0x00,0xae,0xc2,0x32,0xf6,0xc6,0xb1,0xc7,0x85,0xb4,0x30,0x5c,0x12,0x3b,0x37,0xab
+ };
+
+ StrongNameToken const SilverlightPlatformToken = {0x7c,0xec,0x85,0xd7,0xbe,0xa7,0x79,0x8e};
+
+ // Arcade SDK StrongNameKeyId: MicrosoftShared
+ uint8_t const Silverlight[] =
+ {
+ 0x00,0x24,0x00,0x00,0x04,0x80,0x00,0x00,0x94,0x00,0x00,0x00,0x06,0x02,0x00,0x00,
+ 0x00,0x24,0x00,0x00,0x52,0x53,0x41,0x31,0x00,0x04,0x00,0x00,0x01,0x00,0x01,0x00,
+ 0xb5,0xfc,0x90,0xe7,0x02,0x7f,0x67,0x87,0x1e,0x77,0x3a,0x8f,0xde,0x89,0x38,0xc8,
+ 0x1d,0xd4,0x02,0xba,0x65,0xb9,0x20,0x1d,0x60,0x59,0x3e,0x96,0xc4,0x92,0x65,0x1e,
+ 0x88,0x9c,0xc1,0x3f,0x14,0x15,0xeb,0xb5,0x3f,0xac,0x11,0x31,0xae,0x0b,0xd3,0x33,
+ 0xc5,0xee,0x60,0x21,0x67,0x2d,0x97,0x18,0xea,0x31,0xa8,0xae,0xbd,0x0d,0xa0,0x07,
+ 0x2f,0x25,0xd8,0x7d,0xba,0x6f,0xc9,0x0f,0xfd,0x59,0x8e,0xd4,0xda,0x35,0xe4,0x4c,
+ 0x39,0x8c,0x45,0x43,0x07,0xe8,0xe3,0x3b,0x84,0x26,0x14,0x3d,0xae,0xc9,0xf5,0x96,
+ 0x83,0x6f,0x97,0xc8,0xf7,0x47,0x50,0xe5,0x97,0x5c,0x64,0xe2,0x18,0x9f,0x45,0xde,
+ 0xf4,0x6b,0x2a,0x2b,0x12,0x47,0xad,0xc3,0x65,0x2b,0xf5,0xc3,0x08,0x05,0x5d,0xa9
+ };
+
+ StrongNameToken const SilverlightToken = {0x31,0xBF,0x38,0x56,0xAD,0x36,0x4E,0x35};
+
+ // Arcade SDK StrongNameKeyId: MicrosoftAspNetCore
+ uint8_t const AspNetCore[] =
+ {
+ 0x00,0x24,0x00,0x00,0x04,0x80,0x00,0x00,0x94,0x00,0x00,0x00,0x06,0x02,0x00,0x00,
+ 0x00,0x24,0x00,0x00,0x52,0x53,0x41,0x31,0x00,0x04,0x00,0x00,0x01,0x00,0x01,0x00,
+ 0xF3,0x3A,0x29,0x04,0x4F,0xA9,0xD7,0x40,0xC9,0xB3,0x21,0x3A,0x93,0xE5,0x7C,0x84,
+ 0xB4,0x72,0xC8,0x4E,0x0B,0x8A,0x0E,0x1A,0xE4,0x8E,0x67,0xA9,0xF8,0xF6,0xDE,0x9D,
+ 0x5F,0x7F,0x3D,0x52,0xAC,0x23,0xE4,0x8A,0xC5,0x18,0x01,0xF1,0xDC,0x95,0x0A,0xBE,
+ 0x90,0x1D,0xA3,0x4D,0x2A,0x9E,0x3B,0xAA,0xDB,0x14,0x1A,0x17,0xC7,0x7E,0xF3,0xC5,
+ 0x65,0xDD,0x5E,0xE5,0x05,0x4B,0x91,0xCF,0x63,0xBB,0x3C,0x6A,0xB8,0x3F,0x72,0xAB,
+ 0x3A,0xAF,0xE9,0x3D,0x0F,0xC3,0xC2,0x34,0x8B,0x76,0x4F,0xAF,0xB0,0xB1,0xC0,0x73,
+ 0x3D,0xE5,0x14,0x59,0xAE,0xAB,0x46,0x58,0x03,0x84,0xBF,0x9D,0x74,0xC4,0xE2,0x81,
+ 0x64,0xB7,0xCD,0xE2,0x47,0xF8,0x91,0xBA,0x07,0x89,0x1C,0x9D,0x87,0x2A,0xD2,0xBB
+ };
+
+ StrongNameToken const AspNetCoreToken = {0xad, 0xb9, 0x79, 0x38, 0x29, 0xdd, 0xae, 0x60};
+
+ // Arcade SDK StrongNameKeyId: Open
+ uint8_t const Open[] =
+ {
+ 0x00,0x24,0x00,0x00,0x04,0x80,0x00,0x00,0x94,0x00,0x00,0x00,0x06,0x02,0x00,0x00,
+ 0x00,0x24,0x00,0x00,0x52,0x53,0x41,0x31,0x00,0x04,0x00,0x00,0x01,0x00,0x01,0x00,
+ 0x4B,0x86,0xC4,0xCB,0x78,0x54,0x9B,0x34,0xBA,0xB6,0x1A,0x3B,0x18,0x00,0xE2,0x3B,
+ 0xFE,0xB5,0xB3,0xEC,0x39,0x00,0x74,0x04,0x15,0x36,0xA7,0xE3,0xCB,0xD9,0x7F,0x5F,
+ 0x04,0xCF,0x0F,0x85,0x71,0x55,0xA8,0x92,0x8E,0xAA,0x29,0xEB,0xFD,0x11,0xCF,0xBB,
+ 0xAD,0x3B,0xA7,0x0E,0xFE,0xA7,0xBD,0xA3,0x22,0x6C,0x6A,0x8D,0x37,0x0A,0x4C,0xD3,
+ 0x03,0xF7,0x14,0x48,0x6B,0x6E,0xBC,0x22,0x59,0x85,0xA6,0x38,0x47,0x1E,0x6E,0xF5,
+ 0x71,0xCC,0x92,0xA4,0x61,0x3C,0x00,0xB8,0xFA,0x65,0xD6,0x1C,0xCE,0xE0,0xCB,0xE5,
+ 0xF3,0x63,0x30,0xC9,0xA0,0x1F,0x41,0x83,0x55,0x9F,0x1B,0xEF,0x24,0xCC,0x29,0x17,
+ 0xC6,0xD9,0x13,0xE3,0xA5,0x41,0x33,0x3A,0x1D,0x05,0xD9,0xBE,0xD2,0x2B,0x38,0xCB
+ };
+
+ StrongNameToken const OpenToken = {0xcc, 0x7b, 0x13, 0xff, 0xcd, 0x2d, 0xdd, 0x51};
+
+ struct WellKnownKey final
+ {
+ uint8_t const* const PublicKey;
+ size_t const PublicKeyLen;
+ StrongNameToken const& Token;
+ };
+
+ static WellKnownKey const WellKnownKeys[] =
+ {
+ { EcmaPublicKey, sizeof(EcmaPublicKey), EcmaToken },
+ { Microsoft, sizeof(Microsoft), MicrosoftToken },
+ { SilverlightPlatform, sizeof(SilverlightPlatform), SilverlightPlatformToken },
+ { Silverlight, sizeof(Silverlight), SilverlightToken },
+ { AspNetCore, sizeof(AspNetCore), AspNetCoreToken },
+ { Open, sizeof(Open), OpenToken },
+ };
+
+ bool GetTokenForWellKnownKey(uint8_t const* key, size_t keyLength, StrongNameToken* token)
+ {
+ for (size_t i = 0; i < ARRAY_SIZE(WellKnownKeys); i++)
+ {
+ if (keyLength == WellKnownKeys[i].PublicKeyLen
+ && std::memcmp(key, WellKnownKeys[i].PublicKey, keyLength) == 0)
+ {
+ *token = WellKnownKeys[i].Token;
+ return true;
+ }
+ }
+
+ return false;
+ }
+ }
+
+ struct PublicKeyBlob final
+ {
+ uint32_t SigAlgID;
+ uint32_t HashAlgID;
+ uint32_t PublicKeyLength;
+ uint8_t PublicKey[];
+ };
+
+ HRESULT StrongNameTokenFromPublicKey(span publicKeyBlob, StrongNameToken& strongNameTokenBuffer)
+ {
+ if (publicKeyBlob.size() < sizeof(PublicKeyBlob))
+ return CORSEC_E_INVALID_PUBLICKEY;
+
+ PublicKeyBlob const* publicKey = reinterpret_cast(publicKeyBlob.data());
+
+ if (publicKey->PublicKeyLength != publicKeyBlob.size() - sizeof(PublicKeyBlob))
+ return CORSEC_E_INVALID_PUBLICKEY;
+
+ if (publicKeyBlob.size() == sizeof(StrongNameKeys::EcmaPublicKey)
+ && std::memcmp(publicKeyBlob.data(), StrongNameKeys::EcmaPublicKey, sizeof(StrongNameKeys::EcmaPublicKey)) == 0)
+ {
+ return S_OK;
+ }
+
+ if (publicKey->HashAlgID != 0)
+ {
+ if (GET_ALG_CLASS(publicKey->HashAlgID) != ALG_CLASS_HASH)
+ return CORSEC_E_INVALID_PUBLICKEY;
+
+ if (GET_ALG_SID(publicKey->HashAlgID) < ALG_SID_SHA1)
+ return CORSEC_E_INVALID_PUBLICKEY;
+ }
+
+ if (publicKey->SigAlgID != 0 && GET_ALG_CLASS(publicKey->SigAlgID) != ALG_CLASS_SIGNATURE)
+ return CORSEC_E_INVALID_PUBLICKEY;
+
+ if (publicKey->PublicKeyLength == 0 || publicKey->PublicKey[0] != PUBLICKEYBLOB)
+ return CORSEC_E_INVALID_PUBLICKEY;
+
+ // Check well-known keys first.
+ if (StrongNameKeys::GetTokenForWellKnownKey(publicKey->PublicKey, publicKey->PublicKeyLength, &strongNameTokenBuffer))
+ return S_OK;
+
+ std::array hash;
+ if (!pal::ComputeSha1Hash(publicKeyBlob, hash))
+ return CORSEC_E_INVALID_PUBLICKEY;
+
+ // Take the last few bytes of the hash value for our token.
+ // These are the low order bytes from a big-endian point of view.
+ // Reverse the order of these bytes in the output buffer to get little-endian byte order.
+ // The byte order of the strong name token is not specified in ECMA-335, but is what CLR, CoreCLR, and Mono Desktop have always done.
+ std::reverse_copy(hash.begin() + pal::SHA1_HASH_SIZE - StrongNameTokenSize, hash.end(), strongNameTokenBuffer.begin());
+
+ return S_OK;
+ }
+}
+
+namespace
+{
+ struct AssemblyVersionMatcher
+ {
+ bool(*IsApplicable)(char const* name);
+ HRESULT(*Match)(mdcursor_t c, uint32_t majorVersion, uint32_t minorVersion, uint32_t buildNumber, uint32_t revisionNumber);
+ };
+
+ std::array const AssemblyVersionMatchers =
+ {
+ {
+ // COMPAT: CoreCLR resolves all references to mscorlib and Microsoft.VisualC to the same assembly ref ignoring the build and revision version.
+ {
+ [](char const* name) -> bool
+ {
+ auto AsciiCaseInsensitiveEquals = [](char const* a, char const* b)
+ {
+ while (*a != '\0' && *b != '\0')
+ {
+ if (std::tolower(*a) != std::tolower(*b))
+ return false;
+
+ a++;
+ b++;
+ }
+
+ return *a == '\0' && *b == '\0';
+ };
+
+ return AsciiCaseInsensitiveEquals(name, "mscorlib")
+ || AsciiCaseInsensitiveEquals(name, "microsoft.visualc");
+ },
+ [](mdcursor_t c, uint32_t majorVersion, uint32_t minorVersion, uint32_t buildNumber, uint32_t revisionNumber)
+ {
+ UNREFERENCED_PARAMETER(buildNumber);
+ UNREFERENCED_PARAMETER(revisionNumber);
+ uint32_t temp;
+ if (!md_get_column_value_as_constant(c, mdtAssemblyRef_MajorVersion, &temp))
+ return CLDB_E_FILE_CORRUPT;
+
+ if (temp != majorVersion)
+ return S_FALSE;
+
+ if (!md_get_column_value_as_constant(c, mdtAssemblyRef_MinorVersion, &temp))
+ return CLDB_E_FILE_CORRUPT;
+
+ if (temp != minorVersion)
+ return S_FALSE;
+
+ return S_OK;
+ }
+ },
+ // Otherwise, we'll compare all of the version components.
+ {
+ [](char const* name)
+ {
+ UNREFERENCED_PARAMETER(name);
+ return true;
+ },
+ [](mdcursor_t c, uint32_t majorVersion, uint32_t minorVersion, uint32_t buildNumber, uint32_t revisionNumber)
+ {
+ uint32_t temp;
+ if (!md_get_column_value_as_constant(c, mdtAssemblyRef_MajorVersion, &temp))
+ return CLDB_E_FILE_CORRUPT;
+
+ if (temp != majorVersion)
+ return S_FALSE;
+
+ if (!md_get_column_value_as_constant(c, mdtAssemblyRef_MinorVersion, &temp))
+ return CLDB_E_FILE_CORRUPT;
+
+ if (temp != minorVersion)
+ return S_FALSE;
+ if (!md_get_column_value_as_constant(c, mdtAssemblyRef_BuildNumber, &temp))
+ return CLDB_E_FILE_CORRUPT;
+
+ if (temp != buildNumber)
+ return S_FALSE;
+
+ if (!md_get_column_value_as_constant(c, mdtAssemblyRef_RevisionNumber, &temp))
+ return CLDB_E_FILE_CORRUPT;
+
+ if (temp != revisionNumber)
+ return S_FALSE;
+
+ return S_OK;
+ }
+ }
+ }
+ };
+
+ AssemblyVersionMatcher const& GetAssemblyVersionMatcher(char const* name)
+ {
+ for (AssemblyVersionMatcher const& matcher : AssemblyVersionMatchers)
+ {
+ if (matcher.IsApplicable(name))
+ return matcher;
+ }
+
+ // The final matcher should always be applicable.
+ // If it isn't, we have a bug in our code.
+ assert(false);
+ return AssemblyVersionMatchers[AssemblyVersionMatchers.size() - 1];
+ }
+
+ HRESULT FindAssemblyRef(
+ mdhandle_t targetModule,
+ uint32_t majorVersion,
+ uint32_t minorVersion,
+ uint32_t buildNumber,
+ uint32_t revisionNumber,
+ uint32_t flags,
+ char const* name,
+ char const* culture,
+ span publicKeyOrToken,
+ mdcursor_t* assemblyRef)
+ {
+ HRESULT hr;
+
+ bool calculatedPublicKeyToken = false;
+ StrongNameToken publicKeyToken{};
+ if (IsAfPublicKeyToken(flags) && publicKeyOrToken.size() == StrongNameTokenSize)
+ {
+ std::copy(publicKeyOrToken.begin(), publicKeyOrToken.end(), publicKeyToken.begin());
+ calculatedPublicKeyToken = true;
+ }
+
+ // Search the assembly ref table for a matching row.
+ mdcursor_t c;
+ uint32_t count;
+ if (!md_create_cursor(targetModule, mdtid_AssemblyRef, &c, &count))
+ return E_FAIL;
+
+ AssemblyVersionMatcher const& matcher = GetAssemblyVersionMatcher(name);
+
+ for (uint32_t i = 0; i < count; i++, md_cursor_next(&c))
+ {
+ // Search the table linearly by manually reading the columns.
+ hr = matcher.Match(c, majorVersion, minorVersion, buildNumber, revisionNumber);
+ RETURN_IF_FAILED(hr);
+ if (hr == S_FALSE)
+ continue;
+
+ char const* tempString;
+ if (!md_get_column_value_as_utf8(c, mdtAssemblyRef_Name, &tempString))
+ return CLDB_E_FILE_CORRUPT;
+
+ if (std::strcmp(tempString, name) != 0)
+ continue;
+
+ if (!md_get_column_value_as_utf8(c, mdtAssemblyRef_Culture, &tempString))
+ return CLDB_E_FILE_CORRUPT;
+
+ if (std::strcmp(tempString, culture) != 0)
+ continue;
+
+ uint8_t const* tempBlob;
+ uint32_t tempBlobLength;
+ if (!md_get_column_value_as_blob(c, mdtAssemblyRef_PublicKeyOrToken, &tempBlob, &tempBlobLength))
+ return CLDB_E_FILE_CORRUPT;
+
+ // If our source has a public key or token, we can only match against an AssemblyRef that has a public key or token.
+ // If our source doesn't have a public key or token, we can only match against an AssemblyRef that doesn't have a public key or token.
+ if ((publicKeyOrToken.size() == 0) != (tempBlobLength == 0))
+ continue;
+
+ if (tempBlobLength != 0)
+ {
+ // Handle the case when a ref may be using a full public key instead of a token.
+ StrongNameToken refPublicKeyToken;
+
+ uint32_t assemblyRefFlags;
+ if (!md_get_column_value_as_constant(c, mdtAssemblyRef_Flags, &assemblyRefFlags))
+ return CLDB_E_FILE_CORRUPT;
+
+ if (IsAfPublicKey(flags) == IsAfPublicKey(assemblyRefFlags))
+ {
+ // If the source and destination either both have a full key or both have a key token, we can compare them directly.
+ if (tempBlobLength != publicKeyOrToken.size() || !std::equal(publicKeyOrToken.begin(), publicKeyOrToken.end(), tempBlob))
+ continue;
+ }
+ else if (IsAfPublicKey(assemblyRefFlags))
+ {
+ // This AssemblyRef row has a full public key and our source has a token.
+ // We need to get the token from the key.
+ RETURN_IF_FAILED(StrongNameTokenFromPublicKey({ tempBlob, tempBlobLength }, refPublicKeyToken));
+ }
+ else
+ {
+ // This AssemblyRef row has a token and our source has a full public key.
+ // We need to get the token from the key.
+ if (!calculatedPublicKeyToken)
+ {
+ RETURN_IF_FAILED(StrongNameTokenFromPublicKey(publicKeyOrToken, publicKeyToken));
+ calculatedPublicKeyToken = true;
+ }
+ }
+
+ // At this point, we have a token for both our source and the AssemblyRef we are checking against.
+
+ // If our source started with a public key token, we should have initialized publicKeyToken to it
+ // and set calculatedPublicKeyToken to true.
+ // If our source started with a public key, then we should have calculated the token above and
+ // set calculatedPublicKeyToken to true.
+ assert(calculatedPublicKeyToken);
+ if (publicKeyToken != refPublicKeyToken)
+ continue;
+ }
+
+ *assemblyRef = c;
+ return S_OK;
+ }
+
+ return S_FALSE;
+ }
+
+ HRESULT ImportReferenceToAssemblyRef(
+ mdcursor_t sourceAssemblyRef,
+ mdhandle_t targetModule,
+ std::function onRowAdded,
+ mdcursor_t* targetAssembly
+ )
+ {
+ HRESULT hr;
+ uint32_t flags;
+ if (!md_get_column_value_as_constant(sourceAssemblyRef, mdtAssemblyRef_Flags, &flags))
+ return E_FAIL;
+
+ uint8_t const* publicKey;
+ uint32_t publicKeyLength;
+ if (!md_get_column_value_as_blob(sourceAssemblyRef, mdtAssemblyRef_PublicKeyOrToken, &publicKey, &publicKeyLength))
+ return E_FAIL;
+
+ uint32_t majorVersion;
+ if (!md_get_column_value_as_constant(sourceAssemblyRef, mdtAssemblyRef_MajorVersion, &majorVersion))
+ return E_FAIL;
+
+ uint32_t minorVersion;
+ if (!md_get_column_value_as_constant(sourceAssemblyRef, mdtAssemblyRef_MinorVersion, &minorVersion))
+ return E_FAIL;
+
+ uint32_t buildNumber;
+ if (!md_get_column_value_as_constant(sourceAssemblyRef, mdtAssemblyRef_BuildNumber, &buildNumber))
+ return E_FAIL;
+
+ uint32_t revisionNumber;
+ if (!md_get_column_value_as_constant(sourceAssemblyRef, mdtAssemblyRef_RevisionNumber, &revisionNumber))
+ return E_FAIL;
+
+ char const* assemblyName;
+ if (!md_get_column_value_as_utf8(sourceAssemblyRef, mdtAssemblyRef_Name, &assemblyName))
+ return E_FAIL;
+
+ char const* assemblyCulture;
+ if (!md_get_column_value_as_utf8(sourceAssemblyRef, mdtAssemblyRef_Culture, &assemblyCulture))
+ return E_FAIL;
+
+ RETURN_IF_FAILED(FindAssemblyRef(
+ targetModule,
+ majorVersion,
+ minorVersion,
+ buildNumber,
+ revisionNumber,
+ flags,
+ assemblyName,
+ assemblyCulture,
+ { publicKey, publicKeyLength },
+ targetAssembly));
+
+ if (hr == S_OK)
+ {
+ return S_OK;
+ }
+
+ md_added_row_t assemblyRef;
+ if (!md_append_row(targetModule, mdtid_AssemblyRef, &assemblyRef))
+ return E_FAIL;
+
+ onRowAdded(assemblyRef);
+
+ if (!md_set_column_value_as_constant(assemblyRef, mdtAssemblyRef_MajorVersion, majorVersion))
+ return E_FAIL;
+
+ if (!md_set_column_value_as_constant(assemblyRef, mdtAssemblyRef_MinorVersion, minorVersion))
+ return E_FAIL;
+
+ if (!md_set_column_value_as_constant(assemblyRef, mdtAssemblyRef_BuildNumber, buildNumber))
+ return E_FAIL;
+
+ if (md_set_column_value_as_constant(assemblyRef, mdtAssemblyRef_RevisionNumber, revisionNumber))
+ return E_FAIL;
+
+ if (!md_set_column_value_as_constant(assemblyRef, mdtAssemblyRef_Flags, flags))
+ return E_FAIL;
+
+ if (!md_set_column_value_as_utf8(assemblyRef, mdtAssemblyRef_Name, assemblyName))
+ return E_FAIL;
+
+ if (!md_set_column_value_as_utf8(assemblyRef, mdtAssemblyRef_Culture, assemblyCulture))
+ return E_FAIL;
+
+ if (!md_set_column_value_as_blob(assemblyRef, mdtAssemblyRef_PublicKeyOrToken, publicKey, publicKeyLength))
+ return E_FAIL;
+
+ *targetAssembly = assemblyRef;
+ return S_OK;
+ }
+
+ // Add a reference to sourceAssembly
+ // in the AssemblyRef tables in targetModule and targetAssembly.
+ // Returns the resulting cursor into targetModule's AssemblyRef table.
+ HRESULT ImportReferenceToAssemblyRef(
+ mdcursor_t sourceAssemblyRef,
+ mdhandle_t targetModule,
+ mdhandle_t targetAssembly,
+ std::function onRowAdded,
+ mdcursor_t* assemblyRefInTargetModule)
+ {
+ HRESULT hr;
+
+ // Add a reference to the assembly in the target module.
+ RETURN_IF_FAILED(ImportReferenceToAssemblyRef(sourceAssemblyRef, targetModule, onRowAdded, assemblyRefInTargetModule));
+
+ // Also add a reference to the assembly in the target assembly.
+ // In most cases, the target module will be the same as the target assembly, so this will be a no-op.
+ // However, if the target module is a netmodule, then the target assembly will be the main assembly.
+ // CoreCLR doesn't support multi-module assemblies, but they're still valid in ECMA-335.
+ if (targetModule != targetAssembly)
+ {
+ mdcursor_t ignored;
+ RETURN_IF_FAILED(ImportReferenceToAssemblyRef(sourceAssemblyRef, targetAssembly, onRowAdded, &ignored));
+ }
+
+ return S_OK;
+ }
+
+ HRESULT ImportReferenceToAssembly(
+ mdcursor_t sourceAssembly,
+ span sourceAssemblyHash,
+ mdhandle_t targetModule,
+ std::function onRowAdded,
+ mdcursor_t* targetAssembly)
+ {
+ HRESULT hr;
+ uint32_t flags;
+ if (!md_get_column_value_as_constant(sourceAssembly, mdtAssembly_Flags, &flags))
+ return E_FAIL;
+
+ uint8_t const* publicKey;
+ uint32_t publicKeyLength;
+ if (!md_get_column_value_as_blob(sourceAssembly, mdtAssembly_PublicKey, &publicKey, &publicKeyLength))
+ return E_FAIL;
+
+ span publicKeyTokenSpan;
+ StrongNameToken publicKeyToken;
+ if (publicKey != nullptr)
+ {
+ assert(IsAfPublicKey(flags));
+ flags &= ~afPublicKey;
+ RETURN_IF_FAILED(StrongNameTokenFromPublicKey({ publicKey, publicKeyLength }, publicKeyToken));
+ publicKeyTokenSpan = { publicKeyToken.data(), publicKeyToken.size() };
+ }
+ else
+ {
+ assert(!IsAfPublicKey(flags));
+ }
+
+ uint32_t majorVersion;
+ if (!md_get_column_value_as_constant(sourceAssembly, mdtAssembly_MajorVersion, &majorVersion))
+ return E_FAIL;
+
+ uint32_t minorVersion;
+ if (!md_get_column_value_as_constant(sourceAssembly, mdtAssembly_MinorVersion, &minorVersion))
+ return E_FAIL;
+
+ uint32_t buildNumber;
+ if (!md_get_column_value_as_constant(sourceAssembly, mdtAssembly_BuildNumber, &buildNumber))
+ return E_FAIL;
+
+ uint32_t revisionNumber;
+ if (!md_get_column_value_as_constant(sourceAssembly, mdtAssembly_RevisionNumber, &revisionNumber))
+ return E_FAIL;
+
+ char const* assemblyName;
+ if (!md_get_column_value_as_utf8(sourceAssembly, mdtAssembly_Name, &assemblyName))
+ return E_FAIL;
+
+ char const* assemblyCulture;
+ if (!md_get_column_value_as_utf8(sourceAssembly, mdtAssembly_Culture, &assemblyCulture))
+ return E_FAIL;
+
+ RETURN_IF_FAILED(FindAssemblyRef(
+ targetModule,
+ majorVersion,
+ minorVersion,
+ buildNumber,
+ revisionNumber,
+ flags,
+ assemblyName,
+ assemblyCulture,
+ publicKeyTokenSpan,
+ targetAssembly));
+
+ if (hr == S_OK)
+ {
+ return S_OK;
+ }
+
+ md_added_row_t assemblyRef;
+ if (!md_append_row(targetModule, mdtid_AssemblyRef, &assemblyRef))
+ return E_FAIL;
+
+ onRowAdded(assemblyRef);
+
+ if (!md_set_column_value_as_constant(assemblyRef, mdtAssemblyRef_MajorVersion, majorVersion))
+ return E_FAIL;
+
+ if (!md_set_column_value_as_constant(assemblyRef, mdtAssemblyRef_MinorVersion, minorVersion))
+ return E_FAIL;
+
+ if (!md_set_column_value_as_constant(assemblyRef, mdtAssemblyRef_BuildNumber, buildNumber))
+ return E_FAIL;
+
+ if (md_set_column_value_as_constant(assemblyRef, mdtAssemblyRef_RevisionNumber, revisionNumber))
+ return E_FAIL;
+
+ if (!md_set_column_value_as_constant(assemblyRef, mdtAssemblyRef_Flags, flags))
+ return E_FAIL;
+
+ if (!md_set_column_value_as_utf8(assemblyRef, mdtAssemblyRef_Name, assemblyName))
+ return E_FAIL;
+
+ if (!md_set_column_value_as_utf8(assemblyRef, mdtAssemblyRef_Culture, assemblyCulture))
+ return E_FAIL;
+
+ if (!md_set_column_value_as_blob(assemblyRef, mdtAssemblyRef_HashValue, sourceAssemblyHash.data(), (uint32_t)sourceAssemblyHash.size()))
+ return E_FAIL;
+
+ if (!md_set_column_value_as_blob(assemblyRef, mdtAssemblyRef_PublicKeyOrToken, publicKeyToken.data(), (uint32_t)publicKeyToken.size()))
+ return E_FAIL;
+
+ *targetAssembly = assemblyRef;
+ return S_OK;
+ }
+
+ // Add a reference to sourceAssembly
+ // in the AssemblyRef tables in targetModule and targetAssembly.
+ // Returns the resulting cursor into targetModule's AssemblyRef table.
+ HRESULT ImportReferenceToAssembly(
+ mdhandle_t sourceAssembly,
+ span sourceAssemblyHash,
+ mdhandle_t targetModule,
+ mdhandle_t targetAssembly,
+ std::function onRowAdded,
+ mdcursor_t* assemblyRefInTargetModule)
+ {
+ HRESULT hr;
+ mdcursor_t importAssembly;
+ if (!md_token_to_cursor(sourceAssembly, TokenFromRid(1, mdtAssembly), &importAssembly))
+ return E_FAIL;
+
+ // Add a reference to the assembly in the target module.
+ RETURN_IF_FAILED(ImportReferenceToAssembly(importAssembly, sourceAssemblyHash, targetModule, onRowAdded, assemblyRefInTargetModule));
+
+ // Also add a reference to the assembly in the target assembly.
+ // In most cases, the target module will be the same as the target assembly, so this will be a no-op.
+ // However, if the target module is a netmodule, then the target assembly will be the main assembly.
+ // CoreCLR doesn't support multi-module assemblies, but they're still valid in ECMA-335.
+ if (targetModule != targetAssembly)
+ {
+ mdcursor_t ignored;
+ RETURN_IF_FAILED(ImportReferenceToAssembly(importAssembly, sourceAssemblyHash, targetAssembly, onRowAdded, &ignored));
+ }
+
+ return S_OK;
+ }
+}
+
+HRESULT ImportReferenceToTypeDef(
+ mdcursor_t sourceTypeDef,
+ mdhandle_t sourceAssembly,
+ span sourceAssemblyHash,
+ mdhandle_t targetAssembly,
+ mdhandle_t targetModule,
+ bool alwaysImport,
+ std::function onRowAdded,
+ mdcursor_t* targetTypeDef)
+{
+ HRESULT hr;
+ mdhandle_t sourceModule = md_extract_handle_from_cursor(sourceTypeDef);
+
+ mdguid_t targetModuleMvid = {};
+ mdguid_t targetAssemblyMvid = {};
+ mdguid_t sourceAssemblyMvid = {};
+ mdguid_t sourceModuleMvid = {};
+ RETURN_IF_FAILED(GetMvid(targetModule, &targetModuleMvid));
+ RETURN_IF_FAILED(GetMvid(targetAssembly, &targetAssemblyMvid));
+ RETURN_IF_FAILED(GetMvid(sourceModule, &sourceModuleMvid));
+ RETURN_IF_FAILED(GetMvid(sourceAssembly, &sourceAssemblyMvid));
+
+ bool sameModuleMvid = std::memcmp(&targetModuleMvid, &sourceModuleMvid, sizeof(mdguid_t)) == 0;
+ bool sameAssemblyMvid = std::memcmp(&targetAssemblyMvid, &sourceAssemblyMvid, sizeof(mdguid_t)) == 0;
+
+ mdcursor_t resolutionScope;
+ if (sameAssemblyMvid && sameModuleMvid)
+ {
+ if (!alwaysImport)
+ {
+ // If we don't need to always import the TypeDef,
+ // we can resolve it to an existing TypeDef.
+ mdToken token;
+ if (!md_cursor_to_token(sourceTypeDef, &token))
+ return E_FAIL;
+
+ // All images with the same MVID should have the same metadata tables.
+ if (!md_token_to_cursor(targetModule, token, targetTypeDef))
+ return CLDB_E_FILE_CORRUPT;
+
+ return S_OK;
+ }
+ uint32_t count;
+ if (!md_create_cursor(targetModule, mdtid_Module, &resolutionScope, &count))
+ return E_FAIL;
+ }
+ else if (sameAssemblyMvid && !sameModuleMvid)
+ {
+ char const* importName;
+ mdcursor_t importModule;
+ uint32_t count;
+ if (!md_create_cursor(sourceModule, mdtid_Module, &importModule, &count)
+ || !md_get_column_value_as_utf8(importModule, mdtModule_Name, &importName))
+ {
+ return E_FAIL;
+ }
+
+ md_added_row_t moduleRef;
+ if (!md_append_row(targetModule, mdtid_ModuleRef, &moduleRef)
+ || !md_set_column_value_as_utf8(moduleRef, mdtModuleRef_Name, importName))
+ {
+ return E_FAIL;
+ }
+
+ resolutionScope = moduleRef;
+ onRowAdded(moduleRef);
+ }
+ else if (sameModuleMvid)
+ {
+ // The import can't be the same module and different assemblies.
+ // COMPAT-BREAK: CoreCLR allows this for cases where there is no source assembly open, with a TODO from FX-era
+ // relating to using a sample compiler from the .NET Framework SDK from before VS6.0.
+ // This tool never shipped, so we don't need to account for this bug here.
+ return E_INVALIDARG;
+ }
+ else
+ {
+ RETURN_IF_FAILED(ImportReferenceToAssembly(sourceAssembly, sourceAssemblyHash, targetModule, targetAssembly, onRowAdded, &resolutionScope));
+ }
+
+ try
+ {
+ std::stack typesForTypeRefs;
+
+ mdcursor_t importType;
+ if (!md_token_to_cursor(sourceModule, tdImport, &importType))
+ return CLDB_E_FILE_CORRUPT;
+
+ typesForTypeRefs.push(importType);
+
+ mdcursor_t nestedClasses;
+ uint32_t nestedClassCount;
+ if (!md_create_cursor(sourceModule, mdtid_NestedClass, &nestedClasses, &nestedClassCount))
+ return E_FAIL;
+
+ mdToken nestedTypeToken = tdImport;
+ mdcursor_t nestedClass;
+ while (md_find_row_from_cursor(nestedClasses, mdtNestedClass_NestedClass, RidFromToken(nestedTypeToken), &nestedClass))
+ {
+ mdcursor_t enclosingClass;
+ if (!md_get_column_value_as_cursor(nestedClass, mdtNestedClass_EnclosingClass, &enclosingClass))
+ return E_FAIL;
+
+ typesForTypeRefs.push(enclosingClass);
+ if (!md_cursor_to_token(enclosingClass, &nestedTypeToken))
+ return E_FAIL;
+ }
+
+ for (; !typesForTypeRefs.empty(); typesForTypeRefs.pop())
+ {
+ mdcursor_t typeDef = typesForTypeRefs.top();
+ md_added_row_t typeRef;
+ if (!md_append_row(targetModule, mdtid_TypeRef, &typeRef))
+ return E_FAIL;
+
+ if (!md_set_column_value_as_cursor(typeRef, mdtTypeRef_ResolutionScope, resolutionScope))
+ return E_FAIL;
+
+ char const* typeName;
+ if (!md_get_column_value_as_utf8(typeDef, mdtTypeDef_TypeName, &typeName)
+ || !md_set_column_value_as_utf8(typeRef, mdtTypeRef_TypeName, typeName))
+ {
+ return E_FAIL;
+ }
+
+ char const* typeNamespace;
+ if (!md_get_column_value_as_utf8(typeDef, mdtTypeDef_TypeNamespace, &typeNamespace)
+ || !md_set_column_value_as_utf8(typeRef, mdtTypeRef_TypeNamespace, typeNamespace))
+ {
+ return E_FAIL;
+ }
+
+ resolutionScope = typeRef;
+ onRowAdded(typeRef);
+ }
+
+ *targetTypeDef = resolutionScope;
+ }
+ catch (std::bad_alloc const&)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ return S_OK;
+}
+
+namespace
+{
+ bool FindModuleRef(mdhandle_t image, char const* moduleName, mdcursor_t* existingModuleRef)
+ {
+ mdcursor_t c;
+ uint32_t count;
+ if (!md_create_cursor(image, mdtid_ModuleRef, &c, &count))
+ return false;
+
+ for (uint32_t i = 0; i < count; i++, md_cursor_next(&c))
+ {
+ char const* name;
+ if (!md_get_column_value_as_utf8(c, mdtModuleRef_Name, &name))
+ return false;
+
+ if (std::strcmp(name, moduleName) == 0)
+ {
+ *existingModuleRef = c;
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ // Given a type name and type namespace for a type in a (possibly multi-module) assembly,
+ // import a reference to the type into the target module.
+ // This function also handles type forwards into the target assembly.
+ HRESULT ImportScopeForTypeByNameInAssembly(
+ char const* typeName,
+ char const* typeNamespace,
+ mdhandle_t module,
+ mdhandle_t assembly,
+ std::function onRowAdded,
+ mdcursor_t* importedScope
+ )
+ {
+ // Search the ExportedType table in the targetAssembly for a type with the given name or namespace.
+ // An empty ExportedType table is okay.
+ mdcursor_t exportedType;
+ uint32_t count;
+ bool foundExportedType = false;
+ if (md_create_cursor(assembly, mdtid_ExportedType, &exportedType, &count))
+ {
+ for (uint32_t i = 0; i < count; ++i, md_cursor_next(&exportedType))
+ {
+ char const* exportedTypeName;
+ if (!md_get_column_value_as_utf8(exportedType, mdtExportedType_TypeName, &exportedTypeName))
+ return E_FAIL;
+
+ char const* exportedTypeNamespace;
+ if (!md_get_column_value_as_utf8(exportedType, mdtExportedType_TypeNamespace, &exportedTypeNamespace))
+ return E_FAIL;
+
+ if (std::strcmp(typeName, exportedTypeName) == 0 && std::strcmp(typeNamespace, exportedTypeNamespace) == 0)
+ {
+ foundExportedType = true;
+ break;
+ }
+ }
+ }
+
+ if (foundExportedType)
+ {
+ // If we found an ExportedType, then the type is defined in another module or is forwarded to another assembly.
+ // We need to find the imported scope for the type.
+ mdcursor_t implementation;
+ if (!md_get_column_value_as_cursor(exportedType, mdtExportedType_Implementation, &implementation))
+ return E_FAIL;
+
+ switch (GetTokenTypeFromCursor(implementation))
+ {
+ // If the ExportedType.Implementation is a File:
+ // - If the File refers to module's module, then we can use the module cursor in module as the imported scope.
+ // - If the File refers to another module, then we'll create a ModuleRef to that module.
+ case mdtFile:
+ {
+ char const* fileName;
+ if (!md_get_column_value_as_utf8(implementation, mdtFile_Name, &fileName))
+ return E_FAIL;
+
+ mdcursor_t moduleCursor;
+ if (!md_token_to_cursor(module, TokenFromRid(1, mdtModule), &moduleCursor))
+ return E_FAIL;
+
+ char const* moduleName;
+ if (!md_get_column_value_as_utf8(moduleCursor, mdtModule_Name, &moduleName))
+ return E_FAIL;
+
+ if (std::strcmp(fileName, moduleName) == 0)
+ {
+ *importedScope = moduleCursor;
+ }
+ else
+ {
+ if (!FindModuleRef(module, fileName, importedScope))
+ {
+ md_added_row_t moduleRef;
+ if (!md_append_row(module, mdtid_ModuleRef, &moduleRef))
+ return E_FAIL;
+
+ if (!md_set_column_value_as_utf8(moduleRef, mdtModuleRef_Name, fileName))
+ return E_FAIL;
+
+ *importedScope = moduleRef;
+ onRowAdded(moduleRef);
+ }
+ }
+ return S_OK;
+ }
+ // If the ExportedType.Implementation is an AssemblyRef, then we'll use that as the imported scope.
+ // COMPAT-BREAK: CoreCLR does not support this case (it assumes that this ExportedType entry is never a type forwarder).
+ case mdtAssemblyRef:
+ return ImportReferenceToAssemblyRef(implementation, module, assembly, onRowAdded, importedScope);
+
+ // If the ExportedType.Implementation is an ExportedType, then we're in an error scenario.
+ case mdtExportedType:
+ return E_FAIL;
+ default:
+ assert(false);
+ return E_FAIL;
+ }
+ }
+
+ // If we couldn't find an ExportedType, then we need to search the TypeDef table in the assembly.
+ // We must be able to find the type here, otherwise the metadata is invalid as we can't make a reference to a type we can't find.
+ mdcursor_t typeDef;
+ if (!md_create_cursor(assembly, mdtid_TypeDef, &typeDef, &count))
+ return E_FAIL;
+
+ for (uint32_t i = 0; i < count; ++i, md_cursor_next(&typeDef))
+ {
+ char const* typeDefName;
+ if (!md_get_column_value_as_utf8(typeDef, mdtTypeDef_TypeName, &typeDefName))
+ return E_FAIL;
+
+ char const* typeDefNamespace;
+ if (!md_get_column_value_as_utf8(typeDef, mdtTypeDef_TypeNamespace, &typeDefNamespace))
+ return E_FAIL;
+
+ if (std::strcmp(typeName, typeDefName) == 0 && std::strcmp(typeNamespace, typeDefNamespace) == 0)
+ {
+ // Make sure that this type is not nested.
+ // For this to be the same type, it must not be a nested type.
+ mdcursor_t nestedType;
+ uint32_t nestedTypeCount;
+ if (!md_create_cursor(assembly, mdtid_NestedClass, &nestedType, &nestedTypeCount))
+ return E_FAIL;
+
+ mdToken typeDefToken;
+ if (!md_cursor_to_token(typeDef, &typeDefToken))
+ return CLDB_E_FILE_CORRUPT;
+
+ if (md_find_row_from_cursor(nestedType, mdtNestedClass_NestedClass, RidFromToken(typeDefToken), &nestedType))
+ return E_FAIL;
+
+ // If we found the type defined in the assembly, then the correct imported scope is the assembly module.
+ // COMPAT-BREAK: CLR and CoreCLR always use the module token as the ResolutionScope here,
+ // which is invalid as the type lives in the Assembly manifest module, which may not be the current module.
+ // When the assembly module is the manifest module, it ends up being correct,
+ // but when the assembly manifest module is a different module, the TypeRef will not resolve.
+ mdcursor_t assemblyModule;
+ if (!md_token_to_cursor(assembly, TokenFromRid(1, mdtModule), &assemblyModule))
+ return CLDB_E_FILE_CORRUPT;
+
+ char const* assemblyModuleName;
+ if (!md_get_column_value_as_utf8(assemblyModule, mdtModule_Name, &assemblyModuleName))
+ return E_FAIL;
+
+ mdcursor_t moduleCursor;
+ if (!md_token_to_cursor(module, TokenFromRid(1, mdtModule), &moduleCursor))
+ return CLDB_E_FILE_CORRUPT;
+
+ char const* moduleName;
+ if (!md_get_column_value_as_utf8(moduleCursor, mdtModule_Name, &moduleName))
+ return E_FAIL;
+
+ if (std::strcmp(assemblyModuleName, moduleName) == 0)
+ {
+ // If the assembly module has the same name as the current module,
+ // assume that the assembly manifest module is the same module as the current module.
+ *importedScope = moduleCursor;
+ }
+ else if (!FindModuleRef(module, assemblyModuleName, importedScope))
+ {
+ md_added_row_t moduleRef;
+ if (!md_append_row(module, mdtid_ModuleRef, &moduleRef))
+ return E_FAIL;
+
+ if (!md_set_column_value_as_utf8(moduleRef, mdtModuleRef_Name, assemblyModuleName))
+ return E_FAIL;
+
+ *importedScope = moduleRef;
+ onRowAdded(moduleRef);
+ }
+ return S_OK;
+ }
+ }
+ return CLDB_E_RECORD_NOTFOUND;
+ }
+
+ HRESULT AssemblyRefPointsToAssembly(
+ mdcursor_t assemblyRef,
+ mdcursor_t assembly)
+ {
+ HRESULT hr;
+ // Compare version, Name, Locale, and PublicKeyOrToken (possibly creating token from the assembly's key if needed)
+ uint32_t refMajorVersion;
+ if (!md_get_column_value_as_constant(assemblyRef, mdtAssemblyRef_MajorVersion, &refMajorVersion))
+ return CLDB_E_FILE_CORRUPT;
+
+ uint32_t majorVersion;
+ if (!md_get_column_value_as_constant(assembly, mdtAssembly_MajorVersion, &majorVersion))
+ return CLDB_E_FILE_CORRUPT;
+
+ if (refMajorVersion != majorVersion)
+ return S_FALSE;
+
+ uint32_t refMinorVersion;
+ if (!md_get_column_value_as_constant(assemblyRef, mdtAssemblyRef_MinorVersion, &refMinorVersion))
+ return CLDB_E_FILE_CORRUPT;
+
+ uint32_t minorVersion;
+ if (!md_get_column_value_as_constant(assembly, mdtAssembly_MinorVersion, &minorVersion))
+ return CLDB_E_FILE_CORRUPT;
+
+ if (refMinorVersion != minorVersion)
+ return S_FALSE;
+
+ uint32_t refBuildNumber;
+ if (!md_get_column_value_as_constant(assemblyRef, mdtAssemblyRef_BuildNumber, &refBuildNumber))
+ return CLDB_E_FILE_CORRUPT;
+
+ uint32_t buildNumber;
+ if (!md_get_column_value_as_constant(assembly, mdtAssembly_BuildNumber, &buildNumber))
+ return CLDB_E_FILE_CORRUPT;
+
+ if (refBuildNumber != buildNumber)
+ return S_FALSE;
+
+ uint32_t refRevisionNumber;
+ if (!md_get_column_value_as_constant(assemblyRef, mdtAssemblyRef_RevisionNumber, &refRevisionNumber))
+ return CLDB_E_FILE_CORRUPT;
+
+ uint32_t revisionNumber;
+ if (!md_get_column_value_as_constant(assembly, mdtAssembly_RevisionNumber, &revisionNumber))
+ return CLDB_E_FILE_CORRUPT;
+
+ if (refRevisionNumber != revisionNumber)
+ return S_FALSE;
+
+ char const* refName;
+ if (!md_get_column_value_as_utf8(assemblyRef, mdtAssemblyRef_Name, &refName))
+ return CLDB_E_FILE_CORRUPT;
+
+ char const* name;
+ if (!md_get_column_value_as_utf8(assembly, mdtAssembly_Name, &name))
+ return CLDB_E_FILE_CORRUPT;
+
+ if (std::strcmp(refName, name) != 0)
+ return S_FALSE;
+
+ char const* refCulture;
+ if (!md_get_column_value_as_utf8(assemblyRef, mdtAssemblyRef_Culture, &refCulture))
+ return CLDB_E_FILE_CORRUPT;
+
+ char const* culture;
+ if (!md_get_column_value_as_utf8(assembly, mdtAssembly_Culture, &culture))
+ return CLDB_E_FILE_CORRUPT;
+
+ if (std::strcmp(refCulture, culture) != 0)
+ return S_FALSE;
+
+ uint8_t const* refPublicKeyOrToken;
+ uint32_t refPublicKeyOrTokenLength;
+ if (!md_get_column_value_as_blob(assemblyRef, mdtAssemblyRef_PublicKeyOrToken, &refPublicKeyOrToken, &refPublicKeyOrTokenLength))
+ return CLDB_E_FILE_CORRUPT;
+
+ uint8_t const* publicKey;
+ uint32_t publicKeyLength;
+ if (!md_get_column_value_as_blob(assembly, mdtAssembly_PublicKey, &publicKey, &publicKeyLength))
+ return CLDB_E_FILE_CORRUPT;
+
+ if ((refPublicKeyOrTokenLength == 0) != (publicKeyLength == 0))
+ return S_FALSE;
+
+ if (refPublicKeyOrTokenLength != 0)
+ {
+ uint32_t refFlags;
+ if (!md_get_column_value_as_constant(assemblyRef, mdtAssemblyRef_Flags, &refFlags))
+ return CLDB_E_FILE_CORRUPT;
+
+ if (IsAfPublicKey(refFlags))
+ {
+ // If we have a full public key for the reference, then we can compare the full public key.
+ if (refPublicKeyOrTokenLength != publicKeyLength || std::memcmp(refPublicKeyOrToken, publicKey, publicKeyLength) != 0)
+ return S_FALSE;
+
+ return S_OK;
+ }
+
+ StrongNameToken asmPublicKeyToken;
+ RETURN_IF_FAILED(StrongNameTokenFromPublicKey({ publicKey, publicKeyLength }, asmPublicKeyToken));
+
+ if (refPublicKeyOrTokenLength != asmPublicKeyToken.size() || !std::equal(asmPublicKeyToken.begin(), asmPublicKeyToken.end(), refPublicKeyOrToken))
+ return S_FALSE;
+
+ return S_OK;
+ }
+ return S_OK;
+ }
+
+ HRESULT ImportReferenceToTypeRef(
+ mdcursor_t sourceTypeRef,
+ mdhandle_t sourceAssembly,
+ span sourceAssemblyHash,
+ mdhandle_t targetAssembly,
+ mdhandle_t targetModule,
+ std::function onRowAdded,
+ mdcursor_t* targetTypeRef)
+ {
+ assert(sourceAssembly != nullptr && targetAssembly != nullptr && targetModule != nullptr);
+
+ HRESULT hr;
+ std::stack typesForTypeRefs;
+ typesForTypeRefs.push(sourceTypeRef);
+
+ mdcursor_t scope = sourceTypeRef;
+ while (GetTokenTypeFromCursor(scope) == mdtTypeRef)
+ {
+ mdcursor_t resolutionScope;
+ if (!md_get_column_value_as_cursor(scope, mdtTypeRef_ResolutionScope, &resolutionScope))
+ return E_FAIL;
+
+ typesForTypeRefs.push(resolutionScope);
+ scope = resolutionScope;
+ }
+
+ mdhandle_t sourceModule = md_extract_handle_from_cursor(sourceTypeRef);
+ mdguid_t targetModuleMvid = {};
+ mdguid_t targetAssemblyMvid = {};
+ mdguid_t sourceAssemblyMvid = {};
+ mdguid_t sourceModuleMvid = {};
+ RETURN_IF_FAILED(GetMvid(targetModule, &targetModuleMvid));
+ RETURN_IF_FAILED(GetMvid(targetAssembly, &targetAssemblyMvid));
+ RETURN_IF_FAILED(GetMvid(sourceModule, &sourceModuleMvid));
+ RETURN_IF_FAILED(GetMvid(sourceAssembly, &sourceAssemblyMvid));
+
+ bool sameModuleMvid = std::memcmp(&targetModuleMvid, &sourceModuleMvid, sizeof(mdguid_t)) == 0;
+ bool sameAssemblyMvid = std::memcmp(&targetAssemblyMvid, &sourceAssemblyMvid, sizeof(mdguid_t)) == 0;
+
+ // II.22.38 1. Valid ResolutionScope values
+ // - null
+ // - TypeRef token
+ // - ModuleRef token
+ // - Module token
+ // - AssemblyRef token
+ mdcursor_t targetOutermostScope = {};
+ if (sameAssemblyMvid && sameModuleMvid)
+ {
+ mdToken token;
+ if (!md_cursor_to_token(sourceTypeRef, &token))
+ return E_FAIL;
+
+ if (!md_token_to_cursor(targetModule, token, targetTypeRef))
+ return CLDB_E_FILE_CORRUPT;
+
+ return S_OK;
+ }
+ else if (sameAssemblyMvid && !sameModuleMvid)
+ {
+ mdToken scopeToken;
+ if (!md_cursor_to_token(scope, &scopeToken))
+ return E_FAIL;
+
+ if (IsNilToken(scopeToken))
+ {
+ // A Nil ResolutionScope means a reference to an ExportedType entry
+ // in the assembly.
+ // Since the source and target assemblies have the same identity,
+ // we can use the Nil token and we don't have to resolve the ExportedType
+ // as the target and source assemblies are the same.
+ targetOutermostScope = {};
+ }
+ else if (TypeFromToken(scopeToken) == mdtModule)
+ {
+ // Create a ModuleRef from the target module to the source module.
+ char const* moduleName;
+ if (!md_get_column_value_as_utf8(scope, mdtModule_Name, &moduleName))
+ return CLDB_E_FILE_CORRUPT;
+
+ if (!FindModuleRef(targetModule, moduleName, &targetOutermostScope))
+ {
+ md_added_row_t moduleRef;
+ if (!md_append_row(targetModule, mdtid_ModuleRef, &moduleRef))
+ return E_FAIL;
+
+ if (!md_set_column_value_as_utf8(moduleRef, mdtModuleRef_Name, moduleName))
+ return E_FAIL;
+
+ targetOutermostScope = moduleRef;
+ onRowAdded(moduleRef);
+ }
+ }
+ else if (TypeFromToken(scopeToken) == mdtModuleRef)
+ {
+ // If this ModuleRef points from the source module into the target module,
+ // then we can use the Module token as the outermost scope.
+ // otherwise, create ModuleRef to the module that the source ModuleRef points to.
+ char const* moduleName;
+ if (!md_get_column_value_as_utf8(scope, mdtModuleRef_Name, &moduleName))
+ return CLDB_E_FILE_CORRUPT;
+
+ mdcursor_t targetModuleCursor;
+ uint32_t count;
+ if (!md_create_cursor(targetModule, mdtid_Module, &targetModuleCursor, &count))
+ return E_FAIL;
+
+ char const* targetModuleName;
+ if (!md_get_column_value_as_utf8(targetModuleCursor, mdtModule_Name, &targetModuleName))
+ return E_FAIL;
+
+ if (std::strcmp(moduleName, targetModuleName) == 0)
+ {
+ targetOutermostScope = targetModuleCursor;
+ }
+ else if (!FindModuleRef(targetModule, moduleName, &targetOutermostScope))
+ {
+ md_added_row_t moduleRef;
+ if (!md_append_row(targetModule, mdtid_ModuleRef, &moduleRef))
+ return E_FAIL;
+
+ if (!md_set_column_value_as_utf8(moduleRef, mdtModuleRef_Name, moduleName))
+ return E_FAIL;
+
+ targetOutermostScope = moduleRef;
+ onRowAdded(moduleRef);
+ }
+ }
+ else if (TypeFromToken(scopeToken) == mdtAssemblyRef)
+ {
+ // Copy the AssemblyRef from the source module to the target module.
+ RETURN_IF_FAILED(ImportReferenceToAssemblyRef(scope, targetModule, targetAssembly, onRowAdded, &targetOutermostScope));
+ }
+ else
+ {
+ return E_INVALIDARG;
+ }
+ }
+ else if (!sameAssemblyMvid)
+ {
+ assert(!sameModuleMvid);
+
+ mdToken scopeToken;
+ if (!md_cursor_to_token(scope, &scopeToken))
+ return E_FAIL;
+
+ if (IsNilToken(scopeToken))
+ {
+ // Lookup ExportedType entry in the source assembly for this type.
+ mdcursor_t exportedType;
+ uint32_t count;
+ bool foundExportedType = false;
+ mdcursor_t implementation = {};
+ if (md_create_cursor(sourceAssembly, mdtid_ExportedType, &exportedType, &count))
+ {
+ mdcursor_t outermostTypeRef = typesForTypeRefs.top();
+ char const* typeName;
+ if (!md_get_column_value_as_utf8(outermostTypeRef, mdtTypeRef_TypeName, &typeName))
+ return E_FAIL;
+
+ char const* typeNamespace;
+ if (!md_get_column_value_as_utf8(outermostTypeRef, mdtTypeRef_TypeNamespace, &typeNamespace))
+ return E_FAIL;
+
+ // If we can't find an ExportedType entry for this type, we'll just move over the TypeRef with a Nil ResolutionScope.
+ for (uint32_t i = 0; i < count; ++i, md_cursor_next(&exportedType))
+ {
+ char const* exportedTypeName;
+ if (!md_get_column_value_as_utf8(exportedType, mdtExportedType_TypeName, &exportedTypeName))
+ return E_FAIL;
+
+ char const* exportedTypeNamespace;
+ if (!md_get_column_value_as_utf8(exportedType, mdtExportedType_TypeNamespace, &exportedTypeNamespace))
+ return E_FAIL;
+
+ if (std::strcmp(typeName, exportedTypeName) == 0 && std::strcmp(typeNamespace, exportedTypeNamespace) == 0)
+ {
+ if (!md_get_column_value_as_cursor(exportedType, mdtExportedType_Implementation, &implementation))
+ return E_FAIL;
+
+ foundExportedType = true;
+ break;
+ }
+ }
+ }
+
+ if (foundExportedType)
+ {
+ switch (GetTokenTypeFromCursor(implementation))
+ {
+ case mdtFile:
+ {
+ // This type is from a file in the source assembly, so we need to create an AssemblyRef to the source assembly.
+ RETURN_IF_FAILED(ImportReferenceToAssembly(sourceAssembly, sourceAssemblyHash, targetModule, targetAssembly, onRowAdded, &targetOutermostScope));
+ }
+ FALLTHROUGH;
+ case mdtAssemblyRef:
+ {
+ // This type is a type-forward from another assembly.
+ // Reset the scope and scopeToken variables to this AssemblyRef.
+ // If this is a type forward from the target assembly, we want to resolve it to the target assembly to avoid a self-referential
+ // AssemblyRef.
+ scope = implementation;
+ if (!md_cursor_to_token(scope, &scopeToken))
+ return CLDB_E_FILE_CORRUPT;
+ break;
+ }
+ case mdtExportedType:
+ {
+ assert(false && "We should be looking at the outermost type already. Therefore, the ExportedType entry for this type should not be enclosed in another type.");
+ return E_FAIL;
+ }
+ default:
+ {
+ assert(false && "Unexpected token type for ExportedType.Implementation");
+ return E_FAIL;
+ }
+ }
+ }
+ else
+ {
+ // COMPAT-BREAK: CoreCLR and CLR treat a type that is not found in the ExportedType table as though it is an imported type from the target assembly.
+ // This is incorrect per the spec as the type is not defined in the target assembly.
+ // Early .NET compilers wouldn't always have an AssemblyRef to the core library (mscorlib), so we could end up in a situation where we'd be importing
+ // a TypeRef to mscorlib from a module that doesn't have a reference to mscorlib.
+ // In this case, this branch would treat the ResolutionScope as the Nil token.
+ // Nowadays, all of the managed code compilers correctly emit references to all types, including the core library (which in many cases now is not mscorlib).
+ // Additionally, multimodule assemblies aren't supported by CoreCLR, so we won't even reach this branch anyway (the whole IsNilToken(scopeToken) branch will only happen in multimodule scenarios).
+
+ // If we can't find the type in the source assembly, then we can't import it as we can't find the definition anywhere.
+ mdcursor_t sourceAssemblyTypeDef;
+ uint32_t sourceAssemblyTypeDefCount;
+ if (!md_create_cursor(sourceAssembly, mdtid_TypeDef, &sourceAssemblyTypeDef, &sourceAssemblyTypeDefCount))
+ return E_FAIL;
+
+ mdcursor_t outermostTypeRef = typesForTypeRefs.top();
+ char const* typeName;
+ if (!md_get_column_value_as_utf8(outermostTypeRef, mdtTypeRef_TypeName, &typeName))
+ return E_FAIL;
+
+ char const* typeNamespace;
+ if (!md_get_column_value_as_utf8(outermostTypeRef, mdtTypeRef_TypeNamespace, &typeNamespace))
+ return E_FAIL;
+
+ bool found = false;
+ for (uint32_t i = 0; i < sourceAssemblyTypeDefCount; ++i, md_cursor_next(&sourceAssemblyTypeDef))
+ {
+ char const* sourceAssemblyTypeDefName;
+ if (!md_get_column_value_as_utf8(sourceAssemblyTypeDef, mdtTypeDef_TypeName, &sourceAssemblyTypeDefName))
+ return E_FAIL;
+
+ char const* sourceAssemblyTypeDefNamespace;
+ if (!md_get_column_value_as_utf8(sourceAssemblyTypeDef, mdtTypeDef_TypeNamespace, &sourceAssemblyTypeDefNamespace))
+ return E_FAIL;
+
+ if (std::strcmp(typeName, sourceAssemblyTypeDefName) != 0 && std::strcmp(typeNamespace, sourceAssemblyTypeDefNamespace) != 0)
+ continue;
+
+ mdcursor_t sourceAssemblyTypeDefEnclosingClass;
+ uint32_t ignored;
+ if (md_create_cursor(sourceAssembly, mdtid_NestedClass, &sourceAssemblyTypeDefEnclosingClass, &ignored)
+ && md_find_row_from_cursor(sourceAssemblyTypeDefEnclosingClass, mdtNestedClass_NestedClass, TokenFromRid(i + 1, mdtTypeDef), &sourceAssemblyTypeDefEnclosingClass))
+ {
+ // If the type is nested, then it can't be the right type as we're already at the outermost scope.
+ continue;
+ }
+
+ // If we found the type defined in the source assembly, then the correct imported scope is the assembly module.
+ mdcursor_t importAssembly;
+ if (!md_token_to_cursor(sourceAssembly, TokenFromRid(1, mdtAssembly), &importAssembly))
+ return E_FAIL;
+
+ // Add a reference to the assembly in the target module and assembly.
+ RETURN_IF_FAILED(ImportReferenceToAssembly(sourceAssembly, sourceAssemblyHash, targetModule, targetAssembly, onRowAdded, &targetOutermostScope));
+ found = true;
+ break;
+ }
+
+ if (!found)
+ return CLDB_E_RECORD_NOTFOUND;
+ }
+ }
+ else if (TypeFromToken(scopeToken) == mdtModule)
+ {
+ // Create an AssemblyRef from the destination assembly to the source assembly.
+ RETURN_IF_FAILED(ImportReferenceToAssembly(sourceAssembly, sourceAssemblyHash, targetModule, targetAssembly, onRowAdded, &targetOutermostScope));
+ }
+
+ // The IsNilToken case can resolve to an ExportedType entry whose scope is an AssemblyRef.
+ // We want to catch that case here, so we split this out to a separate if instead of a chained else if.
+ if (TypeFromToken(scopeToken) == mdtAssemblyRef)
+ {
+ // Convert from AssemblyRef to Assembly if the source AssemblyRef points to the target assembly.
+ mdcursor_t targetAssemblyCursor;
+ uint32_t count;
+ if (!md_create_cursor(targetModule, mdtid_Assembly, &targetAssemblyCursor, &count))
+ return E_FAIL;
+
+ RETURN_IF_FAILED(AssemblyRefPointsToAssembly(scope, targetAssemblyCursor));
+ if (hr == S_OK)
+ {
+ // The type is defined in the target assembly, so we need to correctly define its scope.
+ mdcursor_t outermostTypeRef = typesForTypeRefs.top();
+ char const* typeName;
+ if (!md_get_column_value_as_utf8(outermostTypeRef, mdtTypeRef_TypeName, &typeName))
+ return E_FAIL;
+
+ char const* typeNamespace;
+ if (!md_get_column_value_as_utf8(outermostTypeRef, mdtTypeRef_TypeNamespace, &typeNamespace))
+ return E_FAIL;
+
+ RETURN_IF_FAILED(ImportScopeForTypeByNameInAssembly(
+ typeName,
+ typeNamespace,
+ targetModule,
+ targetAssembly,
+ onRowAdded,
+ &targetOutermostScope));
+ }
+ else
+ {
+ // The type is defined in another assembly. We need to create an AssemblyRef to that assembly.
+ assert(hr == S_FALSE);
+ RETURN_IF_FAILED(ImportReferenceToAssemblyRef(scope, targetModule, targetAssembly, onRowAdded, &targetOutermostScope));
+ }
+ }
+ else if (TypeFromToken(scopeToken) == mdtModuleRef)
+ {
+ // In this case, the type is from the source assembly, but a different module than the source module.
+ // Since the source assembly and target assembly are different, we can't make a module reference to the type's module
+ // as module references are only within assembly boundaries.
+ // Make an AssemblyRef to the source assembly from the target assembly.
+ RETURN_IF_FAILED(ImportReferenceToAssembly(sourceAssembly, sourceAssemblyHash, targetModule, targetAssembly, onRowAdded, &targetOutermostScope));
+ }
+ else
+ {
+ return E_FAIL;
+ }
+ }
+
+ assert(md_extract_handle_from_cursor(targetOutermostScope) == targetModule);
+
+ mdToken targetOutermostScopeToken;
+ if (!md_cursor_to_token(targetOutermostScope, &targetOutermostScopeToken))
+ return E_FAIL;
+
+ if (TypeFromToken(targetOutermostScopeToken) == mdtModule && !IsNilToken(targetOutermostScopeToken))
+ {
+ // Find a nested TypeDef in the target module that matches the name and namespace of the source TypeRef.
+ // We've resolved the TypeRef's outermost scope to be in the target module,
+ // so the TypeDef must be in the target module.
+ mdcursor_t enclosingScope = targetOutermostScope;
+
+ mdcursor_t targetTypeDef;
+ uint32_t targetTypeDefCount;
+ if (!md_create_cursor(targetModule, mdtid_TypeDef, &targetTypeDef, &targetTypeDefCount))
+ return E_FAIL;
+ for (; !typesForTypeRefs.empty(); typesForTypeRefs.pop())
+ {
+ mdcursor_t sourceEnclosingTypeRef = typesForTypeRefs.top();
+
+ char const* typeName;
+ if (!md_get_column_value_as_utf8(sourceEnclosingTypeRef, mdtTypeRef_TypeName, &typeName))
+ return E_FAIL;
+
+ char const* typeNamespace;
+ if (!md_get_column_value_as_utf8(sourceEnclosingTypeRef, mdtTypeRef_TypeNamespace, &typeNamespace))
+ return E_FAIL;
+
+ mdToken enclosingScopeToken;
+ if (!md_cursor_to_token(enclosingScope, &enclosingScopeToken))
+ return E_FAIL;
+
+ bool shouldHaveEnclosingType = !IsNilToken(enclosingScopeToken) && TypeFromToken(enclosingScopeToken) == mdtTypeDef;
+ // The TypeDef table must be sorted such that enclosing types are defined before nesting types.
+ // Therefore, we can search the table linearly.
+ // See the commentary in II.22 before II.22.1 for more information.
+ bool found = false;
+ do
+ {
+ char const* targetTypeName;
+ if (!md_get_column_value_as_utf8(targetTypeDef, mdtTypeDef_TypeName, &targetTypeName))
+ return E_FAIL;
+
+ char const* targetTypeNamespace;
+ if (!md_get_column_value_as_utf8(targetTypeDef, mdtTypeDef_TypeNamespace, &targetTypeNamespace))
+ return E_FAIL;
+
+ // Check the name of the type.
+ if (std::strcmp(typeName, targetTypeName) != 0 || std::strcmp(typeNamespace, targetTypeNamespace) != 0)
+ continue;
+
+ // Now that we've validated that the target TypeDef has an enclosing type,
+ // we need to validate that the enclosing type matches the source TypeRef's enclosing type.
+ mdToken targetTypeDefToken;
+ if (!md_cursor_to_token(targetTypeDef, &targetTypeDefToken))
+ return E_FAIL;
+
+ mdcursor_t targetNestedClass;
+ uint32_t targetNestedClassCount;
+ if (shouldHaveEnclosingType !=
+ (md_create_cursor(targetModule, mdtid_NestedClass, &targetNestedClass, &targetNestedClassCount)
+ && md_find_row_from_cursor(targetNestedClass, mdtNestedClass_NestedClass, RidFromToken(targetTypeDefToken), &targetNestedClass)))
+ {
+ // If the source TypeRef has an enclosing type, then the target TypeDef must have an enclosing type and vice versa.
+ continue;
+ }
+
+ if (shouldHaveEnclosingType)
+ {
+ mdToken targetEnclosingType;
+ if (!md_get_column_value_as_token(targetNestedClass, mdtNestedClass_EnclosingClass, &targetEnclosingType))
+ return E_FAIL;
+
+ // If the enclosing type doesn't match, then we are in a failure state.
+ if (enclosingScopeToken != targetTypeDefToken)
+ return CLDB_E_RECORD_NOTFOUND;
+ }
+
+ found = true;
+ break;
+ } while (md_cursor_next(&targetTypeDef));
+
+ if (!found)
+ return CLDB_E_RECORD_NOTFOUND;
+
+ enclosingScope = targetTypeDef;
+ }
+ *targetTypeRef = enclosingScope;
+ return S_OK;
+ }
+
+ mdcursor_t resolutionScope = targetOutermostScope;
+ for (; !typesForTypeRefs.empty(); typesForTypeRefs.pop())
+ {
+ mdcursor_t sourceEnclosingTypeRef = typesForTypeRefs.top();
+ md_added_row_t targetEnclosingTypeRef;
+ if (!md_append_row(targetModule, mdtid_TypeRef, &targetEnclosingTypeRef))
+ return E_FAIL;
+
+ if (!md_set_column_value_as_cursor(targetEnclosingTypeRef, mdtTypeRef_ResolutionScope, resolutionScope))
+ return E_FAIL;
+
+ char const* typeName;
+ if (!md_get_column_value_as_utf8(sourceEnclosingTypeRef, mdtTypeRef_TypeName, &typeName)
+ || !md_set_column_value_as_utf8(targetEnclosingTypeRef, mdtTypeRef_TypeName, typeName))
+ {
+ return E_FAIL;
+ }
+
+ char const* typeNamespace;
+ if (!md_get_column_value_as_utf8(sourceEnclosingTypeRef, mdtTypeRef_TypeNamespace, &typeNamespace)
+ || !md_set_column_value_as_utf8(targetEnclosingTypeRef, mdtTypeRef_TypeNamespace, typeNamespace))
+ {
+ return E_FAIL;
+ }
+
+ resolutionScope = targetEnclosingTypeRef;
+ onRowAdded(targetEnclosingTypeRef);
+ }
+
+ *targetTypeRef = resolutionScope;
+
+ return S_OK;
+ }
+}
+
+HRESULT ImportReferenceToTypeDefOrRefOrSpec(
+ mdhandle_t sourceAssembly,
+ mdhandle_t sourceModule,
+ span sourceAssemblyHash,
+ mdhandle_t targetAssembly,
+ mdhandle_t targetModule,
+ std::function onRowAdded,
+ mdToken* importedToken)
+{
+ HRESULT hr;
+ mdcursor_t sourceCursor;
+ if (!md_token_to_cursor(sourceModule, *importedToken, &sourceCursor))
+ return CLDB_E_FILE_CORRUPT;
+
+ switch (GetTokenTypeFromCursor(sourceCursor))
+ {
+ case mdtTypeDef:
+ {
+ mdcursor_t targetCursor;
+ RETURN_IF_FAILED(ImportReferenceToTypeDef(sourceCursor, sourceAssembly, sourceAssemblyHash, targetAssembly, targetModule, true, onRowAdded, &targetCursor));
+ if (!md_cursor_to_token(targetCursor, importedToken))
+ return E_FAIL;
+
+ return S_OK;
+ }
+ case mdtTypeRef:
+ {
+ mdcursor_t targetCursor;
+ RETURN_IF_FAILED(ImportReferenceToTypeRef(sourceCursor, sourceAssembly, sourceAssemblyHash, targetAssembly, targetModule, onRowAdded, &targetCursor));
+ if (!md_cursor_to_token(targetCursor, importedToken))
+ return E_FAIL;
+
+ return S_OK;
+ }
+ case mdtTypeSpec:
+ {
+ uint8_t const* signature;
+ uint32_t signatureLength;
+ if (!md_get_column_value_as_blob(sourceCursor, mdtTypeSpec_Signature, &signature, &signatureLength))
+ return E_FAIL;
+
+ inline_span importedSignature;
+ RETURN_IF_FAILED(ImportTypeSpecBlob(sourceAssembly, sourceModule, sourceAssemblyHash, targetAssembly, targetModule, {signature, signatureLength}, onRowAdded, importedSignature));
+
+ md_added_row_t typeSpec;
+ if (!md_append_row(targetModule, mdtid_TypeSpec, &typeSpec))
+ return E_FAIL;
+
+ if (!md_set_column_value_as_blob(typeSpec, mdtTypeSpec_Signature, importedSignature.data(), (uint32_t)importedSignature.size()))
+ return E_FAIL;
+
+ if (!md_cursor_to_token(typeSpec, importedToken))
+ return E_FAIL;
+
+ return S_OK;
+ }
+ default:
+ return E_INVALIDARG;
+ }
+}
+
+HRESULT DefineImportMember(
+ IMetaDataEmit* emit, // [In] Module into which the Member is imported.
+ IMetaDataAssemblyImport *pAssemImport, // [IN] Assembly containing the Member.
+ void const *pbHashValue, // [IN] Hash Blob for Assembly.
+ ULONG cbHashValue, // [IN] Count of bytes.
+ IMetaDataImport *pImport, // [IN] Import scope, with member.
+ mdToken mbMember, // [IN] Member in import scope.
+ IMetaDataAssemblyEmit *pAssemEmit, // [IN] Assembly into which the Member is imported.
+ mdToken tkImport, // [IN] Classref or classdef in emit scope.
+ mdMemberRef *pmr) // [OUT] Put member ref here.
+{
+ HRESULT hr;
+ assert(pImport && pmr);
+ assert(TypeFromToken(tkImport) == mdtTypeRef || TypeFromToken(tkImport) == mdtModuleRef ||
+ IsNilToken(tkImport) || TypeFromToken(tkImport) == mdtTypeSpec);
+ assert((TypeFromToken(mbMember) == mdtMethodDef && mbMember != mdMethodDefNil) ||
+ (TypeFromToken(mbMember) == mdtFieldDef && mbMember != mdFieldDefNil));
+
+ size_t memberNameSize = 128;
+ std::unique_ptr memberName { new WCHAR[memberNameSize] }; // Name of the imported member.
+ GUID mvidImport; // MVID of the import module.
+ GUID mvidEmit; // MVID of the emit module.
+ PCCOR_SIGNATURE pvSig; // Member's signature.
+ ULONG cbSig; // Length of member's signature.
+ ULONG translatedSigLength; // Length of translated signature.
+
+ if (TypeFromToken(mbMember) == mdtMethodDef)
+ {
+ ULONG acutalNameLength;
+ for (;;)
+ {
+ hr = pImport->GetMethodProps(mbMember, nullptr, memberName.get(), (DWORD)memberNameSize, &acutalNameLength,
+ nullptr, &pvSig, &cbSig, nullptr, nullptr);
+ if (hr == CLDB_S_TRUNCATION)
+ {
+ memberName.reset(new WCHAR[acutalNameLength]);
+ memberNameSize = (size_t)acutalNameLength;
+ continue;
+ }
+ break;
+ }
+ }
+ else // TypeFromToken(mbMember) == mdtFieldDef
+ {
+ ULONG acutalNameLength;
+ for (;;)
+ {
+ hr = pImport->GetMethodProps(mbMember, nullptr, memberName.get(),(DWORD)memberNameSize, &acutalNameLength,
+ nullptr, &pvSig,&cbSig, nullptr, nullptr);
+ if (hr == CLDB_S_TRUNCATION)
+ {
+ memberName.reset(new WCHAR[acutalNameLength]);
+ memberNameSize = (size_t)acutalNameLength;
+ continue;
+ }
+ break;
+ }
+ }
+ RETURN_IF_FAILED(hr);
+
+ ULONG sigSizeMax = cbSig * 3; // Set translated signature buffer size conservatively.
+ std::unique_ptr translatedSig { new uint8_t[sigSizeMax] };
+
+ RETURN_IF_FAILED(emit->TranslateSigWithScope(
+ pAssemImport,
+ pbHashValue,
+ cbHashValue,
+ pImport,
+ pvSig,
+ cbSig,
+ pAssemEmit,
+ emit,
+ translatedSig.get(),
+ sigSizeMax,
+ &translatedSigLength));
+
+ // Define ModuleRef for imported Member functions
+
+ // Check if the Member being imported is a global function.
+ minipal::com_ptr pEmitImport;
+ RETURN_IF_FAILED(emit->QueryInterface(IID_IMetaDataImport, (void**)&pEmitImport));
+ RETURN_IF_FAILED(pEmitImport->GetScopeProps(nullptr, 0, nullptr, &mvidEmit));
+
+ DWORD scopeNameSize;
+ RETURN_IF_FAILED(pImport->GetScopeProps(nullptr, 0, &scopeNameSize, &mvidImport));
+ if (mvidEmit != mvidImport && IsNilToken(tkImport))
+ {
+ std::unique_ptr scopeName { new WCHAR[scopeNameSize] }; // Name of the imported member's scope.
+ RETURN_IF_FAILED(pImport->GetScopeProps(scopeName.get(), scopeNameSize,
+ nullptr, nullptr));
+ RETURN_IF_FAILED(emit->DefineModuleRef(scopeName.get(), &tkImport));
+ }
+
+ // Define MemberRef base on the name, sig, and parent
+ RETURN_IF_FAILED(emit->DefineMemberRef(
+ tkImport,
+ memberName.get(),
+ translatedSig.get(),
+ translatedSigLength,
+ pmr));
+
+ return S_OK;
+}
diff --git a/src/native/dnmd/src/interfaces/importhelpers.hpp b/src/native/dnmd/src/interfaces/importhelpers.hpp
new file mode 100644
index 0000000000000..df6751193c281
--- /dev/null
+++ b/src/native/dnmd/src/interfaces/importhelpers.hpp
@@ -0,0 +1,43 @@
+#ifndef _SRC_INTERFACES_IMPORTHELPERS_HPP
+#define _SRC_INTERFACES_IMPORTHELPERS_HPP
+
+#include
+#include
+#include
+
+// Import a reference to a TypeDef row from one module and assembly pair to another.
+HRESULT ImportReferenceToTypeDef(
+ mdcursor_t sourceTypeDef,
+ mdhandle_t sourceAssembly,
+ span sourceAssemblyHash,
+ mdhandle_t targetAssembly,
+ mdhandle_t targetModule,
+ bool alwaysImport, // Always import a reference to the TypeDef, even if the source and destination modules are the same.
+ std::function onRowEdited,
+ mdcursor_t* targetTypeDef);
+
+// Import a reference to a TypeDef, TypeRef, or TypeSpec row from one module and assembly pair to another, and return a TypeDef or TypeRef or TypeSpec token
+// that can be used to refer to the imported type.
+HRESULT ImportReferenceToTypeDefOrRefOrSpec(
+ mdhandle_t sourceAssembly,
+ mdhandle_t sourceModule,
+ span sourceAssemblyHash,
+ mdhandle_t targetAssembly,
+ mdhandle_t targetModule,
+ std::function onRowAdded,
+ mdToken* importedToken);
+
+// Import a reference to a MemberRef row from one module and assembly pair to another.
+// This method works at the IMetadataEmit/Import level as it is implementation-agnostic.
+HRESULT DefineImportMember(
+ IMetaDataEmit* emit,
+ IMetaDataAssemblyImport *pAssemImport,
+ void const *pbHashValue,
+ ULONG cbHashValue,
+ IMetaDataImport *pImport,
+ mdToken mbMember,
+ IMetaDataAssemblyEmit *pAssemEmit,
+ mdToken tkImport,
+ mdMemberRef *pmr);
+
+#endif // _SRC_INTERFACES_IMPORTHELPERS_HPP
diff --git a/src/native/dnmd/src/interfaces/metadataemit.cpp b/src/native/dnmd/src/interfaces/metadataemit.cpp
new file mode 100644
index 0000000000000..f75fcd67c5647
--- /dev/null
+++ b/src/native/dnmd/src/interfaces/metadataemit.cpp
@@ -0,0 +1,3173 @@
+#include "metadataemit.hpp"
+#include "importhelpers.hpp"
+#include "signatures.hpp"
+#include "pal.hpp"
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#define RETURN_IF_FAILED(exp) \
+{ \
+ hr = (exp); \
+ if (FAILED(hr)) \
+ { \
+ return hr; \
+ } \
+}
+
+#define MD_MODULE_TOKEN TokenFromRid(1, mdtModule)
+#define MD_GLOBAL_PARENT_TOKEN TokenFromRid(1, mdtTypeDef)
+
+namespace
+{
+ void SplitTypeName(
+ char* typeName,
+ char const** nspace,
+ char const** name)
+ {
+ // Search for the last delimiter.
+ char* pos = std::strrchr(typeName, '.');
+ if (pos == nullptr)
+ {
+ // No namespace is indicated by an empty string.
+ *nspace = "";
+ *name = typeName;
+ }
+ else
+ {
+ *pos = '\0';
+ *nspace = typeName;
+ *name = pos + 1;
+ }
+ }
+}
+
+HRESULT MetadataEmit::SetModuleProps(
+ LPCWSTR szName)
+{
+ // If the name is null, we have nothing to do.
+ // COMPAT-BREAK: CoreCLR would still record the token in the EncLog in this case.
+ if (szName == nullptr)
+ return S_OK;
+
+ pal::StringConvert cvt(szName);
+ if (!cvt.Success())
+ return E_INVALIDARG;
+
+ mdcursor_t c;
+ uint32_t count;
+ if (!md_create_cursor(MetaData(), mdtid_Module, &c, &count))
+ {
+ if (md_append_row(MetaData(), mdtid_Module, &c))
+ {
+ md_commit_row_add(c);
+ }
+ else
+ {
+ return E_FAIL;
+ }
+ }
+
+ // Search for a file name in the provided path
+ // and use that as the module name.
+ char* modulePath = cvt;
+ std::size_t len = std::strlen(modulePath);
+ char const* start = modulePath;
+ for (char const* p = modulePath + len - 1; p >= modulePath; p--)
+ {
+ if (*p == '\\' || *p == '/')
+ {
+ start = p + 1;
+ break;
+ }
+ }
+
+ if (!md_set_column_value_as_utf8(c, mdtModule_Name, start))
+ return E_FAIL;
+
+ // TODO: Record ENC Log.
+
+ return S_OK;
+}
+
+HRESULT MetadataEmit::Save(
+ LPCWSTR szFile,
+ DWORD dwSaveFlags)
+{
+ if (dwSaveFlags != 0)
+ return E_INVALIDARG;
+
+ pal::StringConvert cvt(szFile);
+ if (!cvt.Success())
+ return E_INVALIDARG;
+
+ size_t saveSize;
+ md_write_to_buffer(MetaData(), nullptr, &saveSize);
+ std::unique_ptr buffer { new uint8_t[saveSize] };
+ if (!md_write_to_buffer(MetaData(), buffer.get(), &saveSize))
+ return E_FAIL;
+
+ std::FILE* file = std::fopen(cvt, "wb");
+ if (file == nullptr)
+ {
+ return E_FAIL;
+ }
+
+ size_t totalSaved = 0;
+ while (totalSaved < saveSize)
+ {
+ totalSaved += std::fwrite(buffer.get(), sizeof(uint8_t), saveSize - totalSaved, file);
+ if (ferror(file) != 0)
+ {
+ std::fclose(file);
+ return E_FAIL;
+ }
+ }
+
+ if (std::fclose(file) == EOF)
+ {
+ return E_FAIL;
+ }
+
+ return S_OK;
+}
+
+HRESULT MetadataEmit::SaveToStream(
+ IStream *pIStream,
+ DWORD dwSaveFlags)
+{
+ HRESULT hr;
+ if (dwSaveFlags != 0)
+ return E_INVALIDARG;
+
+ size_t saveSize;
+ md_write_to_buffer(MetaData(), nullptr, &saveSize);
+ std::unique_ptr buffer { new uint8_t[saveSize] };
+ md_write_to_buffer(MetaData(), buffer.get(), &saveSize);
+
+ size_t totalSaved = 0;
+ while (totalSaved < saveSize)
+ {
+ ULONG numBytesToWrite = (ULONG)std::min(saveSize, (size_t)std::numeric_limits::max());
+ RETURN_IF_FAILED(pIStream->Write((char const*)buffer.get() + totalSaved, numBytesToWrite, nullptr));
+ totalSaved += numBytesToWrite;
+ }
+
+ return pIStream->Write(buffer.get(), (ULONG)saveSize, nullptr);
+}
+
+HRESULT MetadataEmit::GetSaveSize(
+ CorSaveSize fSave,
+ DWORD *pdwSaveSize)
+{
+ // TODO: Do we want to support different save modes (as specified through dispenser options)?
+ // If so, we'll need to handle that here in addition to the ::Save* methods.
+ UNREFERENCED_PARAMETER(fSave);
+ size_t saveSize;
+ md_write_to_buffer(MetaData(), nullptr, &saveSize);
+ if (saveSize > std::numeric_limits::max())
+ return CLDB_E_TOO_BIG;
+ *pdwSaveSize = (DWORD)saveSize;
+ return S_OK;
+}
+
+HRESULT MetadataEmit::DefineTypeDef(
+ LPCWSTR szTypeDef,
+ DWORD dwTypeDefFlags,
+ mdToken tkExtends,
+ mdToken rtkImplements[],
+ mdTypeDef *ptd)
+{
+ md_added_row_t c;
+ if (!md_append_row(MetaData(), mdtid_TypeDef, &c))
+ return E_FAIL;
+
+ pal::StringConvert cvt(szTypeDef);
+ if (!cvt.Success())
+ return E_INVALIDARG;
+
+ // TODO: Check for duplicate type definitions
+
+ char const* ns;
+ char const* name;
+ SplitTypeName(cvt, &ns, &name);
+ if (!md_set_column_value_as_utf8(c, mdtTypeDef_TypeNamespace, ns))
+ return E_FAIL;
+ if (!md_set_column_value_as_utf8(c, mdtTypeDef_TypeName, name))
+ return E_FAIL;
+
+ // TODO: Handle reserved flags
+ uint32_t flags = (uint32_t)dwTypeDefFlags;
+ if (!md_set_column_value_as_constant(c, mdtTypeDef_Flags, flags))
+ return E_FAIL;
+
+ if (!md_set_column_value_as_token(c, mdtTypeDef_Extends, tkExtends))
+ return E_FAIL;
+
+ mdcursor_t fieldCursor;
+ uint32_t numFields;
+ if (!md_create_cursor(MetaData(), mdtid_Field, &fieldCursor, &numFields))
+ {
+ mdToken nilField = mdFieldDefNil;
+ if (!md_set_column_value_as_token(c, mdtTypeDef_FieldList, nilField))
+ return E_FAIL;
+ }
+ else
+ {
+ md_cursor_move(&fieldCursor, numFields);
+ if (!md_set_column_value_as_cursor(c, mdtTypeDef_FieldList, fieldCursor))
+ return E_FAIL;
+ }
+
+ mdcursor_t methodCursor;
+ uint32_t numMethods;
+ if (!md_create_cursor(MetaData(), mdtid_MethodDef, &methodCursor, &numMethods))
+ {
+ mdToken nilMethod = mdMethodDefNil;
+ if (!md_set_column_value_as_token(c, mdtTypeDef_MethodList, nilMethod))
+ return E_FAIL;
+ }
+ else
+ {
+ md_cursor_move(&methodCursor, numMethods);
+ if (!md_set_column_value_as_cursor(c, mdtTypeDef_MethodList, methodCursor))
+ return E_FAIL;
+ }
+
+ size_t i = 0;
+
+ if (rtkImplements != nullptr)
+ {
+ for (mdToken currentImplementation = rtkImplements[i]; currentImplementation != mdTokenNil; currentImplementation = rtkImplements[++i])
+ {
+ md_added_row_t interfaceImpl;
+ if (!md_append_row(MetaData(), mdtid_InterfaceImpl, &interfaceImpl))
+ return E_FAIL;
+
+ if (!md_set_column_value_as_cursor(interfaceImpl, mdtInterfaceImpl_Class, c))
+ return E_FAIL;
+
+ if (!md_set_column_value_as_token(interfaceImpl, mdtInterfaceImpl_Interface, currentImplementation))
+ return E_FAIL;
+ }
+ }
+
+ // TODO: Update Enc Log
+
+ if (!md_cursor_to_token(c, ptd))
+ return E_FAIL;
+
+ return S_OK;
+}
+
+HRESULT MetadataEmit::DefineNestedType(
+ LPCWSTR szTypeDef,
+ DWORD dwTypeDefFlags,
+ mdToken tkExtends,
+ mdToken rtkImplements[],
+ mdTypeDef tdEncloser,
+ mdTypeDef *ptd)
+{
+ HRESULT hr;
+
+ if (TypeFromToken(tdEncloser) != mdtTypeDef || IsNilToken(tdEncloser))
+ return E_INVALIDARG;
+
+ if (IsTdNested(dwTypeDefFlags))
+ return E_INVALIDARG;
+
+ RETURN_IF_FAILED(DefineTypeDef(szTypeDef, dwTypeDefFlags, tkExtends, rtkImplements, ptd));
+
+ md_added_row_t c;
+ if (!md_append_row(MetaData(), mdtid_NestedClass, &c))
+ return E_FAIL;
+
+ if (!md_set_column_value_as_token(c, mdtNestedClass_NestedClass, *ptd))
+ return E_FAIL;
+
+ if (!md_set_column_value_as_token(c, mdtNestedClass_EnclosingClass, tdEncloser))
+ return E_FAIL;
+
+ // TODO: Update ENC log
+ return S_OK;
+}
+
+HRESULT MetadataEmit::SetHandler(
+ IUnknown *pUnk)
+{
+ // The this implementation of MetadataEmit doesn't ever remap tokens,
+ // so this method (which is for registering a callback for when tokens are remapped)
+ // is a no-op.
+ UNREFERENCED_PARAMETER(pUnk);
+ return S_OK;
+}
+
+HRESULT MetadataEmit::DefineMethod(
+ mdTypeDef td,
+ LPCWSTR szName,
+ DWORD dwMethodFlags,
+ PCCOR_SIGNATURE pvSigBlob,
+ ULONG cbSigBlob,
+ ULONG ulCodeRVA,
+ DWORD dwImplFlags,
+ mdMethodDef *pmd)
+{
+ if (TypeFromToken(td) != mdtTypeDef)
+ return E_INVALIDARG;
+
+ mdcursor_t type;
+ if (!md_token_to_cursor(MetaData(), td, &type))
+ return CLDB_E_FILE_CORRUPT;
+
+ md_added_row_t newMethod;
+ if (!md_add_new_row_to_list(type, mdtTypeDef_MethodList, &newMethod))
+ return E_FAIL;
+
+ pal::StringConvert cvt(szName);
+
+ char const* name = cvt;
+ if (!md_set_column_value_as_utf8(newMethod, mdtMethodDef_Name, name))
+ return E_FAIL;
+
+ uint32_t flags = dwMethodFlags;
+ if (!md_set_column_value_as_constant(newMethod, mdtMethodDef_Flags, flags))
+ return E_FAIL;
+
+ uint32_t sigLength = cbSigBlob;
+ if (!md_set_column_value_as_blob(newMethod, mdtMethodDef_Signature, pvSigBlob, sigLength))
+ return E_FAIL;
+
+ uint32_t implFlags = dwImplFlags;
+ if (!md_set_column_value_as_constant(newMethod, mdtMethodDef_ImplFlags, implFlags))
+ return E_FAIL;
+
+ uint32_t rva = ulCodeRVA;
+ if (!md_set_column_value_as_constant(newMethod, mdtMethodDef_Rva, rva))
+ return E_FAIL;
+
+ if (!md_cursor_to_token(newMethod, pmd))
+ return CLDB_E_FILE_CORRUPT;
+
+ // TODO: Update ENC log
+ return S_OK;
+}
+
+HRESULT MetadataEmit::DefineMethodImpl(
+ mdTypeDef td,
+ mdToken tkBody,
+ mdToken tkDecl)
+{
+ md_added_row_t c;
+ if (!md_append_row(MetaData(), mdtid_MethodImpl, &c))
+ return E_FAIL;
+
+ if (!md_set_column_value_as_token(c, mdtMethodImpl_Class, td))
+ return E_FAIL;
+
+ if (!md_set_column_value_as_token(c, mdtMethodImpl_MethodBody, tkBody))
+ return E_FAIL;
+
+ if (!md_set_column_value_as_token(c, mdtMethodImpl_MethodDeclaration, tkDecl))
+ return E_FAIL;
+
+ // TODO: Update ENC log
+ return S_OK;
+}
+
+HRESULT MetadataEmit::DefineTypeRefByName(
+ mdToken tkResolutionScope,
+ LPCWSTR szName,
+ mdTypeRef *ptr)
+{
+ md_added_row_t c;
+ if (!md_append_row(MetaData(), mdtid_TypeRef, &c))
+ return E_FAIL;
+
+ if (!md_set_column_value_as_token(c, mdtTypeRef_ResolutionScope, tkResolutionScope))
+ return E_FAIL;
+
+ pal::StringConvert cv(szName);
+
+ if (!cv.Success())
+ return E_FAIL;
+
+ char const* ns;
+ char const* name;
+ SplitTypeName(cv, &ns, &name);
+
+ if (!md_set_column_value_as_utf8(c, mdtTypeRef_TypeNamespace, ns))
+ return E_FAIL;
+ if (!md_set_column_value_as_utf8(c, mdtTypeRef_TypeName, name))
+ return E_FAIL;
+
+ if (!md_cursor_to_token(c, ptr))
+ return E_FAIL;
+
+ // TODO: Update ENC log
+ return S_OK;
+}
+
+HRESULT MetadataEmit::DefineImportType(
+ IMetaDataAssemblyImport *pAssemImport,
+ void const *pbHashValue,
+ ULONG cbHashValue,
+ IMetaDataImport *pImport,
+ mdTypeDef tdImport,
+ IMetaDataAssemblyEmit *pAssemEmit,
+ mdTypeRef *ptr)
+{
+ HRESULT hr;
+ minipal::com_ptr assemImport{};
+
+ if (pAssemImport != nullptr)
+ RETURN_IF_FAILED(pAssemImport->QueryInterface(IID_IDNMDOwner, (void**)&assemImport));
+
+ minipal::com_ptr assemEmit{};
+ if (pAssemEmit != nullptr)
+ RETURN_IF_FAILED(pAssemEmit->QueryInterface(IID_IDNMDOwner, (void**)&assemEmit));
+
+ if (pImport == nullptr)
+ return E_INVALIDARG;
+
+ minipal::com_ptr import{};
+ RETURN_IF_FAILED(pImport->QueryInterface(IID_IDNMDOwner, (void**)&import));
+
+ mdcursor_t originalTypeDef;
+ if (!md_token_to_cursor(import->MetaData(), tdImport, &originalTypeDef))
+ return CLDB_E_FILE_CORRUPT;
+
+ mdcursor_t importedTypeDef;
+
+ RETURN_IF_FAILED(ImportReferenceToTypeDef(
+ originalTypeDef,
+ assemImport->MetaData(),
+ { reinterpret_cast(pbHashValue), cbHashValue },
+ assemEmit->MetaData(),
+ MetaData(),
+ false,
+ [](mdcursor_t){},
+ &importedTypeDef
+ ));
+
+ if (!md_cursor_to_token(importedTypeDef, ptr))
+ return E_FAIL;
+
+ return S_OK;
+}
+
+HRESULT MetadataEmit::DefineMemberRef(
+ mdToken tkImport,
+ LPCWSTR szName,
+ PCCOR_SIGNATURE pvSigBlob,
+ ULONG cbSigBlob,
+ mdMemberRef *pmr)
+{
+ if (IsNilToken(tkImport))
+ tkImport = MD_GLOBAL_PARENT_TOKEN;
+
+ pal::StringConvert cvt(szName);
+ if (!cvt.Success())
+ return E_INVALIDARG;
+ char const* name = cvt;
+
+ // TODO: Check for duplicates
+
+ md_added_row_t c;
+ if (!md_append_row(MetaData(), mdtid_MemberRef, &c))
+ return E_FAIL;
+
+ if (!md_set_column_value_as_token(c, mdtMemberRef_Class, tkImport))
+ return E_FAIL;
+
+ if (!md_set_column_value_as_utf8(c, mdtMemberRef_Name, name))
+ return E_FAIL;
+
+ uint8_t const* sig = (uint8_t const*)pvSigBlob;
+ uint32_t sigLength = cbSigBlob;
+ if (!md_set_column_value_as_blob(c, mdtMemberRef_Signature, sig, sigLength))
+ return E_FAIL;
+
+ if (!md_cursor_to_token(c, pmr))
+ return E_FAIL;
+
+ // TODO: Update EncLog
+ return S_OK;
+}
+
+HRESULT MetadataEmit::DefineImportMember(
+ IMetaDataAssemblyImport *pAssemImport,
+ void const *pbHashValue,
+ ULONG cbHashValue,
+ IMetaDataImport *pImport,
+ mdToken mbMember,
+ IMetaDataAssemblyEmit *pAssemEmit,
+ mdToken tkParent,
+ mdMemberRef *pmr)
+{
+ return ::DefineImportMember(
+ this,
+ pAssemImport,
+ pbHashValue,
+ cbHashValue,
+ pImport,
+ mbMember,
+ pAssemEmit,
+ tkParent,
+ pmr);
+}
+
+namespace
+{
+ HRESULT AddMethodSemantic(mdhandle_t md, mdcursor_t parent, CorMethodSemanticsAttr semantic, mdMethodDef method)
+ {
+ md_added_row_t addMethodSemantic;
+ if (!md_append_row(md, mdtid_MethodSemantics, &addMethodSemantic))
+ return E_FAIL;
+
+ if (!md_set_column_value_as_cursor(addMethodSemantic, mdtMethodSemantics_Association, parent))
+ return E_FAIL;
+
+ uint32_t semantics = semantic;
+ if (!md_set_column_value_as_constant(addMethodSemantic, mdtMethodSemantics_Semantics, semantics))
+ return E_FAIL;
+
+ if (!md_set_column_value_as_token(addMethodSemantic, mdtMethodSemantics_Method, method))
+ return E_FAIL;
+
+ // TODO: Update EncLog
+ return S_OK;
+ }
+
+ HRESULT DeleteParentedToken(mdhandle_t md, mdToken parent, mdtable_id_t childTable, col_index_t parentColumn)
+ {
+ mdcursor_t c;
+ uint32_t count;
+ if (!md_create_cursor(md, childTable, &c, &count))
+ return CLDB_E_RECORD_NOTFOUND;
+
+ if (!md_find_row_from_cursor(c, parentColumn, parent, &c))
+ return CLDB_E_RECORD_NOTFOUND;
+
+ mdToken nilParent = mdFieldDefNil;
+ if (!md_set_column_value_as_token(c, mdtFieldMarshal_Parent, nilParent))
+ return E_FAIL;
+
+ mdcursor_t parentCursor;
+ if (!md_token_to_cursor(md, parent, &parentCursor))
+ return CLDB_E_FILE_CORRUPT;
+ // TODO: Update EncLog
+ return S_OK;
+ }
+
+ HRESULT RemoveFlag(mdhandle_t md, mdToken tk, col_index_t flagsColumn, uint32_t flagToRemove)
+ {
+ // TODO: Update EncLog
+ mdcursor_t c;
+ if (!md_token_to_cursor(md, tk, &c))
+ return CLDB_E_FILE_CORRUPT;
+
+ uint32_t flags;
+ if (!md_get_column_value_as_constant(c, flagsColumn, &flags))
+ return E_FAIL;
+
+ flags &= ~flagToRemove;
+ if (!md_set_column_value_as_constant(c, flagsColumn, flags))
+ return E_FAIL;
+
+ // TODO: Update EncLog
+ return S_OK;
+ }
+
+ HRESULT AddFlag(mdhandle_t md, mdToken tk, col_index_t flagsColumn, uint32_t flagToAdd)
+ {
+ // TODO: Update EncLog
+ mdcursor_t c;
+ if (!md_token_to_cursor(md, tk, &c))
+ return CLDB_E_FILE_CORRUPT;
+
+ uint32_t flags;
+ if (!md_get_column_value_as_constant(c, flagsColumn, &flags))
+ return E_FAIL;
+
+ flags |= flagToAdd;
+ if (!md_set_column_value_as_constant(c, flagsColumn, flags))
+ return E_FAIL;
+
+ // TODO: Update EncLog
+ return S_OK;
+ }
+
+ template
+ HRESULT FindOrCreateParentedRow(mdhandle_t md, mdToken parent, mdtable_id_t childTable, col_index_t parentCol, T const& setTableData)
+ {
+ HRESULT hr;
+ mdcursor_t c;
+ md_added_row_t addedRow;
+ uint32_t count;
+ if (!md_create_cursor(md, childTable, &c, &count)
+ || !md_find_row_from_cursor(c, parentCol, parent, &c))
+ {
+ // TODO: Update EncLog
+ if (!md_append_row(md, childTable, &addedRow))
+ return E_FAIL;
+
+ if (!md_set_column_value_as_token(addedRow, parentCol, parent))
+ return E_FAIL;
+ c = addedRow;
+ }
+ RETURN_IF_FAILED(setTableData(c));
+
+ return S_OK;
+ }
+}
+
+HRESULT MetadataEmit::DefineEvent(
+ mdTypeDef td,
+ LPCWSTR szEvent,
+ DWORD dwEventFlags,
+ mdToken tkEventType,
+ mdMethodDef mdAddOn,
+ mdMethodDef mdRemoveOn,
+ mdMethodDef mdFire,
+ mdMethodDef rmdOtherMethods[],
+ mdEvent *pmdEvent)
+{
+ assert(TypeFromToken(td) == mdtTypeDef && td != mdTypeDefNil);
+ assert(IsNilToken(tkEventType) || TypeFromToken(tkEventType) == mdtTypeDef ||
+ TypeFromToken(tkEventType) == mdtTypeRef || TypeFromToken(tkEventType) == mdtTypeSpec);
+ assert(TypeFromToken(mdAddOn) == mdtMethodDef && mdAddOn != mdMethodDefNil);
+ assert(TypeFromToken(mdRemoveOn) == mdtMethodDef && mdRemoveOn != mdMethodDefNil);
+ assert(IsNilToken(mdFire) || TypeFromToken(mdFire) == mdtMethodDef);
+ assert(szEvent && pmdEvent);
+
+ pal::StringConvert cvt(szEvent);
+ if (!cvt.Success())
+ return E_INVALIDARG;
+
+ char const* name = cvt;
+
+ return FindOrCreateParentedRow(MetaData(), td, mdtid_EventMap, mdtEventMap_Parent, [=](mdcursor_t c)
+ {
+ HRESULT hr;
+ // TODO: Check for duplicates
+ md_added_row_t addedEvent;
+ if (!md_add_new_row_to_list(c, mdtEventMap_EventList, &addedEvent))
+ return E_FAIL;
+
+ if (!md_set_column_value_as_utf8(addedEvent, mdtEvent_Name, name))
+ return E_FAIL;
+
+ uint32_t flags = dwEventFlags;
+ if (!md_set_column_value_as_constant(addedEvent, mdtEvent_EventFlags, flags))
+ return E_FAIL;
+
+ if (!md_set_column_value_as_token(addedEvent, mdtEvent_EventType, tkEventType))
+ return E_FAIL;
+
+ if (mdAddOn != mdMethodDefNil)
+ {
+ RETURN_IF_FAILED(AddMethodSemantic(MetaData(), addedEvent, msAddOn, mdAddOn));
+ }
+
+ if (mdRemoveOn != mdMethodDefNil)
+ {
+ RETURN_IF_FAILED(AddMethodSemantic(MetaData(), addedEvent, msRemoveOn, mdRemoveOn));
+ }
+
+ if (mdFire != mdMethodDefNil)
+ {
+ RETURN_IF_FAILED(AddMethodSemantic(MetaData(), addedEvent, msFire, mdFire));
+ }
+
+ if (rmdOtherMethods != nullptr)
+ {
+ for (size_t i = 0; !IsNilToken(rmdOtherMethods[i]); i++)
+ {
+ RETURN_IF_FAILED(AddMethodSemantic(MetaData(), addedEvent, msOther, rmdOtherMethods[i]));
+ }
+ }
+
+ if (!md_cursor_to_token(addedEvent, pmdEvent))
+ return E_FAIL;
+
+ // TODO: Update EncLog
+ return S_OK;
+ });
+}
+
+HRESULT MetadataEmit::SetClassLayout(
+ mdTypeDef td,
+ DWORD dwPackSize,
+ COR_FIELD_OFFSET rFieldOffsets[],
+ ULONG ulClassSize)
+{
+ HRESULT hr;
+ assert(TypeFromToken(td) == mdtTypeDef);
+
+ if (rFieldOffsets != nullptr)
+ {
+ for (size_t i = 0; rFieldOffsets[i].ridOfField != mdFieldDefNil; ++i)
+ {
+ if (rFieldOffsets[i].ulOffset != UINT32_MAX)
+ {
+ mdToken field = TokenFromRid(rFieldOffsets[i].ridOfField, mdtFieldDef);
+ uint32_t offset = rFieldOffsets[i].ulOffset;
+ RETURN_IF_FAILED(FindOrCreateParentedRow(MetaData(), field, mdtid_FieldLayout, mdtFieldLayout_Field, [=](mdcursor_t c)
+ {
+ if (!md_set_column_value_as_constant(c, mdtFieldLayout_Offset, offset))
+ return E_FAIL;
+
+ return S_OK;
+ }));
+ }
+ }
+ }
+
+ RETURN_IF_FAILED(FindOrCreateParentedRow(MetaData(), td, mdtid_ClassLayout, mdtClassLayout_Parent, [=](mdcursor_t c)
+ {
+ uint32_t packSize = (uint32_t)dwPackSize;
+ if (!md_set_column_value_as_constant(c, mdtClassLayout_PackingSize, packSize))
+ return E_FAIL;
+
+ uint32_t classSize = (uint32_t)ulClassSize;
+ if (!md_set_column_value_as_constant(c, mdtClassLayout_ClassSize, classSize))
+ return E_FAIL;
+
+ return S_OK;
+ }));
+
+ return S_OK;
+}
+
+HRESULT MetadataEmit::DeleteClassLayout(
+ mdTypeDef td)
+{
+ assert(TypeFromToken(td) == mdtTypeDef);
+ HRESULT hr;
+ mdcursor_t c;
+ uint32_t count;
+ if (!md_create_cursor(MetaData(), mdtid_ClassLayout, &c, &count))
+ return CLDB_E_RECORD_NOTFOUND;
+
+ if (!md_find_row_from_cursor(c, mdtClassLayout_Parent, td, &c))
+ return CLDB_E_RECORD_NOTFOUND;
+
+ RETURN_IF_FAILED(DeleteParentedToken(MetaData(), td, mdtid_ClassLayout, mdtClassLayout_Parent));
+
+ // Now that we've deleted the class layout entry,
+ // we need to delete the field layout entries for the fields of the type.
+ mdcursor_t type;
+ if (!md_token_to_cursor(MetaData(), td, &type))
+ return CLDB_E_FILE_CORRUPT;
+
+ mdcursor_t field;
+ uint32_t fieldCount;
+ if (!md_get_column_value_as_range(type, mdtTypeDef_FieldList, &field, &fieldCount))
+ return S_OK;
+
+ for (uint32_t i = 0; i < fieldCount; ++i, md_cursor_next(&field))
+ {
+ mdcursor_t resolvedField;
+ if (!md_resolve_indirect_cursor(field, &resolvedField))
+ return E_FAIL;
+
+ mdToken fieldToken;
+ if (!md_cursor_to_token(resolvedField, &fieldToken))
+ return E_FAIL;
+
+ hr = DeleteParentedToken(MetaData(), fieldToken, mdtid_FieldLayout, mdtFieldLayout_Field);
+
+ // If we couldn't find the field layout entry, that's fine.
+ // If we hit another error, return that error.
+ if (hr == CLDB_E_RECORD_NOTFOUND)
+ continue;
+ RETURN_IF_FAILED(hr);
+ }
+
+ return S_OK;
+}
+
+HRESULT MetadataEmit::SetFieldMarshal(
+ mdToken tk,
+ PCCOR_SIGNATURE pvNativeType,
+ ULONG cbNativeType)
+{
+ mdcursor_t parent;
+ if (!md_token_to_cursor(MetaData(), tk, &parent))
+ return CLDB_E_FILE_CORRUPT;
+
+ col_index_t col = TypeFromToken(tk) == mdtFieldDef ? mdtField_Flags : mdtParam_Flags;
+ uint32_t flagToAdd = TypeFromToken(tk) == mdtFieldDef ? (uint32_t)fdHasFieldMarshal : (uint32_t)pdHasFieldMarshal;
+ uint32_t flags;
+ if (!md_get_column_value_as_constant(parent, col, &flags))
+ return E_FAIL;
+
+ flags |= flagToAdd;
+ if (!md_set_column_value_as_constant(parent, col, flags))
+ return E_FAIL;
+
+ FindOrCreateParentedRow(MetaData(), tk, mdtid_FieldMarshal, mdtFieldMarshal_Parent, [=](mdcursor_t c)
+ {
+ uint8_t const* sig = (uint8_t const*)pvNativeType;
+ uint32_t sigLength = cbNativeType;
+ if (!md_set_column_value_as_blob(c, mdtFieldMarshal_NativeType, sig, sigLength))
+ return E_FAIL;
+
+ return S_OK;
+ });
+
+ // TODO: Update EncLog
+ return S_OK;
+}
+
+HRESULT MetadataEmit::DeleteFieldMarshal(
+ mdToken tk)
+{
+ HRESULT hr;
+ assert(TypeFromToken(tk) == mdtFieldDef || TypeFromToken(tk) == mdtParamDef);
+ assert(!IsNilToken(tk));
+
+ RETURN_IF_FAILED(DeleteParentedToken(
+ MetaData(),
+ tk,
+ mdtid_FieldMarshal,
+ mdtFieldMarshal_Parent));
+
+ RETURN_IF_FAILED(RemoveFlag(
+ MetaData(),
+ tk,
+ TypeFromToken(tk) == mdtFieldDef ? mdtField_Flags : mdtParam_Flags,
+ TypeFromToken(tk) == mdtFieldDef ? (uint32_t)fdHasFieldMarshal : (uint32_t)pdHasFieldMarshal));
+ return S_OK;
+}
+
+HRESULT MetadataEmit::DefinePermissionSet(
+ mdToken tk,
+ DWORD dwAction,
+ void const *pvPermission,
+ ULONG cbPermission,
+ mdPermission *ppm)
+{
+ // TODO: Check for duplicates
+ assert(TypeFromToken(tk) == mdtTypeDef || TypeFromToken(tk) == mdtMethodDef ||
+ TypeFromToken(tk) == mdtAssembly);
+
+ md_added_row_t c;
+ if (!md_append_row(MetaData(), mdtid_DeclSecurity, &c))
+ return E_FAIL;
+
+ if (!md_set_column_value_as_token(c, mdtDeclSecurity_Parent, tk))
+ return E_FAIL;
+
+ if (TypeFromToken(tk) == mdtTypeDef
+ || TypeFromToken(tk) == mdtMethodDef)
+ {
+ uint32_t flagToAdd = TypeFromToken(tk) == mdtTypeDef ? (uint32_t)tdHasSecurity : (uint32_t)mdHasSecurity;
+ col_index_t flagsCol = TypeFromToken(tk) == mdtTypeDef ? mdtTypeDef_Flags : mdtMethodDef_Flags;
+
+ mdcursor_t parent;
+ if (!md_get_column_value_as_cursor(c, mdtDeclSecurity_Parent, &parent))
+ return E_FAIL;
+
+ uint32_t flags;
+ if (!md_get_column_value_as_constant(parent, flagsCol, &flags))
+ return E_FAIL;
+
+ flags |= flagToAdd;
+
+ if (!md_set_column_value_as_constant(parent, flagsCol, flags))
+ return E_FAIL;
+ // TODO: Update EncLog
+ }
+
+ uint32_t action = dwAction;
+ if (!md_set_column_value_as_constant(c, mdtDeclSecurity_Action, action))
+ return E_FAIL;
+
+ uint8_t const* permission = (uint8_t const*)pvPermission;
+ uint32_t permissionLength = cbPermission;
+ if (!md_set_column_value_as_blob(c, mdtDeclSecurity_PermissionSet, permission, permissionLength))
+ return E_FAIL;
+
+ if (!md_cursor_to_token(c, ppm))
+ return E_FAIL;
+
+ // TODO: Update EncLog
+ return S_OK;
+}
+
+HRESULT MetadataEmit::SetRVA(
+ mdMethodDef md,
+ ULONG ulRVA)
+{
+ mdcursor_t method;
+ if (!md_token_to_cursor(MetaData(), md, &method))
+ return CLDB_E_FILE_CORRUPT;
+
+ uint32_t rva = ulRVA;
+ if (!md_set_column_value_as_constant(method, mdtMethodDef_Rva, rva))
+ return E_FAIL;
+
+ // TODO: Update EncLog
+ return S_OK;
+}
+
+HRESULT MetadataEmit::GetTokenFromSig(
+ PCCOR_SIGNATURE pvSig,
+ ULONG cbSig,
+ mdSignature *pmsig)
+{
+ md_added_row_t c;
+ if (!md_append_row(MetaData(), mdtid_StandAloneSig, &c))
+ return E_FAIL;
+
+ uint32_t sigLength = cbSig;
+ if (!md_set_column_value_as_blob(c, mdtStandAloneSig_Signature, pvSig, sigLength))
+ return E_FAIL;
+
+ if (!md_cursor_to_token(c, pmsig))
+ return CLDB_E_FILE_CORRUPT;
+
+ // TODO: Update EncLog
+ return S_OK;
+}
+
+HRESULT MetadataEmit::DefineModuleRef(
+ LPCWSTR szName,
+ mdModuleRef *pmur)
+{
+ md_added_row_t c;
+ if (!md_append_row(MetaData(), mdtid_ModuleRef, &c))
+ return E_FAIL;
+
+ pal::StringConvert cvt(szName);
+ char const* name = cvt;
+
+ if (!md_set_column_value_as_utf8(c, mdtModuleRef_Name, name))
+ return E_FAIL;
+
+ if (!md_cursor_to_token(c, pmur))
+ return CLDB_E_FILE_CORRUPT;
+
+ // TODO: Update EncLog
+ return S_OK;
+}
+
+
+HRESULT MetadataEmit::SetParent(
+ mdMemberRef mr,
+ mdToken tk)
+{
+ mdcursor_t c;
+ if (!md_token_to_cursor(MetaData(), mr, &c))
+ return CLDB_E_FILE_CORRUPT;
+
+ if (!md_set_column_value_as_token(c, mdtMemberRef_Class, tk))
+ return E_FAIL;
+
+ // TODO: Update EncLog
+ return S_OK;
+}
+
+HRESULT MetadataEmit::GetTokenFromTypeSpec(
+ PCCOR_SIGNATURE pvSig,
+ ULONG cbSig,
+ mdTypeSpec *ptypespec)
+{
+ md_added_row_t c;
+ if (!md_append_row(MetaData(), mdtid_TypeSpec, &c))
+ return E_FAIL;
+
+ uint32_t sigLength = cbSig;
+ if (!md_set_column_value_as_blob(c, mdtTypeSpec_Signature, pvSig, sigLength))
+ return E_FAIL;
+
+ if (!md_cursor_to_token(c, ptypespec))
+ return CLDB_E_FILE_CORRUPT;
+
+ // TODO: Update EncLog
+ return S_OK;
+}
+
+HRESULT MetadataEmit::SaveToMemory(
+ void *pbData,
+ ULONG cbData)
+{
+ size_t saveSize = cbData;
+ return md_write_to_buffer(MetaData(), (uint8_t*)pbData, &saveSize) ? S_OK : E_OUTOFMEMORY;
+}
+
+HRESULT MetadataEmit::DefineUserString(
+ LPCWSTR szString,
+ ULONG cchString,
+ mdString *pstk)
+{
+ std::unique_ptr pString{ new char16_t[cchString + 1] };
+ std::memcpy(pString.get(), szString, cchString * sizeof(char16_t));
+ pString[cchString] = u'\0';
+
+ mduserstringcursor_t c = md_add_userstring_to_heap(MetaData(), pString.get());
+
+ if (c == 0)
+ return E_FAIL;
+
+ if ((c & 0xff000000) != 0)
+ return META_E_STRINGSPACE_FULL;
+
+ *pstk = TokenFromRid((mdString)c, mdtString);
+ return S_OK;
+}
+
+HRESULT MetadataEmit::DeleteToken(
+ mdToken tkObj)
+{
+ mdcursor_t c;
+ if (!md_token_to_cursor(MetaData(), tkObj, &c))
+ return E_INVALIDARG;
+
+ char const* deletedName = COR_DELETED_NAME_A;
+ switch (TypeFromToken(tkObj))
+ {
+ case mdtTypeDef:
+ {
+ if (!md_set_column_value_as_utf8(c, mdtTypeDef_TypeName, deletedName))
+ return E_FAIL;
+ return AddFlag(MetaData(), tkObj, mdtTypeDef_Flags, tdSpecialName | tdRTSpecialName);
+ }
+ case mdtMethodDef:
+ {
+ if (!md_set_column_value_as_utf8(c, mdtMethodDef_Name, deletedName))
+ return E_FAIL;
+ return AddFlag(MetaData(), tkObj, mdtMethodDef_Flags, mdSpecialName | mdRTSpecialName);
+ }
+ case mdtFieldDef:
+ {
+ if (!md_set_column_value_as_utf8(c, mdtField_Name, deletedName))
+ return E_FAIL;
+ return AddFlag(MetaData(), tkObj, mdtField_Flags, fdSpecialName | fdRTSpecialName);
+ }
+ case mdtEvent:
+ {
+ if (!md_set_column_value_as_utf8(c, mdtEvent_Name, deletedName))
+ return E_FAIL;
+ return AddFlag(MetaData(), tkObj, mdtEvent_EventFlags, evSpecialName | evRTSpecialName);
+ }
+ case mdtProperty:
+ {
+ if (!md_set_column_value_as_utf8(c, mdtProperty_Name, deletedName))
+ return E_FAIL;
+ return AddFlag(MetaData(), tkObj, mdtProperty_Flags, prSpecialName | prRTSpecialName);
+ }
+ case mdtExportedType:
+ {
+ if (!md_set_column_value_as_utf8(c, mdtExportedType_TypeName, deletedName))
+ return E_FAIL;
+ return S_OK;
+ }
+ case mdtCustomAttribute:
+ {
+ mdToken parent;
+ if (!md_get_column_value_as_token(c, mdtCustomAttribute_Parent, &parent))
+ return E_FAIL;
+
+ // Change the parent to the nil token.
+ parent = TokenFromRid(mdTokenNil, TypeFromToken(parent));
+
+ if (!md_set_column_value_as_token(c, mdtCustomAttribute_Parent, parent))
+ return E_FAIL;
+
+ return S_OK;
+ }
+ case mdtGenericParam:
+ {
+ mdToken parent;
+ if (!md_get_column_value_as_token(c, mdtGenericParam_Owner, &parent))
+ return E_FAIL;
+
+ // Change the parent to the nil token.
+ parent = TokenFromRid(mdTokenNil, TypeFromToken(parent));
+
+ if (!md_set_column_value_as_token(c, mdtGenericParam_Owner, parent))
+ return E_FAIL;
+
+ return S_OK;
+ }
+ case mdtGenericParamConstraint:
+ {
+ mdToken parent = mdGenericParamNil;
+ if (!md_set_column_value_as_token(c, mdtGenericParamConstraint_Owner, parent))
+ return E_FAIL;
+
+ return S_OK;
+ }
+ case mdtPermission:
+ {
+ mdToken parent;
+ if (!md_get_column_value_as_token(c, mdtDeclSecurity_Parent, &parent))
+ return E_FAIL;
+
+ // Change the parent to the nil token.
+ mdToken originalParent = parent;
+ parent = TokenFromRid(mdTokenNil, TypeFromToken(parent));
+
+ if (!md_set_column_value_as_token(c, mdtDeclSecurity_Parent, parent))
+ return E_FAIL;
+
+ if (TypeFromToken(originalParent) == mdtAssembly)
+ {
+ // There is no HasSecurity flag for an assembly, so we're done.
+ return S_OK;
+ }
+
+ mdcursor_t permissions;
+ uint32_t numPermissions;
+ if (!md_create_cursor(MetaData(), mdtid_DeclSecurity, &permissions, &numPermissions))
+ return E_FAIL;
+
+ // If we have no more permissions for this parent, remove the HasSecurity bit.
+ // Since we just need to know if there's any matching row and we don't need a range of rows,
+ // we can use find_row instead of find_range.
+ if (!md_find_row_from_cursor(permissions, mdtDeclSecurity_Parent, originalParent, &permissions))
+ {
+ return RemoveFlag(
+ MetaData(),
+ originalParent,
+ TypeFromToken(originalParent) == mdtTypeDef ? mdtTypeDef_Flags : mdtMethodDef_Flags,
+ TypeFromToken(originalParent) == mdtTypeDef ? (uint32_t)tdHasSecurity : (uint32_t)mdHasSecurity);
+ }
+
+ return S_OK;
+ }
+ default:
+ break;
+ }
+ return E_INVALIDARG;
+}
+
+HRESULT MetadataEmit::SetMethodProps(
+ mdMethodDef md,
+ DWORD dwMethodFlags,
+ ULONG ulCodeRVA,
+ DWORD dwImplFlags)
+{
+ mdcursor_t c;
+ if (!md_token_to_cursor(MetaData(), md, &c))
+ return CLDB_E_FILE_CORRUPT;
+
+ if (dwMethodFlags != std::numeric_limits::max())
+ {
+ // TODO: Strip the reserved flags from user input and preserve the existing reserved flags.
+ uint32_t flags = dwMethodFlags;
+ if (!md_set_column_value_as_constant(c, mdtMethodDef_Flags, flags))
+ return E_FAIL;
+ }
+
+ if (ulCodeRVA != std::numeric_limits::max())
+ {
+ uint32_t rva = ulCodeRVA;
+ if (!md_set_column_value_as_constant(c, mdtMethodDef_Rva, rva))
+ return E_FAIL;
+ }
+
+ if (dwImplFlags != std::numeric_limits::max())
+ {
+ uint32_t implFlags = dwImplFlags;
+ if (!md_set_column_value_as_constant(c, mdtMethodDef_ImplFlags, implFlags))
+ return E_FAIL;
+ }
+
+ // TODO: Update EncLog
+ return S_OK;
+}
+
+HRESULT MetadataEmit::SetTypeDefProps(
+ mdTypeDef td,
+ DWORD dwTypeDefFlags,
+ mdToken tkExtends,
+ mdToken rtkImplements[])
+{
+ mdcursor_t c;
+ if (!md_token_to_cursor(MetaData(), td, &c))
+ return CLDB_E_FILE_CORRUPT;
+
+ if (dwTypeDefFlags != std::numeric_limits::max())
+ {
+ // TODO: Strip the reserved flags from user input and preserve the existing reserved flags.
+ uint32_t flags = dwTypeDefFlags;
+ if (!md_set_column_value_as_constant(c, mdtTypeDef_Flags, flags))
+ return E_FAIL;
+ }
+
+ if (tkExtends != std::numeric_limits::max())
+ {
+ if (IsNilToken(tkExtends))
+ tkExtends = mdTypeDefNil;
+
+ if (!md_set_column_value_as_token(c, mdtTypeDef_Extends, tkExtends))
+ return E_FAIL;
+ }
+
+ if (rtkImplements)
+ {
+ // First null-out the Class columns of the current implementations.
+ // We can't delete here as we hand out tokens into this table to the caller.
+ // This would be much more efficient if we could delete rows, as nulling out the parent will almost assuredly make the column
+ // unsorted.
+ mdcursor_t interfaceImplCursor;
+ uint32_t numInterfaceImpls;
+ if (md_create_cursor(MetaData(), mdtid_InterfaceImpl, &interfaceImplCursor, &numInterfaceImpls)
+ && md_find_range_from_cursor(interfaceImplCursor, mdtInterfaceImpl_Class, RidFromToken(td), &interfaceImplCursor, &numInterfaceImpls) != MD_RANGE_NOT_FOUND)
+ {
+ for (uint32_t i = 0; i < numInterfaceImpls; ++i)
+ {
+ mdToken parent;
+ if (!md_get_column_value_as_token(interfaceImplCursor, mdtInterfaceImpl_Class, &parent))
+ return E_FAIL;
+
+ // If getting a range was unsupported, then we're doing a whole table scan here.
+ // In that case, we can't assume that we've already validated the parent.
+ // Update it here.
+ if (parent == td)
+ {
+ mdToken newParent = mdTypeDefNil;
+ if (!md_set_column_value_as_token(interfaceImplCursor, mdtInterfaceImpl_Class, newParent))
+ return E_FAIL;
+ }
+ }
+ }
+
+ size_t implIndex = 0;
+ mdToken currentImplementation = rtkImplements[implIndex];
+ do
+ {
+ md_added_row_t interfaceImpl;
+ if (!md_append_row(MetaData(), mdtid_InterfaceImpl, &interfaceImpl))
+ return E_FAIL;
+
+ if (!md_set_column_value_as_cursor(interfaceImpl, mdtInterfaceImpl_Class, c))
+ return E_FAIL;
+
+ if (!md_set_column_value_as_token(interfaceImpl, mdtInterfaceImpl_Interface, currentImplementation))
+ return E_FAIL;
+ } while ((currentImplementation = rtkImplements[++implIndex]) != mdTokenNil);
+ }
+
+ // TODO: Update EncLog
+ return S_OK;
+}
+
+namespace
+{
+ // Set all rows in the MethodSemantic table with a matching Association column of parent to the nil token of parent's table.
+ HRESULT RemoveSemantics(mdhandle_t md, mdToken parent, CorMethodSemanticsAttr semantic)
+ {
+ mdcursor_t c;
+ uint32_t count;
+ if (!md_create_cursor(md, mdtid_MethodSemantics, &c, &count))
+ return CLDB_E_RECORD_NOTFOUND;
+
+ md_range_result_t result = md_find_range_from_cursor(c, mdtMethodSemantics_Association, parent, &c, &count);
+ if (result == MD_RANGE_NOT_FOUND)
+ return S_OK;
+
+ for (uint32_t i = 0; i < count; ++i, md_cursor_next(&c))
+ {
+ mdToken association;
+ if (!md_get_column_value_as_token(c, mdtMethodSemantics_Association, &association))
+ return E_FAIL;
+
+ uint32_t recordSemantic;
+ if (!md_get_column_value_as_constant(c, mdtMethodSemantics_Semantics, &recordSemantic))
+ return E_FAIL;
+
+ if (association == parent && recordSemantic == (uint32_t)semantic)
+ {
+ association = TokenFromRid(mdTokenNil, TypeFromToken(association));
+ if (!md_set_column_value_as_token(c, mdtMethodSemantics_Association, association))
+ return E_FAIL;
+ }
+ }
+
+ return S_OK;
+ }
+}
+
+HRESULT MetadataEmit::SetEventProps(
+ mdEvent ev,
+ DWORD dwEventFlags,
+ mdToken tkEventType,
+ mdMethodDef mdAddOn,
+ mdMethodDef mdRemoveOn,
+ mdMethodDef mdFire,
+ mdMethodDef rmdOtherMethods[])
+{
+ HRESULT hr;
+ mdcursor_t c;
+ if (!md_token_to_cursor(MetaData(), ev, &c))
+ return CLDB_E_FILE_CORRUPT;
+
+ if (dwEventFlags != std::numeric_limits::max())
+ {
+ uint32_t eventFlags = dwEventFlags;
+ if (!md_set_column_value_as_constant(c, mdtEvent_EventFlags, eventFlags))
+ return E_FAIL;
+ }
+
+ if (!IsNilToken(tkEventType))
+ {
+ if (!md_set_column_value_as_token(c, mdtEvent_EventType, tkEventType))
+ return E_FAIL;
+ }
+
+ if (!IsNilToken(mdAddOn))
+ {
+ RemoveSemantics(MetaData(), ev, msAddOn);
+ RETURN_IF_FAILED(AddMethodSemantic(MetaData(), c, msAddOn, mdAddOn));
+ }
+
+ if (!IsNilToken(mdRemoveOn))
+ {
+ RemoveSemantics(MetaData(), ev, msRemoveOn);
+ RETURN_IF_FAILED(AddMethodSemantic(MetaData(), c, msRemoveOn, mdRemoveOn));
+ }
+
+ if (!IsNilToken(mdFire))
+ {
+ RemoveSemantics(MetaData(), ev, msFire);
+ RETURN_IF_FAILED(AddMethodSemantic(MetaData(), c, msFire, mdFire));
+ }
+
+ if (rmdOtherMethods)
+ {
+ RemoveSemantics(MetaData(), ev, msOther);
+ for (size_t i = 0; rmdOtherMethods[i] != mdMethodDefNil; ++i)
+ {
+ RETURN_IF_FAILED(AddMethodSemantic(MetaData(), c, msOther, rmdOtherMethods[i]));
+ }
+ }
+
+ // TODO: Update EncLog
+
+ return S_OK;
+}
+
+HRESULT MetadataEmit::SetPermissionSetProps(
+ mdToken tk,
+ DWORD dwAction,
+ void const *pvPermission,
+ ULONG cbPermission,
+ mdPermission *ppm)
+{
+ assert(TypeFromToken(tk) == mdtTypeDef || TypeFromToken(tk) == mdtMethodDef ||
+ TypeFromToken(tk) == mdtAssembly);
+
+ if (dwAction == UINT32_MAX || dwAction == 0 || dwAction > dclMaximumValue)
+ return E_INVALIDARG;
+
+ mdcursor_t c;
+ uint32_t count;
+ if (!md_create_cursor(MetaData(), mdtid_DeclSecurity, &c, &count))
+ return CLDB_E_RECORD_NOTFOUND;
+
+ if (!md_find_row_from_cursor(c, mdtDeclSecurity_Parent, tk, &c))
+ return CLDB_E_RECORD_NOTFOUND;
+
+ uint32_t action = dwAction;
+ if (!md_set_column_value_as_constant(c, mdtDeclSecurity_Action, action))
+ return E_FAIL;
+
+ uint8_t const* permission = (uint8_t const*)pvPermission;
+ uint32_t permissionLength = cbPermission;
+ if (!md_set_column_value_as_blob(c, mdtDeclSecurity_PermissionSet, permission, permissionLength))
+ return E_FAIL;
+
+ if (!md_cursor_to_token(c, ppm))
+ return CLDB_E_FILE_CORRUPT;
+
+ // TODO: Update EncLog
+ return S_OK;
+}
+
+HRESULT MetadataEmit::DefinePinvokeMap(
+ mdToken tk,
+ DWORD dwMappingFlags,
+ LPCWSTR szImportName,
+ mdModuleRef mrImportDLL)
+{
+ mdcursor_t c;
+ if (!md_token_to_cursor(MetaData(), tk, &c))
+ return CLDB_E_FILE_CORRUPT;
+
+ if (TypeFromToken(tk) == mdtMethodDef)
+ {
+ AddFlag(MetaData(), tk, mdtMethodDef_Flags, mdPinvokeImpl);
+ }
+ else if (TypeFromToken(tk) == mdtFieldDef)
+ {
+ AddFlag(MetaData(), tk, mdtField_Flags, fdPinvokeImpl);
+ }
+ // TODO: check for duplicates
+
+ // If we found a duplicate and ENC is on, update.
+ // If we found a duplicate and ENC is off, fail.
+ // Otherwise, we need to make a new row
+ mdcursor_t row_to_edit;
+ md_added_row_t added_row_wrapper;
+
+ // TODO: We don't expose tokens for the ImplMap table, so as long as we aren't generating ENC deltas
+ // we can insert in-place.
+ if (!md_append_row(MetaData(), mdtid_ImplMap, &row_to_edit))
+ return E_FAIL;
+ added_row_wrapper = md_added_row_t(row_to_edit);
+
+ if (!md_set_column_value_as_token(row_to_edit, mdtImplMap_MemberForwarded, tk))
+ return E_FAIL;
+
+ if (dwMappingFlags == std::numeric_limits::max())
+ {
+ // Unspecified by the user, set to the default.
+ dwMappingFlags = 0;
+ }
+
+ uint32_t mappingFlags = dwMappingFlags;
+ if (!md_set_column_value_as_constant(row_to_edit, mdtImplMap_MappingFlags, mappingFlags))
+ return E_FAIL;
+
+ pal::StringConvert cvt(szImportName);
+ char const* name = cvt;
+ if (!md_set_column_value_as_utf8(row_to_edit, mdtImplMap_ImportName, name))
+ return E_FAIL;
+
+ if (IsNilToken(mrImportDLL))
+ {
+ // TODO: If the token is nil, create a module ref to "" (if it doesn't exist) and use that.
+ }
+
+ if (!md_set_column_value_as_token(row_to_edit, mdtImplMap_ImportScope, mrImportDLL))
+ return E_FAIL;
+
+ // TODO: Update EncLog
+ return S_OK;
+}
+
+HRESULT MetadataEmit::SetPinvokeMap(
+ mdToken tk,
+ DWORD dwMappingFlags,
+ LPCWSTR szImportName,
+ mdModuleRef mrImportDLL)
+{
+ mdcursor_t c;
+ if (!md_token_to_cursor(MetaData(), tk, &c))
+ return CLDB_E_FILE_CORRUPT;
+
+ mdcursor_t implMapCursor;
+ uint32_t numImplMaps;
+ if (!md_create_cursor(MetaData(), mdtid_ImplMap, &implMapCursor, &numImplMaps))
+ return E_FAIL;
+
+ mdcursor_t row_to_edit;
+ if (!md_find_row_from_cursor(implMapCursor, mdtImplMap_MemberForwarded, tk, &row_to_edit))
+ return CLDB_E_RECORD_NOTFOUND;
+
+ if (dwMappingFlags != std::numeric_limits::max())
+ {
+ uint32_t mappingFlags = dwMappingFlags;
+ if (!md_set_column_value_as_constant(row_to_edit, mdtImplMap_MappingFlags, mappingFlags))
+ return E_FAIL;
+ }
+
+ if (szImportName != nullptr)
+ {
+ pal::StringConvert cvt(szImportName);
+ char const* name = cvt;
+ if (!md_set_column_value_as_utf8(row_to_edit, mdtImplMap_ImportName, name))
+ return E_FAIL;
+ }
+
+ if (!md_set_column_value_as_token(row_to_edit, mdtImplMap_ImportScope, mrImportDLL))
+ return E_FAIL;
+
+ // TODO: Update EncLog
+ return S_OK;
+}
+
+HRESULT MetadataEmit::DeletePinvokeMap(
+ mdToken tk)
+{
+ HRESULT hr;
+ assert(TypeFromToken(tk) == mdtFieldDef || TypeFromToken(tk) == mdtMethodDef);
+ assert(!IsNilToken(tk));
+
+ RETURN_IF_FAILED(DeleteParentedToken(
+ MetaData(),
+ tk,
+ mdtid_ImplMap,
+ mdtImplMap_MemberForwarded));
+
+ RETURN_IF_FAILED(RemoveFlag(
+ MetaData(),
+ tk,
+ TypeFromToken(tk) == mdtFieldDef ? mdtField_Flags : mdtMethodDef_Flags,
+ TypeFromToken(tk) == mdtFieldDef ? (uint32_t)fdPinvokeImpl : (uint32_t)mdPinvokeImpl));
+
+ return S_OK;
+}
+
+
+HRESULT MetadataEmit::DefineCustomAttribute(
+ mdToken tkOwner,
+ mdToken tkCtor,
+ void const *pCustomAttribute,
+ ULONG cbCustomAttribute,
+ mdCustomAttribute *pcv)
+{
+ if (TypeFromToken(tkOwner) == mdtCustomAttribute)
+ return E_INVALIDARG;
+
+ if (IsNilToken(tkOwner)
+ || IsNilToken(tkCtor)
+ || (TypeFromToken(tkCtor) != mdtMethodDef
+ && TypeFromToken(tkCtor) != mdtMemberRef) )
+ {
+ return E_INVALIDARG;
+ }
+
+ // TODO: Recognize pseudoattributes and handle them appropriately.
+
+ // We hand out tokens here, so we can't move rows to keep the parent column sorted.
+ md_added_row_t new_row;
+ if (!md_append_row(MetaData(), mdtid_CustomAttribute, &new_row))
+ return E_FAIL;
+
+ if (!md_set_column_value_as_token(new_row, mdtCustomAttribute_Parent, tkOwner))
+ return E_FAIL;
+
+ if (!md_set_column_value_as_token(new_row, mdtCustomAttribute_Type, tkCtor))
+ return E_FAIL;
+
+ uint8_t const* pCustomAttributeBlob = (uint8_t const*)pCustomAttribute;
+ uint32_t customAttributeBlobLen = cbCustomAttribute;
+ if (!md_set_column_value_as_blob(new_row, mdtCustomAttribute_Value, pCustomAttributeBlob, customAttributeBlobLen))
+ return E_FAIL;
+
+ if (!md_cursor_to_token(new_row, pcv))
+ return CLDB_E_FILE_CORRUPT;
+
+ // TODO: Update EncLog
+ return S_OK;
+}
+
+HRESULT MetadataEmit::SetCustomAttributeValue(
+ mdCustomAttribute pcv,
+ void const *pCustomAttribute,
+ ULONG cbCustomAttribute)
+{
+ if (TypeFromToken(pcv) != mdtCustomAttribute)
+ return E_INVALIDARG;
+
+ mdcursor_t c;
+ if (!md_token_to_cursor(MetaData(), pcv, &c))
+ return CLDB_E_FILE_CORRUPT;
+
+ uint8_t const* pCustomAttributeBlob = (uint8_t const*)pCustomAttribute;
+ uint32_t customAttributeBlobLen = cbCustomAttribute;
+ if (!md_set_column_value_as_blob(c, mdtCustomAttribute_Value, pCustomAttributeBlob, customAttributeBlobLen))
+ return E_FAIL;
+
+ // TODO: Update EncLog
+ return S_OK;
+}
+
+namespace
+{
+ // Determine the blob size base of the ELEMENT_TYPE_* associated with the blob.
+ // This cannot be a table lookup because ELEMENT_TYPE_STRING is an unicode string.
+ uint32_t GetSizeOfConstantBlob(
+ int32_t type,
+ void const* pValue,
+ uint32_t strLen)
+ {
+ uint32_t size = 0;
+
+ switch (type)
+ {
+ case ELEMENT_TYPE_BOOLEAN:
+ size = sizeof(bool);
+ break;
+ case ELEMENT_TYPE_I1:
+ case ELEMENT_TYPE_U1:
+ size = sizeof(uint8_t);
+ break;
+ case ELEMENT_TYPE_CHAR:
+ case ELEMENT_TYPE_I2:
+ case ELEMENT_TYPE_U2:
+ size = sizeof(uint16_t);
+ break;
+ case ELEMENT_TYPE_I4:
+ case ELEMENT_TYPE_U4:
+ case ELEMENT_TYPE_R4:
+ size = sizeof(uint32_t);
+
+ break;
+
+ case ELEMENT_TYPE_I8:
+ case ELEMENT_TYPE_U8:
+ case ELEMENT_TYPE_R8:
+ size = sizeof(uint64_t);
+ break;
+
+ case ELEMENT_TYPE_STRING:
+ if (pValue == 0)
+ size = 0;
+ else
+ if (strLen != (uint32_t) -1)
+ size = strLen * sizeof(WCHAR);
+ else
+ size = (uint32_t)(sizeof(WCHAR) * minipal_u16_strlen((CHAR16_T*)pValue));
+ break;
+
+ case ELEMENT_TYPE_CLASS:
+ // The only legal value is a null pointer, and on 32 bit platforms we've already
+ // stored 32 bits, so we will use just 32 bits of null. If the type is
+ // E_T_CLASS, the caller should know that the value is always null anyway.
+ size = sizeof(uint32_t);
+ break;
+ default:
+ assert(!"Not a valid type to specify default value!");
+ break;
+ }
+ return size;
+ }
+}
+
+HRESULT MetadataEmit::DefineField(
+ mdTypeDef td,
+ LPCWSTR szName,
+ DWORD dwFieldFlags,
+ PCCOR_SIGNATURE pvSigBlob,
+ ULONG cbSigBlob,
+ DWORD dwCPlusTypeFlag,
+ void const *pValue,
+ ULONG cchValue,
+ mdFieldDef *pmd)
+{
+ pal::StringConvert cvt(szName);
+ if (!cvt.Success())
+ return E_INVALIDARG;
+
+ char const* name = cvt;
+
+ md_added_row_t c;
+ mdcursor_t typeDef;
+ if (!md_token_to_cursor(MetaData(), td, &typeDef))
+ return CLDB_E_FILE_CORRUPT;
+
+ if (!md_add_new_row_to_list(typeDef, mdtTypeDef_FieldList, &c))
+ return E_FAIL;
+
+ if (!md_set_column_value_as_utf8(c, mdtField_Name, name))
+ return E_FAIL;
+
+ bool hasConstant = false;
+ // See if there is a Constant.
+ if ((dwCPlusTypeFlag != ELEMENT_TYPE_VOID && dwCPlusTypeFlag != ELEMENT_TYPE_END &&
+ dwCPlusTypeFlag != UINT32_MAX) &&
+ (pValue || (pValue == 0 && (dwCPlusTypeFlag == ELEMENT_TYPE_STRING ||
+ dwCPlusTypeFlag == ELEMENT_TYPE_CLASS))))
+ {
+ hasConstant = true;
+ }
+
+ if (dwFieldFlags != std::numeric_limits::max())
+ {
+ // TODO: Handle reserved flags
+ uint32_t fieldFlags = dwFieldFlags;
+
+ // If the field name has the special name for enum fields,
+ // set the special name and RTSpecialName flags.
+ // COMPAT: CoreCLR does not check if the field is actually in an enum type.
+ if (strcmp(name, COR_ENUM_FIELD_NAME) == 0)
+ {
+ fieldFlags |= fdRTSpecialName | fdSpecialName;
+ }
+ if (!md_set_column_value_as_constant(c, mdtField_Flags, fieldFlags))
+ return E_FAIL;
+ }
+ else
+ {
+ uint32_t fieldFlags = 0;
+
+ // If the field name has the special name for enum fields,
+ // set the special name and RTSpecialName flags.
+ // COMPAT: CoreCLR does not check if the field is actually in an enum type.
+ if (strcmp(name, COR_ENUM_FIELD_NAME) == 0)
+ {
+ fieldFlags |= fdRTSpecialName | fdSpecialName;
+ }
+ if (!md_set_column_value_as_constant(c, mdtField_Flags, fieldFlags))
+ return E_FAIL;
+ }
+
+ uint8_t const* sig = (uint8_t const*)pvSigBlob;
+ uint32_t sigLength = cbSigBlob;
+ if (sigLength != 0)
+ {
+ if (!md_set_column_value_as_blob(c, mdtField_Signature, sig, sigLength))
+ return E_FAIL;
+ }
+
+ if (hasConstant)
+ {
+ md_added_row_t constant;
+ if (!md_append_row(MetaData(), mdtid_Constant, &constant))
+ return E_FAIL;
+
+ if (!md_set_column_value_as_cursor(constant, mdtConstant_Parent, c))
+ return E_FAIL;
+
+ uint32_t type = dwCPlusTypeFlag;
+ if (!md_set_column_value_as_constant(constant, mdtConstant_Type, type))
+ return E_FAIL;
+
+ uint64_t defaultConstantValue = 0;
+ uint8_t const* pConstantValue = (uint8_t const*)pValue;
+ if (pConstantValue == nullptr)
+ pConstantValue = (uint8_t const*)&defaultConstantValue;
+
+ uint32_t constantSize = GetSizeOfConstantBlob(dwCPlusTypeFlag, pConstantValue, cchValue);
+ if (!md_set_column_value_as_blob(constant, mdtConstant_Value, pConstantValue, constantSize))
+ return E_FAIL;
+
+ }
+
+ if (!md_cursor_to_token(c, pmd))
+ return CLDB_E_FILE_CORRUPT;
+
+ // TODO: Update EncLog
+ return S_OK;
+}
+
+HRESULT MetadataEmit::DefineProperty(
+ mdTypeDef td,
+ LPCWSTR szProperty,
+ DWORD dwPropFlags,
+ PCCOR_SIGNATURE pvSig,
+ ULONG cbSig,
+ DWORD dwCPlusTypeFlag,
+ void const *pValue,
+ ULONG cchValue,
+ mdMethodDef mdSetter,
+ mdMethodDef mdGetter,
+ mdMethodDef rmdOtherMethods[],
+ mdProperty *pmdProp)
+{
+ return FindOrCreateParentedRow(
+ MetaData(),
+ td,
+ mdtid_PropertyMap,
+ mdtPropertyMap_Parent,
+ [=] (mdcursor_t map)
+ {
+ HRESULT hr;
+ md_added_row_t c;
+ if (!md_add_new_row_to_list(map, mdtPropertyMap_Parent, &c))
+ return E_FAIL;
+
+ pal::StringConvert cvt(szProperty);
+ if (!cvt.Success())
+ return E_INVALIDARG;
+
+ char const* name = cvt;
+ if (!md_set_column_value_as_utf8(c, mdtProperty_Name, name))
+ return E_FAIL;
+
+
+ if (pvSig != nullptr)
+ {
+ uint8_t const* sig = (uint8_t const*)pvSig;
+ uint32_t sigLength = cbSig;
+ if (!md_set_column_value_as_blob(c, mdtProperty_Type, sig, sigLength))
+ return E_FAIL;
+ }
+
+ uint32_t propFlags = (uint32_t)dwPropFlags;
+ if (propFlags != std::numeric_limits::max())
+ {
+ propFlags &= ~prReservedMask;
+ }
+ else
+ {
+ propFlags = 0;
+ }
+
+ bool hasConstant = false;
+ // See if there is a Constant.
+ if ((dwCPlusTypeFlag != ELEMENT_TYPE_VOID && dwCPlusTypeFlag != ELEMENT_TYPE_END &&
+ dwCPlusTypeFlag != UINT32_MAX) &&
+ (pValue || (pValue == 0 && (dwCPlusTypeFlag == ELEMENT_TYPE_STRING ||
+ dwCPlusTypeFlag == ELEMENT_TYPE_CLASS))))
+ {
+ if (propFlags == std::numeric_limits::max())
+ propFlags = 0;
+ propFlags |= prHasDefault;
+ hasConstant = true;
+ }
+
+ if (!md_set_column_value_as_constant(c, mdtProperty_Flags, propFlags))
+ return E_FAIL;
+
+ if (mdGetter != mdMethodDefNil)
+ {
+ RETURN_IF_FAILED(AddMethodSemantic(MetaData(), c, msGetter, mdGetter));
+ }
+
+ if (mdSetter != mdMethodDefNil)
+ {
+ RETURN_IF_FAILED(AddMethodSemantic(MetaData(), c, msSetter, mdSetter));
+ }
+
+ if (rmdOtherMethods)
+ {
+ for (size_t i = 0; RidFromToken(rmdOtherMethods[i]) != mdTokenNil; ++i)
+ {
+ RETURN_IF_FAILED(AddMethodSemantic(MetaData(), c, msOther, rmdOtherMethods[i]));
+ }
+ }
+
+ if (hasConstant)
+ {
+ md_added_row_t constant;
+ if (!md_append_row(MetaData(), mdtid_Constant, &constant))
+ return E_FAIL;
+
+ if (!md_set_column_value_as_cursor(constant, mdtConstant_Parent, c))
+ return E_FAIL;
+
+ uint32_t type = dwCPlusTypeFlag;
+ if (!md_set_column_value_as_constant(constant, mdtConstant_Type, type))
+ return E_FAIL;
+
+ uint64_t defaultConstantValue = 0;
+ uint8_t const* pConstantValue = (uint8_t const*)pValue;
+ if (pConstantValue == nullptr)
+ pConstantValue = (uint8_t const*)&defaultConstantValue;
+
+ uint32_t constantSize = GetSizeOfConstantBlob(dwCPlusTypeFlag, pConstantValue, cchValue);
+ if (!md_set_column_value_as_blob(constant, mdtConstant_Value, pConstantValue, constantSize))
+ return E_FAIL;
+ }
+
+ if (!md_cursor_to_token(c, pmdProp))
+ return CLDB_E_FILE_CORRUPT;
+
+ // TODO: Update EncLog
+
+ return S_OK;
+ }
+ );
+}
+
+HRESULT MetadataEmit::DefineParam(
+ mdMethodDef md,
+ ULONG ulParamSeq,
+ LPCWSTR szName,
+ DWORD dwParamFlags,
+ DWORD dwCPlusTypeFlag,
+ void const *pValue,
+ ULONG cchValue,
+ mdParamDef *ppd)
+{
+ pal::StringConvert cvt(szName);
+ if (!cvt.Success())
+ return E_INVALIDARG;
+
+ char const* name = cvt;
+
+ md_added_row_t c;
+ mdcursor_t method;
+ if (!md_token_to_cursor(MetaData(), md, &method))
+ return CLDB_E_FILE_CORRUPT;
+
+ if (!md_add_new_row_to_sorted_list(method, mdtMethodDef_ParamList, mdtParam_Sequence, (uint32_t)ulParamSeq, &c))
+ return E_FAIL;
+
+ if (!md_set_column_value_as_utf8(c, mdtParam_Name, name))
+ return E_FAIL;
+
+ bool hasConstant = false;
+ // See if there is a Constant.
+ if ((dwCPlusTypeFlag != ELEMENT_TYPE_VOID && dwCPlusTypeFlag != ELEMENT_TYPE_END &&
+ dwCPlusTypeFlag != UINT32_MAX) &&
+ (pValue || (pValue == 0 && (dwCPlusTypeFlag == ELEMENT_TYPE_STRING ||
+ dwCPlusTypeFlag == ELEMENT_TYPE_CLASS))))
+ {
+ hasConstant = true;
+ }
+
+ if (dwParamFlags != std::numeric_limits::max())
+ {
+ // TODO: Handle reserved flags
+ uint32_t flags = dwParamFlags;
+
+ if (!md_set_column_value_as_constant(c, mdtParam_Flags, flags))
+ return E_FAIL;
+ }
+ else
+ {
+ uint32_t flags = 0;
+ if (!md_set_column_value_as_constant(c, mdtParam_Flags, flags))
+ return E_FAIL;
+ }
+
+ if (hasConstant)
+ {
+ md_added_row_t constant;
+ if (!md_append_row(MetaData(), mdtid_Constant, &constant))
+ return E_FAIL;
+
+ if (!md_set_column_value_as_cursor(constant, mdtConstant_Parent, c))
+ return E_FAIL;
+
+ uint32_t type = dwCPlusTypeFlag;
+ if (!md_set_column_value_as_constant(constant, mdtConstant_Type, type))
+ return E_FAIL;
+
+ uint64_t defaultConstantValue = 0;
+ uint8_t const* pConstantValue = (uint8_t const*)pValue;
+ if (pConstantValue == nullptr)
+ pConstantValue = (uint8_t const*)&defaultConstantValue;
+
+ uint32_t constantSize = GetSizeOfConstantBlob(dwCPlusTypeFlag, pConstantValue, cchValue);
+ if (!md_set_column_value_as_blob(constant, mdtConstant_Value, pConstantValue, constantSize))
+ return E_FAIL;
+
+ }
+
+ if (!md_cursor_to_token(c, ppd))
+ return CLDB_E_FILE_CORRUPT;
+
+ // TODO: Update EncLog
+ return S_OK;
+}
+
+HRESULT MetadataEmit::SetFieldProps(
+ mdFieldDef fd,
+ DWORD dwFieldFlags,
+ DWORD dwCPlusTypeFlag,
+ void const *pValue,
+ ULONG cchValue)
+{
+ mdcursor_t c;
+ if (!md_token_to_cursor(MetaData(), fd, &c))
+ return CLDB_E_FILE_CORRUPT;
+
+ bool hasConstant = false;
+ // See if there is a Constant.
+ if ((dwCPlusTypeFlag != ELEMENT_TYPE_VOID && dwCPlusTypeFlag != ELEMENT_TYPE_END &&
+ dwCPlusTypeFlag != UINT32_MAX) &&
+ (pValue || (pValue == 0 && (dwCPlusTypeFlag == ELEMENT_TYPE_STRING ||
+ dwCPlusTypeFlag == ELEMENT_TYPE_CLASS))))
+ {
+ hasConstant = true;
+ }
+
+ if (dwFieldFlags != std::numeric_limits::max())
+ {
+ // TODO: Handle reserved flags
+ uint32_t fieldFlags = dwFieldFlags;
+ if (!md_set_column_value_as_constant(c, mdtField_Flags, fieldFlags))
+ return E_FAIL;
+ }
+
+ if (hasConstant)
+ {
+ // Create or update the Constant record that points to this field.
+ return FindOrCreateParentedRow(MetaData(), fd, mdtid_Constant, mdtConstant_Parent, [=](mdcursor_t constant)
+ {
+ uint32_t type = dwCPlusTypeFlag;
+ if (!md_set_column_value_as_constant(constant, mdtConstant_Type, type))
+ return E_FAIL;
+
+ uint64_t defaultConstantValue = 0;
+ uint8_t const* pConstantValue = (uint8_t const*)pValue;
+ if (pConstantValue == nullptr)
+ pConstantValue = (uint8_t const*)&defaultConstantValue;
+
+ uint32_t constantSize = GetSizeOfConstantBlob(dwCPlusTypeFlag, pConstantValue, cchValue);
+ if (!md_set_column_value_as_blob(constant, mdtConstant_Value, pConstantValue, constantSize))
+ return E_FAIL;
+
+ return S_OK;
+ });
+ }
+ return S_OK;
+}
+
+HRESULT MetadataEmit::SetPropertyProps(
+ mdProperty pr,
+ DWORD dwPropFlags,
+ DWORD dwCPlusTypeFlag,
+ void const *pValue,
+ ULONG cchValue,
+ mdMethodDef mdSetter,
+ mdMethodDef mdGetter,
+ mdMethodDef rmdOtherMethods[])
+{
+ HRESULT hr;
+ mdcursor_t c;
+ if (!md_token_to_cursor(MetaData(), pr, &c))
+ return CLDB_E_FILE_CORRUPT;
+
+ if (dwPropFlags != std::numeric_limits::max())
+ {
+ dwPropFlags &= ~prReservedMask;
+ }
+
+ bool hasConstant = false;
+ // See if there is a Constant.
+ if ((dwCPlusTypeFlag != ELEMENT_TYPE_VOID && dwCPlusTypeFlag != ELEMENT_TYPE_END &&
+ dwCPlusTypeFlag != UINT32_MAX) &&
+ (pValue || (pValue == 0 && (dwCPlusTypeFlag == ELEMENT_TYPE_STRING ||
+ dwCPlusTypeFlag == ELEMENT_TYPE_CLASS))))
+ {
+ if (dwPropFlags == std::numeric_limits::max())
+ dwPropFlags = 0;
+ dwPropFlags |= prHasDefault;
+ hasConstant = true;
+ }
+
+ if (dwPropFlags != std::numeric_limits::max())
+ {
+ // TODO: Preserve reserved flags
+ uint32_t flags = dwPropFlags;
+ if (!md_set_column_value_as_constant(c, mdtProperty_Flags, flags))
+ return E_FAIL;
+ }
+
+ if (mdGetter != mdMethodDefNil)
+ {
+ RETURN_IF_FAILED(RemoveSemantics(MetaData(), pr, msGetter));
+ RETURN_IF_FAILED(AddMethodSemantic(MetaData(), c, msGetter, mdGetter));
+ }
+
+ if (mdSetter != mdMethodDefNil)
+ {
+ RETURN_IF_FAILED(RemoveSemantics(MetaData(), pr, msSetter));
+ RETURN_IF_FAILED(AddMethodSemantic(MetaData(), c, msSetter, mdSetter));
+ }
+
+ if (rmdOtherMethods)
+ {
+ RETURN_IF_FAILED(RemoveSemantics(MetaData(), pr, msOther));
+ for (size_t i = 0; RidFromToken(rmdOtherMethods[i]) != mdTokenNil; ++i)
+ {
+ RETURN_IF_FAILED(AddMethodSemantic(MetaData(), c, msOther, rmdOtherMethods[i]));
+ }
+ }
+
+ if (hasConstant)
+ {
+ // Create or update the Constant record that points to this property.
+ return FindOrCreateParentedRow(MetaData(), pr, mdtid_Constant, mdtConstant_Parent, [=](mdcursor_t constant)
+ {
+ uint32_t type = dwCPlusTypeFlag;
+ if (!md_set_column_value_as_constant(constant, mdtConstant_Type, type))
+ return E_FAIL;
+
+ uint64_t defaultConstantValue = 0;
+ uint8_t const* pConstantValue = (uint8_t const*)pValue;
+ if (pConstantValue == nullptr)
+ pConstantValue = (uint8_t const*)&defaultConstantValue;
+
+ uint32_t constantSize = GetSizeOfConstantBlob(dwCPlusTypeFlag, pConstantValue, cchValue);
+ if (!md_set_column_value_as_blob(constant, mdtConstant_Value, pConstantValue, constantSize))
+ return E_FAIL;
+
+ return S_OK;
+ });
+ }
+
+ // TODO: Update EncLog
+
+ return S_OK;
+}
+
+HRESULT MetadataEmit::SetParamProps(
+ mdParamDef pd,
+ LPCWSTR szName,
+ DWORD dwParamFlags,
+ DWORD dwCPlusTypeFlag,
+ void const *pValue,
+ ULONG cchValue)
+{
+ mdcursor_t c;
+ if (!md_token_to_cursor(MetaData(), pd, &c))
+ return CLDB_E_FILE_CORRUPT;
+
+ pal::StringConvert cvt(szName);
+ if (!cvt.Success())
+ return E_INVALIDARG;
+
+ char const* name = cvt;
+ if (!md_set_column_value_as_utf8(c, mdtParam_Name, name))
+ return E_FAIL;
+
+ bool hasConstant = false;
+ // See if there is a Constant.
+ if ((dwCPlusTypeFlag != ELEMENT_TYPE_VOID && dwCPlusTypeFlag != ELEMENT_TYPE_END &&
+ dwCPlusTypeFlag != UINT32_MAX) &&
+ (pValue || (pValue == 0 && (dwCPlusTypeFlag == ELEMENT_TYPE_STRING ||
+ dwCPlusTypeFlag == ELEMENT_TYPE_CLASS))))
+ {
+ hasConstant = true;
+ }
+
+ if (dwParamFlags != std::numeric_limits::max())
+ {
+ // TODO: Handle reserved flags
+ uint32_t flags = dwParamFlags;
+ if (!md_set_column_value_as_constant(c, mdtParam_Flags, flags))
+ return E_FAIL;
+ }
+
+ if (hasConstant)
+ {
+ // Create or update the Constant record that points to this field.
+ return FindOrCreateParentedRow(MetaData(), pd, mdtid_Constant, mdtConstant_Parent, [=](mdcursor_t constant)
+ {
+ uint32_t type = dwCPlusTypeFlag;
+ if (!md_set_column_value_as_constant(constant, mdtConstant_Type, type))
+ return E_FAIL;
+
+ uint64_t defaultConstantValue = 0;
+ uint8_t const* pConstantValue = (uint8_t const*)pValue;
+ if (pConstantValue == nullptr)
+ pConstantValue = (uint8_t const*)&defaultConstantValue;
+
+ uint32_t constantSize = GetSizeOfConstantBlob(dwCPlusTypeFlag, pConstantValue, cchValue);
+ if (!md_set_column_value_as_blob(constant, mdtConstant_Value, pConstantValue, constantSize))
+ return E_FAIL;
+
+ return S_OK;
+ });
+ }
+
+ return S_OK;
+}
+
+
+HRESULT MetadataEmit::DefineSecurityAttributeSet(
+ mdToken tkObj,
+ COR_SECATTR rSecAttrs[],
+ ULONG cSecAttrs,
+ ULONG *pulErrorAttr)
+{
+ // Not implemented in CoreCLR
+ UNREFERENCED_PARAMETER(tkObj);
+ UNREFERENCED_PARAMETER(rSecAttrs);
+ UNREFERENCED_PARAMETER(cSecAttrs);
+ UNREFERENCED_PARAMETER(pulErrorAttr);
+ return E_NOTIMPL;
+}
+
+HRESULT MetadataEmit::ApplyEditAndContinue(
+ IUnknown *pImport)
+{
+ HRESULT hr;
+ minipal::com_ptr delta;
+ RETURN_IF_FAILED(pImport->QueryInterface(IID_IDNMDOwner, (void**)&delta));
+
+ if (!md_apply_delta(MetaData(), delta->MetaData()))
+ return E_INVALIDARG;
+
+ // TODO: Reset and copy EncLog from delta metadata to this metadata.
+ return S_OK;
+}
+
+HRESULT MetadataEmit::TranslateSigWithScope(
+ IMetaDataAssemblyImport *pAssemImport,
+ void const *pbHashValue,
+ ULONG cbHashValue,
+ IMetaDataImport *import,
+ PCCOR_SIGNATURE pbSigBlob,
+ ULONG cbSigBlob,
+ IMetaDataAssemblyEmit *pAssemEmit,
+ IMetaDataEmit *emit,
+ PCOR_SIGNATURE pvTranslatedSig,
+ ULONG cbTranslatedSigMax,
+ ULONG *pcbTranslatedSig)
+{
+ HRESULT hr;
+ minipal::com_ptr assemImport{};
+
+ if (pAssemImport != nullptr)
+ RETURN_IF_FAILED(pAssemImport->QueryInterface(IID_IDNMDOwner, (void**)&assemImport));
+
+ minipal::com_ptr assemEmit{};
+ if (pAssemEmit != nullptr)
+ RETURN_IF_FAILED(pAssemEmit->QueryInterface(IID_IDNMDOwner, (void**)&assemEmit));
+
+ if (import == nullptr)
+ return E_INVALIDARG;
+
+ minipal::com_ptr moduleImport{};
+ RETURN_IF_FAILED(import->QueryInterface(IID_IDNMDOwner, (void**)&moduleImport));
+
+ minipal::com_ptr moduleEmit{};
+ RETURN_IF_FAILED(emit->QueryInterface(IID_IDNMDOwner, (void**)&moduleEmit));
+
+ inline_span translatedSig;
+ RETURN_IF_FAILED(ImportSignatureIntoModule(
+ assemImport->MetaData(),
+ moduleImport->MetaData(),
+ { reinterpret_cast(pbHashValue), cbHashValue },
+ assemEmit->MetaData(),
+ moduleEmit->MetaData(),
+ { pbSigBlob, cbSigBlob },
+ [](mdcursor_t){},
+ translatedSig));
+
+ std::copy_n(translatedSig.begin(), std::min(translatedSig.size(), (size_t)cbTranslatedSigMax), (uint8_t*)pvTranslatedSig);
+
+ *pcbTranslatedSig = (ULONG)translatedSig.size();
+ return translatedSig.size() > cbTranslatedSigMax ? CLDB_S_TRUNCATION : S_OK;
+}
+
+HRESULT MetadataEmit::SetMethodImplFlags(
+ mdMethodDef md,
+ DWORD dwImplFlags)
+{
+ mdcursor_t c;
+ if (!md_token_to_cursor(MetaData(), md, &c))
+ return E_INVALIDARG;
+
+ uint32_t flags = (uint32_t)dwImplFlags;
+ if (!md_set_column_value_as_constant(c, mdtMethodDef_ImplFlags, flags))
+ return E_FAIL;
+
+ // TODO: Update ENC log
+ return S_OK;
+}
+
+HRESULT MetadataEmit::SetFieldRVA(
+ mdFieldDef fd,
+ ULONG ulRVA)
+{
+ uint32_t rva = (uint32_t)ulRVA;
+
+ HRESULT hr = FindOrCreateParentedRow(MetaData(), fd, mdtid_FieldRva, mdtFieldRva_Field, [=](mdcursor_t c)
+ {
+ if (!md_set_column_value_as_constant(c, mdtFieldRva_Rva, rva))
+ return E_FAIL;
+
+ return S_OK;
+ });
+
+ RETURN_IF_FAILED(hr);
+
+ mdcursor_t field;
+ if (!md_token_to_cursor(MetaData(), fd, &field))
+ return E_INVALIDARG;
+
+ uint32_t flags;
+ if (!md_get_column_value_as_constant(field, mdtField_Flags, &flags))
+ return CLDB_E_FILE_CORRUPT;
+
+ flags |= fdHasFieldRVA;
+ if (!md_set_column_value_as_constant(field, mdtField_Flags, flags))
+ return E_FAIL;
+
+ // TODO: Update ENC log
+
+ return S_OK;
+}
+
+HRESULT MetadataEmit::Merge(
+ IMetaDataImport *pImport,
+ IMapToken *pHostMapToken,
+ IUnknown *pHandler)
+{
+ // Not Implemented in CoreCLR
+ UNREFERENCED_PARAMETER(pImport);
+ UNREFERENCED_PARAMETER(pHostMapToken);
+ UNREFERENCED_PARAMETER(pHandler);
+ return E_NOTIMPL;
+}
+
+HRESULT MetadataEmit::MergeEnd()
+{
+ // Not Implemented in CoreCLR
+ return E_NOTIMPL;
+}
+
+HRESULT MetadataEmit::DefineMethodSpec(
+ mdToken tkParent,
+ PCCOR_SIGNATURE pvSigBlob,
+ ULONG cbSigBlob,
+ mdMethodSpec *pmi)
+{
+ if (TypeFromToken(tkParent) != mdtMethodDef && TypeFromToken(tkParent) != mdtMemberRef)
+ return META_E_BAD_INPUT_PARAMETER;
+
+ if (cbSigBlob == 0 || pvSigBlob == nullptr || pmi == nullptr)
+ return META_E_BAD_INPUT_PARAMETER;
+
+ md_added_row_t c;
+ if (!md_append_row(MetaData(), mdtid_MethodSpec, &c))
+ return E_FAIL;
+
+ if (!md_set_column_value_as_token(c, mdtMethodSpec_Method, tkParent))
+ return E_FAIL;
+
+ uint32_t sigLength = cbSigBlob;
+ if (!md_set_column_value_as_blob(c, mdtMethodSpec_Instantiation, pvSigBlob, sigLength))
+ return E_FAIL;
+
+ if (!md_cursor_to_token(c, pmi))
+ return CLDB_E_FILE_CORRUPT;
+
+ // TODO: Update EncLog
+ return S_OK;
+}
+
+// TODO: Add EnC mode support to the emit implementation.
+// Maybe we can do a layering model where we have a base emit implementation that doesn't support EnC,
+// and then a wrapper that does?
+HRESULT MetadataEmit::GetDeltaSaveSize(
+ CorSaveSize fSave,
+ DWORD *pdwSaveSize)
+{
+ UNREFERENCED_PARAMETER(fSave);
+ UNREFERENCED_PARAMETER(pdwSaveSize);
+ return META_E_NOT_IN_ENC_MODE;
+}
+
+HRESULT MetadataEmit::SaveDelta(
+ LPCWSTR szFile,
+ DWORD dwSaveFlags)
+{
+ UNREFERENCED_PARAMETER(szFile);
+ UNREFERENCED_PARAMETER(dwSaveFlags);
+ return META_E_NOT_IN_ENC_MODE;
+}
+
+HRESULT MetadataEmit::SaveDeltaToStream(
+ IStream *pIStream,
+ DWORD dwSaveFlags)
+{
+ UNREFERENCED_PARAMETER(pIStream);
+ UNREFERENCED_PARAMETER(dwSaveFlags);
+ return META_E_NOT_IN_ENC_MODE;
+}
+
+HRESULT MetadataEmit::SaveDeltaToMemory(
+ void *pbData,
+ ULONG cbData)
+{
+ UNREFERENCED_PARAMETER(pbData);
+ UNREFERENCED_PARAMETER(cbData);
+ return META_E_NOT_IN_ENC_MODE;
+}
+
+HRESULT MetadataEmit::DefineGenericParam(
+ mdToken tk,
+ ULONG ulParamSeq,
+ DWORD dwParamFlags,
+ LPCWSTR szname,
+ DWORD reserved,
+ mdToken rtkConstraints[],
+ mdGenericParam *pgp)
+{
+ if (reserved != 0)
+ return META_E_BAD_INPUT_PARAMETER;
+
+ if (TypeFromToken(tk) != mdtMethodDef && TypeFromToken(tk) != mdtTypeDef)
+ return META_E_BAD_INPUT_PARAMETER;
+
+ // TODO: Check for duplicates
+
+ md_added_row_t c;
+ if (!md_append_row(MetaData(), mdtid_GenericParam, &c))
+ return E_FAIL;
+
+ if (!md_set_column_value_as_token(c, mdtGenericParam_Owner, tk))
+ return E_FAIL;
+
+ uint32_t paramSeq = ulParamSeq;
+ if (!md_set_column_value_as_constant(c, mdtGenericParam_Number, paramSeq))
+ return E_FAIL;
+
+ uint32_t flags = dwParamFlags;
+ if (!md_set_column_value_as_constant(c, mdtGenericParam_Flags, flags))
+ return E_FAIL;
+
+ if (szname != nullptr)
+ {
+ pal::StringConvert cvt(szname);
+ if (!cvt.Success())
+ return E_INVALIDARG;
+
+ char const* name = cvt;
+ if (!md_set_column_value_as_utf8(c, mdtGenericParam_Name, name))
+ return E_FAIL;
+ }
+ else
+ {
+ char const* name = nullptr;
+ if (!md_set_column_value_as_utf8(c, mdtGenericParam_Name, name))
+ return E_FAIL;
+ }
+
+ if (rtkConstraints != nullptr)
+ {
+ for (size_t i = 0; RidFromToken(rtkConstraints[i]) != mdTokenNil; i++)
+ {
+ md_added_row_t added_row;
+ if (!md_append_row(MetaData(), mdtid_GenericParamConstraint, &added_row))
+ return E_FAIL;
+
+ if (!md_set_column_value_as_cursor(added_row, mdtGenericParamConstraint_Owner, c))
+ return E_FAIL;
+
+ if (md_set_column_value_as_token(added_row, mdtGenericParamConstraint_Constraint, rtkConstraints[i]))
+ return E_FAIL;
+
+ // TODO: Update EncLog
+ }
+ }
+
+ // TODO: Update EncLog
+ if (!md_cursor_to_token(c, pgp))
+ return CLDB_E_FILE_CORRUPT;
+
+ return S_OK;
+}
+
+HRESULT MetadataEmit::SetGenericParamProps(
+ mdGenericParam gp,
+ DWORD dwParamFlags,
+ LPCWSTR szName,
+ DWORD reserved,
+ mdToken rtkConstraints[])
+{
+ if (reserved != 0)
+ return META_E_BAD_INPUT_PARAMETER;
+
+ mdcursor_t c;
+ if (!md_token_to_cursor(MetaData(), gp, &c))
+ return E_INVALIDARG;
+
+ uint32_t flags = dwParamFlags;
+ if (!md_set_column_value_as_constant(c, mdtGenericParam_Flags, flags))
+ return E_FAIL;
+
+ if (szName != nullptr)
+ {
+ pal::StringConvert cvt(szName);
+ if (!cvt.Success())
+ return E_INVALIDARG;
+
+ char const* name = cvt;
+ if (!md_set_column_value_as_utf8(c, mdtGenericParam_Name, name))
+ return E_FAIL;
+ }
+
+ if (rtkConstraints != nullptr)
+ {
+ // Delete all existing constraints
+ mdcursor_t constraint;
+ uint32_t count;
+ if (!md_create_cursor(MetaData(), mdtid_GenericParamConstraint, &constraint, &count))
+ return E_FAIL;
+
+ md_range_result_t result = md_find_range_from_cursor(constraint, mdtGenericParamConstraint_Owner, gp, &constraint, &count);
+ if (result != MD_RANGE_NOT_FOUND)
+ {
+ for (uint32_t i = 0; i < count; ++i, md_cursor_next(&constraint))
+ {
+ mdToken parent;
+ if (!md_get_column_value_as_token(constraint, mdtGenericParamConstraint_Owner, &parent))
+ return E_FAIL;
+
+ if (parent == gp)
+ {
+ parent = mdGenericParamNil;
+ if (!md_set_column_value_as_token(constraint, mdtGenericParamConstraint_Owner, parent))
+ return E_FAIL;
+ }
+ }
+ }
+
+ for (size_t i = 0; RidFromToken(rtkConstraints[i]) != mdTokenNil; i++)
+ {
+ md_added_row_t added_row;
+ if (!md_append_row(MetaData(), mdtid_GenericParamConstraint, &added_row))
+ return E_FAIL;
+
+ if (!md_set_column_value_as_cursor(added_row, mdtGenericParamConstraint_Owner, c))
+ return E_FAIL;
+
+ if (md_set_column_value_as_token(added_row, mdtGenericParamConstraint_Constraint, rtkConstraints[i]))
+ return E_FAIL;
+
+ // TODO: Update EncLog
+ }
+ }
+
+ // TODO: Update EncLog
+
+ return S_OK;
+}
+
+HRESULT MetadataEmit::ResetENCLog()
+{
+ return META_E_NOT_IN_ENC_MODE;
+}
+
+HRESULT MetadataEmit::DefineAssembly(
+ void const *pbPublicKey,
+ ULONG cbPublicKey,
+ ULONG ulHashAlgId,
+ LPCWSTR szName,
+ ASSEMBLYMETADATA const *pMetaData,
+ DWORD dwAssemblyFlags,
+ mdAssembly *pma)
+{
+ if (szName == nullptr || pMetaData == nullptr || pma == nullptr)
+ return E_INVALIDARG;
+
+ pal::StringConvert cvt(szName);
+ if (!cvt.Success())
+ return E_INVALIDARG;
+
+ mdcursor_t c;
+ uint32_t count;
+ if (!md_create_cursor(MetaData(), mdtid_Assembly, &c, &count))
+ {
+ if (md_append_row(MetaData(), mdtid_Assembly, &c))
+ {
+ md_commit_row_add(c);
+ }
+ else
+ {
+ return E_FAIL;
+ }
+ }
+
+ uint32_t assemblyFlags = dwAssemblyFlags;
+ if (cbPublicKey != 0)
+ {
+ assemblyFlags |= afPublicKey;
+ }
+
+ const uint8_t* publicKey = (const uint8_t*)pbPublicKey;
+ if (publicKey != nullptr)
+ {
+ uint32_t publicKeyLength = cbPublicKey;
+ if (!md_set_column_value_as_blob(c, mdtAssembly_PublicKey, publicKey, publicKeyLength))
+ return E_FAIL;
+ }
+ else
+ {
+ uint32_t publicKeyLength = 0;
+ if (!md_set_column_value_as_blob(c, mdtAssembly_PublicKey, publicKey, publicKeyLength))
+ return E_FAIL;
+ }
+
+ if (!md_set_column_value_as_constant(c, mdtAssembly_Flags, assemblyFlags))
+ return E_FAIL;
+
+ char const* name = cvt;
+ if (!md_set_column_value_as_utf8(c, mdtAssembly_Name, name))
+ return E_FAIL;
+
+ uint32_t hashAlgId = ulHashAlgId;
+ if (!md_set_column_value_as_constant(c, mdtAssembly_HashAlgId, hashAlgId))
+ return E_FAIL;
+
+ uint32_t majorVersion = pMetaData->usMajorVersion != std::numeric_limits::max() ? pMetaData->usMajorVersion : 0;
+ if (!md_set_column_value_as_constant(c, mdtAssembly_MajorVersion, majorVersion))
+ return E_FAIL;
+
+ uint32_t minorVersion = pMetaData->usMinorVersion != std::numeric_limits::max() ? pMetaData->usMinorVersion : 0;
+ if (!md_set_column_value_as_constant(c, mdtAssembly_MinorVersion, minorVersion))
+ return E_FAIL;
+
+ uint32_t buildNumber = pMetaData->usBuildNumber != std::numeric_limits::max() ? pMetaData->usBuildNumber : 0;
+ if (!md_set_column_value_as_constant(c, mdtAssembly_BuildNumber, buildNumber))
+ return E_FAIL;
+
+ uint32_t revisionNumber = pMetaData->usRevisionNumber != std::numeric_limits::max() ? pMetaData->usRevisionNumber : 0;
+ if (!md_set_column_value_as_constant(c, mdtAssembly_RevisionNumber, revisionNumber))
+ return E_FAIL;
+
+ if (pMetaData->szLocale != nullptr)
+ {
+ pal::StringConvert cvtLocale(pMetaData->szLocale);
+ if (!cvtLocale.Success())
+ return E_INVALIDARG;
+
+ char const* locale = cvtLocale;
+ if (!md_set_column_value_as_utf8(c, mdtAssembly_Culture, locale))
+ return E_FAIL;
+ }
+ else
+ {
+ char const* locale = nullptr;
+ if (!md_set_column_value_as_utf8(c, mdtAssembly_Culture, locale))
+ return E_FAIL;
+ }
+
+ if (!md_cursor_to_token(c, pma))
+ return E_FAIL;
+
+ // TODO: Update ENC Log
+
+ return S_OK;
+}
+
+HRESULT MetadataEmit::DefineAssemblyRef(
+ void const *pbPublicKeyOrToken,
+ ULONG cbPublicKeyOrToken,
+ LPCWSTR szName,
+ ASSEMBLYMETADATA const *pMetaData,
+ void const *pbHashValue,
+ ULONG cbHashValue,
+ DWORD dwAssemblyRefFlags,
+ mdAssemblyRef *pmdar)
+{
+ if (szName == nullptr || pMetaData == nullptr || pmdar == nullptr)
+ return E_INVALIDARG;
+
+ pal::StringConvert cvt(szName);
+ if (!cvt.Success())
+ return E_INVALIDARG;
+
+ md_added_row_t c;
+ if (!md_append_row(MetaData(), mdtid_AssemblyRef, &c))
+ return E_FAIL;
+
+ const uint8_t* publicKey = (const uint8_t*)pbPublicKeyOrToken;
+ if (publicKey != nullptr)
+ {
+ uint32_t publicKeyLength = cbPublicKeyOrToken;
+ if (!md_set_column_value_as_blob(c, mdtAssemblyRef_PublicKeyOrToken, publicKey, publicKeyLength))
+ return E_FAIL;
+ }
+ else
+ {
+ uint32_t publicKeyLength = 0;
+ if (!md_set_column_value_as_blob(c, mdtAssemblyRef_PublicKeyOrToken, publicKey, publicKeyLength))
+ return E_FAIL;
+ }
+
+ if (pbHashValue != nullptr)
+ {
+ uint8_t const* hashValue = (uint8_t const*)pbHashValue;
+ uint32_t hashValueLength = cbHashValue;
+ if (!md_set_column_value_as_blob(c, mdtAssemblyRef_HashValue, hashValue, hashValueLength))
+ return E_FAIL;
+ }
+ else
+ {
+ uint8_t const* hashValue = nullptr;
+ uint32_t hashValueLength = 0;
+ if (!md_set_column_value_as_blob(c, mdtAssemblyRef_HashValue, hashValue, hashValueLength))
+ return E_FAIL;
+ }
+
+ uint32_t assemblyFlags = PrepareForSaving(dwAssemblyRefFlags);
+ if (!md_set_column_value_as_constant(c, mdtAssemblyRef_Flags, assemblyFlags))
+ return E_FAIL;
+
+ char const* name = cvt;
+ if (!md_set_column_value_as_utf8(c, mdtAssemblyRef_Name, name))
+ return E_FAIL;
+
+ uint32_t majorVersion = pMetaData->usMajorVersion != std::numeric_limits::max() ? pMetaData->usMajorVersion : 0;
+ if (!md_set_column_value_as_constant(c, mdtAssemblyRef_MajorVersion, majorVersion))
+ return E_FAIL;
+
+ uint32_t minorVersion = pMetaData->usMinorVersion != std::numeric_limits::max() ? pMetaData->usMinorVersion : 0;
+ if (!md_set_column_value_as_constant(c, mdtAssemblyRef_MinorVersion, minorVersion))
+ return E_FAIL;
+
+ uint32_t buildNumber = pMetaData->usBuildNumber != std::numeric_limits::max() ? pMetaData->usBuildNumber : 0;
+ if (!md_set_column_value_as_constant(c, mdtAssemblyRef_BuildNumber, buildNumber))
+ return E_FAIL;
+
+ uint32_t revisionNumber = pMetaData->usRevisionNumber != std::numeric_limits::max() ? pMetaData->usRevisionNumber : 0;
+ if (!md_set_column_value_as_constant(c, mdtAssemblyRef_RevisionNumber, revisionNumber))
+ return E_FAIL;
+
+ if (pMetaData->szLocale != nullptr)
+ {
+ pal::StringConvert cvtLocale(pMetaData->szLocale);
+ if (!cvtLocale.Success())
+ return E_INVALIDARG;
+
+ char const* locale = cvtLocale;
+ if (!md_set_column_value_as_utf8(c, mdtAssemblyRef_Culture, locale))
+ return E_FAIL;
+ }
+ else
+ {
+ char const* locale = nullptr;
+ if (!md_set_column_value_as_utf8(c, mdtAssemblyRef_Culture, locale))
+ return E_FAIL;
+ }
+
+ if (!md_cursor_to_token(c, pmdar))
+ return E_FAIL;
+
+ // TODO: Update ENC Log
+
+ return S_OK;
+}
+
+HRESULT MetadataEmit::DefineFile(
+ LPCWSTR szName,
+ void const *pbHashValue,
+ ULONG cbHashValue,
+ DWORD dwFileFlags,
+ mdFile *pmdf)
+{
+
+ pal::StringConvert cvt(szName);
+ if (!cvt.Success())
+ return E_INVALIDARG;
+
+ md_added_row_t c;
+
+ if (!md_append_row(MetaData(), mdtid_File, &c))
+ return E_FAIL;
+
+ char const* name = cvt;
+
+ if (!md_set_column_value_as_utf8(c, mdtFile_Name, name))
+ return E_FAIL;
+
+ if (pbHashValue != nullptr)
+ {
+ uint8_t const* hashValue = (uint8_t const*)pbHashValue;
+ uint32_t hashValueLength = cbHashValue;
+ if (!md_set_column_value_as_blob(c, mdtFile_HashValue, hashValue, hashValueLength))
+ return E_FAIL;
+ }
+ else
+ {
+ uint8_t const* hashValue = nullptr;
+ uint32_t hashValueLength = 0;
+ if (!md_set_column_value_as_blob(c, mdtFile_HashValue, hashValue, hashValueLength))
+ return E_FAIL;
+ }
+
+ uint32_t fileFlags = dwFileFlags != std::numeric_limits::max() ? dwFileFlags : 0;
+ if (!md_set_column_value_as_constant(c, mdtFile_Flags, fileFlags))
+ return E_FAIL;
+
+ if (!md_cursor_to_token(c, pmdf))
+ return E_FAIL;
+
+ // TODO: Update ENC Log
+ return S_OK;
+}
+
+HRESULT MetadataEmit::DefineExportedType(
+ LPCWSTR szName,
+ mdToken tkImplementation,
+ mdTypeDef tkTypeDef,
+ DWORD dwExportedTypeFlags,
+ mdExportedType *pmdct)
+{
+ md_added_row_t c;
+ if (!md_append_row(MetaData(), mdtid_ExportedType, &c))
+ return E_FAIL;
+
+ pal::StringConvert cvt(szName);
+ if (!cvt.Success())
+ return E_INVALIDARG;
+
+ // TODO: check for duplicates
+ char const* ns;
+ char const* name;
+ SplitTypeName(cvt, &ns, &name);
+ if (!md_set_column_value_as_utf8(c, mdtExportedType_TypeNamespace, ns))
+ return E_FAIL;
+ if (!md_set_column_value_as_utf8(c, mdtExportedType_TypeName, name))
+ return E_FAIL;
+
+ if (!IsNilToken(tkImplementation))
+ {
+ if (!md_set_column_value_as_token(c, mdtExportedType_Implementation, tkImplementation))
+ return E_FAIL;
+ }
+ else
+ {
+ // COMPAT: When the implementation column isn't defined, it is defaulted to the 0 value.
+ // For the Implementation coded index, the nil File token is the 0 value;
+ mdToken nilToken = mdFileNil;
+ if (!md_set_column_value_as_token(c, mdtExportedType_Implementation, nilToken))
+ return E_FAIL;
+ }
+
+ if (!IsNilToken(tkTypeDef))
+ {
+ if (!md_set_column_value_as_constant(c, mdtExportedType_TypeDefId, tkTypeDef))
+ return E_FAIL;
+ }
+ else
+ {
+ mdToken nilToken = 0;
+ if (!md_set_column_value_as_constant(c, mdtExportedType_TypeDefId, nilToken))
+ return E_FAIL;
+ }
+
+ uint32_t exportedTypeFlags = dwExportedTypeFlags != std::numeric_limits::max() ? dwExportedTypeFlags : 0;
+ if (!md_set_column_value_as_constant(c, mdtExportedType_Flags, exportedTypeFlags))
+ return E_FAIL;
+
+ if (!md_cursor_to_token(c, pmdct))
+ return E_FAIL;
+
+ // TODO: Update ENC Log
+ return S_OK;
+}
+
+HRESULT MetadataEmit::DefineManifestResource(
+ LPCWSTR szName,
+ mdToken tkImplementation,
+ DWORD dwOffset,
+ DWORD dwResourceFlags,
+ mdManifestResource *pmdmr)
+{
+ // TODO: check for duplicates
+ md_added_row_t c;
+ if (!md_append_row(MetaData(), mdtid_ManifestResource, &c))
+ return E_FAIL;
+
+ pal::StringConvert cvt(szName);
+ if (!cvt.Success())
+ return E_INVALIDARG;
+
+ char const* name = cvt;
+ if (!md_set_column_value_as_utf8(c, mdtManifestResource_Name, name))
+ return E_FAIL;
+
+ if (!IsNilToken(tkImplementation))
+ {
+ if (!md_set_column_value_as_token(c, mdtManifestResource_Implementation, tkImplementation))
+ return E_FAIL;
+ }
+ else
+ {
+ // COMPAT: When the implementation column isn't defined, it is defaulted to the 0 value.
+ // For the Implementation coded index, the nil File token is the 0 value;
+ mdToken nilToken = mdFileNil;
+ if (!md_set_column_value_as_token(c, mdtManifestResource_Implementation, nilToken))
+ return E_FAIL;
+ }
+
+ uint32_t offset = dwOffset != std::numeric_limits::max() ? dwOffset : 0;
+ if (!md_set_column_value_as_constant(c, mdtManifestResource_Offset, offset))
+ return E_FAIL;
+
+ uint32_t resourceFlags = dwResourceFlags != std::numeric_limits::max() ? dwResourceFlags : 0;
+ if (!md_set_column_value_as_constant(c, mdtManifestResource_Flags, resourceFlags))
+ return E_FAIL;
+
+ if (!md_cursor_to_token(c, pmdmr))
+ return E_FAIL;
+
+ // TODO: Update ENC Log
+ return S_OK;
+}
+
+HRESULT MetadataEmit::SetAssemblyProps(
+ mdAssembly pma,
+ void const *pbPublicKey,
+ ULONG cbPublicKey,
+ ULONG ulHashAlgId,
+ LPCWSTR szName,
+ ASSEMBLYMETADATA const *pMetaData,
+ DWORD dwAssemblyFlags)
+{
+ mdcursor_t c;
+ if (!md_token_to_cursor(MetaData(), pma, &c))
+ return E_INVALIDARG;
+
+ uint32_t assemblyFlags = dwAssemblyFlags;
+ if (cbPublicKey != 0)
+ {
+ assemblyFlags |= afPublicKey;
+ }
+
+ const uint8_t* publicKey = (const uint8_t*)pbPublicKey;
+ if (publicKey != nullptr)
+ {
+ uint32_t publicKeyLength = cbPublicKey;
+ if (!md_set_column_value_as_blob(c, mdtAssembly_PublicKey, publicKey, publicKeyLength))
+ return E_FAIL;
+ }
+
+ if (!md_set_column_value_as_constant(c, mdtAssembly_Flags, assemblyFlags))
+ return E_FAIL;
+
+ if (szName != nullptr)
+ {
+ pal::StringConvert cvt(szName);
+ if (!cvt.Success())
+ return E_INVALIDARG;
+
+ char const* name = cvt;
+ if (!md_set_column_value_as_utf8(c, mdtAssembly_Name, name))
+ return E_FAIL;
+ }
+
+ if (ulHashAlgId != std::numeric_limits::max())
+ {
+ uint32_t hashAlgId = ulHashAlgId;
+ if (!md_set_column_value_as_constant(c, mdtAssembly_HashAlgId, hashAlgId))
+ return E_FAIL;
+ }
+
+ if (pMetaData->usMajorVersion != std::numeric_limits::max())
+ {
+ uint32_t majorVersion = pMetaData->usMajorVersion;
+ if (!md_set_column_value_as_constant(c, mdtAssembly_MajorVersion, majorVersion))
+ return E_FAIL;
+ }
+
+ if (pMetaData->usMinorVersion != std::numeric_limits