Index: lld/COFF/CMakeLists.txt =================================================================== --- lld/COFF/CMakeLists.txt +++ lld/COFF/CMakeLists.txt @@ -39,6 +39,7 @@ Target Option Support + WindowsManifest LINK_LIBS lldCore Index: lld/COFF/DriverUtils.cpp =================================================================== --- lld/COFF/DriverUtils.cpp +++ lld/COFF/DriverUtils.cpp @@ -20,6 +20,7 @@ #include "Symbols.h" #include "llvm/ADT/Optional.h" #include "llvm/ADT/StringSwitch.h" +#include "llvm/ADT/Triple.h" #include "llvm/BinaryFormat/COFF.h" #include "llvm/Object/COFF.h" #include "llvm/Object/WindowsResource.h" @@ -32,6 +33,7 @@ #include "llvm/Support/Process.h" #include "llvm/Support/Program.h" #include "llvm/Support/raw_ostream.h" +#include "llvm/WindowsManifest/WindowsManifestMerger.h" #include using namespace llvm::COFF; @@ -312,16 +314,9 @@ }; } -// Create the default manifest file as a temporary file. -TemporaryFile createDefaultXml() { - // Create a temporary file. - TemporaryFile File("defaultxml", "manifest"); - - // Open the temporary file for writing. - std::error_code EC; - raw_fd_ostream OS(File.Path, EC, sys::fs::F_Text); - if (EC) - fatal(EC, "failed to open " + File.Path); +static std::string createDefaultXml() { + std::string Ret; + raw_string_ostream OS(Ret); // Emit the XML. Note that we do *not* verify that the XML attributes are // syntactically correct. This is intentional for link.exe compatibility. @@ -346,37 +341,73 @@ << " \n"; } OS << "\n"; - OS.close(); - return File; -} - -static std::string readFile(StringRef Path) { - std::unique_ptr MB = - check(MemoryBuffer::getFile(Path), "could not open " + Path); - return MB->getBuffer(); + return Ret; } static std::string createManifestXml() { - // Create the default manifest file. - TemporaryFile File1 = createDefaultXml(); + std::string DefaultXml = createDefaultXml(); if (Config->ManifestInput.empty()) - return readFile(File1.Path); + return DefaultXml; + + std::unique_ptr OutputBuffer; // If manifest files are supplied by the user using /MANIFESTINPUT - // option, we need to merge them with the default manifest. - TemporaryFile File2("user", "manifest"); + // option, we need to merge them with the default manifest. If libxml2 + // is enabled, we may merge them with LLVM's own library. + + std::unique_ptr DefaultXmlCopy = + MemoryBuffer::getMemBufferCopy(DefaultXml); + + windows_manifest::WindowsManifestMerger Merger; + if (auto E = Merger.merge(*DefaultXmlCopy.get())) { + fatal(E, "failed to merge default manifest"); + } + + for (StringRef Filename : Config->ManifestInput) { + std::unique_ptr Manifest = + check(MemoryBuffer::getFile(Filename)); + if (auto E = Merger.merge(*Manifest.get())) + fatal(E, "failed to merge " + Filename); + } + + OutputBuffer = Merger.getMergedManifest(); + + // Using built-in library failed, possibly because libxml2 is not installed. + // Shell out to mt.exe instead. + if (!OutputBuffer) { + const Triple HostTriple(Triple::normalize(LLVM_HOST_TRIPLE)); + if (!HostTriple.isOSWindows()) { + warn("manifest ignored because no manifest merging tool available"); + return std::string(); + } + // Create the default manifest file as a temporary file. + TemporaryFile Default("defaultxml", "manifest"); + std::error_code EC; + raw_fd_ostream OS(Default.Path, EC, sys::fs::F_Text); + if (EC) + fatal(EC, "failed to open " + Default.Path); + OS << DefaultXml; + OS.close(); + + // Merge user-supplied manifests if they are given. Since libxml2 is not + // enabled, we must shell out to Microsoft's mt.exe tool. + TemporaryFile User("user", "manifest"); Executor E("mt.exe"); E.add("/manifest"); - E.add(File1.Path); + E.add(Default.Path); for (StringRef Filename : Config->ManifestInput) { E.add("/manifest"); E.add(Filename); } E.add("/nologo"); - E.add("/out:" + StringRef(File2.Path)); + E.add("/out:" + StringRef(User.Path)); E.run(); - return readFile(File2.Path); + + OutputBuffer = + check(MemoryBuffer::getFile(User.Path), "could not open " + User.Path); + } + return OutputBuffer->getBuffer(); } static std::unique_ptr Index: lld/test/COFF/manifestinput.test =================================================================== --- lld/test/COFF/manifestinput.test +++ lld/test/COFF/manifestinput.test @@ -1,4 +1,4 @@ -# REQUIRES: win_mt +# REQUIRES: manifest_tool # RUN: yaml2obj %p/Inputs/ret42.yaml > %t.obj # RUN: lld-link /out:%t.exe /entry:main \ Index: lld/test/lit.cfg =================================================================== --- lld/test/lit.cfg +++ lld/test/lit.cfg @@ -265,6 +265,7 @@ config.environment['LLD_VERSION'] = 'LLD 1.0' # Indirectly check if the mt.exe Microsoft utility exists by searching for -# cvtres, which always accompanies it. -if lit.util.which('cvtres', config.environment['PATH']): - config.available_features.add('win_mt') +# cvtres, which always accompanies it. Alternatively, check if we can use +# libxml2 to merge manifests. +if (lit.util.which('cvtres', config.environment['PATH'])) or (config.llvm_libxml2_enabled == "1"): + config.available_features.add('manifest_tool') Index: lld/test/lit.site.cfg.in =================================================================== --- lld/test/lit.site.cfg.in +++ lld/test/lit.site.cfg.in @@ -4,6 +4,7 @@ config.llvm_obj_root = "@LLVM_BINARY_DIR@" config.llvm_tools_dir = "@LLVM_TOOLS_DIR@" config.llvm_libs_dir = "@LLVM_LIBS_DIR@" +config.llvm_libxml2_enabled = "@LLVM_LIBXML2_ENABLED@" config.lit_tools_dir = "@LLVM_LIT_TOOLS_DIR@" config.lld_obj_root = "@LLD_BINARY_DIR@" config.lld_libs_dir = "@LLVM_LIBRARY_OUTPUT_INTDIR@" Index: llvm/include/llvm/WindowsManifest/WindowsManifestMerger.h =================================================================== --- llvm/include/llvm/WindowsManifest/WindowsManifestMerger.h +++ llvm/include/llvm/WindowsManifest/WindowsManifestMerger.h @@ -29,21 +29,11 @@ #include "llvm/Config/config.h" #include "llvm/Support/Error.h" -#if LLVM_LIBXML2_ENABLED -#include -#endif - namespace llvm { class MemoryBuffer; -#if LLVM_LIBXML2_ENABLED -typedef xmlDocPtr XMLDocumentImpl; -typedef xmlNodePtr XMLNodeImpl; -#else -typedef void *XMLDocumentImpl; -typedef void *XMLNodeImpl; -#endif +namespace windows_manifest { class WindowsManifestError : public ErrorInfo { public: @@ -56,25 +46,19 @@ }; class WindowsManifestMerger { +private: + class WindowsManifestMergerImpl; + std::unique_ptr Impl; public: + WindowsManifestMerger(); ~WindowsManifestMerger(); - Error merge(const MemoryBuffer &Manifest); // Returns vector containing merged xml manifest, or uninitialized vector for // empty manifest. std::unique_ptr getMergedManifest(); - -private: - static void errorCallback(void *Ctx, const char *Format, ...); - Error getParseError(); - -#if LLVM_LIBXML2_ENABLED - XMLDocumentImpl CombinedDoc = nullptr; - std::vector MergedDocs; -#endif - bool ParseErrorOccurred = false; }; +} // namespace windows_manifest } // namespace llvm #endif Index: llvm/lib/WindowsManifest/WindowsManifestMerger.cpp =================================================================== --- llvm/lib/WindowsManifest/WindowsManifestMerger.cpp +++ llvm/lib/WindowsManifest/WindowsManifestMerger.cpp @@ -14,14 +14,15 @@ #include "llvm/WindowsManifest/WindowsManifestMerger.h" #include "llvm/Support/MemoryBuffer.h" -#include +#if LLVM_LIBXML2_ENABLED +#include +#endif #define TO_XML_CHAR(X) reinterpret_cast(X) #define FROM_XML_CHAR(X) reinterpret_cast(X) using namespace llvm; - -namespace llvm { +using namespace windows_manifest; char WindowsManifestError::ID = 0; @@ -29,13 +30,42 @@ void WindowsManifestError::log(raw_ostream &OS) const { OS << Msg; } +class WindowsManifestMerger::WindowsManifestMergerImpl { +public: + ~WindowsManifestMergerImpl(); + Error merge(const MemoryBuffer &Manifest); + std::unique_ptr getMergedManifest(); +private: + static void errorCallback(void *Ctx, const char *Format, ...); + Error getParseError(); #if LLVM_LIBXML2_ENABLED + xmlDocPtr CombinedDoc = nullptr; + std::vector MergedDocs; +#endif + bool ParseErrorOccurred = false; +}; + +#if LLVM_LIBXML2_ENABLED + +static const std::pair + MtNsHrefsPrefixes[] = { + {"urn:schemas-microsoft-com:asm.v1", "ms_asmv1"}, + {"urn:schemas-microsoft-com:asm.v2", "ms_asmv2"}, + {"urn:schemas-microsoft-com:asm.v3", "ms_asmv3"}, + {"http://schemas.microsoft.com/SMI/2005/WindowsSettings", + "ms_windowsSettings"}, + {"urn:schemas-microsoft-com:compatibility.v1", "ms_compatibilityv1"}}; + static bool xmlStringsEqual(const unsigned char *A, const unsigned char *B) { + // Handle null pointers. Comparison of 2 null pointers returns true because + // this indicates the prefix of a default namespace. + if (!A || !B) + return A == B; + return strcmp(FROM_XML_CHAR(A), FROM_XML_CHAR(B)) == 0; } -#endif -bool isMergeableElement(const unsigned char *ElementName) { +static bool isMergeableElement(const unsigned char *ElementName) { for (StringRef S : {"application", "assembly", "assemblyIdentity", "compatibility", "noInherit", "requestedExecutionLevel", "requestedPrivileges", "security", "trustInfo"}) { @@ -45,121 +75,561 @@ return false; } -XMLNodeImpl getChildWithName(XMLNodeImpl Parent, +static xmlNodePtr getChildWithName(xmlNodePtr Parent, const unsigned char *ElementName) { -#if LLVM_LIBXML2_ENABLED - for (XMLNodeImpl Child = Parent->children; Child; Child = Child->next) + for (xmlNodePtr Child = Parent->children; Child; Child = Child->next) if (xmlStringsEqual(Child->name, ElementName)) { return Child; } -#endif return nullptr; } -const unsigned char *getAttribute(XMLNodeImpl Node, - const unsigned char *AttributeName) { -#if LLVM_LIBXML2_ENABLED +static xmlAttrPtr getAttribute(xmlNodePtr Node, const unsigned char *AttributeName) { for (xmlAttrPtr Attribute = Node->properties; Attribute != nullptr; Attribute = Attribute->next) { if (xmlStringsEqual(Attribute->name, AttributeName)) - return Attribute->children->content; + return Attribute; } -#endif return nullptr; } -Error mergeAttributes(XMLNodeImpl OriginalNode, XMLNodeImpl AdditionalNode) { -#if LLVM_LIBXML2_ENABLED - for (xmlAttrPtr Attribute = AdditionalNode->properties; Attribute != nullptr; +// Check if namespace specified by HRef1 overrides that of HRef2. +static bool namespaceOverrides(const unsigned char *HRef1, + const unsigned char *HRef2) { + auto HRef1Position = std::find_if( + std::begin(MtNsHrefsPrefixes), std::end(MtNsHrefsPrefixes), + [HRef1](const std::pair &element) { + return xmlStringsEqual(HRef1, TO_XML_CHAR(element.first.c_str())); + }); + if (HRef1Position == std::end(MtNsHrefsPrefixes)) + return false; + auto HRef2Position = std::find_if( + std::begin(MtNsHrefsPrefixes), std::end(MtNsHrefsPrefixes), + [HRef2](const std::pair &element) { + return xmlStringsEqual(HRef2, TO_XML_CHAR(element.first.c_str())); + }); + if (HRef2Position == std::end(MtNsHrefsPrefixes)) + return true; + int HRef1Priority = std::distance(HRef1Position, std::end(MtNsHrefsPrefixes)); + int HRef2Priority = std::distance(HRef2Position, std::end(MtNsHrefsPrefixes)); + if (HRef1Priority > HRef2Priority) + return true; + return false; +} + +// Search for prefix-defined namespace specified by HRef, starting on Node and +// continuing recurisvely upwards. Returns the namespace or nullptr if not +// found. +static xmlNsPtr search(const unsigned char *HRef, xmlNodePtr Node) { + for (xmlNsPtr Def = Node->nsDef; Def; Def = Def->next) { + if (Def->prefix && xmlStringsEqual(Def->href, HRef)) + return Def; + } + if (Node->parent) { + return search(HRef, Node->parent); + } + return nullptr; +} + +// Return the prefix that corresponds to the HRef. If HRef is not a recognized +// URI, then just return the HRef itself to use as the prefix. +static const unsigned char *getPrefixForHref(const unsigned char *HRef) { + for (auto &Ns : MtNsHrefsPrefixes) { + if (xmlStringsEqual(HRef, TO_XML_CHAR(Ns.first.c_str()))) { + return TO_XML_CHAR(Ns.second.c_str()); + } + } + return HRef; +} + +// Search for prefix-defined namespace specified by HRef, starting on Node and +// continuing recurisvely upwards. If it is found, then return it. If it is +// not found, then prefix-define that namespace on the node and return a +// reference to it. +static Expected searchOrDefine(const unsigned char *HRef, + xmlNodePtr Node) { + xmlNsPtr Def = search(HRef, Node); + if (Def) + return Def; + Def = xmlNewNs(Node, HRef, getPrefixForHref(HRef)); + if (!Def) + return make_error("failed to create new namespace"); + return Def; +} + +// Set the namespace of OrigionalAttribute on OriginalNode to be that of +// AdditionalAttribute's. +static Error copyAttributeNamespace(xmlAttrPtr OriginalAttribute, + xmlNodePtr OriginalNode, + xmlAttrPtr AdditionalAttribute) { + + Expected ExplicitOrError = + searchOrDefine(AdditionalAttribute->ns->href, OriginalNode); + if (!ExplicitOrError) + return ExplicitOrError.takeError(); + OriginalAttribute->ns = std::move(ExplicitOrError.get()); + return Error::success(); +} + +// Return the corresponding namespace definition for the prefix, defined on the +// given Node. Returns nullptr if there is no such definition. +static xmlNsPtr getNamespaceWithPrefix(const unsigned char *Prefix, + xmlNodePtr Node) { + + if (Node == nullptr) + return nullptr; + for (xmlNsPtr Def = Node->nsDef; Def; Def = Def->next) { + if (xmlStringsEqual(Def->prefix, Prefix)) + return Def; + } + return nullptr; +} + +// Search for the closest inheritable default namespace, starting on (and +// including) the Node and traveling upwards through parent nodes. Returns +// nullptr if there are no inheritable default namespaces. +static xmlNsPtr getClosestDefault(xmlNodePtr Node) { + if (xmlNsPtr Ret = getNamespaceWithPrefix(nullptr, Node)) + return Ret; + if (Node->parent == nullptr) + return nullptr; + return getClosestDefault(Node->parent); +} + +// Merge the attributes of AdditionalNode into OriginalNode. If attributes +// with identical types are present, they are not duplicated but rather if +// their values are not consistent and error is thrown. In addition, the +// higher priority namespace is used for each attribute, EXCEPT in the case +// of merging two default namespaces and the lower priority namespace +// definition occurs closer than the higher priority one. +static Error mergeAttributes(xmlNodePtr OriginalNode, xmlNodePtr AdditionalNode) { + xmlNsPtr ClosestDefault = getClosestDefault(OriginalNode); + for (xmlAttrPtr Attribute = AdditionalNode->properties; Attribute; Attribute = Attribute->next) { - if (const unsigned char *OriginalValue = + if (xmlAttrPtr OriginalAttribute = getAttribute(OriginalNode, Attribute->name)) { - // Attributes of the same name must also have the same value. Otherwise - // an error is thrown. - if (!xmlStringsEqual(OriginalValue, Attribute->children->content)) + if (!xmlStringsEqual(OriginalAttribute->children->content, + Attribute->children->content)) return make_error( Twine("conflicting attributes for ") + FROM_XML_CHAR(OriginalNode->name)); + if (!Attribute->ns) { + continue; + } + if (!OriginalAttribute->ns) { + if (auto E = copyAttributeNamespace(OriginalAttribute, OriginalNode, + Attribute)) + return E; + // In this case, the original attribute has a higher priority namespace + // than the incomiing attribute, however the namespace definition of + // the lower priority namespace occurs first traveling upwards in the + // tree. Therefore the lower priority namespace is applied. + } else if (namespaceOverrides(OriginalAttribute->ns->href, + Attribute->ns->href)) { + if (!OriginalAttribute->ns->prefix && !Attribute->ns->prefix && + ClosestDefault && + xmlStringsEqual(Attribute->ns->href, ClosestDefault->href)) { + if (auto E = copyAttributeNamespace(OriginalAttribute, OriginalNode, + Attribute)) + return E; + } + // This covers the case where the incoming attribute has the higher + // priority. The higher priority namespace is applied in all cases + // EXCEPT when both of the namespaces are default inherited, and the + // closest inherited default is the lower priority one. + } else if (Attribute->ns->prefix || OriginalAttribute->ns->prefix || + (ClosestDefault && + !xmlStringsEqual(OriginalAttribute->ns->href, + ClosestDefault->href))) { + if (auto E = copyAttributeNamespace(OriginalAttribute, OriginalNode, + Attribute)) + return E; + } + // If the incoming attribute is not already found on the node, append it + // to the end of the properties list. Also explicitly apply its + // namespace as a prefix because it might be contained in a separate + // namespace that doesn't use the attribute. } else { - char *NameCopy = strdup(FROM_XML_CHAR(Attribute->name)); - char *ContentCopy = strdup(FROM_XML_CHAR(Attribute->children->content)); - xmlNewProp(OriginalNode, TO_XML_CHAR(NameCopy), TO_XML_CHAR(ContentCopy)); + xmlAttrPtr NewProp = xmlNewProp(OriginalNode, Attribute->name, + Attribute->children->content); + Expected ExplicitOrError = + searchOrDefine(Attribute->ns->href, OriginalNode); + if (!ExplicitOrError) + return ExplicitOrError.takeError(); + NewProp->ns = std::move(ExplicitOrError.get()); } } -#endif return Error::success(); } -Error treeMerge(XMLNodeImpl OriginalRoot, XMLNodeImpl AdditionalRoot) { -#if LLVM_LIBXML2_ENABLED - XMLNodeImpl AdditionalFirstChild = AdditionalRoot->children; +// Given two nodes, return the one with the higher priority namespace. +static xmlNodePtr getDominantNode(xmlNodePtr Node1, xmlNodePtr Node2) { + + if (!Node1 || !Node1->ns) + return Node2; + else if (!Node2 || !Node2->ns) + return Node1; + else if (namespaceOverrides(Node1->ns->href, Node2->ns->href)) + return Node1; + else + return Node2; +} + +// Checks if this Node's namespace is inherited or one it defined itself. +static bool hasInheritedNs(xmlNodePtr Node) { + if (Node->ns && Node->ns != getNamespaceWithPrefix(Node->ns->prefix, Node)) + return true; + else + return false; +} + +// Check if this Node's namespace is a default namespace that it inherited, as +// opposed to defining itself. +static bool hasInheritedDefaultNs(xmlNodePtr Node) { + if (hasInheritedNs(Node) && Node->ns->prefix == nullptr) + return true; + else + return false; +} + +// Check if this Node's namespace is a default namespace it defined itself. +static bool hasDefinedDefaultNamespace(xmlNodePtr Node) { + if (Node->ns && (Node->ns == getNamespaceWithPrefix(nullptr, Node))) + return true; + else + return false; +} + +// For the given explicit prefix-definition of a namespace, travel downwards +// from a node recursively, and for every implicit, inherited default usage of +// that namespace replace it with that explicit prefix use. This is important +// when namespace overriding occurs when merging, so that elements unique to a +// namespace will still stay in that namespace. +static void explicateNamespace(xmlNsPtr PrefixDef, xmlNodePtr Node) { + // If a node as its own default namespace definition it clearly cannot have + // inherited the given default namespace, and neither will any of its + // children. + if (hasDefinedDefaultNamespace(Node)) + return; + if (Node->ns && xmlStringsEqual(Node->ns->href, PrefixDef->href) && + hasInheritedDefaultNs(Node)) + Node->ns = PrefixDef; + for (xmlAttrPtr Attribute = Node->properties; Attribute; + Attribute = Attribute->next) { + if (Attribute->ns && + xmlStringsEqual(Attribute->ns->href, PrefixDef->href)) { + Attribute->ns = PrefixDef; + } + } + for (xmlNodePtr Child = Node->children; Child; Child = Child->next) { + explicateNamespace(PrefixDef, Child); + } +} + +// Perform the namespace merge between two nodes. +static Error mergeNamespaces(xmlNodePtr OriginalNode, xmlNodePtr AdditionalNode) { + // Save the original default namespace definition in case the incoming node + // overrides it. + const unsigned char *OriginalDefinedDefaultHref = nullptr; + if (xmlNsPtr OriginalDefinedDefaultNs = + getNamespaceWithPrefix(nullptr, OriginalNode)) { + OriginalDefinedDefaultHref = xmlStrdup(OriginalDefinedDefaultNs->href); + } + const unsigned char *NewDefinedDefaultHref = nullptr; + // Copy all namespace definitions. There can only be one default namespace + // definition per node, so the higher priority one takes precedence in the + // case of collision. + for (xmlNsPtr Def = AdditionalNode->nsDef; Def; Def = Def->next) { + if (xmlNsPtr OriginalNsDef = + getNamespaceWithPrefix(Def->prefix, OriginalNode)) { + if (!Def->prefix) { + if (namespaceOverrides(Def->href, OriginalNsDef->href)) { + NewDefinedDefaultHref = TO_XML_CHAR(strdup(FROM_XML_CHAR(Def->href))); + } + } else if (!xmlStringsEqual(OriginalNsDef->href, Def->href)) { + return make_error( + Twine("conflicting namespace definitions for ") + + FROM_XML_CHAR(Def->prefix)); + } + } else { + xmlNsPtr NewDef = xmlCopyNamespace(Def); + NewDef->next = OriginalNode->nsDef; + OriginalNode->nsDef = NewDef; + } + } + + // Check whether the original node or the incoming node has the higher + // priority namespace. Depending on which one is dominant, we will have + // to recursively apply namespace changes down to children of the original + // node. + xmlNodePtr DominantNode = getDominantNode(OriginalNode, AdditionalNode); + xmlNodePtr NonDominantNode = + DominantNode == OriginalNode ? AdditionalNode : OriginalNode; + if (DominantNode == OriginalNode) { + if (OriginalDefinedDefaultHref) { + xmlNsPtr NonDominantDefinedDefault = + getNamespaceWithPrefix(nullptr, NonDominantNode); + // In this case, both the nodes defined a default namespace. However + // the lower priority node ended up having a higher priority default + // definition. This can occur if the higher priority node is prefix + // namespace defined. In this case we have to define an explicit + // prefix for the overridden definition and apply it to all children + // who relied on that definition. + if (NonDominantDefinedDefault && + namespaceOverrides(NonDominantDefinedDefault->href, + OriginalDefinedDefaultHref)) { + Expected EC = + searchOrDefine(OriginalDefinedDefaultHref, DominantNode); + if (!EC) + return EC.takeError(); + xmlNsPtr PrefixDominantDefinedDefault = std::move(EC.get()); + explicateNamespace(PrefixDominantDefinedDefault, DominantNode); + } + // In this case the node with a higher priority namespace did not have a + // default namespace definition, but the lower priority node did. In this + // case the new default namespace definition is copied. A side effect of + // this is that all children will suddenly find themselves in a different + // default namespace. To maintain correctness we need to ensure that all + // children now explicitly refer to the namespace that they had previously + // implicitly inherited. + } else if (getNamespaceWithPrefix(nullptr, NonDominantNode)) { + if (DominantNode->parent) { + xmlNsPtr ClosestDefault = getClosestDefault(DominantNode->parent); + Expected EC = + searchOrDefine(ClosestDefault->href, DominantNode); + if (!EC) + return EC.takeError(); + xmlNsPtr ExplicitDefault = std::move(EC.get()); + explicateNamespace(ExplicitDefault, DominantNode); + } + } + } else { + // Covers case where the incoming node has a default namespace definition + // that overrides the original node's namespace. This always leads to + // the original node receiving that new default namespace. + if (hasDefinedDefaultNamespace(DominantNode)) { + NonDominantNode->ns = getNamespaceWithPrefix(nullptr, NonDominantNode); + } else { + // This covers the case where the incoming node either has a prefix + // namespace, or an inherited default namespace. Since the namespace + // may not yet be defined in the original tree we do a searchOrDefine + // for it, and then set the namespace equal to it. + Expected EC = + searchOrDefine(DominantNode->ns->href, NonDominantNode); + if (!EC) + return EC.takeError(); + xmlNsPtr Explicit = std::move(EC.get()); + NonDominantNode->ns = Explicit; + } + // This covers cases where the incoming dominant node HAS a default + // namespace definition, but MIGHT NOT NECESSARILY be in that namespace. + if (xmlNsPtr DominantDefaultDefined = + getNamespaceWithPrefix(nullptr, DominantNode)) { + if (OriginalDefinedDefaultHref) { + if (namespaceOverrides(DominantDefaultDefined->href, + OriginalDefinedDefaultHref)) { + // In this case, the incoming node's default definition overrides + // the original default definition, all children who relied on that + // definition must be updated accordingly. + Expected EC = + searchOrDefine(OriginalDefinedDefaultHref, NonDominantNode); + if (!EC) + return EC.takeError(); + xmlNsPtr ExplicitDefault = std::move(EC.get()); + explicateNamespace(ExplicitDefault, NonDominantNode); + } + } else { + // The original did not define a default definition, however the new + // default definition still applies to all children, so they must be + // updated to explicitly refer to the namespace they had previously + // been inheriting implicitly. + xmlNsPtr ClosestDefault = getClosestDefault(NonDominantNode); + Expected EC = + searchOrDefine(ClosestDefault->href, NonDominantNode); + if (!EC) + return EC.takeError(); + xmlNsPtr ExplicitDefault = std::move(EC.get()); + explicateNamespace(ExplicitDefault, NonDominantNode); + } + } + } + if (NewDefinedDefaultHref) { + xmlNsPtr OriginalNsDef = getNamespaceWithPrefix(nullptr, OriginalNode); + xmlFree(const_cast(OriginalNsDef->href)); + OriginalNsDef->href = NewDefinedDefaultHref; + } + xmlFree(const_cast(OriginalDefinedDefaultHref)); + return Error::success(); +} + +static bool isRecognizedNamespace(const unsigned char *NsHref) { + for (auto &Ns : MtNsHrefsPrefixes) { + if (xmlStringsEqual(NsHref, TO_XML_CHAR(Ns.first.c_str()))) + return true; + } + return false; +} + +static bool hasRecognizedNamespace(xmlNodePtr Node) { + return isRecognizedNamespace(Node->ns->href); +} + +// Ensure a node's inherited namespace is actually defined in the tree it +// resides in. +static Error reconcileNamespaces(xmlNodePtr Node) { + if (Node == nullptr) + return Error::success(); + if (hasInheritedNs(Node)) { + Expected ExplicitOrError = searchOrDefine(Node->ns->href, Node); + if (!ExplicitOrError) + return ExplicitOrError.takeError(); + xmlNsPtr Explicit = std::move(ExplicitOrError.get()); + Node->ns = Explicit; + } + for (xmlNodePtr Child = Node->children; Child; Child = Child->next) { + if (auto E = reconcileNamespaces(Child)) + return E; + } + return Error::success(); +} + +// Recursively merge the two given manifest trees, depending on which elements +// are of a mergeable type, and choose namespaces according to which have +// higher priority. +static Error treeMerge(xmlNodePtr OriginalRoot, xmlNodePtr AdditionalRoot) { + if (auto E = mergeAttributes(OriginalRoot, AdditionalRoot)) + return E; + if (auto E = mergeNamespaces(OriginalRoot, AdditionalRoot)) + return E; + xmlNodePtr AdditionalFirstChild = AdditionalRoot->children; xmlNode StoreNext; - for (XMLNodeImpl Child = AdditionalFirstChild; Child; Child = Child->next) { - XMLNodeImpl OriginalChildWithName; + for (xmlNodePtr Child = AdditionalFirstChild; Child; Child = Child->next) { + xmlNodePtr OriginalChildWithName; if (!isMergeableElement(Child->name) || !(OriginalChildWithName = - getChildWithName(OriginalRoot, Child->name))) { + getChildWithName(OriginalRoot, Child->name)) || + !hasRecognizedNamespace(Child)) { StoreNext.next = Child->next; xmlUnlinkNode(Child); if (!xmlAddChild(OriginalRoot, Child)) return make_error(Twine("could not merge ") + FROM_XML_CHAR(Child->name)); + if (auto E = reconcileNamespaces(Child)) + return E; Child = &StoreNext; } else if (auto E = treeMerge(OriginalChildWithName, Child)) { return E; } } - if (auto E = mergeAttributes(OriginalRoot, AdditionalRoot)) - return E; -#endif return Error::success(); } -void stripCommentsAndText(XMLNodeImpl Root) { -#if LLVM_LIBXML2_ENABLED +static void stripComments(xmlNodePtr Root) { xmlNode StoreNext; - for (XMLNodeImpl Child = Root->children; Child; Child = Child->next) { - if (!xmlStringsEqual(Child->name, TO_XML_CHAR("text")) && - !xmlStringsEqual(Child->name, TO_XML_CHAR("comment"))) { - stripCommentsAndText(Child); + for (xmlNodePtr Child = Root->children; Child; Child = Child->next) { + if (!xmlStringsEqual(Child->name, TO_XML_CHAR("comment"))) { + stripComments(Child); } else { StoreNext.next = Child->next; - XMLNodeImpl Remove = Child; + xmlNodePtr Remove = Child; Child = &StoreNext; xmlUnlinkNode(Remove); xmlFreeNode(Remove); } } -#endif } -WindowsManifestMerger::~WindowsManifestMerger() { -#if LLVM_LIBXML2_ENABLED +// The libxml2 assumes that attributes do not inherit default namespaces, +// whereas the original mt.exe does make this assumption. This function +// reconciles this by setting all attributes to have the inherited default +// namespace. +static void setAttributeNamespaces(xmlNodePtr Node) { + for (xmlAttrPtr Attribute = Node->properties; Attribute; + Attribute = Attribute->next) { + if (!Attribute->ns) { + Attribute->ns = getClosestDefault(Node); + } + } + for (xmlNodePtr Child = Node->children; Child; Child = Child->next) { + setAttributeNamespaces(Child); + } +} + +// The merging process may create too many prefix defined namespaces. This +// function removes all unnecessary ones from the tree. +static void checkAndStripPrefixes(xmlNodePtr Node, + std::vector &RequiredPrefixes) { + for (xmlNodePtr Child = Node->children; Child; Child = Child->next) { + checkAndStripPrefixes(Child, RequiredPrefixes); + } + if (Node->ns && Node->ns->prefix != nullptr) { + xmlNsPtr ClosestDefault = getClosestDefault(Node); + if (ClosestDefault && + xmlStringsEqual(ClosestDefault->href, Node->ns->href)) { + Node->ns = ClosestDefault; + } else if (std::find(RequiredPrefixes.begin(), RequiredPrefixes.end(), + Node->ns) == RequiredPrefixes.end()) { + RequiredPrefixes.push_back(Node->ns); + } + } + for (xmlAttrPtr Attribute = Node->properties; Attribute; + Attribute = Attribute->next) { + if (Attribute->ns && Attribute->ns->prefix != nullptr) { + xmlNsPtr ClosestDefault = getClosestDefault(Node); + if (ClosestDefault && + xmlStringsEqual(ClosestDefault->href, Attribute->ns->href)) { + Attribute->ns = ClosestDefault; + } else if (std::find(RequiredPrefixes.begin(), RequiredPrefixes.end(), + Node->ns) == RequiredPrefixes.end()) { + RequiredPrefixes.push_back(Attribute->ns); + } + } + } + xmlNsPtr Prev; + xmlNs Temp; + for (xmlNsPtr Def = Node->nsDef; Def; Def = Def->next) { + if (Def->prefix && + (std::find(RequiredPrefixes.begin(), RequiredPrefixes.end(), Def) == + RequiredPrefixes.end())) { + if (Def == Node->nsDef) + Node->nsDef = Def->next; + else + Prev->next = Def->next; + Temp.next = Def->next; + xmlFreeNs(Def); + Def = &Temp; + } else { + Prev = Def; + } + } +} + +WindowsManifestMerger::WindowsManifestMergerImpl::~WindowsManifestMergerImpl() { for (auto &Doc : MergedDocs) xmlFreeDoc(Doc); -#endif } -Error WindowsManifestMerger::merge(const MemoryBuffer &Manifest) { -#if LLVM_LIBXML2_ENABLED +Error WindowsManifestMerger::WindowsManifestMergerImpl::merge(const MemoryBuffer &Manifest) { if (Manifest.getBufferSize() == 0) return make_error( "attempted to merge empty manifest"); - xmlSetGenericErrorFunc((void *)this, WindowsManifestMerger::errorCallback); - XMLDocumentImpl ManifestXML = + xmlSetGenericErrorFunc((void *)this, WindowsManifestMergerImpl::errorCallback); + xmlDocPtr ManifestXML = xmlReadMemory(Manifest.getBufferStart(), Manifest.getBufferSize(), "manifest.xml", nullptr, XML_PARSE_NOBLANKS); xmlSetGenericErrorFunc(nullptr, nullptr); if (auto E = getParseError()) return E; - XMLNodeImpl AdditionalRoot = xmlDocGetRootElement(ManifestXML); - stripCommentsAndText(AdditionalRoot); + xmlNodePtr AdditionalRoot = xmlDocGetRootElement(ManifestXML); + stripComments(AdditionalRoot); + setAttributeNamespaces(AdditionalRoot); if (CombinedDoc == nullptr) { CombinedDoc = ManifestXML; } else { - XMLNodeImpl CombinedRoot = xmlDocGetRootElement(CombinedDoc); + xmlNodePtr CombinedRoot = xmlDocGetRootElement(CombinedDoc); if (xmlStringsEqual(CombinedRoot->name, AdditionalRoot->name) && - isMergeableElement(AdditionalRoot->name)) { + isMergeableElement(AdditionalRoot->name) && + hasRecognizedNamespace(AdditionalRoot)) { if (auto E = treeMerge(CombinedRoot, AdditionalRoot)) { return E; } @@ -168,38 +638,57 @@ } } MergedDocs.push_back(ManifestXML); -#endif return Error::success(); } -std::unique_ptr WindowsManifestMerger::getMergedManifest() { -#if LLVM_LIBXML2_ENABLED +std::unique_ptr WindowsManifestMerger::WindowsManifestMergerImpl::getMergedManifest() { unsigned char *XmlBuff; int BufferSize = 0; if (CombinedDoc) { + xmlNodePtr CombinedRoot = xmlDocGetRootElement(CombinedDoc); + std::vector RequiredPrefixes; + checkAndStripPrefixes(CombinedRoot, RequiredPrefixes); std::unique_ptr OutputDoc(xmlNewDoc((const unsigned char *)"1.0")); - xmlDocSetRootElement(OutputDoc.get(), xmlDocGetRootElement(CombinedDoc)); + xmlDocSetRootElement(OutputDoc.get(), CombinedRoot); xmlKeepBlanksDefault(0); - xmlDocDumpFormatMemory(OutputDoc.get(), &XmlBuff, &BufferSize, 1); + xmlDocDumpFormatMemoryEnc(OutputDoc.get(), &XmlBuff, &BufferSize, "UTF-8", + 1); } if (BufferSize == 0) return nullptr; return MemoryBuffer::getMemBuffer( StringRef(FROM_XML_CHAR(XmlBuff), (size_t)BufferSize)); +} + #else + +WindowsManifestMerger::WindowsManifestMergerImpl::~WindowsManifestMergerImpl() {} + +Error WindowsManifestMerger::WindowsManifestMergerImpl::merge(const MemoryBuffer &Manifest) { + return Error::success(); +} + +std::unique_ptr WindowsManifestMerger::WindowsManifestMergerImpl::getMergedManifest() { return nullptr; -#endif } -void WindowsManifestMerger::errorCallback(void *Ctx, const char *Format, ...) { - auto *Merger = (WindowsManifestMerger *)Ctx; +#endif + +WindowsManifestMerger::WindowsManifestMerger() : Impl(make_unique()) {} + +WindowsManifestMerger::~WindowsManifestMerger() {} + +Error WindowsManifestMerger::merge(const MemoryBuffer &Manifest) { return Impl->merge(Manifest); } + +std::unique_ptr WindowsManifestMerger::getMergedManifest() { return Impl->getMergedManifest(); } + +void WindowsManifestMerger::WindowsManifestMergerImpl::errorCallback(void *Ctx, const char *Format, ...) { + auto *Merger = (WindowsManifestMergerImpl *)Ctx; Merger->ParseErrorOccurred = true; } -Error WindowsManifestMerger::getParseError() { +Error WindowsManifestMerger::WindowsManifestMergerImpl::getParseError() { if (!ParseErrorOccurred) return Error::success(); return make_error("invalid xml document"); } - -} // namespace llvm Index: llvm/test/tools/llvm-mt/Inputs/assembly_identity.manifest =================================================================== --- /dev/null +++ llvm/test/tools/llvm-mt/Inputs/assembly_identity.manifest @@ -0,0 +1,8 @@ + + + + + + + + Index: llvm/test/tools/llvm-mt/Inputs/compatibility.manifest =================================================================== --- /dev/null +++ llvm/test/tools/llvm-mt/Inputs/compatibility.manifest @@ -0,0 +1,9 @@ + + + + + + + + + Index: llvm/test/tools/llvm-mt/Inputs/expected_big.manifest =================================================================== --- /dev/null +++ llvm/test/tools/llvm-mt/Inputs/expected_big.manifest @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + true/pm + + + Index: llvm/test/tools/llvm-mt/Inputs/test_manifest.manifest =================================================================== --- llvm/test/tools/llvm-mt/Inputs/test_manifest.manifest +++ llvm/test/tools/llvm-mt/Inputs/test_manifest.manifest @@ -1,4 +1,4 @@ - + Index: llvm/test/tools/llvm-mt/Inputs/trust_and_identity.manifest =================================================================== --- /dev/null +++ llvm/test/tools/llvm-mt/Inputs/trust_and_identity.manifest @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + Index: llvm/test/tools/llvm-mt/Inputs/trust_info.manifest =================================================================== --- /dev/null +++ llvm/test/tools/llvm-mt/Inputs/trust_info.manifest @@ -0,0 +1,9 @@ + + + + + + + + + Index: llvm/test/tools/llvm-mt/Inputs/windows_settings.manifest =================================================================== --- /dev/null +++ llvm/test/tools/llvm-mt/Inputs/windows_settings.manifest @@ -0,0 +1,8 @@ + + + + + true/pm + + + Index: llvm/test/tools/llvm-mt/big_merge.test =================================================================== --- /dev/null +++ llvm/test/tools/llvm-mt/big_merge.test @@ -0,0 +1,39 @@ +REQUIRES: libxml2 +UNSUPPORTED: windows + +RUN: llvm-mt /manifest %p/Inputs/trust_info.manifest /manifest \ +RUN: %p/Inputs/assembly_identity.manifest /manifest \ +RUN: %p/Inputs/trust_and_identity.manifest /manifest \ +RUN: %p/Inputs/compatibility.manifest /manifest \ +RUN: %p/Inputs/windows_settings.manifest /out:%t +RUN: FileCheck %s -input-file=%t + +CHECK: +CHECK-NEXT: +CHECK-NEXT: +CHECK-NEXT: +CHECK-NEXT: +CHECK-NEXT: +CHECK-NEXT: +CHECK-NEXT: +CHECK-NEXT: +CHECK-NEXT: +CHECK-NEXT: +CHECK-NEXT: +CHECK-NEXT: +CHECK-NEXT: +CHECK-NEXT: +CHECK-NEXT: +CHECK-NEXT: +CHECK-NEXT: +CHECK-NEXT: +CHECK-NEXT: +CHECK-NEXT: +CHECK-NEXT: +CHECK-NEXT: +CHECK-NEXT: +CHECK-NEXT: +CHECK-NEXT: true/pm +CHECK-NEXT: +CHECK-NEXT: +CHECK-NEXT: Index: llvm/test/tools/llvm-mt/simple_merge.test =================================================================== --- llvm/test/tools/llvm-mt/simple_merge.test +++ llvm/test/tools/llvm-mt/simple_merge.test @@ -5,7 +5,7 @@ RUN: %p/Inputs/additional.manifest /out:%t RUN: FileCheck %s -input-file=%t -CHECK: +CHECK: CHECK-NEXT: CHECK-NEXT: CHECK-NEXT: Index: llvm/test/tools/llvm-mt/single_file.test =================================================================== --- llvm/test/tools/llvm-mt/single_file.test +++ llvm/test/tools/llvm-mt/single_file.test @@ -4,7 +4,7 @@ RUN: llvm-mt /manifest %p/Inputs/test_manifest.manifest /out:%t RUN: FileCheck %s --input-file=%t -CHECK: +CHECK: CHECK-NEXT: CHECK-NEXT: CHECK-NEXT: Index: llvm/tools/llvm-mt/Opts.td =================================================================== --- llvm/tools/llvm-mt/Opts.td +++ llvm/tools/llvm-mt/Opts.td @@ -1,7 +1,8 @@ include "llvm/Option/OptParser.td" +def supported : OptionGroup<"supported">; def unsupported : OptionGroup<"unsupported">; -def manifest : Separate<["/", "-"], "manifest">, HelpText<"Used to specify each manifest that need to be processed">, MetaVarName<"manifest">; +def manifest : Separate<["/", "-"], "manifest">, HelpText<"Used to specify each manifest that need to be processed">, MetaVarName<"manifest">, Group; def identity : Joined<["/", "-"], "identity:">, HelpText<"Not supported">, MetaVarName<"identity">, Group; def rgs : Joined<["/", "-"], "rgs:">, HelpText<"Not supported">, MetaVarName<"script">, Group; def tlb : Joined<["/", "-"], "tlb:">, HelpText<"Not supported">, MetaVarName<"file">, Group; @@ -10,8 +11,8 @@ def managed_assembly_name : Joined<["/", "-"], "managedassemblyname:">, HelpText<"Not supported">, MetaVarName<"assembly">, Group; def no_dependency : Flag<["/", "-"], "nodependency">, HelpText<"Not supported">, Group; def category : Flag<["/", "-"], "category">, HelpText<"Not supported">, Group; -def no_logo : Flag<["/", "-"], "nologo">, HelpText<"No effect as this tool never writes copyright data. Included for parity">; -def out : Joined<["/", "-"], "out:">, HelpText<"Name of the output manifest. If this is skipped and only one manifest is being operated upon by the tool, that manifest is modified in place">, MetaVarName<"manifest">; +def no_logo : Flag<["/", "-"], "nologo">, HelpText<"No effect as this tool never writes copyright data. Included for parity">, Group; +def out : Joined<["/", "-"], "out:">, HelpText<"Name of the output manifest. If this is skipped and only one manifest is being operated upon by the tool, that manifest is modified in place">, MetaVarName<"manifest">, Group; def input_resource : Joined<["/", "-"], "inputresource:">, HelpText<"Not supported">, MetaVarName<"file">, Group; def output_resource : Joined<["/", "-"], "outputresource:">, HelpText<"Not supported">, MetaVarName<"file">, Group; def output_resource_flag : Flag<["/", "-"], "outputresource">, Alias, HelpText<"Not supported">, Group; @@ -24,6 +25,6 @@ def check_for_duplicates : Flag<["/", "-"], "check_for_duplicates:">, HelpText<"Not supported">, Group; def make_cdfs : Flag<["/", "-"], "makecdfs:">, HelpText<"Not supported">, Group; def verbose : Flag<["/", "-"], "verbose">, HelpText<"Not supported">, Group; -def help : Flag<["/", "-"], "?">; -def help_long : Flag<["/", "-"], "help">, Alias; -def h : Flag<["/", "-"], "h">, Alias; +def help : Flag<["/", "-"], "?">, Group; +def help_long : Flag<["/", "-"], "help">, Alias, Group; +def h : Flag<["/", "-"], "h">, Alias, Group; Index: llvm/tools/llvm-mt/llvm-mt.cpp =================================================================== --- llvm/tools/llvm-mt/llvm-mt.cpp +++ llvm/tools/llvm-mt/llvm-mt.cpp @@ -102,6 +102,13 @@ ArrayRef ArgsArr = makeArrayRef(argv + 1, argc); opt::InputArgList InputArgs = T.ParseArgs(ArgsArr, MAI, MAC); + for (auto &Arg : InputArgs) { + if (!(Arg->getOption().matches(OPT_unsupported) || + Arg->getOption().matches(OPT_supported))) { + reportError(Twine("invalid option ") + Arg->getSpelling()); + } + } + for (auto &Arg : InputArgs) { if (Arg->getOption().matches(OPT_unsupported)) { outs() << "llvm-mt: ignoring unsupported '" << Arg->getOption().getName() @@ -129,7 +136,7 @@ reportError("no output file specified"); } - WindowsManifestMerger Merger; + windows_manifest::WindowsManifestMerger Merger; for (const auto &File : InputFiles) { ErrorOr> ManifestOrErr =