#include <stdarg.h>
+#define TO_XML_CHAR(X) reinterpret_cast<const unsigned char *>(X)
+#define FROM_XML_CHAR(X) reinterpret_cast<const char *>(X)
+
+using namespace llvm;
+
namespace llvm {
char WindowsManifestError::ID = 0;
void WindowsManifestError::log(raw_ostream &OS) const { OS << Msg; }
+static bool xmlStringsEqual(const unsigned char *A, const unsigned char *B) {
+ return strcmp(FROM_XML_CHAR(A), FROM_XML_CHAR(B)) == 0;
+}
+
+bool isMergeableElement(const unsigned char *ElementName) {
+ for (StringRef S : {"application", "assembly", "assemblyIdentity",
+ "compatibility", "noInherit", "requestedExecutionLevel",
+ "requestedPrivileges", "security", "trustInfo"}) {
+ if (S == FROM_XML_CHAR(ElementName))
+ return true;
+ }
+ return false;
+}
+
+XMLNodeImpl getChildWithName(XMLNodeImpl Parent,
+ const unsigned char *ElementName) {
+#if LLVM_LIBXML2_ENABLED
+ for (XMLNodeImpl 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
+ for (xmlAttrPtr Attribute = Node->properties; Attribute != nullptr;
+ Attribute = Attribute->next) {
+ if (xmlStringsEqual(Attribute->name, AttributeName))
+ return Attribute->children->content;
+ }
+#endif
+ return nullptr;
+}
+
+Error mergeAttributes(XMLNodeImpl OriginalNode, XMLNodeImpl AdditionalNode) {
+#if LLVM_LIBXML2_ENABLED
+ for (xmlAttrPtr Attribute = AdditionalNode->properties; Attribute != nullptr;
+ Attribute = Attribute->next) {
+ if (const unsigned char *OriginalValue =
+ 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))
+ return make_error<WindowsManifestError>(
+ Twine("conflicting attributes for ") +
+ FROM_XML_CHAR(OriginalNode->name));
+ } 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));
+ }
+ }
+#endif
+ return Error::success();
+}
+
+Error treeMerge(XMLNodeImpl OriginalRoot, XMLNodeImpl AdditionalRoot) {
+#if LLVM_LIBXML2_ENABLED
+ XMLNodeImpl AdditionalFirstChild = AdditionalRoot->children;
+ for (XMLNodeImpl Child = AdditionalFirstChild; Child; Child = Child->next) {
+ XMLNodeImpl OriginalChildWithName;
+ if (!isMergeableElement(Child->name) ||
+ !(OriginalChildWithName =
+ getChildWithName(OriginalRoot, Child->name))) {
+ XMLNodeImpl NewChild = xmlCopyNode(Child, 1);
+ if (!NewChild)
+ return make_error<WindowsManifestError>(Twine("error when copying ") +
+ FROM_XML_CHAR(Child->name));
+ if (NewChild->ns)
+ xmlFreeNs(NewChild->ns); // xmlCopyNode explicitly defines default
+ // namespace, undo this here.
+ if (!xmlAddChild(OriginalRoot, NewChild))
+ return make_error<WindowsManifestError>(Twine("could not merge ") +
+ FROM_XML_CHAR(NewChild->name));
+ } 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) {
+ 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);
+ } else {
+ StoreNext.next = Child->next;
+ XMLNodeImpl Remove = Child;
+ Child = &StoreNext;
+ xmlUnlinkNode(Remove);
+ xmlFreeNode(Remove);
+ }
+ }
+}
+
+WindowsManifestMerger::~WindowsManifestMerger() {
+#if LLVM_LIBXML2_ENABLED
+ for (auto &Doc : MergedDocs)
+ xmlFreeDoc(Doc);
+#endif
+}
+
Error WindowsManifestMerger::merge(const MemoryBuffer &Manifest) {
#if LLVM_LIBXML2_ENABLED
+ if (Manifest.getBufferSize() == 0)
+ return make_error<WindowsManifestError>(
+ "attempted to merge empty manifest");
xmlSetGenericErrorFunc((void *)this, WindowsManifestMerger::errorCallback);
XMLDocumentImpl ManifestXML =
xmlReadMemory(Manifest.getBufferStart(), Manifest.getBufferSize(),
- "manifest.xml", nullptr, 0);
+ "manifest.xml", nullptr, XML_PARSE_NOBLANKS);
xmlSetGenericErrorFunc(nullptr, nullptr);
if (auto E = getParseError())
return E;
- CombinedRoot = xmlDocGetRootElement(ManifestXML);
+ XMLNodeImpl AdditionalRoot = xmlDocGetRootElement(ManifestXML);
+ stripCommentsAndText(AdditionalRoot);
+ if (CombinedDoc == nullptr) {
+ CombinedDoc = ManifestXML;
+ } else {
+ XMLNodeImpl CombinedRoot = xmlDocGetRootElement(CombinedDoc);
+ if (xmlStringsEqual(CombinedRoot->name, AdditionalRoot->name) &&
+ isMergeableElement(AdditionalRoot->name)) {
+ if (auto E = treeMerge(CombinedRoot, AdditionalRoot)) {
+ return E;
+ }
+ } else {
+ XMLNodeImpl NewChild = xmlCopyNode(AdditionalRoot, 1);
+ if (!NewChild)
+ return make_error<WindowsManifestError>("could not copy manifest");
+ if (!xmlAddChild(CombinedRoot, NewChild))
+ return make_error<WindowsManifestError>("could not append manifest");
+ }
+ }
+ MergedDocs.push_back(ManifestXML);
#endif
return Error::success();
}
#if LLVM_LIBXML2_ENABLED
unsigned char *XmlBuff;
int BufferSize = 0;
- if (CombinedRoot) {
+ if (CombinedDoc) {
std::unique_ptr<xmlDoc> OutputDoc(xmlNewDoc((const unsigned char *)"1.0"));
- xmlDocSetRootElement(OutputDoc.get(), CombinedRoot);
- xmlDocDumpMemory(OutputDoc.get(), &XmlBuff, &BufferSize);
+ xmlDocSetRootElement(OutputDoc.get(), xmlDocGetRootElement(CombinedDoc));
+ xmlKeepBlanksDefault(0);
+ xmlDocDumpFormatMemory(OutputDoc.get(), &XmlBuff, &BufferSize, 1);
}
if (BufferSize == 0)
return nullptr;
return MemoryBuffer::getMemBuffer(
- StringRef(reinterpret_cast<const char *>(XmlBuff), (size_t)BufferSize));
+ StringRef(FROM_XML_CHAR(XmlBuff), (size_t)BufferSize));
#else
return nullptr;
#endif
--- /dev/null
+REQUIRES: libxml2
+UNSUPPORTED: windows
+
+RUN: llvm-mt /manifest %p/Inputs/test_manifest.manifest /manifest \
+RUN: %p/Inputs/additional.manifest /out:%t
+RUN: FileCheck %s -input-file=%t
+
+CHECK: <?xml version="1.0"?>
+CHECK-NEXT: <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
+CHECK-NEXT: <trustInfo>
+CHECK-NEXT: <security>
+CHECK-NEXT: <requestedPrivileges>
+CHECK-NEXT: <requestedExecutionLevel level="3" uiAccess="1" admin="false"/>
+CHECK-NEXT: </requestedPrivileges>
+CHECK-NEXT: </security>
+CHECK-NEXT: </trustInfo>
+CHECK-NEXT: <dependency>
+CHECK-NEXT: <dependentAssembly>
+CHECK-NEXT: <assemblyIdentity program="displayDriver"/>
+CHECK-NEXT: </dependentAssembly>
+CHECK-NEXT: </dependency>
+CHECK-NEXT: <compatibility>
+CHECK-NEXT: <dependency>
+CHECK-NEXT: <assemblyIdentity program="compatibilityCheck1"/>
+CHECK-NEXT: </dependency>
+CHECK-NEXT: <application>
+CHECK-NEXT: <supportedOS Id="FooOS"/>
+CHECK-NEXT: <supportedOS Id="BarOS"/>
+CHECK-NEXT: </application>
+CHECK-NEXT: <dependency>
+CHECK-NEXT: <assemblyIdentity program="compatibilityCheck2"/>
+CHECK-NEXT: </dependency>
+CHECK-NEXT: </compatibility>
+CHECK-NEXT: <dependency>
+CHECK-NEXT: <dependentAssembly>
+CHECK-NEXT: <assemblyIdentity program="logDisplay"/>
+CHECK-NEXT: </dependentAssembly>
+CHECK-NEXT: </dependency>
+CHECK-NEXT: </assembly>