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::max()) + { + uint32_t minorVersion = pMetaData->usMinorVersion; + if (!md_set_column_value_as_constant(c, mdtAssembly_MinorVersion, minorVersion)) + return E_FAIL; + } + + if (pMetaData->usBuildNumber != std::numeric_limits::max()) + { + uint32_t buildNumber = pMetaData->usBuildNumber; + if (!md_set_column_value_as_constant(c, mdtAssembly_BuildNumber, buildNumber)) + return E_FAIL; + } + + if (pMetaData->usRevisionNumber != std::numeric_limits::max()) + { + uint32_t revisionNumber = pMetaData->usRevisionNumber; + 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; + } + + // TODO: Update ENC Log + + return S_OK; +} + +HRESULT MetadataEmit::SetAssemblyRefProps( + mdAssemblyRef ar, + void const *pbPublicKeyOrToken, + ULONG cbPublicKeyOrToken, + LPCWSTR szName, + ASSEMBLYMETADATA const *pMetaData, + void const *pbHashValue, + ULONG cbHashValue, + DWORD dwAssemblyRefFlags) +{ + mdcursor_t c; + if (!md_token_to_cursor(MetaData(), ar, &c)) + return E_INVALIDARG; + + uint32_t assemblyFlags = dwAssemblyRefFlags; + if (cbPublicKeyOrToken != 0) + { + assemblyFlags |= afPublicKey; + } + + 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; + } + + 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; + } + + if (!md_set_column_value_as_constant(c, mdtAssemblyRef_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, mdtAssemblyRef_Name, name)) + return E_FAIL; + } + + if (pMetaData->usMajorVersion != std::numeric_limits::max()) + { + uint32_t majorVersion = pMetaData->usMajorVersion; + if (!md_set_column_value_as_constant(c, mdtAssemblyRef_MajorVersion, majorVersion)) + return E_FAIL; + } + + if (pMetaData->usMinorVersion != std::numeric_limits::max()) + { + uint32_t minorVersion = pMetaData->usMinorVersion; + if (!md_set_column_value_as_constant(c, mdtAssemblyRef_MinorVersion, minorVersion)) + return E_FAIL; + } + + if (pMetaData->usBuildNumber != std::numeric_limits::max()) + { + uint32_t buildNumber = pMetaData->usBuildNumber; + if (!md_set_column_value_as_constant(c, mdtAssemblyRef_BuildNumber, buildNumber)) + return E_FAIL; + } + + if (pMetaData->usRevisionNumber != std::numeric_limits::max()) + { + uint32_t revisionNumber = pMetaData->usRevisionNumber; + 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; + } + + // TODO: Update ENC Log + + return S_OK; +} + +HRESULT MetadataEmit::SetFileProps( + mdFile file, + void const *pbHashValue, + ULONG cbHashValue, + DWORD dwFileFlags) +{ + mdcursor_t c; + if (!md_token_to_cursor(MetaData(), file, &c)) + return E_INVALIDARG; + + 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; + } + + if (dwFileFlags != std::numeric_limits::max()) + { + uint32_t fileFlags = dwFileFlags; + if (!md_set_column_value_as_constant(c, mdtFile_Flags, fileFlags)) + return E_FAIL; + } + + // TODO: Update ENC Log + + return S_OK; +} + +HRESULT MetadataEmit::SetExportedTypeProps( + mdExportedType ct, + mdToken tkImplementation, + mdTypeDef tkTypeDef, + DWORD dwExportedTypeFlags) +{ + mdcursor_t c; + if (!md_token_to_cursor(MetaData(), ct, &c)) + return E_INVALIDARG; + + if (!IsNilToken(tkImplementation)) + { + if (!md_set_column_value_as_token(c, mdtExportedType_Implementation, tkImplementation)) + return E_FAIL; + } + + if (!IsNilToken(tkTypeDef)) + { + if (!md_set_column_value_as_token(c, mdtExportedType_TypeDefId, tkTypeDef)) + return E_FAIL; + } + + if (dwExportedTypeFlags != std::numeric_limits::max()) + { + uint32_t exportedTypeFlags = dwExportedTypeFlags; + if (!md_set_column_value_as_constant(c, mdtExportedType_Flags, exportedTypeFlags)) + return E_FAIL; + } + + // TODO: Update ENC Log + + return S_OK; +} + +HRESULT MetadataEmit::SetManifestResourceProps( + mdManifestResource mr, + mdToken tkImplementation, + DWORD dwOffset, + DWORD dwResourceFlags) +{ + mdcursor_t c; + if (!md_token_to_cursor(MetaData(), mr, &c)) + return E_INVALIDARG; + + if (!IsNilToken(tkImplementation)) + { + if (!md_set_column_value_as_token(c, mdtManifestResource_Implementation, tkImplementation)) + return E_FAIL; + } + + if (dwOffset != std::numeric_limits::max()) + { + uint32_t offset = dwOffset; + if (!md_set_column_value_as_constant(c, mdtManifestResource_Offset, offset)) + return E_FAIL; + } + + if (dwResourceFlags != std::numeric_limits::max()) + { + uint32_t resourceFlags = dwResourceFlags; + if (!md_set_column_value_as_constant(c, mdtManifestResource_Flags, resourceFlags)) + return E_FAIL; + } + + // TODO: Update ENC Log + + return S_OK; +} diff --git a/src/native/dnmd/src/interfaces/metadataemit.hpp b/src/native/dnmd/src/interfaces/metadataemit.hpp new file mode 100644 index 0000000000000..b8913d343286d --- /dev/null +++ b/src/native/dnmd/src/interfaces/metadataemit.hpp @@ -0,0 +1,472 @@ +#ifndef _SRC_INTERFACES_METADATAEMIT_HPP_ +#define _SRC_INTERFACES_METADATAEMIT_HPP_ + +#include "internal/dnmd_platform.hpp" +#include "tearoffbase.hpp" +#include "controllingiunknown.hpp" +#include "dnmdowner.hpp" + +#include +#include + +#include +#include + +class MetadataEmit final : public TearOffBase +{ + mdhandle_view _md_ptr; + +protected: + bool TryGetInterfaceOnThis(REFIID riid, void** ppvObject) override + { + if (riid == IID_IMetaDataEmit || riid == IID_IMetaDataEmit) + { + *ppvObject = static_cast(this); + return true; + } + else if (riid == IID_IMetaDataAssemblyEmit) + { + *ppvObject = static_cast(this); + return true; + } + return false; + } + +public: + MetadataEmit(IUnknown* controllingUnknown, mdhandle_view md_ptr) + : TearOffBase(controllingUnknown) + , _md_ptr{ std::move(md_ptr) } + { } + + virtual ~MetadataEmit() = default; + + mdhandle_t MetaData() + { + return _md_ptr.get(); + } + +public: // IMetaDataEmit + STDMETHOD(SetModuleProps)( + LPCWSTR szName) override; + + STDMETHOD(Save)( + LPCWSTR szFile, + DWORD dwSaveFlags) override; + + STDMETHOD(SaveToStream)( + IStream *pIStream, + DWORD dwSaveFlags) override; + + STDMETHOD(GetSaveSize)( + CorSaveSize fSave, + DWORD *pdwSaveSize) override; + + STDMETHOD(DefineTypeDef)( + LPCWSTR szTypeDef, + DWORD dwTypeDefFlags, + mdToken tkExtends, + mdToken rtkImplements[], + mdTypeDef *ptd) override; + + STDMETHOD(DefineNestedType)( + LPCWSTR szTypeDef, + DWORD dwTypeDefFlags, + mdToken tkExtends, + mdToken rtkImplements[], + mdTypeDef tdEncloser, + mdTypeDef *ptd) override; + + STDMETHOD(SetHandler)( + IUnknown *pUnk) override; + + STDMETHOD(DefineMethod)( + mdTypeDef td, + LPCWSTR szName, + DWORD dwMethodFlags, + PCCOR_SIGNATURE pvSigBlob, + ULONG cbSigBlob, + ULONG ulCodeRVA, + DWORD dwImplFlags, + mdMethodDef *pmd) override; + + STDMETHOD(DefineMethodImpl)( + mdTypeDef td, + mdToken tkBody, + mdToken tkDecl) override; + + STDMETHOD(DefineTypeRefByName)( + mdToken tkResolutionScope, + LPCWSTR szName, + mdTypeRef *ptr) override; + + STDMETHOD(DefineImportType)( + IMetaDataAssemblyImport *pAssemImport, + void const *pbHashValue, + ULONG cbHashValue, + IMetaDataImport *pImport, + mdTypeDef tdImport, + IMetaDataAssemblyEmit *pAssemEmit, + mdTypeRef *ptr) override; + + STDMETHOD(DefineMemberRef)( + mdToken tkImport, + LPCWSTR szName, + PCCOR_SIGNATURE pvSigBlob, + ULONG cbSigBlob, + mdMemberRef *pmr) override; + + STDMETHOD(DefineImportMember)( + IMetaDataAssemblyImport *pAssemImport, + void const *pbHashValue, + ULONG cbHashValue, + IMetaDataImport *pImport, + mdToken mbMember, + IMetaDataAssemblyEmit *pAssemEmit, + mdToken tkParent, + mdMemberRef *pmr) override; + + STDMETHOD(DefineEvent) ( + mdTypeDef td, + LPCWSTR szEvent, + DWORD dwEventFlags, + mdToken tkEventType, + mdMethodDef mdAddOn, + mdMethodDef mdRemoveOn, + mdMethodDef mdFire, + mdMethodDef rmdOtherMethods[], + mdEvent *pmdEvent) override; + + STDMETHOD(SetClassLayout) ( + mdTypeDef td, + DWORD dwPackSize, + COR_FIELD_OFFSET rFieldOffsets[], + ULONG ulClassSize) override; + + STDMETHOD(DeleteClassLayout) ( + mdTypeDef td) override; + + STDMETHOD(SetFieldMarshal) ( + mdToken tk, + PCCOR_SIGNATURE pvNativeType, + ULONG cbNativeType) override; + + STDMETHOD(DeleteFieldMarshal) ( + mdToken tk) override; + + STDMETHOD(DefinePermissionSet) ( + mdToken tk, + DWORD dwAction, + void const *pvPermission, + ULONG cbPermission, + mdPermission *ppm) override; + + STDMETHOD(SetRVA)( + mdMethodDef md, + ULONG ulRVA) override; + + STDMETHOD(GetTokenFromSig)( + PCCOR_SIGNATURE pvSig, + ULONG cbSig, + mdSignature *pmsig) override; + + STDMETHOD(DefineModuleRef)( + LPCWSTR szName, + mdModuleRef *pmur) override; + + + STDMETHOD(SetParent)( + mdMemberRef mr, + mdToken tk) override; + + STDMETHOD(GetTokenFromTypeSpec)( + PCCOR_SIGNATURE pvSig, + ULONG cbSig, + mdTypeSpec *ptypespec) override; + + STDMETHOD(SaveToMemory)( + void *pbData, + ULONG cbData) override; + + STDMETHOD(DefineUserString)( + LPCWSTR szString, + ULONG cchString, + mdString *pstk) override; + + STDMETHOD(DeleteToken)( + mdToken tkObj) override; + + STDMETHOD(SetMethodProps)( + mdMethodDef md, + DWORD dwMethodFlags, + ULONG ulCodeRVA, + DWORD dwImplFlags) override; + + STDMETHOD(SetTypeDefProps)( + mdTypeDef td, + DWORD dwTypeDefFlags, + mdToken tkExtends, + mdToken rtkImplements[]) override; + + STDMETHOD(SetEventProps)( + mdEvent ev, + DWORD dwEventFlags, + mdToken tkEventType, + mdMethodDef mdAddOn, + mdMethodDef mdRemoveOn, + mdMethodDef mdFire, + mdMethodDef rmdOtherMethods[]) override; + + STDMETHOD(SetPermissionSetProps)( + mdToken tk, + DWORD dwAction, + void const *pvPermission, + ULONG cbPermission, + mdPermission *ppm) override; + + STDMETHOD(DefinePinvokeMap)( + mdToken tk, + DWORD dwMappingFlags, + LPCWSTR szImportName, + mdModuleRef mrImportDLL) override; + + STDMETHOD(SetPinvokeMap)( + mdToken tk, + DWORD dwMappingFlags, + LPCWSTR szImportName, + mdModuleRef mrImportDLL) override; + + STDMETHOD(DeletePinvokeMap)( + mdToken tk) override; + + + STDMETHOD(DefineCustomAttribute)( + mdToken tkOwner, + mdToken tkCtor, + void const *pCustomAttribute, + ULONG cbCustomAttribute, + mdCustomAttribute *pcv) override; + + STDMETHOD(SetCustomAttributeValue)( + mdCustomAttribute pcv, + void const *pCustomAttribute, + ULONG cbCustomAttribute) override; + + STDMETHOD(DefineField)( + mdTypeDef td, + LPCWSTR szName, + DWORD dwFieldFlags, + PCCOR_SIGNATURE pvSigBlob, + ULONG cbSigBlob, + DWORD dwCPlusTypeFlag, + void const *pValue, + ULONG cchValue, + mdFieldDef *pmd) override; + + STDMETHOD(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) override; + + STDMETHOD(DefineParam)( + mdMethodDef md, + ULONG ulParamSeq, + LPCWSTR szName, + DWORD dwParamFlags, + DWORD dwCPlusTypeFlag, + void const *pValue, + ULONG cchValue, + mdParamDef *ppd) override; + + STDMETHOD(SetFieldProps)( + mdFieldDef fd, + DWORD dwFieldFlags, + DWORD dwCPlusTypeFlag, + void const *pValue, + ULONG cchValue) override; + + STDMETHOD(SetPropertyProps)( + mdProperty pr, + DWORD dwPropFlags, + DWORD dwCPlusTypeFlag, + void const *pValue, + ULONG cchValue, + mdMethodDef mdSetter, + mdMethodDef mdGetter, + mdMethodDef rmdOtherMethods[]) override; + + STDMETHOD(SetParamProps)( + mdParamDef pd, + LPCWSTR szName, + DWORD dwParamFlags, + DWORD dwCPlusTypeFlag, + void const *pValue, + ULONG cchValue) override; + + + STDMETHOD(DefineSecurityAttributeSet)( + mdToken tkObj, + COR_SECATTR rSecAttrs[], + ULONG cSecAttrs, + ULONG *pulErrorAttr) override; + + STDMETHOD(ApplyEditAndContinue)( + IUnknown *pImport) override; + + STDMETHOD(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) override; + + STDMETHOD(SetMethodImplFlags)( + mdMethodDef md, + DWORD dwImplFlags) override; + + STDMETHOD(SetFieldRVA)( + mdFieldDef fd, + ULONG ulRVA) override; + + STDMETHOD(Merge)( + IMetaDataImport *pImport, + IMapToken *pHostMapToken, + IUnknown *pHandler) override; + + STDMETHOD(MergeEnd)() override; + +public: // IMetaDataEmit2 + STDMETHOD(DefineMethodSpec)( + mdToken tkParent, + PCCOR_SIGNATURE pvSigBlob, + ULONG cbSigBlob, + mdMethodSpec *pmi) override; + + STDMETHOD(GetDeltaSaveSize)( + CorSaveSize fSave, + DWORD *pdwSaveSize) override; + + STDMETHOD(SaveDelta)( + LPCWSTR szFile, + DWORD dwSaveFlags) override; + + STDMETHOD(SaveDeltaToStream)( + IStream *pIStream, + DWORD dwSaveFlags) override; + + STDMETHOD(SaveDeltaToMemory)( + void *pbData, + ULONG cbData) override; + + STDMETHOD(DefineGenericParam)( + mdToken tk, + ULONG ulParamSeq, + DWORD dwParamFlags, + LPCWSTR szname, + DWORD reserved, + mdToken rtkConstraints[], + mdGenericParam *pgp) override; + + STDMETHOD(SetGenericParamProps)( + mdGenericParam gp, + DWORD dwParamFlags, + LPCWSTR szName, + DWORD reserved, + mdToken rtkConstraints[]) override; + + STDMETHOD(ResetENCLog)() override; + +public: // IMetaDataAssemblyEmit + STDMETHOD(DefineAssembly)( + void const *pbPublicKey, + ULONG cbPublicKey, + ULONG ulHashAlgId, + LPCWSTR szName, + ASSEMBLYMETADATA const *pMetaData, + DWORD dwAssemblyFlags, + mdAssembly *pma) override; + + STDMETHOD(DefineAssemblyRef)( + void const *pbPublicKeyOrToken, + ULONG cbPublicKeyOrToken, + LPCWSTR szName, + ASSEMBLYMETADATA const *pMetaData, + void const *pbHashValue, + ULONG cbHashValue, + DWORD dwAssemblyRefFlags, + mdAssemblyRef *pmdar) override; + + STDMETHOD(DefineFile)( + LPCWSTR szName, + void const *pbHashValue, + ULONG cbHashValue, + DWORD dwFileFlags, + mdFile *pmdf) override; + + STDMETHOD(DefineExportedType)( + LPCWSTR szName, + mdToken tkImplementation, + mdTypeDef tkTypeDef, + DWORD dwExportedTypeFlags, + mdExportedType *pmdct) override; + + STDMETHOD(DefineManifestResource)( + LPCWSTR szName, + mdToken tkImplementation, + DWORD dwOffset, + DWORD dwResourceFlags, + mdManifestResource *pmdmr) override; + + STDMETHOD(SetAssemblyProps)( + mdAssembly pma, + void const *pbPublicKey, + ULONG cbPublicKey, + ULONG ulHashAlgId, + LPCWSTR szName, + ASSEMBLYMETADATA const *pMetaData, + DWORD dwAssemblyFlags) override; + + STDMETHOD(SetAssemblyRefProps)( + mdAssemblyRef ar, + void const *pbPublicKeyOrToken, + ULONG cbPublicKeyOrToken, + LPCWSTR szName, + ASSEMBLYMETADATA const *pMetaData, + void const *pbHashValue, + ULONG cbHashValue, + DWORD dwAssemblyRefFlags) override; + + STDMETHOD(SetFileProps)( + mdFile file, + void const *pbHashValue, + ULONG cbHashValue, + DWORD dwFileFlags) override; + + STDMETHOD(SetExportedTypeProps)( + mdExportedType ct, + mdToken tkImplementation, + mdTypeDef tkTypeDef, + DWORD dwExportedTypeFlags) override; + + STDMETHOD(SetManifestResourceProps)( + mdManifestResource mr, + mdToken tkImplementation, + DWORD dwOffset, + DWORD dwResourceFlags) override; +}; + +#endif \ No newline at end of file diff --git a/src/native/dnmd/src/interfaces/metadataimport.cpp b/src/native/dnmd/src/interfaces/metadataimport.cpp new file mode 100644 index 0000000000000..f156dacc17a7e --- /dev/null +++ b/src/native/dnmd/src/interfaces/metadataimport.cpp @@ -0,0 +1,3949 @@ +#include + +#include "pal.hpp" +#include "metadataimportro.hpp" +#include "hcorenum.hpp" +#include "signatures.hpp" +#include + +#define MD_MODULE_TOKEN TokenFromRid(1, mdtModule) +#define MD_GLOBAL_PARENT_TOKEN TokenFromRid(1, mdtTypeDef) + +#define ToHCORENUMImpl(hcorenum) (reinterpret_cast(hcorenum)) + +#define RETURN_IF_FAILED(exp) \ +{ \ + hr = (exp); \ + if (FAILED(hr)) \ + { \ + return hr; \ + } \ +} + +mdhandle_t MetadataImportRO::MetaData() +{ + return _md_ptr.get(); +} + +void STDMETHODCALLTYPE MetadataImportRO::CloseEnum(HCORENUM hEnum) +{ + HCORENUMImpl* impl = ToHCORENUMImpl(hEnum); + if (impl != nullptr) + HCORENUMImpl::Destroy(impl); +} + +HRESULT STDMETHODCALLTYPE MetadataImportRO::CountEnum(HCORENUM hEnum, ULONG* pulCount) +{ + if (pulCount == nullptr) + return E_INVALIDARG; + + HCORENUMImpl* enumImpl = ToHCORENUMImpl(hEnum); + *pulCount = enumImpl == nullptr + ? 0 + : enumImpl->Count(); + return S_OK; +} + +HRESULT STDMETHODCALLTYPE MetadataImportRO::ResetEnum(HCORENUM hEnum, ULONG ulPos) +{ + HCORENUMImpl* enumImpl = ToHCORENUMImpl(hEnum); + return enumImpl == nullptr + ? S_OK + : enumImpl->Reset(ulPos); +} + +namespace +{ + HRESULT CreateEnumTokens( + mdhandle_t mdhandle, + mdtable_id_t mdtid, + HCORENUMImpl** pEnumImpl) + { + HRESULT hr; + mdcursor_t cursor; + uint32_t rows; + if (!md_create_cursor(mdhandle, mdtid, &cursor, &rows)) + return CLDB_E_RECORD_NOTFOUND; + + HCORENUMImpl* enumImpl; + RETURN_IF_FAILED(HCORENUMImpl::CreateTableEnum(1, &enumImpl)); + HCORENUMImpl::InitTableEnum(*enumImpl, 0, cursor, rows); + *pEnumImpl = enumImpl; + return S_OK; + } + + struct TokenRangeFilter final + { + col_index_t FilterColumn; + LPCWSTR Value; + }; + + HRESULT CreateEnumTokenRange( + mdhandle_t mdhandle, + mdToken token, + col_index_t column, + _In_opt_ TokenRangeFilter const* filter, + HCORENUMImpl** pEnumImpl) + { + HRESULT hr; + mdcursor_t cursor; + if (!md_token_to_cursor(mdhandle, token, &cursor)) + return CLDB_E_INDEX_NOTFOUND; + + mdcursor_t begin; + uint32_t count; + if (!md_get_column_value_as_range(cursor, column, &begin, &count)) + return CLDB_E_FILE_CORRUPT; + + HCORENUMImpl* enumImpl; + if (filter == nullptr || filter->Value == nullptr) + { + RETURN_IF_FAILED(HCORENUMImpl::CreateTableEnum(1, &enumImpl)); + HCORENUMImpl::InitTableEnum(*enumImpl, 0, begin, count); + } + else + { + assert(filter != nullptr && filter->Value != nullptr); + pal::StringConvert cvt{ filter->Value }; + if (!cvt.Success()) + return E_INVALIDARG; + + char const* toMatch; + mdToken matchedTk; + RETURN_IF_FAILED(HCORENUMImpl::CreateDynamicEnum(&enumImpl)); + + HCORENUMImpl_ptr cleanup{ enumImpl }; + + mdcursor_t curr = begin; + for (uint32_t i = 0; i < count; ++i) + { + mdcursor_t target; + if (!md_resolve_indirect_cursor(curr, &target)) + return CLDB_E_FILE_CORRUPT; + if (!md_get_column_value_as_utf8(target, filter->FilterColumn, &toMatch)) + return CLDB_E_FILE_CORRUPT; + + if (0 == ::strcmp(toMatch, cvt)) + { + (void)md_cursor_to_token(target, &matchedTk); + RETURN_IF_FAILED(HCORENUMImpl::AddToDynamicEnum(*enumImpl, matchedTk)); + } + (void)md_cursor_next(&curr); + } + + enumImpl = cleanup.release(); + } + + *pEnumImpl = enumImpl; + return S_OK; + } + + HRESULT CreateEnumTokenRangeForSortedTableKey( + mdhandle_t mdhandle, + mdtable_id_t table, + col_index_t keyColumn, + mdToken token, + HCORENUMImpl** pEnumImpl) + { + HRESULT hr; + mdcursor_t cursor; + uint32_t tableCount; + if (!md_create_cursor(mdhandle, table, &cursor, &tableCount)) + return CLDB_E_INDEX_NOTFOUND; + + mdcursor_t begin; + uint32_t count; + md_range_result_t result = md_find_range_from_cursor(cursor, keyColumn, token, &begin, &count); + + if (result == MD_RANGE_NOT_FOUND) + { + return HCORENUMImpl::CreateDynamicEnum(pEnumImpl); + } + else if (result == MD_RANGE_FOUND) + { + HCORENUMImpl* enumImpl; + RETURN_IF_FAILED(HCORENUMImpl::CreateTableEnum(1, &enumImpl)); + HCORENUMImpl::InitTableEnum(*enumImpl, 0, begin, count); + *pEnumImpl = enumImpl; + return S_OK; + } + else + { + // Unsorted so we need to search across the entire table + HCORENUMImpl* enumImpl; + RETURN_IF_FAILED(HCORENUMImpl::CreateDynamicEnum(&enumImpl)); + HCORENUMImpl_ptr cleanup{ enumImpl }; + mdcursor_t curr = cursor; + uint32_t currCount = tableCount; + + // Read in for matching in bulk + mdToken matchedGroup[64]; + uint32_t i = 0; + while (i < currCount) + { + int32_t read = md_get_many_rows_column_value_as_token(curr, keyColumn, ARRAY_SIZE(matchedGroup), matchedGroup); + if (read == 0) + break; + + assert(read > 0); + for (int32_t j = 0; j < read; ++j) + { + if (matchedGroup[j] == token) + { + mdToken matchedTk; + if (!md_cursor_to_token(curr, &matchedTk)) + return CLDB_E_FILE_CORRUPT; + RETURN_IF_FAILED(HCORENUMImpl::AddToDynamicEnum(*enumImpl, matchedTk)); + } + (void)md_cursor_next(&curr); + } + i += read; + } + + *pEnumImpl = cleanup.release(); + return S_OK; + } + } + + HRESULT ConvertAndReturnStringOutput( + _In_z_ char const* str, + _Out_writes_to_opt_(cchBuffer, *pchBuffer) + WCHAR* szBuffer, + ULONG cchBuffer, + ULONG* pchBuffer) + { + // Handle empty string. + if (str[0] == '\0') + { + if (szBuffer != nullptr) + ::memset(&szBuffer[0], 0, sizeof(*szBuffer)); + if (pchBuffer != nullptr) + *pchBuffer = 0; + return S_OK; + } + + HRESULT hr = pal::ConvertUtf8ToUtf16(str, szBuffer, cchBuffer, (uint32_t*)pchBuffer); + if (FAILED(hr)) + { + if (hr == E_NOT_SUFFICIENT_BUFFER + && szBuffer != nullptr + && cchBuffer > 0) + { + ::memset(&szBuffer[cchBuffer - 1], 0, sizeof(*szBuffer)); + return CLDB_S_TRUNCATION; + } + return E_INVALIDARG; + } + return S_OK; + } + + HRESULT ConstructTypeName( + char const* nspace, + char const* name, + malloc_ptr& mem) + { + char* buffer; + size_t nspaceLen = nspace == nullptr ? 0 : ::strlen(nspace); + size_t nameLen = name == nullptr ? 0 : ::strlen(name); + size_t bufferLength = nspaceLen + nameLen + 1 + 1; // +1 for type delim and +1 for null. + + mem.reset((char*)::malloc(bufferLength * sizeof(*buffer))); + if (mem == nullptr) + return E_OUTOFMEMORY; + + buffer = mem.get(); + buffer[0] = '\0'; + + if (nspaceLen > 0) + { + ::strcat_s(buffer, bufferLength, nspace); + ::strcat_s(buffer, bufferLength, "."); + } + + if (nameLen > 0) + ::strcat_s(buffer, bufferLength, name); + + return S_OK; + } + + void SplitTypeName( + char* typeName, + char const** nspace, + char const** name) + { + // Search for the last delimiter. + char* pos = ::strrchr(typeName, '.'); + if (pos == nullptr) + { + // No namespace is indicated by an empty string. + *nspace = ""; + *name = typeName; + } + else + { + *pos = '\0'; + *nspace = typeName; + *name = pos + 1; + } + } + + // Starting from the supplied cursor, find and then enumerate + // the range of rows in "lookupRange" with the given "lookupTk" + // value. When a value is found, the supplied type instance will + // be used to call back to the caller. + // + // Example of type instance: + // struct Operation + // { + // bool operator()(mdcursor_t cursor) + // { + // return stopEnumeration; // Return true to stop, false to continue. + // } + // }; + template + void EnumTableRange( + mdcursor_t begin, + uint32_t count, + col_index_t lookupRange, + mdToken lookupTk, + T& op) + { + mdcursor_t curr; + uint32_t currCount; + md_range_result_t result = md_find_range_from_cursor(begin, lookupRange, lookupTk, &curr, &currCount); + if (result == MD_RANGE_FOUND) + { + // Table is sorted and subset found + for (uint32_t i = 0; i < currCount; ++i) + { + if (op(curr)) + return; + (void)md_cursor_next(&curr); + } + } + else if (result == MD_RANGE_NOT_SUPPORTED) + { + // Cannot get a range on this table so we need to search across the entire table + curr = begin; + currCount = count; + + // Read in for matching in bulk + mdToken matchedGroup[64]; + uint32_t i = 0; + while (i < currCount) + { + int32_t read = md_get_many_rows_column_value_as_token(curr, lookupRange, ARRAY_SIZE(matchedGroup), matchedGroup); + if (read == 0) + break; + + assert(read > 0); + for (int32_t j = 0; j < read; ++j) + { + if (matchedGroup[j] == lookupTk) + { + if (op(curr)) + return; + } + (void)md_cursor_next(&curr); + } + i += read; + } + } + } +} + +HRESULT STDMETHODCALLTYPE MetadataImportRO::EnumTypeDefs( + HCORENUM* phEnum, + mdTypeDef rTypeDefs[], + ULONG cMax, + ULONG* pcTypeDefs) +{ + HRESULT hr; + HCORENUMImpl* enumImpl = ToHCORENUMImpl(*phEnum); + if (enumImpl == nullptr) + { + mdcursor_t cursor; + uint32_t rows; + if (!md_create_cursor(_md_ptr.get(), mdtid_TypeDef, &cursor, &rows)) + return CLDB_E_RECORD_NOTFOUND; + + // From ECMA-335, section II.22.37: + // "The first row of the TypeDef table represents the pseudo class that acts as parent for functions + // and variables defined at module scope." + // Based on the above we always skip the first row. + rows--; + (void)md_cursor_next(&cursor); + + RETURN_IF_FAILED(HCORENUMImpl::CreateTableEnum(1, &enumImpl)); + HCORENUMImpl::InitTableEnum(*enumImpl, 0, cursor, rows); + *phEnum = enumImpl; + } + return enumImpl->ReadTokens(rTypeDefs, cMax, pcTypeDefs); +} + +HRESULT STDMETHODCALLTYPE MetadataImportRO::EnumInterfaceImpls( + HCORENUM* phEnum, + mdTypeDef td, + mdInterfaceImpl rImpls[], + ULONG cMax, + ULONG* pcImpls) +{ + HRESULT hr; + HCORENUMImpl* enumImpl = ToHCORENUMImpl(*phEnum); + if (enumImpl == nullptr) + { + mdcursor_t cursor; + uint32_t rows; + if (!md_create_cursor(_md_ptr.get(), mdtid_InterfaceImpl, &cursor, &rows)) + return CLDB_E_RECORD_NOTFOUND; + + RETURN_IF_FAILED(CreateEnumTokenRangeForSortedTableKey(_md_ptr.get(), mdtid_InterfaceImpl, mdtInterfaceImpl_Class, td, &enumImpl)); + *phEnum = enumImpl; + } + return enumImpl->ReadTokens(rImpls, cMax, pcImpls); +} + +HRESULT STDMETHODCALLTYPE MetadataImportRO::EnumTypeRefs( + HCORENUM* phEnum, + mdTypeRef rTypeRefs[], + ULONG cMax, + ULONG* pcTypeRefs) +{ + HRESULT hr; + HCORENUMImpl* enumImpl = ToHCORENUMImpl(*phEnum); + if (enumImpl == nullptr) + { + RETURN_IF_FAILED(CreateEnumTokens(_md_ptr.get(), mdtid_TypeRef, &enumImpl)); + *phEnum = enumImpl; + } + return enumImpl->ReadTokens(rTypeRefs, cMax, pcTypeRefs); +} + +namespace +{ + HRESULT FindTypeDefByName( + MetadataImportRO* importer, + char const* nspace, + char const* name, + mdToken tkEnclosingClass, + mdTypeDef* ptd) + { + assert(importer != nullptr && nspace != nullptr && name != nullptr && ptd != nullptr); + *ptd = mdTypeDefNil; + + HRESULT hr; + + // If the caller supplied a TypeRef scope, we need to walk until we find + // a TypeDef scope we can use to look up the inner definition. + if (TypeFromToken(tkEnclosingClass) == mdtTypeRef) + { + mdcursor_t typeRefCursor; + if (!md_token_to_cursor(importer->MetaData(), tkEnclosingClass, &typeRefCursor)) + return CLDB_E_RECORD_NOTFOUND; + + uint32_t typeRefScope; + char const* typeRefNspace; + char const* typeRefName; + if (!md_get_column_value_as_token(typeRefCursor, mdtTypeRef_ResolutionScope, &typeRefScope) + || !md_get_column_value_as_utf8(typeRefCursor, mdtTypeRef_TypeNamespace, &typeRefNspace) + || !md_get_column_value_as_utf8(typeRefCursor, mdtTypeRef_TypeName, &typeRefName)) + { + return CLDB_E_FILE_CORRUPT; + } + + if (tkEnclosingClass == typeRefScope + && 0 == ::strcmp(name, typeRefName) + && 0 == ::strcmp(nspace, typeRefNspace)) + { + // This defensive workaround works around a feature of DotFuscator that adds a bad TypeRef + // which causes tools like ILDASM to crash. The TypeRef's parent is set to itself + // which causes this function to recurse infinitely. + return CLDB_E_FILE_CORRUPT; + } + + // Update tkEnclosingClass to TypeDef + RETURN_IF_FAILED(FindTypeDefByName( + importer, + typeRefNspace, + typeRefName, + (TypeFromToken(typeRefScope) == mdtTypeRef) ? typeRefScope : mdTokenNil, + &tkEnclosingClass)); + assert(TypeFromToken(tkEnclosingClass) == mdtTypeDef); + } + + mdcursor_t cursor; + uint32_t count; + if (!md_create_cursor(importer->MetaData(), mdtid_TypeDef, &cursor, &count)) + return CLDB_E_RECORD_NOTFOUND; + + uint32_t flags; + char const* str; + mdToken tk; + mdToken tmpTk; + for (uint32_t i = 0; i < count; (void)md_cursor_next(&cursor), ++i) + { + if (!md_get_column_value_as_constant(cursor, mdtTypeDef_Flags, &flags)) + return CLDB_E_FILE_CORRUPT; + + // Use XOR to handle the following in a single expression: + // - The class is Nested and EnclosingClass passed is nil + // or + // - The class is not Nested and EnclosingClass passed in is not nil + if (!(IsTdNested(flags) ^ IsNilToken(tkEnclosingClass))) + continue; + + // Filter to enclosing class + if (!IsNilToken(tkEnclosingClass)) + { + assert(TypeFromToken(tkEnclosingClass) == mdtTypeDef); + (void)md_cursor_to_token(cursor, &tk); + hr = importer->GetNestedClassProps(tk, &tmpTk); + + // Skip this type if it doesn't have an enclosing class + // or its enclosing doesn't match the filter. + if (FAILED(hr) || tmpTk != tkEnclosingClass) + continue; + } + + if (!md_get_column_value_as_utf8(cursor, mdtTypeDef_TypeNamespace, &str)) + return CLDB_E_FILE_CORRUPT; + + if (0 != ::strcmp(nspace, str)) + continue; + + if (!md_get_column_value_as_utf8(cursor, mdtTypeDef_TypeName, &str)) + return CLDB_E_FILE_CORRUPT; + + if (0 == ::strcmp(name, str)) + { + (void)md_cursor_to_token(cursor, ptd); + return S_OK; + } + } + return CLDB_E_RECORD_NOTFOUND; + } +} + +HRESULT STDMETHODCALLTYPE MetadataImportRO::FindTypeDefByName( + LPCWSTR szTypeDef, + mdToken tkEnclosingClass, + mdTypeDef* ptd) +{ + if (szTypeDef == nullptr || ptd == nullptr) + return E_INVALIDARG; + + // Check the enclosing token is either valid or nil. + if (TypeFromToken(tkEnclosingClass) != mdtTypeDef + && TypeFromToken(tkEnclosingClass) != mdtTypeRef + && TypeFromToken(tkEnclosingClass) != mdtModule + && !IsNilToken(tkEnclosingClass)) + { + return E_INVALIDARG; + } + else if (tkEnclosingClass == MD_MODULE_TOKEN) + { + // Module scope is the same as no scope + tkEnclosingClass = mdTokenNil; + } + + pal::StringConvert cvt{ szTypeDef }; + if (!cvt.Success()) + return E_INVALIDARG; + + char const* nspace; + char const* name; + SplitTypeName(cvt, &nspace, &name); + return ::FindTypeDefByName(this, nspace, name, tkEnclosingClass, ptd); +} + +HRESULT STDMETHODCALLTYPE MetadataImportRO::GetScopeProps( + _Out_writes_to_opt_(cchName, *pchName) + LPWSTR szName, + ULONG cchName, + ULONG* pchName, + GUID* pmvid) +{ + mdcursor_t cursor; + if (!md_token_to_cursor(_md_ptr.get(), MD_MODULE_TOKEN, &cursor)) + return CLDB_E_INDEX_NOTFOUND; + + GUID mvid; + if (!md_get_column_value_as_guid(cursor, mdtModule_Mvid, reinterpret_cast(&mvid))) + return CLDB_E_FILE_CORRUPT; + *pmvid = mvid; + + char const* name; + if (!md_get_column_value_as_utf8(cursor, mdtModule_Name, &name)) + return CLDB_E_FILE_CORRUPT; + return ConvertAndReturnStringOutput(name, szName, cchName, pchName); +} + +HRESULT STDMETHODCALLTYPE MetadataImportRO::GetModuleFromScope( + mdModule* pmd) +{ + if (pmd == nullptr) + return E_POINTER; + + *pmd = TokenFromRid(1, mdtModule); + return S_OK; +} + +HRESULT STDMETHODCALLTYPE MetadataImportRO::GetTypeDefProps( + mdTypeDef td, + _Out_writes_to_opt_(cchTypeDef, *pchTypeDef) + LPWSTR szTypeDef, + ULONG cchTypeDef, + ULONG* pchTypeDef, + DWORD* pdwTypeDefFlags, + mdToken* ptkExtends) +{ + if (TypeFromToken(td) != mdtTypeDef) + return E_INVALIDARG; + + mdcursor_t cursor; + if (!md_token_to_cursor(_md_ptr.get(), td, &cursor)) + return CLDB_E_RECORD_NOTFOUND; + + uint32_t flags; + if (!md_get_column_value_as_constant(cursor, mdtTypeDef_Flags, &flags)) + return CLDB_E_FILE_CORRUPT; + *pdwTypeDefFlags = flags; + + mdToken extends; + if (!md_get_column_value_as_token(cursor, mdtTypeDef_Extends, &extends)) + return CLDB_E_FILE_CORRUPT; + *ptkExtends = extends == mdTypeDefNil + ? mdTypeRefNil + : extends; + + char const* name; + char const* nspace; + if (!md_get_column_value_as_utf8(cursor, mdtTypeDef_TypeName, &name) + || !md_get_column_value_as_utf8(cursor, mdtTypeDef_TypeNamespace, &nspace)) + { + return CLDB_E_FILE_CORRUPT; + } + + HRESULT hr; + malloc_ptr mem; + RETURN_IF_FAILED(ConstructTypeName(nspace, name, mem)); + return ConvertAndReturnStringOutput(mem.get(), szTypeDef, cchTypeDef, pchTypeDef); +} + +HRESULT STDMETHODCALLTYPE MetadataImportRO::GetInterfaceImplProps( + mdInterfaceImpl iiImpl, + mdTypeDef* pClass, + mdToken* ptkIface) +{ + if (TypeFromToken(iiImpl) != mdtInterfaceImpl) + return E_INVALIDARG; + + mdcursor_t cursor; + if (!md_token_to_cursor(_md_ptr.get(), iiImpl, &cursor)) + return CLDB_E_INDEX_NOTFOUND; + + mdTypeDef type; + if (!md_get_column_value_as_token(cursor, mdtInterfaceImpl_Class, &type)) + return CLDB_E_FILE_CORRUPT; + *pClass = type; + + mdToken iface; + if (!md_get_column_value_as_token(cursor, mdtInterfaceImpl_Interface, &iface)) + return CLDB_E_FILE_CORRUPT; + *ptkIface = iface; + + return S_OK; +} + +HRESULT STDMETHODCALLTYPE MetadataImportRO::GetTypeRefProps( + mdTypeRef tr, + mdToken* ptkResolutionScope, + _Out_writes_to_opt_(cchName, *pchName) + LPWSTR szName, + ULONG cchName, + ULONG* pchName) +{ + if (TypeFromToken(tr) != mdtTypeRef) + return E_INVALIDARG; + + mdcursor_t cursor; + if (!md_token_to_cursor(_md_ptr.get(), tr, &cursor)) + return CLDB_E_RECORD_NOTFOUND; + + mdToken resScope; + if (!md_get_column_value_as_token(cursor, mdtTypeRef_ResolutionScope, &resScope)) + return CLDB_E_FILE_CORRUPT; + *ptkResolutionScope = resScope; + + char const* name; + char const* nspace; + if (!md_get_column_value_as_utf8(cursor, mdtTypeRef_TypeName, &name) + || !md_get_column_value_as_utf8(cursor, mdtTypeRef_TypeNamespace, &nspace)) + { + return CLDB_E_FILE_CORRUPT; + } + + HRESULT hr; + malloc_ptr mem; + RETURN_IF_FAILED(ConstructTypeName(nspace, name, mem)); + return ConvertAndReturnStringOutput(mem.get(), szName, cchName, pchName); +} + +HRESULT STDMETHODCALLTYPE MetadataImportRO::ResolveTypeRef(mdTypeRef tr, REFIID riid, IUnknown** ppIScope, mdTypeDef* ptd) +{ + UNREFERENCED_PARAMETER(tr); + UNREFERENCED_PARAMETER(riid); + UNREFERENCED_PARAMETER(ppIScope); + UNREFERENCED_PARAMETER(ptd); + + // Requires VM knowledge + return E_NOTIMPL; +} + +HRESULT STDMETHODCALLTYPE MetadataImportRO::EnumMembers( + HCORENUM* phEnum, + mdTypeDef cl, + mdToken rMembers[], + ULONG cMax, + ULONG* pcTokens) +{ + HRESULT hr; + HCORENUMImpl* enumImpl = ToHCORENUMImpl(*phEnum); + if (enumImpl == nullptr) + { + if (TypeFromToken(cl) != mdtTypeDef) + return E_INVALIDARG; + + mdcursor_t cursor; + if (!md_token_to_cursor(_md_ptr.get(), cl, &cursor)) + return CLDB_E_INDEX_NOTFOUND; + + mdcursor_t methodList; + uint32_t methodListCount; + mdcursor_t fieldList; + uint32_t fieldListCount; + if (!md_get_column_value_as_range(cursor, mdtTypeDef_FieldList, &fieldList, &fieldListCount) + || !md_get_column_value_as_range(cursor, mdtTypeDef_MethodList, &methodList, &methodListCount)) + { + return CLDB_E_FILE_CORRUPT; + } + + RETURN_IF_FAILED(HCORENUMImpl::CreateTableEnum(2, &enumImpl)); + HCORENUMImpl::InitTableEnum(*enumImpl, 0, methodList, methodListCount); + HCORENUMImpl::InitTableEnum(*enumImpl, 1, fieldList, fieldListCount); + *phEnum = enumImpl; + } + return enumImpl->ReadTokens(rMembers, cMax, pcTokens); +} + +HRESULT STDMETHODCALLTYPE MetadataImportRO::EnumMembersWithName( + HCORENUM* phEnum, + mdTypeDef cl, + LPCWSTR szName, + mdToken rMembers[], + ULONG cMax, + ULONG* pcTokens) +{ + HRESULT hr; + HCORENUMImpl* enumImpl = ToHCORENUMImpl(*phEnum); + if (enumImpl == nullptr) + { + // If name is null, defer to the EnumMembers() API. + if (szName == nullptr) + return EnumMembers(phEnum, cl, rMembers, cMax, pcTokens); + + if (TypeFromToken(cl) != mdtTypeDef) + return E_INVALIDARG; + + mdcursor_t cursor; + if (!md_token_to_cursor(_md_ptr.get(), cl, &cursor)) + return CLDB_E_INDEX_NOTFOUND; + + mdcursor_t methodList; + uint32_t methodListCount; + mdcursor_t fieldList; + uint32_t fieldListCount; + if (!md_get_column_value_as_range(cursor, mdtTypeDef_FieldList, &fieldList, &fieldListCount) + || !md_get_column_value_as_range(cursor, mdtTypeDef_MethodList, &methodList, &methodListCount)) + { + return CLDB_E_FILE_CORRUPT; + } + + assert(szName != nullptr); + pal::StringConvert cvt{ szName }; + if (!cvt.Success()) + return E_INVALIDARG; + + char const* toMatch; + mdToken matchedTk; + RETURN_IF_FAILED(HCORENUMImpl::CreateDynamicEnum(&enumImpl)); + + HCORENUMImpl_ptr cleanup{ enumImpl }; + + // Iterate the Type's methods + for (uint32_t i = 0; i < methodListCount; ++i) + { + mdcursor_t methodCursor; + if (!md_resolve_indirect_cursor(methodList, &methodCursor)) + return CLDB_E_FILE_CORRUPT; + if (!md_get_column_value_as_utf8(methodCursor, mdtMethodDef_Name, &toMatch)) + return CLDB_E_FILE_CORRUPT; + + if (0 == ::strcmp(toMatch, cvt)) + { + (void)md_cursor_to_token(methodCursor, &matchedTk); + RETURN_IF_FAILED(HCORENUMImpl::AddToDynamicEnum(*enumImpl, matchedTk)); + } + (void)md_cursor_next(&methodList); + } + + // Iterate the Type's fields + for (uint32_t i = 0; i < fieldListCount; ++i) + { + mdcursor_t fieldCursor; + if (!md_resolve_indirect_cursor(fieldList, &fieldCursor)) + return CLDB_E_FILE_CORRUPT; + if (!md_get_column_value_as_utf8(fieldCursor, mdtField_Name, &toMatch)) + return CLDB_E_FILE_CORRUPT; + + if (0 == ::strcmp(toMatch, cvt)) + { + (void)md_cursor_to_token(fieldCursor, &matchedTk); + RETURN_IF_FAILED(HCORENUMImpl::AddToDynamicEnum(*enumImpl, matchedTk)); + } + (void)md_cursor_next(&fieldList); + } + + *phEnum = cleanup.release(); + } + return enumImpl->ReadTokens(rMembers, cMax, pcTokens); +} + +HRESULT STDMETHODCALLTYPE MetadataImportRO::EnumMethods( + HCORENUM* phEnum, + mdTypeDef cl, + mdMethodDef rMethods[], + ULONG cMax, + ULONG* pcTokens) +{ + return EnumMethodsWithName(phEnum, cl, nullptr, rMethods, cMax, pcTokens); +} + +HRESULT STDMETHODCALLTYPE MetadataImportRO::EnumMethodsWithName( + HCORENUM* phEnum, + mdTypeDef cl, + LPCWSTR szName, + mdMethodDef rMethods[], + ULONG cMax, + ULONG* pcTokens) +{ + HRESULT hr; + HCORENUMImpl* enumImpl = ToHCORENUMImpl(*phEnum); + if (enumImpl == nullptr) + { + if (TypeFromToken(cl) != mdtTypeDef) + return E_INVALIDARG; + + TokenRangeFilter filter{ mdtMethodDef_Name, szName }; + RETURN_IF_FAILED(CreateEnumTokenRange(_md_ptr.get(), cl, mdtTypeDef_MethodList, &filter, &enumImpl)); + *phEnum = enumImpl; + } + return enumImpl->ReadTokens(rMethods, cMax, pcTokens); +} + +HRESULT STDMETHODCALLTYPE MetadataImportRO::EnumFields( + HCORENUM* phEnum, + mdTypeDef cl, + mdFieldDef rFields[], + ULONG cMax, + ULONG* pcTokens) +{ + return EnumFieldsWithName(phEnum, cl, nullptr, rFields, cMax, pcTokens); +} + +HRESULT STDMETHODCALLTYPE MetadataImportRO::EnumFieldsWithName( + HCORENUM* phEnum, + mdTypeDef cl, + LPCWSTR szName, + mdFieldDef rFields[], + ULONG cMax, + ULONG* pcTokens) +{ + HRESULT hr; + HCORENUMImpl* enumImpl = ToHCORENUMImpl(*phEnum); + if (enumImpl == nullptr) + { + if (TypeFromToken(cl) != mdtTypeDef) + return E_INVALIDARG; + + TokenRangeFilter filter{ mdtField_Name, szName }; + RETURN_IF_FAILED(CreateEnumTokenRange(_md_ptr.get(), cl, mdtTypeDef_FieldList, &filter, &enumImpl)); + *phEnum = enumImpl; + } + return enumImpl->ReadTokens(rFields, cMax, pcTokens); +} + +HRESULT STDMETHODCALLTYPE MetadataImportRO::EnumParams( + HCORENUM* phEnum, + mdMethodDef mb, + mdParamDef rParams[], + ULONG cMax, + ULONG* pcTokens) +{ + HRESULT hr; + HCORENUMImpl* enumImpl = ToHCORENUMImpl(*phEnum); + if (enumImpl == nullptr) + { + if (TypeFromToken(mb) != mdtMethodDef) + return E_INVALIDARG; + + RETURN_IF_FAILED(CreateEnumTokenRange(_md_ptr.get(), mb, mdtMethodDef_ParamList, nullptr, &enumImpl)); + *phEnum = enumImpl; + } + return enumImpl->ReadTokens(rParams, cMax, pcTokens); +} + +HRESULT STDMETHODCALLTYPE MetadataImportRO::EnumMemberRefs( + HCORENUM* phEnum, + mdToken tkParent, + mdMemberRef rMemberRefs[], + ULONG cMax, + ULONG* pcTokens) +{ + HRESULT hr; + HCORENUMImpl* enumImpl = ToHCORENUMImpl(*phEnum); + if (enumImpl == nullptr) + { + mdcursor_t cursor; + uint32_t count; + if (!md_create_cursor(_md_ptr.get(), mdtid_MemberRef, &cursor, &count)) + return CLDB_E_RECORD_NOTFOUND; + + RETURN_IF_FAILED(HCORENUMImpl::CreateDynamicEnum(&enumImpl)); + + HCORENUMImpl_ptr cleanup{ enumImpl }; + + // Read in for matching in bulk + mdToken toMatch[64]; + mdToken matchedTk; + uint32_t i = 0; + while (i < count) + { + int32_t read = md_get_many_rows_column_value_as_token(cursor, mdtMemberRef_Class, ARRAY_SIZE(toMatch), toMatch); + if (read == 0) + break; + + assert(read > 0); + for (int32_t j = 0; j < read; ++j) + { + if (toMatch[j] == tkParent) + { + (void)md_cursor_to_token(cursor, &matchedTk); + RETURN_IF_FAILED(HCORENUMImpl::AddToDynamicEnum(*enumImpl, matchedTk)); + } + (void)md_cursor_next(&cursor); + } + i += read; + } + *phEnum = cleanup.release(); + } + return enumImpl->ReadTokens(rMemberRefs, cMax, pcTokens); +} + +HRESULT STDMETHODCALLTYPE MetadataImportRO::EnumMethodImpls( + HCORENUM* phEnum, + mdTypeDef td, + mdToken rMethodBody[], + mdToken rMethodDecl[], + ULONG cMax, + ULONG* pcTokens) +{ + HRESULT hr; + HCORENUMImpl* enumImpl = ToHCORENUMImpl(*phEnum); + if (enumImpl == nullptr) + { + if (TypeFromToken(td) != mdtTypeDef) + return E_INVALIDARG; + + mdcursor_t cursor; + uint32_t count; + if (!md_create_cursor(_md_ptr.get(), mdtid_MethodImpl, &cursor, &count)) + return CLDB_E_RECORD_NOTFOUND; + + RETURN_IF_FAILED(HCORENUMImpl::CreateDynamicEnum(&enumImpl, 2)); + HCORENUMImpl_ptr cleanup{ enumImpl }; + + struct _Finder + { + HCORENUMImpl& EnumImpl; + mdToken Body; + mdToken Decl; + HRESULT hr; + HRESULT Result; // Result of the operation + + bool operator()(mdcursor_t c) + { + if (!md_get_column_value_as_token(c, mdtMethodImpl_MethodBody, &Body) + || !md_get_column_value_as_token(c, mdtMethodImpl_MethodDeclaration, &Decl)) + { + Result = CLDB_E_FILE_CORRUPT; + return true; + } + + if (FAILED(hr = HCORENUMImpl::AddToDynamicEnum(EnumImpl, Body)) + || FAILED(hr = HCORENUMImpl::AddToDynamicEnum(EnumImpl, Decl))) + { + Result = hr; + return true; + } + + return false; + } + } finder{ *enumImpl, mdTokenNil, mdTokenNil, S_OK, S_OK }; + + EnumTableRange(cursor, count, mdtMethodImpl_Class, td, finder); + RETURN_IF_FAILED(finder.Result); + + *phEnum = cleanup.release(); + } + return enumImpl->ReadTokenPairs(rMethodBody, rMethodDecl, cMax, pcTokens); +} + +HRESULT STDMETHODCALLTYPE MetadataImportRO::EnumPermissionSets( + HCORENUM* phEnum, + mdToken tk, + DWORD dwActions, + mdPermission rPermission[], + ULONG cMax, + ULONG* pcTokens) +{ + HRESULT hr; + HCORENUMImpl* enumImpl = ToHCORENUMImpl(*phEnum); + if (enumImpl == nullptr) + { + CorTokenType type = (CorTokenType)TypeFromToken(tk); + if (type != mdtTypeDef + && type != mdtMethodDef + && type != mdtAssembly) + { + *pcTokens = 0; + return S_FALSE; + } + + mdcursor_t cursor; + uint32_t count; + if (!md_create_cursor(_md_ptr.get(), mdtid_DeclSecurity, &cursor, &count)) + return CLDB_E_RECORD_NOTFOUND; + + if (!IsNilToken(tk)) + { + if (md_find_range_from_cursor(cursor, mdtDeclSecurity_Parent, tk, &cursor, &count) == MD_RANGE_NOT_FOUND) + return CLDB_E_RECORD_NOTFOUND; + } + + if (IsNilToken(tk) && IsDclActionNil(dwActions)) + { + RETURN_IF_FAILED(HCORENUMImpl::CreateTableEnum(1, &enumImpl)); + HCORENUMImpl::InitTableEnum(*enumImpl, 0, cursor, count); + } + else + { + uint32_t action; + mdToken parent; + mdToken toAdd; + RETURN_IF_FAILED(HCORENUMImpl::CreateDynamicEnum(&enumImpl)); + + HCORENUMImpl_ptr cleanup{ enumImpl }; + for (uint32_t i = 0; i < count; ++i) + { + if ((IsDclActionNil(dwActions) + || (md_get_column_value_as_constant(cursor, mdtDeclSecurity_Action, &action) + && action == dwActions)) + && (IsNilToken(tk) + || (md_get_column_value_as_token(cursor, mdtDeclSecurity_Parent, &parent) + && parent == tk))) + { + (void)md_cursor_to_token(cursor, &toAdd); + RETURN_IF_FAILED(HCORENUMImpl::AddToDynamicEnum(*enumImpl, toAdd)); + } + + if (!md_cursor_next(&cursor)) + return CLDB_E_RECORD_NOTFOUND; + } + enumImpl = cleanup.release(); + } + *phEnum = enumImpl; + } + return enumImpl->ReadTokens(rPermission, cMax, pcTokens); +} + +HRESULT STDMETHODCALLTYPE MetadataImportRO::FindMember( + mdTypeDef td, + LPCWSTR szName, + PCCOR_SIGNATURE pvSigBlob, + ULONG cbSigBlob, + mdToken* pmb) +{ + HRESULT hr = FindMethod(td, szName, pvSigBlob, cbSigBlob, (mdMethodDef*)pmb); + if (hr == CLDB_E_RECORD_NOTFOUND) + hr = FindField(td, szName, pvSigBlob, cbSigBlob, (mdFieldDef*)pmb); + return hr; +} + +HRESULT STDMETHODCALLTYPE MetadataImportRO::FindMethod( + mdTypeDef td, + LPCWSTR szName, + PCCOR_SIGNATURE pvSigBlob, + ULONG cbSigBlob, + mdMethodDef* pmb) +{ + if (TypeFromToken(td) != mdtTypeDef && td != mdTokenNil) + return E_INVALIDARG; + + if (td == mdTypeDefNil || td == mdTokenNil) + td = MD_GLOBAL_PARENT_TOKEN; + + mdcursor_t typedefCursor; + if (!md_token_to_cursor(_md_ptr.get(), td, &typedefCursor)) + return CLDB_E_INDEX_NOTFOUND; + + mdcursor_t methodCursor; + uint32_t count; + if (!md_get_column_value_as_range(typedefCursor, mdtTypeDef_MethodList, &methodCursor, &count)) + return CLDB_E_FILE_CORRUPT; + + inline_span methodDefSig; + try + { + GetMethodDefSigFromMethodRefSig({ (uint8_t*)pvSigBlob, (size_t)cbSigBlob }, methodDefSig); + } + catch (std::exception const&) + { + return E_INVALIDARG; + } + + pal::StringConvert cvt{ szName }; + if (!cvt.Success()) + return E_INVALIDARG; + + for (uint32_t i = 0; i < count; (void)md_cursor_next(&methodCursor), ++i) + { + mdcursor_t target; + if (!md_resolve_indirect_cursor(methodCursor, &target)) + return CLDB_E_FILE_CORRUPT; + uint32_t flags; + if (!md_get_column_value_as_constant(target, mdtMethodDef_Flags, &flags)) + return CLDB_E_FILE_CORRUPT; + + // Ignore PrivateScope methods. By the spec, they can only be referred to by a MethodDef token + // and cannot be discovered in any other way. + if (IsMdPrivateScope(flags)) + continue; + + char const* methodName; + if (!md_get_column_value_as_utf8(target, mdtMethodDef_Name, &methodName)) + return CLDB_E_FILE_CORRUPT; + if (::strncmp(methodName, cvt, cvt.Length()) != 0) + continue; + + if (pvSigBlob != nullptr) + { + uint8_t const* sig; + uint32_t sigLen; + if (!md_get_column_value_as_blob(target, mdtMethodDef_Signature, &sig, &sigLen)) + return CLDB_E_FILE_CORRUPT; + if (sigLen != methodDefSig.size() + || ::memcmp(methodDefSig.data(), sig, sigLen) != 0) + { + continue; + } + } + if (!md_cursor_to_token(target, pmb)) + return CLDB_E_FILE_CORRUPT; + return S_OK; + } + return CLDB_E_RECORD_NOTFOUND; +} + +HRESULT STDMETHODCALLTYPE MetadataImportRO::FindField( + mdTypeDef td, + LPCWSTR szName, + PCCOR_SIGNATURE pvSigBlob, + ULONG cbSigBlob, + mdFieldDef* pmb) +{ + if (TypeFromToken(td) != mdtTypeDef && td != mdTokenNil) + return E_INVALIDARG; + + if (td == mdTypeDefNil || td == mdTokenNil) + td = MD_GLOBAL_PARENT_TOKEN; + + mdcursor_t typedefCursor; + if (!md_token_to_cursor(_md_ptr.get(), td, &typedefCursor)) + return CLDB_E_INDEX_NOTFOUND; + + mdcursor_t fieldCursor; + uint32_t count; + if (!md_get_column_value_as_range(typedefCursor, mdtTypeDef_FieldList, &fieldCursor, &count)) + return CLDB_E_FILE_CORRUPT; + + pal::StringConvert cvt{ szName }; + if (!cvt.Success()) + return E_INVALIDARG; + + for (uint32_t i = 0; i < count; (void)md_cursor_next(&fieldCursor), ++i) + { + mdcursor_t target; + if (!md_resolve_indirect_cursor(fieldCursor, &target)) + return CLDB_E_FILE_CORRUPT; + uint32_t flags; + if (!md_get_column_value_as_constant(target, mdtField_Flags, &flags)) + return CLDB_E_FILE_CORRUPT; + + // Ignore PrivateScope fields. By the spec, they can only be referred to by a FieldDef token + // and cannot be discovered in any other way. + if (IsFdPrivateScope(flags)) + continue; + + char const* name; + if (!md_get_column_value_as_utf8(target, mdtField_Name, &name)) + return CLDB_E_FILE_CORRUPT; + if (::strncmp(name, cvt, cvt.Length()) != 0) + continue; + + if (pvSigBlob != nullptr) + { + uint8_t const* sig; + uint32_t sigLen; + if (!md_get_column_value_as_blob(target, mdtField_Signature, &sig, &sigLen)) + return CLDB_E_FILE_CORRUPT; + if (cbSigBlob != sigLen + || ::memcmp(pvSigBlob, sig, sigLen) != 0) + { + continue; + } + } + if (!md_cursor_to_token(target, pmb)) + return CLDB_E_FILE_CORRUPT; + return S_OK; + } + return CLDB_E_RECORD_NOTFOUND; +} + +HRESULT STDMETHODCALLTYPE MetadataImportRO::FindMemberRef( + mdTypeRef td, + LPCWSTR szName, + PCCOR_SIGNATURE pvSigBlob, + ULONG cbSigBlob, + mdMemberRef* pmr) +{ + if (TypeFromToken(td) != mdtTypeRef + && TypeFromToken(td) != mdtMethodDef + && TypeFromToken(td) != mdtModuleRef + && TypeFromToken(td) != mdtTypeDef + && TypeFromToken(td) != mdtTypeSpec) + { + return E_INVALIDARG; + } + + if (szName == nullptr || pmr == nullptr) + return CLDB_E_RECORD_NOTFOUND; + + if (IsNilToken(td)) + td = MD_GLOBAL_PARENT_TOKEN; + + mdcursor_t cursor; + uint32_t count; + if (!md_create_cursor(_md_ptr.get(), mdtid_MemberRef, &cursor, &count)) + return CLDB_E_FILE_CORRUPT; + + for (uint32_t i = 0; i < count; (void)md_cursor_next(&cursor), ++i) + { + mdToken refParent; + if (!md_get_column_value_as_token(cursor, mdtMemberRef_Class, &refParent)) + return CLDB_E_FILE_CORRUPT; + + if (refParent != td) + continue; + + if (szName != nullptr) + { + char const* name; + if (!md_get_column_value_as_utf8(cursor, mdtMemberRef_Name, &name)) + return CLDB_E_FILE_CORRUPT; + pal::StringConvert cvt{ szName }; + if (!cvt.Success()) + return E_INVALIDARG; + if (::strncmp(name, cvt, cvt.Length()) != 0) + continue; + } + + if (pvSigBlob != nullptr) + { + uint8_t const* sig; + uint32_t sigLen; + if (!md_get_column_value_as_blob(cursor, mdtMemberRef_Signature, &sig, &sigLen)) + return CLDB_E_FILE_CORRUPT; + if (cbSigBlob != sigLen + || ::memcmp(pvSigBlob, sig, sigLen) != 0) + { + continue; + } + } + if (!md_cursor_to_token(cursor, pmr)) + return CLDB_E_FILE_CORRUPT; + return S_OK; + } + return CLDB_E_RECORD_NOTFOUND; +} + +HRESULT STDMETHODCALLTYPE MetadataImportRO::GetMethodProps( + mdMethodDef mb, + mdTypeDef* pClass, + _Out_writes_to_opt_(cchMethod, *pchMethod) + LPWSTR szMethod, + ULONG cchMethod, + ULONG* pchMethod, + DWORD* pdwAttr, + PCCOR_SIGNATURE* ppvSigBlob, + ULONG* pcbSigBlob, + ULONG* pulCodeRVA, + DWORD* pdwImplFlags) +{ + if (TypeFromToken(mb) != mdtMethodDef) + return E_INVALIDARG; + + mdcursor_t cursor; + if (!md_token_to_cursor(_md_ptr.get(), mb, &cursor)) + return CLDB_E_INDEX_NOTFOUND; + + mdTypeDef classDef; + if (!md_find_token_of_range_element(cursor, &classDef)) + return CLDB_E_RECORD_NOTFOUND; + *pClass = classDef; + + uint32_t attrs; + if (!md_get_column_value_as_constant(cursor, mdtMethodDef_Flags, &attrs)) + return CLDB_E_FILE_CORRUPT; + *pdwAttr = attrs; + + uint32_t rva; + if (!md_get_column_value_as_constant(cursor, mdtMethodDef_Rva, &rva)) + return CLDB_E_FILE_CORRUPT; + *pulCodeRVA = rva; + + uint32_t implFlags; + if (!md_get_column_value_as_constant(cursor, mdtMethodDef_ImplFlags, &implFlags)) + return CLDB_E_FILE_CORRUPT; + *pdwImplFlags = implFlags; + + uint8_t const* sig; + uint32_t sigLen; + if (!md_get_column_value_as_blob(cursor, mdtMethodDef_Signature, &sig, &sigLen)) + return CLDB_E_FILE_CORRUPT; + *ppvSigBlob = sig; + *pcbSigBlob = sigLen; + + char const* name; + if (!md_get_column_value_as_utf8(cursor, mdtMethodDef_Name, &name)) + return CLDB_E_FILE_CORRUPT; + return ConvertAndReturnStringOutput(name, szMethod, cchMethod, pchMethod); +} + +HRESULT STDMETHODCALLTYPE MetadataImportRO::GetMemberRefProps( + mdMemberRef mr, + mdToken* ptk, + _Out_writes_to_opt_(cchMember, *pchMember) + LPWSTR szMember, + ULONG cchMember, + ULONG* pchMember, + PCCOR_SIGNATURE* ppvSigBlob, + ULONG* pbSig) +{ + if (TypeFromToken(mr) != mdtMemberRef) + return E_INVALIDARG; + + mdcursor_t cursor; + if (!md_token_to_cursor(_md_ptr.get(), mr, &cursor)) + return CLDB_E_INDEX_NOTFOUND; + + mdToken type; + if (!md_get_column_value_as_token(cursor, mdtMemberRef_Class, &type)) + return CLDB_E_FILE_CORRUPT; + + *ptk = type; + + uint8_t const* sig; + uint32_t sigLen; + if (!md_get_column_value_as_blob(cursor, mdtMemberRef_Signature, &sig, &sigLen)) + return CLDB_E_FILE_CORRUPT; + + *ppvSigBlob = sig; + *pbSig = sigLen; + + char const* name; + if (!md_get_column_value_as_utf8(cursor, mdtMemberRef_Name, &name)) + return CLDB_E_FILE_CORRUPT; + return ConvertAndReturnStringOutput(name, szMember, cchMember, pchMember); +} + +HRESULT STDMETHODCALLTYPE MetadataImportRO::EnumProperties( + HCORENUM* phEnum, + mdTypeDef td, + mdProperty rProperties[], + ULONG cMax, + ULONG* pcProperties) +{ + HRESULT hr; + HCORENUMImpl* enumImpl = ToHCORENUMImpl(*phEnum); + if (enumImpl == nullptr) + { + if (TypeFromToken(td) != mdtTypeDef) + return E_INVALIDARG; + + // Create cursor for PropertyMap table + mdcursor_t propertyMap; + uint32_t propertyMapCount; + if (!md_create_cursor(_md_ptr.get(), mdtid_PropertyMap, &propertyMap, &propertyMapCount)) + return CLDB_E_RECORD_NOTFOUND; + + // Find the entry in the PropertyMap table and then + // resolve the column to the range in the Property table. + mdcursor_t typedefPropMap; + mdcursor_t propertyList; + uint32_t propertyListCount; + if (!md_find_row_from_cursor(propertyMap, mdtPropertyMap_Parent, RidFromToken(td), &typedefPropMap) + || !md_get_column_value_as_range(typedefPropMap, mdtPropertyMap_PropertyList, &propertyList, &propertyListCount)) + { + return CLDB_E_FILE_CORRUPT; + } + + RETURN_IF_FAILED(HCORENUMImpl::CreateTableEnum(1, &enumImpl)); + HCORENUMImpl::InitTableEnum(*enumImpl, 0, propertyList, propertyListCount); + *phEnum = enumImpl; + } + return enumImpl->ReadTokens(rProperties, cMax, pcProperties); +} + +HRESULT STDMETHODCALLTYPE MetadataImportRO::EnumEvents( + HCORENUM* phEnum, + mdTypeDef td, + mdEvent rEvents[], + ULONG cMax, + ULONG* pcEvents) +{ + HRESULT hr; + HCORENUMImpl* enumImpl = ToHCORENUMImpl(*phEnum); + if (enumImpl == nullptr) + { + if (TypeFromToken(td) != mdtTypeDef) + return E_INVALIDARG; + + // Create cursor for EventMap table + mdcursor_t eventMap; + uint32_t eventMapCount; + if (!md_create_cursor(_md_ptr.get(), mdtid_EventMap, &eventMap, &eventMapCount)) + return CLDB_E_RECORD_NOTFOUND; + + // Find the entry in the EventMap table and then + // resolve the column to the range in the Event table. + mdcursor_t typedefEventMap; + mdcursor_t eventList; + uint32_t eventListCount; + if (!md_find_row_from_cursor(eventMap, mdtEventMap_Parent, RidFromToken(td), &typedefEventMap) + || !md_get_column_value_as_range(typedefEventMap, mdtEventMap_EventList, &eventList, &eventListCount)) + { + return CLDB_E_FILE_CORRUPT; + } + + RETURN_IF_FAILED(HCORENUMImpl::CreateTableEnum(1, &enumImpl)); + HCORENUMImpl::InitTableEnum(*enumImpl, 0, eventList, eventListCount); + *phEnum = enumImpl; + } + return enumImpl->ReadTokens(rEvents, cMax, pcEvents); +} + +HRESULT STDMETHODCALLTYPE MetadataImportRO::GetEventProps( + mdEvent ev, + mdTypeDef* pClass, + // Should be defined as _Out_writes_to_opt_(cchEvent, *pchEvent) and non-const. Mistake from initial release. + LPCWSTR szEvent, + ULONG cchEvent, + ULONG* pchEvent, + DWORD* pdwEventFlags, + mdToken* ptkEventType, + mdMethodDef* pmdAddOn, + mdMethodDef* pmdRemoveOn, + mdMethodDef* pmdFire, + mdMethodDef rmdOtherMethod[], + ULONG cMax, + ULONG* pcOtherMethod) +{ + if (TypeFromToken(ev) != mdtEvent) + return E_INVALIDARG; + + mdcursor_t cursor; + if (!md_token_to_cursor(_md_ptr.get(), ev, &cursor)) + return CLDB_E_RECORD_NOTFOUND; + + mdTypeDef classDef; + if (!md_find_token_of_range_element(cursor, &classDef)) + return CLDB_E_RECORD_NOTFOUND; + *pClass = classDef; + + uint32_t flags; + if (!md_get_column_value_as_constant(cursor, mdtEvent_EventFlags, &flags)) + return CLDB_E_FILE_CORRUPT; + *pdwEventFlags = flags; + + mdToken type; + if (!md_get_column_value_as_token(cursor, mdtEvent_EventType, &type)) + return CLDB_E_FILE_CORRUPT; + *ptkEventType = type; + + mdcursor_t methodSemCursor; + uint32_t methodSemCount; + if (!md_create_cursor(_md_ptr.get(), mdtid_MethodSemantics, &methodSemCursor, &methodSemCount)) + return CLDB_E_RECORD_NOTFOUND; + + struct _Finder + { + mdMethodDef AddOn; + mdMethodDef RemoveOn; + mdMethodDef Fire; + mdMethodDef* Other; + uint32_t const OtherLen; + uint32_t OtherCount; + HRESULT Result; // Result of the operation + + bool operator()(mdcursor_t c) + { + mdMethodDef tk; + uint32_t semantics; + if (!md_get_column_value_as_token(c, mdtMethodSemantics_Method, &tk) + || !md_get_column_value_as_constant(c, mdtMethodSemantics_Semantics, &semantics)) + { + Result = CLDB_E_FILE_CORRUPT; + return true; // Failure detected, so stop. + } + switch (semantics) + { + case msAddOn: AddOn = tk; + break; + case msRemoveOn: RemoveOn = tk; + break; + case msFire: Fire = tk; + break; + case msOther: + if (OtherCount < OtherLen) + Other[OtherCount] = tk; + OtherCount++; + break; + default: + assert(!"Unknown semantic"); + } + return false; + } + } finder{ mdMethodDefNil, mdMethodDefNil, mdMethodDefNil, rmdOtherMethod, cMax, 0, S_OK }; + + EnumTableRange(methodSemCursor, methodSemCount, mdtMethodSemantics_Association, ev, finder); + + HRESULT hr; + RETURN_IF_FAILED(finder.Result); + + *pmdAddOn = finder.AddOn; + *pmdRemoveOn = finder.RemoveOn; + *pmdFire = finder.Fire; + *pcOtherMethod = finder.OtherCount; + + char const* name; + if (!md_get_column_value_as_utf8(cursor, mdtEvent_Name, &name)) + return CLDB_E_FILE_CORRUPT; + // The const_cast<> is needed because the signature incorrectly expresses the + // desired semantics. This has been wrong since .NET Framework 1.0. + return ConvertAndReturnStringOutput(name, const_cast(szEvent), cchEvent, pchEvent); +} + +HRESULT STDMETHODCALLTYPE MetadataImportRO::EnumMethodSemantics( + HCORENUM* phEnum, + mdMethodDef mb, + mdToken rEventProp[], + ULONG cMax, + ULONG* pcEventProp) +{ + HRESULT hr; + HCORENUMImpl* enumImpl = ToHCORENUMImpl(*phEnum); + if (enumImpl == nullptr) + { + if (TypeFromToken(mb) != mdtMethodDef) + return E_INVALIDARG; + + mdcursor_t cursor; + uint32_t count; + if (!md_create_cursor(_md_ptr.get(), mdtid_MethodSemantics, &cursor, &count)) + return CLDB_E_RECORD_NOTFOUND; + + RETURN_IF_FAILED(HCORENUMImpl::CreateDynamicEnum(&enumImpl)); + + HCORENUMImpl_ptr cleanup{ enumImpl }; + + // Read in for matching in bulk + mdToken toMatch[64]; + mdToken matchedTk; + uint32_t i = 0; + while (i < count) + { + int32_t read = md_get_many_rows_column_value_as_token(cursor, mdtMethodSemantics_Method, ARRAY_SIZE(toMatch), toMatch); + if (read == 0) + break; + + assert(read > 0); + for (int32_t j = 0; j < read; ++j) + { + if (toMatch[j] == mb) + { + if (!md_get_column_value_as_token(cursor, mdtMethodSemantics_Association, &matchedTk)) + return CLDB_E_FILE_CORRUPT; + RETURN_IF_FAILED(HCORENUMImpl::AddToDynamicEnum(*enumImpl, matchedTk)); + } + (void)md_cursor_next(&cursor); + } + i += read; + } + *phEnum = cleanup.release(); + } + return enumImpl->ReadTokens(rEventProp, cMax, pcEventProp); +} + +HRESULT STDMETHODCALLTYPE MetadataImportRO::GetMethodSemantics( + mdMethodDef mb, + mdToken tkEventProp, + DWORD* pdwSemanticsFlags) +{ + if (TypeFromToken(mb) != mdtMethodDef || pdwSemanticsFlags == nullptr) + return E_INVALIDARG; + + mdcursor_t cursor; + uint32_t count; + if (!md_create_cursor(_md_ptr.get(), mdtid_MethodSemantics, &cursor, &count)) + return CLDB_E_RECORD_NOTFOUND; + + struct _Finder + { + mdMethodDef const MethodDef; // Look for this methoddef + uint32_t Value; // Value to acquire + HRESULT Result; // Result of the operation + + bool operator()(mdcursor_t c) + { + mdToken matchedTk; + if (md_get_column_value_as_token(c, mdtMethodSemantics_Method, &matchedTk) + && MethodDef == matchedTk) + { + // Found result, stop iterating + Result = (md_get_column_value_as_constant(c, mdtMethodSemantics_Semantics, &Value)) + ? S_OK + : CLDB_E_FILE_CORRUPT; + return true; + } + return false; + } + } finder{ mb, 0, CLDB_E_RECORD_NOTFOUND }; + + EnumTableRange(cursor, count, mdtMethodSemantics_Association, tkEventProp, finder); + + HRESULT hr; + RETURN_IF_FAILED(finder.Result); + + *pdwSemanticsFlags = finder.Value; + return S_OK; +} + +HRESULT STDMETHODCALLTYPE MetadataImportRO::GetClassLayout( + mdTypeDef td, + DWORD* pdwPackSize, + COR_FIELD_OFFSET rFieldOffset[], + ULONG cMax, + ULONG* pcFieldOffset, + ULONG* pulClassSize) +{ + if (TypeFromToken(td) != mdtTypeDef) + return E_INVALIDARG; + + mdcursor_t begin; + uint32_t count; + mdcursor_t entry; + bool foundLayout = false; + if (!md_create_cursor(_md_ptr.get(), mdtid_ClassLayout, &begin, &count) + || !md_find_row_from_cursor(begin, mdtClassLayout_Parent, RidFromToken(td), &entry)) + { + *pdwPackSize = 0; + *pulClassSize = 0; + } + else + { + foundLayout = true; + + uint32_t packSize; + uint32_t classSize; + // Acquire the packing and class sizes for the type and cursor to the typedef entry. + if (!md_get_column_value_as_constant(entry, mdtClassLayout_PackingSize, &packSize) + || !md_get_column_value_as_constant(entry, mdtClassLayout_ClassSize, &classSize)) + { + return CLDB_E_FILE_CORRUPT; + } + + *pdwPackSize = packSize; + *pulClassSize = classSize; + } + + mdcursor_t typeEntry; + if (!md_token_to_cursor(_md_ptr.get(), td, &typeEntry)) + return CLDB_E_RECORD_NOTFOUND; + + // Get the list of field data + mdcursor_t fieldList; + uint32_t fieldListCount; + if (!md_get_column_value_as_range(typeEntry, mdtTypeDef_FieldList, &fieldList, &fieldListCount)) + return CLDB_E_FILE_CORRUPT; + + *pcFieldOffset = fieldListCount; + if (fieldListCount > 0) + { + // It is possible the table is empty and can therefore fail. This API permits this + // behavior and sets the offset as -1 if this occurs. + mdcursor_t fieldLayoutBegin; + if (!md_create_cursor(_md_ptr.get(), mdtid_FieldLayout, &fieldLayoutBegin, &count)) + ::memset(&fieldLayoutBegin, 0, sizeof(fieldLayoutBegin)); + + uint32_t readIn = 0; + for (uint32_t i = 0; i < cMax; ++i) + { + COR_FIELD_OFFSET& offset = rFieldOffset[i]; + if (!md_cursor_to_token(fieldList, &offset.ridOfField)) + return CLDB_E_FILE_CORRUPT; + + // See above comment about empty FieldLayout table. + offset.ulOffset = (ULONG)-1; + mdcursor_t fieldLayoutRow; + if (md_find_row_from_cursor(fieldLayoutBegin, mdtFieldLayout_Field, RidFromToken(offset.ridOfField), &fieldLayoutRow)) + { + (void)md_get_column_value_as_constant(fieldLayoutRow, mdtFieldLayout_Offset, (uint32_t*)&offset.ulOffset); + foundLayout = true; + } + + readIn++; + if (readIn >= fieldListCount) + break; + + if (!md_cursor_next(&fieldList)) + return CLDB_E_FILE_CORRUPT; + } + } + + if (!foundLayout) + return CLDB_E_RECORD_NOTFOUND; + + return S_OK; +} + +HRESULT STDMETHODCALLTYPE MetadataImportRO::GetFieldMarshal( + mdToken tk, + PCCOR_SIGNATURE* ppvNativeType, + ULONG* pcbNativeType) +{ + if (TypeFromToken(tk) != mdtParamDef && TypeFromToken(tk) != mdtFieldDef) + return E_INVALIDARG; + + mdcursor_t cursor; + uint32_t count; + mdcursor_t fieldMarshalRow; + if (!md_create_cursor(_md_ptr.get(), mdtid_FieldMarshal, &cursor, &count) + || !md_find_row_from_cursor(cursor, mdtFieldMarshal_Parent, tk, &fieldMarshalRow)) + { + return CLDB_E_RECORD_NOTFOUND; + } + + uint8_t const* sig; + uint32_t sigLen; + if (!md_get_column_value_as_blob(fieldMarshalRow, mdtFieldMarshal_NativeType, &sig, &sigLen)) + return CLDB_E_FILE_CORRUPT; + + *ppvNativeType = (PCCOR_SIGNATURE)sig; + *pcbNativeType = sigLen; + return S_OK; +} + +HRESULT STDMETHODCALLTYPE MetadataImportRO::GetRVA( + mdToken tk, + ULONG* pulCodeRVA, + DWORD* pdwImplFlags) +{ + uint32_t codeRVA = 0; + uint32_t implFlags = 0; + + mdcursor_t cursor; + if (TypeFromToken(tk) == mdtMethodDef) + { + if (!md_token_to_cursor(_md_ptr.get(), tk, &cursor)) + return CLDB_E_RECORD_NOTFOUND; + + if (!md_get_column_value_as_constant(cursor, mdtMethodDef_Rva, &codeRVA) + || !md_get_column_value_as_constant(cursor, mdtMethodDef_ImplFlags, &implFlags)) + { + return CLDB_E_FILE_CORRUPT; + } + } + else + { + if (TypeFromToken(tk) != mdtFieldDef) + return E_INVALIDARG; + + uint32_t count; + mdcursor_t fieldRvaRow; + if (!md_create_cursor(_md_ptr.get(), mdtid_FieldRva, &cursor, &count) + || !md_find_row_from_cursor(cursor, mdtFieldRva_Field, RidFromToken(tk), &fieldRvaRow)) + { + return CLDB_E_RECORD_NOTFOUND; + } + + if (!md_get_column_value_as_constant(fieldRvaRow, mdtFieldRva_Rva, &codeRVA)) + return CLDB_E_FILE_CORRUPT; + } + + if (pulCodeRVA != nullptr) + *pulCodeRVA = (ULONG)codeRVA; + + if (pdwImplFlags != nullptr) + *pdwImplFlags = (DWORD)implFlags; + + return S_OK; +} + +HRESULT STDMETHODCALLTYPE MetadataImportRO::GetPermissionSetProps( + mdPermission pm, + DWORD* pdwAction, + void const** ppvPermission, + ULONG* pcbPermission) +{ + if (TypeFromToken(pm) != mdtPermission) + return E_INVALIDARG; + + mdcursor_t cursor; + if (!md_token_to_cursor(_md_ptr.get(), pm, &cursor)) + return CLDB_E_RECORD_NOTFOUND; + + if (pdwAction != nullptr + && !md_get_column_value_as_constant(cursor, mdtDeclSecurity_Action, (uint32_t*)pdwAction)) + { + return CLDB_E_FILE_CORRUPT; + } + + if (ppvPermission != nullptr + && !md_get_column_value_as_blob(cursor, mdtDeclSecurity_PermissionSet, (uint8_t const**)ppvPermission, (uint32_t*)pcbPermission)) + { + return CLDB_E_FILE_CORRUPT; + } + + return S_OK; +} + +HRESULT STDMETHODCALLTYPE MetadataImportRO::GetSigFromToken( + mdSignature mdSig, + PCCOR_SIGNATURE* ppvSig, + ULONG* pcbSig) +{ + if (TypeFromToken(mdSig) != mdtSignature || ppvSig == nullptr || pcbSig == nullptr) + return E_INVALIDARG; + + mdcursor_t cursor; + if (!md_token_to_cursor(_md_ptr.get(), mdSig, &cursor)) + return CLDB_E_INDEX_NOTFOUND; + + uint8_t const* sig; + uint32_t sigLen; + if (!md_get_column_value_as_blob(cursor, mdtStandAloneSig_Signature, &sig, &sigLen)) + return CLDB_E_FILE_CORRUPT; + *ppvSig = (PCCOR_SIGNATURE)sig; + *pcbSig = sigLen; + + return S_OK; +} + +HRESULT STDMETHODCALLTYPE MetadataImportRO::GetModuleRefProps( + mdModuleRef mur, + _Out_writes_to_opt_(cchName, *pchName) + LPWSTR szName, + ULONG cchName, + ULONG* pchName) +{ + if (TypeFromToken(mur) != mdtModuleRef) + return E_INVALIDARG; + + mdcursor_t cursor; + if (!md_token_to_cursor(_md_ptr.get(), mur, &cursor)) + return CLDB_E_INDEX_NOTFOUND; + + char const* name; + if (!md_get_column_value_as_utf8(cursor, mdtModuleRef_Name, &name)) + return CLDB_E_FILE_CORRUPT; + return ConvertAndReturnStringOutput(name, szName, cchName, pchName); +} + +HRESULT STDMETHODCALLTYPE MetadataImportRO::EnumModuleRefs( + HCORENUM* phEnum, + mdModuleRef rModuleRefs[], + ULONG cMax, + ULONG* pcModuleRefs) +{ + HRESULT hr; + HCORENUMImpl* enumImpl = ToHCORENUMImpl(*phEnum); + if (enumImpl == nullptr) + { + RETURN_IF_FAILED(CreateEnumTokens(_md_ptr.get(), mdtid_ModuleRef, &enumImpl)); + *phEnum = enumImpl; + } + return enumImpl->ReadTokens(rModuleRefs, cMax, pcModuleRefs); +} + +HRESULT STDMETHODCALLTYPE MetadataImportRO::GetTypeSpecFromToken( + mdTypeSpec typespec, + PCCOR_SIGNATURE* ppvSig, + ULONG* pcbSig) +{ + if (TypeFromToken(typespec) != mdtTypeSpec) + return E_INVALIDARG; + + mdcursor_t cursor; + if (!md_token_to_cursor(_md_ptr.get(), typespec, &cursor) + || !md_get_column_value_as_blob(cursor, mdtTypeSpec_Signature, ppvSig, (uint32_t*)pcbSig)) + { + return CLDB_E_RECORD_NOTFOUND; + } + + return S_OK; +} + +HRESULT STDMETHODCALLTYPE MetadataImportRO::GetNameFromToken( // Not Recommended! May be removed! + mdToken tk, + MDUTF8CSTR* pszUtf8NamePtr) +{ + if (pszUtf8NamePtr == nullptr) + return E_INVALIDARG; + + col_index_t col_idx; + switch (TypeFromToken(tk)) + { + case mdtModule: + col_idx = mdtModule_Name; + break; + case mdtTypeRef: + col_idx = mdtTypeRef_TypeName; + break; + case mdtTypeDef: + col_idx = mdtTypeDef_TypeName; + break; + case mdtFieldDef: + col_idx = mdtField_Name; + break; + case mdtMethodDef: + col_idx = mdtMethodDef_Name; + break; + case mdtParamDef: + col_idx = mdtParam_Name; + break; + case mdtMemberRef: + col_idx = mdtMemberRef_Name; + break; + case mdtEvent: + col_idx = mdtEvent_Name; + break; + case mdtProperty: + col_idx = mdtProperty_Name; + break; + case mdtModuleRef: + col_idx = mdtModuleRef_Name; + break; + default: + return E_INVALIDARG; + } + + mdcursor_t cursor; + if (!md_token_to_cursor(_md_ptr.get(), tk, &cursor)) + return CLDB_E_RECORD_NOTFOUND; + + if (!md_get_column_value_as_utf8(cursor, col_idx, pszUtf8NamePtr)) + return CLDB_E_FILE_CORRUPT; + + return S_OK; +} + +HRESULT STDMETHODCALLTYPE MetadataImportRO::EnumUnresolvedMethods( + HCORENUM* phEnum, + mdToken rMethods[], + ULONG cMax, + ULONG* pcTokens) +{ + UNREFERENCED_PARAMETER(phEnum); + UNREFERENCED_PARAMETER(rMethods); + UNREFERENCED_PARAMETER(cMax); + UNREFERENCED_PARAMETER(pcTokens); + + // IMetaDataEmit only + return E_NOTIMPL; +} + +HRESULT STDMETHODCALLTYPE MetadataImportRO::GetUserString( + mdString stk, + _Out_writes_to_opt_(cchString, *pchString) + LPWSTR szString, + ULONG cchString, + ULONG* pchString) +{ + if (TypeFromToken(stk) != mdtString || pchString == nullptr) + return E_INVALIDARG; + + mduserstringcursor_t cursor = RidFromToken(stk); + mduserstring_t string; + uint32_t offset; + if (!md_walk_user_string_heap(_md_ptr.get(), &cursor, &string, &offset)) + return CLDB_E_INDEX_NOTFOUND; + + // Strings in #US should have a trailing single byte. + if (string.str_bytes % sizeof(WCHAR) == 0) + return CLDB_E_FILE_CORRUPT; + + // Compute the string size in characters + uint32_t retCharLen = (string.str_bytes - 1) / sizeof(WCHAR); + *pchString = retCharLen; + if (cchString > 0 && szString != nullptr) + { + uint32_t toCopyWChar = cchString < retCharLen ? cchString : retCharLen; + ::memcpy(szString, string.str, toCopyWChar * sizeof(WCHAR)); + if (cchString < retCharLen) + { + ::memset(&szString[toCopyWChar - 1], 0, sizeof(WCHAR)); // Ensure null terminator + return CLDB_S_TRUNCATION; + } + } + + return S_OK; +} + +HRESULT STDMETHODCALLTYPE MetadataImportRO::GetPinvokeMap( + mdToken tk, + DWORD* pdwMappingFlags, + _Out_writes_to_opt_(cchImportName, *pchImportName) + LPWSTR szImportName, + ULONG cchImportName, + ULONG* pchImportName, + mdModuleRef* pmrImportDLL) +{ + if (TypeFromToken(tk) != mdtMethodDef && TypeFromToken(tk) != mdtFieldDef) + return E_INVALIDARG; + + mdcursor_t cursor; + uint32_t count; + mdcursor_t implRow; + if (!md_create_cursor(_md_ptr.get(), mdtid_ImplMap, &cursor, &count) + || !md_find_row_from_cursor(cursor, mdtImplMap_MemberForwarded, tk, &implRow)) + { + return CLDB_E_RECORD_NOTFOUND; + } + + uint32_t flags; + if (!md_get_column_value_as_constant(implRow, mdtImplMap_MappingFlags, &flags)) + return CLDB_E_FILE_CORRUPT; + *pdwMappingFlags = flags; + + mdModuleRef token; + if (!md_get_column_value_as_token(implRow, mdtImplMap_ImportScope, &token)) + return CLDB_E_FILE_CORRUPT; + *pmrImportDLL = token; + + char const* importName; + if (!md_get_column_value_as_utf8(implRow, mdtImplMap_ImportName, &importName)) + return CLDB_E_FILE_CORRUPT; + return ConvertAndReturnStringOutput(importName, szImportName, cchImportName, pchImportName); +} + +HRESULT STDMETHODCALLTYPE MetadataImportRO::EnumSignatures( + HCORENUM* phEnum, + mdSignature rSignatures[], + ULONG cMax, + ULONG* pcSignatures) +{ + HRESULT hr; + HCORENUMImpl* enumImpl = ToHCORENUMImpl(*phEnum); + if (enumImpl == nullptr) + { + RETURN_IF_FAILED(CreateEnumTokens(_md_ptr.get(), mdtid_StandAloneSig, &enumImpl)); + *phEnum = enumImpl; + } + return enumImpl->ReadTokens(rSignatures, cMax, pcSignatures); +} + +HRESULT STDMETHODCALLTYPE MetadataImportRO::EnumTypeSpecs( + HCORENUM* phEnum, + mdTypeSpec rTypeSpecs[], + ULONG cMax, + ULONG* pcTypeSpecs) +{ + HRESULT hr; + HCORENUMImpl* enumImpl = ToHCORENUMImpl(*phEnum); + if (enumImpl == nullptr) + { + RETURN_IF_FAILED(CreateEnumTokens(_md_ptr.get(), mdtid_TypeSpec, &enumImpl)); + *phEnum = enumImpl; + } + return enumImpl->ReadTokens(rTypeSpecs, cMax, pcTypeSpecs); +} + +HRESULT STDMETHODCALLTYPE MetadataImportRO::EnumUserStrings( + HCORENUM* phEnum, + mdString rStrings[], + ULONG cMax, + ULONG* pcStrings) +{ + HRESULT hr; + HCORENUMImpl* enumImpl = ToHCORENUMImpl(*phEnum); + if (enumImpl == nullptr) + { + RETURN_IF_FAILED(HCORENUMImpl::CreateDynamicEnum(&enumImpl)); + + HCORENUMImpl_ptr cleanup{ enumImpl }; + mduserstring_t us; + uint32_t offset; + mduserstringcursor_t cursor = 0; + for (;;) + { + if (!md_walk_user_string_heap(_md_ptr.get(), &cursor, &us, &offset)) + break; + + // Ignore strings that are of zero length. + if (us.str_bytes == 0) + continue; + + // Add mdtString token types to the enumeration. + RETURN_IF_FAILED(HCORENUMImpl::AddToDynamicEnum(*enumImpl, RidToToken(offset, mdtString))); + } + + *phEnum = cleanup.release(); + } + return enumImpl->ReadTokens(rStrings, cMax, pcStrings); +} + +HRESULT STDMETHODCALLTYPE MetadataImportRO::GetParamForMethodIndex( + mdMethodDef md, + ULONG ulParamSeq, + mdParamDef* ppd) +{ + if (TypeFromToken(md) != mdtMethodDef || ulParamSeq == UINT32_MAX || ppd == nullptr) + return E_INVALIDARG; + + mdcursor_t cursor; + if (!md_token_to_cursor(_md_ptr.get(), md, &cursor)) + return CLDB_E_RECORD_NOTFOUND; + + mdcursor_t curr; + uint32_t count; + if (!md_get_column_value_as_range(cursor, mdtMethodDef_ParamList, &curr, &count)) + return CLDB_E_FILE_CORRUPT; + + uint32_t seqMaybe; + for (uint32_t i = 0; i < count; ++i) + { + mdcursor_t target; + if (!md_resolve_indirect_cursor(curr, &target)) + return CLDB_E_FILE_CORRUPT; + if (!md_get_column_value_as_constant(target, mdtParam_Sequence, &seqMaybe)) + return CLDB_E_FILE_CORRUPT; + + if (ulParamSeq == seqMaybe) + { + (void)md_cursor_to_token(target, ppd); + return S_OK; + } + + // If the requested sequence value is less than what is returned, + // we are done. Param sequences numbers are ordered - see II.22.33. + if (ulParamSeq < seqMaybe) + break; + + (void)md_cursor_next(&curr); + } + + return CLDB_E_RECORD_NOTFOUND; +} + +HRESULT STDMETHODCALLTYPE MetadataImportRO::EnumCustomAttributes( + HCORENUM* phEnum, + mdToken tk, + mdToken tkType, + mdCustomAttribute rCustomAttributes[], + ULONG cMax, + ULONG* pcCustomAttributes) +{ + HRESULT hr; + HCORENUMImpl* enumImpl = ToHCORENUMImpl(*phEnum); + if (enumImpl == nullptr) + { + mdcursor_t cursor; + uint32_t count; + if (!md_create_cursor(_md_ptr.get(), mdtid_CustomAttribute, &cursor, &count)) + return CLDB_E_RECORD_NOTFOUND; + + mdcursor_t curr; + uint32_t currCount; + if (IsNilToken(tk)) + { + // Caller is looking across all attributes + assert(IsNilToken(tkType)); // Ignoring type filter + RETURN_IF_FAILED(HCORENUMImpl::CreateTableEnum(1, &enumImpl)); + HCORENUMImpl::InitTableEnum(*enumImpl, 0, cursor, count); + } + else + { + md_range_result_t result = md_find_range_from_cursor(cursor, mdtCustomAttribute_Parent, tk, &curr, &currCount); + if (IsNilToken(tkType) && result != MD_RANGE_NOT_SUPPORTED) + { + // Caller is looking for all associated attributes and we got a table range. + if (result == MD_RANGE_FOUND) + { + RETURN_IF_FAILED(HCORENUMImpl::CreateTableEnum(1, &enumImpl)); + HCORENUMImpl::InitTableEnum(*enumImpl, 0, curr, currCount); + } + else if (result == MD_RANGE_NOT_FOUND) + { + // If there are no tokens found, create an empty enumeration. + RETURN_IF_FAILED(HCORENUMImpl::CreateDynamicEnum(&enumImpl)); + } + } + else + { + RETURN_IF_FAILED(HCORENUMImpl::CreateDynamicEnum(&enumImpl)); + + HCORENUMImpl_ptr cleanup{ enumImpl }; + + // We need to search across the entire table as we couldn't get a range. + curr = cursor; + currCount = count; + + // Read in for matching in bulk + mdToken toMatch[64]; + mdToken matchedTk; + uint32_t i = 0; + while (i < currCount) + { + int32_t read = md_get_many_rows_column_value_as_token(curr, mdtCustomAttribute_Parent, ARRAY_SIZE(toMatch), toMatch); + if (read == 0) + break; + + assert(read > 0); + for (int32_t j = 0; j < read; ++j) + { + if (toMatch[j] == tkType) + { + (void)md_cursor_to_token(curr, &matchedTk); + RETURN_IF_FAILED(HCORENUMImpl::AddToDynamicEnum(*enumImpl, matchedTk)); + } + (void)md_cursor_next(&curr); + } + i += read; + } + enumImpl = cleanup.release(); + } + } + *phEnum = enumImpl; + } + return enumImpl->ReadTokens(rCustomAttributes, cMax, pcCustomAttributes); +} + +HRESULT STDMETHODCALLTYPE MetadataImportRO::GetCustomAttributeProps( + mdCustomAttribute cv, + mdToken* ptkObj, + mdToken* ptkType, + void const** ppBlob, + ULONG* pcbSize) +{ + if (TypeFromToken(cv) != mdtCustomAttribute) + return E_INVALIDARG; + + mdcursor_t cursor; + if (!md_token_to_cursor(_md_ptr.get(), cv, &cursor)) + return CLDB_E_RECORD_NOTFOUND; + + mdToken obj; + if (!md_get_column_value_as_token(cursor, mdtCustomAttribute_Parent, &obj)) + return CLDB_E_FILE_CORRUPT; + *ptkObj = obj; + + mdToken type; + if (!md_get_column_value_as_token(cursor, mdtCustomAttribute_Type, &type)) + return CLDB_E_FILE_CORRUPT; + *ptkType = type; + + uint8_t const* blob; + uint32_t blobLen; + if (!md_get_column_value_as_blob(cursor, mdtCustomAttribute_Value, &blob, &blobLen)) + return CLDB_E_FILE_CORRUPT; + *ppBlob = blob; + *pcbSize = blobLen; + + return S_OK; +} + +HRESULT STDMETHODCALLTYPE MetadataImportRO::FindTypeRef( + mdToken tkResolutionScope, + LPCWSTR szName, + mdTypeRef* ptr) +{ + mdcursor_t cursor; + uint32_t count; + if (!md_create_cursor(_md_ptr.get(), mdtid_TypeRef, &cursor, &count)) + return CLDB_E_RECORD_NOTFOUND; + + pal::StringConvert cvt{ szName }; + if (!cvt.Success()) + return E_INVALIDARG; + + char const* nspace; + char const* name; + SplitTypeName(cvt, &nspace, &name); + + bool scopeIsSet = !IsNilToken(tkResolutionScope); + mdToken resMaybe; + char const* str; + for (uint32_t i = 0; i < count; (void)md_cursor_next(&cursor), ++i) + { + if (!md_get_column_value_as_token(cursor, mdtTypeRef_ResolutionScope, &resMaybe)) + return CLDB_E_FILE_CORRUPT; + + // See if the Resolution scopes match. + if ((IsNilToken(resMaybe) && scopeIsSet) // User didn't state scope. + || resMaybe != tkResolutionScope) // Match user scope. + { + continue; + } + + if (!md_get_column_value_as_utf8(cursor, mdtTypeRef_TypeNamespace, &str)) + return CLDB_E_FILE_CORRUPT; + + if (0 != ::strcmp(nspace, str)) + continue; + + if (!md_get_column_value_as_utf8(cursor, mdtTypeRef_TypeName, &str)) + return CLDB_E_FILE_CORRUPT; + + if (0 == ::strcmp(name, str)) + { + (void)md_cursor_to_token(cursor, ptr); + return S_OK; + } + } + + // Not found. + return CLDB_E_RECORD_NOTFOUND; +} + +HRESULT STDMETHODCALLTYPE MetadataImportRO::GetMemberProps( + mdToken mb, + mdTypeDef* pClass, + _Out_writes_to_opt_(cchMember, *pchMember) + LPWSTR szMember, + ULONG cchMember, + ULONG* pchMember, + DWORD* pdwAttr, + PCCOR_SIGNATURE* ppvSigBlob, + ULONG* pcbSigBlob, + ULONG* pulCodeRVA, + DWORD* pdwImplFlags, + DWORD* pdwCPlusTypeFlag, + UVCP_CONSTANT* ppValue, + ULONG* pcchValue) +{ + if (TypeFromToken(mb) == mdtMethodDef) + { + return GetMethodProps( + mb, + pClass, + szMember, + cchMember, + pchMember, + pdwAttr, + ppvSigBlob, + pcbSigBlob, + pulCodeRVA, + pdwImplFlags); + } + + if (TypeFromToken(mb) == mdtFieldDef) + { + return GetFieldProps( + mb, + pClass, + szMember, + cchMember, + pchMember, + pdwAttr, + ppvSigBlob, + pcbSigBlob, + pdwCPlusTypeFlag, + ppValue, + pcchValue); + } + + return E_INVALIDARG; +} + +namespace +{ + HRESULT FindConstant( + mdhandle_view ptr, + uint32_t lookupValue, + DWORD& cnstCorType, + UVCP_CONSTANT& cnst, + ULONG& cnstLen) + { + assert(ptr != nullptr); + + mdcursor_t constantCursor; + uint32_t constantCount; + mdcursor_t constantPropCursor; + uint32_t corType; + uint8_t const* defaultValue; + uint32_t defaultValueLen; + if (!md_create_cursor(ptr.get(), mdtid_Constant, &constantCursor, &constantCount) + || !md_find_row_from_cursor(constantCursor, mdtConstant_Parent, lookupValue, &constantPropCursor)) + { + corType = ELEMENT_TYPE_VOID; + defaultValue = nullptr; + defaultValueLen = 0; + } + else + { + if (!md_get_column_value_as_constant(constantPropCursor, mdtConstant_Type, &corType)) + return CLDB_E_FILE_CORRUPT; + if (!md_get_column_value_as_blob(constantPropCursor, mdtConstant_Value, &defaultValue, &defaultValueLen)) + return CLDB_E_FILE_CORRUPT; + } + + cnstCorType = corType; + cnst = (UVCP_CONSTANT)defaultValue; + cnstLen = corType == ELEMENT_TYPE_STRING + ? defaultValueLen / sizeof(WCHAR) + : 0; // Only string return a non-zero length for the constant value. + return S_OK; + } +} + +HRESULT STDMETHODCALLTYPE MetadataImportRO::GetFieldProps( + mdFieldDef mb, + mdTypeDef* pClass, + _Out_writes_to_opt_(cchField, *pchField) + LPWSTR szField, + ULONG cchField, + ULONG* pchField, + DWORD* pdwAttr, + PCCOR_SIGNATURE* ppvSigBlob, + ULONG* pcbSigBlob, + DWORD* pdwCPlusTypeFlag, + UVCP_CONSTANT* ppValue, + ULONG* pcchValue) +{ + if (TypeFromToken(mb) != mdtFieldDef) + return E_INVALIDARG; + + mdcursor_t cursor; + if (!md_token_to_cursor(_md_ptr.get(), mb, &cursor)) + return CLDB_E_RECORD_NOTFOUND; + + mdTypeDef classDef; + if (!md_find_token_of_range_element(cursor, &classDef)) + return CLDB_E_RECORD_NOTFOUND; + *pClass = classDef; + + uint32_t flags; + if (!md_get_column_value_as_constant(cursor, mdtField_Flags, &flags)) + return CLDB_E_FILE_CORRUPT; + *pdwAttr = flags; + + uint8_t const* sig; + uint32_t sigLen; + if (!md_get_column_value_as_blob(cursor, mdtField_Signature, &sig, &sigLen)) + return CLDB_E_FILE_CORRUPT; + *ppvSigBlob = (PCCOR_SIGNATURE)sig; + *pcbSigBlob = sigLen; + + HRESULT hr; + RETURN_IF_FAILED(FindConstant(_md_ptr, mb, *pdwCPlusTypeFlag, *ppValue, *pcchValue)); + + char const* name; + if (!md_get_column_value_as_utf8(cursor, mdtField_Name, &name)) + return CLDB_E_FILE_CORRUPT; + return ConvertAndReturnStringOutput(name, szField, cchField, pchField); +} + +HRESULT STDMETHODCALLTYPE MetadataImportRO::GetPropertyProps( + mdProperty prop, + mdTypeDef* pClass, + // Should be defined as _Out_writes_to_opt_(cchProperty, *pchProperty) and non-const. Mistake from initial release. + LPCWSTR szProperty, + ULONG cchProperty, + ULONG* pchProperty, + DWORD* pdwPropFlags, + PCCOR_SIGNATURE* ppvSig, + ULONG* pbSig, + DWORD* pdwCPlusTypeFlag, + UVCP_CONSTANT* ppDefaultValue, + ULONG* pcchDefaultValue, + mdMethodDef* pmdSetter, + mdMethodDef* pmdGetter, + mdMethodDef rmdOtherMethod[], + ULONG cMax, + ULONG* pcOtherMethod) +{ + if (TypeFromToken(prop) != mdtProperty) + return E_INVALIDARG; + + mdcursor_t cursor; + if (!md_token_to_cursor(_md_ptr.get(), prop, &cursor)) + return CLDB_E_RECORD_NOTFOUND; + + mdTypeDef classDef; + if (!md_find_token_of_range_element(cursor, &classDef)) + return CLDB_E_RECORD_NOTFOUND; + *pClass = classDef; + + uint32_t flags; + if (!md_get_column_value_as_constant(cursor, mdtProperty_Flags, &flags)) + return CLDB_E_FILE_CORRUPT; + *pdwPropFlags = flags; + + uint8_t const* sig; + uint32_t sigLen; + if (!md_get_column_value_as_blob(cursor, mdtProperty_Type, &sig, &sigLen)) + return CLDB_E_FILE_CORRUPT; + *ppvSig = sig; + *pbSig = sigLen; + + HRESULT hr; + RETURN_IF_FAILED(FindConstant(_md_ptr, prop, *pdwCPlusTypeFlag, *ppDefaultValue, *pcchDefaultValue)); + + mdcursor_t methodSemCursor; + uint32_t methodSemCount; + if (!md_create_cursor(_md_ptr.get(), mdtid_MethodSemantics, &methodSemCursor, &methodSemCount)) + return CLDB_E_RECORD_NOTFOUND; + + struct _Finder + { + mdMethodDef Setter; + mdMethodDef Getter; + mdMethodDef* Other; + uint32_t const OtherLen; + uint32_t OtherCount; + HRESULT Result; // Result of the operation + + bool operator()(mdcursor_t c) + { + mdMethodDef tk; + uint32_t semantics; + if (!md_get_column_value_as_token(c, mdtMethodSemantics_Method, &tk) + || !md_get_column_value_as_constant(c, mdtMethodSemantics_Semantics, &semantics)) + { + Result = CLDB_E_FILE_CORRUPT; + return true; // Failure detected, so stop. + } + switch (semantics) + { + case msSetter: Setter = tk; + break; + case msGetter: Getter = tk; + break; + case msOther: + if (OtherCount < OtherLen) + Other[OtherCount] = tk; + OtherCount++; + break; + default: + assert(!"Unknown semantic"); + } + return false; + } + } finder{ mdMethodDefNil, mdMethodDefNil, rmdOtherMethod, cMax, 0, S_OK }; + + EnumTableRange(methodSemCursor, methodSemCount, mdtMethodSemantics_Association, prop, finder); + + RETURN_IF_FAILED(finder.Result); + + *pmdSetter = finder.Setter; + *pmdGetter = finder.Getter; + *pcOtherMethod = finder.OtherCount; + + char const* name; + if (!md_get_column_value_as_utf8(cursor, mdtProperty_Name, &name)) + return CLDB_E_FILE_CORRUPT; + + // The const_cast<> is needed because the signature incorrectly expresses the + // desired semantics. This has been wrong since .NET Framework 1.0. + return ConvertAndReturnStringOutput(name, const_cast(szProperty), cchProperty, pchProperty); +} + +HRESULT STDMETHODCALLTYPE MetadataImportRO::GetParamProps( + mdParamDef tk, + mdMethodDef* pmd, + ULONG* pulSequence, + _Out_writes_to_opt_(cchName, *pchName) + LPWSTR szName, + ULONG cchName, + ULONG* pchName, + DWORD* pdwAttr, + DWORD* pdwCPlusTypeFlag, + UVCP_CONSTANT* ppValue, + ULONG* pcchValue) +{ + if (TypeFromToken(tk) != mdtParamDef) + return E_INVALIDARG; + + mdcursor_t cursor; + if (!md_token_to_cursor(_md_ptr.get(), tk, &cursor)) + return CLDB_E_RECORD_NOTFOUND; + + mdMethodDef methodDef; + if (!md_find_token_of_range_element(cursor, &methodDef)) + return CLDB_E_FILE_CORRUPT; + *pmd = methodDef; + + uint32_t seq; + if (!md_get_column_value_as_constant(cursor, mdtParam_Sequence, &seq)) + return CLDB_E_FILE_CORRUPT; + *pulSequence = seq; + + uint32_t flags; + if (!md_get_column_value_as_constant(cursor, mdtParam_Flags, &flags)) + return CLDB_E_FILE_CORRUPT; + *pdwAttr = flags; + + HRESULT hr; + RETURN_IF_FAILED(FindConstant(_md_ptr, tk, *pdwCPlusTypeFlag, *ppValue, *pcchValue)); + + char const* name; + if (!md_get_column_value_as_utf8(cursor, mdtParam_Name, &name)) + return CLDB_E_FILE_CORRUPT; + return ConvertAndReturnStringOutput(name, szName, cchName, pchName); +} + +namespace +{ + // See TypeSpec definition at II.23.2.14 + HRESULT ExtractTypeDefRefFromSpec(uint8_t const* specBlob, uint32_t specBlobLen, mdToken& tk) + { + assert(specBlob != nullptr); + if (specBlobLen == 0) + return COR_E_BADIMAGEFORMAT; + + PCCOR_SIGNATURE sig = specBlob; + PCCOR_SIGNATURE sigEnd = specBlob + specBlobLen; + + ULONG data; + sig += CorSigUncompressData(sig, &data); + + while (sig < sigEnd + && (CorIsModifierElementType((CorElementType)data) + || data == ELEMENT_TYPE_GENERICINST)) + { + sig += CorSigUncompressData(sig, &data); + } + + if (sig >= sigEnd) + return COR_E_BADIMAGEFORMAT; + + if (data == ELEMENT_TYPE_VALUETYPE || data == ELEMENT_TYPE_CLASS) + { + if (mdTokenNil == CorSigUncompressToken(sig, &tk)) + return COR_E_BADIMAGEFORMAT; + return S_OK; + } + + tk = mdTokenNil; + return S_FALSE; + } + + HRESULT ResolveTypeDefRefSpecToName(mdcursor_t cursor, char const** nspace, char const** name) + { + assert(nspace != nullptr && name != nullptr); + + HRESULT hr; + mdToken typeTk; + if (!md_cursor_to_token(cursor, &typeTk)) + return E_FAIL; + + uint8_t const* specBlob; + uint32_t specBlobLen; + uint32_t tokenType = TypeFromToken(typeTk); + while (tokenType == mdtTypeSpec) + { + if (!md_get_column_value_as_blob(cursor, mdtTypeSpec_Signature, &specBlob, &specBlobLen)) + return CLDB_E_FILE_CORRUPT; + + RETURN_IF_FAILED(ExtractTypeDefRefFromSpec(specBlob, specBlobLen, typeTk)); + if (typeTk == mdTokenNil) + return S_FALSE; + + if (!md_token_to_cursor(md_extract_handle_from_cursor(cursor), typeTk, &cursor)) + return CLDB_E_FILE_CORRUPT; + tokenType = TypeFromToken(typeTk); + } + + switch (tokenType) + { + case mdtTypeDef: + return (md_get_column_value_as_utf8(cursor, mdtTypeDef_TypeNamespace, nspace) + && md_get_column_value_as_utf8(cursor, mdtTypeDef_TypeName, name)) + ? S_OK + : CLDB_E_FILE_CORRUPT; + case mdtTypeRef: + return (md_get_column_value_as_utf8(cursor, mdtTypeRef_TypeNamespace, nspace) + && md_get_column_value_as_utf8(cursor, mdtTypeRef_TypeName, name)) + ? S_OK + : CLDB_E_FILE_CORRUPT; + default: + assert(!"Unexpected token in ResolveTypeDefRefSpecToName"); + return E_FAIL; + } + } +} + +HRESULT STDMETHODCALLTYPE MetadataImportRO::GetCustomAttributeByName( + mdToken tkObj, + LPCWSTR szName, + void const** ppData, + ULONG* pcbData) +{ + if (szName == nullptr || ppData == nullptr || pcbData == nullptr) + return E_INVALIDARG; + + mdcursor_t cursor; + uint32_t count; + if (!md_create_cursor(_md_ptr.get(), mdtid_CustomAttribute, &cursor, &count)) + return S_FALSE; // If no custom attributes are defined, treat it the same as if the attribute is not found. + + char buffer[1024]; + pal::StringConvert cvt{ szName, buffer }; + if (!cvt.Success()) + return E_INVALIDARG; + + struct + { + HRESULT hr; + void const** ppData; + ULONG* pcbData; + pal::StringConvert const& cvt; + + bool operator()(mdcursor_t custAttrCurr) + { + char const* nspace; + char const* name; + + mdcursor_t type; + mdcursor_t tgtType; + mdToken typeTk; + size_t len; + char const* curr; + + if (!md_get_column_value_as_cursor(custAttrCurr, mdtCustomAttribute_Type, &type)) + { + hr = CLDB_E_FILE_CORRUPT; + return true; + } + + // Cursor was returned so must be valid. + (void)md_cursor_to_token(type, &typeTk); + + // Resolve the cursor based on its type. + switch (TypeFromToken(typeTk)) + { + case mdtMethodDef: + if (!md_find_cursor_of_range_element(type, &tgtType)) + { + hr = CLDB_E_FILE_CORRUPT; + return true; + } + break; + case mdtMemberRef: + if (!md_get_column_value_as_cursor(type, mdtMemberRef_Class, &tgtType)) + { + hr = CLDB_E_FILE_CORRUPT; + return true; + } + break; + default: + assert(!"Unexpected token in GetCustomAttributeByName"); + { + hr = COR_E_BADIMAGEFORMAT; + return true; + } + } + + if (FAILED(hr = ResolveTypeDefRefSpecToName(tgtType, &nspace, &name))) + { + return true; + } + else + { + hr = S_FALSE; + curr = cvt; + if (nspace[0] != '\0') + { + len = ::strlen(nspace); + if (0 != ::strncmp(cvt, nspace, len)) + return false; + curr += len; + + // Check for overrun and next character + if (cvt.Length() <= len || curr[0] != '.') + return false; + curr += 1; + } + + if (0 == ::strcmp(curr, name)) + { + uint8_t const* data; + uint32_t dataLen; + if (!md_get_column_value_as_blob(custAttrCurr, mdtCustomAttribute_Value, &data, &dataLen)) + { + hr = CLDB_E_FILE_CORRUPT; + return true; + } + *ppData = data; + *pcbData = dataLen; + hr = S_OK; + return true; + } + } + return false; + } + } finder {S_FALSE, ppData, pcbData, cvt}; + + EnumTableRange(cursor, count, mdtCustomAttribute_Parent, tkObj, finder); + + if (finder.hr != S_OK) + { + *ppData = nullptr; + *pcbData = 0; + } + return finder.hr; +} + +BOOL STDMETHODCALLTYPE MetadataImportRO::IsValidToken( + mdToken tk) +{ + // If we can create a cursor, the token is valid. + mdcursor_t cursor; + return md_token_to_cursor(_md_ptr.get(), tk, &cursor) + ? TRUE + : FALSE; +} + +HRESULT STDMETHODCALLTYPE MetadataImportRO::GetNestedClassProps( + mdTypeDef tdNestedClass, + mdTypeDef* ptdEnclosingClass) +{ + if (TypeFromToken(tdNestedClass) != mdtTypeDef) + return E_INVALIDARG; + + mdcursor_t cursor; + uint32_t count; + mdcursor_t nestedClassRow; + if (!md_create_cursor(_md_ptr.get(), mdtid_NestedClass, &cursor, &count) + || !md_find_row_from_cursor(cursor, mdtNestedClass_NestedClass, RidFromToken(tdNestedClass), &nestedClassRow)) + { + return CLDB_E_RECORD_NOTFOUND; + } + + mdTypeDef enclosed; + if (!md_get_column_value_as_token(nestedClassRow, mdtNestedClass_EnclosingClass, &enclosed)) + return CLDB_E_FILE_CORRUPT; + + *ptdEnclosingClass = enclosed; + return S_OK; +} + +namespace +{ + uint32_t const FoundValue = 0; + uint32_t const InvalidReadCount = (uint32_t)-1; + + struct ReadSigContext + { + mdhandle_t Handle; + CorPinvokeMap PinvokeCallConv; + }; + + // II.23.2.8 TypeDefOrRefOrSpecEncoded as potential calling convention + uint32_t ReadTypeDefOrRefOrSpecEncodedAsCallConv(PCCOR_SIGNATURE sig, ReadSigContext& cxt) + { + mdToken tk; + uint32_t readIn = CorSigUncompressToken(sig, &tk); + if (IsNilToken(tk) || TypeFromToken(tk) == mdtTypeSpec) + return readIn; + + // See if this token is a calling convention. + uint32_t tkType = TypeFromToken(tk); + if (tkType != mdtTypeRef && tkType != mdtTypeDef) + return readIn; + + mdcursor_t cursor; + if (!md_token_to_cursor(cxt.Handle, tk, &cursor)) + return InvalidReadCount; + + col_index_t colNspace; + col_index_t colName; + if (tkType == mdtTypeRef) + { + colNspace = mdtTypeRef_TypeNamespace; + colName = mdtTypeRef_TypeName; + } + else + { + assert(tkType == mdtTypeDef); + colNspace = mdtTypeDef_TypeNamespace; + colName = mdtTypeDef_TypeName; + } + + char const* nspace; + if (!md_get_column_value_as_utf8(cursor, colNspace, &nspace)) + return InvalidReadCount; + + if (0 == ::strcmp(nspace, CMOD_CALLCONV_NAMESPACE) || 0 == ::strcmp(nspace, CMOD_CALLCONV_NAMESPACE_OLD)) + { + char const* name; + if (!md_get_column_value_as_utf8(cursor, colName, &name)) + return InvalidReadCount; + + if (0 == ::strcmp(name, CMOD_CALLCONV_NAME_CDECL)) + { + cxt.PinvokeCallConv = pmCallConvCdecl; + return FoundValue; + } + if (0 == ::strcmp(name, CMOD_CALLCONV_NAME_STDCALL)) + { + cxt.PinvokeCallConv = pmCallConvStdcall; + return FoundValue; + } + if (0 == ::strcmp(name, CMOD_CALLCONV_NAME_THISCALL)) + { + cxt.PinvokeCallConv = pmCallConvThiscall; + return FoundValue; + } + if (0 == ::strcmp(name, CMOD_CALLCONV_NAME_FASTCALL)) + { + cxt.PinvokeCallConv = pmCallConvFastcall; + return FoundValue; + } + } + return readIn; + } + + // Handles processing the following metadata signature rules. + // + // S := MethodDefSig | MethodRefSig | StandAloneMethodSig + // II.23.2.1 MethodDefSig : = (DEFAULT | VARARG | (GENERIC GenParamCount)) ParamCount RetType Param* + // II.23.2.2 MethodRefSig : = (DEFAULT | (GENERIC GenParamCount)) ParamCount RetType Param* + // | VARARG ParamCount RetType Param* (SENTINEL Param+)? + // II.23.2.3 StandAloneMethodSig : = (DEFAULT | STDCALL | THISCALL | FASTCALL) ParamCount RetType Param* + // | (VARARG | C) ParamCount RetType Param* (SENTINEL Param+)? + // + // II.23.2.11 RetType := CustomMod* (BYREF? Type | TYPEDBYREF | VOID) + // II.23.2.10 Param := CustomMod* (BYREF? Type | TYPEDBYREF) + // II.23.2.7 CustomMod := (CMOD_OPT | CMOD_REQD) TypeDefOrRefOrSpecEncoded + // II.23.2.12 Type := BOOLEAN | CHAR | I1 | U1 | I2 | U2 | I4 | U4 | I8 | U8 | R4 | R8 | I | U + // | ARRAY Type ArrayShape + // | CLASS TypeDefOrRefOrSpecEncoded + // | FNPTR MethodDefSig + // | FNPTR MethodRefSig + // | GENERICINST (CLASS | VALUETYPE) TypeDefOrRefOrSpecEncoded GenArgCount Type* + // | MVAR Number + // | OBJECT + // | PTR CustomMod* Type + // | PTR CustomMod* VOID + // | STRING + // | SZARRAY CustomMod* Type + // | VALUETYPE TypeDefOrRefOrSpecEncoded + // | VAR Number + // II.23.2.13 ArrayShape := Rank NumSizes Size* NumLoBounds LoBound* + // + // II.23.2.8 TypeDefOrRefOrSpecEncoded := mdToken + // GenArgCount := uint32 + // ParamCount := uint32 + // Number := uint32 + // Rank := uint32 + // NumSizes := uint32 + // Size := uint32 + // NumLoBounds := uint32 + // LoBound := int32 + uint32_t ReadCorType_CallConv(PCCOR_SIGNATURE sig, PCCOR_SIGNATURE sigEnd, ReadSigContext& cxt) + { +#define RETURN_IF_NOT_ADVANCE(stmt) \ +{\ + if (sigCurr >= sigEnd) return InvalidReadCount; \ + cnt = stmt; \ + if (cnt <= FoundValue) return cnt; \ + sigCurr += cnt; \ +} + + assert(sig != nullptr && sigEnd != nullptr); + + PCCOR_SIGNATURE sigCurr = sig; + + // Process modifiers (e.g., PTR or BYREF) and VARARG sentinel values - see MethodDefSig and MethodRefSig. + CorElementType corType = CorSigUncompressElementType(sigCurr); + while (CorIsModifierElementType(corType) || corType == ELEMENT_TYPE_SENTINEL) + corType = CorSigUncompressElementType(sigCurr); + + // Read in CorType + uint32_t cnt = 0; + ULONG data; + ULONG tmp; + int32_t stmp; + mdToken tk; + switch (corType) + { + case ELEMENT_TYPE_SZARRAY: + RETURN_IF_NOT_ADVANCE(ReadCorType_CallConv(sigCurr, sigEnd, cxt)); // CustomMod* Type + break; + case ELEMENT_TYPE_VAR: + case ELEMENT_TYPE_MVAR: + RETURN_IF_NOT_ADVANCE(CorSigUncompressData(sigCurr, &data)); // Number + break; + case ELEMENT_TYPE_GENERICINST: + RETURN_IF_NOT_ADVANCE(ReadCorType_CallConv(sigCurr, sigEnd, cxt)); // (CLASS | VALUETYPE) TypeDefOrRefOrSpecEncoded + RETURN_IF_NOT_ADVANCE(CorSigUncompressData(sigCurr, &data)); // GenArgCount + for (uint32_t i = 0; i < data; ++i) + RETURN_IF_NOT_ADVANCE(ReadCorType_CallConv(sigCurr, sigEnd, cxt)); // Type* + break; + case ELEMENT_TYPE_FNPTR: // MethodDefSig | MethodRefSig + RETURN_IF_NOT_ADVANCE(CorSigUncompressData(sigCurr, &data)); // Metadata calling convention + RETURN_IF_NOT_ADVANCE(CorSigUncompressData(sigCurr, &data)); // ParamCount + RETURN_IF_NOT_ADVANCE(ReadCorType_CallConv(sigCurr, sigEnd, cxt)); // RetType + for (uint32_t i = 0; i < data; ++i) + RETURN_IF_NOT_ADVANCE(ReadCorType_CallConv(sigCurr, sigEnd, cxt)); // Type* + break; + case ELEMENT_TYPE_ARRAY: + RETURN_IF_NOT_ADVANCE(ReadCorType_CallConv(sigCurr, sigEnd, cxt)); // Type + RETURN_IF_NOT_ADVANCE(CorSigUncompressData(sigCurr, &data)); // Rank + RETURN_IF_NOT_ADVANCE(CorSigUncompressData(sigCurr, &data)); // NumSizes + for (uint32_t i = 0; i < data; ++i) + RETURN_IF_NOT_ADVANCE(CorSigUncompressData(sigCurr, &tmp)); // Size* + RETURN_IF_NOT_ADVANCE(CorSigUncompressData(sigCurr, &data)); // NumLoBounds + for (uint32_t i = 0; i < data; ++i) + RETURN_IF_NOT_ADVANCE(CorSigUncompressSignedInt(sigCurr, &stmp)); // LoBounds* + break; + case ELEMENT_TYPE_VALUETYPE: + case ELEMENT_TYPE_CLASS: + RETURN_IF_NOT_ADVANCE(CorSigUncompressToken(sigCurr, &tk)); // TypeDefOrRefOrSpecEncoded + break; + case ELEMENT_TYPE_CMOD_REQD: + case ELEMENT_TYPE_CMOD_OPT: + RETURN_IF_NOT_ADVANCE(ReadTypeDefOrRefOrSpecEncodedAsCallConv(sigCurr, cxt)); // TypeDefOrRefOrSpecEncoded + RETURN_IF_NOT_ADVANCE(ReadCorType_CallConv(sigCurr, sigEnd, cxt)); // (Target) Type + break; + default: + break; + } + + if (sigCurr > sigEnd) + return InvalidReadCount; + + return (uint32_t)(sigCurr - sig); +#undef RETURN_IF_NOT_ADVANCE + } +} + +HRESULT STDMETHODCALLTYPE MetadataImportRO::GetNativeCallConvFromSig( + void const* pvSig, + ULONG cbSig, + ULONG* pCallConv) +{ + if (cbSig == 0 || pCallConv == nullptr) + return E_INVALIDARG; + + PCCOR_SIGNATURE sig = (PCCOR_SIGNATURE)pvSig; + PCCOR_SIGNATURE sigEnd = sig + cbSig; + ULONG callConv; // Metadata callconv position value, not the expected return type. + + // Signature processing specified in ECMA-335 sections: + // - II.23.2.1 MethodDefSig + // - II.23.2.2 MethodRefSig + // - II.23.2.3 StandAloneMethodSig - Primary signature containing native calling conventions + // + uint32_t cnt = CorSigUncompressData(sig, &callConv); + if (cnt == InvalidReadCount) + return CORSEC_E_INVALID_IMAGE_FORMAT; + + PCCOR_SIGNATURE sigTypeArgs = sig + cnt; + PCCOR_SIGNATURE sigArgCount = sigTypeArgs; + + // Check for generic signature, defined in II.23.2.1. + if (callConv & IMAGE_CEE_CS_CALLCONV_GENERIC) + { + ULONG typeArgs; + cnt = CorSigUncompressData(sigTypeArgs, &typeArgs); + if (cnt == InvalidReadCount) + return CORSEC_E_INVALID_IMAGE_FORMAT; + sigArgCount = sigTypeArgs + cnt; + } + + // Read in signature arg count. + assert(sigArgCount < sigEnd); + ULONG argCount; + cnt = CorSigUncompressData(sigArgCount, &argCount); + if (cnt == InvalidReadCount) + return CORSEC_E_INVALID_IMAGE_FORMAT; + + ReadSigContext cxt{ _md_ptr.get(), pmCallConvWinapi }; + + PCCOR_SIGNATURE sigRetType = sigArgCount + cnt; + assert(sigRetType < sigEnd); + // Process the return type - II.23.2.11. + cnt = ReadCorType_CallConv(sigRetType, sigEnd, cxt); + if (cnt == InvalidReadCount) + return CORSEC_E_INVALID_IMAGE_FORMAT; + + // Check if calling convention was found. If not + // found, continue looking on the arguments. + if (cnt != 0) + { + PCCOR_SIGNATURE sigArgs = sigRetType + cnt; + PCCOR_SIGNATURE sigCurrArg = sigArgs; + // Process arguments - II.23.2.10. + for (uint32_t i = 0; i < argCount; ++i) + { + assert(sigCurrArg < sigEnd); + + cnt = ReadCorType_CallConv(sigCurrArg, sigEnd, cxt); + if (cnt == InvalidReadCount) + return CORSEC_E_INVALID_IMAGE_FORMAT; + + if (cnt == FoundValue) + goto Done; + + // Move to the next argument + sigCurrArg = sigCurrArg + cnt; + } + } +Done: + *pCallConv = cxt.PinvokeCallConv; + return S_OK; +} + +HRESULT STDMETHODCALLTYPE MetadataImportRO::IsGlobal( + mdToken pd, + int* pbGlobal) +{ + if (IsValidToken(pd) != TRUE) + return E_INVALIDARG; + + mdToken parent; + mdcursor_t cursor; + BOOL result; + switch (TypeFromToken(pd)) + { + case mdtTypeDef: + result = pd == MD_GLOBAL_PARENT_TOKEN ? TRUE : FALSE; + break; + case mdtFieldDef: + case mdtMethodDef: + case mdtEvent: + case mdtProperty: + if (!md_token_to_cursor(_md_ptr.get(), pd, &cursor)) + return CLDB_E_RECORD_NOTFOUND; + if (!md_find_token_of_range_element(cursor, &parent)) + return CLDB_E_FILE_CORRUPT; + result = parent == MD_GLOBAL_PARENT_TOKEN ? TRUE : FALSE; + break; + default: + result = FALSE; + } + + *pbGlobal = result; + return S_OK; +} + +HRESULT STDMETHODCALLTYPE MetadataImportRO::EnumGenericParams( + HCORENUM* phEnum, + mdToken tk, + mdGenericParam rGenericParams[], + ULONG cMax, + ULONG* pcGenericParams) +{ + HRESULT hr; + HCORENUMImpl* enumImpl = ToHCORENUMImpl(*phEnum); + if (enumImpl == nullptr) + { + mdcursor_t cursor; + uint32_t count; + if (!md_create_cursor(_md_ptr.get(), mdtid_GenericParam, &cursor, &count)) + return CLDB_E_RECORD_NOTFOUND; + + RETURN_IF_FAILED(HCORENUMImpl::CreateDynamicEnum(&enumImpl)); + HCORENUMImpl_ptr cleanup{ enumImpl }; + + struct _Finder + { + HCORENUMImpl& EnumImpl; + mdToken Token; + HRESULT hr; + HRESULT Result; // Result of the operation + + bool operator()(mdcursor_t c) + { + (void)md_cursor_to_token(c, &Token); + if (FAILED(hr = HCORENUMImpl::AddToDynamicEnum(EnumImpl, Token))) + { + Result = hr; + return true; + } + + return false; + } + } finder{ *enumImpl, mdTokenNil, S_OK, S_OK }; + + EnumTableRange(cursor, count, mdtGenericParam_Owner, tk, finder); + RETURN_IF_FAILED(finder.Result); + + *phEnum = cleanup.release(); + } + return enumImpl->ReadTokens(rGenericParams, cMax, pcGenericParams); +} + +HRESULT STDMETHODCALLTYPE MetadataImportRO::GetGenericParamProps( + mdGenericParam gp, + ULONG* pulParamSeq, + DWORD* pdwParamFlags, + mdToken* ptOwner, + DWORD* reserved, + _Out_writes_to_opt_(cchName, *pchName) + LPWSTR wzname, + ULONG cchName, + ULONG* pchName) +{ + UNREFERENCED_PARAMETER(reserved); + + mdcursor_t cursor; + if (!md_token_to_cursor(_md_ptr.get(), gp, &cursor)) + return CLDB_E_RECORD_NOTFOUND; + + if (!md_get_column_value_as_token(cursor, mdtGenericParam_Owner, ptOwner)) + return CLDB_E_FILE_CORRUPT; + + uint32_t sequenceNumber; + if (!md_get_column_value_as_constant(cursor, mdtGenericParam_Number, &sequenceNumber)) + return CLDB_E_FILE_CORRUPT; + *pulParamSeq = sequenceNumber; + + uint32_t paramFlags; + if (!md_get_column_value_as_constant(cursor, mdtGenericParam_Flags, ¶mFlags)) + return CLDB_E_FILE_CORRUPT; + *pdwParamFlags = paramFlags; + + char const* name; + if (!md_get_column_value_as_utf8(cursor, mdtGenericParam_Name, &name)) + return CLDB_E_FILE_CORRUPT; + + return ConvertAndReturnStringOutput(name, wzname, cchName, pchName); +} + +HRESULT STDMETHODCALLTYPE MetadataImportRO::GetMethodSpecProps( + mdMethodSpec mi, + mdToken* tkParent, + PCCOR_SIGNATURE* ppvSigBlob, + ULONG* pcbSigBlob) +{ + mdcursor_t cursor; + if (!md_token_to_cursor(_md_ptr.get(), mi, &cursor)) + return CLDB_E_RECORD_NOTFOUND; + + if (!md_get_column_value_as_token(cursor, mdtMethodSpec_Method, tkParent)) + return CLDB_E_FILE_CORRUPT; + + uint8_t const* sig; + uint32_t sigLen; + if (!md_get_column_value_as_blob(cursor, mdtMethodSpec_Instantiation, &sig, &sigLen)) + return CLDB_E_FILE_CORRUPT; + + *ppvSigBlob = (PCCOR_SIGNATURE)sig; + *pcbSigBlob = sigLen; + return S_OK; +} + +HRESULT STDMETHODCALLTYPE MetadataImportRO::EnumGenericParamConstraints( + HCORENUM* phEnum, + mdGenericParam tk, + mdGenericParamConstraint rGenericParamConstraints[], + ULONG cMax, + ULONG* pcGenericParamConstraints) +{ + HRESULT hr; + HCORENUMImpl* enumImpl = ToHCORENUMImpl(*phEnum); + if (enumImpl == nullptr) + { + mdcursor_t cursor; + uint32_t count; + if (!md_create_cursor(_md_ptr.get(), mdtid_GenericParamConstraint, &cursor, &count)) + return CLDB_E_RECORD_NOTFOUND; + + RETURN_IF_FAILED(HCORENUMImpl::CreateDynamicEnum(&enumImpl)); + HCORENUMImpl_ptr cleanup{ enumImpl }; + + struct _Finder + { + HCORENUMImpl& EnumImpl; + mdToken Token; + HRESULT hr; + HRESULT Result; // Result of the operation + + bool operator()(mdcursor_t c) + { + (void)md_cursor_to_token(c, &Token); + if (FAILED(hr = HCORENUMImpl::AddToDynamicEnum(EnumImpl, Token))) + { + Result = hr; + return true; + } + + return false; + } + } finder{ *enumImpl, mdTokenNil, S_OK, S_OK }; + + EnumTableRange(cursor, count, mdtGenericParamConstraint_Owner, tk, finder); + RETURN_IF_FAILED(finder.Result); + + *phEnum = cleanup.release(); + } + return enumImpl->ReadTokens(rGenericParamConstraints, cMax, pcGenericParamConstraints); +} + +HRESULT STDMETHODCALLTYPE MetadataImportRO::GetGenericParamConstraintProps( + mdGenericParamConstraint gpc, + mdGenericParam* ptGenericParam, + mdToken* ptkConstraintType) +{ + mdcursor_t cursor; + if (!md_token_to_cursor(_md_ptr.get(), gpc, &cursor)) + return CLDB_E_RECORD_NOTFOUND; + + if (!md_get_column_value_as_token(cursor, mdtGenericParamConstraint_Owner, ptGenericParam)) + return CLDB_E_FILE_CORRUPT; + + if (!md_get_column_value_as_token(cursor, mdtGenericParamConstraint_Constraint, ptkConstraintType)) + return CLDB_E_FILE_CORRUPT; + + return S_OK; +} + +HRESULT STDMETHODCALLTYPE MetadataImportRO::GetPEKind( + DWORD* pdwPEKind, + DWORD* pdwMAchine) +{ + UNREFERENCED_PARAMETER(pdwPEKind); + UNREFERENCED_PARAMETER(pdwMAchine); + + // Requires PE data to be available. + // This implementation only has the metadata tables. + // It does not have any information about the PE envelope. + return E_NOTIMPL; +} + +HRESULT STDMETHODCALLTYPE MetadataImportRO::GetVersionString( + _Out_writes_to_opt_(ccBufSize, *pccBufSize) + LPWSTR pwzBuf, + DWORD ccBufSize, + DWORD* pccBufSize) +{ + char const* versionString = md_get_version_string(_md_ptr.get()); + if (versionString == nullptr) + versionString = ""; + return ConvertAndReturnStringOutput(versionString, pwzBuf, ccBufSize, pccBufSize); +} + +HRESULT STDMETHODCALLTYPE MetadataImportRO::EnumMethodSpecs( + HCORENUM* phEnum, + mdToken tk, + mdMethodSpec rMethodSpecs[], + ULONG cMax, + ULONG* pcMethodSpecs) +{ + HRESULT hr; + HCORENUMImpl* enumImpl = ToHCORENUMImpl(*phEnum); + if (enumImpl == nullptr) + { + mdcursor_t cursor; + uint32_t count; + if (!md_create_cursor(_md_ptr.get(), mdtid_MethodSpec, &cursor, &count)) + return CLDB_E_RECORD_NOTFOUND; + + RETURN_IF_FAILED(HCORENUMImpl::CreateDynamicEnum(&enumImpl)); + HCORENUMImpl_ptr cleanup{ enumImpl }; + + struct _Finder + { + HCORENUMImpl& EnumImpl; + mdToken Token; + HRESULT hr; + HRESULT Result; // Result of the operation + + bool operator()(mdcursor_t c) + { + (void)md_cursor_to_token(c, &Token); + if (FAILED(hr = HCORENUMImpl::AddToDynamicEnum(EnumImpl, Token))) + { + Result = hr; + return true; + } + + return false; + } + } finder{ *enumImpl, mdTokenNil, S_OK, S_OK }; + + EnumTableRange(cursor, count, mdtMethodSpec_Method, tk, finder); + RETURN_IF_FAILED(finder.Result); + + *phEnum = cleanup.release(); + } + return enumImpl->ReadTokens(rMethodSpecs, cMax, pcMethodSpecs); +} + +HRESULT STDMETHODCALLTYPE MetadataImportRO::GetAssemblyProps( + mdAssembly mda, + void const **ppbPublicKey, + ULONG *pcbPublicKey, + ULONG *pulHashAlgId, + _Out_writes_to_opt_(cchName, *pchName) + LPWSTR szName, + ULONG cchName, + ULONG *pchName, + ASSEMBLYMETADATA *pMetaData, + DWORD *pdwAssemblyFlags) +{ + mdcursor_t cursor; + + if (!md_token_to_cursor(_md_ptr.get(), mda, &cursor)) + return CLDB_E_RECORD_NOTFOUND; + + if (ppbPublicKey != nullptr) + { + uint8_t const* publicKey; + uint32_t publicKeySize; + if (!md_get_column_value_as_blob(cursor, mdtAssembly_PublicKey, &publicKey, &publicKeySize)) + return CLDB_E_FILE_CORRUPT; + + *ppbPublicKey = publicKey; + *pcbPublicKey = publicKeySize; + } + + if (pulHashAlgId != nullptr) + { + uint32_t hashAlgId; + if (!md_get_column_value_as_constant(cursor, mdtAssembly_HashAlgId, &hashAlgId)) + return CLDB_E_FILE_CORRUPT; + *pulHashAlgId = hashAlgId; + } + + if (pMetaData != nullptr) + { + uint32_t majorVersion; + uint32_t minorVersion; + uint32_t buildNumber; + uint32_t patchNumber; + if (!md_get_column_value_as_constant(cursor, mdtAssembly_MajorVersion, &majorVersion)) + return CLDB_E_FILE_CORRUPT; + + if (!md_get_column_value_as_constant(cursor, mdtAssembly_MinorVersion, &minorVersion)) + return CLDB_E_FILE_CORRUPT; + + if (!md_get_column_value_as_constant(cursor, mdtAssembly_BuildNumber, &buildNumber)) + return CLDB_E_FILE_CORRUPT; + + if (!md_get_column_value_as_constant(cursor, mdtAssembly_RevisionNumber, &patchNumber)) + return CLDB_E_FILE_CORRUPT; + + pMetaData->usMajorVersion = static_cast(majorVersion); + pMetaData->usMinorVersion = static_cast(minorVersion); + pMetaData->usBuildNumber = static_cast(buildNumber); + pMetaData->usRevisionNumber = static_cast(patchNumber); + + char const* culture; + if (!md_get_column_value_as_utf8(cursor, mdtAssembly_Culture, &culture)) + return CLDB_E_FILE_CORRUPT; + + HRESULT hr; + RETURN_IF_FAILED(ConvertAndReturnStringOutput(culture, pMetaData->szLocale, pMetaData->cbLocale, &pMetaData->cbLocale)); + + // We do not read the AssemblyOS or AssemblyProcessor tables to fill these arrays since neither .NET Framework or CoreCLR do. + pMetaData->ulProcessor = 0; + pMetaData->ulOS = 0; + } + + if (pdwAssemblyFlags != nullptr) + { + uint32_t assemblyFlags; + if (!md_get_column_value_as_constant(cursor, mdtAssembly_Flags, &assemblyFlags)) + return CLDB_E_FILE_CORRUPT; + + uint8_t const* publicKey; + uint32_t publicKeySize; + if (!md_get_column_value_as_blob(cursor, mdtAssembly_PublicKey, &publicKey, &publicKeySize)) + return CLDB_E_FILE_CORRUPT; + if (publicKeySize != 0) + assemblyFlags |= afPublicKey; + + *pdwAssemblyFlags = assemblyFlags; + } + + if (szName != nullptr || pchName != nullptr) + { + char const* name; + if (!md_get_column_value_as_utf8(cursor, mdtAssembly_Name, &name)) + return CLDB_E_FILE_CORRUPT; + + return ConvertAndReturnStringOutput(name, szName, cchName, pchName); + } + return S_OK; +} + +HRESULT STDMETHODCALLTYPE MetadataImportRO::GetAssemblyRefProps( + mdAssemblyRef mdar, + void const **ppbPublicKeyOrToken, + ULONG *pcbPublicKeyOrToken, + _Out_writes_to_opt_(cchName, *pchName) + LPWSTR szName, + ULONG cchName, + ULONG *pchName, + ASSEMBLYMETADATA *pMetaData, + void const **ppbHashValue, + ULONG *pcbHashValue, + DWORD *pdwAssemblyRefFlags) +{ + mdcursor_t cursor; + + if (!md_token_to_cursor(_md_ptr.get(), mdar, &cursor)) + return CLDB_E_RECORD_NOTFOUND; + + if (ppbPublicKeyOrToken != nullptr) + { + uint8_t const* publicKeyOrToken; + uint32_t publicKeyOrTokenSize; + if (!md_get_column_value_as_blob(cursor, mdtAssemblyRef_PublicKeyOrToken, &publicKeyOrToken, &publicKeyOrTokenSize)) + return CLDB_E_FILE_CORRUPT; + + *ppbPublicKeyOrToken = publicKeyOrToken; + *pcbPublicKeyOrToken = publicKeyOrTokenSize; + } + + if (ppbHashValue != nullptr) + { + uint8_t const* hashValue; + uint32_t hashValueSize; + if (!md_get_column_value_as_blob(cursor, mdtAssemblyRef_HashValue, &hashValue, &hashValueSize)) + return CLDB_E_FILE_CORRUPT; + *ppbHashValue = hashValue; + *pcbHashValue = hashValueSize; + } + + if (pMetaData != nullptr) + { + uint32_t majorVersion; + uint32_t minorVersion; + uint32_t buildNumber; + uint32_t patchNumber; + if (!md_get_column_value_as_constant(cursor, mdtAssemblyRef_MajorVersion, &majorVersion)) + return CLDB_E_FILE_CORRUPT; + + if (!md_get_column_value_as_constant(cursor, mdtAssemblyRef_MinorVersion, &minorVersion)) + return CLDB_E_FILE_CORRUPT; + + if (!md_get_column_value_as_constant(cursor, mdtAssemblyRef_BuildNumber, &buildNumber)) + return CLDB_E_FILE_CORRUPT; + + if (!md_get_column_value_as_constant(cursor, mdtAssemblyRef_RevisionNumber, &patchNumber)) + return CLDB_E_FILE_CORRUPT; + + pMetaData->usMajorVersion = static_cast(majorVersion); + pMetaData->usMinorVersion = static_cast(minorVersion); + pMetaData->usBuildNumber = static_cast(buildNumber); + pMetaData->usRevisionNumber = static_cast(patchNumber); + + char const* culture; + if (!md_get_column_value_as_utf8(cursor, mdtAssemblyRef_Culture, &culture)) + return CLDB_E_FILE_CORRUPT; + + HRESULT hr; + RETURN_IF_FAILED(ConvertAndReturnStringOutput(culture, pMetaData->szLocale, pMetaData->cbLocale, &pMetaData->cbLocale)); + + // We do not read the AssemblyRefOS or AssemblyRefProcessor tables to fill these arrays since neither .NET Framework or CoreCLR do. + pMetaData->ulProcessor = 0; + pMetaData->ulOS = 0; + } + + if (pdwAssemblyRefFlags != nullptr) + { + uint32_t assemblyRefFlags; + if (!md_get_column_value_as_constant(cursor, mdtAssemblyRef_Flags, &assemblyRefFlags)) + return CLDB_E_FILE_CORRUPT; + + *pdwAssemblyRefFlags = assemblyRefFlags; + } + + if (szName != nullptr || pchName != nullptr) + { + char const* name; + if (!md_get_column_value_as_utf8(cursor, mdtAssemblyRef_Name, &name)) + return CLDB_E_FILE_CORRUPT; + + return ConvertAndReturnStringOutput(name, szName, cchName, pchName); + } + return S_OK; +} + +HRESULT STDMETHODCALLTYPE MetadataImportRO::GetFileProps( + mdFile mdf, + _Out_writes_to_opt_(cchName, *pchName) + LPWSTR szName, + ULONG cchName, + ULONG *pchName, + void const **ppbHashValue, + ULONG *pcbHashValue, + DWORD *pdwFileFlags) +{ + mdcursor_t cursor; + + if (!md_token_to_cursor(_md_ptr.get(), mdf, &cursor)) + return CLDB_E_RECORD_NOTFOUND; + + if (ppbHashValue != nullptr) + { + uint8_t const* hashValue; + uint32_t hashValueSize; + if (!md_get_column_value_as_blob(cursor, mdtFile_HashValue, &hashValue, &hashValueSize)) + return CLDB_E_FILE_CORRUPT; + *ppbHashValue = hashValue; + *pcbHashValue = hashValueSize; + } + + if (pdwFileFlags != nullptr) + { + uint32_t fileFlags; + if (!md_get_column_value_as_constant(cursor, mdtFile_Flags, &fileFlags)) + return CLDB_E_FILE_CORRUPT; + + *pdwFileFlags = fileFlags; + } + + if (szName != nullptr || pchName != nullptr) + { + char const* name; + if (!md_get_column_value_as_utf8(cursor, mdtFile_Name, &name)) + return CLDB_E_FILE_CORRUPT; + + return ConvertAndReturnStringOutput(name, szName, cchName, pchName); + } + return S_OK; +} + +HRESULT STDMETHODCALLTYPE MetadataImportRO::GetExportedTypeProps( + mdExportedType mdct, + _Out_writes_to_opt_(cchName, *pchName) + LPWSTR szName, + ULONG cchName, + ULONG *pchName, + mdToken *ptkImplementation, + mdTypeDef *ptkTypeDef, + DWORD *pdwExportedTypeFlags) +{ + mdcursor_t cursor; + + if (!md_token_to_cursor(_md_ptr.get(), mdct, &cursor)) + return CLDB_E_RECORD_NOTFOUND; + + if (ptkImplementation != nullptr) + { + if (!md_get_column_value_as_token(cursor, mdtExportedType_Implementation, ptkImplementation)) + return CLDB_E_FILE_CORRUPT; + } + + if (ptkTypeDef != nullptr) + { + // This column points into the TypeDef table of another module, + // so it isn't a true column reference here. + if (!md_get_column_value_as_constant(cursor, mdtExportedType_TypeDefId, ptkTypeDef)) + return CLDB_E_FILE_CORRUPT; + } + + if (pdwExportedTypeFlags != nullptr) + { + uint32_t exportedTypeFlags; + if (!md_get_column_value_as_constant(cursor, mdtExportedType_Flags, &exportedTypeFlags)) + return CLDB_E_FILE_CORRUPT; + + *pdwExportedTypeFlags = exportedTypeFlags; + } + + if (szName != nullptr || pchName != nullptr) + { + char const* name; + if (!md_get_column_value_as_utf8(cursor, mdtExportedType_TypeName, &name)) + return CLDB_E_FILE_CORRUPT; + + char const* nspace; + if (!md_get_column_value_as_utf8(cursor, mdtExportedType_TypeNamespace, &nspace)) + return CLDB_E_FILE_CORRUPT; + + HRESULT hr; + malloc_ptr mem; + RETURN_IF_FAILED(ConstructTypeName(nspace, name, mem)); + + return ConvertAndReturnStringOutput((char*)mem.get(), szName, cchName, pchName); + } + return S_OK; +} + +HRESULT STDMETHODCALLTYPE MetadataImportRO::GetManifestResourceProps( + mdManifestResource mdmr, + _Out_writes_to_opt_(cchName, *pchName) + LPWSTR szName, + ULONG cchName, + ULONG *pchName, + mdToken *ptkImplementation, + DWORD *pdwOffset, + DWORD *pdwResourceFlags) +{ + mdcursor_t cursor; + + if (!md_token_to_cursor(_md_ptr.get(), mdmr, &cursor)) + return CLDB_E_RECORD_NOTFOUND; + + if (ptkImplementation != nullptr) + { + if (!md_get_column_value_as_token(cursor, mdtManifestResource_Implementation, ptkImplementation)) + return CLDB_E_FILE_CORRUPT; + } + + if (pdwOffset != nullptr) + { + uint32_t offset; + if (!md_get_column_value_as_constant(cursor, mdtManifestResource_Offset, &offset)) + return CLDB_E_FILE_CORRUPT; + + *pdwOffset = offset; + } + + if (pdwResourceFlags != nullptr) + { + uint32_t resourceFlags; + if (!md_get_column_value_as_constant(cursor, mdtManifestResource_Flags, &resourceFlags)) + return CLDB_E_FILE_CORRUPT; + + *pdwResourceFlags = resourceFlags; + } + if (szName != nullptr || pchName != nullptr) + { + char const* name; + if (!md_get_column_value_as_utf8(cursor, mdtManifestResource_Name, &name)) + return CLDB_E_FILE_CORRUPT; + + return ConvertAndReturnStringOutput(name, szName, cchName, pchName); + } + return S_OK; +} + +HRESULT STDMETHODCALLTYPE MetadataImportRO::EnumAssemblyRefs( + HCORENUM *phEnum, + mdAssemblyRef rAssemblyRefs[], + ULONG cMax, + ULONG *pcTokens) +{ + HRESULT hr; + HCORENUMImpl* enumImpl = ToHCORENUMImpl(*phEnum); + if (enumImpl == nullptr) + { + RETURN_IF_FAILED(CreateEnumTokens(_md_ptr.get(), mdtid_AssemblyRef, &enumImpl)); + *phEnum = enumImpl; + } + return enumImpl->ReadTokens(rAssemblyRefs, cMax, pcTokens); +} + +HRESULT STDMETHODCALLTYPE MetadataImportRO::EnumFiles( + HCORENUM *phEnum, + mdFile rFiles[], + ULONG cMax, + ULONG *pcTokens) +{ + HRESULT hr; + HCORENUMImpl* enumImpl = ToHCORENUMImpl(*phEnum); + if (enumImpl == nullptr) + { + RETURN_IF_FAILED(CreateEnumTokens(_md_ptr.get(), mdtid_File, &enumImpl)); + *phEnum = enumImpl; + } + return enumImpl->ReadTokens(rFiles, cMax, pcTokens); +} + +HRESULT STDMETHODCALLTYPE MetadataImportRO::EnumExportedTypes( + HCORENUM *phEnum, + mdExportedType rExportedTypes[], + ULONG cMax, + ULONG *pcTokens) +{ + HRESULT hr; + HCORENUMImpl* enumImpl = ToHCORENUMImpl(*phEnum); + if (enumImpl == nullptr) + { + RETURN_IF_FAILED(CreateEnumTokens(_md_ptr.get(), mdtid_ExportedType, &enumImpl)); + *phEnum = enumImpl; + } + return enumImpl->ReadTokens(rExportedTypes, cMax, pcTokens); +} + +HRESULT STDMETHODCALLTYPE MetadataImportRO::EnumManifestResources( + HCORENUM *phEnum, + mdManifestResource rManifestResources[], + ULONG cMax, + ULONG *pcTokens) +{ + HRESULT hr; + HCORENUMImpl* enumImpl = ToHCORENUMImpl(*phEnum); + if (enumImpl == nullptr) + { + RETURN_IF_FAILED(CreateEnumTokens(_md_ptr.get(), mdtid_ManifestResource, &enumImpl)); + *phEnum = enumImpl; + } + return enumImpl->ReadTokens(rManifestResources, cMax, pcTokens); +} + +HRESULT STDMETHODCALLTYPE MetadataImportRO::GetAssemblyFromScope( + mdAssembly *ptkAssembly) +{ + mdcursor_t cursor; + uint32_t count; + if (!md_create_cursor(_md_ptr.get(), mdtid_Assembly, &cursor, &count)) + return CLDB_E_RECORD_NOTFOUND; + if (!md_cursor_to_token(cursor, ptkAssembly)) + return CLDB_E_FILE_CORRUPT; + return S_OK; +} + +HRESULT STDMETHODCALLTYPE MetadataImportRO::FindExportedTypeByName( + LPCWSTR szName, + mdToken mdtExportedType, + mdExportedType *ptkExportedType) +{ + if (szName == nullptr) + return E_INVALIDARG; + + mdcursor_t cursor; + uint32_t count; + if (!md_create_cursor(_md_ptr.get(), mdtid_ExportedType, &cursor, &count)) + return CLDB_E_RECORD_NOTFOUND; + + pal::StringConvert cvt{ szName }; + if (!cvt.Success()) + return E_INVALIDARG; + char const* nspace; + char const* name; + SplitTypeName(cvt, &nspace, &name); + + for (uint32_t i = 0; i < count; md_cursor_next(&cursor), i++) + { + mdToken implementation; + if (!md_get_column_value_as_token(cursor, mdtExportedType_Implementation, &implementation)) + return CLDB_E_FILE_CORRUPT; + + // Handle the case of nested vs. non-nested classes + if (TypeFromToken(implementation) == CorTokenType::mdtExportedType && !IsNilToken(implementation)) + { + // Current ExportedType being looked at is a nested type, so + // comparing the implementation token. + if (implementation != mdtExportedType) + continue; + } + else if (TypeFromToken(mdtExportedType) == mdtExportedType + && !IsNilToken(mdtExportedType)) + { + // ExportedType passed in is nested but the current ExportedType is not. + continue; + } + + char const* recordNspace; + if (!md_get_column_value_as_utf8(cursor, mdtExportedType_TypeNamespace, &recordNspace)) + return CLDB_E_FILE_CORRUPT; + + if (::strcmp(nspace, recordNspace) != 0) + continue; + + char const* recordName; + if (!md_get_column_value_as_utf8(cursor, mdtExportedType_TypeName, &recordName)) + return CLDB_E_FILE_CORRUPT; + + if (::strcmp(name, recordName) != 0) + continue; + + if (!md_cursor_to_token(cursor, ptkExportedType)) + return CLDB_E_FILE_CORRUPT; + return S_OK; + } + return CLDB_E_RECORD_NOTFOUND; +} + +HRESULT STDMETHODCALLTYPE MetadataImportRO::FindManifestResourceByName( + LPCWSTR szName, + mdManifestResource *ptkManifestResource) +{ + if (szName == nullptr) + return E_INVALIDARG; + + mdcursor_t cursor; + uint32_t count; + if (!md_create_cursor(_md_ptr.get(), mdtid_ManifestResource, &cursor, &count)) + return CLDB_E_RECORD_NOTFOUND; + + pal::StringConvert cvt{ szName }; + if (!cvt.Success()) + return E_INVALIDARG; + + for (uint32_t i = 0; i < count; md_cursor_next(&cursor), i++) + { + mdManifestResource token; + if (!md_cursor_to_token(cursor, &token)) + return CLDB_E_FILE_CORRUPT; + + char const* name; + if (!md_get_column_value_as_utf8(cursor, mdtManifestResource_Name, &name)) + return CLDB_E_FILE_CORRUPT; + + if (::strncmp(name, cvt, cvt.Length()) == 0) + { + *ptkManifestResource = token; + return S_OK; + } + } + return CLDB_E_RECORD_NOTFOUND; +} + +HRESULT STDMETHODCALLTYPE MetadataImportRO::FindAssembliesByName( + LPCWSTR szAppBase, + LPCWSTR szPrivateBin, + LPCWSTR szAssemblyName, + IUnknown *ppIUnk[], + ULONG cMax, + ULONG *pcAssemblies) +{ + UNREFERENCED_PARAMETER(szAppBase); + UNREFERENCED_PARAMETER(szPrivateBin); + UNREFERENCED_PARAMETER(szAssemblyName); + UNREFERENCED_PARAMETER(ppIUnk); + UNREFERENCED_PARAMETER(cMax); + UNREFERENCED_PARAMETER(pcAssemblies); + // Requires VM knowledge and is only supported in .NET Framework. + return E_NOTIMPL; +} diff --git a/src/native/dnmd/src/interfaces/metadataimportro.hpp b/src/native/dnmd/src/interfaces/metadataimportro.hpp new file mode 100644 index 0000000000000..2a7dc1ba50cec --- /dev/null +++ b/src/native/dnmd/src/interfaces/metadataimportro.hpp @@ -0,0 +1,613 @@ +#ifndef _SRC_INTERFACES_METADATAIMPORTRO_HPP_ +#define _SRC_INTERFACES_METADATAIMPORTRO_HPP_ + +#include +#include "tearoffbase.hpp" +#include "controllingiunknown.hpp" +#include "dnmdowner.hpp" + +#include +#include + +#include + +class MetadataImportRO final : public TearOffBase +{ + mdhandle_view _md_ptr; + +protected: + virtual bool TryGetInterfaceOnThis(REFIID riid, void** ppvObject) override + { + assert(riid != IID_IUnknown); + if (riid == IID_IMetaDataImport || riid == IID_IMetaDataImport2) + { + *ppvObject = static_cast(this); + return true; + } + if (riid == IID_IMetaDataAssemblyImport) + { + *ppvObject = static_cast(this); + return true; + } + return false; + } + +public: + MetadataImportRO(IUnknown* controllingUnknown, mdhandle_view md_ptr) + : TearOffBase(controllingUnknown) + , _md_ptr{ md_ptr } + { } + + virtual ~MetadataImportRO() = default; + + mdhandle_t MetaData(); + +public: // IMetaDataImport + STDMETHOD_(void, CloseEnum)(HCORENUM hEnum) override; + STDMETHOD(CountEnum)(HCORENUM hEnum, ULONG *pulCount) override; + STDMETHOD(ResetEnum)(HCORENUM hEnum, ULONG ulPos) override; + STDMETHOD(EnumTypeDefs)(HCORENUM *phEnum, mdTypeDef rTypeDefs[], + ULONG cMax, ULONG *pcTypeDefs) override; + STDMETHOD(EnumInterfaceImpls)(HCORENUM *phEnum, mdTypeDef td, + mdInterfaceImpl rImpls[], ULONG cMax, + ULONG* pcImpls) override; + STDMETHOD(EnumTypeRefs)(HCORENUM *phEnum, mdTypeRef rTypeRefs[], + ULONG cMax, ULONG* pcTypeRefs) override; + + STDMETHOD(FindTypeDefByName)( + LPCWSTR szTypeDef, + mdToken tkEnclosingClass, + mdTypeDef *ptd) override; + + STDMETHOD(GetScopeProps)( + _Out_writes_to_opt_(cchName, *pchName) + LPWSTR szName, + ULONG cchName, + ULONG *pchName, + GUID *pmvid) override; + + STDMETHOD(GetModuleFromScope)( + mdModule *pmd) override; + + STDMETHOD(GetTypeDefProps)( + mdTypeDef td, + _Out_writes_to_opt_(cchTypeDef, *pchTypeDef) + LPWSTR szTypeDef, + ULONG cchTypeDef, + ULONG *pchTypeDef, + DWORD *pdwTypeDefFlags, + mdToken *ptkExtends) override; + + STDMETHOD(GetInterfaceImplProps)( + mdInterfaceImpl iiImpl, + mdTypeDef *pClass, + mdToken *ptkIface) override; + + STDMETHOD(GetTypeRefProps)( + mdTypeRef tr, + mdToken *ptkResolutionScope, + _Out_writes_to_opt_(cchName, *pchName) + LPWSTR szName, + ULONG cchName, + ULONG *pchName) override; + + STDMETHOD(ResolveTypeRef)(mdTypeRef tr, REFIID riid, IUnknown **ppIScope, mdTypeDef *ptd) override; + + STDMETHOD(EnumMembers)( + HCORENUM *phEnum, + mdTypeDef cl, + mdToken rMembers[], + ULONG cMax, + ULONG *pcTokens) override; + + STDMETHOD(EnumMembersWithName)( + HCORENUM *phEnum, + mdTypeDef cl, + LPCWSTR szName, + mdToken rMembers[], + ULONG cMax, + ULONG *pcTokens) override; + + STDMETHOD(EnumMethods)( + HCORENUM *phEnum, + mdTypeDef cl, + mdMethodDef rMethods[], + ULONG cMax, + ULONG *pcTokens) override; + + STDMETHOD(EnumMethodsWithName)( + HCORENUM *phEnum, + mdTypeDef cl, + LPCWSTR szName, + mdMethodDef rMethods[], + ULONG cMax, + ULONG *pcTokens) override; + + STDMETHOD(EnumFields)( + HCORENUM *phEnum, + mdTypeDef cl, + mdFieldDef rFields[], + ULONG cMax, + ULONG *pcTokens) override; + + STDMETHOD(EnumFieldsWithName)( + HCORENUM *phEnum, + mdTypeDef cl, + LPCWSTR szName, + mdFieldDef rFields[], + ULONG cMax, + ULONG *pcTokens) override; + + + STDMETHOD(EnumParams)( + HCORENUM *phEnum, + mdMethodDef mb, + mdParamDef rParams[], + ULONG cMax, + ULONG *pcTokens) override; + + STDMETHOD(EnumMemberRefs)( + HCORENUM *phEnum, + mdToken tkParent, + mdMemberRef rMemberRefs[], + ULONG cMax, + ULONG *pcTokens) override; + + STDMETHOD(EnumMethodImpls)( + HCORENUM *phEnum, + mdTypeDef td, + mdToken rMethodBody[], + mdToken rMethodDecl[], + ULONG cMax, + ULONG *pcTokens) override; + + STDMETHOD(EnumPermissionSets)( + HCORENUM *phEnum, + mdToken tk, + DWORD dwActions, + mdPermission rPermission[], + ULONG cMax, + ULONG *pcTokens) override; + + STDMETHOD(FindMember)( + mdTypeDef td, + LPCWSTR szName, + PCCOR_SIGNATURE pvSigBlob, + ULONG cbSigBlob, + mdToken *pmb) override; + + STDMETHOD(FindMethod)( + mdTypeDef td, + LPCWSTR szName, + PCCOR_SIGNATURE pvSigBlob, + ULONG cbSigBlob, + mdMethodDef *pmb) override; + + STDMETHOD(FindField)( + mdTypeDef td, + LPCWSTR szName, + PCCOR_SIGNATURE pvSigBlob, + ULONG cbSigBlob, + mdFieldDef *pmb) override; + + STDMETHOD(FindMemberRef)( + mdTypeRef td, + LPCWSTR szName, + PCCOR_SIGNATURE pvSigBlob, + ULONG cbSigBlob, + mdMemberRef *pmr) override; + + STDMETHOD (GetMethodProps)( + mdMethodDef mb, + mdTypeDef *pClass, + _Out_writes_to_opt_(cchMethod, *pchMethod) + LPWSTR szMethod, + ULONG cchMethod, + ULONG *pchMethod, + DWORD *pdwAttr, + PCCOR_SIGNATURE *ppvSigBlob, + ULONG *pcbSigBlob, + ULONG *pulCodeRVA, + DWORD *pdwImplFlags) override; + + STDMETHOD(GetMemberRefProps)( + mdMemberRef mr, + mdToken *ptk, + _Out_writes_to_opt_(cchMember, *pchMember) + LPWSTR szMember, + ULONG cchMember, + ULONG *pchMember, + PCCOR_SIGNATURE *ppvSigBlob, + ULONG *pbSig) override; + + STDMETHOD(EnumProperties)( + HCORENUM *phEnum, + mdTypeDef td, + mdProperty rProperties[], + ULONG cMax, + ULONG *pcProperties) override; + + STDMETHOD(EnumEvents)( + HCORENUM *phEnum, + mdTypeDef td, + mdEvent rEvents[], + ULONG cMax, + ULONG *pcEvents) override; + + STDMETHOD(GetEventProps)( + mdEvent ev, + mdTypeDef *pClass, + LPCWSTR szEvent, + ULONG cchEvent, + ULONG *pchEvent, + DWORD *pdwEventFlags, + mdToken *ptkEventType, + mdMethodDef *pmdAddOn, + mdMethodDef *pmdRemoveOn, + mdMethodDef *pmdFire, + mdMethodDef rmdOtherMethod[], + ULONG cMax, + ULONG *pcOtherMethod) override; + + STDMETHOD(EnumMethodSemantics)( + HCORENUM *phEnum, + mdMethodDef mb, + mdToken rEventProp[], + ULONG cMax, + ULONG *pcEventProp) override; + + STDMETHOD(GetMethodSemantics)( + mdMethodDef mb, + mdToken tkEventProp, + DWORD *pdwSemanticsFlags) override; + + STDMETHOD(GetClassLayout) ( + mdTypeDef td, + DWORD *pdwPackSize, + COR_FIELD_OFFSET rFieldOffset[], + ULONG cMax, + ULONG *pcFieldOffset, + ULONG *pulClassSize) override; + + STDMETHOD(GetFieldMarshal) ( + mdToken tk, + PCCOR_SIGNATURE *ppvNativeType, + ULONG *pcbNativeType) override; + + STDMETHOD(GetRVA)( + mdToken tk, + ULONG *pulCodeRVA, + DWORD *pdwImplFlags) override; + + STDMETHOD(GetPermissionSetProps) ( + mdPermission pm, + DWORD *pdwAction, + void const **ppvPermission, + ULONG *pcbPermission) override; + + STDMETHOD(GetSigFromToken)( + mdSignature mdSig, + PCCOR_SIGNATURE *ppvSig, + ULONG *pcbSig) override; + + STDMETHOD(GetModuleRefProps)( + mdModuleRef mur, + _Out_writes_to_opt_(cchName, *pchName) + LPWSTR szName, + ULONG cchName, + ULONG *pchName) override; + + STDMETHOD(EnumModuleRefs)( + HCORENUM *phEnum, + mdModuleRef rModuleRefs[], + ULONG cMax, + ULONG *pcModuleRefs) override; + + STDMETHOD(GetTypeSpecFromToken)( + mdTypeSpec typespec, + PCCOR_SIGNATURE *ppvSig, + ULONG *pcbSig) override; + + STDMETHOD(GetNameFromToken)( // Not Recommended! May be removed! + mdToken tk, + MDUTF8CSTR *pszUtf8NamePtr) override; + + STDMETHOD(EnumUnresolvedMethods)( + HCORENUM *phEnum, + mdToken rMethods[], + ULONG cMax, + ULONG *pcTokens) override; + + STDMETHOD(GetUserString)( + mdString stk, + _Out_writes_to_opt_(cchString, *pchString) + LPWSTR szString, + ULONG cchString, + ULONG *pchString) override; + + STDMETHOD(GetPinvokeMap)( + mdToken tk, + DWORD *pdwMappingFlags, + _Out_writes_to_opt_(cchImportName, *pchImportName) + LPWSTR szImportName, + ULONG cchImportName, + ULONG *pchImportName, + mdModuleRef *pmrImportDLL) override; + + STDMETHOD(EnumSignatures)( + HCORENUM *phEnum, + mdSignature rSignatures[], + ULONG cMax, + ULONG *pcSignatures) override; + + STDMETHOD(EnumTypeSpecs)( + HCORENUM *phEnum, + mdTypeSpec rTypeSpecs[], + ULONG cMax, + ULONG *pcTypeSpecs) override; + + STDMETHOD(EnumUserStrings)( + HCORENUM *phEnum, + mdString rStrings[], + ULONG cMax, + ULONG *pcStrings) override; + + STDMETHOD(GetParamForMethodIndex)( + mdMethodDef md, + ULONG ulParamSeq, + mdParamDef *ppd) override; + + STDMETHOD(EnumCustomAttributes)( + HCORENUM *phEnum, + mdToken tk, + mdToken tkType, + mdCustomAttribute rCustomAttributes[], + ULONG cMax, + ULONG *pcCustomAttributes) override; + + STDMETHOD(GetCustomAttributeProps)( + mdCustomAttribute cv, + mdToken *ptkObj, + mdToken *ptkType, + void const **ppBlob, + ULONG *pcbSize) override; + + STDMETHOD(FindTypeRef)( + mdToken tkResolutionScope, + LPCWSTR szName, + mdTypeRef *ptr) override; + + STDMETHOD(GetMemberProps)( + mdToken mb, + mdTypeDef *pClass, + _Out_writes_to_opt_(cchMember, *pchMember) + LPWSTR szMember, + ULONG cchMember, + ULONG *pchMember, + DWORD *pdwAttr, + PCCOR_SIGNATURE *ppvSigBlob, + ULONG *pcbSigBlob, + ULONG *pulCodeRVA, + DWORD *pdwImplFlags, + DWORD *pdwCPlusTypeFlag, + UVCP_CONSTANT *ppValue, + ULONG *pcchValue) override; + + STDMETHOD(GetFieldProps)( + mdFieldDef mb, + mdTypeDef *pClass, + _Out_writes_to_opt_(cchField, *pchField) + LPWSTR szField, + ULONG cchField, + ULONG *pchField, + DWORD *pdwAttr, + PCCOR_SIGNATURE *ppvSigBlob, + ULONG *pcbSigBlob, + DWORD *pdwCPlusTypeFlag, + UVCP_CONSTANT *ppValue, + ULONG *pcchValue) override; + + STDMETHOD(GetPropertyProps)( + mdProperty prop, + mdTypeDef *pClass, + LPCWSTR szProperty, + ULONG cchProperty, + ULONG *pchProperty, + DWORD *pdwPropFlags, + PCCOR_SIGNATURE *ppvSig, + ULONG *pbSig, + DWORD *pdwCPlusTypeFlag, + UVCP_CONSTANT *ppDefaultValue, + ULONG *pcchDefaultValue, + mdMethodDef *pmdSetter, + mdMethodDef *pmdGetter, + mdMethodDef rmdOtherMethod[], + ULONG cMax, + ULONG *pcOtherMethod) override; + + STDMETHOD(GetParamProps)( + mdParamDef tk, + mdMethodDef *pmd, + ULONG *pulSequence, + _Out_writes_to_opt_(cchName, *pchName) + LPWSTR szName, + ULONG cchName, + ULONG *pchName, + DWORD *pdwAttr, + DWORD *pdwCPlusTypeFlag, + UVCP_CONSTANT *ppValue, + ULONG *pcchValue) override; + + STDMETHOD(GetCustomAttributeByName)( + mdToken tkObj, + LPCWSTR szName, + void const** ppData, + ULONG *pcbData) override; + + STDMETHOD_(BOOL, IsValidToken)( + mdToken tk) override; + + STDMETHOD(GetNestedClassProps)( + mdTypeDef tdNestedClass, + mdTypeDef *ptdEnclosingClass) override; + + STDMETHOD(GetNativeCallConvFromSig)( + void const *pvSig, + ULONG cbSig, + ULONG *pCallConv) override; + + STDMETHOD(IsGlobal)( + mdToken pd, + int *pbGlobal) override; + +public: // IMetaDataImport2 + STDMETHOD(EnumGenericParams)( + HCORENUM *phEnum, + mdToken tk, + mdGenericParam rGenericParams[], + ULONG cMax, + ULONG *pcGenericParams) override; + + STDMETHOD(GetGenericParamProps)( + mdGenericParam gp, + ULONG *pulParamSeq, + DWORD *pdwParamFlags, + mdToken *ptOwner, + DWORD *reserved, + _Out_writes_to_opt_(cchName, *pchName) + LPWSTR wzname, + ULONG cchName, + ULONG *pchName) override; + + STDMETHOD(GetMethodSpecProps)( + mdMethodSpec mi, + mdToken *tkParent, + PCCOR_SIGNATURE *ppvSigBlob, + ULONG *pcbSigBlob) override; + + STDMETHOD(EnumGenericParamConstraints)( + HCORENUM *phEnum, + mdGenericParam tk, + mdGenericParamConstraint rGenericParamConstraints[], + ULONG cMax, + ULONG *pcGenericParamConstraints) override; + + STDMETHOD(GetGenericParamConstraintProps)( + mdGenericParamConstraint gpc, + mdGenericParam *ptGenericParam, + mdToken *ptkConstraintType) override; + + STDMETHOD(GetPEKind)( + DWORD* pdwPEKind, + DWORD* pdwMAchine) override; + + STDMETHOD(GetVersionString)( + _Out_writes_to_opt_(ccBufSize, *pccBufSize) + LPWSTR pwzBuf, + DWORD ccBufSize, + DWORD *pccBufSize) override; + + STDMETHOD(EnumMethodSpecs)( + HCORENUM *phEnum, + mdToken tk, + mdMethodSpec rMethodSpecs[], + ULONG cMax, + ULONG *pcMethodSpecs) override; + +public: // IMetaDataAssemblyImport + STDMETHOD(GetAssemblyProps)( + mdAssembly mda, + void const** ppbPublicKey, + ULONG* pcbPublicKey, + ULONG* pulHashAlgId, + _Out_writes_to_opt_(cchName, *pchName) LPWSTR szName, + ULONG cchName, + ULONG* pchName, + ASSEMBLYMETADATA* pMetaData, + DWORD* pdwAssemblyFlags) override; + + STDMETHOD(GetAssemblyRefProps)( + mdAssemblyRef mdar, + void const** ppbPublicKeyOrToken, + ULONG* pcbPublicKeyOrToken, + _Out_writes_to_opt_(cchName, *pchName)LPWSTR szName, + ULONG cchName, + ULONG* pchName, + ASSEMBLYMETADATA* pMetaData, + void const** ppbHashValue, + ULONG* pcbHashValue, + DWORD* pdwAssemblyRefFlags) override; + + STDMETHOD(GetFileProps)( + mdFile mdf, + _Out_writes_to_opt_(cchName, *pchName) LPWSTR szName, + ULONG cchName, + ULONG* pchName, + void const** ppbHashValue, + ULONG* pcbHashValue, + DWORD* pdwFileFlags) override; + + STDMETHOD(GetExportedTypeProps)( + mdExportedType mdct, + _Out_writes_to_opt_(cchName, *pchName) LPWSTR szName, + ULONG cchName, + ULONG* pchName, + mdToken* ptkImplementation, + mdTypeDef* ptkTypeDef, + DWORD* pdwExportedTypeFlags) override; + + STDMETHOD(GetManifestResourceProps)( + mdManifestResource mdmr, + _Out_writes_to_opt_(cchName, *pchName)LPWSTR szName, + ULONG cchName, + ULONG* pchName, + mdToken* ptkImplementation, + DWORD* pdwOffset, + DWORD* pdwResourceFlags) override; + + STDMETHOD(EnumAssemblyRefs)( + HCORENUM* phEnum, + mdAssemblyRef rAssemblyRefs[], + ULONG cMax, + ULONG* pcTokens) override; + + STDMETHOD(EnumFiles)( + HCORENUM* phEnum, + mdFile rFiles[], + ULONG cMax, + ULONG* pcTokens) override; + + STDMETHOD(EnumExportedTypes)( + HCORENUM* phEnum, + mdExportedType rExportedTypes[], + ULONG cMax, + ULONG* pcTokens) override; + + STDMETHOD(EnumManifestResources)( + HCORENUM* phEnum, + mdManifestResource rManifestResources[], + ULONG cMax, + ULONG* pcTokens) override; + + STDMETHOD(GetAssemblyFromScope)( + mdAssembly* ptkAssembly) override; + + STDMETHOD(FindExportedTypeByName)( + LPCWSTR szName, + mdToken mdtExportedType, + mdExportedType* ptkExportedType) override; + + STDMETHOD(FindManifestResourceByName)( + LPCWSTR szName, + mdManifestResource* ptkManifestResource) override; + + STDMETHOD(FindAssembliesByName)( + LPCWSTR szAppBase, + LPCWSTR szPrivateBin, + LPCWSTR szAssemblyName, + IUnknown* ppIUnk[], + ULONG cMax, + ULONG* pcAssemblies) override; +}; + +#endif // _SRC_INTERFACES_METADATAIMPORTRO_HPP_ \ No newline at end of file diff --git a/src/native/dnmd/src/interfaces/options.cpp b/src/native/dnmd/src/interfaces/options.cpp new file mode 100644 index 0000000000000..d2aff168b345a --- /dev/null +++ b/src/native/dnmd/src/interfaces/options.cpp @@ -0,0 +1,11 @@ +#include +#include + +#define MINIPAL_COM_DEFINE_GUID +#include + +#define MIDL_DEFINE_GUID(type,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) + +// Define the IMetaDataDispenserEx option Guids here. They're declared in cor.h +MIDL_DEFINE_GUID(GUID, MetaDataSetUpdate, 0x2eee315c, 0xd7db, 0x11d2, 0x9f, 0x80, 0x0, 0xc0, 0x4f, 0x79, 0xa0, 0xa3); diff --git a/src/native/dnmd/src/interfaces/pal.cpp b/src/native/dnmd/src/interfaces/pal.cpp new file mode 100644 index 0000000000000..8cf64a02c9677 --- /dev/null +++ b/src/native/dnmd/src/interfaces/pal.cpp @@ -0,0 +1,229 @@ +#include "pal.hpp" +#include +#include +#include +#include +#include +#include +#include + +#if defined(BUILD_WINDOWS) +#include +#else +#include +#endif + +// String conversion functions +HRESULT pal::ConvertUtf16ToUtf8( + WCHAR const* str, + char* buffer, + uint32_t bufferLength, + _Out_opt_ uint32_t* writtenOrNeeded) +{ + assert(str != nullptr); + size_t length = minipal_u16_strlen((CHAR16_T*)str) + 1; + + size_t requiredBufferLength = minipal_get_length_utf16_to_utf8((CHAR16_T*)str, length, 0); + + if (requiredBufferLength > (size_t)std::numeric_limits::max()) + { + return E_FAIL; + } + + if (requiredBufferLength > bufferLength) + { + if (writtenOrNeeded != nullptr) + { + *writtenOrNeeded = (uint32_t)requiredBufferLength; + } + if (bufferLength == 0) + { + return S_OK; + } + return E_NOT_SUFFICIENT_BUFFER; + } + + size_t written = minipal_convert_utf16_to_utf8((CHAR16_T*)str, length, buffer, bufferLength, 0); + if (written >= 0) + { + *writtenOrNeeded = (uint32_t)written; + return S_OK; + } + return E_FAIL; +} + +HRESULT pal::ConvertUtf8ToUtf16( + char const* str, + WCHAR* buffer, + uint32_t bufferLength, + _Out_opt_ uint32_t* writtenOrNeeded) +{ + assert(str != nullptr); + size_t length = strlen(str) + 1; + + size_t requiredBufferLength = minipal_get_length_utf8_to_utf16(str, length, 0); + + if (requiredBufferLength > (size_t)std::numeric_limits::max()) + { + return E_FAIL; + } + + if (requiredBufferLength > bufferLength) + { + if (writtenOrNeeded != nullptr) + { + *writtenOrNeeded = (uint32_t)requiredBufferLength; + } + if (bufferLength == 0) + { + return S_OK; + } + return E_NOT_SUFFICIENT_BUFFER; + } + + size_t written = minipal_convert_utf8_to_utf16(str, length, (CHAR16_T*)buffer, bufferLength, 0); + if (written >= 0) + { + *writtenOrNeeded = (uint32_t)written; + return S_OK; + } + return E_FAIL; +} + +template<> +HRESULT pal::StringConvert::ConvertWorker(WCHAR const* c, char* buffer, uint32_t& bufferLength) +{ + return ConvertUtf16ToUtf8(c, buffer, bufferLength, &bufferLength); +} + +template<> +HRESULT pal::StringConvert::ConvertWorker(char const* c, WCHAR* buffer, uint32_t& bufferLength) +{ + return ConvertUtf8ToUtf16(c, buffer, bufferLength, &bufferLength); +} + +#if !defined(__STDC_LIB_EXT1__) && !defined(BUILD_WINDOWS) +int strcat_s(char* dest, rsize_t destsz, char const* src) +{ + assert(dest != nullptr && src != nullptr); + (void)::strcat(dest, src); + return 0; +} +#endif // !defined(__STDC_LIB_EXT1__) && !defined(BUILD_WINDOWS) + +bool pal::ComputeSha1Hash(span data, std::array& hashDestination) +{ + minipal_sha1(data.data(), data.size(), hashDestination.data(), SHA1_HASH_SIZE); + return true; +} + +// Read-write lock implementation +// The implementation type matches the C++11 BasicLockable and the C++14 SharedLockable requirements (excluding the try_lock_shared method). +// This allows us to move to exposing the C++14 API surface in the future more easily. +#if defined(BUILD_WINDOWS) +namespace pal +{ + class ReadWriteLock::Impl final + { + SRWLOCK _lock; + public: + Impl() + { + ::InitializeSRWLock(&_lock); + } + + void lock_shared() noexcept + { + ::AcquireSRWLockShared(&_lock); + } + + void unlock_shared() noexcept + { + ::ReleaseSRWLockShared(&_lock); + } + + void lock() noexcept + { + ::AcquireSRWLockExclusive(&_lock); + } + + void unlock() noexcept + { + ::ReleaseSRWLockExclusive(&_lock); + } + }; +} +#else +namespace pal +{ + class ReadWriteLock::Impl final + { + pthread_rwlock_t _lock; + public: + Impl() + { + ::pthread_rwlock_init(&_lock, nullptr); + } + + void lock_shared() noexcept + { + ::pthread_rwlock_rdlock(&_lock); + } + + void unlock_shared() noexcept + { + ::pthread_rwlock_unlock(&_lock); + } + + void lock() noexcept + { + ::pthread_rwlock_wrlock(&_lock); + } + + void unlock() noexcept + { + ::pthread_rwlock_unlock(&_lock); + } + }; +} +#endif + +pal::ReadWriteLock::ReadWriteLock() + : _impl{ std::make_unique() } + , _readLock{ *this } + , _writeLock{ *this } +{ +} + +// Define here where pal::ReadWriteLock::Impl is defined +pal::ReadWriteLock::~ReadWriteLock() = default; + +pal::ReadLock::ReadLock(pal::ReadWriteLock& lock) noexcept + : _lock{ lock } +{ +} + +void pal::ReadLock::lock() noexcept +{ + _lock._impl->lock_shared(); +} + +void pal::ReadLock::unlock() noexcept +{ + _lock._impl->unlock_shared(); +} + +pal::WriteLock::WriteLock(pal::ReadWriteLock& lock) noexcept + : _lock{ lock } +{ +} + +void pal::WriteLock::lock() noexcept +{ + _lock._impl->lock(); +} + +void pal::WriteLock::unlock() noexcept +{ + _lock._impl->unlock(); +} diff --git a/src/native/dnmd/src/interfaces/pal.hpp b/src/native/dnmd/src/interfaces/pal.hpp new file mode 100644 index 0000000000000..8183dea3dbb83 --- /dev/null +++ b/src/native/dnmd/src/interfaces/pal.hpp @@ -0,0 +1,171 @@ +#ifndef _SRC_INTERFACES_PAL_HPP_ +#define _SRC_INTERFACES_PAL_HPP_ + +#include +#include +#include +#include +#include + +namespace pal +{ + // Convert the UTF-16 string into UTF-8 + // Buffer length should include null terminator. + // Written length includes null terminator. + HRESULT ConvertUtf16ToUtf8( + WCHAR const* str, + char* buffer, + uint32_t bufferLength, + _Out_opt_ uint32_t* writtenOrNeeded); + + // Convert the UTF-8 string into UTF-16 + // Buffer length should include null terminator. + // Written length includes null terminator. + HRESULT ConvertUtf8ToUtf16( + char const* str, + WCHAR* buffer, + uint32_t bufferLength, + _Out_opt_ uint32_t* writtenOrNeeded); + + // Template class for conversion UTF-8 <=> UTF-16 + template + class StringConvert + { + B* _ptr; + malloc_ptr _owner; + uint32_t _charLength; + bool _converted; + HRESULT ConvertWorker(A const* c, B* buffer, uint32_t& bufferLength); + + public: + StringConvert(A const* c, B* buffer, uint32_t bufferLength) noexcept + : _owner{} + , _charLength{} + , _converted{} + { + uint32_t neededLength = bufferLength; + // Compute needed size for conversion and/or rely on the user supplied buffer. + HRESULT hr = ConvertWorker(c, buffer, neededLength); + if (hr == S_OK) + { + if (bufferLength < neededLength) + { + buffer = (B*)::malloc(sizeof(*buffer) * neededLength); + _owner.reset(buffer); + bufferLength = neededLength; + } + + // Do conversion + hr = ConvertWorker(c, buffer, bufferLength); + _converted = SUCCEEDED(hr); + if (_converted) + { + _ptr = buffer; + _charLength = bufferLength; + } + } + else if (neededLength != bufferLength) + { + // Failed to convert. If the needed length was updated + // then set that so the caller can possibly use it. + _charLength = neededLength; + } + } + + explicit StringConvert(A const* c) noexcept + : StringConvert(c, nullptr, 0) + { } + + template + StringConvert(A const* c, B(&buffer)[N]) noexcept + : StringConvert(c, buffer, N) + { } + + StringConvert(StringConvert const&) = delete; + StringConvert(StringConvert&&) = delete; + + ~StringConvert() noexcept = default; + + StringConvert& operator=(StringConvert const&) = delete; + StringConvert& operator=(StringConvert&&) = delete; + + bool Success() const noexcept + { + return _converted; + } + + uint32_t Length() const noexcept + { + return _charLength; + } + + operator B const*() const noexcept + { + return _ptr; + } + + operator B*() noexcept + { + return _ptr; + } + }; + + constexpr size_t SHA1_HASH_SIZE = 20; + + bool ComputeSha1Hash(span data, std::array& hashDestination); + + // A simple read-write lock that provides accessors to meet the C++11 BasicLockable requirements. + class ReadWriteLock; + + class ReadLock final + { + ReadWriteLock& _lock; + public: + ReadLock(ReadWriteLock& lock) noexcept; + ReadLock(ReadLock const&) = delete; + ReadLock(ReadLock&&) = delete; + void lock() noexcept; + void unlock() noexcept; + }; + + class WriteLock final + { + ReadWriteLock& _lock; + public: + WriteLock(ReadWriteLock& lock) noexcept; + WriteLock(WriteLock const&) = delete; + WriteLock(WriteLock&&) = delete; + void lock() noexcept; + void unlock() noexcept; + }; + + class ReadWriteLock + { + friend class ReadLock; + friend class WriteLock; + class Impl; + std::unique_ptr _impl; + ReadLock _readLock; + WriteLock _writeLock; + public: + ReadWriteLock(); + ~ReadWriteLock(); + ReadLock& GetReadLock() noexcept + { + return _readLock; + } + WriteLock& GetWriteLock() noexcept + { + return _writeLock; + } + }; +} + +// Implementations for missing bounds checking APIs. +// See https://en.cppreference.com/w/c/error#Bounds_checking +#if !defined(__STDC_LIB_EXT1__) && !defined(BUILD_WINDOWS) +using rsize_t = size_t; +int strcat_s(char* dest, rsize_t destsz, char const* src); +#endif // !defined(__STDC_LIB_EXT1__) && !defined(BUILD_WINDOWS) + +#endif // _SRC_INTERFACES_PAL_HPP_ \ No newline at end of file diff --git a/src/native/dnmd/src/interfaces/signatures.cpp b/src/native/dnmd/src/interfaces/signatures.cpp new file mode 100644 index 0000000000000..51596bcf37287 --- /dev/null +++ b/src/native/dnmd/src/interfaces/signatures.cpp @@ -0,0 +1,530 @@ +#include "signatures.hpp" +#include "importhelpers.hpp" +#include +#include +#include +#include + +namespace +{ + template::type, uint8_t>::value>::type> + std::tuple> read_compressed_uint(span signature) + { + ULONG value = 0; + signature = slice(signature, CorSigUncompressData(signature.data(), &value)); + return std::make_tuple(value, signature); + } + + template::type, uint8_t>::value>::type> + std::tuple> read_compressed_int(span signature) + { + int value = 0; + signature = slice(signature, CorSigUncompressSignedInt(signature.data(), &value)); + return std::make_tuple(value, signature); + } + + template::type, uint8_t>::value>::type> + std::tuple> read_compressed_token(span signature) + { + mdToken value = mdTokenNil; + signature = slice(signature, CorSigUncompressToken(signature.data(), &value)); + return std::make_tuple(value, signature); + } + + + struct signature_element_part_tag + { + }; + + struct raw_byte_tag final : signature_element_part_tag + { + }; + + struct compressed_uint_tag final : signature_element_part_tag + { + }; + + struct compressed_int_tag final : signature_element_part_tag + { + }; + + struct token_tag final : signature_element_part_tag + { + }; + + template::type, uint8_t>::value>::type> + span WalkSignatureElement(span signature, TCallback callback) + { + uint8_t elementType = signature[0]; + signature = slice(signature, 1); + + callback(elementType, raw_byte_tag{}); + switch (elementType) + { + case ELEMENT_TYPE_VOID: + 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: + case ELEMENT_TYPE_R4: + case ELEMENT_TYPE_R8: + case ELEMENT_TYPE_STRING: + case ELEMENT_TYPE_OBJECT: + case ELEMENT_TYPE_TYPEDBYREF: + case ELEMENT_TYPE_I: + case ELEMENT_TYPE_U: + case ELEMENT_TYPE_SENTINEL: + break; + case ELEMENT_TYPE_FNPTR: + { + uint8_t callingConvention = signature[0]; + signature = slice(signature, 1); + callback(callingConvention, raw_byte_tag{}); + + uint32_t genericParameterCount; + if ((callingConvention & IMAGE_CEE_CS_CALLCONV_GENERIC) == IMAGE_CEE_CS_CALLCONV_GENERIC) + { + std::tie(genericParameterCount, signature) = read_compressed_uint(signature); + callback(genericParameterCount, compressed_uint_tag{}); + } + + uint32_t parameterCount; + std::tie(parameterCount, signature) = read_compressed_uint(signature); + callback(parameterCount, compressed_uint_tag{}); + + // Walk the return type + signature = WalkSignatureElement(signature, callback); + + // Walk the parameters + for (uint32_t i = 0; i < parameterCount; i++) + { + if (signature[0] == ELEMENT_TYPE_SENTINEL) + { + signature = slice(signature, 1); + callback((uint8_t)ELEMENT_TYPE_SENTINEL, raw_byte_tag{}); + } + + signature = WalkSignatureElement(signature, callback); + } + break; + } + case ELEMENT_TYPE_PTR: + case ELEMENT_TYPE_BYREF: + case ELEMENT_TYPE_SZARRAY: + case ELEMENT_TYPE_PINNED: + signature = WalkSignatureElement(signature, callback); + break; + + case ELEMENT_TYPE_VAR: + case ELEMENT_TYPE_MVAR: + { + uint32_t genericParameterIndex; + std::tie(genericParameterIndex, signature) = read_compressed_uint(signature); + callback(genericParameterIndex, compressed_uint_tag{}); + break; + } + + case ELEMENT_TYPE_VALUETYPE: + case ELEMENT_TYPE_CLASS: + { + mdToken token; + std::tie(token, signature) = read_compressed_token(signature); + callback(token, token_tag{}); + break; + } + + case ELEMENT_TYPE_CMOD_REQD: + case ELEMENT_TYPE_CMOD_OPT: + { + mdToken token; + std::tie(token, signature) = read_compressed_token(signature); + callback(token, token_tag{}); + signature = WalkSignatureElement(signature, callback); + break; + } + + case ELEMENT_TYPE_ARRAY: + { + signature = WalkSignatureElement(signature, callback); + + uint32_t rank; + std::tie(rank, signature) = read_compressed_uint(signature); + callback(rank, compressed_uint_tag{}); + + uint32_t numSizes; + std::tie(numSizes, signature) = read_compressed_uint(signature); + callback(numSizes, compressed_uint_tag{}); + + for (uint32_t i = 0; i < numSizes; i++) + { + uint32_t size; + std::tie(size, signature) = read_compressed_uint(signature); + callback(size, compressed_uint_tag{}); + } + + uint32_t numLoBounds; + std::tie(numLoBounds, signature) = read_compressed_uint(signature); + callback(numLoBounds, compressed_uint_tag{}); + + for (uint32_t i = 0; i < numLoBounds; i++) + { + int32_t loBound; + std::tie(loBound, signature) = read_compressed_int(signature); + callback(loBound, compressed_int_tag{}); + } + break; + } + + case ELEMENT_TYPE_GENERICINST: + { + signature = WalkSignatureElement(signature, callback); + + uint32_t genericArgumentCount; + std::tie(genericArgumentCount, signature) = read_compressed_uint(signature); + callback(genericArgumentCount, compressed_uint_tag{}); + + for (uint32_t i = 0; i < genericArgumentCount; i++) + { + signature = WalkSignatureElement(signature, callback); + } + break; + } + default: + throw std::invalid_argument { "Invalid signature element type" }; + } + + return signature; + } +} + +void GetMethodDefSigFromMethodRefSig(span methodRefSig, inline_span& methodDefSig) +{ + assert(methodRefSig.size() > 0); + // We don't need to do anything with the various elements of the signature, + // we just need to know how many parameters are before the sentinel. + span signature = methodRefSig; + uint8_t const callingConvention = signature[0]; + signature = slice(signature, 1); + + // The MethodDefSig is the same as the MethodRefSig if the calling convention is not vararg. + // Only in the vararg case does the MethodRefSig have additional data to describe the exact vararg + // parameter list. + if ((callingConvention & IMAGE_CEE_CS_CALLCONV_MASK) != IMAGE_CEE_CS_CALLCONV_VARARG) + { + methodDefSig.resize(methodRefSig.size()); + std::copy(methodRefSig.begin(), methodRefSig.end(), methodDefSig.begin()); + return; + } + + uint32_t genericParameterCount = 0; + if ((callingConvention & IMAGE_CEE_CS_CALLCONV_GENERIC) == IMAGE_CEE_CS_CALLCONV_GENERIC) + { + std::tie(genericParameterCount, signature) = read_compressed_uint(signature); + } + + uint32_t originalParameterCount; + std::tie(originalParameterCount, signature) = read_compressed_uint(signature); + + // Save this part of the signature for us to copy later. + span returnTypeAndParameters = signature; + // Walk the return type + // Use std::intmax_t here as it can handle all the values of the various parts of the signature. + // If we were using C++14, we could use auto here instead. + signature = WalkSignatureElement(signature, [](std::intmax_t, signature_element_part_tag) { }); + + // Walk the parameters + uint32_t i = 0; + for (; i < originalParameterCount; i++) + { + if (signature[0] == ELEMENT_TYPE_SENTINEL) + { + break; + } + + signature = WalkSignatureElement(signature, [](std::intmax_t, signature_element_part_tag) { }); + } + + // Now that we know the number of parameters, we can copy the MethodDefSig portion of the signature + // and update the parameter count. + // We need to account for the fact that the parameter count may be encoded with less bytes + // as it is emitted using the compressed unsigned integer format. + // An ECMA-335 compressed integer will take up no more than 4 bytes. + uint8_t buffer[4]; + ULONG originalParamCountCompressedSize = CorSigCompressData(originalParameterCount, buffer); + ULONG newParamCountCompressedSize = CorSigCompressData(i, buffer); + span compressedNewParamCount = { buffer, newParamCountCompressedSize }; + + // The MethodDefSig length will be the length of the original signature up to the ELEMENT_TYPE_SENTINEL value, + // minus the difference in the compressed size of the original parameter count and the new parameter count, if any. + size_t methodDefSigBufferLength = methodRefSig.size() - signature.size() - originalParamCountCompressedSize + newParamCountCompressedSize; + methodDefSig.resize(methodDefSigBufferLength); + + // Copy over the signature into the new buffer. + // In case the parameter count was encoded with less bytes, we need to account for that + // and copy the signature piece by piece. + size_t offset = 0; + methodDefSig[offset++] = callingConvention; + if ((callingConvention & IMAGE_CEE_CS_CALLCONV_GENERIC) == IMAGE_CEE_CS_CALLCONV_GENERIC) + { + offset += CorSigCompressData(genericParameterCount, methodDefSig.data() + offset); + } + std::memcpy(methodDefSig.data() + offset, compressedNewParamCount.data(), newParamCountCompressedSize); + offset += newParamCountCompressedSize; + + // Now that we've re-written the parameter count, we can copy the rest of the signature directly from the MethodRefSig + assert(returnTypeAndParameters.size() >= methodDefSigBufferLength - offset); + std::memcpy(methodDefSig.data() + offset, returnTypeAndParameters.data(), methodDefSigBufferLength - offset); + + return; +} + +// Define a function object that enables us to combine multiple lambdas into a single overload set. +namespace +{ + template + struct Overload; + + template + struct Overload + { + Overload(T&& t) : _t{ std::forward(t) } + { } + + // Define a perfectly-forwarding operator() that will call the function object with the given arguments. + template + auto operator()(Args&&... args) const + -> decltype(std::declval()(std::forward(args)...)) + { + return _t(std::forward(args)...); + } + private: + T _t; + }; + + template + struct Overload : Overload, Overload + { + using Overload::operator(); + using Overload::operator(); + + Overload(T&& t, Ts&&... ts) + :Overload(std::forward(t)), + Overload(std::forward(ts)...) + { + } + }; + + template + Overload make_overload(Ts&&... ts) + { + return Overload(std::forward(ts)...); + } +} + +HRESULT ImportSignatureIntoModule( + mdhandle_t sourceAssembly, + mdhandle_t sourceModule, + span sourceAssemblyHash, + mdhandle_t destinationAssembly, + mdhandle_t destinationModule, + span signature, + std::function onRowAdded, + inline_span& importedSignature) +{ + HRESULT hr; + // We are going to copy over the signature and replace the tokens from the source module in the signature + // with equivalent tokens in the destination module, creating them if needed. + std::vector importedSignatureBuffer; + // Our imported signature will likely be a very similar size to the original signature. + importedSignatureBuffer.reserve(signature.size()); + + auto onSignatureItemCallback = make_overload( + [&](uint8_t byte, raw_byte_tag) + { + importedSignatureBuffer.push_back(byte); + }, + [&](uint32_t value, compressed_uint_tag) + { + uint8_t buffer[4]; + ULONG compressedSize = CorSigCompressData(value, buffer); + importedSignatureBuffer.insert(importedSignatureBuffer.end(), buffer, buffer + compressedSize); + }, + [&](int32_t value, compressed_int_tag) + { + uint8_t buffer[4]; + ULONG compressedSize = CorSigCompressSignedInt(value, buffer); + importedSignatureBuffer.insert(importedSignatureBuffer.end(), buffer, buffer + compressedSize); + }, + [=, &importedSignatureBuffer, &hr](mdToken token, token_tag) + { + HRESULT localHR = ImportReferenceToTypeDefOrRefOrSpec( + sourceAssembly, + sourceModule, + sourceAssemblyHash, + destinationAssembly, + destinationModule, + onRowAdded, + &token); + + // We can safely continue walking the signature even if we failed to import the token. + // We'll return the failure code when we're done. + if (FAILED(localHR)) + { + hr = localHR; + return; + } + + uint8_t buffer[4]; + ULONG compressedSize = CorSigCompressToken(token, buffer); + importedSignatureBuffer.insert(importedSignatureBuffer.end(), buffer, buffer + compressedSize); + } + ); + + const uint8_t callingConvention = signature[0]; + signature = slice(signature, 1); + onSignatureItemCallback(callingConvention, raw_byte_tag{}); + + + uint32_t genericParameterCount = 0; + if ((callingConvention & IMAGE_CEE_CS_CALLCONV_GENERIC) == IMAGE_CEE_CS_CALLCONV_GENERIC) + { + std::tie(genericParameterCount, signature) = read_compressed_uint(signature); + onSignatureItemCallback(genericParameterCount, compressed_uint_tag{}); + } + + uint32_t parameterCount = 0; + // FieldSig doesn't have a parameter count. + // It also has only one element, so treating FieldSig as having 0 parameters ends up with the correct behavior. + if ((callingConvention & IMAGE_CEE_CS_CALLCONV_MASK) != IMAGE_CEE_CS_CALLCONV_FIELD) + { + std::tie(parameterCount, signature) = read_compressed_uint(signature); + onSignatureItemCallback(parameterCount, compressed_uint_tag{}); + } + + // Walk the return type + // LocalVarSig and MethodSpecSig both don't have a return type. They both have only N elements, + // captured in the parameter count. + if ((callingConvention & IMAGE_CEE_CS_CALLCONV_MASK) != IMAGE_CEE_CS_CALLCONV_LOCAL_SIG + && (callingConvention & IMAGE_CEE_CS_CALLCONV_MASK) != IMAGE_CEE_CS_CALLCONV_GENERICINST) + { + signature = WalkSignatureElement(signature, onSignatureItemCallback); + } + + if (FAILED(hr)) + return hr; + + // Walk the parameters + uint32_t i = 0; + for (; i < parameterCount; i++) + { + if (signature[0] == ELEMENT_TYPE_SENTINEL) + { + break; + } + + signature = WalkSignatureElement(signature, onSignatureItemCallback); + + if (FAILED(hr)) + return hr; + } + + try + { + importedSignature.resize(importedSignature.size()); + } + catch (std::bad_alloc const&) + { + return E_OUTOFMEMORY; + } + + std::copy(importedSignatureBuffer.begin(), importedSignatureBuffer.end(), importedSignature.begin()); + + return S_OK; +} + +HRESULT ImportTypeSpecBlob( + mdhandle_t sourceAssembly, + mdhandle_t sourceModule, + span sourceAssemblyHash, + mdhandle_t destinationAssembly, + mdhandle_t destinationModule, + span typeSpecBlob, + std::function onRowAdded, + inline_span& importedTypeSpecBlob) +{ + std::vector importedTypeSpecBlobBuffer; + // Our imported blob will likely be a very similar size to the original blob. + importedTypeSpecBlobBuffer.reserve(typeSpecBlob.size()); + + HRESULT hr = S_OK; + + // WalkSignatureElement is more permissive of what it will accept than the requirements of the TypeSpecBlob. + span remaining = WalkSignatureElement(typeSpecBlob, make_overload( + [&](uint8_t byte, raw_byte_tag) + { + importedTypeSpecBlobBuffer.push_back(byte); + }, + [&](uint32_t value, compressed_uint_tag) + { + uint8_t buffer[4]; + ULONG compressedSize = CorSigCompressData(value, buffer); + importedTypeSpecBlobBuffer.insert(importedTypeSpecBlobBuffer.end(), buffer, buffer + compressedSize); + }, + [&](int32_t value, compressed_int_tag) + { + uint8_t buffer[4]; + ULONG compressedSize = CorSigCompressSignedInt(value, buffer); + importedTypeSpecBlobBuffer.insert(importedTypeSpecBlobBuffer.end(), buffer, buffer + compressedSize); + }, + [=, &importedTypeSpecBlobBuffer, &hr](mdToken token, token_tag) + { + HRESULT localHR = ImportReferenceToTypeDefOrRefOrSpec( + sourceAssembly, + sourceModule, + sourceAssemblyHash, + destinationAssembly, + destinationModule, + onRowAdded, + &token); + + // We can safely continue walking the signature even if we failed to import the token. + // We'll return the failure code when we're done. + if (FAILED(localHR)) + { + hr = localHR; + return; + } + + uint8_t buffer[4]; + ULONG compressedSize = CorSigCompressToken(token, buffer); + importedTypeSpecBlobBuffer.insert(importedTypeSpecBlobBuffer.end(), buffer, buffer + compressedSize); + } + )); + + if (FAILED(hr)) + return hr; + + if (remaining.size() != 0) + { + // If we have any bytes remaining, then the TypeSpecBlob was invalid. + return E_INVALIDARG; + } + + try + { + importedTypeSpecBlob.resize(importedTypeSpecBlobBuffer.size()); + } + catch (std::bad_alloc const&) + { + return E_OUTOFMEMORY; + } + + std::copy(importedTypeSpecBlobBuffer.begin(), importedTypeSpecBlobBuffer.end(), importedTypeSpecBlob.begin()); + return S_OK; +} diff --git a/src/native/dnmd/src/interfaces/signatures.hpp b/src/native/dnmd/src/interfaces/signatures.hpp new file mode 100644 index 0000000000000..1cdd6c621d3b9 --- /dev/null +++ b/src/native/dnmd/src/interfaces/signatures.hpp @@ -0,0 +1,158 @@ +#ifndef _SRC_INTERFACES_SIGNATURES_HPP_ +#define _SRC_INTERFACES_SIGNATURES_HPP_ + +#include +#include + +#include + +#include +#include +#include +#include + +/// @brief A span that that supports owning a specified number of elements in itself. +/// @tparam T The type of the elements in the span. +/// @tparam NumInlineElements The number of elements to store in the span itself. +template +struct base_inline_span : public span +{ + base_inline_span() : span() + { + this->_ptr = _storage.data(); + } + + base_inline_span(size_t size) : span() + { + this->_ptr = size > NumInlineElements ? base_inline_span::allocate_noninline_memory(size) : _storage.data(); + this->_size = size; + } + + base_inline_span(base_inline_span&& other) + { + *this = std::move(other); + } + + base_inline_span& operator=(base_inline_span&& other) noexcept + { + if (this->size() > NumInlineElements) + { + base_inline_span::free_noninline_memory(this->_ptr, this->size()); + this->_ptr = nullptr; + } + + if (other.size() > NumInlineElements) + { + this->_ptr = other._ptr; + other._ptr = nullptr; + } + else + { + std::copy(other.begin(), other.end(), _storage.begin()); + this->_ptr = _storage.data(); + this->_size = other._size; + } + + return *this; + } + + void resize(size_t newSize) + { + if (this->size() > NumInlineElements && newSize < NumInlineElements) + { + // Transitioning from a non-inline buffer to the inline buffer. + std::copy(this->begin(), this->begin() + newSize, _storage.begin()); + base_inline_span::free_noninline_memory(this->_ptr, this->size()); + this->_ptr = _storage.data(); + } + else if (this->size() <= NumInlineElements && newSize <= NumInlineElements) + { + // We're staying within the inline buffer, so just update the size. + this->_size = newSize; + } + else if (this->size() > NumInlineElements && newSize < this->size()) + { + // Shrinking the buffer, but still keeping it as a non-inline buffer. + this->_size = newSize; + } + else + { + // Growing the buffer from the inline buffer to a non-inline buffer. + assert(this->size() <= NumInlineElements && newSize > NumInlineElements); + T* newPtr = base_inline_span::allocate_noninline_memory(this->size()); + std::copy(this->begin(), this->end(), newPtr); + this->_ptr = newPtr; + this->_size = newSize; + } + } + + ~base_inline_span() + { + if (this->size() > NumInlineElements) + { + assert(this->_ptr != _storage.data()); + base_inline_span::free_noninline_memory(this->_ptr, this->size()); + this->_ptr = nullptr; + } + else + { + assert(this->_ptr == _storage.data()); + } + } + +private: + std::array _storage; + + static T* allocate_noninline_memory(size_t numElements) + { + assert(numElements > NumInlineElements); + return new T[numElements]; + } + + static void free_noninline_memory(T* ptr, size_t numElements) + { + UNREFERENCED_PARAMETER(numElements); + assert(numElements > NumInlineElements); + delete[] ptr; + } +}; + +/// @brief An span with inline storage for up to 64 bytes. +/// @tparam T The element type of the span. +template +using inline_span = base_inline_span; + +void GetMethodDefSigFromMethodRefSig(span methodRefSig, inline_span& methodDefSig); + +// Import a signature from one set of module and assembly metadata into another set of module and assembly metadata. +// The module and assembly metadata for source or destination can be the same metadata. +// The supported signature kinds are: +// - MethodDefSig (II.23.2.1) +// - MethodRefSig (II.23.2.2) +// - StandaloneMethodSig (II.23.2.3) +// - FieldSig (II.23.2.4) +// - PropertySig (II.23.2.5) +// - LocalVarSig (II.23.2.6) +// - MethodSpec (II.23.2.15) +HRESULT ImportSignatureIntoModule( + mdhandle_t sourceAssembly, + mdhandle_t sourceModule, + span sourceAssemblyHash, + mdhandle_t destinationAssembly, + mdhandle_t destinationModule, + span signature, + std::function onRowAdded, + inline_span& importedSignature); + +// Import a TypeSpecBlob (II.23.2.14) from one set of module and assembly metadata into another set of module and assembly metadata. +HRESULT ImportTypeSpecBlob( + mdhandle_t sourceAssembly, + mdhandle_t sourceModule, + span sourceAssemblyHash, + mdhandle_t destinationAssembly, + mdhandle_t destinationModule, + span typeSpecBlob, + std::function onRowAdded, + inline_span& importedTypeSpecBlob); + +#endif // _SRC_INTERFACES_SIGNATURES_HPP_ diff --git a/src/native/dnmd/src/interfaces/symbinder.cpp b/src/native/dnmd/src/interfaces/symbinder.cpp new file mode 100644 index 0000000000000..10bdf9391fba3 --- /dev/null +++ b/src/native/dnmd/src/interfaces/symbinder.cpp @@ -0,0 +1,102 @@ +#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 +#include +#include + +EXTERN_GUID(IID_ISymUnmanagedBinder, 0xaa544d42, 0x28cb, 0x11d3, 0xbd, 0x22, 0x00, 0x00, 0xf8, 0x08, 0x49, 0xbd); + +namespace +{ + class SymUnmanagedBinderStateless final : ISymUnmanagedBinder + { + public: // ISymUnmanagedBinder + STDMETHOD(GetReaderForFile)( + /* [in] */ __RPC__in_opt IUnknown *importer, + /* [in] */ __RPC__in WCHAR const *fileName, + /* [in] */ __RPC__in WCHAR const *searchPath, + /* [retval][out] */ __RPC__deref_out_opt ISymUnmanagedReader **pRetVal) + { + UNREFERENCED_PARAMETER(importer); + UNREFERENCED_PARAMETER(fileName); + UNREFERENCED_PARAMETER(searchPath); + UNREFERENCED_PARAMETER(pRetVal); + return E_NOTIMPL; + } + + STDMETHOD(GetReaderFromStream)( + /* [in] */ __RPC__in_opt IUnknown *importer, + /* [in] */ __RPC__in_opt IStream *pstream, + /* [retval][out] */ __RPC__deref_out_opt ISymUnmanagedReader **pRetVal) + { + UNREFERENCED_PARAMETER(importer); + UNREFERENCED_PARAMETER(pstream); + UNREFERENCED_PARAMETER(pRetVal); + return E_NOTIMPL; + } + + public: // IUnknown + virtual HRESULT STDMETHODCALLTYPE QueryInterface( + /* [in] */ REFIID riid, + /* [iid_is][out] */ _COM_Outptr_ void __RPC_FAR* __RPC_FAR* ppvObject) + { + if (ppvObject == nullptr) + return E_POINTER; + + if (riid == IID_IUnknown) + { + *ppvObject = static_cast(this); + } + else if (riid == IID_ISymUnmanagedBinder) + { + *ppvObject = static_cast(this); + } + else + { + *ppvObject = nullptr; + return E_NOINTERFACE; + } + + (void)AddRef(); + return S_OK; + } + + virtual ULONG STDMETHODCALLTYPE AddRef(void) + { + return 1; + } + + virtual ULONG STDMETHODCALLTYPE Release(void) + { + return 1; + } + }; + + // The only available binder is stateless and + // statically allocated. There is no lifetime management + // needed. + SymUnmanagedBinderStateless g_binder; +} + +extern "C" DNMD_EXPORT +HRESULT GetSymBinder( + REFGUID riid, + void** ppObj) +{ + if (riid != IID_ISymUnmanagedBinder) + return E_INVALIDARG; + + if (ppObj == nullptr) + return E_INVALIDARG; + + return g_binder.QueryInterface(riid, (void**)ppObj); +} \ No newline at end of file diff --git a/src/native/dnmd/src/interfaces/tearoffbase.hpp b/src/native/dnmd/src/interfaces/tearoffbase.hpp new file mode 100644 index 0000000000000..741dce7f5f8d8 --- /dev/null +++ b/src/native/dnmd/src/interfaces/tearoffbase.hpp @@ -0,0 +1,85 @@ +#ifndef _SRC_INTERFACES_TEAROFFBASE_HPP_ +#define _SRC_INTERFACES_TEAROFFBASE_HPP_ + +#include +#include +#include +#include + +class TearOffUnknown : public IUnknown +{ + friend class ControllingIUnknown; +private: + IUnknown* _pUnkOuter; + +protected: + virtual bool TryGetInterfaceOnThis(REFIID riid, void** ppvObject) PURE; + +public: + explicit TearOffUnknown(IUnknown* outer) + : _pUnkOuter{ outer } + { + assert(outer != nullptr); + } + + virtual ~TearOffUnknown() = default; + +public: // IUnknown + STDMETHOD_(ULONG, AddRef)() override + { + return _pUnkOuter->AddRef(); + } + + STDMETHOD_(ULONG, Release)() override + { + return _pUnkOuter->Release(); + } + + STDMETHOD(QueryInterface)(REFIID riid, void** ppvObject) override + { + if (ppvObject == nullptr) + return E_POINTER; + + // The outer IUnknown must always be returned + // when IID_IUnknown is requested. + // To ensure we handle nested composition, + // we'll call into the outer QI to ensure we propagate + // up to the true outer IUnknown. + if (riid == IID_IUnknown) + { + return _pUnkOuter->QueryInterface(riid, ppvObject); + } + + if (TryGetInterfaceOnThis(riid, ppvObject)) + { + (void)AddRef(); + return S_OK; + } + else + { + return _pUnkOuter->QueryInterface(riid, ppvObject); + } + } +}; + +template +class TearOffBase : public TearOffUnknown, public T... +{ +public: + using TearOffUnknown::TearOffUnknown; + + STDMETHOD_(ULONG, AddRef)() override final + { + return TearOffUnknown::AddRef(); + } + STDMETHOD_(ULONG, Release)() override final + { + return TearOffUnknown::Release(); + } + STDMETHOD(QueryInterface)(REFIID riid, void** ppvObject) override final + { + return TearOffUnknown::QueryInterface(riid, ppvObject); + } +}; + +#endif // _SRC_INTERFACES_TEAROFFBASE_HPP_ diff --git a/src/native/dnmd/src/interfaces/threadsafe.hpp b/src/native/dnmd/src/interfaces/threadsafe.hpp new file mode 100644 index 0000000000000..c0937d984964e --- /dev/null +++ b/src/native/dnmd/src/interfaces/threadsafe.hpp @@ -0,0 +1,1695 @@ +#ifndef _SRC_INTERFACES_THREADSAFE_HPP_ +#define _SRC_INTERFACES_THREADSAFE_HPP_ + +#include "internal/dnmd_platform.hpp" +#include "tearoffbase.hpp" +#include "controllingiunknown.hpp" +#include "dnmdowner.hpp" +#include "pal.hpp" +#include "importhelpers.hpp" + +#include +#include + +#include +#include + +// A tear-off that re-exposes an mdhandle_view as an IDNMDOwner*. +class DelegatingDNMDOwner final : public TearOffBase +{ + mdhandle_view _inner; + +protected: + virtual bool TryGetInterfaceOnThis(REFIID riid, void** ppvObject) override + { + if (riid == IID_IDNMDOwner) + { + *ppvObject = static_cast(this); + return true; + } + return false; + } + +public: + DelegatingDNMDOwner(IUnknown* controllingUnknown, mdhandle_view inner) + : TearOffBase(controllingUnknown) + , _inner{ inner } + { } + + virtual ~DelegatingDNMDOwner() = default; + + mdhandle_t MetaData() override + { + return _inner.get(); + } +}; + +template +class ThreadSafeImportEmit : public TearOffBase +{ + pal::ReadWriteLock _lock; + // owning reference to the thread-unsafe object that provides the underlying implementation. + minipal::com_ptr _threadUnsafe; + // non-owning reference to the concrete non-locking implementations + TImport* _import; + TEmit* _emit; + +protected: + virtual bool TryGetInterfaceOnThis(REFIID riid, void** ppvObject) override + { + assert(riid != IID_IUnknown); + if (riid == IID_IMetaDataImport || riid == IID_IMetaDataImport2) + { + *ppvObject = static_cast(this); + return true; + } + if (riid == IID_IMetaDataAssemblyImport) + { + *ppvObject = static_cast(this); + return true; + } + if (riid == IID_IMetaDataEmit || riid == IID_IMetaDataEmit2) + { + *ppvObject = static_cast(this); + return true; + } + if (riid == IID_IMetaDataAssemblyEmit) + { + *ppvObject = static_cast(this); + return true; + } + return false; + } + +public: + ThreadSafeImportEmit(IUnknown* controllingUnknown, minipal::com_ptr&& threadUnsafe, TImport* import, TEmit* emit) + : TearOffBase(controllingUnknown) + , _lock { } + , _threadUnsafe{ std::move(threadUnsafe) } + , _import{ import } + , _emit{ emit } + { + assert(_threadUnsafe.p != nullptr); + assert(_import != nullptr); + assert(_emit != nullptr); + } + + virtual ~ThreadSafeImportEmit() = default; + +public: // IMetaDataImport + STDMETHOD_(void, CloseEnum)(HCORENUM hEnum) override + { + std::lock_guard lock { this->_lock.GetReadLock() }; + return _import->CloseEnum(hEnum); + } + + STDMETHOD(CountEnum)(HCORENUM hEnum, ULONG *pulCount) override + { + std::lock_guard lock { this->_lock.GetReadLock() }; + return _import->CountEnum(hEnum, pulCount); + } + + STDMETHOD(ResetEnum)(HCORENUM hEnum, ULONG ulPos) override + { + std::lock_guard lock { this->_lock.GetReadLock() }; + return _import->ResetEnum(hEnum, ulPos); + } + + STDMETHOD(EnumTypeDefs)(HCORENUM *phEnum, mdTypeDef rTypeDefs[], + ULONG cMax, ULONG *pcTypeDefs) override + { + std::lock_guard lock { this->_lock.GetReadLock() }; + return _import->EnumTypeDefs(phEnum, rTypeDefs, cMax, pcTypeDefs); + } + + STDMETHOD(EnumInterfaceImpls)(HCORENUM *phEnum, mdTypeDef td, + mdInterfaceImpl rImpls[], ULONG cMax, + ULONG* pcImpls) override + { + std::lock_guard lock { this->_lock.GetReadLock() }; + return _import->EnumInterfaceImpls(phEnum, td, rImpls, cMax, pcImpls); + } + + STDMETHOD(EnumTypeRefs)(HCORENUM *phEnum, mdTypeRef rTypeRefs[], + ULONG cMax, ULONG* pcTypeRefs) override + { + std::lock_guard lock { this->_lock.GetReadLock() }; + return _import->EnumTypeRefs(phEnum, rTypeRefs, cMax, pcTypeRefs); + } + + STDMETHOD(FindTypeDefByName)( + LPCWSTR szTypeDef, + mdToken tkEnclosingClass, + mdTypeDef *ptd) override + { + std::lock_guard lock { this->_lock.GetReadLock() }; + return _import->FindTypeDefByName(szTypeDef, tkEnclosingClass, ptd); + } + + STDMETHOD(GetScopeProps)( + _Out_writes_to_opt_(cchName, *pchName) + LPWSTR szName, + ULONG cchName, + ULONG *pchName, + GUID *pmvid) override + { + std::lock_guard lock { this->_lock.GetReadLock() }; + return _import->GetScopeProps(szName, cchName, pchName, pmvid); + } + + STDMETHOD(GetModuleFromScope)( + mdModule *pmd) override + { + std::lock_guard lock { this->_lock.GetReadLock() }; + return _import->GetModuleFromScope(pmd); + } + + STDMETHOD(GetTypeDefProps)( + mdTypeDef td, + _Out_writes_to_opt_(cchTypeDef, *pchTypeDef) + LPWSTR szTypeDef, + ULONG cchTypeDef, + ULONG *pchTypeDef, + DWORD *pdwTypeDefFlags, + mdToken *ptkExtends) override + { + std::lock_guard lock { this->_lock.GetReadLock() }; + return _import->GetTypeDefProps(td, szTypeDef, cchTypeDef, pchTypeDef, pdwTypeDefFlags, ptkExtends); + } + + STDMETHOD(GetInterfaceImplProps)( + mdInterfaceImpl iiImpl, + mdTypeDef *pClass, + mdToken *ptkIface) override + { + std::lock_guard lock { this->_lock.GetReadLock() }; + return _import->GetInterfaceImplProps(iiImpl, pClass, ptkIface); + } + + STDMETHOD(GetTypeRefProps)( + mdTypeRef tr, + mdToken *ptkResolutionScope, + _Out_writes_to_opt_(cchName, *pchName) + LPWSTR szName, + ULONG cchName, + ULONG *pchName) override + { + std::lock_guard lock { this->_lock.GetReadLock() }; + return _import->GetTypeRefProps(tr, ptkResolutionScope, szName, cchName, pchName); + } + + STDMETHOD(ResolveTypeRef)(mdTypeRef tr, REFIID riid, IUnknown **ppIScope, mdTypeDef *ptd) override + { + std::lock_guard lock { this->_lock.GetReadLock() }; + return _import->ResolveTypeRef(tr, riid, ppIScope, ptd); + } + + STDMETHOD(EnumMembers)( + HCORENUM *phEnum, + mdTypeDef cl, + mdToken rMembers[], + ULONG cMax, + ULONG *pcTokens) override + { + std::lock_guard lock { this->_lock.GetReadLock() }; + return _import->EnumMembers(phEnum, cl, rMembers, cMax, pcTokens); + } + + STDMETHOD(EnumMembersWithName)( + HCORENUM *phEnum, + mdTypeDef cl, + LPCWSTR szName, + mdToken rMembers[], + ULONG cMax, + ULONG *pcTokens) override + { + std::lock_guard lock { this->_lock.GetReadLock() }; + return _import->EnumMembersWithName(phEnum, cl, szName, rMembers, cMax, pcTokens); + } + + STDMETHOD(EnumMethods)( + HCORENUM *phEnum, + mdTypeDef cl, + mdMethodDef rMethods[], + ULONG cMax, + ULONG *pcTokens) override + { + std::lock_guard lock { this->_lock.GetReadLock() }; + return _import->EnumMethods(phEnum, cl, rMethods, cMax, pcTokens); + } + + STDMETHOD(EnumMethodsWithName)( + HCORENUM *phEnum, + mdTypeDef cl, + LPCWSTR szName, + mdMethodDef rMethods[], + ULONG cMax, + ULONG *pcTokens) override + { + std::lock_guard lock { this->_lock.GetReadLock() }; + return _import->EnumMethodsWithName(phEnum, cl, szName, rMethods, cMax, pcTokens); + } + + STDMETHOD(EnumFields)( + HCORENUM *phEnum, + mdTypeDef cl, + mdFieldDef rFields[], + ULONG cMax, + ULONG *pcTokens) override + { + std::lock_guard lock { this->_lock.GetReadLock() }; + return _import->EnumFields(phEnum, cl, rFields, cMax, pcTokens); + } + + STDMETHOD(EnumFieldsWithName)( + HCORENUM *phEnum, + mdTypeDef cl, + LPCWSTR szName, + mdFieldDef rFields[], + ULONG cMax, + ULONG *pcTokens) override + { + std::lock_guard lock { this->_lock.GetReadLock() }; + return _import->EnumFieldsWithName(phEnum, cl, szName, rFields, cMax, pcTokens); + } + + STDMETHOD(EnumParams)( + HCORENUM *phEnum, + mdMethodDef mb, + mdParamDef rParams[], + ULONG cMax, + ULONG *pcTokens) override + { + std::lock_guard lock { this->_lock.GetReadLock() }; + return _import->EnumParams(phEnum, mb, rParams, cMax, pcTokens); + } + + STDMETHOD(EnumMemberRefs)( + HCORENUM *phEnum, + mdToken tkParent, + mdMemberRef rMemberRefs[], + ULONG cMax, + ULONG *pcTokens) override + { + std::lock_guard lock { this->_lock.GetReadLock() }; + return _import->EnumMemberRefs(phEnum, tkParent, rMemberRefs, cMax, pcTokens); + } + + STDMETHOD(EnumMethodImpls)( + HCORENUM *phEnum, + mdTypeDef td, + mdToken rMethodBody[], + mdToken rMethodDecl[], + ULONG cMax, + ULONG *pcTokens) override + { + std::lock_guard lock { this->_lock.GetReadLock() }; + return _import->EnumMethodImpls(phEnum, td, rMethodBody, rMethodDecl, cMax, pcTokens); + } + + STDMETHOD(EnumPermissionSets)( + HCORENUM *phEnum, + mdToken tk, + DWORD dwActions, + mdPermission rPermission[], + ULONG cMax, + ULONG *pcTokens) override + { + std::lock_guard lock { this->_lock.GetReadLock() }; + return _import->EnumPermissionSets(phEnum, tk, dwActions, rPermission, cMax, pcTokens); + } + + STDMETHOD(FindMember)( + mdTypeDef td, + LPCWSTR szName, + PCCOR_SIGNATURE pvSigBlob, + ULONG cbSigBlob, + mdToken *pmb) override + { + std::lock_guard lock { this->_lock.GetReadLock() }; + return _import->FindMember(td, szName, pvSigBlob, cbSigBlob, pmb); + } + + STDMETHOD(FindMethod)( + mdTypeDef td, + LPCWSTR szName, + PCCOR_SIGNATURE pvSigBlob, + ULONG cbSigBlob, + mdMethodDef *pmb) override + { + std::lock_guard lock { this->_lock.GetReadLock() }; + return _import->FindMethod(td, szName, pvSigBlob, cbSigBlob, pmb); + } + + STDMETHOD(FindField)( + mdTypeDef td, + LPCWSTR szName, + PCCOR_SIGNATURE pvSigBlob, + ULONG cbSigBlob, + mdFieldDef *pmb) override + { + std::lock_guard lock { this->_lock.GetReadLock() }; + return _import->FindField(td, szName, pvSigBlob, cbSigBlob, pmb); + } + + STDMETHOD(FindMemberRef)( + mdTypeRef td, + LPCWSTR szName, + PCCOR_SIGNATURE pvSigBlob, + ULONG cbSigBlob, + mdMemberRef *pmr) override + { + std::lock_guard lock { this->_lock.GetReadLock() }; + return _import->FindMemberRef(td, szName, pvSigBlob, cbSigBlob, pmr); + } + + STDMETHOD (GetMethodProps)( + mdMethodDef mb, + mdTypeDef *pClass, + _Out_writes_to_opt_(cchMethod, *pchMethod) + LPWSTR szMethod, + ULONG cchMethod, + ULONG *pchMethod, + DWORD *pdwAttr, + PCCOR_SIGNATURE *ppvSigBlob, + ULONG *pcbSigBlob, + ULONG *pulCodeRVA, + DWORD *pdwImplFlags) override + { + std::lock_guard lock { this->_lock.GetReadLock() }; + return _import->GetMethodProps(mb, pClass, szMethod, cchMethod, pchMethod, pdwAttr, ppvSigBlob, pcbSigBlob, pulCodeRVA, pdwImplFlags); + } + + STDMETHOD(GetMemberRefProps)( + mdMemberRef mr, + mdToken *ptk, + _Out_writes_to_opt_(cchMember, *pchMember) + LPWSTR szMember, + ULONG cchMember, + ULONG *pchMember, + PCCOR_SIGNATURE *ppvSigBlob, + ULONG *pbSig) override + { + std::lock_guard lock { this->_lock.GetReadLock() }; + return _import->GetMemberRefProps(mr, ptk, szMember, cchMember, pchMember, ppvSigBlob, pbSig); + } + + STDMETHOD(EnumProperties)( + HCORENUM *phEnum, + mdTypeDef td, + mdProperty rProperties[], + ULONG cMax, + ULONG *pcProperties) override + { + std::lock_guard lock { this->_lock.GetReadLock() }; + return _import->EnumProperties(phEnum, td, rProperties, cMax, pcProperties); + } + + STDMETHOD(EnumEvents)( + HCORENUM *phEnum, + mdTypeDef td, + mdEvent rEvents[], + ULONG cMax, + ULONG *pcEvents) override + { + std::lock_guard lock { this->_lock.GetReadLock() }; + return _import->EnumEvents(phEnum, td, rEvents, cMax, pcEvents); + } + + STDMETHOD(GetEventProps)( + mdEvent ev, + mdTypeDef *pClass, + LPCWSTR szEvent, + ULONG cchEvent, + ULONG *pchEvent, + DWORD *pdwEventFlags, + mdToken *ptkEventType, + mdMethodDef *pmdAddOn, + mdMethodDef *pmdRemoveOn, + mdMethodDef *pmdFire, + mdMethodDef rmdOtherMethod[], + ULONG cMax, + ULONG *pcOtherMethod) override + { + std::lock_guard lock { this->_lock.GetReadLock() }; + return _import->GetEventProps(ev, pClass, szEvent, cchEvent, pchEvent, pdwEventFlags, ptkEventType, pmdAddOn, pmdRemoveOn, pmdFire, rmdOtherMethod, cMax, pcOtherMethod); + } + + STDMETHOD(EnumMethodSemantics)( + HCORENUM *phEnum, + mdMethodDef mb, + mdToken rEventProp[], + ULONG cMax, + ULONG *pcEventProp) override + { + std::lock_guard lock { this->_lock.GetReadLock() }; + return _import->EnumMethodSemantics(phEnum, mb, rEventProp, cMax, pcEventProp); + } + + STDMETHOD(GetMethodSemantics)( + mdMethodDef mb, + mdToken tkEventProp, + DWORD *pdwSemanticsFlags) override + { + std::lock_guard lock { this->_lock.GetReadLock() }; + return _import->GetMethodSemantics(mb, tkEventProp, pdwSemanticsFlags); + } + + STDMETHOD(GetClassLayout) ( + mdTypeDef td, + DWORD *pdwPackSize, + COR_FIELD_OFFSET rFieldOffset[], + ULONG cMax, + ULONG *pcFieldOffset, + ULONG *pulClassSize) override + { + std::lock_guard lock { this->_lock.GetReadLock() }; + return _import->GetClassLayout(td, pdwPackSize, rFieldOffset, cMax, pcFieldOffset, pulClassSize); + } + + STDMETHOD(GetFieldMarshal) ( + mdToken tk, + PCCOR_SIGNATURE *ppvNativeType, + ULONG *pcbNativeType) override + { + std::lock_guard lock { this->_lock.GetReadLock() }; + return _import->GetFieldMarshal(tk, ppvNativeType, pcbNativeType); + } + + STDMETHOD(GetRVA)( + mdToken tk, + ULONG *pulCodeRVA, + DWORD *pdwImplFlags) override + { + std::lock_guard lock { this->_lock.GetReadLock() }; + return _import->GetRVA(tk, pulCodeRVA, pdwImplFlags); + } + + STDMETHOD(GetPermissionSetProps) ( + mdPermission pm, + DWORD *pdwAction, + void const **ppvPermission, + ULONG *pcbPermission) override + { + std::lock_guard lock { this->_lock.GetReadLock() }; + return _import->GetPermissionSetProps(pm, pdwAction, ppvPermission, pcbPermission); + } + + STDMETHOD(GetSigFromToken)( + mdSignature mdSig, + PCCOR_SIGNATURE *ppvSig, + ULONG *pcbSig) override + { + std::lock_guard lock { this->_lock.GetReadLock() }; + return _import->GetSigFromToken(mdSig, ppvSig, pcbSig); + } + + STDMETHOD(GetModuleRefProps)( + mdModuleRef mur, + _Out_writes_to_opt_(cchName, *pchName) + LPWSTR szName, + ULONG cchName, + ULONG *pchName) override + { + std::lock_guard lock { this->_lock.GetReadLock() }; + return _import->GetModuleRefProps(mur, szName, cchName, pchName); + } + + STDMETHOD(EnumModuleRefs)( + HCORENUM *phEnum, + mdModuleRef rModuleRefs[], + ULONG cMax, + ULONG *pcModuleRefs) override + { + std::lock_guard lock { this->_lock.GetReadLock() }; + return _import->EnumModuleRefs(phEnum, rModuleRefs, cMax, pcModuleRefs); + } + + STDMETHOD(GetTypeSpecFromToken)( + mdTypeSpec typespec, + PCCOR_SIGNATURE *ppvSig, + ULONG *pcbSig) override + { + std::lock_guard lock { this->_lock.GetReadLock() }; + return _import->GetTypeSpecFromToken(typespec, ppvSig, pcbSig); + } + + STDMETHOD(GetNameFromToken)( // Not Recommended! May be removed! + mdToken tk, + MDUTF8CSTR *pszUtf8NamePtr) override + { + std::lock_guard lock { this->_lock.GetReadLock() }; + return _import->GetNameFromToken(tk, pszUtf8NamePtr); + } + + STDMETHOD(EnumUnresolvedMethods)( + HCORENUM *phEnum, + mdToken rMethods[], + ULONG cMax, + ULONG *pcTokens) override + { + std::lock_guard lock { this->_lock.GetReadLock() }; + return _import->EnumUnresolvedMethods(phEnum, rMethods, cMax, pcTokens); + } + + STDMETHOD(GetUserString)( + mdString stk, + _Out_writes_to_opt_(cchString, *pchString) + LPWSTR szString, + ULONG cchString, + ULONG *pchString) override + { + std::lock_guard lock { this->_lock.GetReadLock() }; + return _import->GetUserString(stk, szString, cchString, pchString); + } + + STDMETHOD(GetPinvokeMap)( + mdToken tk, + DWORD *pdwMappingFlags, + _Out_writes_to_opt_(cchImportName, *pchImportName) + LPWSTR szImportName, + ULONG cchImportName, + ULONG *pchImportName, + mdModuleRef *pmrImportDLL) override + { + std::lock_guard lock { this->_lock.GetReadLock() }; + return _import->GetPinvokeMap(tk, pdwMappingFlags, szImportName, cchImportName, pchImportName, pmrImportDLL); + } + + STDMETHOD(EnumSignatures)( + HCORENUM *phEnum, + mdSignature rSignatures[], + ULONG cMax, + ULONG *pcSignatures) override + { + std::lock_guard lock { this->_lock.GetReadLock() }; + return _import->EnumSignatures(phEnum, rSignatures, cMax, pcSignatures); + } + + STDMETHOD(EnumTypeSpecs)( + HCORENUM *phEnum, + mdTypeSpec rTypeSpecs[], + ULONG cMax, + ULONG *pcTypeSpecs) override + { + std::lock_guard lock { this->_lock.GetReadLock() }; + return _import->EnumTypeSpecs(phEnum, rTypeSpecs, cMax, pcTypeSpecs); + } + + STDMETHOD(EnumUserStrings)( + HCORENUM *phEnum, + mdString rStrings[], + ULONG cMax, + ULONG *pcStrings) override + { + std::lock_guard lock { this->_lock.GetReadLock() }; + return _import->EnumUserStrings(phEnum, rStrings, cMax, pcStrings); + } + + STDMETHOD(GetParamForMethodIndex)( + mdMethodDef md, + ULONG ulParamSeq, + mdParamDef *ppd) override + { + std::lock_guard lock { this->_lock.GetReadLock() }; + return _import->GetParamForMethodIndex(md, ulParamSeq, ppd); + } + + STDMETHOD(EnumCustomAttributes)( + HCORENUM *phEnum, + mdToken tk, + mdToken tkType, + mdCustomAttribute rCustomAttributes[], + ULONG cMax, + ULONG *pcCustomAttributes) override + { + std::lock_guard lock { this->_lock.GetReadLock() }; + return _import->EnumCustomAttributes(phEnum, tk, tkType, rCustomAttributes, cMax, pcCustomAttributes); + } + + STDMETHOD(GetCustomAttributeProps)( + mdCustomAttribute cv, + mdToken *ptkObj, + mdToken *ptkType, + void const **ppBlob, + ULONG *pcbSize) override + { + std::lock_guard lock { this->_lock.GetReadLock() }; + return _import->GetCustomAttributeProps(cv, ptkObj, ptkType, ppBlob, pcbSize); + } + + STDMETHOD(FindTypeRef)( + mdToken tkResolutionScope, + LPCWSTR szName, + mdTypeRef *ptr) override + { + std::lock_guard lock { this->_lock.GetReadLock() }; + return _import->FindTypeRef(tkResolutionScope, szName, ptr); + } + + STDMETHOD(GetMemberProps)( + mdToken mb, + mdTypeDef *pClass, + _Out_writes_to_opt_(cchMember, *pchMember) + LPWSTR szMember, + ULONG cchMember, + ULONG *pchMember, + DWORD *pdwAttr, + PCCOR_SIGNATURE *ppvSigBlob, + ULONG *pcbSigBlob, + ULONG *pulCodeRVA, + DWORD *pdwImplFlags, + DWORD *pdwCPlusTypeFlag, + UVCP_CONSTANT *ppValue, + ULONG *pcchValue) override + { + std::lock_guard lock { this->_lock.GetReadLock() }; + return _import->GetMemberProps(mb, pClass, szMember, cchMember, pchMember, pdwAttr, ppvSigBlob, pcbSigBlob, pulCodeRVA, pdwImplFlags, pdwCPlusTypeFlag, ppValue, pcchValue); + } + + STDMETHOD(GetFieldProps)( + mdFieldDef mb, + mdTypeDef *pClass, + _Out_writes_to_opt_(cchField, *pchField) + LPWSTR szField, + ULONG cchField, + ULONG *pchField, + DWORD *pdwAttr, + PCCOR_SIGNATURE *ppvSigBlob, + ULONG *pcbSigBlob, + DWORD *pdwCPlusTypeFlag, + UVCP_CONSTANT *ppValue, + ULONG *pcchValue) override + { + std::lock_guard lock { this->_lock.GetReadLock() }; + return _import->GetFieldProps(mb, pClass, szField, cchField, pchField, pdwAttr, ppvSigBlob, pcbSigBlob, pdwCPlusTypeFlag, ppValue, pcchValue); + } + + STDMETHOD(GetPropertyProps)( + mdProperty prop, + mdTypeDef *pClass, + LPCWSTR szProperty, + ULONG cchProperty, + ULONG *pchProperty, + DWORD *pdwPropFlags, + PCCOR_SIGNATURE *ppvSig, + ULONG *pbSig, + DWORD *pdwCPlusTypeFlag, + UVCP_CONSTANT *ppDefaultValue, + ULONG *pcchDefaultValue, + mdMethodDef *pmdSetter, + mdMethodDef *pmdGetter, + mdMethodDef rmdOtherMethod[], + ULONG cMax, + ULONG *pcOtherMethod) override + { + std::lock_guard lock { this->_lock.GetReadLock() }; + return _import->GetPropertyProps(prop, pClass, szProperty, cchProperty, pchProperty, pdwPropFlags, ppvSig, pbSig, pdwCPlusTypeFlag, ppDefaultValue, pcchDefaultValue, pmdSetter, pmdGetter, rmdOtherMethod, cMax, pcOtherMethod); + } + + STDMETHOD(GetParamProps)( + mdParamDef tk, + mdMethodDef *pmd, + ULONG *pulSequence, + _Out_writes_to_opt_(cchName, *pchName) + LPWSTR szName, + ULONG cchName, + ULONG *pchName, + DWORD *pdwAttr, + DWORD *pdwCPlusTypeFlag, + UVCP_CONSTANT *ppValue, + ULONG *pcchValue) override + { + std::lock_guard lock { this->_lock.GetReadLock() }; + return _import->GetParamProps(tk, pmd, pulSequence, szName, cchName, pchName, pdwAttr, pdwCPlusTypeFlag, ppValue, pcchValue); + } + + STDMETHOD(GetCustomAttributeByName)( + mdToken tkObj, + LPCWSTR szName, + void const** ppData, + ULONG *pcbData) override + { + std::lock_guard lock { this->_lock.GetReadLock() }; + return _import->GetCustomAttributeByName(tkObj, szName, ppData, pcbData); + } + + STDMETHOD_(BOOL, IsValidToken)( + mdToken tk) override + { + std::lock_guard lock { this->_lock.GetReadLock() }; + return _import->IsValidToken(tk); + } + + STDMETHOD(GetNestedClassProps)( + mdTypeDef tdNestedClass, + mdTypeDef *ptdEnclosingClass) override + { + std::lock_guard lock { this->_lock.GetReadLock() }; + return _import->GetNestedClassProps(tdNestedClass, ptdEnclosingClass); + } + + STDMETHOD(GetNativeCallConvFromSig)( + void const *pvSig, + ULONG cbSig, + ULONG *pCallConv) override + { + std::lock_guard lock { this->_lock.GetReadLock() }; + return _import->GetNativeCallConvFromSig(pvSig, cbSig, pCallConv); + } + + STDMETHOD(IsGlobal)( + mdToken pd, + int *pbGlobal) override + { + std::lock_guard lock { this->_lock.GetReadLock() }; + return _import->IsGlobal(pd, pbGlobal); + } + +public: // IMetaDataImport2 + STDMETHOD(EnumGenericParams)( + HCORENUM *phEnum, + mdToken tk, + mdGenericParam rGenericParams[], + ULONG cMax, + ULONG *pcGenericParams) override + { + std::lock_guard lock { this->_lock.GetReadLock() }; + return _import->EnumGenericParams(phEnum, tk, rGenericParams, cMax, pcGenericParams); + } + + STDMETHOD(GetGenericParamProps)( + mdGenericParam gp, + ULONG *pulParamSeq, + DWORD *pdwParamFlags, + mdToken *ptOwner, + DWORD *reserved, + _Out_writes_to_opt_(cchName, *pchName) + LPWSTR wzname, + ULONG cchName, + ULONG *pchName) override + { + std::lock_guard lock { this->_lock.GetReadLock() }; + return _import->GetGenericParamProps(gp, pulParamSeq, pdwParamFlags, ptOwner, reserved, wzname, cchName, pchName); + } + + STDMETHOD(GetMethodSpecProps)( + mdMethodSpec mi, + mdToken *tkParent, + PCCOR_SIGNATURE *ppvSigBlob, + ULONG *pcbSigBlob) override + { + std::lock_guard lock { this->_lock.GetReadLock() }; + return _import->GetMethodSpecProps(mi, tkParent, ppvSigBlob, pcbSigBlob); + } + + STDMETHOD(EnumGenericParamConstraints)( + HCORENUM *phEnum, + mdGenericParam tk, + mdGenericParamConstraint rGenericParamConstraints[], + ULONG cMax, + ULONG *pcGenericParamConstraints) override + { + std::lock_guard lock { this->_lock.GetReadLock() }; + return _import->EnumGenericParamConstraints(phEnum, tk, rGenericParamConstraints, cMax, pcGenericParamConstraints); + } + + STDMETHOD(GetGenericParamConstraintProps)( + mdGenericParamConstraint gpc, + mdGenericParam *ptGenericParam, + mdToken *ptkConstraintType) override + { + std::lock_guard lock { this->_lock.GetReadLock() }; + return _import->GetGenericParamConstraintProps(gpc, ptGenericParam, ptkConstraintType); + } + + STDMETHOD(GetPEKind)( + DWORD* pdwPEKind, + DWORD* pdwMAchine) override + { + std::lock_guard lock { this->_lock.GetReadLock() }; + return _import->GetPEKind(pdwPEKind, pdwMAchine); + } + + STDMETHOD(GetVersionString)( + _Out_writes_to_opt_(ccBufSize, *pccBufSize) + LPWSTR pwzBuf, + DWORD ccBufSize, + DWORD *pccBufSize) override + { + std::lock_guard lock { this->_lock.GetReadLock() }; + return _import->GetVersionString(pwzBuf, ccBufSize, pccBufSize); + } + + STDMETHOD(EnumMethodSpecs)( + HCORENUM *phEnum, + mdToken tk, + mdMethodSpec rMethodSpecs[], + ULONG cMax, + ULONG *pcMethodSpecs) override + { + std::lock_guard lock { this->_lock.GetReadLock() }; + return _import->EnumMethodSpecs(phEnum, tk, rMethodSpecs, cMax, pcMethodSpecs); + } + +public: // IMetaDataAssemblyImport + STDMETHOD(GetAssemblyProps)( + mdAssembly mda, + void const** ppbPublicKey, + ULONG* pcbPublicKey, + ULONG* pulHashAlgId, + _Out_writes_to_opt_(cchName, *pchName) LPWSTR szName, + ULONG cchName, + ULONG* pchName, + ASSEMBLYMETADATA* pMetaData, + DWORD* pdwAssemblyFlags) override + { + std::lock_guard lock { this->_lock.GetReadLock() }; + return _import->GetAssemblyProps(mda, ppbPublicKey, pcbPublicKey, pulHashAlgId, szName, cchName, pchName, pMetaData, pdwAssemblyFlags); + } + + STDMETHOD(GetAssemblyRefProps)( + mdAssemblyRef mdar, + void const** ppbPublicKeyOrToken, + ULONG* pcbPublicKeyOrToken, + _Out_writes_to_opt_(cchName, *pchName)LPWSTR szName, + ULONG cchName, + ULONG* pchName, + ASSEMBLYMETADATA* pMetaData, + void const** ppbHashValue, + ULONG* pcbHashValue, + DWORD* pdwAssemblyRefFlags) override + { + std::lock_guard lock { this->_lock.GetReadLock() }; + return _import->GetAssemblyRefProps(mdar, ppbPublicKeyOrToken, pcbPublicKeyOrToken, szName, cchName, pchName, pMetaData, ppbHashValue, pcbHashValue, pdwAssemblyRefFlags); + } + + STDMETHOD(GetFileProps)( + mdFile mdf, + _Out_writes_to_opt_(cchName, *pchName) LPWSTR szName, + ULONG cchName, + ULONG* pchName, + void const** ppbHashValue, + ULONG* pcbHashValue, + DWORD* pdwFileFlags) override + { + std::lock_guard lock { this->_lock.GetReadLock() }; + return _import->GetFileProps(mdf, szName, cchName, pchName, ppbHashValue, pcbHashValue, pdwFileFlags); + } + + STDMETHOD(GetExportedTypeProps)( + mdExportedType mdct, + _Out_writes_to_opt_(cchName, *pchName) LPWSTR szName, + ULONG cchName, + ULONG* pchName, + mdToken* ptkImplementation, + mdTypeDef* ptkTypeDef, + DWORD* pdwExportedTypeFlags) override + { + std::lock_guard lock { this->_lock.GetReadLock() }; + return _import->GetExportedTypeProps(mdct, szName, cchName, pchName, ptkImplementation, ptkTypeDef, pdwExportedTypeFlags); + } + + STDMETHOD(GetManifestResourceProps)( + mdManifestResource mdmr, + _Out_writes_to_opt_(cchName, *pchName)LPWSTR szName, + ULONG cchName, + ULONG* pchName, + mdToken* ptkImplementation, + DWORD* pdwOffset, + DWORD* pdwResourceFlags) override + { + std::lock_guard lock { this->_lock.GetReadLock() }; + return _import->GetManifestResourceProps(mdmr, szName, cchName, pchName, ptkImplementation, pdwOffset, pdwResourceFlags); + } + + STDMETHOD(EnumAssemblyRefs)( + HCORENUM* phEnum, + mdAssemblyRef rAssemblyRefs[], + ULONG cMax, + ULONG* pcTokens) override + { + std::lock_guard lock { this->_lock.GetReadLock() }; + return _import->EnumAssemblyRefs(phEnum, rAssemblyRefs, cMax, pcTokens); + } + + STDMETHOD(EnumFiles)( + HCORENUM* phEnum, + mdFile rFiles[], + ULONG cMax, + ULONG* pcTokens) override + { + std::lock_guard lock { this->_lock.GetReadLock() }; + return _import->EnumFiles(phEnum, rFiles, cMax, pcTokens); + } + + STDMETHOD(EnumExportedTypes)( + HCORENUM* phEnum, + mdExportedType rExportedTypes[], + ULONG cMax, + ULONG* pcTokens) override + { + std::lock_guard lock { this->_lock.GetReadLock() }; + return _import->EnumExportedTypes(phEnum, rExportedTypes, cMax, pcTokens); + } + + STDMETHOD(EnumManifestResources)( + HCORENUM* phEnum, + mdManifestResource rManifestResources[], + ULONG cMax, + ULONG* pcTokens) override + { + std::lock_guard lock { this->_lock.GetReadLock() }; + return _import->EnumManifestResources(phEnum, rManifestResources, cMax, pcTokens); + } + + STDMETHOD(GetAssemblyFromScope)( + mdAssembly* ptkAssembly) override + { + std::lock_guard lock { this->_lock.GetReadLock() }; + return _import->GetAssemblyFromScope(ptkAssembly); + } + + STDMETHOD(FindExportedTypeByName)( + LPCWSTR szName, + mdToken mdtExportedType, + mdExportedType* ptkExportedType) override + { + std::lock_guard lock { this->_lock.GetReadLock() }; + return _import->FindExportedTypeByName(szName, mdtExportedType, ptkExportedType); + } + + STDMETHOD(FindManifestResourceByName)( + LPCWSTR szName, + mdManifestResource* ptkManifestResource) override + { + std::lock_guard lock { this->_lock.GetReadLock() }; + return _import->FindManifestResourceByName(szName, ptkManifestResource); + + } + + STDMETHOD(FindAssembliesByName)( + LPCWSTR szAppBase, + LPCWSTR szPrivateBin, + LPCWSTR szAssemblyName, + IUnknown* ppIUnk[], + ULONG cMax, + ULONG* pcAssemblies) override + { + std::lock_guard lock { this->_lock.GetReadLock() }; + return _import->FindAssembliesByName(szAppBase, szPrivateBin, szAssemblyName, ppIUnk, cMax, pcAssemblies); + } + +public: // IMetaDataEmit + STDMETHOD(SetModuleProps)( + LPCWSTR szName) override + { + std::lock_guard lock { this->_lock.GetWriteLock() }; + return _emit->SetModuleProps(szName); + } + + STDMETHOD(Save)( + LPCWSTR szFile, + DWORD dwSaveFlags) override + { + std::lock_guard lock { this->_lock.GetWriteLock() }; + return _emit->Save(szFile, dwSaveFlags); + } + + STDMETHOD(SaveToStream)( + IStream *pIStream, + DWORD dwSaveFlags) override + { + std::lock_guard lock { this->_lock.GetWriteLock() }; + return _emit->SaveToStream(pIStream, dwSaveFlags); + + } + + STDMETHOD(GetSaveSize)( + CorSaveSize fSave, + DWORD *pdwSaveSize) override + { + std::lock_guard lock { this->_lock.GetWriteLock() }; + return _emit->GetSaveSize(fSave, pdwSaveSize); + } + + STDMETHOD(DefineTypeDef)( + LPCWSTR szTypeDef, + DWORD dwTypeDefFlags, + mdToken tkExtends, + mdToken rtkImplements[], + mdTypeDef *ptd) override + { + std::lock_guard lock { this->_lock.GetWriteLock() }; + return _emit->DefineTypeDef(szTypeDef, dwTypeDefFlags, tkExtends, rtkImplements, ptd); + } + + STDMETHOD(DefineNestedType)( + LPCWSTR szTypeDef, + DWORD dwTypeDefFlags, + mdToken tkExtends, + mdToken rtkImplements[], + mdTypeDef tdEncloser, + mdTypeDef *ptd) override + { + std::lock_guard lock { this->_lock.GetWriteLock() }; + return _emit->DefineNestedType(szTypeDef, dwTypeDefFlags, tkExtends, rtkImplements, tdEncloser, ptd); + } + + STDMETHOD(SetHandler)( + IUnknown *pUnk) override + { + std::lock_guard lock { this->_lock.GetWriteLock() }; + return _emit->SetHandler(pUnk); + } + + STDMETHOD(DefineMethod)( + mdTypeDef td, + LPCWSTR szName, + DWORD dwMethodFlags, + PCCOR_SIGNATURE pvSigBlob, + ULONG cbSigBlob, + ULONG ulCodeRVA, + DWORD dwImplFlags, + mdMethodDef *pmd) override + { + std::lock_guard lock { this->_lock.GetWriteLock() }; + return _emit->DefineMethod(td, szName, dwMethodFlags, pvSigBlob, cbSigBlob, ulCodeRVA, dwImplFlags, pmd); + } + + STDMETHOD(DefineMethodImpl)( + mdTypeDef td, + mdToken tkBody, + mdToken tkDecl) override + { + std::lock_guard lock { this->_lock.GetWriteLock() }; + return _emit->DefineMethodImpl(td, tkBody, tkDecl); + } + + STDMETHOD(DefineTypeRefByName)( + mdToken tkResolutionScope, + LPCWSTR szName, + mdTypeRef *ptr) override + { + std::lock_guard lock { this->_lock.GetWriteLock() }; + return _emit->DefineTypeRefByName(tkResolutionScope, szName, ptr); + } + + STDMETHOD(DefineImportType)( + IMetaDataAssemblyImport *pAssemImport, + void const *pbHashValue, + ULONG cbHashValue, + IMetaDataImport *pImport, + mdTypeDef tdImport, + IMetaDataAssemblyEmit *pAssemEmit, + mdTypeRef *ptr) override + { + std::lock_guard lock { this->_lock.GetWriteLock() }; + return _emit->DefineImportType(pAssemImport, pbHashValue, cbHashValue, pImport, tdImport, pAssemEmit, ptr); + } + + STDMETHOD(DefineMemberRef)( + mdToken tkImport, + LPCWSTR szName, + PCCOR_SIGNATURE pvSigBlob, + ULONG cbSigBlob, + mdMemberRef *pmr) override + { + std::lock_guard lock { this->_lock.GetWriteLock() }; + return _emit->DefineMemberRef(tkImport, szName, pvSigBlob, cbSigBlob, pmr); + } + + STDMETHOD(DefineImportMember)( + IMetaDataAssemblyImport *pAssemImport, + void const *pbHashValue, + ULONG cbHashValue, + IMetaDataImport *pImport, + mdToken mbMember, + IMetaDataAssemblyEmit *pAssemEmit, + mdToken tkParent, + mdMemberRef *pmr) override + { + // No need to lock this function. The implementation will lock when necessary + return ::DefineImportMember(this, pAssemImport, pbHashValue, cbHashValue, pImport, mbMember, pAssemEmit, tkParent, pmr); + } + + STDMETHOD(DefineEvent) ( + mdTypeDef td, + LPCWSTR szEvent, + DWORD dwEventFlags, + mdToken tkEventType, + mdMethodDef mdAddOn, + mdMethodDef mdRemoveOn, + mdMethodDef mdFire, + mdMethodDef rmdOtherMethods[], + mdEvent *pmdEvent) override + { + std::lock_guard lock { this->_lock.GetWriteLock() }; + return _emit->DefineEvent(td, szEvent, dwEventFlags, tkEventType, mdAddOn, mdRemoveOn, mdFire, rmdOtherMethods, pmdEvent); + } + + STDMETHOD(SetClassLayout) ( + mdTypeDef td, + DWORD dwPackSize, + COR_FIELD_OFFSET rFieldOffsets[], + ULONG ulClassSize) override + { + std::lock_guard lock { this->_lock.GetWriteLock() }; + return _emit->SetClassLayout(td, dwPackSize, rFieldOffsets, ulClassSize); + } + + STDMETHOD(DeleteClassLayout) ( + mdTypeDef td) override + { + std::lock_guard lock { this->_lock.GetWriteLock() }; + return _emit->DeleteClassLayout(td); + } + + STDMETHOD(SetFieldMarshal) ( + mdToken tk, + PCCOR_SIGNATURE pvNativeType, + ULONG cbNativeType) override + { + std::lock_guard lock { this->_lock.GetWriteLock() }; + return _emit->SetFieldMarshal(tk, pvNativeType, cbNativeType); + } + + STDMETHOD(DeleteFieldMarshal) ( + mdToken tk) override + { + std::lock_guard lock { this->_lock.GetWriteLock() }; + return _emit->DeleteFieldMarshal(tk); + } + + STDMETHOD(DefinePermissionSet) ( + mdToken tk, + DWORD dwAction, + void const *pvPermission, + ULONG cbPermission, + mdPermission *ppm) override + { + std::lock_guard lock { this->_lock.GetWriteLock() }; + return _emit->DefinePermissionSet(tk, dwAction, pvPermission, cbPermission, ppm); + } + + STDMETHOD(SetRVA)( + mdMethodDef md, + ULONG ulRVA) override + { + std::lock_guard lock { this->_lock.GetWriteLock() }; + return _emit->SetRVA(md, ulRVA); + } + + STDMETHOD(GetTokenFromSig)( + PCCOR_SIGNATURE pvSig, + ULONG cbSig, + mdSignature *pmsig) override + { + std::lock_guard lock { this->_lock.GetWriteLock() }; + return _emit->GetTokenFromSig(pvSig, cbSig, pmsig); + } + + STDMETHOD(DefineModuleRef)( + LPCWSTR szName, + mdModuleRef *pmur) override + { + std::lock_guard lock { this->_lock.GetWriteLock() }; + return _emit->DefineModuleRef(szName, pmur); + } + + STDMETHOD(SetParent)( + mdMemberRef mr, + mdToken tk) override + { + std::lock_guard lock { this->_lock.GetWriteLock() }; + return _emit->SetParent(mr, tk); + } + + STDMETHOD(GetTokenFromTypeSpec)( + PCCOR_SIGNATURE pvSig, + ULONG cbSig, + mdTypeSpec *ptypespec) override + { + std::lock_guard lock { this->_lock.GetWriteLock() }; + return _emit->GetTokenFromTypeSpec(pvSig, cbSig, ptypespec); + } + + STDMETHOD(SaveToMemory)( + void *pbData, + ULONG cbData) override + { + std::lock_guard lock { this->_lock.GetWriteLock() }; + return _emit->SaveToMemory(pbData, cbData); + } + + STDMETHOD(DefineUserString)( + LPCWSTR szString, + ULONG cchString, + mdString *pstk) override + { + std::lock_guard lock { this->_lock.GetWriteLock() }; + return _emit->DefineUserString(szString, cchString, pstk); + } + + STDMETHOD(DeleteToken)( + mdToken tkObj) override + { + std::lock_guard lock { this->_lock.GetWriteLock() }; + return _emit->DeleteToken(tkObj); + } + + STDMETHOD(SetMethodProps)( + mdMethodDef md, + DWORD dwMethodFlags, + ULONG ulCodeRVA, + DWORD dwImplFlags) override + { + std::lock_guard lock { this->_lock.GetWriteLock() }; + return _emit->SetMethodProps(md, dwMethodFlags, ulCodeRVA, dwImplFlags); + } + + STDMETHOD(SetTypeDefProps)( + mdTypeDef td, + DWORD dwTypeDefFlags, + mdToken tkExtends, + mdToken rtkImplements[]) override + { + std::lock_guard lock { this->_lock.GetWriteLock() }; + return _emit->SetTypeDefProps(td, dwTypeDefFlags, tkExtends, rtkImplements); + } + + STDMETHOD(SetEventProps)( + mdEvent ev, + DWORD dwEventFlags, + mdToken tkEventType, + mdMethodDef mdAddOn, + mdMethodDef mdRemoveOn, + mdMethodDef mdFire, + mdMethodDef rmdOtherMethods[]) override + { + std::lock_guard lock { this->_lock.GetWriteLock() }; + return _emit->SetEventProps(ev, dwEventFlags, tkEventType, mdAddOn, mdRemoveOn, mdFire, rmdOtherMethods); + } + + STDMETHOD(SetPermissionSetProps)( + mdToken tk, + DWORD dwAction, + void const *pvPermission, + ULONG cbPermission, + mdPermission *ppm) override + { + std::lock_guard lock { this->_lock.GetWriteLock() }; + return _emit->SetPermissionSetProps(tk, dwAction, pvPermission, cbPermission, ppm); + } + + STDMETHOD(DefinePinvokeMap)( + mdToken tk, + DWORD dwMappingFlags, + LPCWSTR szImportName, + mdModuleRef mrImportDLL) override + { + std::lock_guard lock { this->_lock.GetWriteLock() }; + return _emit->DefinePinvokeMap(tk, dwMappingFlags, szImportName, mrImportDLL); + } + + STDMETHOD(SetPinvokeMap)( + mdToken tk, + DWORD dwMappingFlags, + LPCWSTR szImportName, + mdModuleRef mrImportDLL) override + { + std::lock_guard lock { this->_lock.GetWriteLock() }; + return _emit->SetPinvokeMap(tk, dwMappingFlags, szImportName, mrImportDLL); + } + + STDMETHOD(DeletePinvokeMap)( + mdToken tk) override + { + std::lock_guard lock { this->_lock.GetWriteLock() }; + return _emit->DeletePinvokeMap(tk); + } + + + STDMETHOD(DefineCustomAttribute)( + mdToken tkOwner, + mdToken tkCtor, + void const *pCustomAttribute, + ULONG cbCustomAttribute, + mdCustomAttribute *pcv) override + { + std::lock_guard lock { this->_lock.GetWriteLock() }; + return _emit->DefineCustomAttribute(tkOwner, tkCtor, pCustomAttribute, cbCustomAttribute, pcv); + } + + STDMETHOD(SetCustomAttributeValue)( + mdCustomAttribute pcv, + void const *pCustomAttribute, + ULONG cbCustomAttribute) override + { + std::lock_guard lock { this->_lock.GetWriteLock() }; + return _emit->SetCustomAttributeValue(pcv, pCustomAttribute, cbCustomAttribute); + } + + STDMETHOD(DefineField)( + mdTypeDef td, + LPCWSTR szName, + DWORD dwFieldFlags, + PCCOR_SIGNATURE pvSigBlob, + ULONG cbSigBlob, + DWORD dwCPlusTypeFlag, + void const *pValue, + ULONG cchValue, + mdFieldDef *pmd) override + { + std::lock_guard lock { this->_lock.GetWriteLock() }; + return _emit->DefineField(td, szName, dwFieldFlags, pvSigBlob, cbSigBlob, dwCPlusTypeFlag, pValue, cchValue, pmd); + } + + STDMETHOD(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) override + { + std::lock_guard lock { this->_lock.GetWriteLock() }; + return _emit->DefineProperty(td, szProperty, dwPropFlags, pvSig, cbSig, dwCPlusTypeFlag, pValue, cchValue, mdSetter, mdGetter, rmdOtherMethods, pmdProp); + } + + STDMETHOD(DefineParam)( + mdMethodDef md, + ULONG ulParamSeq, + LPCWSTR szName, + DWORD dwParamFlags, + DWORD dwCPlusTypeFlag, + void const *pValue, + ULONG cchValue, + mdParamDef *ppd) override + { + std::lock_guard lock { this->_lock.GetWriteLock() }; + return _emit->DefineParam(md, ulParamSeq, szName, dwParamFlags, dwCPlusTypeFlag, pValue, cchValue, ppd); + } + + STDMETHOD(SetFieldProps)( + mdFieldDef fd, + DWORD dwFieldFlags, + DWORD dwCPlusTypeFlag, + void const *pValue, + ULONG cchValue) override + { + std::lock_guard lock { this->_lock.GetWriteLock() }; + return _emit->SetFieldProps(fd, dwFieldFlags, dwCPlusTypeFlag, pValue, cchValue); + } + + STDMETHOD(SetPropertyProps)( + mdProperty pr, + DWORD dwPropFlags, + DWORD dwCPlusTypeFlag, + void const *pValue, + ULONG cchValue, + mdMethodDef mdSetter, + mdMethodDef mdGetter, + mdMethodDef rmdOtherMethods[]) override + { + std::lock_guard lock { this->_lock.GetWriteLock() }; + return _emit->SetPropertyProps(pr, dwPropFlags, dwCPlusTypeFlag, pValue, cchValue, mdSetter, mdGetter, rmdOtherMethods); + } + + STDMETHOD(SetParamProps)( + mdParamDef pd, + LPCWSTR szName, + DWORD dwParamFlags, + DWORD dwCPlusTypeFlag, + void const *pValue, + ULONG cchValue) override + { + std::lock_guard lock { this->_lock.GetWriteLock() }; + return _emit->SetParamProps(pd, szName, dwParamFlags, dwCPlusTypeFlag, pValue, cchValue); + } + + + STDMETHOD(DefineSecurityAttributeSet)( + mdToken tkObj, + COR_SECATTR rSecAttrs[], + ULONG cSecAttrs, + ULONG *pulErrorAttr) override + { + std::lock_guard lock { this->_lock.GetWriteLock() }; + return _emit->DefineSecurityAttributeSet(tkObj, rSecAttrs, cSecAttrs, pulErrorAttr); + } + + STDMETHOD(ApplyEditAndContinue)( + IUnknown *pImport) override + { + std::lock_guard lock { this->_lock.GetWriteLock() }; + return _emit->ApplyEditAndContinue(pImport); + } + + STDMETHOD(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) override + { + std::lock_guard lock { this->_lock.GetWriteLock() }; + return _emit->TranslateSigWithScope(pAssemImport, pbHashValue, cbHashValue, import, pbSigBlob, cbSigBlob, pAssemEmit, emit, pvTranslatedSig, cbTranslatedSigMax, pcbTranslatedSig); + } + + STDMETHOD(SetMethodImplFlags)( + mdMethodDef md, + DWORD dwImplFlags) override + { + std::lock_guard lock { this->_lock.GetWriteLock() }; + return _emit->SetMethodImplFlags(md, dwImplFlags); + } + + STDMETHOD(SetFieldRVA)( + mdFieldDef fd, + ULONG ulRVA) override + { + std::lock_guard lock { this->_lock.GetWriteLock() }; + return _emit->SetFieldRVA(fd, ulRVA); + } + + STDMETHOD(Merge)( + IMetaDataImport *pImport, + IMapToken *pHostMapToken, + IUnknown *pHandler) override + { + std::lock_guard lock { this->_lock.GetWriteLock() }; + return _emit->Merge(pImport, pHostMapToken, pHandler); + } + + STDMETHOD(MergeEnd)() override + { + std::lock_guard lock { this->_lock.GetWriteLock() }; + return _emit->MergeEnd(); + } + +public: // IMetaDataEmit2 + STDMETHOD(DefineMethodSpec)( + mdToken tkParent, + PCCOR_SIGNATURE pvSigBlob, + ULONG cbSigBlob, + mdMethodSpec *pmi) override + { + std::lock_guard lock { this->_lock.GetWriteLock() }; + return _emit->DefineMethodSpec(tkParent, pvSigBlob, cbSigBlob, pmi); + } + + STDMETHOD(GetDeltaSaveSize)( + CorSaveSize fSave, + DWORD *pdwSaveSize) override + { + std::lock_guard lock { this->_lock.GetWriteLock() }; + return _emit->GetDeltaSaveSize(fSave, pdwSaveSize); + } + + STDMETHOD(SaveDelta)( + LPCWSTR szFile, + DWORD dwSaveFlags) override + { + std::lock_guard lock { this->_lock.GetWriteLock() }; + return _emit->SaveDelta(szFile, dwSaveFlags); + } + + STDMETHOD(SaveDeltaToStream)( + IStream *pIStream, + DWORD dwSaveFlags) override + { + std::lock_guard lock { this->_lock.GetWriteLock() }; + return _emit->SaveDeltaToStream(pIStream, dwSaveFlags); + } + + STDMETHOD(SaveDeltaToMemory)( + void *pbData, + ULONG cbData) override + { + std::lock_guard lock { this->_lock.GetWriteLock() }; + return _emit->SaveDeltaToMemory(pbData, cbData); + } + + STDMETHOD(DefineGenericParam)( + mdToken tk, + ULONG ulParamSeq, + DWORD dwParamFlags, + LPCWSTR szname, + DWORD reserved, + mdToken rtkConstraints[], + mdGenericParam *pgp) override + { + std::lock_guard lock { this->_lock.GetWriteLock() }; + return _emit->DefineGenericParam(tk, ulParamSeq, dwParamFlags, szname, reserved, rtkConstraints, pgp); + } + + STDMETHOD(SetGenericParamProps)( + mdGenericParam gp, + DWORD dwParamFlags, + LPCWSTR szName, + DWORD reserved, + mdToken rtkConstraints[]) override + { + std::lock_guard lock { this->_lock.GetWriteLock() }; + return _emit->SetGenericParamProps(gp, dwParamFlags, szName, reserved, rtkConstraints); + } + + STDMETHOD(ResetENCLog)() override + { + std::lock_guard lock { this->_lock.GetWriteLock() }; + return _emit->ResetENCLog(); + } + +public: // IMetaDataAssemblyEmit + STDMETHOD(DefineAssembly)( + void const *pbPublicKey, + ULONG cbPublicKey, + ULONG ulHashAlgId, + LPCWSTR szName, + ASSEMBLYMETADATA const *pMetaData, + DWORD dwAssemblyFlags, + mdAssembly *pma) override + { + std::lock_guard lock { this->_lock.GetWriteLock() }; + return _emit->DefineAssembly(pbPublicKey, cbPublicKey, ulHashAlgId, szName, pMetaData, dwAssemblyFlags, pma); + } + + STDMETHOD(DefineAssemblyRef)( + void const *pbPublicKeyOrToken, + ULONG cbPublicKeyOrToken, + LPCWSTR szName, + ASSEMBLYMETADATA const *pMetaData, + void const *pbHashValue, + ULONG cbHashValue, + DWORD dwAssemblyRefFlags, + mdAssemblyRef *pmdar) override + { + std::lock_guard lock { this->_lock.GetWriteLock() }; + return _emit->DefineAssemblyRef(pbPublicKeyOrToken, cbPublicKeyOrToken, szName, pMetaData, pbHashValue, cbHashValue, dwAssemblyRefFlags, pmdar); + } + + STDMETHOD(DefineFile)( + LPCWSTR szName, + void const *pbHashValue, + ULONG cbHashValue, + DWORD dwFileFlags, + mdFile *pmdf) override + { + std::lock_guard lock { this->_lock.GetWriteLock() }; + return _emit->DefineFile(szName, pbHashValue, cbHashValue, dwFileFlags, pmdf); + } + + STDMETHOD(DefineExportedType)( + LPCWSTR szName, + mdToken tkImplementation, + mdTypeDef tkTypeDef, + DWORD dwExportedTypeFlags, + mdExportedType *pmdct) override + { + std::lock_guard lock { this->_lock.GetWriteLock() }; + return _emit->DefineExportedType(szName, tkImplementation, tkTypeDef, dwExportedTypeFlags, pmdct); + } + + STDMETHOD(DefineManifestResource)( + LPCWSTR szName, + mdToken tkImplementation, + DWORD dwOffset, + DWORD dwResourceFlags, + mdManifestResource *pmdmr) override + { + std::lock_guard lock { this->_lock.GetWriteLock() }; + return _emit->DefineManifestResource(szName, tkImplementation, dwOffset, dwResourceFlags, pmdmr); + } + + STDMETHOD(SetAssemblyProps)( + mdAssembly pma, + void const *pbPublicKey, + ULONG cbPublicKey, + ULONG ulHashAlgId, + LPCWSTR szName, + ASSEMBLYMETADATA const *pMetaData, + DWORD dwAssemblyFlags) override + { + std::lock_guard lock { this->_lock.GetWriteLock() }; + return _emit->SetAssemblyProps(pma, pbPublicKey, cbPublicKey, ulHashAlgId, szName, pMetaData, dwAssemblyFlags); + } + + STDMETHOD(SetAssemblyRefProps)( + mdAssemblyRef ar, + void const *pbPublicKeyOrToken, + ULONG cbPublicKeyOrToken, + LPCWSTR szName, + ASSEMBLYMETADATA const *pMetaData, + void const *pbHashValue, + ULONG cbHashValue, + DWORD dwAssemblyRefFlags) override + { + std::lock_guard lock { this->_lock.GetWriteLock() }; + return _emit->SetAssemblyRefProps(ar, pbPublicKeyOrToken, cbPublicKeyOrToken, szName, pMetaData, pbHashValue, cbHashValue, dwAssemblyRefFlags); + } + + STDMETHOD(SetFileProps)( + mdFile file, + void const *pbHashValue, + ULONG cbHashValue, + DWORD dwFileFlags) override + { + std::lock_guard lock { this->_lock.GetWriteLock() }; + return _emit->SetFileProps(file, pbHashValue, cbHashValue, dwFileFlags); + } + + STDMETHOD(SetExportedTypeProps)( + mdExportedType ct, + mdToken tkImplementation, + mdTypeDef tkTypeDef, + DWORD dwExportedTypeFlags) override + { + std::lock_guard lock { this->_lock.GetWriteLock() }; + return _emit->SetExportedTypeProps(ct, tkImplementation, tkTypeDef, dwExportedTypeFlags); + } + + STDMETHOD(SetManifestResourceProps)( + mdManifestResource mr, + mdToken tkImplementation, + DWORD dwOffset, + DWORD dwResourceFlags) override + { + std::lock_guard lock { this->_lock.GetWriteLock() }; + return _emit->SetManifestResourceProps(mr, tkImplementation, dwOffset, dwResourceFlags); + } +}; + +#endif // _SRC_INTERFACES_THREADSAFE_HPP_ \ No newline at end of file diff --git a/src/native/dnmd/src/mddump/CMakeLists.txt b/src/native/dnmd/src/mddump/CMakeLists.txt new file mode 100644 index 0000000000000..a8c228b9c7a29 --- /dev/null +++ b/src/native/dnmd/src/mddump/CMakeLists.txt @@ -0,0 +1,19 @@ +set(SOURCES + main.cpp +) + +add_executable(mddump + ${SOURCES} +) + +target_link_libraries(mddump + dnmd::pdb + minipal_com) + +if(NOT MSVC) + target_link_libraries(mddump minipal_comhdrs) +endif() + +if (DNMD_INSTALL) + install(TARGETS mddump) +endif() \ No newline at end of file diff --git a/src/native/dnmd/src/mddump/main.cpp b/src/native/dnmd/src/mddump/main.cpp new file mode 100644 index 0000000000000..ccf30066a7ede --- /dev/null +++ b/src/native/dnmd/src/mddump/main.cpp @@ -0,0 +1,158 @@ +#include +#include +#include +#include +#include + +#include + +bool apply_deltas(mdhandle_t handle, std::vector& deltas, std::vector>& data) +{ + for (char const* p : deltas) + { + malloc_span d; + if (!read_in_file(p, d) || !get_metadata_from_file(d)) + { + std::fprintf(stderr, "Failed to read '%s'.\n", p); + return false; + } + + mdhandle_ptr delta; + if (!create_mdhandle(d, delta)) + { + std::fprintf(stderr, "Failed to create handle for '%s'.\n", p); + return false; + } + + if (!md_apply_delta(handle, delta.get())) + { + std::fprintf(stderr, "Failed to apply delta, '%s'.\n", p); + return false; + } + + // Store the loaded delta data + data.push_back(std::move(d)); + } + return true; +} + +struct dump_config_t final +{ + dump_config_t() + : path{} + , delta_paths{} + , data{} + , table_id{ -1 } + { } + + dump_config_t(dump_config_t const& other) = delete; + dump_config_t(dump_config_t&& other) = default; + + char const* path; + std::vector delta_paths; + std::vector> data; + int32_t table_id; +}; + +void dump(dump_config_t cfg) +{ + malloc_span b; + if (!read_in_file(cfg.path, b)) + { + std::fprintf(stderr, "Failed to read in '%s'\n", cfg.path); + return; + } + + if (!get_metadata_from_pe(b) && !get_metadata_from_file(b)) + { + std::fprintf(stderr, "Failed to read file as PE or metadata blob.\n"); + return; + } + + std::printf("Loaded '%s'.\n Metadata blob size %zu bytes\n", cfg.path, b.size()); + if (cfg.table_id != -1) + std::printf(" Reading in table %d (0x%x)\n", cfg.table_id, cfg.table_id); + + mdhandle_ptr handle; + if (!create_mdhandle(b, handle) + || !apply_deltas(handle.get(), cfg.delta_paths, cfg.data) + || !md_validate(handle.get()) + || !md_dump_tables(handle.get(), cfg.table_id)) + { + std::fprintf(stderr, "invalid metadata!\n"); + } +} + +static char const* s_usage = "Syntax: mddump [-t ]? [-d ]* "; + +int MAIN_CALLCONV main(int ac, char** av) +{ + if (ac <= 1) + { + std::fprintf(stderr, "Missing metadata file.\n\n%s\n", s_usage); + return EXIT_FAILURE; + } + + dump_config_t cfg; + + // Process arguments + span args{ &av[1], (size_t)ac - 1 }; + for (size_t i = 0; i < args.size(); ++i) + { + char* arg = args[i]; + if (arg[0] != '-') + { + cfg.path = arg; + continue; + } + + size_t len = strlen(arg); + if (len >= 2) + { + switch (arg[1]) + { + case 't': + { + i++; + if (i >= args.size()) + { + std::fprintf(stderr, "Missing table ID.\n"); + return EXIT_FAILURE; + } + + cfg.table_id = (int32_t)::strtoul(args[i], nullptr, 0); + if ((errno == ERANGE) || cfg.table_id >= 64) + { + std::fprintf(stderr, "Invalid table ID: '%s'. Must be [0, 64)\n", args[i]); + return EXIT_FAILURE; + } + continue; + } + case 'd': + { + i++; + if (i >= args.size()) + { + std::fprintf(stderr, "Missing delta file.\n"); + return EXIT_FAILURE; + } + cfg.delta_paths.push_back(args[i]); + continue; + } + case 'h': + case '?': + std::printf("%s\n", s_usage); + return EXIT_SUCCESS; + default: + break; + } + } + + std::fprintf(stderr, "Invalid argument: '%s'\n\n%s\n", arg, s_usage); + return EXIT_FAILURE; + } + + dump(std::move(cfg)); + + return EXIT_SUCCESS; +} diff --git a/src/native/dnmd/src/mdmerge/CMakeLists.txt b/src/native/dnmd/src/mdmerge/CMakeLists.txt new file mode 100644 index 0000000000000..2a0ee341cae91 --- /dev/null +++ b/src/native/dnmd/src/mdmerge/CMakeLists.txt @@ -0,0 +1,19 @@ +set(SOURCES + main.cpp +) + +add_executable(mdmerge + ${SOURCES} +) + +target_link_libraries(mdmerge + dnmd::dnmd + minipal_com) + +if(NOT MSVC) + target_link_libraries(mdmerge minipal_comhdrs) +endif() + +if (DNMD_INSTALL) + install(TARGETS mdmerge) +endif() \ No newline at end of file diff --git a/src/native/dnmd/src/mdmerge/main.cpp b/src/native/dnmd/src/mdmerge/main.cpp new file mode 100644 index 0000000000000..eae872450f30e --- /dev/null +++ b/src/native/dnmd/src/mdmerge/main.cpp @@ -0,0 +1,164 @@ +#include +#include +#include +#include +#include + +#include + +bool apply_deltas(mdhandle_t handle, std::vector& deltas, std::vector>& data) +{ + for (char const* p : deltas) + { + std::printf("Reading in delta image '%s'.\n", p); + malloc_span d; + if (!read_in_file(p, d) || !get_metadata_from_file(d)) + { + std::fprintf(stderr, "Failed to read '%s'.\n", p); + return false; + } + + mdhandle_ptr delta; + if (!create_mdhandle(d, delta)) + { + std::fprintf(stderr, "Failed to create handle for '%s'.\n", p); + return false; + } + + if (!md_apply_delta(handle, delta.get())) + { + std::fprintf(stderr, "Failed to apply delta, '%s'.\n", p); + return false; + } + // Store the loaded delta data + data.push_back(std::move(d)); + } + return true; +} + +struct merge_config_t final +{ + merge_config_t() + : path{} + , output_path{} + , delta_paths{} + , data{} + { } + + merge_config_t(merge_config_t const& other) = delete; + merge_config_t(merge_config_t&& other) = default; + + char const* path; + char const* output_path; + std::vector delta_paths; + std::vector> data; +}; + +void merge(merge_config_t cfg) +{ + malloc_span b; + if (!read_in_file(cfg.path, b)) + { + std::fprintf(stderr, "Failed to read in '%s'\n", cfg.path); + return; + } + + if (!get_metadata_from_pe(b) && !get_metadata_from_file(b)) + { + std::fprintf(stderr, "Failed to read file as PE or metadata blob.\n"); + return; + } + + std::printf("Loaded '%s'.\n Metadata blob size %zu bytes\n", cfg.path, b.size()); + + mdhandle_ptr handle; + if (!create_mdhandle(b, handle) + || !apply_deltas(handle.get(), cfg.delta_paths, cfg.data) + || !md_validate(handle.get())) + { + std::fprintf(stderr, "invalid metadata!\n"); + } + + size_t save_size; + md_write_to_buffer(handle.get(), nullptr, &save_size); + malloc_span out_buffer { (uint8_t*)malloc(save_size), save_size }; + if (!md_write_to_buffer(handle.get(), out_buffer.data(), &save_size)) + { + std::fprintf(stderr, "Failed to save image.\n"); + } + + if (!write_out_file(cfg.output_path, std::move(out_buffer))) + { + std::fprintf(stderr, "Failed to write out '%s'\n", cfg.output_path); + return; + } + std::printf("Wrote out '%s'.\n", cfg.output_path); +} + +static char const* s_usage = "Syntax: mdmerge [-o ] [-d ]* "; + +int MAIN_CALLCONV main(int ac, char** av) +{ + if (ac <= 1) + { + std::fprintf(stderr, "Missing metadata file.\n\n%s\n", s_usage); + return EXIT_FAILURE; + } + + merge_config_t cfg; + + // Process arguments + span args{ &av[1], (size_t)ac - 1 }; + for (size_t i = 0; i < args.size(); ++i) + { + char* arg = args[i]; + if (arg[0] != '-') + { + cfg.path = arg; + continue; + } + + size_t len = strlen(arg); + if (len >= 2) + { + switch (arg[1]) + { + case 'o': + { + i++; + if (i >= args.size()) + { + std::fprintf(stderr, "Missing output file path.\n"); + return EXIT_FAILURE; + } + cfg.output_path = args[i]; + continue; + } + case 'd': + { + i++; + if (i >= args.size()) + { + std::fprintf(stderr, "Missing delta file.\n"); + return EXIT_FAILURE; + } + cfg.delta_paths.push_back(args[i]); + continue; + } + case 'h': + case '?': + std::printf("%s\n", s_usage); + return EXIT_SUCCESS; + default: + break; + } + } + + std::fprintf(stderr, "Invalid argument: '%s'\n\n%s\n", arg, s_usage); + return EXIT_FAILURE; + } + + merge(std::move(cfg)); + + return EXIT_SUCCESS; +} diff --git a/src/native/dnmd/test/CMakeLists.txt b/src/native/dnmd/test/CMakeLists.txt new file mode 100644 index 0000000000000..20c2c2db8c4c6 --- /dev/null +++ b/src/native/dnmd/test/CMakeLists.txt @@ -0,0 +1,59 @@ +# Configure the compiler +include(../configure.cmake) +include(FindNetHost.cmake) + +if (POLICY CMP0135) + cmake_policy(SET CMP0135 NEW) # Set timestamps in downloaded archives to the time of download. +endif() + +include(FetchContent) +FetchContent_Declare( + googletest + GIT_REPOSITORY + https://github.com/google/googletest.git + GIT_TAG + v1.14.0 +) + +# For Windows: Prevent overriding the parent project's compiler/linker settings +set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) +set(INSTALL_GTEST OFF CACHE BOOL "" FORCE) +set_property(DIRECTORY ${googletest_SOURCE_DIR} PROPERTY SET_SOURCE_CHARSET OFF) + +FetchContent_Declare( + benchmark + GIT_REPOSITORY + https://github.com/google/benchmark.git + GIT_TAG + v1.8.3 +) + +# Don't build the tests for the benchmark library. +set(BENCHMARK_ENABLE_TESTING OFF CACHE BOOL "" FORCE) +set(BENCHMARK_ENABLE_INSTALL OFF CACHE BOOL "" FORCE) + +FetchContent_MakeAvailable(googletest benchmark) + +if (WIN32) + FetchContent_Declare( + wil + GIT_REPOSITORY + https://github.com/microsoft/wil.git + GIT_TAG + v1.0.231216.1 + ) + + set(WIL_BUILD_PACKAGING OFF CACHE BOOL "" FORCE) + set(WIL_BUILD_TESTS OFF CACHE BOOL "" FORCE) + + FetchContent_MakeAvailable(wil) +endif() + +include(GoogleTest) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +add_subdirectory(regpal) +add_subdirectory(regperf) +add_subdirectory(regtest) +add_subdirectory(emit) diff --git a/src/native/dnmd/test/DNMD.Tests.sln b/src/native/dnmd/test/DNMD.Tests.sln new file mode 100644 index 0000000000000..1c89596751940 --- /dev/null +++ b/src/native/dnmd/test/DNMD.Tests.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.33026.144 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Regression.TargetAssembly", "Regression.TargetAssembly\Regression.TargetAssembly.ilproj", "{D3BEEF3D-C137-49AC-96DD-E5B93E2F4C21}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {D3BEEF3D-C137-49AC-96DD-E5B93E2F4C21}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D3BEEF3D-C137-49AC-96DD-E5B93E2F4C21}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D3BEEF3D-C137-49AC-96DD-E5B93E2F4C21}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D3BEEF3D-C137-49AC-96DD-E5B93E2F4C21}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {45439F24-3FBC-4149-808D-1FCC54AA75E9} + EndGlobalSection +EndGlobal diff --git a/src/native/dnmd/test/Directory.Build.props b/src/native/dnmd/test/Directory.Build.props new file mode 100644 index 0000000000000..556ef2c914a10 --- /dev/null +++ b/src/native/dnmd/test/Directory.Build.props @@ -0,0 +1,12 @@ + + + true + $(MSBuildThisFileDirectory)/../artifacts/managed + + + $(BaseArtifactsPath)/bin + $(BaseArtifactsPath)/lib + + net8.0 + + \ No newline at end of file diff --git a/src/native/dnmd/test/Directory.Build.targets b/src/native/dnmd/test/Directory.Build.targets new file mode 100644 index 0000000000000..c1df2220ddc6e --- /dev/null +++ b/src/native/dnmd/test/Directory.Build.targets @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/src/native/dnmd/test/FindNetHost.cmake b/src/native/dnmd/test/FindNetHost.cmake new file mode 100644 index 0000000000000..b6eb88750c350 --- /dev/null +++ b/src/native/dnmd/test/FindNetHost.cmake @@ -0,0 +1,22 @@ +execute_process( + COMMAND ${CMAKE_COMMAND} -E env DOTNET_NOLOGO=1 dotnet msbuild FindNetHostDir.proj -t:OutputNetHostDir -nologo -p:SuppressNETCoreSdkPreviewMessage=true + OUTPUT_VARIABLE NET_HOST_DIR + OUTPUT_STRIP_TRAILING_WHITESPACE + COMMAND_ERROR_IS_FATAL ANY + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}) + +string(STRIP ${NET_HOST_DIR} NET_HOST_DIR) + +if (WIN32) + add_library(nethost IMPORTED SHARED) + set_target_properties(nethost PROPERTIES + IMPORTED_LOCATION ${NET_HOST_DIR}/nethost.dll + IMPORTED_IMPLIB ${NET_HOST_DIR}/nethost.lib) +else() + add_library(nethost IMPORTED STATIC) + target_compile_definitions(nethost INTERFACE NETHOST_USE_AS_STATIC) + set_target_properties(nethost PROPERTIES + IMPORTED_LOCATION ${NET_HOST_DIR}/libnethost.a) +endif() + +target_include_directories(nethost INTERFACE ${NET_HOST_DIR}) diff --git a/src/native/dnmd/test/FindNetHostDir.proj b/src/native/dnmd/test/FindNetHostDir.proj new file mode 100644 index 0000000000000..c21301914b863 --- /dev/null +++ b/src/native/dnmd/test/FindNetHostDir.proj @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/native/dnmd/test/Regression.TargetAssembly/Nested.il b/src/native/dnmd/test/Regression.TargetAssembly/Nested.il new file mode 100644 index 0000000000000..1afad8c1d68df --- /dev/null +++ b/src/native/dnmd/test/Regression.TargetAssembly/Nested.il @@ -0,0 +1,115 @@ +.assembly extern System.Runtime { .publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A ) } +.assembly extern Regression.TargetAssembly.Ref { } +.assembly Regression.TargetAssembly { } + +.class public auto ansi beforefieldinit A1 + extends [System.Runtime]System.Object +{ + .method public hidebysig specialname rtspecialname + instance void .ctor () cil managed + { + ret + } + + .method public newslot virtual hidebysig instance void MethodDef() cil managed + { + ret + } + + .method public newslot virtual hidebysig instance vararg void MethodRef1( + int32, + uint16*, + int64&, + object[]) cil managed + { + ret + } + + .method public newslot virtual hidebysig instance vararg void MethodRef2( + valuetype [System.Runtime]System.Guid, + string[][1...3,-1...,..., 4] modreq(A1) modopt([System.Runtime]System.Object) pinned, + method void * (int32)) cil managed + { + ret + } +} + +.class public auto ansi beforefieldinit B1 + extends A1 +{ + .class nested public auto ansi beforefieldinit C + extends [System.Runtime]System.Object + { + .method public hidebysig specialname rtspecialname + instance void .ctor () cil managed + { + ret + } + } + + .method public hidebysig specialname rtspecialname + instance void .ctor () cil managed + { + ret + } + + .method public virtual hidebysig instance void MethodDef() cil managed + { + .override A1::MethodDef + ret + } + + // We don't actually use these two methods, but we need them to provide the MemberRef tokens + // that we use in the FindMethod tests. + .method public virtual hidebysig instance vararg void MethodRefWithoutVarargsArguments( + int32, + uint16*, + int64&, + object[]) cil managed + { + .override method instance vararg void A1::MethodRef1( + int32, + uint16*, + int64&, + object[]) + ret + } + + .method public virtual hidebysig instance vararg void MethodRefWithVarargsArguments( + valuetype [System.Runtime]System.Guid, + string[][1...3,-1...,..., 4] modreq(A1) modopt([System.Runtime]System.Object) pinned, + method void * (int32)) cil managed + { + .override method instance vararg void A1::MethodRef2( + valuetype [System.Runtime]System.Guid, + string[][1...3,-1...,..., 4] modreq(A1) modopt([System.Runtime]System.Object) pinned, + method void * (int32), + ..., + int32) + ret + } +} + +.class public auto ansi beforefieldinit B2 + extends [Regression.TargetAssembly.Ref]A2 +{ + .class nested public auto ansi beforefieldinit C + extends [System.Runtime]System.Object + { + .method public hidebysig specialname rtspecialname + instance void .ctor () cil managed + { + ret + } + } + + .method public hidebysig specialname rtspecialname + instance void .ctor () cil managed + { + ret + } + + .field public int32 Field1 + + .field public int64 Field2 +} \ No newline at end of file diff --git a/src/native/dnmd/test/Regression.TargetAssembly/Regression.TargetAssembly.ilproj b/src/native/dnmd/test/Regression.TargetAssembly/Regression.TargetAssembly.ilproj new file mode 100644 index 0000000000000..5f92f5ae6d499 --- /dev/null +++ b/src/native/dnmd/test/Regression.TargetAssembly/Regression.TargetAssembly.ilproj @@ -0,0 +1,11 @@ + + + Library + false + + + + + + + diff --git a/src/native/dnmd/test/emit/CMakeLists.txt b/src/native/dnmd/test/emit/CMakeLists.txt new file mode 100644 index 0000000000000..99abb8889722c --- /dev/null +++ b/src/native/dnmd/test/emit/CMakeLists.txt @@ -0,0 +1,21 @@ +set(SOURCES + typeref.cpp + module.cpp + typedef.cpp + moduleref.cpp + methoddef.cpp + standalonesig.cpp + memberref.cpp + typespec.cpp + assembly.cpp + assemblyref.cpp + param.cpp + fieldmarshal.cpp + fieldrva.cpp) + +set(HEADERS emit.hpp) + +add_executable(emit ${SOURCES} ${HEADERS}) +target_link_libraries(emit PRIVATE dnmd::interfaces_static gtest_main gmock) + +gtest_discover_tests(emit) \ No newline at end of file diff --git a/src/native/dnmd/test/emit/assembly.cpp b/src/native/dnmd/test/emit/assembly.cpp new file mode 100644 index 0000000000000..292d4c2d8510b --- /dev/null +++ b/src/native/dnmd/test/emit/assembly.cpp @@ -0,0 +1,47 @@ +#include "emit.hpp" + +TEST(Assembly, DefineNoPublicKey) +{ + minipal::com_ptr emit; + ASSERT_NO_FATAL_FAILURE(CreateEmit(emit)); + mdAssembly assembly; + + WSTR_string name = W("AssemblyName"); + ASSEMBLYMETADATA assemblyMetadata; + assemblyMetadata.usMajorVersion = 1; + assemblyMetadata.usMinorVersion = 2; + assemblyMetadata.usBuildNumber = 3; + assemblyMetadata.usRevisionNumber = 4; + assemblyMetadata.szLocale = const_cast(W("en-us")); + assemblyMetadata.cbLocale = 5; + uint32_t hashAlgId = 4; + ASSERT_EQ(S_OK, emit->DefineAssembly(nullptr, 0, hashAlgId, name.c_str(), &assemblyMetadata, 0, &assembly)); + ASSERT_EQ(1, RidFromToken(assembly)); + ASSERT_EQ(mdtAssembly, TypeFromToken(assembly)); + + minipal::com_ptr import; + ASSERT_EQ(S_OK, emit->QueryInterface(IID_IMetaDataAssemblyImport, (void**)&import)); + + ASSEMBLYMETADATA metadata; + std::unique_ptr localeName = std::make_unique(20); + metadata.szLocale = localeName.get(); + metadata.cbLocale = 20; + ULONG importHashAlgId; + DWORD assemblyFlags; + WSTR_string assemblyName; + assemblyName.resize(name.capacity() + 1); + ULONG assemblyNameLen; + void const* publicKey; + ULONG publicKeyLength; + ASSERT_EQ(S_OK, import->GetAssemblyProps(assembly, &publicKey, &publicKeyLength, &importHashAlgId, &assemblyName[0], (ULONG)assemblyName.capacity(), &assemblyNameLen, &metadata, & assemblyFlags)); + EXPECT_EQ(0, assemblyFlags); + EXPECT_EQ(W("AssemblyName"), assemblyName.substr(0, assemblyNameLen - 1)); + EXPECT_EQ(1, metadata.usMajorVersion); + EXPECT_EQ(2, metadata.usMinorVersion); + EXPECT_EQ(3, metadata.usBuildNumber); + EXPECT_EQ(4, metadata.usRevisionNumber); + + WSTR_string locale{ metadata.szLocale }; + EXPECT_EQ(W("en-us"), locale); + EXPECT_EQ(locale.length() + 1, metadata.cbLocale); +} diff --git a/src/native/dnmd/test/emit/assemblyref.cpp b/src/native/dnmd/test/emit/assemblyref.cpp new file mode 100644 index 0000000000000..fc01d3a2c4450 --- /dev/null +++ b/src/native/dnmd/test/emit/assemblyref.cpp @@ -0,0 +1,49 @@ +#include "emit.hpp" + +TEST(AssemblyRef, DefineNoPublicKey) +{ + minipal::com_ptr emit; + ASSERT_NO_FATAL_FAILURE(CreateEmit(emit)); + mdAssembly assembly; + + WSTR_string name = W("AssemblyName"); + ASSEMBLYMETADATA assemblyMetadata; + assemblyMetadata.usMajorVersion = 1; + assemblyMetadata.usMinorVersion = 2; + assemblyMetadata.usBuildNumber = 3; + assemblyMetadata.usRevisionNumber = 4; + assemblyMetadata.szLocale = const_cast(W("en-us")); + assemblyMetadata.cbLocale = 5; + ASSERT_EQ(S_OK, emit->DefineAssemblyRef(nullptr, 0, name.c_str(), &assemblyMetadata, nullptr, 0, 0, &assembly)); + ASSERT_EQ(1, RidFromToken(assembly)); + ASSERT_EQ(mdtAssemblyRef, TypeFromToken(assembly)); + + minipal::com_ptr import; + ASSERT_EQ(S_OK, emit->QueryInterface(IID_IMetaDataAssemblyImport, (void**)&import)); + + ASSEMBLYMETADATA metadata; + std::unique_ptr localeName = std::make_unique(20); + metadata.szLocale = localeName.get(); + metadata.cbLocale = 20; + DWORD assemblyFlags; + WSTR_string assemblyName; + assemblyName.resize(name.capacity() + 1); + ULONG assemblyNameLen; + void const* publicKey; + ULONG publicKeyLength; + void const* hash; + ULONG hashLength; + ASSERT_EQ(S_OK, import->GetAssemblyRefProps(assembly, &publicKey, &publicKeyLength, &assemblyName[0], (ULONG)assemblyName.capacity(), &assemblyNameLen, &metadata, &hash, &hashLength, &assemblyFlags)); + EXPECT_EQ(0, assemblyFlags); + EXPECT_EQ(nullptr, hash); + EXPECT_EQ(0, hashLength); + EXPECT_EQ(W("AssemblyName"), assemblyName.substr(0, assemblyNameLen - 1)); + EXPECT_EQ(1, metadata.usMajorVersion); + EXPECT_EQ(2, metadata.usMinorVersion); + EXPECT_EQ(3, metadata.usBuildNumber); + EXPECT_EQ(4, metadata.usRevisionNumber); + + WSTR_string locale{ metadata.szLocale }; + EXPECT_EQ(W("en-us"), locale); + EXPECT_EQ(locale.length() + 1, metadata.cbLocale); +} diff --git a/src/native/dnmd/test/emit/emit.hpp b/src/native/dnmd/test/emit/emit.hpp new file mode 100644 index 0000000000000..9b9ac7cfb7a15 --- /dev/null +++ b/src/native/dnmd/test/emit/emit.hpp @@ -0,0 +1,39 @@ +#ifndef DNMD_TEST_EMIT_EMIT_HPP +#define DNMD_TEST_EMIT_EMIT_HPP + +#ifdef BUILD_WINDOWS +#include +#endif +#include +#include + +#include +#include +#include +#include +#include +#include + +using WSTR_string = std::basic_string; + +inline void CreateEmit(minipal::com_ptr& emit) +{ + minipal::com_ptr dispenser; + ASSERT_EQ(S_OK, GetDispenser(IID_IMetaDataDispenser, (void**)&dispenser)); + ASSERT_EQ(S_OK, dispenser->DefineScope(CLSID_CorMetaDataRuntime, 0, IID_IMetaDataEmit, (IUnknown**)&emit)); +} + +inline void CreateEmit(minipal::com_ptr& emit) +{ + minipal::com_ptr dispenser; + ASSERT_EQ(S_OK, GetDispenser(IID_IMetaDataDispenser, (void**)&dispenser)); + ASSERT_EQ(S_OK, dispenser->DefineScope(CLSID_CorMetaDataRuntime, 0, IID_IMetaDataEmit2, (IUnknown**)&emit)); +} + +inline void CreateEmit(minipal::com_ptr& emit) +{ + minipal::com_ptr dispenser; + ASSERT_EQ(S_OK, GetDispenser(IID_IMetaDataDispenser, (void**)&dispenser)); + ASSERT_EQ(S_OK, dispenser->DefineScope(CLSID_CorMetaDataRuntime, 0, IID_IMetaDataAssemblyEmit, (IUnknown**)&emit)); +} +#endif // DNMD_TEST_EMIT_EMIT_HPP diff --git a/src/native/dnmd/test/emit/fieldmarshal.cpp b/src/native/dnmd/test/emit/fieldmarshal.cpp new file mode 100644 index 0000000000000..238ad8872215b --- /dev/null +++ b/src/native/dnmd/test/emit/fieldmarshal.cpp @@ -0,0 +1,36 @@ +#include "emit.hpp" + +#include + +TEST(FieldMarshal, DefineAndDelete) +{ + // Define a field + minipal::com_ptr emit; + ASSERT_NO_FATAL_FAILURE(CreateEmit(emit)); + mdFieldDef field; + mdTypeDef type; + ASSERT_EQ(S_OK, emit->DefineTypeDef(W("Type"), tdPublic, TokenFromRid(1, mdtTypeDef), nullptr, &type)); + std::array signature = { IMAGE_CEE_CS_CALLCONV_FIELD, ELEMENT_TYPE_VOID, ELEMENT_TYPE_I4 }; + ASSERT_EQ(S_OK, emit->DefineField(type, W("Field"), fdPublic, signature.data(), (ULONG)signature.size(), 0, nullptr, 0, &field)); + + // Define the field marshal signature + std::array marshalSignature = { NATIVE_TYPE_I4 }; + ASSERT_EQ(S_OK, emit->SetFieldMarshal(field, marshalSignature.data(), (ULONG)marshalSignature.size())); + + // Read the field marshal signature + minipal::com_ptr import; + ASSERT_EQ(S_OK, emit->QueryInterface(IID_IMetaDataImport, (void**)&import)); + + ULONG readMarshalLength; + PCCOR_SIGNATURE readMarshal; + ASSERT_EQ(S_OK, import->GetFieldMarshal(field, & readMarshal, & readMarshalLength)); + + EXPECT_EQ(marshalSignature.size(), readMarshalLength); + EXPECT_THAT(std::vector(readMarshal, readMarshal + readMarshalLength), testing::ContainerEq(std::vector(marshalSignature.begin(), marshalSignature.end()))); + + // Delete the field marshal entry + ASSERT_EQ(S_OK, emit->DeleteFieldMarshal(field)); + + // Verify we can't find the field marshal entry after deletion + ASSERT_EQ(CLDB_E_RECORD_NOTFOUND, import->GetFieldMarshal(field, & readMarshal, & readMarshalLength)); +} \ No newline at end of file diff --git a/src/native/dnmd/test/emit/fieldrva.cpp b/src/native/dnmd/test/emit/fieldrva.cpp new file mode 100644 index 0000000000000..4f3620894c9df --- /dev/null +++ b/src/native/dnmd/test/emit/fieldrva.cpp @@ -0,0 +1,28 @@ +#include "emit.hpp" + +TEST(FieldRva, Define) +{ + // Define a field + minipal::com_ptr emit; + ASSERT_NO_FATAL_FAILURE(CreateEmit(emit)); + mdFieldDef field; + mdTypeDef type; + ASSERT_EQ(S_OK, emit->DefineTypeDef(W("Type"), tdPublic, TokenFromRid(1, mdtTypeDef), nullptr, &type)); + std::array signature = { IMAGE_CEE_CS_CALLCONV_FIELD, ELEMENT_TYPE_VOID, ELEMENT_TYPE_I4 }; + ASSERT_EQ(S_OK, emit->DefineField(type, W("Field"), fdPublic, signature.data(), (ULONG)signature.size(), 0, nullptr, 0, &field)); + + // Define the field marshal signature + uint32_t rva = 0x424242; + ASSERT_EQ(S_OK, emit->SetFieldRVA(field, rva)); + + // Read the field marshal signature + minipal::com_ptr import; + ASSERT_EQ(S_OK, emit->QueryInterface(IID_IMetaDataImport, (void**)&import)); + + DWORD readRva = 0; + DWORD implFlags = 0; + ASSERT_EQ(S_OK, import->GetRVA(field, & readRva, & implFlags)); + + EXPECT_EQ(rva, readRva); + EXPECT_EQ(0, implFlags); +} \ No newline at end of file diff --git a/src/native/dnmd/test/emit/memberref.cpp b/src/native/dnmd/test/emit/memberref.cpp new file mode 100644 index 0000000000000..7671b902406dc --- /dev/null +++ b/src/native/dnmd/test/emit/memberref.cpp @@ -0,0 +1,62 @@ +#include "emit.hpp" +#include +#include +#include + +TEST(MemberRef, Define) +{ + minipal::com_ptr emit; + ASSERT_NO_FATAL_FAILURE(CreateEmit(emit)); + mdMemberRef memberRef; + std::array signature = {0x01, 0x02, 0x03}; + ASSERT_EQ(S_OK, emit->DefineMemberRef(TokenFromRid(1, mdtTypeDef), W("Foo"), signature.data(), (ULONG)signature.size(), &memberRef)); + ASSERT_EQ(1, RidFromToken(memberRef)); + ASSERT_EQ(mdtMemberRef, TypeFromToken(memberRef)); + + minipal::com_ptr import; + ASSERT_EQ(S_OK, emit->QueryInterface(IID_IMetaDataImport, (void**)&import)); + + mdTypeDef type; + WSTR_string readName; + readName.resize(3); + ULONG readNameLength; + PCCOR_SIGNATURE sigBlob; + ULONG sigBlobLength; + ASSERT_EQ(S_OK, import->GetMemberRefProps(memberRef, &type, &readName[0], (ULONG)readName.capacity(), &readNameLength, &sigBlob, &sigBlobLength)); + EXPECT_EQ(W("Foo"), readName.substr(0, readNameLength - 1)); + EXPECT_EQ(TokenFromRid(1, mdtTypeDef), type); + EXPECT_THAT(std::vector(sigBlob, sigBlob + sigBlobLength), testing::ContainerEq(std::vector(signature.begin(), signature.end()))); +} + +TEST(MemberRef, SetParent) +{ + minipal::com_ptr emit; + ASSERT_NO_FATAL_FAILURE(CreateEmit(emit)); + mdMemberRef memberRef; + std::array signature = {0x01, 0x02, 0x03}; + ASSERT_EQ(S_OK, emit->DefineMemberRef(TokenFromRid(1, mdtTypeDef), W("Foo"), signature.data(), (ULONG)signature.size(), &memberRef)); + ASSERT_EQ(1, RidFromToken(memberRef)); + ASSERT_EQ(mdtMemberRef, TypeFromToken(memberRef)); + + minipal::com_ptr import; + ASSERT_EQ(S_OK, emit->QueryInterface(IID_IMetaDataImport, (void**)&import)); + + mdToken parent; + WSTR_string readName; + readName.resize(3); + ULONG readNameLength; + PCCOR_SIGNATURE sigBlob; + ULONG sigBlobLength; + ASSERT_EQ(S_OK, import->GetMemberRefProps(memberRef, &parent, &readName[0], (ULONG)readName.capacity(), &readNameLength, &sigBlob, &sigBlobLength)); + EXPECT_EQ(W("Foo"), readName.substr(0, readNameLength - 1)); + EXPECT_EQ(TokenFromRid(1, mdtTypeDef), parent); + EXPECT_THAT(std::vector(sigBlob, sigBlob + sigBlobLength), testing::ContainerEq(std::vector(signature.begin(), signature.end()))); + + ASSERT_EQ(S_OK, emit->SetParent(memberRef, TokenFromRid(2, mdtTypeRef))); + + readName.resize(3); + ASSERT_EQ(S_OK, import->GetMemberRefProps(memberRef, &parent, &readName[0], (ULONG)readName.capacity(), &readNameLength, &sigBlob, &sigBlobLength)); + EXPECT_EQ(W("Foo"), readName.substr(0, readNameLength - 1)); + EXPECT_EQ(TokenFromRid(2, mdtTypeRef), parent); + EXPECT_THAT(std::vector(sigBlob, sigBlob + sigBlobLength), testing::ContainerEq(std::vector(signature.begin(), signature.end()))); +} \ No newline at end of file diff --git a/src/native/dnmd/test/emit/methoddef.cpp b/src/native/dnmd/test/emit/methoddef.cpp new file mode 100644 index 0000000000000..d30e0f0011eec --- /dev/null +++ b/src/native/dnmd/test/emit/methoddef.cpp @@ -0,0 +1,117 @@ +#include "emit.hpp" +#include +#include + +TEST(MethodDef, Define) +{ + minipal::com_ptr emit; + ASSERT_NO_FATAL_FAILURE(CreateEmit(emit)); + mdMethodDef methodDef; + + // TypeDef,1 is the type. + std::array sig = { (uint8_t)IMAGE_CEE_CS_CALLCONV_DEFAULT, (uint8_t)0, (uint8_t)ELEMENT_TYPE_VOID }; + ULONG rva = 0x424242; + ASSERT_EQ(S_OK, emit->DefineMethod(TokenFromRid(1, mdtTypeDef), W("Foo"), mdStatic, sig.data(), (ULONG)sig.size(), rva, 0, &methodDef)); + ASSERT_EQ(1, RidFromToken(methodDef)); + ASSERT_EQ(mdtMethodDef, TypeFromToken(methodDef)); + minipal::com_ptr import; + ASSERT_EQ(S_OK, emit->QueryInterface(IID_IMetaDataImport, (void**)&import)); + + mdTypeDef type; + WSTR_string readName; + readName.resize(3); + ULONG readNameLength; + + DWORD attr; + PCCOR_SIGNATURE sigBlob; + ULONG sigBlobLength; + ULONG codeRVA; + DWORD implFlags; + ASSERT_EQ(S_OK, import->GetMethodProps(methodDef, &type, &readName[0], (ULONG)readName.capacity(), &readNameLength, &attr, &sigBlob, &sigBlobLength, &codeRVA, &implFlags)); + EXPECT_EQ(W("Foo"), readName.substr(0, readNameLength - 1)); + EXPECT_EQ(mdStatic, attr); + EXPECT_EQ(rva, codeRVA); + EXPECT_EQ(0, implFlags); + EXPECT_THAT(std::vector(sigBlob, sigBlob + sigBlobLength), testing::ContainerEq(std::vector(sig.begin(), sig.end()))); +} + +TEST(MethodDef, DefineWithInvalidType) +{ + minipal::com_ptr emit; + ASSERT_NO_FATAL_FAILURE(CreateEmit(emit)); + mdMethodDef methodDef; + + std::array sig = { (uint8_t)IMAGE_CEE_CS_CALLCONV_DEFAULT, (uint8_t)0, (uint8_t)ELEMENT_TYPE_VOID }; + ULONG rva = 0x424242; + ASSERT_EQ(CLDB_E_FILE_CORRUPT, emit->DefineMethod(TokenFromRid(2, mdtTypeDef), W("Foo"), mdStatic, sig.data(), (ULONG)sig.size(), rva, 0, &methodDef)); +} + +TEST(MethodDef, SetRva) +{ + minipal::com_ptr emit; + ASSERT_NO_FATAL_FAILURE(CreateEmit(emit)); + mdMethodDef methodDef; + + std::array sig = { (uint8_t)IMAGE_CEE_CS_CALLCONV_DEFAULT, (uint8_t)0, (uint8_t)ELEMENT_TYPE_VOID }; + ULONG rva = 0x424242; + ASSERT_EQ(S_OK, emit->DefineMethod(TokenFromRid(1, mdtTypeDef), W("Foo"), mdStatic, sig.data(), (ULONG)sig.size(), rva, 0, &methodDef)); + ASSERT_EQ(1, RidFromToken(methodDef)); + ASSERT_EQ(mdtMethodDef, TypeFromToken(methodDef)); + minipal::com_ptr import; + ASSERT_EQ(S_OK, emit->QueryInterface(IID_IMetaDataImport, (void**)&import)); + + ULONG newRva = 0x123456; + ASSERT_EQ(S_OK, emit->SetRVA(methodDef, newRva)); + + mdTypeDef type; + WSTR_string readName; + readName.resize(3); + ULONG readNameLength; + + DWORD attr; + PCCOR_SIGNATURE sigBlob; + ULONG sigBlobLength; + ULONG codeRVA; + DWORD implFlags; + ASSERT_EQ(S_OK, import->GetMethodProps(methodDef, &type, &readName[0], (ULONG)readName.capacity(), &readNameLength, &attr, &sigBlob, &sigBlobLength, &codeRVA, &implFlags)); + EXPECT_EQ(W("Foo"), readName.substr(0, readNameLength - 1)); + EXPECT_EQ(mdStatic, attr); + EXPECT_EQ(newRva, codeRVA); + EXPECT_EQ(0, implFlags); + EXPECT_THAT(std::vector(sigBlob, sigBlob + sigBlobLength), testing::ContainerEq(std::vector(sig.begin(), sig.end()))); +} + +TEST(MethodDef, SetProps) +{ + minipal::com_ptr emit; + ASSERT_NO_FATAL_FAILURE(CreateEmit(emit)); + mdMethodDef methodDef; + + std::array sig = { (uint8_t)IMAGE_CEE_CS_CALLCONV_DEFAULT, (uint8_t)0, (uint8_t)ELEMENT_TYPE_VOID }; + ULONG rva = 0x424242; + ASSERT_EQ(S_OK, emit->DefineMethod(TokenFromRid(1, mdtTypeDef), W("Foo"), mdStatic, sig.data(), (ULONG)sig.size(), rva, 0, &methodDef)); + ASSERT_EQ(1, RidFromToken(methodDef)); + ASSERT_EQ(mdtMethodDef, TypeFromToken(methodDef)); + minipal::com_ptr import; + ASSERT_EQ(S_OK, emit->QueryInterface(IID_IMetaDataImport, (void**)&import)); + + ULONG newRva = 0x123456; + ASSERT_EQ(S_OK, emit->SetMethodProps(methodDef, mdPublic, newRva, miForwardRef)); + + mdTypeDef type; + WSTR_string readName; + readName.resize(3); + ULONG readNameLength; + + DWORD attr; + PCCOR_SIGNATURE sigBlob; + ULONG sigBlobLength; + ULONG codeRVA; + DWORD implFlags; + ASSERT_EQ(S_OK, import->GetMethodProps(methodDef, &type, &readName[0], (ULONG)readName.capacity(), &readNameLength, &attr, &sigBlob, &sigBlobLength, &codeRVA, &implFlags)); + EXPECT_EQ(W("Foo"), readName.substr(0, readNameLength - 1)); + EXPECT_EQ(mdPublic, attr); + EXPECT_EQ(newRva, codeRVA); + EXPECT_EQ(miForwardRef, implFlags); + EXPECT_THAT(std::vector(sigBlob, sigBlob + sigBlobLength), testing::ContainerEq(std::vector(sig.begin(), sig.end()))); +} diff --git a/src/native/dnmd/test/emit/module.cpp b/src/native/dnmd/test/emit/module.cpp new file mode 100644 index 0000000000000..46cc2e180cb3c --- /dev/null +++ b/src/native/dnmd/test/emit/module.cpp @@ -0,0 +1,88 @@ +#include "emit.hpp" + +TEST(Module, ModuleNameExcludesDirectoryWin32Paths) +{ + WSTR_string moduleName = W("C:\\foo\\bar\\baz.dll"); + minipal::com_ptr emit; + ASSERT_NO_FATAL_FAILURE(CreateEmit(emit)); + ASSERT_EQ(S_OK, emit->SetModuleProps(moduleName.c_str())); + + minipal::com_ptr import; + ASSERT_EQ(S_OK, emit->QueryInterface(IID_IMetaDataImport, (void**)&import)); + + WSTR_string readModuleName; + readModuleName.resize(moduleName.capacity() + 1); + ULONG readModuleNameLength; + + GUID mvid; + ASSERT_EQ(S_OK, import->GetScopeProps(&readModuleName[0], (ULONG)readModuleName.capacity(), &readModuleNameLength, & mvid)); + + EXPECT_EQ(W("baz.dll"), readModuleName.substr(0, readModuleNameLength - 1)); +} + +TEST(Module, ModuleNameExcludesDirectoryUnixPaths) +{ + WSTR_string moduleName = W("/home/foo/bar/baz.dll"); + minipal::com_ptr emit; + ASSERT_NO_FATAL_FAILURE(CreateEmit(emit)); + ASSERT_EQ(S_OK, emit->SetModuleProps(moduleName.c_str())); + + minipal::com_ptr import; + ASSERT_EQ(S_OK, emit->QueryInterface(IID_IMetaDataImport, (void**)&import)); + + WSTR_string readModuleName; + readModuleName.resize(moduleName.capacity() + 1); + ULONG readModuleNameLength; + + GUID mvid; + ASSERT_EQ(S_OK, import->GetScopeProps(&readModuleName[0], (ULONG)readModuleName.capacity(), &readModuleNameLength, & mvid)); + + EXPECT_EQ(W("baz.dll"), readModuleName.substr(0, readModuleNameLength - 1)); +} + +TEST(Module, ModuleNameWithoutDirectory) +{ + WSTR_string moduleName = W("baz.dll"); + minipal::com_ptr emit; + ASSERT_NO_FATAL_FAILURE(CreateEmit(emit)); + ASSERT_EQ(S_OK, emit->SetModuleProps(moduleName.c_str())); + + minipal::com_ptr import; + ASSERT_EQ(S_OK, emit->QueryInterface(IID_IMetaDataImport, (void**)&import)); + + WSTR_string readModuleName; + readModuleName.resize(moduleName.capacity() + 1); + ULONG readModuleNameLength; + + GUID mvid; + ASSERT_EQ(S_OK, import->GetScopeProps(&readModuleName[0], (ULONG)readModuleName.capacity(), &readModuleNameLength, & mvid)); + + EXPECT_EQ(moduleName.length(), readModuleNameLength - 1); + EXPECT_EQ(moduleName, readModuleName.substr(0, readModuleNameLength - 1)); +} + +TEST(Module, EmptyName) +{ + minipal::com_ptr emit; + ASSERT_NO_FATAL_FAILURE(CreateEmit(emit)); + ASSERT_EQ(S_OK, emit->SetModuleProps(W(""))); + + minipal::com_ptr import; + ASSERT_EQ(S_OK, emit->QueryInterface(IID_IMetaDataImport, (void**)&import)); + + std::array readModuleName; + ULONG readModuleNameLength; + + GUID mvid; + ASSERT_EQ(S_OK, import->GetScopeProps(&readModuleName[0], (ULONG)readModuleName.size(), & readModuleNameLength, & mvid)); + + EXPECT_EQ(0, readModuleNameLength); + EXPECT_EQ('\0', readModuleName[0]); +} + +TEST(Module, NullName) +{ + minipal::com_ptr emit; + ASSERT_NO_FATAL_FAILURE(CreateEmit(emit)); + ASSERT_EQ(S_OK, emit->SetModuleProps(nullptr)); +} \ No newline at end of file diff --git a/src/native/dnmd/test/emit/moduleref.cpp b/src/native/dnmd/test/emit/moduleref.cpp new file mode 100644 index 0000000000000..8a7797e76d079 --- /dev/null +++ b/src/native/dnmd/test/emit/moduleref.cpp @@ -0,0 +1,23 @@ +#include "emit.hpp" + +TEST(ModuleRef, Define) +{ + WSTR_string name = W("Foo"); + minipal::com_ptr emit; + ASSERT_NO_FATAL_FAILURE(CreateEmit(emit)); + mdModuleRef moduleRef; + + ASSERT_EQ(S_OK, emit->DefineModuleRef(name.c_str(), &moduleRef)); + + ASSERT_EQ(1, RidFromToken(moduleRef)); + ASSERT_EQ(mdtModuleRef, TypeFromToken(moduleRef)); + + minipal::com_ptr import; + ASSERT_EQ(S_OK, emit->QueryInterface(IID_IMetaDataImport, (void**)&import)); + + WSTR_string readName; + readName.resize(name.capacity() + 1); + ULONG readNameLength; + ASSERT_EQ(S_OK, import->GetModuleRefProps(moduleRef, &readName[0], (ULONG)readName.capacity(), &readNameLength)); + EXPECT_EQ(name, readName.substr(0, readNameLength - 1)); +} \ No newline at end of file diff --git a/src/native/dnmd/test/emit/param.cpp b/src/native/dnmd/test/emit/param.cpp new file mode 100644 index 0000000000000..fe297f109f6ce --- /dev/null +++ b/src/native/dnmd/test/emit/param.cpp @@ -0,0 +1,150 @@ +#include "emit.hpp" + +TEST(Param, Define) +{ + minipal::com_ptr emit; + ASSERT_NO_FATAL_FAILURE(CreateEmit(emit)); + mdParamDef param; + mdMethodDef method; + std::array signature = { IMAGE_CEE_CS_CALLCONV_DEFAULT_HASTHIS, 1, ELEMENT_TYPE_VOID, ELEMENT_TYPE_I4 }; + ASSERT_EQ(S_OK, emit->DefineMethod(TokenFromRid(1, mdtTypeDef), W("Method"), 0, signature.data(), (ULONG)signature.size(), 0, 0, &method)); + WSTR_string paramName{ W("Param") }; + ASSERT_EQ(S_OK, emit->DefineParam(method, 1, paramName.c_str(), pdIn, ELEMENT_TYPE_VOID, nullptr, 0, ¶m)); + ASSERT_EQ(1, RidFromToken(param)); + ASSERT_EQ(mdtParamDef, TypeFromToken(param)); + + minipal::com_ptr import; + ASSERT_EQ(S_OK, emit->QueryInterface(IID_IMetaDataImport, (void**)&import)); + + WSTR_string readName; + readName.resize(paramName.size() + 1); + mdMethodDef readMethod; + ULONG readNameLength; + ULONG flags; + ULONG sequence; + DWORD paramConstType; + UVCP_CONSTANT constValue; + ULONG constValueLength; + ASSERT_EQ(S_OK, import->GetParamProps(param, & readMethod, & sequence, &readName[0], (ULONG)readName.capacity(), & readNameLength, & flags, & paramConstType, & constValue, & constValueLength)); + + EXPECT_EQ(paramName, readName.substr(0, readNameLength - 1)); + EXPECT_EQ(method, readMethod); + EXPECT_EQ(1, sequence); + EXPECT_EQ(pdIn, flags); + EXPECT_EQ(ELEMENT_TYPE_VOID, paramConstType); + EXPECT_EQ(nullptr, constValue); + EXPECT_EQ(0, constValueLength); +} + +TEST(Param, DefineWithConstant) +{ + minipal::com_ptr emit; + ASSERT_NO_FATAL_FAILURE(CreateEmit(emit)); + mdParamDef param; + mdMethodDef method; + std::array signature = { IMAGE_CEE_CS_CALLCONV_DEFAULT_HASTHIS, 1, ELEMENT_TYPE_VOID, ELEMENT_TYPE_I4 }; + ASSERT_EQ(S_OK, emit->DefineMethod(TokenFromRid(1, mdtTypeDef), W("Method"), 0, signature.data(), (ULONG)signature.size(), 0, 0, &method)); + WSTR_string paramName{ W("Param") }; + + int value = 42; + ASSERT_EQ(S_OK, emit->DefineParam(method, 1, paramName.c_str(), pdIn, ELEMENT_TYPE_I4, &value, sizeof(int), ¶m)); + ASSERT_EQ(1, RidFromToken(param)); + ASSERT_EQ(mdtParamDef, TypeFromToken(param)); + + minipal::com_ptr import; + ASSERT_EQ(S_OK, emit->QueryInterface(IID_IMetaDataImport, (void**)&import)); + + WSTR_string readName; + readName.resize(paramName.size() + 1); + mdMethodDef readMethod; + ULONG readNameLength; + ULONG flags; + ULONG sequence; + DWORD paramConstType; + UVCP_CONSTANT constValue; + ULONG constValueLength; + ASSERT_EQ(S_OK, import->GetParamProps(param, & readMethod, & sequence, &readName[0], (ULONG)readName.capacity(), & readNameLength, & flags, & paramConstType, & constValue, & constValueLength)); + + EXPECT_EQ(paramName, readName.substr(0, readNameLength - 1)); + EXPECT_EQ(method, readMethod); + EXPECT_EQ(1, sequence); + EXPECT_EQ(pdIn, flags); + EXPECT_EQ(ELEMENT_TYPE_I4, paramConstType); + + // The constant value should be stored in the metadata in the #Blob heap, not just as a pointer to the passed-in value. + EXPECT_NE(&value, constValue); + EXPECT_EQ(value, *(int*)constValue); + + // Constant length only returned for string constants. + EXPECT_EQ(0, constValueLength); +} + +TEST(Param, DefineWithConstantString) +{ + minipal::com_ptr emit; + ASSERT_NO_FATAL_FAILURE(CreateEmit(emit)); + mdParamDef param; + mdMethodDef method; + std::array signature = { IMAGE_CEE_CS_CALLCONV_DEFAULT_HASTHIS, 1, ELEMENT_TYPE_VOID, ELEMENT_TYPE_STRING }; + ASSERT_EQ(S_OK, emit->DefineMethod(TokenFromRid(1, mdtTypeDef), W("Method"), 0, signature.data(), (ULONG)signature.size(), 0, 0, &method)); + WSTR_string paramName{ W("Param") }; + + WSTR_string value = { W("ConstantValue") }; + ASSERT_EQ(S_OK, emit->DefineParam(method, 1, paramName.c_str(), pdIn, ELEMENT_TYPE_STRING, value.c_str(), (ULONG)value.length(), ¶m)); + ASSERT_EQ(1, RidFromToken(param)); + ASSERT_EQ(mdtParamDef, TypeFromToken(param)); + + minipal::com_ptr import; + ASSERT_EQ(S_OK, emit->QueryInterface(IID_IMetaDataImport, (void**)&import)); + + WSTR_string readName; + readName.resize(paramName.size() + 1); + mdMethodDef readMethod; + ULONG readNameLength; + ULONG flags; + ULONG sequence; + DWORD paramConstType; + UVCP_CONSTANT constValue; + ULONG constValueLength; + ASSERT_EQ(S_OK, import->GetParamProps(param, &readMethod, &sequence, &readName[0], (ULONG)readName.capacity(), &readNameLength, &flags, ¶mConstType, &constValue, &constValueLength)); + + EXPECT_EQ(paramName, readName.substr(0, readNameLength - 1)); + EXPECT_EQ(method, readMethod); + EXPECT_EQ(1, sequence); + EXPECT_EQ(pdIn, flags); + EXPECT_EQ(ELEMENT_TYPE_STRING, paramConstType); + + // The constant value should be stored in the metadata in the #Blob heap, not just as a pointer to the passed-in value. + EXPECT_NE(&value, constValue); + WSTR_string retrievedConstant{ (WCHAR*)constValue, constValueLength }; + EXPECT_EQ(value, retrievedConstant); +} + +TEST(Param, DefineOutOfOrder) +{ + minipal::com_ptr emit; + ASSERT_NO_FATAL_FAILURE(CreateEmit(emit)); + mdParamDef param1; + mdParamDef param; + mdMethodDef method; + std::array signature = { IMAGE_CEE_CS_CALLCONV_DEFAULT_HASTHIS, 1, ELEMENT_TYPE_VOID, ELEMENT_TYPE_I4 }; + ASSERT_EQ(S_OK, emit->DefineMethod(TokenFromRid(1, mdtTypeDef), W("Method"), 0, signature.data(), (ULONG)signature.size(), 0, 0, &method)); + WSTR_string param1Name{ W("Param") }; + WSTR_string paramName{ W("Param0") }; + ASSERT_EQ(S_OK, emit->DefineParam(method, 1, param1Name.c_str(), pdIn, ELEMENT_TYPE_VOID, nullptr, 0, ¶m1)); + ASSERT_EQ(S_OK, emit->DefineParam(method, 0, paramName.c_str(), pdIn, ELEMENT_TYPE_VOID, nullptr, 0, ¶m)); + ASSERT_EQ(2, RidFromToken(param)); + ASSERT_EQ(mdtParamDef, TypeFromToken(param)); + + minipal::com_ptr import; + ASSERT_EQ(S_OK, emit->QueryInterface(IID_IMetaDataImport, (void**)&import)); + + HCORENUM paramEnum = nullptr; + mdParamDef readParams[2]; + ULONG readParamsCount; + ASSERT_EQ(S_OK, import->EnumParams(¶mEnum, method, readParams, 2, &readParamsCount)); + import->CloseEnum(paramEnum); + EXPECT_EQ(2, readParamsCount); + EXPECT_EQ(param, readParams[0]); + EXPECT_EQ(param1, readParams[1]); +} \ No newline at end of file diff --git a/src/native/dnmd/test/emit/standalonesig.cpp b/src/native/dnmd/test/emit/standalonesig.cpp new file mode 100644 index 0000000000000..a94e627d73f75 --- /dev/null +++ b/src/native/dnmd/test/emit/standalonesig.cpp @@ -0,0 +1,23 @@ +#include "emit.hpp" +#include +#include +#include + +TEST(StandaloneSig, Define) +{ + minipal::com_ptr emit; + ASSERT_NO_FATAL_FAILURE(CreateEmit(emit)); + mdSignature sig; + std::array signature = {0x01, 0x02, 0x03}; + ASSERT_EQ(S_OK, emit->GetTokenFromSig(signature.data(), (ULONG)signature.size(), &sig)); + ASSERT_EQ(1, RidFromToken(sig)); + ASSERT_EQ(mdtSignature, TypeFromToken(sig)); + + minipal::com_ptr import; + ASSERT_EQ(S_OK, emit->QueryInterface(IID_IMetaDataImport, (void**)&import)); + + PCCOR_SIGNATURE sigBlob; + ULONG sigBlobLength; + ASSERT_EQ(S_OK, import->GetSigFromToken(sig, &sigBlob, &sigBlobLength)); + EXPECT_THAT(std::vector(sigBlob, sigBlob + sigBlobLength), testing::ContainerEq(std::vector(signature.begin(), signature.end()))); +} \ No newline at end of file diff --git a/src/native/dnmd/test/emit/typedef.cpp b/src/native/dnmd/test/emit/typedef.cpp new file mode 100644 index 0000000000000..6e895d670632a --- /dev/null +++ b/src/native/dnmd/test/emit/typedef.cpp @@ -0,0 +1,203 @@ +#include "emit.hpp" + +TEST(TypeDef, Define) +{ + WSTR_string name = W("Foo"); + minipal::com_ptr emit; + ASSERT_NO_FATAL_FAILURE(CreateEmit(emit)); + mdTypeDef typeDef; + mdToken implements = mdTokenNil; + + ASSERT_EQ(S_OK, emit->DefineTypeDef(name.c_str(), 0, mdTypeDefNil, &implements, &typeDef)); + + // The first type is the type, + // so the second type is the one we just defined. + ASSERT_EQ(2, RidFromToken(typeDef)); + ASSERT_EQ(mdtTypeDef, TypeFromToken(typeDef)); + + minipal::com_ptr import; + ASSERT_EQ(S_OK, emit->QueryInterface(IID_IMetaDataImport, (void**)&import)); + + WSTR_string readName; + readName.resize(name.capacity() + 1); + ULONG readNameLength; + DWORD typeDefFlags; + mdToken extends; + ASSERT_EQ(S_OK, import->GetTypeDefProps(typeDef, &readName[0], (ULONG)readName.capacity(), &readNameLength, &typeDefFlags, &extends)); + EXPECT_EQ(name, readName.substr(0, readNameLength - 1)); + EXPECT_EQ(0, typeDefFlags); + EXPECT_EQ(mdTypeRefNil, extends); + + HCORENUM hEnum = nullptr; + mdFieldDef field; + ULONG count; + EXPECT_EQ(S_FALSE, import->EnumFields(&hEnum, typeDef, &field, 1, &count)); + import->CloseEnum(hEnum); + + hEnum = nullptr; + mdMethodDef method; + EXPECT_EQ(S_FALSE, import->EnumMethods(&hEnum, typeDef, &method, 1, &count)); + import->CloseEnum(hEnum); +} + +TEST(TypeDef, DefineWithInterfaces) +{ + WSTR_string name = W("Foo"); + minipal::com_ptr emit; + ASSERT_NO_FATAL_FAILURE(CreateEmit(emit)); + mdTypeDef typeDef; + mdToken implements[] = { TokenFromRid(1, mdtTypeRef), mdTokenNil }; + + ASSERT_EQ(S_OK, emit->DefineTypeDef(name.c_str(), 0, mdTypeDefNil, implements, &typeDef)); + + // The first type is the type, + // so the second type is the one we just defined. + ASSERT_EQ(2, RidFromToken(typeDef)); + ASSERT_EQ(mdtTypeDef, TypeFromToken(typeDef)); + + minipal::com_ptr import; + ASSERT_EQ(S_OK, emit->QueryInterface(IID_IMetaDataImport, (void**)&import)); + + HCORENUM hEnum = nullptr; + mdInterfaceImpl interfaceImpls[2] = {}; + ULONG count; + ASSERT_EQ(S_OK, import->EnumInterfaceImpls(&hEnum, typeDef, interfaceImpls, 2, &count)); + ASSERT_EQ(1, count); + EXPECT_EQ(TokenFromRid(1, mdtInterfaceImpl), interfaceImpls[0]); + EXPECT_EQ(0, interfaceImpls[1]); // The second element should not be touched. + import->CloseEnum(hEnum); + + mdTypeDef classType; + mdToken interfaceType; + ASSERT_EQ(S_OK, import->GetInterfaceImplProps(interfaceImpls[0], &classType, &interfaceType)); + EXPECT_EQ(typeDef, classType); + EXPECT_EQ(TokenFromRid(1, mdtTypeRef), interfaceType); + +} + +TEST(TypeDef, DefineWithBase) +{ + WSTR_string name = W("Foo"); + minipal::com_ptr emit; + ASSERT_NO_FATAL_FAILURE(CreateEmit(emit)); + mdTypeDef typeDef; + mdToken base = TokenFromRid(1, mdtTypeRef); + mdToken implements = mdTokenNil; + + ASSERT_EQ(S_OK, emit->DefineTypeDef(name.c_str(), 0, base, &implements, &typeDef)); + + // The first type is the type, + // so the second type is the one we just defined. + ASSERT_EQ(2, RidFromToken(typeDef)); + ASSERT_EQ(mdtTypeDef, TypeFromToken(typeDef)); + + minipal::com_ptr import; + ASSERT_EQ(S_OK, emit->QueryInterface(IID_IMetaDataImport, (void**)&import)); + + WSTR_string readName; + readName.resize(name.capacity() + 1); + ULONG readNameLength; + DWORD typeDefFlags; + mdToken extends; + ASSERT_EQ(S_OK, import->GetTypeDefProps(typeDef, &readName[0], (ULONG)readName.capacity(), &readNameLength, &typeDefFlags, &extends)); + EXPECT_EQ(name, readName.substr(0, readNameLength - 1)); + EXPECT_EQ(0, typeDefFlags); + EXPECT_EQ(base, extends); +} + +TEST(TypeDef, NestedDefine) +{ + WSTR_string name = W("Foo"); + minipal::com_ptr emit; + ASSERT_NO_FATAL_FAILURE(CreateEmit(emit)); + mdTypeDef outerTypeDef, typeDef; + mdToken base = TokenFromRid(1, mdtTypeRef); + mdToken outerImplements = mdTokenNil; + mdToken implements[] = { TokenFromRid(1, mdtTypeRef), mdTokenNil }; + + ASSERT_EQ(S_OK, emit->DefineTypeDef(name.c_str(), 0, mdTypeDefNil, &outerImplements, &outerTypeDef)); + ASSERT_EQ(S_OK, emit->DefineNestedType(name.c_str(), 0, base, implements, outerTypeDef, &typeDef)); + + // The first type is the type, + // the second is the outer type, + // so the third is the nested type. + ASSERT_EQ(3, RidFromToken(typeDef)); + ASSERT_EQ(mdtTypeDef, TypeFromToken(typeDef)); + + minipal::com_ptr import; + ASSERT_EQ(S_OK, emit->QueryInterface(IID_IMetaDataImport, (void**)&import)); + + WSTR_string readName; + readName.resize(name.capacity() + 1); + ULONG readNameLength; + DWORD typeDefFlags; + mdToken extends; + ASSERT_EQ(S_OK, import->GetTypeDefProps(typeDef, &readName[0], (ULONG)readName.capacity(), &readNameLength, &typeDefFlags, &extends)); + EXPECT_EQ(name, readName.substr(0, readNameLength - 1)); + EXPECT_EQ(0, typeDefFlags); + EXPECT_EQ(base, extends); + + HCORENUM hEnum = nullptr; + mdInterfaceImpl interfaceImpls[2] = {}; + ULONG count; + ASSERT_EQ(S_OK, import->EnumInterfaceImpls(&hEnum, typeDef, interfaceImpls, 2, &count)); + ASSERT_EQ(1, count); + EXPECT_EQ(TokenFromRid(1, mdtInterfaceImpl), interfaceImpls[0]); + EXPECT_EQ(0, interfaceImpls[1]); // The second element should not be touched. + import->CloseEnum(hEnum); + + mdTypeDef classType; + mdToken interfaceType; + ASSERT_EQ(S_OK, import->GetInterfaceImplProps(interfaceImpls[0], &classType, &interfaceType)); + EXPECT_EQ(typeDef, classType); + EXPECT_EQ(TokenFromRid(1, mdtTypeRef), interfaceType); + + mdTypeDef enclosing; + ASSERT_EQ(S_OK, import->GetNestedClassProps(typeDef, &enclosing)); + EXPECT_EQ(outerTypeDef, enclosing); +} + +TEST(TypeDef, SetProps) +{ + WSTR_string name = W("Foo"); + minipal::com_ptr emit; + ASSERT_NO_FATAL_FAILURE(CreateEmit(emit)); + mdTypeDef typeDef; + mdToken initialImplements[] = { TokenFromRid(2, mdtTypeSpec), mdTokenNil }; + mdToken extends = TokenFromRid(1, mdtTypeRef); + + ASSERT_EQ(S_OK, emit->DefineTypeDef(name.c_str(), 0, mdTypeDefNil, initialImplements, &typeDef)); + + mdToken implements[] = { TokenFromRid(4, mdtTypeRef), mdTokenNil }; + ASSERT_EQ(S_OK, emit->SetTypeDefProps(typeDef, tdAbstract, extends, implements)); + + minipal::com_ptr import; + ASSERT_EQ(S_OK, emit->QueryInterface(IID_IMetaDataImport, (void**)&import)); + + WSTR_string readName; + readName.resize(name.capacity() + 1); + ULONG readNameLength; + DWORD typeDefFlags; + mdToken readExtends; + ASSERT_EQ(S_OK, import->GetTypeDefProps(typeDef, &readName[0], (ULONG)readName.capacity(), &readNameLength, &typeDefFlags, &readExtends)); + EXPECT_EQ(name, readName.substr(0, readNameLength - 1)); + EXPECT_EQ(tdAbstract, typeDefFlags); + EXPECT_EQ(extends, readExtends); + + HCORENUM hEnum = nullptr; + mdInterfaceImpl interfaceImpls[2] = {}; + ULONG count; + ASSERT_EQ(S_OK, import->EnumInterfaceImpls(&hEnum, typeDef, interfaceImpls, 2, &count)); + ASSERT_EQ(1, count); + // We should have created a new entry for the new interface implementation, + // not modified the existing one. + EXPECT_EQ(TokenFromRid(2, mdtInterfaceImpl), interfaceImpls[0]); + EXPECT_EQ(0, interfaceImpls[1]); // The second element should not be touched. + import->CloseEnum(hEnum); + + mdTypeDef classType; + mdToken interfaceType; + ASSERT_EQ(S_OK, import->GetInterfaceImplProps(interfaceImpls[0], &classType, &interfaceType)); + EXPECT_EQ(typeDef, classType); + EXPECT_EQ(implements[0], interfaceType); +} diff --git a/src/native/dnmd/test/emit/typeref.cpp b/src/native/dnmd/test/emit/typeref.cpp new file mode 100644 index 0000000000000..ff8d7b49cb48c --- /dev/null +++ b/src/native/dnmd/test/emit/typeref.cpp @@ -0,0 +1,53 @@ +#include "emit.hpp" + +TEST(TypeRef, ValidScopeAndDottedName) +{ + minipal::com_ptr emit; + ASSERT_NO_FATAL_FAILURE(CreateEmit(emit)); + mdTypeRef typeRef; + WSTR_string name = W("System.Object"); + ASSERT_EQ(S_OK, emit->DefineTypeRefByName(TokenFromRid(1, mdtModule), name.c_str(), &typeRef)); + ASSERT_EQ(1, RidFromToken(typeRef)); + ASSERT_EQ(mdtTypeRef, TypeFromToken(typeRef)); + + minipal::com_ptr import; + ASSERT_EQ(S_OK, emit->QueryInterface(IID_IMetaDataImport, (void**)&import)); + mdToken resolutionScope; + WSTR_string readName; + readName.resize(name.capacity() + 1); + ULONG readNameLength; + ASSERT_EQ(S_OK, import->GetTypeRefProps(typeRef, &resolutionScope, &readName[0], (ULONG) readName.size(), &readNameLength)); + EXPECT_EQ(TokenFromRid(1, mdtModule), resolutionScope); + EXPECT_EQ(readNameLength, name.size() + 1); + EXPECT_EQ(name, readName.substr(0, readNameLength - 1)); +} + +TEST(TypeRef, InvalidScope) +{ + minipal::com_ptr emit; + ASSERT_NO_FATAL_FAILURE(CreateEmit(emit)); + mdTypeRef typeRef; + ASSERT_EQ(E_FAIL, emit->DefineTypeRefByName(TokenFromRid(1, mdtTypeDef), W("System.Object"), &typeRef)); +} + +TEST(TypeRef, ValidScopeAndNonDottedName) +{ + minipal::com_ptr emit; + ASSERT_NO_FATAL_FAILURE(CreateEmit(emit)); + mdTypeRef typeRef; + WSTR_string name = W("Bar"); + ASSERT_EQ(S_OK, emit->DefineTypeRefByName(TokenFromRid(1, mdtModule), name.c_str(), &typeRef)); + ASSERT_EQ(1, RidFromToken(typeRef)); + ASSERT_EQ(mdtTypeRef, TypeFromToken(typeRef)); + + minipal::com_ptr import; + ASSERT_EQ(S_OK, emit->QueryInterface(IID_IMetaDataImport, (void**)&import)); + mdToken resolutionScope; + WSTR_string readName; + readName.resize(name.capacity() + 1); + ULONG readNameLength; + ASSERT_EQ(S_OK, import->GetTypeRefProps(typeRef, &resolutionScope, &readName[0], (ULONG) readName.size(), &readNameLength)); + EXPECT_EQ(TokenFromRid(1, mdtModule), resolutionScope); + EXPECT_EQ(readNameLength, name.size() + 1); + EXPECT_EQ(name, readName.substr(0, readNameLength - 1)); +} \ No newline at end of file diff --git a/src/native/dnmd/test/emit/typespec.cpp b/src/native/dnmd/test/emit/typespec.cpp new file mode 100644 index 0000000000000..810f9fcc92d3c --- /dev/null +++ b/src/native/dnmd/test/emit/typespec.cpp @@ -0,0 +1,23 @@ +#include "emit.hpp" +#include +#include +#include + +TEST(TypeSpec, Define) +{ + minipal::com_ptr emit; + ASSERT_NO_FATAL_FAILURE(CreateEmit(emit)); + mdTypeSpec spec; + std::array signature = {0x01, 0x02, 0x03}; + ASSERT_EQ(S_OK, emit->GetTokenFromTypeSpec(signature.data(), (ULONG)signature.size(), &spec)); + ASSERT_EQ(1, RidFromToken(spec)); + ASSERT_EQ(mdtTypeSpec, TypeFromToken(spec)); + + minipal::com_ptr import; + ASSERT_EQ(S_OK, emit->QueryInterface(IID_IMetaDataImport, (void**)&import)); + + PCCOR_SIGNATURE sigBlob; + ULONG sigBlobLength; + ASSERT_EQ(S_OK, import->GetTypeSpecFromToken(spec, &sigBlob, &sigBlobLength)); + EXPECT_THAT(std::vector(sigBlob, sigBlob + sigBlobLength), testing::ContainerEq(std::vector(signature.begin(), signature.end()))); +} \ No newline at end of file diff --git a/src/native/dnmd/test/regpal/CMakeLists.txt b/src/native/dnmd/test/regpal/CMakeLists.txt new file mode 100644 index 0000000000000..29aaf7158616f --- /dev/null +++ b/src/native/dnmd/test/regpal/CMakeLists.txt @@ -0,0 +1,19 @@ +set(SOURCES + ./pal.cpp +) + +add_library(regpal STATIC ${SOURCES}) + +target_link_libraries(regpal PUBLIC nethost minipal_com) + +if (NOT WIN32) + target_link_libraries(regpal PUBLIC minipal_comhdrs) +else() + target_link_libraries(regpal PUBLIC WIL Shlwapi) +endif() + +if (NOT WIN32 AND NOT APPLE) + target_link_libraries(regpal PUBLIC dl) +endif() + +target_include_directories(regpal PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/src/native/dnmd/test/regpal/pal.cpp b/src/native/dnmd/test/regpal/pal.cpp new file mode 100644 index 0000000000000..39e6319d01a36 --- /dev/null +++ b/src/native/dnmd/test/regpal/pal.cpp @@ -0,0 +1,248 @@ +#define MINIPAL_COM_DEFINE_GUID +#include "pal.hpp" + +#include +#include + +#ifdef BUILD_WINDOWS +#include +#include +#include +#else +#include +#include +#endif + +#include +#include + +#ifdef BUILD_WINDOWS +#define W_StdString(str) std::wstring{L##str} +#else +#define W_StdString(str) std::string{str} +#endif + +#ifdef BUILD_WINDOWS +std::wostream& pal::cout() +{ + return std::wcout; +} +std::wostream& pal::cerr() +{ + return std::wcerr; +} +#else +std::ostream& pal::cout() +{ + return std::cout; +} +std::ostream& pal::cerr() +{ + return std::cerr; +} +#endif + +namespace +{ + void* LoadModule(pal::path path) + { +#ifdef BUILD_WINDOWS + return ::LoadLibraryW(path.c_str()); +#else + return ::dlopen(path.c_str(), RTLD_LAZY); +#endif + } + + void* GetSymbol(void* module, char const* name) + { +#ifdef BUILD_WINDOWS + return ::GetProcAddress((HMODULE)module, name); +#else + return ::dlsym(module, name); +#endif + } + + using MetaDataGetDispenser = HRESULT(STDMETHODCALLTYPE*)(REFCLSID, REFIID, LPVOID*); + + using CoreCLRInitialize = int(STDMETHODCALLTYPE*)( + char const* exePath, + char const* appDomainFriendlyName, + int propertyCount, + char const** propertyKeys, + char const** propertyValues, + void** hostHandle, + uint32_t* domainId); + + MetaDataGetDispenser LoadGetDispenser() + { + auto coreClrPath = pal::GetCoreClrPath(); + if (coreClrPath.empty()) + { + std::cerr << "Failed to get coreclr path" << std::endl; + return nullptr; + } + + auto mod = LoadModule(coreClrPath); + if (mod == nullptr) + { + pal::cerr() << X("Failed to load metadata baseline module: ") << coreClrPath << std::endl; + return nullptr; + } +#ifndef BUILD_WINDOWS + // On non-Windows, the metadata APIs in CoreCLR don't work until the PAL is initialized. + // Initialize the runtime just enough to load the PAL. + auto init = (CoreCLRInitialize)GetSymbol(mod, "coreclr_initialize"); + if (init == nullptr) + { + pal::cerr() << X("Failed to find coreclr_initialize in module: ") << coreClrPath << std::endl; + return nullptr; + } + + char const* propertyKeys[] = { "TRUSTED_PLATFORM_ASSEMBLIES" }; + char const* propertyValues[] = { coreClrPath.c_str() }; + init("regpal", "regpal", 1, propertyKeys, propertyValues, nullptr, nullptr); +#endif + + auto getDispenser = (MetaDataGetDispenser)GetSymbol(mod, "MetaDataGetDispenser"); + if (getDispenser == nullptr) + { + pal::cerr() << X("Failed to find MetaDataGetDispenser in module: ") << coreClrPath << std::endl; + return nullptr; + } + + return getDispenser; + } + + MetaDataGetDispenser GetDispenser = LoadGetDispenser(); +} + +HRESULT pal::GetBaselineMetadataDispenser(IMetaDataDispenser** dispenser) +{ + if (GetDispenser == nullptr) + { + return E_FAIL; + } + + return GetDispenser(CLSID_CorMetaDataDispenser, IID_IMetaDataDispenser, (void**)dispenser); +} + +bool pal::ReadFile(pal::path path, malloc_span& b) +{ +#ifdef BUILD_WINDOWS + wil::unique_handle file{ CreateFileW(path.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr) }; + if (!file) + return false; + + DWORD size = GetFileSize(file.get(), nullptr); + if (size == INVALID_FILE_SIZE) + return false; + + b = { (uint8_t*)std::malloc(size), size }; + + DWORD bytesRead; + if (!::ReadFile(file.get(), b.data(), (DWORD)b.size(), &bytesRead, nullptr)) + return false; + + return bytesRead == b.size(); +#else + struct stat st; + if (stat(path.c_str(), &st) != 0) + return false; + b = { (uint8_t*)std::malloc((size_t)st.st_size), (size_t)st.st_size }; + + std::ifstream file{ path, std::ios::binary }; + + if (!file) + return false; + + file.read((char*)b.data(), b.size()); + + return true; +#endif +} + +constexpr int NetHostBufferTooSmall = 0x80008098; + +pal::path pal::GetCoreClrPath() +{ + int result = 0; + size_t bufferSize = 4096; + std::unique_ptr hostfxr_path; + do + { + hostfxr_path.reset(new char_t[bufferSize]); + result = get_hostfxr_path(hostfxr_path.get(), &bufferSize, nullptr); + } while (result == NetHostBufferTooSmall); + + if (result != 0) + { + std::cerr << "Failed to get hostfxr path. Error code: " << std::hex << result << std::dec << std::endl; + return {}; + } + + pal::path hostFxrPath = hostfxr_path.get(); + void* hostfxrModule = LoadModule(hostfxr_path.get()); + if (hostfxrModule == nullptr) + { + cerr() << "Failed to load hostfxr module: " << hostFxrPath << std::endl; + return {}; + } + + + // The hostfxr path is in the form: /host/fxr//hostfxr.dll + // We need to get the dotnet root, which is 3 levels up + // We need to do this because hostfxr_get_dotnet_environment_info only returns information + // for a globally-installed dotnet if we don't pass a path to the dotnet root. + // The macOS machines on GitHub Actions don't have dotnet globally installed. +#ifdef BUILD_WINDOWS + pal::path dotnetRoot = hostFxrPath.substr(0, hostFxrPath.find(X("host\\fxr"), 0)); +#else + pal::path dotnetRoot = hostFxrPath.substr(0, hostFxrPath.find(X("host/fxr"), 0)); +#endif + + pal::path coreClrPath = {}; + auto getDotnetEnvironmentInfo = (hostfxr_get_dotnet_environment_info_fn)GetSymbol(hostfxrModule, "hostfxr_get_dotnet_environment_info"); + if (getDotnetEnvironmentInfo( + dotnetRoot.c_str(), + nullptr, + [](const hostfxr_dotnet_environment_info* info, void* result_context) + { + path& coreClrPath = *(path*)result_context; + for (size_t i = 0; i < info->framework_count; ++i) + { + if (info->frameworks[i].name == W_StdString("Microsoft.NETCore.App")) + { + coreClrPath = info->frameworks[i].path; + coreClrPath += X('/'); + coreClrPath += info->frameworks[i].version; + coreClrPath += X('/'); +#ifdef BUILD_WINDOWS + coreClrPath += X("coreclr.dll"); +#elif BUILD_MACOS + coreClrPath += X("libcoreclr.dylib"); +#elif BUILD_UNIX + coreClrPath += X("libcoreclr.so"); +#else +#error "Unknown platform, cannot determine name for CoreCLR executable" +#endif + } + } + }, + &coreClrPath + ) != 0) + { + std::cerr << "Failed to get dotnet environment info" << std::endl; + return {}; + } + + return coreClrPath; +} + +bool pal::FileExists(pal::path path) +{ +#ifdef BUILD_WINDOWS + return PathFileExistsW(path.c_str()); +#else + return access(path.c_str(), F_OK) != -1; +#endif +} diff --git a/src/native/dnmd/test/regpal/pal.hpp b/src/native/dnmd/test/regpal/pal.hpp new file mode 100644 index 0000000000000..e1ac293e92617 --- /dev/null +++ b/src/native/dnmd/test/regpal/pal.hpp @@ -0,0 +1,39 @@ +#ifndef _TEST_REGPAL_PAL_H_ +#define _TEST_REGPAL_PAL_H_ + +#include +#include + +#include +#include +#include + +#ifdef BUILD_WINDOWS +#define X(str) W(str) +#else +#define X(str) str +#endif + +namespace pal +{ +#ifdef BUILD_WINDOWS + using path = std::wstring; +#else + using path = std::string; +#endif + path GetCoreClrPath(); + HRESULT GetBaselineMetadataDispenser(IMetaDataDispenser** dispenser); + bool ReadFile(path path, malloc_span& b); + + bool FileExists(path path); + +#ifdef BUILD_WINDOWS + std::wostream& cout(); + std::wostream& cerr(); +#else + std::ostream& cout(); + std::ostream& cerr(); +#endif +} + +#endif // !_TEST_REGPAL_PAL_H_ diff --git a/src/native/dnmd/test/regperf/CMakeLists.txt b/src/native/dnmd/test/regperf/CMakeLists.txt new file mode 100644 index 0000000000000..52fd1c218cde2 --- /dev/null +++ b/src/native/dnmd/test/regperf/CMakeLists.txt @@ -0,0 +1,22 @@ +set(SOURCES + ./perf.cpp +) + +add_executable(regperf + ${SOURCES} +) + +target_link_libraries(regperf + dnmd::interfaces + minipal_com + regpal) + +if(NOT MSVC) + target_link_libraries(regperf minipal_comhdrs) +endif() + + +target_link_libraries(regperf benchmark::benchmark) + +install(DIRECTORY ${RegressionLocatorDirectory} DESTINATION bin) +install(TARGETS regperf DESTINATION bin) diff --git a/src/native/dnmd/test/regperf/perf.cpp b/src/native/dnmd/test/regperf/perf.cpp new file mode 100644 index 0000000000000..f2d1d3b3e21ab --- /dev/null +++ b/src/native/dnmd/test/regperf/perf.cpp @@ -0,0 +1,217 @@ +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +#ifdef _MSC_VER +#define EXPORT extern "C" __declspec(dllexport) +#else +#define EXPORT extern "C" __attribute__((__visibility__("default"))) +#endif // !_MSC_VER + +#define RETURN_IF_FAILED(x) { auto hr = x; if (FAILED(hr)) return hr; } + +namespace +{ + void const* g_data; + uint32_t g_dataLen; + + IMetaDataDispenser* g_baselineDisp; + IMetaDataDispenser* g_currentDisp; + + IMetaDataImport* g_baselineImport; + IMetaDataImport* g_currentImport; + + HRESULT CreateImport(IMetaDataDispenser* disp, IMetaDataImport** import) + { + assert(disp != nullptr && import != nullptr); + return disp->OpenScopeOnMemory( + g_data, + g_dataLen, + CorOpenFlags::ofReadOnly, + IID_IMetaDataImport, + reinterpret_cast(import)); + } + + HRESULT EnumTypeDefs(IMetaDataImport* import) + { + assert(import != nullptr); + HRESULT hr; + HCORENUM hcorenum = {}; + uint32_t buffer[1]; + uint32_t count; + while (S_OK == (hr = import->EnumTypeDefs(&hcorenum, buffer, ARRAY_SIZE(buffer), (ULONG*)&count)) + && count != 0) + { + } + return hr; + } + + HRESULT GetScopeProps(IMetaDataImport* import) + { + assert(import != nullptr); + WCHAR name[512]; + ULONG nameLen; + GUID mvid; + return import->GetScopeProps(name, ARRAY_SIZE(name), &nameLen, &mvid); + } + + HRESULT EnumUserStrings(IMetaDataImport* import) + { + assert(import != nullptr); + HRESULT hr; + HCORENUM hcorenum = {}; + uint32_t buffer[1]; + uint32_t count; + while (S_OK == (hr = import->EnumUserStrings(&hcorenum, buffer, ARRAY_SIZE(buffer), (ULONG*)&count)) + && count != 0) + { + } + return hr; + } + + HRESULT GetCustomAttributeByName(IMetaDataImport* import) + { + assert(import != nullptr); + mdToken tk = TokenFromRid(2, mdtTypeDef); + void const* data; + uint32_t dataLen; + return import->GetCustomAttributeByName(tk, W("NotAnAttribute"), &data, (ULONG*)&dataLen); + } +} + +HRESULT PerfInitialize( + void const* data, + uint32_t dataLen) +{ + if (data == nullptr) + return E_INVALIDARG; + + g_data = data; + g_dataLen = dataLen; + + RETURN_IF_FAILED(CreateImport(g_baselineDisp, &g_baselineImport)); + + RETURN_IF_FAILED(CreateImport(g_currentDisp, &g_currentImport)); + + return S_OK; +} + +void CreateImport(benchmark::State& state, IMetaDataDispenser* disp) +{ + IMetaDataImport* import; + for (auto _ : state) + { + if (SUCCEEDED(CreateImport(disp, &import))) + (void)import->Release(); + } +} + +BENCHMARK_CAPTURE(CreateImport, BaselineCreateImport, g_baselineDisp); +BENCHMARK_CAPTURE(CreateImport, CurrentCreateImport, g_currentDisp); + +#define IMPORT_BENCHMARK(func) \ + BENCHMARK_CAPTURE(func, Baseline##func, g_baselineImport); \ + BENCHMARK_CAPTURE(func, Current##func, g_currentImport); + +void EnumTypeDefs(benchmark::State& state, IMetaDataImport* import) +{ + for (auto _ : state) + { + if (FAILED(EnumTypeDefs(import))) + { + state.SkipWithError("Failed to enumerate typedefs"); + } + } +} + +IMPORT_BENCHMARK(EnumTypeDefs); + +void GetScopeProps(benchmark::State& state, IMetaDataImport* import) +{ + HRESULT hr; + for (auto _ : state) + { + if (FAILED(hr = GetScopeProps(import))) + { + state.SkipWithError("Failed to get scope props"); + } + } +} + +IMPORT_BENCHMARK(GetScopeProps); + +void EnumUserStrings(benchmark::State& state, IMetaDataImport* import) +{ + HRESULT hr; + for (auto _ : state) + { + if (FAILED(hr = EnumUserStrings(import))) + { + state.SkipWithError("Failed to enumerate user strings"); + } + } +} + +IMPORT_BENCHMARK(EnumUserStrings); + +void EnumCustomAttributeByName(benchmark::State& state, IMetaDataImport* import) +{ + HRESULT hr; + for (auto _ : state) + { + if (FAILED(hr = GetCustomAttributeByName(import))) + { + state.SkipWithError("Failed to get custom attributes"); + } + } +} + +IMPORT_BENCHMARK(EnumCustomAttributeByName); + +int MAIN_CALLCONV main(int argc, char** argv) +{ + RETURN_IF_FAILED(pal::GetBaselineMetadataDispenser(&g_baselineDisp)); + RETURN_IF_FAILED(GetDispenser(IID_IMetaDataDispenser, reinterpret_cast(&g_currentDisp))); + auto coreClrPath = pal::GetCoreClrPath(); + if (coreClrPath.empty()) + { + std::cerr << "Failed to get coreclr path" << std::endl; + return -1; + } + + pal::path dataImagePath = std::move(coreClrPath); + dataImagePath = dataImagePath.substr(0, dataImagePath.find_last_of(X('/'))) + X("/") + X("System.Private.CoreLib.dll"); + + pal::cerr() << X("Loading System.Private.CoreLib from: ") << dataImagePath << std::endl; + + malloc_span dataImage; + if (!pal::ReadFile(dataImagePath, dataImage)) + { + std::cerr << "Failed to read System.Private.CoreLib" << std::endl; + return EXIT_FAILURE; + } + + if (!get_metadata_from_pe(dataImage)) + { + std::cerr << "Failed to get metadata from System.Private.CoreLib" << std::endl; + return EXIT_FAILURE; + } + + RETURN_IF_FAILED(PerfInitialize( + dataImage.data(), + (uint32_t)dataImage.size())); + + benchmark::Initialize(&argc, argv); + benchmark::RunSpecifiedBenchmarks(); + benchmark::Shutdown(); + return EXIT_SUCCESS; +} diff --git a/src/native/dnmd/test/regtest/CMakeLists.txt b/src/native/dnmd/test/regtest/CMakeLists.txt new file mode 100644 index 0000000000000..96f0fa1a6fe3b --- /dev/null +++ b/src/native/dnmd/test/regtest/CMakeLists.txt @@ -0,0 +1,40 @@ +set(HEADERS + ./baseline.h + ./fixtures.h + ./asserts.h +) +set(SOURCES + ./main.cpp + ./discovery.cpp + ./metadata.cpp +) + +add_executable(regtest ${SOURCES} ${HEADERS}) +target_link_libraries(regtest PRIVATE dnmd::interfaces gtest gmock minipal_com regpal) # Reference gmock for better collection assertions + +target_compile_definitions(regtest PRIVATE COM_NO_WINDOWS_H) + +if (NOT WIN32) + target_link_libraries(regtest PRIVATE minipal_comhdrs) +else() + target_link_libraries(regtest PRIVATE WIL) +endif() + +add_custom_target(Regression.TargetAssembly + dotnet build ${CMAKE_CURRENT_SOURCE_DIR}/../Regression.TargetAssembly/Regression.TargetAssembly.ilproj -c $ + BYPRODUCTS ${CMAKE_SOURCE_DIR}/artifacts/managed/bin/Regression.TargetAssembly/$>/Regression.TargetAssembly.dll + COMMENT "Building Regression.TargetAssembly.dll" + ) + +add_dependencies(regtest Regression.TargetAssembly) + +add_custom_command(TARGET regtest POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy $ $ + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_SOURCE_DIR}/artifacts/managed/bin/Regression.TargetAssembly/$>/Regression.TargetAssembly.dll $) + +if(WIN32) + add_custom_command(TARGET regtest POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy $ $) +endif() + +# gtest_discover_tests(regtest DISCOVERY_TIMEOUT 1200) diff --git a/src/native/dnmd/test/regtest/asserts.h b/src/native/dnmd/test/regtest/asserts.h new file mode 100644 index 0000000000000..0cc2c4b1ea487 --- /dev/null +++ b/src/native/dnmd/test/regtest/asserts.h @@ -0,0 +1,25 @@ +#ifndef _TEST_REGTEST_ASSERTS_H_ +#define _TEST_REGTEST_ASSERTS_H_ + +#include +#include + +#define EXPECT_THAT_AND_RETURN(a, match) ([&](){ auto&& _actual = (a); EXPECT_THAT(_actual, match); return _actual; }()) + +template +void AssertEqualAndSet(T& result, T&& expected, T&& actual) +{ + ASSERT_EQ(actual, expected); + result = std::move(actual); +} + +template +void AssertEqualAndSet(std::vector& result, std::vector&& expected, std::vector&& actual) +{ + ASSERT_THAT(actual, ::testing::ElementsAreArray(expected)); + result = std::move(actual); +} + +#define ASSERT_EQUAL_AND_SET(result, expected, actual) ASSERT_NO_FATAL_FAILURE(AssertEqualAndSet(result, expected, actual)) + +#endif // !_TEST_REGTEST_ASSERTS_H_ diff --git a/src/native/dnmd/test/regtest/baseline.h b/src/native/dnmd/test/regtest/baseline.h new file mode 100644 index 0000000000000..f0caceb07e4d1 --- /dev/null +++ b/src/native/dnmd/test/regtest/baseline.h @@ -0,0 +1,14 @@ +#ifndef _TEST_REGTEST_BASELINE_H_ +#define _TEST_REGTEST_BASELINE_H_ + +#include +#include + +namespace TestBaseline +{ + extern minipal::com_ptr Metadata; + extern minipal::com_ptr DeltaMetadataBuilder; + extern minipal::com_ptr Symbol; +} + +#endif // !_TEST_REGTEST_BASELINE_H_ \ No newline at end of file diff --git a/src/native/dnmd/test/regtest/discovery.cpp b/src/native/dnmd/test/regtest/discovery.cpp new file mode 100644 index 0000000000000..1838809e510fc --- /dev/null +++ b/src/native/dnmd/test/regtest/discovery.cpp @@ -0,0 +1,390 @@ +#include "fixtures.h" +#include "baseline.h" +#include +#include +#include + +#ifdef BUILD_WINDOWS +#include +#include +#include +#else +#define THROW_IF_FAILED(x) do { HRESULT hr = (x); if (FAILED(hr)) { throw std::runtime_error("Failed HR when running '" #x "'"); } } while (false) +#include +#endif + +#include + +#ifdef BUILD_WINDOWS +#define DNNE_API_OVERRIDE __declspec(dllimport) +#endif + +namespace +{ + pal::path baselinePath; + pal::path regressionAssemblyPath; + + template + struct OnExit + { + T callback; + ~OnExit() + { + callback(); + } + }; + + template + [[nodiscard]] OnExit on_scope_exit(T callback) + { + return { callback }; + } + + malloc_span ReadMetadataFromFile(pal::path path) + { + malloc_span b; + if (!pal::ReadFile(path, b) + || !get_metadata_from_pe(b)) + { + return {}; + } + + return b; + } + + // Create an image with indirection tables, like an image that has had a delta applied to it. + // This is used to test that the importer can handle out-of-order rows. + // This image is intentinally minimal as our other regression tests cover more full-filled metadata scenarios. + malloc_span CreateImageWithIndirectionTables() + { + std::cout << "Creating image with indirection tables" << std::endl; + minipal::com_ptr image; + THROW_IF_FAILED(TestBaseline::DeltaMetadataBuilder->DefineScope(CLSID_CorMetaDataRuntime, 0, IID_IMetaDataEmit, (IUnknown**)&image)); + + THROW_IF_FAILED(image->SetModuleProps(W("IndirectionTables.dll"))); + + minipal::com_ptr assemblyEmit; + THROW_IF_FAILED(image->QueryInterface(IID_IMetaDataAssemblyEmit, (void**)&assemblyEmit)); + + ASSEMBLYMETADATA assemblyMetadata = { 0 }; + + mdAssemblyRef systemRuntimeRef; + THROW_IF_FAILED(assemblyEmit->DefineAssemblyRef(nullptr, 0, W("System.Runtime"), &assemblyMetadata, nullptr, 0, 0, &systemRuntimeRef)); + + mdTypeRef systemObject; + THROW_IF_FAILED(image->DefineTypeRefByName(systemRuntimeRef, W("System.Object"), &systemObject)); + + // Define two types so we can define out-of-order rows. + mdTypeDef type1; + THROW_IF_FAILED(image->DefineTypeDef(W("Type1"), tdSealed, systemObject, nullptr, &type1)); + + mdTypeDef type2; + THROW_IF_FAILED(image->DefineTypeDef(W("Type2"), tdSealed, systemObject, nullptr, &type2)); + + // Define a signature that has two parameters and a return type. + // This will provide us with enough structure to define out-of-order Param rows. + std::array signature = { (uint8_t)IMAGE_CEE_CS_CALLCONV_DEFAULT, (uint8_t)0x02, (uint8_t)ELEMENT_TYPE_I4, (uint8_t)ELEMENT_TYPE_I2, (uint8_t)ELEMENT_TYPE_I8}; + + mdMethodDef method1; + THROW_IF_FAILED(image->DefineMethod(type1, W("Method1"), 0, signature.data(), (ULONG)signature.size(), 0, 0, &method1)); + + mdParamDef param1; + THROW_IF_FAILED(image->DefineParam(method1, 2, W("Param2"), 0, 0, nullptr, 0, ¶m1)); + + // Define the Param row for the first parameter after we've already defined the second parameter. + mdParamDef paramOutOfOrder; + THROW_IF_FAILED(image->DefineParam(method1, 1, W("Param1"), 0, 0, nullptr, 0, ¶mOutOfOrder)); + + mdMethodDef method2; + THROW_IF_FAILED(image->DefineMethod(type2, W("Method2"), 0, signature.data(), (ULONG)signature.size(), 0, 0, &method2)); + + // Define a method on the first type after we've already defined a method on the second type. + mdMethodDef methodOutOfOrder; + THROW_IF_FAILED(image->DefineMethod(type1, W("MethodOutOfOrder"), 0, signature.data(), (ULONG)signature.size(), 0, 0, &methodOutOfOrder)); + + std::array fieldSignature = { (uint8_t)IMAGE_CEE_CS_CALLCONV_FIELD, (uint8_t)ELEMENT_TYPE_I4 }; + + mdFieldDef field1; + THROW_IF_FAILED(image->DefineField(type1, W("Field1"), 0, fieldSignature.data(), (ULONG)fieldSignature.size(), 0, nullptr, 0, &field1)); + + mdFieldDef field2; + THROW_IF_FAILED(image->DefineField(type2, W("Field2"), 0, fieldSignature.data(), (ULONG)fieldSignature.size(), 0, nullptr, 0, &field2)); + + // Define a field on the first type after we've already defined a field on the second type. + mdFieldDef fieldOutOfOrder; + THROW_IF_FAILED(image->DefineField(type1, W("FieldOutOfOrder"), 0, fieldSignature.data(), (ULONG)fieldSignature.size(), 0, nullptr, 0, &fieldOutOfOrder)); + + std::array propertySignature = { (uint8_t)IMAGE_CEE_CS_CALLCONV_PROPERTY, (uint8_t)ELEMENT_TYPE_I4 }; + std::array getterSignature = { (uint8_t)IMAGE_CEE_CS_CALLCONV_DEFAULT, (uint8_t)0x00, (uint8_t)ELEMENT_TYPE_I4 }; + + mdMethodDef getter1; + THROW_IF_FAILED(image->DefineMethod(type1, W("get_Property1"), 0, getterSignature.data(), (ULONG)getterSignature.size(), 0, 0, &getter1)); + + mdProperty property1; + THROW_IF_FAILED(image->DefineProperty(type1, W("Property1"), 0, propertySignature.data(), (ULONG)propertySignature.size(), 0, nullptr, 0, getter1, mdMethodDefNil, nullptr, &property1)); + + mdMethodDef getter2; + THROW_IF_FAILED(image->DefineMethod(type2, W("get_Property2"), 0, getterSignature.data(), (ULONG)getterSignature.size(), 0, 0, &getter2)); + + mdProperty property2; + THROW_IF_FAILED(image->DefineProperty(type2, W("Property2"), 0, propertySignature.data(), (ULONG)propertySignature.size(), 0, nullptr, 0, getter2, mdMethodDefNil, nullptr, &property2)); + + // Define a property on the first type after we've already defined a property on the second type. + mdProperty propertyOutOfOrder; + THROW_IF_FAILED(image->DefineProperty(type1, W("PropertyOutOfOrder"), 0, propertySignature.data(), (ULONG)propertySignature.size(), 0, nullptr, 0, mdMethodDefNil, mdMethodDefNil, nullptr, &propertyOutOfOrder)); + + mdTypeRef eventHandlerRef; + THROW_IF_FAILED(image->DefineTypeRefByName(systemRuntimeRef, W("System.EventHandler"), &eventHandlerRef)); + + mdEvent event1; + THROW_IF_FAILED(image->DefineEvent(type1, W("Event1"), 0, eventHandlerRef, mdMethodDefNil, mdMethodDefNil, mdMethodDefNil, nullptr, &event1)); + + mdEvent event2; + THROW_IF_FAILED(image->DefineEvent(type2, W("Event2"), 0, eventHandlerRef, mdMethodDefNil, mdMethodDefNil, mdMethodDefNil, nullptr, &event2)); + + // Define an event on the first type after we've already defined an event on the second type. + mdEvent eventOutOfOrder; + THROW_IF_FAILED(image->DefineEvent(type1, W("EventOutOfOrder"), 0, eventHandlerRef, mdMethodDefNil, mdMethodDefNil, mdMethodDefNil, nullptr, &eventOutOfOrder)); + + ULONG size; + THROW_IF_FAILED(image->GetSaveSize(cssAccurate, &size)); + + malloc_span imageWithIndirectionTables{ (uint8_t*)malloc(size), size }; + THROW_IF_FAILED(image->SaveToMemory(imageWithIndirectionTables.data(), size)); + + return imageWithIndirectionTables; + } + + malloc_span GetMetadataFromKey(std::string key) + { + if (key == IndirectionTablesKey) + { + return CreateImageWithIndirectionTables(); + } + return {}; + } +} + +std::vector MetadataFilesInDirectory(pal::path directory) +{ + pal::cout() << X("Discovering metadata files in directory: ") << directory << std::endl; + std::vector scenarios; + + if (!pal::FileExists(directory)) + { + pal::cout() << X("Directory '") << directory << X("' does not exist") << std::endl; + return scenarios; + } +#ifdef BUILD_WINDOWS + pal::path pattern = directory + X("\\*.dll"); + WIN32_FIND_DATAW findData; + auto findHandle = FindFirstFileW(pattern.c_str(), &findData); + if (findHandle == INVALID_HANDLE_VALUE) + { + return scenarios; + } + + do + { + if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + { + continue; + } + + auto fileName = findData.cFileName; + if (wcsstr(fileName, L".Native.") != nullptr + || wcsstr(fileName, L".Thunk.") != nullptr) + { + continue; + } + + if (wcsstr(fileName, L"System.") != fileName + && wcsstr(fileName, L"Microsoft.") != fileName) + { + continue; + } + + std::wcout << "Found file: " << fileName << std::endl; + scenarios.emplace_back(MetadataFile::Kind::OnDisk, fileName); + } while (FindNextFileW(findHandle, &findData)); +#else + DIR* dirInfo; + dirent *file; + if ((dirInfo = opendir(directory.c_str())) == nullptr) + { + return scenarios; + } + + while ((file = readdir(dirInfo)) != nullptr) + { + if (file->d_type != DT_REG) + { + continue; + } + + auto fileName = file->d_name; + if (strstr(fileName, ".Native.") != nullptr + || strstr(fileName, ".Thunk.") != nullptr) + { + continue; + } + + if (strstr(fileName, "System.") != fileName + && strstr(fileName, "Microsoft.") != fileName) + { + continue; + } + + if (strstr(fileName, ".dll") == nullptr) + { + continue; + } + + std::cout << "Found file: " << fileName << std::endl; + scenarios.emplace_back(MetadataFile::Kind::OnDisk, fileName); + } + +#endif + return scenarios; +} + +std::vector CoreLibFiles() +{ + std::cout << "Discovering CoreLib files" << std::endl; + std::vector scenarios; + + scenarios.emplace_back(MetadataFile::Kind::OnDisk, (GetBaselineDirectory() + X("/System.Private.CoreLib.dll")), "System_Private_CoreLib"); + +#ifdef BUILD_WINDOWS + scenarios.emplace_back(MetadataFile::Kind::OnDisk, FindFrameworkInstall(X("v4.0.30319")).append(X("\\mscorlib.dll")), "4_0_mscorlib"); + + auto fx2mscorlib = FindFrameworkInstall(X("v2.0.50727")).append(X("\\mscorlib.dll")); + if (pal::FileExists(fx2mscorlib)) + { + scenarios.emplace_back(MetadataFile::Kind::OnDisk, fx2mscorlib, "2_0_mscorlib"); + } +#endif + return scenarios; +} + +namespace +{ + std::mutex metadataCacheMutex; + + struct MetadataFileHash + { + size_t operator()(const MetadataFile& file) const + { + return std::hash{}(file.pathOrKey); + } + }; + + std::unordered_map, MetadataFileHash> metadataCache; +} + +span GetMetadataForFile(MetadataFile file) +{ + std::lock_guard lock{ metadataCacheMutex }; + auto it = metadataCache.find(file); + if (it != metadataCache.end()) + { + return it->second; + } + + malloc_span b; + if (file.kind == MetadataFile::Kind::OnDisk) + { + pal::path pathOrKey; + +#ifdef BUILD_WINDOWS + wil::AdaptFixedSizeToAllocatedResult(pathOrKey, [&](LPWSTR value, size_t valueLength, size_t* valueLengthNeededWithNul) + { + *valueLengthNeededWithNul = ::MultiByteToWideChar(CP_UTF8, 0, file.pathOrKey.c_str(), (int)file.pathOrKey.size(), value, (int)valueLength) + 1; + if (*valueLengthNeededWithNul == 0) + { + return HRESULT_FROM_WIN32(GetLastError()); + } + return S_OK; + }); +#else + pathOrKey = file.pathOrKey; +#endif + + pal::path path; + if (pathOrKey[0] == X('/')) + { + path = pathOrKey; + } + else + { + path = baselinePath.substr(0, baselinePath.find_last_of('/')) + X("/") + pathOrKey; + } + b = ReadMetadataFromFile(path); + } + else + { + b = GetMetadataFromKey(file.pathOrKey.c_str()); + } + + if (b.size() == 0) + { + return {}; + } + + span spanToReturn = b; + + [[maybe_unused]] auto [_, inserted] = metadataCache.emplace(std::move(file), std::move(b)); + assert(inserted); + return spanToReturn; +} + +std::string PrintName(testing::TestParamInfo info) +{ + if (info.param.testNameOverride.size() > 0) + { + return info.param.testNameOverride; + } + std::string name; + if (info.param.kind == MetadataFile::Kind::OnDisk) + { + name = info.param.pathOrKey.substr(0, info.param.pathOrKey.find_last_of('.')); + std::replace(name.begin(), name.end(), '.', '_'); + } + else + { + name = info.param.pathOrKey + "_InMemory"; + } + return name; +} + +pal::path GetBaselineDirectory() +{ + return baselinePath.substr(0, baselinePath.find_last_of(X('/'))); +} + +void SetBaselineModulePath(pal::path path) +{ + baselinePath = std::move(path); +} + +void SetRegressionAssemblyPath(pal::path path) +{ + regressionAssemblyPath = path; +} + +malloc_span GetRegressionAssemblyMetadata() +{ + return ReadMetadataFromFile(regressionAssemblyPath); +} + +pal::path FindFrameworkInstall(pal::path version) +{ + pal::cout() << X("Discovering framework install for version: ") << version << std::endl; +#ifdef BUILD_WINDOWS + auto key = wil::reg::create_unique_key(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\.NETFramework"); + pal::path installPath{ wil::reg::get_value_string(key.get(), L"InstallRoot") }; + return installPath.append(version); +#else + return {}; +#endif +} diff --git a/src/native/dnmd/test/regtest/fixtures.h b/src/native/dnmd/test/regtest/fixtures.h new file mode 100644 index 0000000000000..594783486e114 --- /dev/null +++ b/src/native/dnmd/test/regtest/fixtures.h @@ -0,0 +1,68 @@ +#ifndef _TEST_REGTEST_FIXTURES_H_ +#define _TEST_REGTEST_FIXTURES_H_ + +#include + +#include + +#include +#include +#include "pal.hpp" + +struct MetadataFile final +{ + enum class Kind + { + OnDisk, + Generated + } kind; + + MetadataFile(Kind kind, std::string pathOrKey, std::string testNameOverride = "") + : kind(kind), pathOrKey(std::move(pathOrKey)), testNameOverride(testNameOverride) {} + +#ifdef BUILD_WINDOWS + MetadataFile(Kind kind, pal::path pathOrKey, std::string testNameOverride = "") + : kind(kind), pathOrKey(), testNameOverride(testNameOverride) + { + ULONG length = WideCharToMultiByte(CP_UTF8, 0, pathOrKey.c_str(), (int)pathOrKey.size(), nullptr, 0, nullptr, nullptr); + this->pathOrKey.resize(length); + WideCharToMultiByte(CP_UTF8, 0, pathOrKey.c_str(), (int)pathOrKey.size(), &this->pathOrKey[0], length, nullptr, nullptr); + } +#endif + + std::string pathOrKey; + std::string testNameOverride; + + bool operator==(const MetadataFile& rhs) const noexcept + { + return kind == rhs.kind && pathOrKey == rhs.pathOrKey; + } +}; + +inline static std::string IndirectionTablesKey = "IndirectionTables"; + +std::string PrintName(testing::TestParamInfo info); + +std::vector MetadataFilesInDirectory(pal::path directory); + +std::vector CoreLibFiles(); + +span GetMetadataForFile(MetadataFile file); + +malloc_span GetRegressionAssemblyMetadata(); + +pal::path FindFrameworkInstall(pal::path version); + +pal::path GetBaselineDirectory(); + +void SetBaselineModulePath(pal::path path); + +void SetRegressionAssemblyPath(pal::path path); + +class RegressionTest : public ::testing::TestWithParam +{ +protected: + using TokenList = std::vector; +}; + +#endif // !_TEST_REGTEST_FIXTURES_H_ diff --git a/src/native/dnmd/test/regtest/main.cpp b/src/native/dnmd/test/regtest/main.cpp new file mode 100644 index 0000000000000..cfbaf789105b6 --- /dev/null +++ b/src/native/dnmd/test/regtest/main.cpp @@ -0,0 +1,75 @@ +#include +#include +#ifdef BUILD_WINDOWS +#include +#include +#else +#define RETURN_IF_FAILED(x) { auto hr = x; if (FAILED(hr)) return hr; } +#endif +#include "baseline.h" +#include "fixtures.h" +#include "pal.hpp" + +namespace TestBaseline +{ + minipal::com_ptr Metadata = nullptr; + minipal::com_ptr DeltaMetadataBuilder = nullptr; + minipal::com_ptr Symbol = nullptr; +} + + +class ThrowListener final : public testing::EmptyTestEventListener +{ + void OnTestPartResult(testing::TestPartResult const& result) override + { + if (result.fatally_failed()) + { + throw testing::AssertionException(result); + } + } +}; + +int MAIN_CALLCONV main(int argc, char** argv) +{ + RETURN_IF_FAILED(pal::GetBaselineMetadataDispenser(&TestBaseline::Metadata)); + + minipal::com_ptr deltaBuilder; + RETURN_IF_FAILED(pal::GetBaselineMetadataDispenser(&deltaBuilder)); + RETURN_IF_FAILED(deltaBuilder->QueryInterface(IID_IMetaDataDispenserEx, (void**)&TestBaseline::DeltaMetadataBuilder)); + + VARIANT vt; + V_VT(&vt) = VT_UI4; + V_UI4(&vt) = MDUpdateExtension; + if (HRESULT hr = TestBaseline::DeltaMetadataBuilder->SetOption(MetaDataSetENC, &vt); FAILED(hr)) + return hr; + + auto coreClrPath = pal::GetCoreClrPath(); + pal::cout() << X("Loaded metadata baseline module: ") << coreClrPath << std::endl; + SetBaselineModulePath(std::move(coreClrPath)); + +#ifdef BUILD_WINDOWS + pal::path regressionAssemblyPath; + wil::AdaptFixedSizeToAllocatedResult(regressionAssemblyPath, [&](LPWSTR value, size_t valueLength, size_t* valueLengthNeededWithNul) + { + *valueLengthNeededWithNul = ::MultiByteToWideChar(CP_UTF8, 0, argv[0], (int)strlen(argv[0]), value, (int)valueLength) + 1; + if (*valueLengthNeededWithNul == 0) + { + return HRESULT_FROM_WIN32(GetLastError()); + } + return S_OK; + }); + regressionAssemblyPath.erase(regressionAssemblyPath.find_last_of(X('\\')) + 1).append(X("Regression.TargetAssembly.dll")); +#else + std::string regressionAssemblyPath = argv[0]; + regressionAssemblyPath.erase(regressionAssemblyPath.find_last_of(X('/')) + 1).append(X("Regression.TargetAssembly.dll")); +#endif + + SetRegressionAssemblyPath(regressionAssemblyPath); + + pal::cout() << X("Regression assembly path: ") << regressionAssemblyPath << std::endl; + + ::testing::InitGoogleTest(&argc, argv); + testing::UnitTest::GetInstance()->listeners().Append(new ThrowListener); + + return RUN_ALL_TESTS(); +} diff --git a/src/native/dnmd/test/regtest/metadata.cpp b/src/native/dnmd/test/regtest/metadata.cpp new file mode 100644 index 0000000000000..8c29aef60b525 --- /dev/null +++ b/src/native/dnmd/test/regtest/metadata.cpp @@ -0,0 +1,2098 @@ +#include "asserts.h" +#include "fixtures.h" +#include "baseline.h" + +#include + +#include +#include + +#include +#include + +#ifndef BUILD_WINDOWS +#define EXPECT_HRESULT_SUCCEEDED(hr) EXPECT_THAT((hr), testing::Ge(S_OK)) +#define EXPECT_HRESULT_FAILED(hr) EXPECT_THAT((hr), testing::Lt(0)) +#define ASSERT_HRESULT_SUCCEEDED(hr) ASSERT_THAT((hr), testing::Ge(S_OK)) +#define ASSERT_HRESULT_FAILED(hr) ASSERT_THAT((hr), testing::Lt(0)) +#endif + +namespace +{ + HRESULT CreateImport(IMetaDataDispenser* disp, void const* data, uint32_t dataLen, IMetaDataImport2** import) + { + assert(disp != nullptr && data != nullptr && dataLen > 0 && import != nullptr); + return disp->OpenScopeOnMemory( + data, + dataLen, + CorOpenFlags::ofReadOnly, + IID_IMetaDataImport2, + reinterpret_cast(import)); + } + + template + using static_enum_buffer = std::array; + + template + using static_char_buffer = std::array; + + // default values recommended by http://isthe.com/chongo/tech/comp/fnv/ + uint32_t const Prime = 0x01000193; // 16777619 + uint32_t const Seed = 0x811C9DC5; // 2166136261 + // hash a single byte + uint32_t fnv1a(uint8_t oneByte, uint32_t hash = Seed) + { + return (oneByte ^ hash) * Prime; + } + + // Based on https://create.stephan-brumme.com/fnv-hash/ + uint32_t HashCharArray(static_char_buffer const& arr, uint32_t written) + { + uint32_t hash = Seed; + auto curr = std::begin(arr); + auto end = curr + written; + for (; curr < end; ++curr) + { + WCHAR c = *curr; + std::array r; + memcpy(r.data(), &c, r.size()); + for (uint8_t b : r) + hash = fnv1a(b, hash); + } + return hash; + } + + // Based on https://create.stephan-brumme.com/fnv-hash/ + uint32_t HashByteArray(void const* arr, size_t byteLength) + { + uint32_t hash = Seed; + auto curr = (uint8_t const*)arr; + auto end = curr + byteLength; + for (; curr < end; ++curr) + { + hash = fnv1a(*curr, hash); + } + return hash; + } + + std::vector GetCustomAttributeByName(IMetaDataImport2* import, LPCWSTR customAttr, mdToken tkObj) + { + std::vector values; + + void const* ppData; + ULONG pcbData; + HRESULT hr = import->GetCustomAttributeByName(tkObj, + customAttr, + &ppData, + &pcbData); + + values.push_back(hr); + if (hr == S_OK) + { + values.push_back(HashByteArray(ppData, pcbData)); + values.push_back(pcbData); + } + return values; + } + + std::vector GetCustomAttribute_Nullable(IMetaDataImport2* import, mdToken tkObj) + { + auto NullableAttrName = W("System.Runtime.CompilerServices.NullableAttribute"); + return GetCustomAttributeByName(import, NullableAttrName, tkObj); + } + + std::vector GetCustomAttribute_CompilerGenerated(IMetaDataImport2* import, mdToken tkObj) + { + auto CompilerGeneratedAttrName = W("System.Runtime.CompilerServices.CompilerGeneratedAttribute"); + return GetCustomAttributeByName(import, CompilerGeneratedAttrName, tkObj); + } + + void ValidateAndCloseEnum(IMetaDataImport2* import, HCORENUM hcorenum, ULONG expectedCount) + { + ULONG count; + ASSERT_HRESULT_SUCCEEDED(import->CountEnum(hcorenum, &count)); + ASSERT_EQ(count, expectedCount); + import->CloseEnum(hcorenum); + } + + std::vector EnumTypeDefs(IMetaDataImport2* import) + { + std::vector tokens; + static_enum_buffer tokensBuffer{}; + HCORENUM hcorenum{}; + ULONG returned; + while (0 == import->EnumTypeDefs(&hcorenum, tokensBuffer.data(), (ULONG)tokensBuffer.size(), &returned) + && returned != 0) + { + for (ULONG i = 0; i < returned; ++i) + { + tokens.push_back(tokensBuffer[i]); + } + } + ValidateAndCloseEnum(import, hcorenum, (ULONG)tokens.size()); + return tokens; + } + + std::vector EnumTypeRefs(IMetaDataImport2* import) + { + std::vector tokens; + static_enum_buffer tokensBuffer{}; + HCORENUM hcorenum{}; + ULONG returned; + while (0 == import->EnumTypeRefs(&hcorenum, tokensBuffer.data(), (ULONG)tokensBuffer.size(), &returned) + && returned != 0) + { + for (ULONG i = 0; i < returned; ++i) + tokens.push_back(tokensBuffer[i]); + } + ValidateAndCloseEnum(import, hcorenum, (ULONG)tokens.size()); + return tokens; + } + + std::vector EnumTypeSpecs(IMetaDataImport2* import) + { + std::vector tokens; + static_enum_buffer tokensBuffer{}; + HCORENUM hcorenum{}; + ULONG returned; + while (0 == import->EnumTypeSpecs(&hcorenum, tokensBuffer.data(), (ULONG)tokensBuffer.size(), &returned) + && returned != 0) + { + for (ULONG i = 0; i < returned; ++i) + tokens.push_back(tokensBuffer[i]); + } + ValidateAndCloseEnum(import, hcorenum, (ULONG)tokens.size()); + return tokens; + } + + std::vector EnumModuleRefs(IMetaDataImport2* import) + { + std::vector tokens; + static_enum_buffer tokensBuffer{}; + HCORENUM hcorenum{}; + ULONG returned; + while (0 == import->EnumModuleRefs(&hcorenum, tokensBuffer.data(), (ULONG)tokensBuffer.size(), &returned) + && returned != 0) + { + for (ULONG i = 0; i < returned; ++i) + tokens.push_back(tokensBuffer[i]); + } + ValidateAndCloseEnum(import, hcorenum, (ULONG)tokens.size()); + return tokens; + } + + std::vector EnumInterfaceImpls(IMetaDataImport2* import, mdTypeDef typdef) + { + std::vector tokens; + static_enum_buffer tokensBuffer{}; + HCORENUM hcorenum{}; + ULONG returned; + while (0 == import->EnumInterfaceImpls(&hcorenum, typdef, tokensBuffer.data(), (ULONG)tokensBuffer.size(), &returned) + && returned != 0) + { + for (ULONG i = 0; i < returned; ++i) + tokens.push_back(tokensBuffer[i]); + } + ValidateAndCloseEnum(import, hcorenum, (ULONG)tokens.size()); + return tokens; + } + + std::vector EnumMembers(IMetaDataImport2* import, mdTypeDef typdef) + { + std::vector tokens; + static_enum_buffer tokensBuffer{}; + HCORENUM hcorenum{}; + ULONG returned; + while (0 == import->EnumMembers(&hcorenum, typdef, tokensBuffer.data(), (ULONG)tokensBuffer.size(), &returned) + && returned != 0) + { + for (ULONG i = 0; i < returned; ++i) + tokens.push_back(tokensBuffer[i]); + } + ValidateAndCloseEnum(import, hcorenum, (ULONG)tokens.size()); + return tokens; + } + + std::vector EnumMembersWithName(IMetaDataImport2* import, mdTypeDef typdef, LPCWSTR memberName = W(".ctor")) + { + std::vector tokens; + static_enum_buffer tokensBuffer{}; + HCORENUM hcorenum{}; + ULONG returned; + while (0 == import->EnumMembersWithName(&hcorenum, typdef, memberName, tokensBuffer.data(), (ULONG)tokensBuffer.size(), &returned) + && returned != 0) + { + for (ULONG i = 0; i < returned; ++i) + tokens.push_back(tokensBuffer[i]); + } + ValidateAndCloseEnum(import, hcorenum, (ULONG)tokens.size()); + return tokens; + } + + std::vector EnumMemberRefs(IMetaDataImport2* import, mdToken tkParent) + { + std::vector tokens; + static_enum_buffer tokensBuffer{}; + HCORENUM hcorenum{}; + ULONG returned; + while (0 == import->EnumMemberRefs(&hcorenum, tkParent, tokensBuffer.data(), (ULONG)tokensBuffer.size(), &returned) + && returned != 0) + { + for (ULONG i = 0; i < returned; ++i) + tokens.push_back(tokensBuffer[i]); + } + ValidateAndCloseEnum(import, hcorenum, (ULONG)tokens.size()); + return tokens; + } + + std::vector EnumMethods(IMetaDataImport2* import, mdTypeDef typdef) + { + std::vector tokens; + static_enum_buffer tokensBuffer{}; + HCORENUM hcorenum{}; + ULONG returned; + while (0 == import->EnumMethods(&hcorenum, typdef, tokensBuffer.data(), (ULONG)tokensBuffer.size(), &returned) + && returned != 0) + { + for (ULONG i = 0; i < returned; ++i) + tokens.push_back(tokensBuffer[i]); + } + ValidateAndCloseEnum(import, hcorenum, (ULONG)tokens.size()); + return tokens; + } + + std::vector EnumMethodsWithName(IMetaDataImport2* import, mdToken typdef, LPCWSTR methodName = W(".ctor")) + { + std::vector tokens; + static_enum_buffer tokensBuffer{}; + HCORENUM hcorenum{}; + ULONG returned; + while (0 == import->EnumMethodsWithName(&hcorenum, typdef, methodName, tokensBuffer.data(), (ULONG)tokensBuffer.size(), &returned) + && returned != 0) + { + for (ULONG i = 0; i < returned; ++i) + tokens.push_back(tokensBuffer[i]); + } + ValidateAndCloseEnum(import, hcorenum, (ULONG)tokens.size()); + return tokens; + } + + std::vector EnumMethodImpls(IMetaDataImport2* import, mdTypeDef typdef) + { + std::vector tokens; + static_enum_buffer tokensBuffer1{}; + static_enum_buffer tokensBuffer2{}; + HCORENUM hcorenum{}; + ULONG returned; + while (0 == import->EnumMethodImpls(&hcorenum, typdef, tokensBuffer1.data(), tokensBuffer2.data(), (ULONG)tokensBuffer1.size(), &returned) + && returned != 0) + { + for (ULONG i = 0; i < returned; ++i) + { + tokens.push_back(tokensBuffer1[i]); + tokens.push_back(tokensBuffer2[i]); + } + } + ValidateAndCloseEnum(import, hcorenum, (ULONG)(tokens.size() / 2)); + return tokens; + } + + std::vector EnumMethodSemantics(IMetaDataImport2* import, mdMethodDef mb) + { + std::vector tokens; + static_enum_buffer tokensBuffer{}; + HCORENUM hcorenum{}; + ULONG returned; + while (0 == import->EnumMethodSemantics(&hcorenum, mb, tokensBuffer.data(), (ULONG)tokensBuffer.size(), &returned) + && returned != 0) + { + for (ULONG i = 0; i < returned; ++i) + tokens.push_back(tokensBuffer[i]); + } + ValidateAndCloseEnum(import, hcorenum, (ULONG)tokens.size()); + return tokens; + } + + std::vector EnumParams(IMetaDataImport2* import, mdMethodDef methoddef) + { + std::vector tokens; + static_enum_buffer tokensBuffer{}; + HCORENUM hcorenum{}; + ULONG returned; + while (0 == import->EnumParams(&hcorenum, methoddef, tokensBuffer.data(), (ULONG)tokensBuffer.size(), &returned) + && returned != 0) + { + for (ULONG i = 0; i < returned; ++i) + tokens.push_back(tokensBuffer[i]); + } + ValidateAndCloseEnum(import, hcorenum, (ULONG)tokens.size()); + return tokens; + } + + std::vector EnumMethodSpecs(IMetaDataImport2* import, mdToken tk) + { + std::vector tokens; + static_enum_buffer tokensBuffer{}; + HCORENUM hcorenum{}; + ULONG returned; + while (0 == import->EnumMethodSpecs(&hcorenum, tk, tokensBuffer.data(), (ULONG)tokensBuffer.size(), &returned) + && returned != 0) + { + for (ULONG i = 0; i < returned; ++i) + tokens.push_back(tokensBuffer[i]); + } + ValidateAndCloseEnum(import, hcorenum, (ULONG)tokens.size()); + return tokens; + } + + std::vector EnumEvents(IMetaDataImport2* import, mdTypeDef tk) + { + std::vector tokens; + static_enum_buffer tokensBuffer{}; + HCORENUM hcorenum{}; + ULONG returned; + while (0 == import->EnumEvents(&hcorenum, tk, tokensBuffer.data(), (ULONG)tokensBuffer.size(), &returned) + && returned != 0) + { + for (ULONG i = 0; i < returned; ++i) + tokens.push_back(tokensBuffer[i]); + } + ValidateAndCloseEnum(import, hcorenum, (ULONG)tokens.size()); + return tokens; + } + + std::vector EnumProperties(IMetaDataImport2* import, mdTypeDef tk) + { + std::vector tokens; + static_enum_buffer tokensBuffer{}; + HCORENUM hcorenum{}; + ULONG returned; + while (0 == import->EnumProperties(&hcorenum, tk, tokensBuffer.data(), (ULONG)tokensBuffer.size(), &returned) + && returned != 0) + { + for (ULONG i = 0; i < returned; ++i) + tokens.push_back(tokensBuffer[i]); + } + ValidateAndCloseEnum(import, hcorenum, (ULONG)tokens.size()); + return tokens; + } + + std::vector EnumFields(IMetaDataImport2* import, mdTypeDef tk) + { + std::vector tokens; + static_enum_buffer tokensBuffer{}; + HCORENUM hcorenum{}; + ULONG returned; + while (0 == import->EnumFields(&hcorenum, tk, tokensBuffer.data(), (ULONG)tokensBuffer.size(), &returned) + && returned != 0) + { + for (ULONG i = 0; i < returned; ++i) + tokens.push_back(tokensBuffer[i]); + } + ValidateAndCloseEnum(import, hcorenum, (ULONG)tokens.size()); + return tokens; + } + + std::vector EnumFieldsWithName(IMetaDataImport2* import, mdTypeDef tk, LPCWSTR name = W("_name")) + { + std::vector tokens; + static_enum_buffer tokensBuffer{}; + HCORENUM hcorenum{}; + ULONG returned; + while (0 == import->EnumFieldsWithName(&hcorenum, tk, name, tokensBuffer.data(), (ULONG)tokensBuffer.size(), &returned) + && returned != 0) + { + for (ULONG i = 0; i < returned; ++i) + tokens.push_back(tokensBuffer[i]); + } + ValidateAndCloseEnum(import, hcorenum, (ULONG)tokens.size()); + return tokens; + } + + std::vector EnumSignatures(IMetaDataImport2* import) + { + std::vector tokens; + static_enum_buffer tokensBuffer{}; + HCORENUM hcorenum{}; + ULONG returned; + while (0 == import->EnumSignatures(&hcorenum, tokensBuffer.data(), (ULONG)tokensBuffer.size(), &returned) + && returned != 0) + { + for (ULONG i = 0; i < returned; ++i) + tokens.push_back(tokensBuffer[i]); + } + ValidateAndCloseEnum(import, hcorenum, (ULONG)tokens.size()); + return tokens; + } + + std::vector EnumUserStrings(IMetaDataImport2* import) + { + std::vector tokens; + static_enum_buffer tokensBuffer{}; + HCORENUM hcorenum{}; + ULONG returned; + while (0 == import->EnumUserStrings(&hcorenum, tokensBuffer.data(), (ULONG)tokensBuffer.size(), &returned) + && returned != 0) + { + for (ULONG i = 0; i < returned; ++i) + tokens.push_back(tokensBuffer[i]); + } + ValidateAndCloseEnum(import, hcorenum, (ULONG)tokens.size()); + return tokens; + } + + std::vector EnumCustomAttributes(IMetaDataImport2* import, mdToken tk = mdTokenNil, mdToken tkType = mdTokenNil) + { + std::vector tokens; + static_enum_buffer tokensBuffer{}; + HCORENUM hcorenum{}; + ULONG returned; + while (0 == import->EnumCustomAttributes(&hcorenum, tk, tkType, tokensBuffer.data(), (ULONG)tokensBuffer.size(), &returned) + && returned != 0) + { + for (ULONG i = 0; i < returned; ++i) + tokens.push_back(tokensBuffer[i]); + } + ValidateAndCloseEnum(import, hcorenum, (ULONG)tokens.size()); + return tokens; + } + + std::vector EnumGenericParams(IMetaDataImport2* import, mdToken tk) + { + std::vector tokens; + static_enum_buffer tokensBuffer{}; + HCORENUM hcorenum{}; + ULONG returned; + while (0 == import->EnumGenericParams(&hcorenum, tk, tokensBuffer.data(), (ULONG)tokensBuffer.size(), &returned) + && returned != 0) + { + for (ULONG i = 0; i < returned; ++i) + tokens.push_back(tokensBuffer[i]); + } + ValidateAndCloseEnum(import, hcorenum, (ULONG)tokens.size()); + return tokens; + } + + std::vector EnumGenericParamConstraints(IMetaDataImport2* import, mdGenericParam tk) + { + std::vector tokens; + static_enum_buffer tokensBuffer{}; + HCORENUM hcorenum{}; + ULONG returned; + while (0 == import->EnumGenericParamConstraints(&hcorenum, tk, tokensBuffer.data(), (ULONG)tokensBuffer.size(), &returned) + && returned != 0) + { + for (ULONG i = 0; i < returned; ++i) + tokens.push_back(tokensBuffer[i]); + } + ValidateAndCloseEnum(import, hcorenum, (ULONG)tokens.size()); + return tokens; + } + + std::vector EnumPermissionSetsAndGetProps(IMetaDataImport2* import, mdToken permTk) + { + std::vector values; + static_enum_buffer tokensBuffer{}; + + // See CorDeclSecurity for actions definitions + for (int32_t action = (int32_t)dclActionNil; action <= dclMaximumValue; ++action) + { + std::vector tokens; + HCORENUM hcorenum{}; + { + ULONG returned; + while (0 == import->EnumPermissionSets(&hcorenum, permTk, action, tokensBuffer.data(), (ULONG)tokensBuffer.size(), &returned) + && returned != 0) + { + for (ULONG j = 0; j < returned; ++j) + { + tokens.push_back(tokensBuffer[j]); + } + } + ValidateAndCloseEnum(import, hcorenum, (ULONG)tokens.size()); + } + + for (uint32_t pk : tokens) + { + DWORD a; + void const* ppvPermission; + ULONG pcbPermission; + HRESULT hr = import->GetPermissionSetProps(pk, &a, &ppvPermission, &pcbPermission); + values.push_back(hr); + if (hr == S_OK) + { + values.push_back(a); + values.push_back(HashByteArray(ppvPermission, pcbPermission)); + values.push_back(pcbPermission); + } + } + } + return values; + } + + std::vector EnumAssemblyRefs(IMetaDataAssemblyImport* import) + { + std::vector tokens; + static_enum_buffer tokensBuffer{}; + HCORENUM hcorenum{}; + ULONG returned; + while (0 == import->EnumAssemblyRefs(&hcorenum, tokensBuffer.data(), (ULONG)tokensBuffer.size(), &returned) + && returned != 0) + { + for (ULONG i = 0; i < returned; ++i) + tokens.push_back(tokensBuffer[i]); + } + minipal::com_ptr mdImport; + HRESULT hr = import->QueryInterface(IID_IMetaDataImport2, (void**)&mdImport); + EXPECT_HRESULT_SUCCEEDED(hr); + ValidateAndCloseEnum(mdImport, hcorenum, (ULONG)tokens.size()); + return tokens; + } + + std::vector EnumFiles(IMetaDataAssemblyImport* import) + { + std::vector tokens; + static_enum_buffer tokensBuffer{}; + HCORENUM hcorenum{}; + ULONG returned; + while (0 == import->EnumFiles(&hcorenum, tokensBuffer.data(), (ULONG)tokensBuffer.size(), &returned) + && returned != 0) + { + for (ULONG i = 0; i < returned; ++i) + tokens.push_back(tokensBuffer[i]); + } + minipal::com_ptr mdImport; + HRESULT hr = import->QueryInterface(IID_IMetaDataImport2, (void**)&mdImport); + EXPECT_HRESULT_SUCCEEDED(hr); + ValidateAndCloseEnum(mdImport, hcorenum, (ULONG)tokens.size()); + return tokens; + } + + std::vector EnumExportedTypes(IMetaDataAssemblyImport* import) + { + std::vector tokens; + static_enum_buffer tokensBuffer{}; + HCORENUM hcorenum{}; + ULONG returned; + while (0 == import->EnumExportedTypes(&hcorenum, tokensBuffer.data(), (ULONG)tokensBuffer.size(), &returned) + && returned != 0) + { + for (ULONG i = 0; i < returned; ++i) + tokens.push_back(tokensBuffer[i]); + } + minipal::com_ptr mdImport; + HRESULT hr = import->QueryInterface(IID_IMetaDataImport2, (void**)&mdImport); + EXPECT_HRESULT_SUCCEEDED(hr); + ValidateAndCloseEnum(mdImport, hcorenum, (ULONG)tokens.size()); + return tokens; + } + + std::vector EnumManifestResources(IMetaDataAssemblyImport* import) + { + std::vector tokens; + static_enum_buffer tokensBuffer{}; + HCORENUM hcorenum{}; + ULONG returned; + while (0 == import->EnumManifestResources(&hcorenum, tokensBuffer.data(), (ULONG)tokensBuffer.size(), &returned) + && returned != 0) + { + for (ULONG i = 0; i < returned; ++i) + tokens.push_back(tokensBuffer[i]); + } + minipal::com_ptr mdImport; + HRESULT hr = import->QueryInterface(IID_IMetaDataImport2, (void**)&mdImport); + EXPECT_HRESULT_SUCCEEDED(hr); + ValidateAndCloseEnum(mdImport, hcorenum, (ULONG)tokens.size()); + return tokens; + } + + std::vector FindTypeRef(IMetaDataImport2* import) + { + std::vector values; + HRESULT hr; + mdToken tk; + + // The first assembly ref token typically contains System.Object and Enumerator. + mdToken const assemblyRefToken = 0x23000001; + hr = import->FindTypeRef(assemblyRefToken, W("System.Object"), &tk); + values.push_back(hr); + if (hr == S_OK) + values.push_back(tk); + + // Look for a type that won't ever exist + hr = import->FindTypeRef(assemblyRefToken, W("DoesntExist"), &tk); + values.push_back(hr); + if (hr == S_OK) + values.push_back(tk); + return values; + } + + std::vector FindTypeDefByName(IMetaDataImport2* import, LPCWSTR name, mdToken scope) + { + std::vector values; + + mdTypeDef ptd; + HRESULT hr = import->FindTypeDefByName(name, scope, &ptd); + + values.push_back(hr); + if (hr == S_OK) + values.push_back(ptd); + return values; + } + + std::vector FindExportedTypeByName(IMetaDataAssemblyImport* import, LPCWSTR name, mdToken tkImplementation) + { + std::vector values; + + mdExportedType exported; + HRESULT hr = import->FindExportedTypeByName(name, tkImplementation, &exported); + + values.push_back(hr); + if (hr == S_OK) + values.push_back(exported); + return values; + } + + std::vector FindManifestResourceByName(IMetaDataAssemblyImport* import, LPCWSTR name) + { + std::vector values; + + mdManifestResource resource; + HRESULT hr = import->FindManifestResourceByName(name, &resource); + + values.push_back(hr); + if (hr == S_OK) + values.push_back(resource); + return values; + } + + std::vector GetTypeDefProps(IMetaDataImport2* import, mdTypeDef typdef) + { + std::vector values; + + static_char_buffer name{}; + ULONG pchTypeDef; + DWORD pdwTypeDefFlags; + mdToken ptkExtends; + HRESULT hr = import->GetTypeDefProps(typdef, + name.data(), + (ULONG)name.size(), + &pchTypeDef, + &pdwTypeDefFlags, + &ptkExtends); + + values.push_back(hr); + if (hr == S_OK) + { + uint32_t hash = HashCharArray(name, pchTypeDef); + values.push_back(hash); + values.push_back(pchTypeDef); + values.push_back(pdwTypeDefFlags); + values.push_back(ptkExtends); + } + return values; + } + + std::vector GetTypeRefProps(IMetaDataImport2* import, mdTypeRef typeref) + { + std::vector values; + + static_char_buffer name{}; + mdToken tkResolutionScope; + ULONG pchTypeRef; + HRESULT hr = import->GetTypeRefProps(typeref, + &tkResolutionScope, + name.data(), + (ULONG)name.size(), + &pchTypeRef); + + values.push_back(hr); + if (hr == S_OK) + { + values.push_back(tkResolutionScope); + uint32_t hash = HashCharArray(name, pchTypeRef); + values.push_back(hash); + values.push_back(pchTypeRef); + } + return values; + } + + std::vector GetScopeProps(IMetaDataImport2* import) + { + std::vector values; + + static_char_buffer name{}; + ULONG pchName; + GUID mvid; + HRESULT hr = import->GetScopeProps( + name.data(), + (ULONG)name.size(), + &pchName, + &mvid); + + values.push_back(hr); + if (hr == S_OK) + { + uint32_t hash = HashCharArray(name, pchName); + values.push_back(hash); + values.push_back(pchName); + + std::array buffer{}; + memcpy(buffer.data(), &mvid, buffer.size()); + for (auto b : buffer) + values.push_back(b); + } + return values; + } + + std::vector GetModuleRefProps(IMetaDataImport2* import, mdModuleRef moduleref) + { + std::vector values; + + static_char_buffer name{}; + ULONG pchModuleRef; + HRESULT hr = import->GetModuleRefProps(moduleref, + name.data(), + (ULONG)name.size(), + &pchModuleRef); + + values.push_back(hr); + if (hr == S_OK) + { + uint32_t hash = HashCharArray(name, pchModuleRef); + values.push_back(hash); + values.push_back(pchModuleRef); + } + return values; + } + + std::vector GetMethodProps(IMetaDataImport2* import, mdToken tk, void const** sig = nullptr, ULONG* sigLen = nullptr) + { + std::vector values; + + mdTypeDef pClass; + static_char_buffer name{}; + ULONG pchMethod; + DWORD pdwAttr; + PCCOR_SIGNATURE ppvSigBlob; + ULONG pcbSigBlob; + ULONG pulCodeRVA; + DWORD pdwImplFlags; + HRESULT hr = import->GetMethodProps(tk, + &pClass, + name.data(), + (ULONG)name.size(), + &pchMethod, + &pdwAttr, + &ppvSigBlob, + &pcbSigBlob, + &pulCodeRVA, + &pdwImplFlags); + values.push_back(hr); + if (hr == S_OK) + { + values.push_back(pClass); + uint32_t hash = HashCharArray(name, pchMethod); + values.push_back(hash); + values.push_back(pchMethod); + values.push_back(pdwAttr); + values.push_back(HashByteArray(ppvSigBlob, pcbSigBlob)); + values.push_back(pcbSigBlob); + values.push_back(pulCodeRVA); + values.push_back(pdwImplFlags); + + if (sig != nullptr) + *sig = ppvSigBlob; + if (sigLen != nullptr) + *sigLen = pcbSigBlob; + } + return values; + } + + std::vector GetParamProps(IMetaDataImport2* import, mdToken tk) + { + std::vector values; + + mdMethodDef pmd; + ULONG pulSequence; + static_char_buffer name{}; + ULONG pchName; + DWORD pdwAttr; + DWORD pdwCPlusTypeFlag; + UVCP_CONSTANT ppValue; + ULONG pcchValue; + HRESULT hr = import->GetParamProps(tk, + &pmd, + &pulSequence, + name.data(), + (ULONG)name.size(), + &pchName, + &pdwAttr, + &pdwCPlusTypeFlag, + &ppValue, + &pcchValue); + + values.push_back(hr); + if (hr == S_OK) + { + values.push_back(pmd); + values.push_back(pulSequence); + uint32_t hash = HashCharArray(name, pchName); + values.push_back(hash); + values.push_back(pchName); + values.push_back(pdwAttr); + values.push_back(pdwCPlusTypeFlag); + values.push_back(HashByteArray(ppValue, pcchValue)); + values.push_back(pcchValue); + } + return values; + } + + std::vector GetMethodSpecProps(IMetaDataImport2* import, mdMethodSpec methodSpec) + { + std::vector values; + + mdToken parent; + PCCOR_SIGNATURE sig; + ULONG sigLen; + HRESULT hr = import->GetMethodSpecProps(methodSpec, + &parent, + &sig, + &sigLen); + + values.push_back(hr); + if (hr == S_OK) + { + values.push_back(parent); + values.push_back(HashByteArray(sig, sigLen)); + values.push_back(sigLen); + } + return values; + } + + std::vector GetMemberRefProps(IMetaDataImport2* import, mdMemberRef mr, PCCOR_SIGNATURE* sig = nullptr, ULONG* sigLen = nullptr) + { + std::vector values; + + mdToken ptk; + static_char_buffer name{}; + ULONG pchMember; + PCCOR_SIGNATURE ppvSigBlob; + ULONG pcbSigBlob; + HRESULT hr = import->GetMemberRefProps(mr, + &ptk, + name.data(), + (ULONG)name.size(), + &pchMember, + &ppvSigBlob, + &pcbSigBlob); + + values.push_back(hr); + if (hr == S_OK) + { + values.push_back(ptk); + uint32_t hash = HashCharArray(name, pchMember); + values.push_back(hash); + values.push_back(pchMember); + values.push_back(HashByteArray(ppvSigBlob, pcbSigBlob)); + values.push_back(pcbSigBlob); + + if (sig != nullptr) + *sig = ppvSigBlob; + if (sigLen != nullptr) + *sigLen = pcbSigBlob; + } + return values; + } + + std::vector GetEventProps(IMetaDataImport2* import, mdEvent tk, std::vector* methoddefs = nullptr) + { + std::vector values; + + mdTypeDef pClass; + static_char_buffer name{}; + ULONG pchEvent; + DWORD pdwEventFlags; + mdToken ptkEventType; + mdMethodDef pmdAddOn; + mdMethodDef pmdRemoveOn; + mdMethodDef pmdFire; + static_enum_buffer rmdOtherMethod; + ULONG pcOtherMethod; + HRESULT hr = import->GetEventProps(tk, + &pClass, + name.data(), + (ULONG)name.size(), + &pchEvent, + &pdwEventFlags, + &ptkEventType, + &pmdAddOn, + &pmdRemoveOn, + &pmdFire, + rmdOtherMethod.data(), + (ULONG)rmdOtherMethod.size(), + &pcOtherMethod); + values.push_back(hr); + if (hr == S_OK) + { + values.push_back(pClass); + uint32_t hash = HashCharArray(name, pchEvent); + values.push_back(hash); + values.push_back(pchEvent); + values.push_back(pdwEventFlags); + values.push_back(ptkEventType); + values.push_back(pmdAddOn); + values.push_back(pmdRemoveOn); + values.push_back(pmdFire); + + std::vector retMaybe; + for (ULONG i = 0; i < std::min(pcOtherMethod, (ULONG)rmdOtherMethod.size()); ++i) + { + values.push_back(rmdOtherMethod[i]); + retMaybe.push_back(rmdOtherMethod[i]); + } + + retMaybe.push_back(pmdAddOn); + retMaybe.push_back(pmdRemoveOn); + retMaybe.push_back(pmdFire); + + if (methoddefs != nullptr) + *methoddefs = std::move(retMaybe); + } + return values; + } + + std::vector GetPropertyProps(IMetaDataImport2* import, mdProperty tk, std::vector* methoddefs = nullptr) + { + std::vector values; + + mdTypeDef pClass; + static_char_buffer name{}; + ULONG pchProperty; + DWORD pdwPropFlags; + PCCOR_SIGNATURE sig; + ULONG sigLen; + DWORD pdwCPlusTypeFlag; + UVCP_CONSTANT ppDefaultValue; + ULONG pcchDefaultValue; + mdMethodDef pmdSetter; + mdMethodDef pmdGetter; + static_enum_buffer rmdOtherMethod{}; + ULONG pcOtherMethod; + HRESULT hr = import->GetPropertyProps(tk, + &pClass, + name.data(), + (ULONG)name.size(), + &pchProperty, + &pdwPropFlags, + &sig, + &sigLen, + &pdwCPlusTypeFlag, + &ppDefaultValue, + &pcchDefaultValue, + &pmdSetter, + &pmdGetter, + rmdOtherMethod.data(), + (ULONG)rmdOtherMethod.size(), + &pcOtherMethod); + values.push_back(hr); + if (hr == S_OK) + { + values.push_back(pClass); + uint32_t hash = HashCharArray(name, pchProperty); + values.push_back(hash); + values.push_back(pchProperty); + values.push_back(pdwPropFlags); + values.push_back(HashByteArray(sig, sigLen)); + values.push_back(sigLen); + values.push_back(pdwCPlusTypeFlag); + values.push_back(HashByteArray(ppDefaultValue, pcchDefaultValue)); + values.push_back(pcchDefaultValue); + values.push_back(pmdSetter); + values.push_back(pmdGetter); + + std::vector retMaybe; + for (ULONG i = 0; i < std::min(pcOtherMethod, (ULONG)rmdOtherMethod.size()); ++i) + { + values.push_back(rmdOtherMethod[i]); + retMaybe.push_back(rmdOtherMethod[i]); + } + + retMaybe.push_back(pmdSetter); + retMaybe.push_back(pmdGetter); + + if (methoddefs != nullptr) + *methoddefs = std::move(retMaybe); + } + return values; + } + + std::vector GetFieldProps(IMetaDataImport2* import, mdFieldDef tk, void const** sig = nullptr, ULONG* sigLen = nullptr) + { + std::vector values; + + mdTypeDef pClass; + static_char_buffer name{}; + ULONG pchField; + DWORD pdwAttr; + PCCOR_SIGNATURE ppvSigBlob; + ULONG pcbSigBlob; + DWORD pdwCPlusTypeFlag; + UVCP_CONSTANT ppValue = nullptr; + ULONG pcchValue = 0; + HRESULT hr = import->GetFieldProps(tk, + &pClass, + name.data(), + (ULONG)name.size(), + &pchField, + &pdwAttr, + &ppvSigBlob, + &pcbSigBlob, + &pdwCPlusTypeFlag, + &ppValue, + &pcchValue); + + values.push_back(hr); + if (hr == S_OK) + { + values.push_back(pClass); + uint32_t hash = HashCharArray(name, pchField); + values.push_back(hash); + values.push_back(pchField); + values.push_back(pdwAttr); + values.push_back(HashByteArray(ppvSigBlob, pcbSigBlob)); + values.push_back(pcbSigBlob); + values.push_back(pdwCPlusTypeFlag); + values.push_back(HashByteArray(ppValue, pcchValue)); + values.push_back(pcchValue); + + if (sig != nullptr) + *sig = ppvSigBlob; + if (sigLen != nullptr) + *sigLen = pcbSigBlob; + } + return values; + } + + std::vector GetCustomAttributeProps(IMetaDataImport2* import, mdCustomAttribute cv) + { + std::vector values; + + mdToken ptkObj; + mdToken ptkType; + void const* sig; + ULONG sigLen; + HRESULT hr = import->GetCustomAttributeProps(cv, + &ptkObj, + &ptkType, + &sig, + &sigLen); + + values.push_back(hr); + if (hr == S_OK) + { + values.push_back(ptkObj); + values.push_back(ptkType); + values.push_back(HashByteArray(sig, sigLen)); + values.push_back(sigLen); + } + return values; + } + + std::vector GetGenericParamProps(IMetaDataImport2* import, mdGenericParam gp) + { + std::vector values; + + ULONG pulParamSeq; + DWORD pdwParamFlags; + mdToken ptOwner; + DWORD reserved; + static_char_buffer name{}; + ULONG pchName; + HRESULT hr = import->GetGenericParamProps(gp, + &pulParamSeq, + &pdwParamFlags, + &ptOwner, + &reserved, + name.data(), + (ULONG)name.size(), + &pchName); + + values.push_back(hr); + if (hr == S_OK) + { + values.push_back(pulParamSeq); + values.push_back(pdwParamFlags); + values.push_back(ptOwner); + // We don't care about reserved + // as its value is unspecified + uint32_t hash = HashCharArray(name, pchName); + values.push_back(hash); + values.push_back(pchName); + } + return values; + } + + std::vector GetGenericParamConstraintProps(IMetaDataImport2* import, mdGenericParamConstraint tk) + { + std::vector values; + + mdGenericParam ptGenericParam; + mdToken ptkConstraintType; + HRESULT hr = import->GetGenericParamConstraintProps(tk, + &ptGenericParam, + &ptkConstraintType); + + values.push_back(hr); + if (hr == S_OK) + { + values.push_back(ptGenericParam); + values.push_back(ptkConstraintType); + } + return values; + } + + std::vector GetPinvokeMap(IMetaDataImport2* import, mdToken tk) + { + std::vector values; + + DWORD pdwMappingFlags; + static_char_buffer name{}; + ULONG pchImportName; + mdModuleRef pmrImportDLL; + HRESULT hr = import->GetPinvokeMap(tk, + &pdwMappingFlags, + name.data(), + (ULONG)name.size(), + &pchImportName, + &pmrImportDLL); + + values.push_back(hr); + if (hr == S_OK) + { + values.push_back(pdwMappingFlags); + uint32_t hash = HashCharArray(name, pchImportName); + values.push_back(hash); + values.push_back(pchImportName); + values.push_back(pmrImportDLL); + } + return values; + } + + std::vector GetNativeCallConvFromSig(IMetaDataImport2* import, void const* sig, ULONG sigLen) + { + std::vector values; + + // .NET 2,4 and CoreCLR metadata imports do not handle null signatures. + if (sigLen != 0) + { + ULONG pCallConv; + HRESULT hr = import->GetNativeCallConvFromSig(sig, sigLen, &pCallConv); + + values.push_back(hr); + if (hr == S_OK) + values.push_back(pCallConv); + } + + return values; + } + + std::vector GetTypeSpecFromToken(IMetaDataImport2* import, mdTypeSpec typespec) + { + std::vector values; + + PCCOR_SIGNATURE sig; + ULONG sigLen; + HRESULT hr = import->GetTypeSpecFromToken(typespec, &sig, &sigLen); + values.push_back(hr); + if (hr == S_OK) + { + values.push_back(HashByteArray(sig, sigLen)); + values.push_back(sigLen); + } + return values; + } + + std::vector GetSigFromToken(IMetaDataImport2* import, mdSignature tkSig) + { + std::vector values; + + PCCOR_SIGNATURE sig; + ULONG sigLen; + HRESULT hr = import->GetSigFromToken(tkSig, &sig, &sigLen); + values.push_back(hr); + if (hr == S_OK) + { + values.push_back(HashByteArray(sig, sigLen)); + values.push_back(sigLen); + } + return values; + } + + std::vector GetMethodSemantics(IMetaDataImport2* import, mdToken tkEventProp, mdMethodDef methodDef) + { + std::vector values; + + DWORD pdwSemanticsFlags; + HRESULT hr = import->GetMethodSemantics(methodDef, tkEventProp, &pdwSemanticsFlags); + + values.push_back(hr); + if (hr == S_OK) + values.push_back(pdwSemanticsFlags); + + return values; + } + + std::vector GetUserString(IMetaDataImport2* import, mdString tkStr) + { + std::vector values; + + static_char_buffer name{}; + ULONG pchString; + HRESULT hr = import->GetUserString(tkStr, name.data(), (ULONG)name.size(), &pchString); + values.push_back(hr); + if (hr == S_OK) + { + uint32_t hash = HashCharArray(name, pchString); + values.push_back(hash); + values.push_back(pchString); + } + return values; + } + + std::vector GetNameFromToken(IMetaDataImport2* import, mdToken tkObj) + { + std::vector values; + + MDUTF8CSTR pszUtf8NamePtr; + HRESULT hr = import->GetNameFromToken(tkObj, &pszUtf8NamePtr); + values.push_back(hr); + if (hr == S_OK) + { + values.push_back(HashByteArray(pszUtf8NamePtr, ::strlen(pszUtf8NamePtr) + 1)); + } + return values; + } + + std::vector GetFieldMarshal(IMetaDataImport2* import, mdToken tk) + { + std::vector values; + + PCCOR_SIGNATURE sig; + ULONG sigLen; + HRESULT hr = import->GetFieldMarshal(tk, &sig, &sigLen); + values.push_back(hr); + if (hr == S_OK) + { + values.push_back(HashByteArray(sig, sigLen)); + values.push_back(sigLen); + } + return values; + } + + std::vector GetNestedClassProps(IMetaDataImport2* import, mdTypeDef tk) + { + std::vector values; + + mdTypeDef ptdEnclosingClass; + HRESULT hr = import->GetNestedClassProps(tk, &ptdEnclosingClass); + values.push_back(hr); + if (hr == S_OK) + values.push_back(ptdEnclosingClass); + + return values; + } + + std::vector GetClassLayout(IMetaDataImport2* import, mdTypeDef tk) + { + std::vector values; + + DWORD pdwPackSize; + std::vector offsets(24); + ULONG pcFieldOffset; + ULONG pulClassSize; + HRESULT hr = import->GetClassLayout(tk, + &pdwPackSize, + offsets.data(), + (ULONG)offsets.size(), + &pcFieldOffset, + &pulClassSize); + values.push_back(hr); + if (hr == S_OK) + { + values.push_back(pdwPackSize); + for (ULONG i = 0; i < std::min(pcFieldOffset, (ULONG)offsets.size()); ++i) + { + COR_FIELD_OFFSET const& o = offsets[i]; + values.push_back(o.ridOfField); + values.push_back(o.ulOffset); + } + values.push_back(pcFieldOffset); + values.push_back(pulClassSize); + } + + return values; + } + + std::vector GetRVA(IMetaDataImport2* import, mdToken tk) + { + std::vector values; + + ULONG pulCodeRVA; + DWORD pdwImplFlags; + HRESULT hr = import->GetRVA(tk, &pulCodeRVA, &pdwImplFlags); + values.push_back(hr); + if (hr == S_OK) + { + values.push_back(pulCodeRVA); + values.push_back(pdwImplFlags); + } + return values; + } + + std::vector GetParamForMethodIndex(IMetaDataImport2* import, mdToken tk) + { + std::vector values; + + mdParamDef def; + for (uint32_t i = 0; i < std::numeric_limits::max(); ++i) + { + HRESULT hr = import->GetParamForMethodIndex(tk, i, &def); + values.push_back(hr); + if (hr != S_OK) + break; + values.push_back(def); + } + return values; + } + + int32_t IsGlobal(IMetaDataImport2* import, mdToken tk) + { + int32_t pbGlobal; + HRESULT hr = import->IsGlobal(tk, &pbGlobal); + if (hr != S_OK) + return hr; + return pbGlobal; + } + + std::vector GetVersionString(IMetaDataImport2* import) + { + std::vector values; + + static_char_buffer name{}; + ULONG pccBufSize; + HRESULT hr = import->GetVersionString(name.data(), (DWORD)name.size(), &pccBufSize); + values.push_back(hr); + if (hr == S_OK) + { + uint32_t hash = HashCharArray(name, pccBufSize); + values.push_back(hash); + values.push_back(pccBufSize); + } + + return values; + } + + std::vector GetAssemblyFromScope(IMetaDataAssemblyImport* import) + { + std::vector values; + + mdAssembly mdAsm; + HRESULT hr = import->GetAssemblyFromScope(&mdAsm); + if (hr == S_OK) + values.push_back(mdAsm); + return values; + } + + std::vector GetAssemblyProps(IMetaDataAssemblyImport* import, mdAssembly mda) + { + std::vector values; + static_char_buffer name{}; + static_char_buffer locale{}; + std::vector processor(1); + std::vector osInfo(1); + + ASSEMBLYMETADATA metadata; + metadata.szLocale = locale.data(); + metadata.cbLocale = (ULONG)locale.size(); + metadata.rProcessor = processor.data(); + metadata.ulProcessor = (ULONG)processor.size(); + metadata.rOS = osInfo.data(); + metadata.ulOS = (ULONG)osInfo.size(); + + void const* publicKey; + ULONG publicKeyLength; + ULONG hashAlgId; + ULONG nameLength; + ULONG flags; + HRESULT hr = import->GetAssemblyProps(mda, &publicKey, &publicKeyLength, &hashAlgId, name.data(), (ULONG)name.size(), &nameLength, &metadata, &flags); + values.push_back(hr); + + if (hr == S_OK) + { + values.push_back(HashByteArray(publicKey, publicKeyLength)); + values.push_back(publicKeyLength); + values.push_back(hashAlgId); + values.push_back(HashCharArray(name, nameLength)); + values.push_back((size_t)nameLength); + values.push_back(metadata.usMajorVersion); + values.push_back(metadata.usMinorVersion); + values.push_back(metadata.usBuildNumber); + values.push_back(metadata.usRevisionNumber); + values.push_back(HashCharArray(locale, metadata.cbLocale)); + values.push_back(metadata.cbLocale); + values.push_back(metadata.ulProcessor); + values.push_back(metadata.ulOS); + values.push_back(flags); + } + return values; + } + + std::vector GetAssemblyRefProps(IMetaDataAssemblyImport* import, mdAssemblyRef mdar) + { + std::vector values; + static_char_buffer name{}; + static_char_buffer locale{}; + std::vector processor(1); + std::vector osInfo(1); + + ASSEMBLYMETADATA metadata; + metadata.szLocale = locale.data(); + metadata.cbLocale = (ULONG)locale.size(); + metadata.rProcessor = processor.data(); + metadata.ulProcessor = (ULONG)processor.size(); + metadata.rOS = osInfo.data(); + metadata.ulOS = (ULONG)osInfo.size(); + + void const* publicKeyOrToken; + ULONG publicKeyOrTokenLength; + ULONG nameLength; + void const* hash; + ULONG hashLength; + DWORD flags; + HRESULT hr = import->GetAssemblyRefProps(mdar, &publicKeyOrToken, &publicKeyOrTokenLength, name.data(), (ULONG)name.size(), &nameLength, &metadata, &hash, &hashLength, &flags); + values.push_back(hr); + + if (hr == S_OK) + { + values.push_back(HashByteArray(publicKeyOrToken, publicKeyOrTokenLength)); + values.push_back(publicKeyOrTokenLength); + values.push_back(HashCharArray(name, nameLength)); + values.push_back((size_t)nameLength); + values.push_back(metadata.usMajorVersion); + values.push_back(metadata.usMinorVersion); + values.push_back(metadata.usBuildNumber); + values.push_back(metadata.usRevisionNumber); + values.push_back(HashCharArray(locale, metadata.cbLocale)); + values.push_back(metadata.cbLocale); + values.push_back(metadata.ulProcessor); + values.push_back(metadata.ulOS); + values.push_back(HashByteArray(hash, hashLength)); + values.push_back(hashLength); + values.push_back(flags); + } + return values; + } + + std::vector GetFileProps(IMetaDataAssemblyImport* import, mdFile mdf) + { + std::vector values; + static_char_buffer name{}; + + ULONG nameLength; + void const* hash; + ULONG hashLength; + DWORD flags; + HRESULT hr = import->GetFileProps(mdf, name.data(), (ULONG)name.size(), &nameLength, &hash, &hashLength, &flags); + values.push_back(hr); + + if (hr == S_OK) + { + values.push_back(HashCharArray(name, nameLength)); + values.push_back((size_t)nameLength); + values.push_back(HashByteArray(hash, hashLength)); + values.push_back(hashLength); + values.push_back(flags); + } + return values; + } + + std::vector GetExportedTypeProps(IMetaDataAssemblyImport* import, mdFile mdf, std::vector* nameBuffer = nullptr, uint32_t* implementationToken = nullptr) + { + std::vector values; + static_char_buffer name{}; + + ULONG nameLength; + mdToken implementation; + mdTypeDef typeDef; + DWORD flags; + HRESULT hr = import->GetExportedTypeProps(mdf, name.data(), (ULONG)name.size(), &nameLength, &implementation, &typeDef, &flags); + values.push_back(hr); + + if (hr == S_OK) + { + values.push_back(HashCharArray(name, nameLength)); + values.push_back(nameLength); + values.push_back(implementation); + values.push_back(typeDef); + values.push_back(flags); + + if (nameBuffer != nullptr) + *nameBuffer = { std::begin(name), std::begin(name) + nameLength }; + if (implementationToken != nullptr) + *implementationToken = implementation; + } + return values; + } + + std::vector GetManifestResourceProps(IMetaDataAssemblyImport* import, mdManifestResource mmr, std::vector* nameBuffer = nullptr) + { + std::vector values; + static_char_buffer name{}; + + ULONG nameLength; + ULONG offset; + mdToken implementation; + DWORD flags; + HRESULT hr = import->GetManifestResourceProps(mmr, name.data(), (ULONG)name.size(), &nameLength, &implementation, &offset, &flags); + values.push_back(hr); + + if (hr == S_OK) + { + values.push_back(HashCharArray(name, nameLength)); + values.push_back(nameLength); + values.push_back(implementation); + values.push_back(flags); + + if (nameBuffer != nullptr) + *nameBuffer = { std::begin(name), std::begin(name) + nameLength }; + } + return values; + } + + std::vector ResetEnum(IMetaDataImport2* import) + { + // We are going to test the ResetEnum() API using the + // EnumMembers() API because it enumerates more than one table. + std::vector tokens; + auto typedefs = EnumTypeDefs(import); + if (typedefs.size() == 0) + return tokens; + + auto tk = typedefs[0]; + HCORENUM hcorenum{}; + try + { + static auto ReadInMembers = [](IMetaDataImport2* import, HCORENUM& hcorenum, mdToken tk, std::vector& tokens) + { + static_enum_buffer tokensBuffer{}; + ULONG returned; + if (0 == import->EnumMembers(&hcorenum, tk, tokensBuffer.data(), (ULONG)tokensBuffer.size(), &returned) + && returned != 0) + { + for (ULONG i = 0; i < returned; ++i) + tokens.push_back(tokensBuffer[i]); + } + }; + + ReadInMembers(import, hcorenum, tk, tokens); + + // Determine how many we have and move to right before end + ULONG count; + EXPECT_HRESULT_SUCCEEDED(import->CountEnum(hcorenum, &count)); + if (count != 0) + { + EXPECT_HRESULT_SUCCEEDED(import->ResetEnum(hcorenum, count - 1)); + ReadInMembers(import, hcorenum, tk, tokens); + + // Fully reset the enum + EXPECT_HRESULT_SUCCEEDED(import->ResetEnum(hcorenum, 0)); + ReadInMembers(import, hcorenum, tk, tokens); + } + } + catch (...) + { + import->CloseEnum(hcorenum); + throw; + } + return tokens; + } +} + +TEST(FindTest, FindAPIs) +{ + malloc_span metadata = GetRegressionAssemblyMetadata(); + + minipal::com_ptr baselineImport; + ASSERT_HRESULT_SUCCEEDED(CreateImport(TestBaseline::Metadata, metadata.data(), (uint32_t)metadata.size(), &baselineImport)); + // Load metadata + minipal::com_ptr currentImport; + + minipal::com_ptr dispenser; + ASSERT_HRESULT_SUCCEEDED(GetDispenser(IID_IMetaDataDispenser, (void**)&dispenser)); + + ASSERT_HRESULT_SUCCEEDED(CreateImport(dispenser, metadata.data(), (uint32_t)metadata.size(), ¤tImport)); + + static auto FindTokenByName = [](IMetaDataImport2* import, LPCWSTR name, mdToken enclosing = mdTokenNil) -> mdToken + { + mdTypeDef ptd; + EXPECT_HRESULT_SUCCEEDED(import->FindTypeDefByName(name, enclosing, &ptd)); + return ptd; + }; + + static auto GetTypeDefBaseToken = [](IMetaDataImport2* import, mdTypeDef tk) -> mdToken + { + static_char_buffer name{}; + ULONG pchTypeDef; + DWORD pdwTypeDefFlags; + mdToken ptkExtends; + EXPECT_HRESULT_SUCCEEDED(import->GetTypeDefProps(tk, + name.data(), + (ULONG)name.size(), + &pchTypeDef, + &pdwTypeDefFlags, + &ptkExtends)); + return ptkExtends; + }; + + static auto FindMethodDef = [](IMetaDataImport2* import, mdTypeDef type, LPCWSTR methodName) -> mdToken + { + std::vector methoddefs = EnumMembersWithName(import, type, methodName); + EXPECT_TRUE(!methoddefs.empty()); + return methoddefs[0]; + }; + + static auto FindMemberRef = [](IMetaDataImport2* import, mdTypeDef type, LPCWSTR methodName) -> mdToken + { + auto methodDef = FindMethodDef(import, type, methodName); + mdMemberRef pmr; + EXPECT_HRESULT_SUCCEEDED(import->FindMemberRef(methodDef, methodName, nullptr, 0, &pmr)); + return pmr; + }; + + static auto FindMethod = [](IMetaDataImport2* import, mdTypeDef td, LPCWSTR name, void const* pvSigBlob, ULONG cbSigBlob) -> uint32_t + { + mdMethodDef tkMethod; + mdToken tkMember; + EXPECT_HRESULT_SUCCEEDED(import->FindMethod(td, name, (PCCOR_SIGNATURE)pvSigBlob, cbSigBlob, &tkMethod)); + EXPECT_HRESULT_SUCCEEDED(import->FindMember(td, name, (PCCOR_SIGNATURE)pvSigBlob, cbSigBlob, &tkMember)); + EXPECT_EQ(tkMethod, tkMember); + return tkMethod; + }; + + static auto FindField = [](IMetaDataImport2* import, mdTypeDef td, LPCWSTR name, void const* pvSigBlob, ULONG cbSigBlob) -> uint32_t + { + mdFieldDef tkField; + mdToken tkMember; + EXPECT_HRESULT_SUCCEEDED(import->FindField(td, name, (PCCOR_SIGNATURE)pvSigBlob, cbSigBlob, &tkField)); + EXPECT_HRESULT_SUCCEEDED(import->FindMember(td, name, (PCCOR_SIGNATURE)pvSigBlob, cbSigBlob, &tkMember)); + EXPECT_EQ(tkField, tkMember); + return tkField; + }; + + auto tgt = W("C"); + + auto baseTypeDef = W("B1"); + auto tkB1 = EXPECT_THAT_AND_RETURN(FindTokenByName(baselineImport, baseTypeDef), testing::Eq(FindTokenByName(currentImport, baseTypeDef))); + auto tkB1Base = EXPECT_THAT_AND_RETURN(GetTypeDefBaseToken(baselineImport, tkB1), testing::Eq(GetTypeDefBaseToken(currentImport, tkB1))); + ASSERT_EQ(FindTypeDefByName(baselineImport, tgt, tkB1Base), FindTypeDefByName(currentImport, tgt, tkB1Base)); + + auto baseTypeRef = W("B2"); + auto tkB2 = EXPECT_THAT_AND_RETURN(FindTokenByName(baselineImport, baseTypeRef), testing::Eq(FindTokenByName(currentImport, baseTypeRef))); + auto tkB2Base = EXPECT_THAT_AND_RETURN(GetTypeDefBaseToken(baselineImport, tkB2), testing::Eq(GetTypeDefBaseToken(currentImport, tkB2))); + ASSERT_THAT(FindTypeDefByName(baselineImport, tgt, tkB2Base), testing::Eq(FindTypeDefByName(currentImport, tgt, tkB2Base))); + + auto methodDefName = W("MethodDef"); + auto tkMethodDef = EXPECT_THAT_AND_RETURN(FindMethodDef(baselineImport, tkB1Base, methodDefName), testing::Eq(FindMethodDef(currentImport, tkB1Base, methodDefName))); + + void const* defSigBlob; + ULONG defSigBlobLength; + ASSERT_EQ( + GetMethodProps(baselineImport, tkMethodDef), + GetMethodProps(currentImport, tkMethodDef, &defSigBlob, &defSigBlobLength)); + ASSERT_EQ( + FindMethod(baselineImport, tkB1Base, methodDefName, defSigBlob, defSigBlobLength), + FindMethod(currentImport, tkB1Base, methodDefName, defSigBlob, defSigBlobLength)); + + auto methodRef1Name = W("MethodRef1"); + auto tkMemberRefNoVarArgsBase = EXPECT_THAT_AND_RETURN( + FindMemberRef(baselineImport, tkB1Base, methodRef1Name), + testing::Eq(FindMemberRef(currentImport, tkB1Base, methodRef1Name))); + + PCCOR_SIGNATURE ref1Blob; + ULONG ref1BlobLength; + ASSERT_EQ( + GetMemberRefProps(baselineImport, tkMemberRefNoVarArgsBase), + GetMemberRefProps(currentImport, tkMemberRefNoVarArgsBase, &ref1Blob, &ref1BlobLength)); + ASSERT_EQ( + FindMethod(baselineImport, tkB1Base, methodRef1Name, ref1Blob, ref1BlobLength), + FindMethod(currentImport, tkB1Base, methodRef1Name, ref1Blob, ref1BlobLength)); + + auto methodRef2Name = W("MethodRef2"); + auto tkMemberRefVarArgsBase = EXPECT_THAT_AND_RETURN( + FindMemberRef(baselineImport, tkB1Base, methodRef2Name), + testing::Eq(FindMemberRef(currentImport, tkB1Base, methodRef2Name))); + + PCCOR_SIGNATURE ref2Blob; + ULONG ref2BlobLength; + ASSERT_EQ( + GetMemberRefProps(baselineImport, tkMemberRefVarArgsBase), + GetMemberRefProps(currentImport, tkMemberRefVarArgsBase, &ref2Blob, &ref2BlobLength)); + EXPECT_EQ( + FindMethod(baselineImport, tkB1Base, methodRef2Name, ref2Blob, ref2BlobLength), + FindMethod(currentImport, tkB1Base, methodRef2Name, ref2Blob, ref2BlobLength)); + + auto fieldName = W("Field1"); + auto tkFields = EXPECT_THAT_AND_RETURN( + EnumFieldsWithName(baselineImport, tkB2, fieldName), + testing::ElementsAreArray(EnumFieldsWithName(currentImport, tkB2, fieldName))); + ASSERT_FALSE(tkFields.empty()); + mdToken tkField = tkFields[0]; + + void const* sigBlob; + ULONG sigBlobLength; + ASSERT_EQ( + GetFieldProps(baselineImport, tkField), + GetFieldProps(currentImport, tkField, &sigBlob, &sigBlobLength)); + EXPECT_EQ( + FindField(baselineImport, tkB2, fieldName, sigBlob, sigBlobLength), + FindField(currentImport, tkB2, fieldName, sigBlob, sigBlobLength)); +} + +class MetadataImportTest : public RegressionTest +{ +}; + +TEST_P(MetadataImportTest, ImportAPIs) +{ + auto param = GetParam(); + span blob = GetMetadataForFile(param); + void const* data = blob.data(); + uint32_t dataLen = (uint32_t)blob.size(); + + // Load metadata + minipal::com_ptr baselineImport; + ASSERT_HRESULT_SUCCEEDED(CreateImport(TestBaseline::Metadata, data, dataLen, &baselineImport)); + + minipal::com_ptr dispenser; + ASSERT_HRESULT_SUCCEEDED(GetDispenser(IID_IMetaDataDispenser, (void**)&dispenser)); + minipal::com_ptr currentImport; + ASSERT_HRESULT_SUCCEEDED(CreateImport(dispenser, data, dataLen, ¤tImport)); + + // Verify APIs + ASSERT_THAT(ResetEnum(currentImport), testing::ElementsAreArray(ResetEnum(baselineImport))); + ASSERT_THAT(GetScopeProps(currentImport), testing::ElementsAreArray(GetScopeProps(baselineImport))); + ASSERT_THAT(GetVersionString(currentImport), testing::ElementsAreArray(GetVersionString(baselineImport))); + + TokenList sigs; + ASSERT_EQUAL_AND_SET(sigs, EnumSignatures(baselineImport), EnumSignatures(currentImport)); + for (auto sig : sigs) + { + ASSERT_THAT(GetSigFromToken(currentImport, sig), testing::ElementsAreArray(GetSigFromToken(baselineImport, sig))); + } + + TokenList userStrings; + ASSERT_EQUAL_AND_SET(userStrings, EnumUserStrings(baselineImport), EnumUserStrings(currentImport)); + for (auto us : userStrings) + { + ASSERT_THAT(GetUserString(currentImport, us), testing::ElementsAreArray(GetUserString(baselineImport, us))); + } + + TokenList custAttrs; + ASSERT_EQUAL_AND_SET(custAttrs, EnumCustomAttributes(baselineImport), EnumCustomAttributes(currentImport)); + for (auto ca : custAttrs) + { + ASSERT_THAT(GetCustomAttributeProps(currentImport, ca), testing::ElementsAreArray(GetCustomAttributeProps(baselineImport, ca))); + } + + TokenList modulerefs; + ASSERT_EQUAL_AND_SET(modulerefs, EnumModuleRefs(baselineImport), EnumModuleRefs(currentImport)); + for (auto moduleref : modulerefs) + { + ASSERT_THAT(GetModuleRefProps(currentImport, moduleref), testing::ElementsAreArray(GetModuleRefProps(baselineImport, moduleref))); + ASSERT_THAT(GetNameFromToken(currentImport, moduleref), testing::ElementsAreArray(GetNameFromToken(baselineImport, moduleref))); + } + + ASSERT_THAT(FindTypeRef(currentImport), testing::ElementsAreArray(FindTypeRef(baselineImport))); + TokenList typerefs; + ASSERT_EQUAL_AND_SET(typerefs, EnumTypeRefs(baselineImport), EnumTypeRefs(currentImport)); + for (auto typeref : typerefs) + { + ASSERT_THAT(GetTypeRefProps(currentImport, typeref), testing::ElementsAreArray(GetTypeRefProps(baselineImport, typeref))); + ASSERT_THAT(GetCustomAttribute_CompilerGenerated(currentImport, typeref), testing::ElementsAreArray(GetCustomAttribute_CompilerGenerated(baselineImport, typeref))); + ASSERT_THAT(GetNameFromToken(currentImport, typeref), testing::ElementsAreArray(GetNameFromToken(baselineImport, typeref))); + } + + TokenList typespecs; + ASSERT_EQUAL_AND_SET(typespecs, EnumTypeSpecs(baselineImport), EnumTypeSpecs(currentImport)); + for (auto typespec : typespecs) + { + ASSERT_THAT(GetTypeSpecFromToken(currentImport, typespec), testing::ElementsAreArray(GetTypeSpecFromToken(baselineImport, typespec))); + ASSERT_THAT(GetCustomAttribute_CompilerGenerated(currentImport, typespec), testing::ElementsAreArray(GetCustomAttribute_CompilerGenerated(baselineImport, typespec))); + } + + TokenList typedefs; + ASSERT_EQUAL_AND_SET(typedefs, EnumTypeDefs(baselineImport), EnumTypeDefs(currentImport)); + for (auto typdef : typedefs) + { + ASSERT_THAT(GetTypeDefProps(currentImport, typdef), testing::ElementsAreArray(GetTypeDefProps(baselineImport, typdef))); + ASSERT_THAT(GetNameFromToken(currentImport, typdef), testing::ElementsAreArray(GetNameFromToken(baselineImport, typdef))); + ASSERT_THAT(IsGlobal(currentImport, typdef), testing::Eq(IsGlobal(baselineImport, typdef))); + ASSERT_THAT(EnumInterfaceImpls(currentImport, typdef), testing::ElementsAreArray(EnumInterfaceImpls(baselineImport, typdef))); + ASSERT_THAT(EnumPermissionSetsAndGetProps(currentImport, typdef), testing::ElementsAreArray(EnumPermissionSetsAndGetProps(baselineImport, typdef))); + ASSERT_THAT(EnumMembers(currentImport, typdef), testing::ElementsAreArray(EnumMembers(baselineImport, typdef))); + ASSERT_THAT(EnumMembersWithName(currentImport, typdef), testing::ElementsAreArray(EnumMembersWithName(baselineImport, typdef))); + ASSERT_THAT(EnumMethodsWithName(currentImport, typdef), testing::ElementsAreArray(EnumMethodsWithName(baselineImport, typdef))); + ASSERT_THAT(EnumMethodImpls(currentImport, typdef), testing::ElementsAreArray(EnumMethodImpls(baselineImport, typdef))); + ASSERT_THAT(GetNestedClassProps(currentImport, typdef), testing::ElementsAreArray(GetNestedClassProps(baselineImport, typdef))); + ASSERT_THAT(GetClassLayout(currentImport, typdef), testing::ElementsAreArray(GetClassLayout(baselineImport, typdef))); + ASSERT_THAT(GetCustomAttribute_CompilerGenerated(currentImport, typdef), testing::ElementsAreArray(GetCustomAttribute_CompilerGenerated(baselineImport, typdef))); + + TokenList methoddefs; + ASSERT_EQUAL_AND_SET(methoddefs, EnumMethods(baselineImport, typdef), EnumMethods(currentImport, typdef)); + for (auto methoddef : methoddefs) + { + void const* sig = nullptr; + ULONG sigLen = 0; + ASSERT_THAT(GetMethodProps(currentImport, methoddef, &sig, &sigLen), testing::ElementsAreArray(GetMethodProps(baselineImport, methoddef))); + ASSERT_THAT(GetNativeCallConvFromSig(currentImport, sig, sigLen), testing::ElementsAreArray(GetNativeCallConvFromSig(baselineImport, sig, sigLen))); + ASSERT_THAT(GetNameFromToken(currentImport, methoddef), testing::ElementsAreArray(GetNameFromToken(baselineImport, methoddef))); + ASSERT_THAT(IsGlobal(currentImport, methoddef), testing::Eq(IsGlobal(baselineImport, methoddef))); + ASSERT_THAT(GetCustomAttribute_CompilerGenerated(currentImport, methoddef), testing::ElementsAreArray(GetCustomAttribute_CompilerGenerated(baselineImport, methoddef))); + + TokenList paramdefs; + ASSERT_EQUAL_AND_SET(paramdefs, EnumParams(baselineImport, methoddef), EnumParams(currentImport, methoddef)); + for (auto paramdef : paramdefs) + { + ASSERT_THAT(GetParamProps(currentImport, paramdef), testing::ElementsAreArray(GetParamProps(baselineImport, paramdef))); + ASSERT_THAT(GetFieldMarshal(currentImport, paramdef), testing::ElementsAreArray(GetFieldMarshal(baselineImport, paramdef))); + ASSERT_THAT(GetCustomAttribute_Nullable(currentImport, paramdef), testing::ElementsAreArray(GetCustomAttribute_Nullable(baselineImport, paramdef))); + ASSERT_THAT(GetNameFromToken(currentImport, paramdef), testing::ElementsAreArray(GetNameFromToken(baselineImport, paramdef))); + } + + ASSERT_THAT(GetParamForMethodIndex(currentImport, methoddef), testing::ElementsAreArray(GetParamForMethodIndex(baselineImport, methoddef))); + ASSERT_THAT(EnumPermissionSetsAndGetProps(currentImport, methoddef), testing::ElementsAreArray(EnumPermissionSetsAndGetProps(baselineImport, methoddef))); + ASSERT_THAT(GetPinvokeMap(currentImport, methoddef), testing::ElementsAreArray(GetPinvokeMap(baselineImport, methoddef))); + ASSERT_THAT(GetRVA(currentImport, methoddef), testing::ElementsAreArray(GetRVA(baselineImport, methoddef))); + + TokenList methodspecs; + ASSERT_EQUAL_AND_SET(methodspecs, EnumMethodSpecs(baselineImport, methoddef), EnumMethodSpecs(currentImport, methoddef)); + for (auto methodspec : methodspecs) + { + ASSERT_THAT(GetMethodSpecProps(currentImport, methodspec), testing::ElementsAreArray(GetMethodSpecProps(baselineImport, methodspec))); + } + } + + TokenList eventdefs; + ASSERT_EQUAL_AND_SET(eventdefs, EnumEvents(baselineImport, typdef), EnumEvents(currentImport, typdef)); + for (auto eventdef : eventdefs) + { + std::vector mds; + ASSERT_THAT(GetEventProps(currentImport, eventdef, &mds), testing::ElementsAreArray(GetEventProps(baselineImport, eventdef))); + for (auto md : mds) + { + ASSERT_THAT(GetMethodSemantics(currentImport, eventdef, md), testing::ElementsAreArray(GetMethodSemantics(baselineImport, eventdef, md))); + } + + ASSERT_THAT(GetNameFromToken(currentImport, eventdef), testing::ElementsAreArray(GetNameFromToken(baselineImport, eventdef))); + ASSERT_THAT(IsGlobal(currentImport, eventdef), testing::Eq(IsGlobal(baselineImport, eventdef))); + } + + TokenList properties; + ASSERT_EQUAL_AND_SET(properties, EnumProperties(baselineImport, typdef), EnumProperties(currentImport, typdef)); + for (auto props : properties) + { + std::vector mds; + ASSERT_THAT(GetPropertyProps(currentImport, props, &mds), testing::ElementsAreArray(GetPropertyProps(baselineImport, props))); + for (auto md : mds) + { + ASSERT_THAT(GetMethodSemantics(currentImport, props, md), testing::ElementsAreArray(GetMethodSemantics(baselineImport, props, md))); + } + + ASSERT_THAT(GetNameFromToken(currentImport, props), testing::ElementsAreArray(GetNameFromToken(baselineImport, props))); + ASSERT_THAT(IsGlobal(currentImport, props), testing::Eq(IsGlobal(baselineImport, props))); + } + + ASSERT_THAT(EnumFieldsWithName(baselineImport, typdef), EnumFieldsWithName(currentImport, typdef)); + TokenList fielddefs; + ASSERT_EQUAL_AND_SET(fielddefs, EnumFields(baselineImport, typdef), EnumFields(currentImport, typdef)); + for (auto fielddef : fielddefs) + { + ASSERT_THAT(GetFieldProps(currentImport, fielddef), testing::ElementsAreArray(GetFieldProps(baselineImport, fielddef))); + ASSERT_THAT(GetNameFromToken(currentImport, fielddef), testing::ElementsAreArray(GetNameFromToken(baselineImport, fielddef))); + ASSERT_THAT(IsGlobal(currentImport, fielddef), testing::Eq(IsGlobal(baselineImport, fielddef))); + ASSERT_THAT(GetPinvokeMap(currentImport, fielddef), testing::ElementsAreArray(GetPinvokeMap(baselineImport, fielddef))); + ASSERT_THAT(GetRVA(currentImport, fielddef), testing::ElementsAreArray(GetRVA(baselineImport, fielddef))); + ASSERT_THAT(GetFieldMarshal(currentImport, fielddef), testing::ElementsAreArray(GetFieldMarshal(baselineImport, fielddef))); + ASSERT_THAT(GetCustomAttribute_Nullable(currentImport, fielddef), testing::ElementsAreArray(GetCustomAttribute_Nullable(baselineImport, fielddef))); + } + + TokenList genparams; + ASSERT_EQUAL_AND_SET(genparams, EnumGenericParams(baselineImport, typdef), EnumGenericParams(currentImport, typdef)); + for (auto genparam : genparams) + { + ASSERT_THAT(GetGenericParamProps(currentImport, genparam), testing::ElementsAreArray(GetGenericParamProps(baselineImport, genparam))); + TokenList genparamconsts; + ASSERT_EQUAL_AND_SET(genparamconsts, EnumGenericParamConstraints(baselineImport, genparam), EnumGenericParamConstraints(currentImport, genparam)); + for (auto genparamconst : genparamconsts) + { + ASSERT_THAT(GetGenericParamConstraintProps(currentImport, genparamconst), testing::ElementsAreArray(GetGenericParamConstraintProps(baselineImport, genparamconst))); + } + } + } + + minipal::com_ptr baselineAssembly; + ASSERT_THAT(S_OK, baselineImport->QueryInterface(IID_IMetaDataAssemblyImport, (void**)&baselineAssembly)); + minipal::com_ptr currentAssembly; + ASSERT_THAT(S_OK, currentImport->QueryInterface(IID_IMetaDataAssemblyImport, (void**)¤tAssembly)); + + TokenList assemblyTokens; + ASSERT_EQUAL_AND_SET(assemblyTokens, GetAssemblyFromScope(baselineAssembly), GetAssemblyFromScope(currentAssembly)); + for (auto assembly : assemblyTokens) + { + ASSERT_THAT(GetAssemblyProps(currentAssembly, assembly), testing::ElementsAreArray(GetAssemblyProps(baselineAssembly, assembly))); + } + + TokenList assemblyRefs; + ASSERT_EQUAL_AND_SET(assemblyRefs, EnumAssemblyRefs(baselineAssembly), EnumAssemblyRefs(currentAssembly)); + for (auto assemblyRef : assemblyRefs) + { + ASSERT_THAT(GetAssemblyRefProps(currentAssembly, assemblyRef), testing::ElementsAreArray(GetAssemblyRefProps(baselineAssembly, assemblyRef))); + } + + TokenList files; + ASSERT_EQUAL_AND_SET(files, EnumFiles(baselineAssembly), EnumFiles(currentAssembly)); + for (auto file : files) + { + ASSERT_THAT(GetFileProps(currentAssembly, file), testing::ElementsAreArray(GetFileProps(baselineAssembly, file))); + } + + TokenList exports; + ASSERT_EQUAL_AND_SET(exports, EnumExportedTypes(baselineAssembly), EnumExportedTypes(currentAssembly)); + for (auto exportedType : exports) + { + std::vector name; + uint32_t implementation = mdTokenNil; + ASSERT_THAT(GetExportedTypeProps(currentAssembly, exportedType, &name, &implementation), testing::ElementsAreArray(GetExportedTypeProps(baselineAssembly, exportedType))); + ASSERT_THAT( + FindExportedTypeByName(currentAssembly, name.data(), implementation), + testing::ElementsAreArray(FindExportedTypeByName(baselineAssembly, name.data(), implementation))); + } + + TokenList resources; + ASSERT_EQUAL_AND_SET(resources, EnumManifestResources(baselineAssembly), EnumManifestResources(currentAssembly)); + for (auto resource : resources) + { + std::vector name; + ASSERT_THAT(GetManifestResourceProps(currentAssembly, resource, &name), testing::ElementsAreArray(GetManifestResourceProps(baselineAssembly, resource))); + ASSERT_THAT(FindManifestResourceByName(currentAssembly, name.data()), testing::ElementsAreArray(FindManifestResourceByName(baselineAssembly, name.data()))); + } +} + +INSTANTIATE_TEST_SUITE_P(MetaDataImportTestCore, MetadataImportTest, testing::ValuesIn(MetadataFilesInDirectory(GetBaselineDirectory())), PrintName); + +INSTANTIATE_TEST_SUITE_P(MetaDataImportTestFx4_0, MetadataImportTest, testing::ValuesIn(MetadataFilesInDirectory(FindFrameworkInstall(X("v4.0.30319")))), PrintName); +INSTANTIATE_TEST_SUITE_P(MetaDataImportTestFx2_0, MetadataImportTest, testing::ValuesIn(MetadataFilesInDirectory(FindFrameworkInstall(X("v2.0.50727")))), PrintName); + +INSTANTIATE_TEST_SUITE_P(MetaDataImportTest_IndirectionTables, MetadataImportTest, testing::Values(MetadataFile{ MetadataFile::Kind::Generated, IndirectionTablesKey }), PrintName); + +class MetaDataLongRunningTest : public RegressionTest +{ +}; + +TEST_P(MetaDataLongRunningTest, ImportAPIs) +{ + auto param = GetParam(); + span blob = GetMetadataForFile(param); + void const* data = blob.data(); + uint32_t dataLen = (uint32_t)blob.size(); + + // Load metadata + minipal::com_ptr baselineImport; + ASSERT_HRESULT_SUCCEEDED(CreateImport(TestBaseline::Metadata, data, dataLen, &baselineImport)); + + minipal::com_ptr dispenser; + ASSERT_HRESULT_SUCCEEDED(GetDispenser(IID_IMetaDataDispenser, (void**)&dispenser)); + minipal::com_ptr currentImport; + ASSERT_HRESULT_SUCCEEDED(CreateImport(dispenser, data, dataLen, ¤tImport)); + + static auto VerifyFindMemberRef = [](IMetaDataImport2 * import, mdToken memberRef) -> std::vector + { + std::vector values; + + mdToken ptk; + static_char_buffer name{}; + ULONG pchMember; + PCCOR_SIGNATURE ppvSigBlob; + ULONG pcbSigBlob; + HRESULT hr = import->GetMemberRefProps(memberRef, + & ptk, + name.data(), + (ULONG)name.size(), + & pchMember, + & ppvSigBlob, + & pcbSigBlob); + values.push_back(hr); + if (hr == S_OK) + { + // We were able to get the name, now try looking up a memberRef by name and by sig + mdMemberRef lookup = mdTokenNil; + hr = import->FindMemberRef(ptk, name.data(), ppvSigBlob, pcbSigBlob, & lookup); + values.push_back(hr); + values.push_back(lookup); + lookup = mdTokenNil; + hr = import->FindMemberRef(ptk, name.data(), nullptr, 0, & lookup); + values.push_back(hr); + values.push_back(lookup); + lookup = mdTokenNil; + hr = import->FindMemberRef(ptk, nullptr, ppvSigBlob, pcbSigBlob, & lookup); + values.push_back(hr); + values.push_back(lookup); + } + return values; + }; + + size_t stride; + size_t count; + + TokenList typedefs; + ASSERT_EQUAL_AND_SET(typedefs, EnumTypeDefs(baselineImport), EnumTypeDefs(currentImport)); + count = 0; + stride = std::max(typedefs.size() / 128, (size_t)16); + for (auto typdef : typedefs) + { + if (count++ % stride != 0) + continue; + + EXPECT_THAT(EnumMemberRefs(currentImport, typdef), testing::ElementsAreArray(EnumMemberRefs(baselineImport, typdef))); + + TokenList methoddefs; + ASSERT_EQUAL_AND_SET(methoddefs, EnumMethods(baselineImport, typdef), EnumMethods(currentImport, typdef)); + for (auto methoddef : methoddefs) + { + EXPECT_THAT(EnumMethodSemantics(currentImport, methoddef), testing::ElementsAreArray(EnumMethodSemantics(baselineImport, methoddef))); + } + + EXPECT_THAT(EnumCustomAttributes(currentImport, typdef), testing::ElementsAreArray(EnumCustomAttributes(baselineImport, typdef))); + } + + TokenList typespecs; + ASSERT_EQUAL_AND_SET(typespecs, EnumTypeSpecs(baselineImport), EnumTypeSpecs(currentImport)); + count = 0; + stride = std::max(typespecs.size() / 128, (size_t)16); + for (auto typespec : typespecs) + { + if (count++ % stride != 0) + continue; + + TokenList memberrefs; + ASSERT_EQUAL_AND_SET(memberrefs, EnumMemberRefs(baselineImport, typespec), EnumMemberRefs(currentImport, typespec)); + for (auto memberref : memberrefs) + { + EXPECT_THAT(GetMemberRefProps(currentImport, memberref), testing::ElementsAreArray(GetMemberRefProps(baselineImport, memberref))); + EXPECT_THAT(VerifyFindMemberRef(currentImport, memberref), testing::ElementsAreArray(VerifyFindMemberRef(baselineImport, memberref))); + } + } +} + +INSTANTIATE_TEST_SUITE_P(MetaDataLongRunningTest_CoreLibs, MetaDataLongRunningTest, testing::ValuesIn(CoreLibFiles()), PrintName); diff --git a/src/native/minipal/CMakeLists.txt b/src/native/minipal/CMakeLists.txt index 1504424f3d8a7..9dd7da223ae5d 100644 --- a/src/native/minipal/CMakeLists.txt +++ b/src/native/minipal/CMakeLists.txt @@ -5,6 +5,7 @@ set(SOURCES guid.c random.c debugger.c + sha1.c strings.c time.c unicodedata.c @@ -17,7 +18,7 @@ include_directories(${CLR_SRC_NATIVE_DIR} ${CMAKE_CURRENT_BINARY_DIR}) add_library(minipal_objects OBJECT ${SOURCES}) if (WIN32) - target_link_libraries(minipal_objects PUBLIC bcrypt) + target_link_libraries(minipal_objects PUBLIC bcrypt ole32) endif() # Add a copy of the minipal object library with interprocedural optimization disabled @@ -27,7 +28,7 @@ add_library(aotminipal STATIC ${SOURCES}) set_target_properties(aotminipal PROPERTIES INTERPROCEDURAL_OPTIMIZATION OFF) if (WIN32) - target_link_libraries(aotminipal PUBLIC bcrypt) + target_link_libraries(aotminipal PUBLIC bcrypt ole32) endif() # Provide a static library for our shared library and executable scenarios @@ -40,3 +41,5 @@ add_library(minipal_sanitizer_support OBJECT # Exclude this target from the default build as we may not have sanitzer headers available # in a non-sanitized build. set_target_properties(minipal_sanitizer_support PROPERTIES EXCLUDE_FROM_ALL ON) + +add_subdirectory(com) diff --git a/src/native/minipal/com/CMakeLists.txt b/src/native/minipal/com/CMakeLists.txt new file mode 100644 index 0000000000000..05fa96fad04cc --- /dev/null +++ b/src/native/minipal/com/CMakeLists.txt @@ -0,0 +1,16 @@ +add_library(minipal_comhdrs INTERFACE) +target_include_directories(minipal_comhdrs INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/winhdrs) +target_compile_definitions(minipal_comhdrs INTERFACE MINIPAL_COM_TYPEDEFS MINIPAL_COM_WINHDRS) + +add_library(minipal_com INTERFACE) + +if (CLR_CMAKE_HOST_WIN32 OR HOST_WIN32) +else() + target_link_libraries(minipal_com INTERFACE minipal_comhdrs minipal) +endif() + +if (CMAKE_C_BYTE_ORDER EQUAL "BIG_ENDIAN") + target_compile_definitions(minipal_com INTERFACE MINIPAL_COM_BIGENDIAN) +endif() + +target_include_directories(minipal_com INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/src/native/minipal/com/comtypes.h b/src/native/minipal/com/comtypes.h new file mode 100644 index 0000000000000..e7e208b7c3858 --- /dev/null +++ b/src/native/minipal/com/comtypes.h @@ -0,0 +1,435 @@ +// Copyright 2022 Aaron R Robinson +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is furnished +// to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#ifndef MINIPAL_COM_COMTYPES_H +#define MINIPAL_COM_COMTYPES_H + +// Perform platform check +#ifdef _MSC_VER + #define MINIPAL_COM_WINDOWS +#endif + +// Typedefs typically provided by Windows' headers +#ifdef MINIPAL_COM_TYPEDEFS + typedef void* PVOID; + typedef void* LPVOID; + typedef void const* LPCVOID; + typedef uintptr_t UINT_PTR; + typedef size_t SIZE_T; + + typedef uint8_t BYTE; + typedef char CHAR; + typedef int16_t SHORT; + typedef uint16_t USHORT; + typedef int32_t INT; + typedef int32_t INT32; + typedef uint32_t UINT; + typedef int32_t LONG; + typedef uint32_t ULONG; + typedef uint32_t ULONG32; + typedef uint32_t DWORD; + typedef int64_t LONGLONG; + typedef uint64_t UINT64; + typedef uint64_t ULONG64; + typedef uint64_t ULONGLONG; + typedef float FLOAT; + typedef double DOUBLE; + typedef int32_t SCODE; + typedef int32_t DATE; + +#ifdef MINIPAL_COM_WINDOWS + typedef wchar_t WCHAR; +#elif defined(__cplusplus) + typedef char16_t WCHAR; +#else + typedef uint16_t WCHAR; +#endif // __cplusplus + + typedef WCHAR* LPWSTR; + typedef WCHAR const* LPCWSTR; + + typedef WCHAR OLECHAR; + typedef OLECHAR* LPOLESTR; + typedef OLECHAR const* LPCOLESTR; + typedef OLECHAR* BSTR; + + typedef int32_t BOOL; + #define TRUE ((BOOL)1) + #define FALSE ((BOOL)0) + + typedef int16_t VARIANT_BOOL; + #define VARIANT_TRUE ((VARIANT_BOOL)-1) + #define VARIANT_FALSE ((VARIANT_BOOL)0) + + typedef unsigned short VARTYPE; + + typedef int32_t HRESULT; + typedef void* HANDLE; + + typedef struct + { + uint32_t Data1; + uint16_t Data2; + uint16_t Data3; + uint8_t Data4[8]; + } GUID; + + typedef GUID IID; + + // 00000000-0000-0000-0000-000000000000 + extern "C" IID const GUID_NULL; + + typedef union { + struct { +#ifdef MINIPAL_COM_BIG_ENDIAN + LONG HighPart; + DWORD LowPart; +#else + DWORD LowPart; + LONG HighPart; +#endif + } u; + LONGLONG QuadPart; + } LARGE_INTEGER; + + typedef union { + struct { +#ifdef MINIPAL_COM_BIG_ENDIAN + DWORD HighPart; + DWORD LowPart; +#else + DWORD LowPart; + DWORD HighPart; +#endif + } u; + ULONGLONG QuadPart; + } ULARGE_INTEGER; +#endif // MINIPAL_COM_TYPEDEFS + +// +// Windows headers +// + +#ifdef MINIPAL_COM_WINHDRS + #include + + #if defined(__cplusplus) + + #define EXTERN_C extern "C" + + using REFGUID = GUID const&; + using IID = GUID; + using REFIID = IID const&; + using CLSID = GUID; + using REFCLSID = CLSID const&; + + // The MINIPAL_COM_DEFINE_GUID should only be set in a compilation unit + // to avoid duplicate symbol problems during linking. + #if defined(MINIPAL_COM_DEFINE_GUID) + #define EXTERN_GUID(itf,l1,s1,s2,c1,c2,c3,c4,c5,c6,c7,c8) \ + EXTERN_C constexpr IID itf = {l1,s1,s2,{c1,c2,c3,c4,c5,c6,c7,c8}} + #else + #define EXTERN_GUID(itf,l1,s1,s2,c1,c2,c3,c4,c5,c6,c7,c8) \ + EXTERN_C const IID itf + #endif // !MINIPAL_COM_DEFINE_GUID + + // sal + #define _In_ + #define _In_z_ + #define _In_opt_ + #define _In_reads_bytes_(x) + #define _Inout_ + #define _Out_ + #define _Out_opt_ + #define _Out_writes_to_opt_(x,y) + #define _Out_writes_bytes_to_(x, y) + #define _Out_writes_to_(x,y) + #define _COM_Outptr_ + #define __RPC_FAR + #define __RPC_USER + #define __RPC__in + #define __RPC__in_xcount(x) + #define __RPC__in_ecount_full(x) + #define __RPC__in_opt + #define __RPC__inout + #define __RPC__inout_xcount(x) + #define __RPC__out + #define __RPC__out_ecount_part(x,y) + #define __RPC__deref_out_ecount_full_opt(x) + #define __RPC__deref_out_opt + #define __RPC__out + + // COM Interface definitions + #define __uuidof(type) IID_##type + #define interface struct + #define DECLSPEC_UUID(x) + #define DECLSPEC_NOVTABLE + #define MIDL_INTERFACE(x) struct DECLSPEC_UUID(x) DECLSPEC_NOVTABLE + #define DECLARE_INTERFACE_(iface, baseiface) interface DECLSPEC_NOVTABLE iface : public baseiface + #define STDMETHODCALLTYPE + #define STDMETHOD(method) virtual HRESULT STDMETHODCALLTYPE method + #define STDMETHOD_(type,method) virtual type STDMETHODCALLTYPE method + + #define UNALIGNED + #define PURE = 0 + + #define UNREFERENCED_PARAMETER(p) (void)p + + #include + + // Unusable COM and RPC types + struct tagSTATSTG; + using STATSTG = tagSTATSTG; + interface ITypeInfo; + interface IDispatch; + interface IRpcChannelBuffer; + interface IRecordInfo; + using RPC_IF_HANDLE = void*; + struct SAFEARRAY; + + // Unusable Win32 types + using LPDEBUG_EVENT = SIZE_T; + using LPSTARTUPINFOW = SIZE_T; + using LPPROCESS_INFORMATION = SIZE_T; + using LPSECURITY_ATTRIBUTES = SIZE_T; + + // Other COM interfaces + #include + + // OLE VARIANT types + typedef struct { + struct { + ULONG Lo; + LONG Hi; + } DUMMYSTRUCTNAME; + LONGLONG int64; + } CY; + typedef struct { + USHORT wReserved; + union { + struct { + BYTE scale; + BYTE sign; + } DUMMYSTRUCTNAME; + USHORT signscale; + } DUMMYUNIONNAME; + ULONG Hi32; + union { + struct { + ULONG Lo32; + ULONG Mid32; + } DUMMYSTRUCTNAME2; + ULONGLONG Lo64; + } DUMMYUNIONNAME2; + } DECIMAL; + + enum VARENUM + { + VT_EMPTY = 0, + VT_NULL = 1, + VT_I2 = 2, + VT_I4 = 3, + VT_R4 = 4, + VT_R8 = 5, + VT_CY = 6, + VT_DATE = 7, + VT_BSTR = 8, + VT_DISPATCH = 9, + VT_ERROR = 10, + VT_BOOL = 11, + VT_VARIANT = 12, + VT_UNKNOWN = 13, + VT_DECIMAL = 14, + VT_I1 = 16, + VT_UI1 = 17, + VT_UI2 = 18, + VT_UI4 = 19, + VT_I8 = 20, + VT_UI8 = 21, + VT_INT = 22, + VT_UINT = 23, + VT_VOID = 24, + VT_HRESULT = 25, + VT_PTR = 26, + VT_SAFEARRAY = 27, + VT_CARRAY = 28, + VT_USERDEFINED = 29, + VT_LPSTR = 30, + VT_LPWSTR = 31, + VT_RECORD = 36, + VT_INT_PTR = 37, + VT_UINT_PTR = 38, + VT_FILETIME = 64, + VT_BLOB = 65, + VT_STREAM = 66, + VT_STORAGE = 67, + VT_STREAMED_OBJECT = 68, + VT_STORED_OBJECT = 69, + VT_BLOB_OBJECT = 70, + VT_CF = 71, + VT_CLSID = 72, + VT_VERSIONED_STREAM = 73, + VT_BSTR_BLOB = 0xfff, + VT_VECTOR = 0x1000, + VT_ARRAY = 0x2000, + VT_BYREF = 0x4000, + VT_RESERVED = 0x8000, + VT_ILLEGAL = 0xffff, + VT_ILLEGALMASKED = 0xfff, + VT_TYPEMASK = 0xfff + }; + + typedef struct tagVARIANT { + union { + struct __tagVARIANT { + VARTYPE vt; + uint16_t wReserved1; + uint16_t wReserved2; + uint16_t wReserved3; + union { + LONGLONG llVal; + LONG lVal; + BYTE bVal; + SHORT iVal; + FLOAT fltVal; + DOUBLE dblVal; + VARIANT_BOOL boolVal; + VARIANT_BOOL __OBSOLETE__VARIANT_BOOL; + SCODE scode; + CY cyVal; + DATE date; + BSTR bstrVal; + IUnknown *punkVal; + IDispatch *pdispVal; + SAFEARRAY *parray; + BYTE *pbVal; + SHORT *piVal; + LONG *plVal; + LONGLONG *pllVal; + FLOAT *pfltVal; + DOUBLE *pdblVal; + VARIANT_BOOL *pboolVal; + VARIANT_BOOL *__OBSOLETE__VARIANT_PBOOL; + SCODE *pscode; + CY *pcyVal; + DATE *pdate; + BSTR *pbstrVal; + IUnknown **ppunkVal; + IDispatch **ppdispVal; + SAFEARRAY **pparray; + struct tagVARIANT *pvarVal; + PVOID byref; + CHAR cVal; + USHORT uiVal; + ULONG ulVal; + ULONGLONG ullVal; + INT intVal; + UINT uintVal; + DECIMAL *pdecVal; + CHAR *pcVal; + USHORT *puiVal; + ULONG *pulVal; + ULONGLONG *pullVal; + INT *pintVal; + UINT *puintVal; + struct __tagBRECORD { + PVOID pvRecord; + IRecordInfo *pRecInfo; + } n4; + } n3; + } n2; + DECIMAL decVal; + } n1; + } VARIANT; + + #define V_UNION(X, Y) ((X)->n1.n2.n3.Y) + #define V_VT(X) ((X)->n1.n2.vt) + #define V_RECORDINFO(X) ((X)->n1.n2.n3.brecVal.pRecInfo) + #define V_RECORD(X) ((X)->n1.n2.n3.brecVal.pvRecord) + #define V_DECIMAL(X) ((X)->n1.decVal) + + // VARIANT access macros + #define V_ISBYREF(X) (V_VT(X)&VT_BYREF) + #define V_ISARRAY(X) (V_VT(X)&VT_ARRAY) + #define V_ISVECTOR(X) (V_VT(X)&VT_VECTOR) + #define V_NONE(X) V_I2(X) + + #define V_UI1(X) V_UNION(X, bVal) + #define V_UI1REF(X) V_UNION(X, pbVal) + #define V_I2(X) V_UNION(X, iVal) + #define V_I2REF(X) V_UNION(X, piVal) + #define V_I4(X) V_UNION(X, lVal) + #define V_I4REF(X) V_UNION(X, plVal) + #define V_I8(X) V_UNION(X, llVal) + #define V_I8REF(X) V_UNION(X, pllVal) + #define V_R4(X) V_UNION(X, fltVal) + #define V_R4REF(X) V_UNION(X, pfltVal) + #define V_R8(X) V_UNION(X, dblVal) + #define V_R8REF(X) V_UNION(X, pdblVal) + #define V_I1(X) V_UNION(X, cVal) + #define V_I1REF(X) V_UNION(X, pcVal) + #define V_UI2(X) V_UNION(X, uiVal) + #define V_UI2REF(X) V_UNION(X, puiVal) + #define V_UI4(X) V_UNION(X, ulVal) + #define V_UI4REF(X) V_UNION(X, pulVal) + #define V_UI8(X) V_UNION(X, ullVal) + #define V_UI8REF(X) V_UNION(X, pullVal) + #define V_INT(X) V_UNION(X, intVal) + #define V_INTREF(X) V_UNION(X, pintVal) + #define V_UINT(X) V_UNION(X, uintVal) + #define V_UINTREF(X) V_UNION(X, puintVal) + + #if INTPTR_MAX == INT64_MAX + #define V_INT_PTR(X) V_UNION(X, llVal) + #define V_UINT_PTR(X) V_UNION(X, ullVal) + #define V_INT_PTRREF(X) V_UNION(X, pllVal) + #define V_UINT_PTRREF(X) V_UNION(X, pullVal) + #elif INTPTR_MAX == INT32_MAX + #define V_INT_PTR(X) V_UNION(X, lVal) + #define V_UINT_PTR(X) V_UNION(X, ulVal) + #define V_INT_PTRREF(X) V_UNION(X, plVal) + #define V_UINT_PTRREF(X) V_UNION(X, pulVal) + #else + #error "Unknown pointer size" + #endif + + #define V_CY(X) V_UNION(X, cyVal) + #define V_CYREF(X) V_UNION(X, pcyVal) + #define V_DATE(X) V_UNION(X, date) + #define V_DATEREF(X) V_UNION(X, pdate) + #define V_BSTR(X) V_UNION(X, bstrVal) + #define V_BSTRREF(X) V_UNION(X, pbstrVal) + #define V_DISPATCH(X) V_UNION(X, pdispVal) + #define V_DISPATCHREF(X) V_UNION(X, ppdispVal) + #define V_ERROR(X) V_UNION(X, scode) + #define V_ERRORREF(X) V_UNION(X, pscode) + #define V_BOOL(X) V_UNION(X, boolVal) + #define V_BOOLREF(X) V_UNION(X, pboolVal) + #define V_UNKNOWN(X) V_UNION(X, punkVal) + #define V_UNKNOWNREF(X) V_UNION(X, ppunkVal) + #define V_VARIANTREF(X) V_UNION(X, pvarVal) + #define V_ARRAY(X) V_UNION(X, parray) + #define V_ARRAYREF(X) V_UNION(X, pparray) + #define V_BYREF(X) V_UNION(X, byref) + + #define V_DECIMALREF(X) V_UNION(X, pdecVal) + #endif // __cplusplus +#endif // MINIPAL_COM_WINHDRS + +#endif // MINIPAL_COM_COMTYPES_H diff --git a/src/native/minipal/com/minipal_com.h b/src/native/minipal/com/minipal_com.h new file mode 100644 index 0000000000000..cb7cc8671687c --- /dev/null +++ b/src/native/minipal/com/minipal_com.h @@ -0,0 +1,129 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#ifndef MINIPAL_COM_H +#define MINIPAL_COM_H + +#include "comtypes.h" +#include "memory.h" +#include "../guid.h" + +#ifdef __cplusplus + extern "C" + { +#endif // __cplusplus + +// +// Strings +// + +// This macro is used to standardize wide character string literals used in .NET. +#ifdef MINIPAL_COM_WINDOWS + #define W(str) L ## str +#else + #define W(str) u ## str +#endif + +// +// GUIDs +// + +#ifdef __cplusplus + #ifdef MINIPAL_COM_WINHDRS + inline bool operator==(REFGUID a, REFGUID b) + { + minipal_guid_t const& ga = reinterpret_cast(a); + minipal_guid_t const& gb = reinterpret_cast(b); + return ga == gb; + } + + inline bool operator!=(REFGUID a, REFGUID b) + { + return !(a == b); + } + #endif // MINIPAL_COM_WINHDRS + } +#endif // __cplusplus + +#ifdef __cplusplus + #include + #include + namespace minipal + { + // Smart pointer for use with IUnknown based interfaces. + // It is based off of ATL::CComPtr so adoption is easier. + template + class com_ptr + { + public: + T* p; + + public: + com_ptr() : p{} {} + + com_ptr(T* t) + : p{ t } + { + if (p != nullptr) + (void)p->AddRef(); + } + + com_ptr(com_ptr const&) = delete; + + com_ptr(com_ptr&& other) + : p{ other.Detach() } + { } + + ~com_ptr() { Release(); } + + com_ptr& operator=(com_ptr const&) = delete; + + com_ptr& operator=(com_ptr&& other) + { + Attach(other.Detach()); + return (*this); + } + + operator T*() { return p; } + + T** operator&() { return &p; } + + T* operator->() { return p; } + + void Attach(T* t) noexcept + { + Release(); + p = t; + } + + T* Detach() noexcept + { + T* tmp = p; + p = nullptr; + return tmp; + } + + void Release() noexcept + { + if (p != nullptr) + { + (void)p->Release(); + p = nullptr; + } + } + }; + + // Smart pointer for CoTaskMem* + struct cotaskmem_deleter + { + void operator()(LPVOID p) + { + CoTaskMemFree(p); + } + }; + template + using cotaskmem_ptr = std::unique_ptr; + } +#endif // __cplusplus + +#endif // MINIPAL_COM_H diff --git a/src/native/minipal/com/winhdrs/oaidl.h b/src/native/minipal/com/winhdrs/oaidl.h new file mode 100644 index 0000000000000..f8b879232b493 --- /dev/null +++ b/src/native/minipal/com/winhdrs/oaidl.h @@ -0,0 +1,25 @@ +// Copyright 2022 Aaron R Robinson +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is furnished +// to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#ifndef _WINHDRS_OAIDL_H_ +#define _WINHDRS_OAIDL_H_ + +// Dummy header + +#endif // _WINHDRS_OAIDL_H_ diff --git a/src/native/minipal/com/winhdrs/objidl.h b/src/native/minipal/com/winhdrs/objidl.h new file mode 100644 index 0000000000000..2a884b0f9294f --- /dev/null +++ b/src/native/minipal/com/winhdrs/objidl.h @@ -0,0 +1,112 @@ +// Copyright 2022 Aaron R Robinson +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is furnished +// to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +// Heavily modified from Windows SDK + +#include "rpc.h" +#include "rpcndr.h" + +#ifndef __ISequentialStream_INTERFACE_DEFINED__ +#define __ISequentialStream_INTERFACE_DEFINED__ + +/* interface ISequentialStream */ +/* [unique][uuid][object] */ + +EXTERN_C const IID IID_ISequentialStream; + + MIDL_INTERFACE("0c733a30-2a1c-11ce-ade5-00aa0044773d") + ISequentialStream : public IUnknown + { + public: + virtual /* [local] */ HRESULT STDMETHODCALLTYPE Read( + /* [annotation] */ + _Out_writes_bytes_to_(cb, *pcbRead) void *pv, + /* [annotation][in] */ + _In_ ULONG cb, + /* [annotation] */ + _Out_opt_ ULONG *pcbRead) = 0; + + virtual /* [local] */ HRESULT STDMETHODCALLTYPE Write( + /* [annotation] */ + _In_reads_bytes_(cb) const void *pv, + /* [annotation][in] */ + _In_ ULONG cb, + /* [annotation] */ + _Out_opt_ ULONG *pcbWritten) = 0; + + }; + +#endif /* __ISequentialStream_INTERFACE_DEFINED__ */ + + +#ifndef __IStream_INTERFACE_DEFINED__ +#define __IStream_INTERFACE_DEFINED__ + +/* interface IStream */ +/* [unique][uuid][object] */ + +EXTERN_C const IID IID_IStream; + + MIDL_INTERFACE("0000000c-0000-0000-C000-000000000046") + IStream : public ISequentialStream + { + public: + virtual /* [local] */ HRESULT STDMETHODCALLTYPE Seek( + /* [in] */ LARGE_INTEGER dlibMove, + /* [in] */ DWORD dwOrigin, + /* [annotation] */ + _Out_opt_ ULARGE_INTEGER *plibNewPosition) = 0; + + virtual HRESULT STDMETHODCALLTYPE SetSize( + /* [in] */ ULARGE_INTEGER libNewSize) = 0; + + virtual /* [local] */ HRESULT STDMETHODCALLTYPE CopyTo( + /* [annotation][unique][in] */ + _In_ IStream *pstm, + /* [in] */ ULARGE_INTEGER cb, + /* [annotation] */ + _Out_opt_ ULARGE_INTEGER *pcbRead, + /* [annotation] */ + _Out_opt_ ULARGE_INTEGER *pcbWritten) = 0; + + virtual HRESULT STDMETHODCALLTYPE Commit( + /* [in] */ DWORD grfCommitFlags) = 0; + + virtual HRESULT STDMETHODCALLTYPE Revert( void) = 0; + + virtual HRESULT STDMETHODCALLTYPE LockRegion( + /* [in] */ ULARGE_INTEGER libOffset, + /* [in] */ ULARGE_INTEGER cb, + /* [in] */ DWORD dwLockType) = 0; + + virtual HRESULT STDMETHODCALLTYPE UnlockRegion( + /* [in] */ ULARGE_INTEGER libOffset, + /* [in] */ ULARGE_INTEGER cb, + /* [in] */ DWORD dwLockType) = 0; + + virtual HRESULT STDMETHODCALLTYPE Stat( + /* [out] */ __RPC__out STATSTG *pstatstg, + /* [in] */ DWORD grfStatFlag) = 0; + + virtual HRESULT STDMETHODCALLTYPE Clone( + /* [out] */ __RPC__deref_out_opt IStream **ppstm) = 0; + + }; + +#endif /* __IStream_INTERFACE_DEFINED__ */ diff --git a/src/native/minipal/com/winhdrs/ole2.h b/src/native/minipal/com/winhdrs/ole2.h new file mode 100644 index 0000000000000..a5ca500fac23a --- /dev/null +++ b/src/native/minipal/com/winhdrs/ole2.h @@ -0,0 +1,25 @@ +// Copyright 2022 Aaron R Robinson +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is furnished +// to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#ifndef _WINHDRS_OLE2_H_ +#define _WINHDRS_OLE2_H_ + +// Dummy header + +#endif // _WINHDRS_OLE2_H_ diff --git a/src/native/minipal/com/winhdrs/poppack.h b/src/native/minipal/com/winhdrs/poppack.h new file mode 100644 index 0000000000000..0bb3ae839b310 --- /dev/null +++ b/src/native/minipal/com/winhdrs/poppack.h @@ -0,0 +1,38 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// + +// +// =========================================================================== +// File: poppack.h +// +// =========================================================================== +/* +Abstract: + + This file turns packing of structures off. (That is, it enables + automatic alignment of structure fields.) An include file is needed + because various compilers do this in different ways. + + poppack.h is the complement to pshpack?.h. An inclusion of poppack.h + MUST ALWAYS be preceded by an inclusion of one of pshpack?.h, in one-to-one + correspondence. + + For Microsoft compatible compilers, this file uses the pop option + to the pack pragma so that it can restore the previous saved by the + pshpack?.h include file. + +*/ + +#if ! (defined(lint) || defined(RC_INVOKED)) +#if ( _MSC_VER >= 800 && !defined(_M_I86)) || defined(_PUSHPOP_SUPPORTED) +#pragma warning(disable:4103) +#if !(defined( MIDL_PASS )) || defined( __midl ) +#pragma pack(pop) +#else +#pragma pack() +#endif +#else +#pragma pack() +#endif +#endif // ! (defined(lint) || defined(RC_INVOKED)) diff --git a/src/native/minipal/com/winhdrs/pshpack1.h b/src/native/minipal/com/winhdrs/pshpack1.h new file mode 100644 index 0000000000000..92f7a83448bbe --- /dev/null +++ b/src/native/minipal/com/winhdrs/pshpack1.h @@ -0,0 +1,37 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// + +// +// =========================================================================== +// File: pshpack1.h +// +// =========================================================================== + +/*++ + +Abstract: + + This file turns 1 byte packing of structures on. (That is, it disables + automatic alignment of structure fields.) An include file is needed + because various compilers do this in different ways. For Microsoft + compatible compilers, this files uses the push option to the pack pragma + so that the poppack.h include file can restore the previous packing + reliably. + + The file poppack.h is the complement to this file. + +--*/ + +#if ! (defined(lint) || defined(RC_INVOKED)) +#if ( _MSC_VER >= 800 && !defined(_M_I86)) || defined(_PUSHPOP_SUPPORTED) +#pragma warning(disable:4103) +#if !(defined( MIDL_PASS )) || defined( __midl ) +#pragma pack(push,1) +#else +#pragma pack(1) +#endif +#else +#pragma pack(1) +#endif +#endif // ! (defined(lint) || defined(RC_INVOKED)) diff --git a/src/native/minipal/com/winhdrs/rpc.h b/src/native/minipal/com/winhdrs/rpc.h new file mode 100644 index 0000000000000..4f03a4230759e --- /dev/null +++ b/src/native/minipal/com/winhdrs/rpc.h @@ -0,0 +1,25 @@ +// Copyright 2022 Aaron R Robinson +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is furnished +// to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#ifndef _WINHDRS_RPC_H_ +#define _WINHDRS_RPC_H_ + +// Dummy header + +#endif // _WINHDRS_RPC_H_ diff --git a/src/native/minipal/com/winhdrs/rpcndr.h b/src/native/minipal/com/winhdrs/rpcndr.h new file mode 100644 index 0000000000000..bda139a72c990 --- /dev/null +++ b/src/native/minipal/com/winhdrs/rpcndr.h @@ -0,0 +1,27 @@ +// Copyright 2022 Aaron R Robinson +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is furnished +// to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#ifndef _WINHDRS_RPCNDR_H_ +#define _WINHDRS_RPCNDR_H_ + +#ifndef __RPCNDR_H_VERSION__ + #define __RPCNDR_H_VERSION__ +#endif /* __RPCNDR_H_VERSION__ */ + +#endif // _WINHDRS_RPCNDR_H_ diff --git a/src/native/minipal/com/winhdrs/specstrings.h b/src/native/minipal/com/winhdrs/specstrings.h new file mode 100644 index 0000000000000..3025f0a600317 --- /dev/null +++ b/src/native/minipal/com/winhdrs/specstrings.h @@ -0,0 +1,25 @@ +// Copyright 2022 Aaron R Robinson +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is furnished +// to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#ifndef _WINHDRS_SPECSTRINGS_H_ +#define _WINHDRS_SPECSTRINGS_H_ + +// Dummy header + +#endif // _WINHDRS_SPECSTRINGS_H_ \ No newline at end of file diff --git a/src/native/minipal/com/winhdrs/unknwn.h b/src/native/minipal/com/winhdrs/unknwn.h new file mode 100644 index 0000000000000..795555ee895c1 --- /dev/null +++ b/src/native/minipal/com/winhdrs/unknwn.h @@ -0,0 +1,65 @@ +// Copyright 2022 Aaron R Robinson +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is furnished +// to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +// Heavily modified from Windows SDK + +#include "rpc.h" +#include "rpcndr.h" + +#ifndef __IUnknown_INTERFACE_DEFINED__ +#define __IUnknown_INTERFACE_DEFINED__ + +typedef interface IUnknown IUnknown; + +// 00000000-0000-0000-C000-000000000046 +EXTERN_C const IID IID_IUnknown; + +MIDL_INTERFACE("00000000-0000-0000-C000-000000000046") +IUnknown +{ + virtual HRESULT STDMETHODCALLTYPE QueryInterface( + REFIID riid, + void **ppvObject) = 0; + + virtual ULONG STDMETHODCALLTYPE AddRef( void) = 0; + + virtual ULONG STDMETHODCALLTYPE Release( void) = 0; +}; + +#endif // __IUnknown_INTERFACE_DEFINED__ + +#ifndef __IClassFactory_INTERFACE_DEFINED__ +#define __IClassFactory_INTERFACE_DEFINED__ + +// 00000001-0000-0000-C000-000000000046 +EXTERN_C const IID IID_IClassFactory; + +MIDL_INTERFACE("00000001-0000-0000-C000-000000000046") +IClassFactory : public IUnknown +{ + virtual HRESULT STDMETHODCALLTYPE CreateInstance( + IUnknown *pUnkOuter, + REFIID riid, + void **ppvObject) = 0; + + virtual HRESULT STDMETHODCALLTYPE LockServer( + BOOL fLock) = 0; +}; + +#endif // __IClassFactory_INTERFACE_DEFINED__ diff --git a/src/native/minipal/com/winhdrs/winerror.h b/src/native/minipal/com/winhdrs/winerror.h new file mode 100644 index 0000000000000..fd39855a74645 --- /dev/null +++ b/src/native/minipal/com/winhdrs/winerror.h @@ -0,0 +1,52 @@ +// Copyright 2022 Aaron R Robinson +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is furnished +// to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#ifndef _WINHDRS_WINERROR_H_ +#define _WINHDRS_WINERROR_H_ + +#include + +#define SEVERITY_SUCCESS 0 +#define SEVERITY_ERROR 1 +#define FACILITY_ITF 4 +#define FACILITY_WIN32 7 + +#define MAKE_HRESULT(sev,fac,code) \ + ((HRESULT) (((ULONG)(sev)<<31) | ((ULONG)(fac)<<16) | ((ULONG)(code))) ) + +#define SUCCEEDED(x) (x >= 0) +#define FAILED(x) (x < 0) + +// Win32 HRESULTs +#define S_OK ((HRESULT)0) +#define S_FALSE ((HRESULT)1) +#define E_OUTOFMEMORY ((HRESULT)0x8007000E) +#define E_INVALIDARG ((HRESULT)0x80070057) + +#define E_NOTIMPL ((HRESULT)0x80004001) +#define E_NOINTERFACE ((HRESULT)0x80004002) +#define E_POINTER ((HRESULT)0x80004003) +#define E_ABORT ((HRESULT)0x80004004) +#define E_FAIL ((HRESULT)0x80004005) + +#define E_NOT_SET MAKE_HRESULT(SEVERITY_ERROR, FACILITY_WIN32, 1168) +#define E_NOT_VALID_STATE MAKE_HRESULT(SEVERITY_ERROR, FACILITY_WIN32, 5023) +#define E_NOT_SUFFICIENT_BUFFER MAKE_HRESULT(SEVERITY_ERROR, FACILITY_WIN32, 122) + +#endif // _WINHDRS_WINERROR_H_ diff --git a/src/coreclr/utilcode/sha1.cpp b/src/native/minipal/sha1.c similarity index 59% rename from src/coreclr/utilcode/sha1.cpp rename to src/native/minipal/sha1.c index 9c2e091b52754..3cf1d09b59821 100644 --- a/src/coreclr/utilcode/sha1.cpp +++ b/src/native/minipal/sha1.c @@ -1,68 +1,59 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -// -// -// -// =========================================================================== -// File: sha1.cpp -// -// =========================================================================== -/*++ -Abstract: - - SHA-1 implementation - -Revision History: - ---*/ - -/* - File sha1.cpp Version 03 August 2000. - - - This implements the SHA-1 hash function. - For algorithmic background see (for example) - - - Alfred J. Menezes et al - Handbook of Applied Cryptography - The CRC Press Series on Discrete Mathematics - and its Applications - CRC Press LLC, 1997 - ISBN 0-8495-8523-7 - QA76.9A25M643 - - Also see FIPS 180-1 - Secure Hash Standard, - 1993 May 11 and 1995 April 17, by the U.S. - National Institute of Standards and Technology (NIST). +#include "sha1.h" +#include +#include +#include + +typedef struct { + uint32_t magic_sha1; // Magic value for A_SHA_CTX + uint32_t awaiting_data[16]; + // Data awaiting full 512-bit block. + // Length (nbit_total[0] % 512) bits. + // Unused part of buffer (at end) is zero + uint32_t partial_hash[5]; + // Hash through last full block + uint32_t nbit_total[2]; + // Total length of message so far + // (bits, mod 2^64) +} SHA1_CTX; + + +#if !defined(_MSC_VER) +#if !__has_builtin(_rotl) && !defined(_rotl) +inline static +unsigned int _rotl(unsigned int value, int shift) +{ + unsigned int retval = 0; -*/ + shift &= 0x1f; + retval = (value << shift) | (value >> (sizeof(int) * CHAR_BIT - shift)); + return retval; +} +#endif // !__has_builtin(_rotl) +#endif // !_MSC_VER -#include "stdafx.h" -#include // Utility helpers. -#include "sha1.h" +#define MIN(a,b) (((a) < (b)) ? (a) : (b)) -typedef const DWORD DWORDC; #define ROTATE32L(x,n) _rotl(x,n) -#define SHAVE32(x) (DWORD)(x) +#define SHAVE32(x) (uint32_t)(x) -static void SHA1_block(SHA1_CTX *ctx) /* Update the SHA-1 hash from a fresh 64 bytes of data. */ +static void SHA1_block(SHA1_CTX *ctx) { - static DWORDC sha1_round1 = 0x5A827999u; - static DWORDC sha1_round2 = 0x6ED9EBA1u; - static DWORDC sha1_round3 = 0x8F1BBCDCu; - static DWORDC sha1_round4 = 0xCA62C1D6u; - - DWORD a = ctx->partial_hash[0], b = ctx->partial_hash[1]; - DWORD c = ctx->partial_hash[2], d = ctx->partial_hash[3]; - DWORD e = ctx->partial_hash[4]; - DWORD msg80[80]; + static const uint32_t sha1_round1 = 0x5A827999u; + static const uint32_t sha1_round2 = 0x6ED9EBA1u; + static const uint32_t sha1_round3 = 0x8F1BBCDCu; + static const uint32_t sha1_round4 = 0xCA62C1D6u; + + uint32_t a = ctx->partial_hash[0], b = ctx->partial_hash[1]; + uint32_t c = ctx->partial_hash[2], d = ctx->partial_hash[3]; + uint32_t e = ctx->partial_hash[4]; + uint32_t msg80[80]; int i; - BOOL OK = TRUE; // OACR note: // Loop conditions are using (i <= limit - increment) instead of (i < limit) to satisfy OACR. When the increment is greater @@ -70,15 +61,15 @@ static void SHA1_block(SHA1_CTX *ctx) for (i = 0; i < 16; i++) { // Copy to local array, zero original // Extend length to 80 - DWORDC datval = ctx->awaiting_data[i]; + const uint32_t datval = ctx->awaiting_data[i]; ctx->awaiting_data[i] = 0; msg80[i] = datval; } for (i = 16; i <= 80 - 2; i += 2) { - DWORDC temp1 = msg80[i-3] ^ msg80[i-8] + const uint32_t temp1 = msg80[i-3] ^ msg80[i-8] ^ msg80[i-14] ^ msg80[i-16]; - DWORDC temp2 = msg80[i-2] ^ msg80[i-7] + const uint32_t temp2 = msg80[i-2] ^ msg80[i-7] ^ msg80[i-13] ^ msg80[i-15]; msg80[i ] = ROTATE32L(temp1, 1); msg80[i+1] = ROTATE32L(temp2, 1); @@ -179,34 +170,21 @@ static void SHA1_block(SHA1_CTX *ctx) ctx->partial_hash[2] += c; ctx->partial_hash[3] += d; ctx->partial_hash[4] += e; -#if 0 - for (i = 0; i < 16; i++) { - printf("%8lx ", msg16[i]); - if ((i & 7) == 7) printf("\n"); - } - printf("a, b, c, d, e = %08lx %08lx %08lx %08lx %08lx\n", - a, b, c, d, e); - printf("Partial hash = %08lx %08lx %08lx %08lx %08lx\n", - (long)ctx->partial_hash[0], (long)ctx->partial_hash[1], - (long)ctx->partial_hash[2], (long)ctx->partial_hash[3], - (long)ctx->partial_hash[4]); -#endif } // end SHA1_block -void SHA1Hash::SHA1Init(SHA1_CTX *ctx) + +/* + Initialize the hash context. +*/ +static void SHA1Init(SHA1_CTX *ctx) { ctx->nbit_total[0] = ctx->nbit_total[1] = 0; - for (DWORD i = 0; i != 16; i++) { + for (uint32_t i = 0; i != 16; i++) { ctx->awaiting_data[i] = 0; } - /* - Initialize hash variables. - - */ - ctx->partial_hash[0] = 0x67452301u; ctx->partial_hash[1] = 0xefcdab89u; ctx->partial_hash[2] = ~ctx->partial_hash[0]; @@ -215,22 +193,22 @@ void SHA1Hash::SHA1Init(SHA1_CTX *ctx) } -void SHA1Hash::SHA1Update( - SHA1_CTX * ctx, // IN/OUT - const BYTE * msg, // IN - DWORD nbyte) // IN /* Append data to a partially hashed SHA-1 message. */ +static void SHA1Update( + SHA1_CTX * ctx, // IN/OUT + const uint8_t * msg, // IN + uint32_t nbyte) // IN { - const BYTE *fresh_data = msg; - DWORD nbyte_left = nbyte; - DWORD nbit_occupied = ctx->nbit_total[0] & 511; - DWORD *awaiting_data; - DWORDC nbitnew_low = SHAVE32(8*nbyte); + const uint8_t *fresh_data = msg; + uint32_t nbyte_left = nbyte; + uint32_t nbit_occupied = ctx->nbit_total[0] & 511; + uint32_t *awaiting_data; + const uint32_t nbitnew_low = SHAVE32(8*nbyte); - _ASSERTE((nbit_occupied & 7) == 0); // Partial bytes not implemented + assert((nbit_occupied & 7) == 0); // Partial bytes not implemented ctx->nbit_total[0] += nbitnew_low; ctx->nbit_total[1] += (nbyte >> 29) @@ -243,7 +221,7 @@ void SHA1Hash::SHA1Update( while ((nbit_occupied & 31) != 0 && nbyte_left != 0) { nbit_occupied += 8; - *awaiting_data |= (DWORD)*fresh_data++ + *awaiting_data |= (uint32_t)*fresh_data++ << ((-(int)nbit_occupied) & 31); nbyte_left--; // Start at most significant byte } @@ -252,19 +230,19 @@ void SHA1Hash::SHA1Update( /* Transfer 4 bytes at a time */ do { - DWORDC nword_occupied = nbit_occupied/32; - DWORD nwcopy = min(nbyte_left/4, 16 - nword_occupied); - _ASSERTE (nbit_occupied <= 512); - _ASSERTE ((nbit_occupied & 31) == 0 || nbyte_left == 0); + const uint32_t nword_occupied = nbit_occupied/32; + uint32_t nwcopy = MIN(nbyte_left/4, 16 - nword_occupied); + assert (nbit_occupied <= 512); + assert ((nbit_occupied & 31) == 0 || nbyte_left == 0); awaiting_data = ctx->awaiting_data + nword_occupied; nbyte_left -= 4*nwcopy; nbit_occupied += 32*nwcopy; while (nwcopy != 0) { - DWORDC byte0 = (DWORD)fresh_data[0]; - DWORDC byte1 = (DWORD)fresh_data[1]; - DWORDC byte2 = (DWORD)fresh_data[2]; - DWORDC byte3 = (DWORD)fresh_data[3]; + const uint32_t byte0 = (uint32_t)fresh_data[0]; + const uint32_t byte1 = (uint32_t)fresh_data[1]; + const uint32_t byte2 = (uint32_t)fresh_data[2]; + const uint32_t byte3 = (uint32_t)fresh_data[3]; *awaiting_data++ = byte3 | (byte2 << 8) | (byte1 << 16) | (byte0 << 24); /* Big endian */ @@ -276,43 +254,41 @@ void SHA1Hash::SHA1Update( SHA1_block(ctx); nbit_occupied = 0; awaiting_data -= 16; - _ASSERTE(awaiting_data == ctx->awaiting_data); + assert(awaiting_data == ctx->awaiting_data); } } while (nbyte_left >= 4); - _ASSERTE (ctx->awaiting_data + nbit_occupied/32 + assert (ctx->awaiting_data + nbit_occupied/32 == awaiting_data); while (nbyte_left != 0) { - DWORDC new_byte = (DWORD)*fresh_data++; + const uint32_t new_byte = (uint32_t)*fresh_data++; - _ASSERTE((nbit_occupied & 31) <= 16); + assert((nbit_occupied & 31) <= 16); nbit_occupied += 8; *awaiting_data |= new_byte << ((-(int)nbit_occupied) & 31); nbyte_left--; } - _ASSERTE (nbit_occupied == (ctx->nbit_total[0] & 511)); -} // end SHA1Update - - + assert (nbit_occupied == (ctx->nbit_total[0] & 511)); +} -void SHA1Hash::SHA1Final( - SHA1_CTX * ctx, // IN/OUT - BYTE * digest) // OUT /* Finish a SHA-1 hash. */ +static void SHA1Final( + SHA1_CTX * ctx, // IN/OUT + uint8_t * digest) // OUT { - DWORDC nbit0 = ctx->nbit_total[0]; - DWORDC nbit1 = ctx->nbit_total[1]; - DWORD nbit_occupied = nbit0 & 511; - DWORD i; + const uint32_t nbit0 = ctx->nbit_total[0]; + const uint32_t nbit1 = ctx->nbit_total[1]; + uint32_t nbit_occupied = nbit0 & 511; + uint32_t i; - _ASSERTE((nbit_occupied & 7) == 0); + assert((nbit_occupied & 7) == 0); ctx->awaiting_data[nbit_occupied/32] - |= (DWORD)0x80 << ((-8-nbit_occupied) & 31); + |= (uint32_t)0x80 << ((-8-nbit_occupied) & 31); // Append a 1 bit nbit_occupied += 8; @@ -332,37 +308,29 @@ void SHA1Hash::SHA1Final( /* Copy final digest to user-supplied byte array */ for (i = 0; i != 5; i++) { - DWORDC dwi = ctx->partial_hash[i]; - digest[4*i + 0] = (BYTE)((dwi >> 24) & 255); - digest[4*i + 1] = (BYTE)((dwi >> 16) & 255); - digest[4*i + 2] = (BYTE)((dwi >> 8) & 255); - digest[4*i + 3] = (BYTE)(dwi & 255); // Big-endian + const uint32_t dwi = ctx->partial_hash[i]; + digest[4*i + 0] = (uint8_t)((dwi >> 24) & 255); + digest[4*i + 1] = (uint8_t)((dwi >> 16) & 255); + digest[4*i + 2] = (uint8_t)((dwi >> 8) & 255); + digest[4*i + 3] = (uint8_t)(dwi & 255); // Big-endian } -} // end SHA1Final - -SHA1Hash::SHA1Hash() -{ - m_fFinalized = FALSE; - SHA1Init(&m_Context); -} - -void SHA1Hash::AddData(BYTE *pbData, DWORD cbData) -{ - if (m_fFinalized) - return; - - SHA1Update(&m_Context, pbData, cbData); } -// Retrieve a pointer to the final hash. -BYTE *SHA1Hash::GetHash() +void minipal_sha1(const void *data, size_t length, uint8_t *hash, size_t hashBufferLength) { - if (m_fFinalized) - return m_Value; - - SHA1Final(&m_Context, m_Value); - - m_fFinalized = TRUE; - - return m_Value; + assert(hashBufferLength >= SHA1_HASH_SIZE); + SHA1_CTX ctx; + SHA1Init(&ctx); + if (length > UINT32_MAX) + { + SHA1Update(&ctx, data, UINT32_MAX); + data = (uint8_t*)data + UINT32_MAX; + length -= UINT32_MAX; + SHA1Update(&ctx, data, (uint32_t)length); + } + else + { + SHA1Update(&ctx, data, (uint32_t)length); + } + SHA1Final(&ctx, hash); } diff --git a/src/native/minipal/sha1.h b/src/native/minipal/sha1.h new file mode 100644 index 0000000000000..d0e4d01718d6d --- /dev/null +++ b/src/native/minipal/sha1.h @@ -0,0 +1,28 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// + +#ifndef MINIPAL_SHA1_H_ +#define MINIPAL_SHA1_H_ + +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif // __cplusplus + +#define SHA1_HASH_SIZE 20 // Number of bytes output by SHA-1 + +// Fill the hash buffer with the SHA-1 hash of the data. +// This function should only be used for non-cryptographic purposes. +// Cryptographic usages should need to use the platform-native SHA-1 implementations +// for security reasons. +void minipal_sha1(const void *data, size_t length, uint8_t *hash, size_t hashBufferLength); + +#ifdef __cplusplus +} +#endif // __cplusplus + +#endif // SHA1_H_