Fix parsing of embedded .clsidmap in comhost (#40022)
authorElinor Fung <47805090+elinor-fung@users.noreply.github.com>
Tue, 28 Jul 2020 22:57:17 +0000 (15:57 -0700)
committerGitHub <noreply@github.com>
Tue, 28 Jul 2020 22:57:17 +0000 (15:57 -0700)
* Fix parsing of embedded .clsidmap in comhost
* Update tests to use HostModel to create/embed clsidmap

src/installer/corehost/cli/comhost/clsidmap.cpp
src/installer/corehost/cli/json_parser.cpp
src/installer/corehost/cli/json_parser.h
src/installer/tests/Assets/TestProjects/ComLibrary/ComLibrary.csproj
src/installer/tests/HostActivation.Tests/NativeHosting/Comhost.cs

index b119c50..7d740b9 100644 (file)
@@ -43,16 +43,8 @@ namespace
         return S_OK;
     }
 
-    clsid_map parse_stream(_Inout_ pal::istream_t &json_map_raw)
+    clsid_map get_map_from_json(_In_ const json_parser_t &json)
     {
-        json_parser_t json;
-
-        if (!json.parse_stream(json_map_raw, _X("<embedded .clsidmap>")))
-        {
-            trace::error(_X("Embedded .clsidmap format is invalid"));
-            throw HResultException{ StatusCode::InvalidConfigFile };
-        }
-
         // Process JSON and construct a map
         HRESULT hr;
         clsid_map mapping;
@@ -86,16 +78,6 @@ namespace
         return mapping;
     }
 
-    class memory_buffer : public std::basic_streambuf<pal::istream_t::char_type>
-    {
-    public:
-        memory_buffer(_In_ DWORD dataInBytes, _In_reads_bytes_(dataInBytes) void *data)
-        {
-            auto raw_begin = reinterpret_cast<pal::istream_t::char_type*>(data);
-            setg(raw_begin, raw_begin, raw_begin + (dataInBytes / sizeof(pal::istream_t::char_type)));
-        }
-    };
-
     clsid_map get_json_map_from_resource(bool &found_resource)
     {
         found_resource = false;
@@ -117,9 +99,14 @@ namespace
         if (data == nullptr)
             throw HResultException{ E_UNEXPECTED }; // This should never happen in Windows 7+
 
-        memory_buffer resourceBuffer{ size, data };
-        pal::istream_t stream{ &resourceBuffer };
-        return parse_stream(stream);
+        json_parser_t json;
+        if (!json.parse_raw_data(reinterpret_cast<char*>(data), size, _X("<embedded .clsidmap>")))
+        {
+            trace::error(_X("Embedded .clsidmap format is invalid"));
+            throw HResultException{ StatusCode::InvalidConfigFile };
+        }
+
+        return get_map_from_json(json);
     }
 
     bool is_binary_unsigned(const pal::string_t &path)
@@ -190,8 +177,14 @@ namespace
         if (!pal::file_exists(map_file_name))
             return {};
 
-        pal::ifstream_t file{ map_file_name };
-        return parse_stream(file);
+        json_parser_t json;
+        if (!json.parse_file(map_file_name))
+        {
+            trace::error(_X("File .clsidmap format is invalid"));
+            throw HResultException{ StatusCode::InvalidConfigFile };
+        }
+
+        return get_map_from_json(json);
     }
 }
 
index 3953de1..740b714 100644 (file)
@@ -74,8 +74,10 @@ void json_parser_t::realloc_buffer(size_t size)
     m_json[size] = '\0';
 }
 
-bool json_parser_t::parse_json(char* data, int64_t size, const pal::string_t& context)
+bool json_parser_t::parse_raw_data(char* data, int64_t size, const pal::string_t& context)
 {
+    assert(data != nullptr);
+
     constexpr auto flags = rapidjson::ParseFlag::kParseStopWhenDoneFlag | rapidjson::ParseFlag::kParseCommentsFlag;
 #ifdef _WIN32
     // Can't use in-situ parsing on Windows, as JSON data is encoded in
@@ -109,26 +111,6 @@ bool json_parser_t::parse_json(char* data, int64_t size, const pal::string_t& co
     return true;
 }
 
-bool json_parser_t::parse_stream(pal::istream_t& stream,
-                                 const pal::string_t& context)
-{
-    if (!stream.good())
-    {
-        trace::error(_X("Cannot use stream for resource [%s]: %s"), context.c_str(), pal::strerror(errno));
-        return false;
-    }
-
-    auto current_pos = ::get_utf8_bom_length(stream);
-    stream.seekg(0, stream.end);
-    auto stream_size = stream.tellg();
-    stream.seekg(current_pos, stream.beg);
-
-    realloc_buffer(stream_size - current_pos);
-    stream.read(m_json.data(), stream_size - current_pos);
-
-    return parse_json(m_json.data(), m_json.size(), context);
-}
-
 bool json_parser_t::parse_file(const pal::string_t& path)
 {
     // This code assumes that the caller has checked that the file `path` exists
@@ -145,13 +127,33 @@ bool json_parser_t::parse_file(const pal::string_t& path)
 
         if (m_bundle_data != nullptr)
         {
-            bool result = parse_json(m_bundle_data, m_bundle_location->size, path);
+            bool result = parse_raw_data(m_bundle_data, m_bundle_location->size, path);
             return result;
         }
     }
 
     pal::ifstream_t file{ path };
-    return parse_stream(file, path);
+    if (!file.good())
+    {
+        trace::error(_X("Cannot use file stream for [%s]: %s"), path.c_str(), pal::strerror(errno));
+        return false;
+    }
+
+    auto current_pos = ::get_utf8_bom_length(file);
+    file.seekg(0, file.end);
+    auto stream_size = file.tellg();
+    if (stream_size == -1)
+    {
+        trace::error(_X("Failed to get size of file [%s]"), path.c_str());
+        return false;
+    }
+
+    file.seekg(current_pos, file.beg);
+
+    realloc_buffer(stream_size - current_pos);
+    file.read(m_json.data(), stream_size - current_pos);
+
+    return parse_raw_data(m_json.data(), m_json.size(), path);
 }
 
 json_parser_t::~json_parser_t()
index 350ea14..f4be31d 100644 (file)
@@ -30,7 +30,7 @@ class json_parser_t {
 
         const document_t& document() const { return m_document; }
 
-        bool parse_stream(pal::istream_t& stream, const pal::string_t& context);
+        bool parse_raw_data(char* data, int64_t size, const pal::string_t& context);
         bool parse_file(const pal::string_t& path);
 
         json_parser_t()
@@ -52,7 +52,6 @@ class json_parser_t {
         const bundle::location_t* m_bundle_location; // Location of this json file within the bundle.
 
         void realloc_buffer(size_t size);
-        bool parse_json(char* data, int64_t size, const pal::string_t& context);
 };
 
 #endif // __JSON_PARSER_H__
index 637cbf4..27861fa 100644 (file)
@@ -3,6 +3,7 @@
   <PropertyGroup>
     <TargetFramework>$(NetCoreAppCurrent)</TargetFramework>
     <RuntimeFrameworkVersion>$(MNAVersion)</RuntimeFrameworkVersion>
+    <GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
   </PropertyGroup>
 
 </Project>
index ddf80fe..7c6f597 100644 (file)
@@ -1,10 +1,12 @@
 // Licensed to the .NET Foundation under one or more agreements.
 // The .NET Foundation licenses this file to you under the MIT license.
 
-using Microsoft.DotNet.Cli.Build.Framework;
-using Newtonsoft.Json.Linq;
 using System.IO;
+using System.Reflection.Metadata;
 using System.Runtime.InteropServices;
+
+using Microsoft.DotNet.Cli.Build.Framework;
+using Microsoft.NET.HostModel.ComHost;
 using Xunit;
 
 namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.NativeHosting
@@ -136,26 +138,26 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.NativeHosting
                     .EnsureRestored(RepoDirectories.CorehostPackages)
                     .BuildProject();
 
-                // [TODO] BEGIN - Remove once using .NET Core 3.0 to build tests
-                ComHostPath = Path.Combine(
-                    ComLibraryFixture.TestProject.BuiltApp.Location,
-                    $"{ ComLibraryFixture.TestProject.AssemblyName }.comhost.dll");
-
-                File.Copy(Path.Combine(RepoDirectories.CorehostPackages, "comhost.dll"), ComHostPath);
-
-                RuntimeConfig.FromFile(ComLibraryFixture.TestProject.RuntimeConfigJson)
-                    .WithFramework(new RuntimeConfig.Framework("Microsoft.NETCore.App", RepoDirectories.MicrosoftNETCoreAppVersion))
-                    .Save();
-
-                JObject clsidMap = new JObject()
+                // Create a .clsidmap from the assembly
+                string clsidMapPath = Path.Combine(BaseDirectory, $"{ ComLibraryFixture.TestProject.AssemblyName }.clsidmap");
+                using (var assemblyStream = new FileStream(ComLibraryFixture.TestProject.AppDll, FileMode.Open, FileAccess.Read, FileShare.Delete | FileShare.Read))
+                using (var peReader = new System.Reflection.PortableExecutable.PEReader(assemblyStream))
                 {
+                    if (peReader.HasMetadata)
                     {
-                        ClsidString,
-                        new JObject() { {"assembly", "ComLibrary" }, {"type", "ComLibrary.Server" } }
+                        MetadataReader reader = peReader.GetMetadataReader();
+                        ClsidMap.Create(reader, clsidMapPath);
                     }
-                };
-                File.WriteAllText($"{ ComHostPath }.clsidmap", clsidMap.ToString());
-                // [TODO] END - Remove once using .NET Core 3.0 to build tests
+                }
+
+                // Use the locally built comhost to create a comhost with the embedded .clsidmap 
+                ComHostPath = Path.Combine(
+                    ComLibraryFixture.TestProject.BuiltApp.Location,
+                    $"{ ComLibraryFixture.TestProject.AssemblyName }.comhost.dll");
+                ComHost.Create(
+                    Path.Combine(RepoDirectories.CorehostPackages, "comhost.dll"),
+                    ComHostPath,
+                    clsidMapPath);
             }
 
             protected override void Dispose(bool disposing)