[llvm] 47369e1 - [yaml2obj][obj2yaml] - Teach tools to work with regular archives.

Georgii Rymar via llvm-commits llvm-commits at lists.llvm.org
Wed Oct 28 05:27:40 PDT 2020


Author: Georgii Rymar
Date: 2020-10-28T15:27:11+03:00
New Revision: 47369e194a48dbe3cf04a7845e23e25e43e973f0

URL: https://github.com/llvm/llvm-project/commit/47369e194a48dbe3cf04a7845e23e25e43e973f0
DIFF: https://github.com/llvm/llvm-project/commit/47369e194a48dbe3cf04a7845e23e25e43e973f0.diff

LOG: [yaml2obj][obj2yaml] - Teach tools to work with regular archives.

This teaches obj2yaml to dump valid regular (not thin) archives.
This also teaches yaml2obj to recognize archives YAML descriptions,
what allows to craft all different kinds of archives (valid and broken ones).

Differential revision: https://reviews.llvm.org/D89949

Added: 
    llvm/include/llvm/ObjectYAML/ArchiveYAML.h
    llvm/lib/ObjectYAML/ArchiveEmitter.cpp
    llvm/lib/ObjectYAML/ArchiveYAML.cpp
    llvm/test/tools/obj2yaml/Archives/regular.yaml
    llvm/test/tools/yaml2obj/Archives/regular.yaml
    llvm/tools/obj2yaml/archive2yaml.cpp

Modified: 
    llvm/include/llvm/ObjectYAML/ObjectYAML.h
    llvm/include/llvm/ObjectYAML/yaml2obj.h
    llvm/lib/ObjectYAML/CMakeLists.txt
    llvm/lib/ObjectYAML/ObjectYAML.cpp
    llvm/lib/ObjectYAML/yaml2obj.cpp
    llvm/tools/obj2yaml/CMakeLists.txt
    llvm/tools/obj2yaml/obj2yaml.cpp
    llvm/tools/obj2yaml/obj2yaml.h

Removed: 
    


################################################################################
diff  --git a/llvm/include/llvm/ObjectYAML/ArchiveYAML.h b/llvm/include/llvm/ObjectYAML/ArchiveYAML.h
new file mode 100644
index 000000000000..8d05feedcc62
--- /dev/null
+++ b/llvm/include/llvm/ObjectYAML/ArchiveYAML.h
@@ -0,0 +1,77 @@
+//===- ArchiveYAML.h - Archive YAMLIO implementation ------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// This file declares classes for handling the YAML representation of archives.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_OBJECTYAML_ARCHIVEYAML_H
+#define LLVM_OBJECTYAML_ARCHIVEYAML_H
+
+#include "llvm/Support/YAMLTraits.h"
+#include "llvm/ObjectYAML/YAML.h"
+#include "llvm/ADT/MapVector.h"
+
+namespace llvm {
+namespace ArchYAML {
+
+struct Archive {
+  struct Child {
+    struct Field {
+      Field() = default;
+      Field(StringRef Default, unsigned Length)
+          : DefaultValue(Default), MaxLength(Length) {}
+      StringRef Value;
+      StringRef DefaultValue;
+      unsigned MaxLength;
+    };
+
+    Child() {
+      Fields["Name"] = {"", 16};
+      Fields["LastModified"] = {"0", 12};
+      Fields["UID"] = {"0", 6};
+      Fields["GID"] = {"0", 6};
+      Fields["AccessMode"] = {"0", 8};
+      Fields["Size"] = {"0", 10};
+      Fields["Terminator"] = {"`\n", 2};
+    }
+
+    MapVector<StringRef, Field> Fields;
+
+    Optional<yaml::BinaryRef> Content;
+    Optional<llvm::yaml::Hex8> PaddingByte;
+  };
+
+  StringRef Magic;
+  Optional<std::vector<Child>> Members;
+  Optional<yaml::BinaryRef> Content;
+};
+
+} // end namespace ArchYAML
+} // end namespace llvm
+
+LLVM_YAML_IS_SEQUENCE_VECTOR(llvm::ArchYAML::Archive::Child)
+
+namespace llvm {
+namespace yaml {
+
+template <> struct MappingTraits<ArchYAML::Archive> {
+  static void mapping(IO &IO, ArchYAML::Archive &A);
+  static std::string validate(IO &, ArchYAML::Archive &A);
+};
+
+template <> struct MappingTraits<ArchYAML::Archive::Child> {
+  static void mapping(IO &IO, ArchYAML::Archive::Child &C);
+  static std::string validate(IO &, ArchYAML::Archive::Child &C);
+};
+
+} // end namespace yaml
+} // end namespace llvm
+
+#endif // LLVM_OBJECTYAML_ARCHIVEYAML_H

diff  --git a/llvm/include/llvm/ObjectYAML/ObjectYAML.h b/llvm/include/llvm/ObjectYAML/ObjectYAML.h
index 0015fd3dc501..dd26ce3e9703 100644
--- a/llvm/include/llvm/ObjectYAML/ObjectYAML.h
+++ b/llvm/include/llvm/ObjectYAML/ObjectYAML.h
@@ -9,6 +9,7 @@
 #ifndef LLVM_OBJECTYAML_OBJECTYAML_H
 #define LLVM_OBJECTYAML_OBJECTYAML_H
 
+#include "llvm/ObjectYAML/ArchiveYAML.h"
 #include "llvm/ObjectYAML/COFFYAML.h"
 #include "llvm/ObjectYAML/ELFYAML.h"
 #include "llvm/ObjectYAML/MachOYAML.h"
@@ -23,6 +24,7 @@ namespace yaml {
 class IO;
 
 struct YamlObjectFile {
+  std::unique_ptr<ArchYAML::Archive> Arch;
   std::unique_ptr<ELFYAML::Object> Elf;
   std::unique_ptr<COFFYAML::Object> Coff;
   std::unique_ptr<MachOYAML::Object> MachO;

diff  --git a/llvm/include/llvm/ObjectYAML/yaml2obj.h b/llvm/include/llvm/ObjectYAML/yaml2obj.h
index 34def363a55b..1f693475c946 100644
--- a/llvm/include/llvm/ObjectYAML/yaml2obj.h
+++ b/llvm/include/llvm/ObjectYAML/yaml2obj.h
@@ -40,12 +40,17 @@ namespace WasmYAML {
 struct Object;
 }
 
+namespace ArchYAML {
+struct Archive;
+}
+
 namespace yaml {
 class Input;
 struct YamlObjectFile;
 
 using ErrorHandler = llvm::function_ref<void(const Twine &Msg)>;
 
+bool yaml2archive(ArchYAML::Archive &Doc, raw_ostream &Out, ErrorHandler EH);
 bool yaml2coff(COFFYAML::Object &Doc, raw_ostream &Out, ErrorHandler EH);
 bool yaml2elf(ELFYAML::Object &Doc, raw_ostream &Out, ErrorHandler EH,
               uint64_t MaxSize);

diff  --git a/llvm/lib/ObjectYAML/ArchiveEmitter.cpp b/llvm/lib/ObjectYAML/ArchiveEmitter.cpp
new file mode 100644
index 000000000000..a0cf8fe360da
--- /dev/null
+++ b/llvm/lib/ObjectYAML/ArchiveEmitter.cpp
@@ -0,0 +1,51 @@
+//===- ArchiveEmitter.cpp ---------------------------- --------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/ObjectYAML/ArchiveYAML.h"
+#include "llvm/ObjectYAML/yaml2obj.h"
+#include "llvm/Support/Error.h"
+#include "llvm/Support/raw_ostream.h"
+
+using namespace llvm;
+using namespace ArchYAML;
+
+namespace llvm {
+namespace yaml {
+
+bool yaml2archive(ArchYAML::Archive &Doc, raw_ostream &Out, ErrorHandler EH) {
+  Out.write(Doc.Magic.data(), Doc.Magic.size());
+
+  if (Doc.Content) {
+    Doc.Content->writeAsBinary(Out);
+    return true;
+  }
+
+  if (!Doc.Members)
+    return true;
+
+  auto WriteField = [&](StringRef Field, uint8_t Size) {
+    Out.write(Field.data(), Field.size());
+    for (size_t I = Field.size(); I != Size; ++I)
+      Out.write(' ');
+  };
+
+  for (const Archive::Child &C : *Doc.Members) {
+    for (auto &P : C.Fields)
+      WriteField(P.second.Value, P.second.MaxLength);
+
+    if (C.Content)
+      C.Content->writeAsBinary(Out);
+    if (C.PaddingByte)
+      Out.write(*C.PaddingByte);
+  }
+
+  return true;
+}
+
+} // namespace yaml
+} // namespace llvm

diff  --git a/llvm/lib/ObjectYAML/ArchiveYAML.cpp b/llvm/lib/ObjectYAML/ArchiveYAML.cpp
new file mode 100644
index 000000000000..d2ea1eaf5210
--- /dev/null
+++ b/llvm/lib/ObjectYAML/ArchiveYAML.cpp
@@ -0,0 +1,58 @@
+//===- ArchiveYAML.cpp - ELF YAMLIO implementation -------------------- ----===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file defines classes for handling the YAML representation of archives.
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/ObjectYAML/ArchiveYAML.h"
+
+namespace llvm {
+
+namespace yaml {
+
+void MappingTraits<ArchYAML::Archive>::mapping(IO &IO, ArchYAML::Archive &A) {
+  assert(!IO.getContext() && "The IO context is initialized already");
+  IO.setContext(&A);
+  IO.mapTag("!Arch", true);
+  IO.mapOptional("Magic", A.Magic, "!<arch>\n");
+  IO.mapOptional("Members", A.Members);
+  IO.mapOptional("Content", A.Content);
+  IO.setContext(nullptr);
+}
+
+std::string MappingTraits<ArchYAML::Archive>::validate(IO &,
+                                                       ArchYAML::Archive &A) {
+  if (A.Members && A.Content)
+    return "\"Content\" and \"Members\" cannot be used together";
+  return "";
+}
+
+void MappingTraits<ArchYAML::Archive::Child>::mapping(
+    IO &IO, ArchYAML::Archive::Child &E) {
+  assert(IO.getContext() && "The IO context is not initialized");
+  for (auto &P : E.Fields)
+    IO.mapOptional(P.first.data(), P.second.Value, P.second.DefaultValue);
+  IO.mapOptional("Content", E.Content);
+  IO.mapOptional("PaddingByte", E.PaddingByte);
+}
+
+std::string
+MappingTraits<ArchYAML::Archive::Child>::validate(IO &,
+                                                  ArchYAML::Archive::Child &C) {
+  for (auto &P : C.Fields)
+    if (P.second.Value.size() > P.second.MaxLength)
+      return ("the maximum length of \"" + P.first + "\" field is " +
+              Twine(P.second.MaxLength))
+          .str();
+  return "";
+}
+
+} // end namespace yaml
+
+} // end namespace llvm

diff  --git a/llvm/lib/ObjectYAML/CMakeLists.txt b/llvm/lib/ObjectYAML/CMakeLists.txt
index 92ea1ec9866c..a024fa96535e 100644
--- a/llvm/lib/ObjectYAML/CMakeLists.txt
+++ b/llvm/lib/ObjectYAML/CMakeLists.txt
@@ -1,4 +1,6 @@
 add_llvm_component_library(LLVMObjectYAML
+  ArchiveEmitter.cpp
+  ArchiveYAML.cpp
   CodeViewYAMLDebugSections.cpp
   CodeViewYAMLSymbols.cpp
   CodeViewYAMLTypeHashing.cpp

diff  --git a/llvm/lib/ObjectYAML/ObjectYAML.cpp b/llvm/lib/ObjectYAML/ObjectYAML.cpp
index 7f636f4eabac..4564b537c9a1 100644
--- a/llvm/lib/ObjectYAML/ObjectYAML.cpp
+++ b/llvm/lib/ObjectYAML/ObjectYAML.cpp
@@ -33,7 +33,14 @@ void MappingTraits<YamlObjectFile>::mapping(IO &IO,
                                                          *ObjectFile.FatMachO);
   } else {
     Input &In = (Input &)IO;
-    if (IO.mapTag("!ELF")) {
+    if (IO.mapTag("!Arch")) {
+      ObjectFile.Arch.reset(new ArchYAML::Archive());
+      MappingTraits<ArchYAML::Archive>::mapping(IO, *ObjectFile.Arch);
+      std::string Err =
+          MappingTraits<ArchYAML::Archive>::validate(IO, *ObjectFile.Arch);
+      if (!Err.empty())
+        IO.setError(Err);
+    } else if (IO.mapTag("!ELF")) {
       ObjectFile.Elf.reset(new ELFYAML::Object());
       MappingTraits<ELFYAML::Object>::mapping(IO, *ObjectFile.Elf);
     } else if (IO.mapTag("!COFF")) {

diff  --git a/llvm/lib/ObjectYAML/yaml2obj.cpp b/llvm/lib/ObjectYAML/yaml2obj.cpp
index a04345f1294a..ef2ab83dcd24 100644
--- a/llvm/lib/ObjectYAML/yaml2obj.cpp
+++ b/llvm/lib/ObjectYAML/yaml2obj.cpp
@@ -32,6 +32,8 @@ bool convertYAML(yaml::Input &YIn, raw_ostream &Out, ErrorHandler ErrHandler,
       return false;
     }
 
+    if (Doc.Arch)
+      return yaml2archive(*Doc.Arch, Out, ErrHandler);
     if (Doc.Elf)
       return yaml2elf(*Doc.Elf, Out, ErrHandler, MaxSize);
     if (Doc.Coff)

diff  --git a/llvm/test/tools/obj2yaml/Archives/regular.yaml b/llvm/test/tools/obj2yaml/Archives/regular.yaml
new file mode 100644
index 000000000000..8b2969fdb101
--- /dev/null
+++ b/llvm/test/tools/obj2yaml/Archives/regular.yaml
@@ -0,0 +1,158 @@
+## Check how obj2yaml dumps regular archives.
+
+## Check how we dump an empty archive.
+
+# RUN: rm -f %t.empty.a
+# RUN: llvm-ar rc %t.empty.a
+# RUN: obj2yaml %t.empty.a | FileCheck %s --check-prefix=EMPTY
+
+# EMPTY:      --- !Arch
+# EMPTY-NEXT: Members: []
+# EMPTY-NEXT: ...
+
+## Check how we dump archives with multiple members.
+## Check we don't dump excessive spaces when dumping fields.
+## Check we don't dump fields with values that are equal to default values.
+## Check how we dump empty field values.
+
+# RUN: yaml2obj %s --docnum=1 -o %t.multiple.a
+# RUN: obj2yaml %t.multiple.a | FileCheck %s --check-prefix=MULTIPLE
+
+# MULTIPLE:      --- !Arch
+# MULTIPLE-NEXT: Members:
+# MULTIPLE-NEXT:   - Name:         'bbb/'
+# MULTIPLE-NEXT:     LastModified: '1'
+# MULTIPLE-NEXT:     UID:          '2'
+# MULTIPLE-NEXT:     GID:          '3'
+# MULTIPLE-NEXT:     AccessMode:   '644'
+# MULTIPLE-NEXT:     Size:         '6'
+# MULTIPLE-NEXT:     Content:      20616161200A
+# MULTIPLE-NEXT:   - Name:         'dddd/'
+# MULTIPLE-NEXT:     LastModified: '4'
+# MULTIPLE-NEXT:     UID:          '5'
+# MULTIPLE-NEXT:     GID:          '6'
+# MULTIPLE-NEXT:     AccessMode:   '987'
+# MULTIPLE-NEXT:     Size:         '7'
+# MULTIPLE-NEXT:     Content:      2063636363200A
+# MULTIPLE-NEXT:     PaddingByte:  0x0A
+# MULTIPLE-NEXT:   - LastModified: ''
+# MULTIPLE-NEXT:     UID:          ''
+# MULTIPLE-NEXT:     GID:          ''
+# MULTIPLE-NEXT:     AccessMode:   ''
+# MULTIPLE-NEXT:     Terminator:   ''
+# MULTIPLE-NEXT:     Content:      ''
+# MULTIPLE-NEXT:   - {}
+# MULTIPLE-NEXT: ...
+
+--- !Arch
+Members:
+  - Name:         'bbb/'
+    LastModified: '1'
+    UID:          '2'
+    GID:          '3'
+    AccessMode:   '644'
+    Size:         '6'
+    Terminator:   "`\n"
+    Content:      20616161200A ## " aaa \n"
+  - Name:         'dddd/'
+    LastModified: '4'
+    UID:          '5'
+    GID:          '6'
+    AccessMode:   '987'
+    Size:         '7'
+    Terminator:   "`\n"
+    Content:      2063636363200A ## " cccc \n"
+    PaddingByte:  0x0A
+## All fields are empty (where possible).
+  - Name:         ''
+    LastModified: ''
+    UID:          ''
+    GID:          ''
+    AccessMode:   ''
+    Size:         '0'
+    Terminator:   ''
+    Content:      ''
+## All fields are explicitly set to the default values.
+  - Name:         ''
+    LastModified: '0'
+    UID:          '0'
+    GID:          '0'
+    AccessMode:   '0'
+    Size:         '0'
+    Terminator:   "`\n"
+    Content:      ""
+...
+
+## Check we report an error for non-regular archives.
+
+# RUN: yaml2obj %s --docnum=2 -o %t.not.regular.a
+# RUN: not obj2yaml %t.not.regular.a 2>&1 | \
+# RUN:   FileCheck %s -DFILE=%t.not.regular.a --check-prefix=NOT-REGULAR-ERR
+
+# NOT-REGULAR-ERR: Error reading file: [[FILE]]: only regular archives are supported
+
+--- !Arch
+Magic: "!<thin>\n"
+Members:
+  - {}
+
+## Check we report an error when unable to read the header of an archive member.
+
+# RUN: yaml2obj %s --docnum=3 -o %t.truncated.a
+# RUN: not obj2yaml %t.truncated.a 2>&1 | \
+# RUN:   FileCheck %s -DFILE=%t.truncated.a --check-prefix=TRUNCATED-ERR
+
+# TRUNCATED-ERR: Error reading file: [[FILE]]: unable to read the header of a child at offset 0x8
+
+--- !Arch
+Content: "00"
+
+## Check we report an error when unable to read the data of an archive member.
+
+# RUN: yaml2obj %s --docnum=4 -o %t.entdata.a
+# RUN: not obj2yaml %t.entdata.a 2>&1 | \
+# RUN:   FileCheck %s -DFILE=%t.entdata.a --check-prefix=ENTDATA-ERR
+
+# ENTDATA-ERR: Error reading file: [[FILE]]: unable to read the data of a child at offset 0x8 of size 1: the remaining archive size is 0
+
+--- !Arch
+Members:
+  - Size: [[SIZE='1']]
+
+## Check we report an error when unable to read the size of an archive member.
+
+# RUN: yaml2obj %s --docnum=4 -DSIZE='x' -o %t.entsize.a
+# RUN: not obj2yaml %t.entsize.a 2>&1 | \
+# RUN:   FileCheck %s -DFILE=%t.entsize.a --check-prefix=ENTSIZE-ERR
+
+# ENTSIZE-ERR: Error reading file: [[FILE]]: unable to read the size of a child at offset 0x8 as integer: "x"
+
+## Check we don't try to dump the padding byte when the size of the content is odd and
+## the content ends at the end of a file.
+
+# RUN: yaml2obj %s --docnum=5 -DCONTENT="61" -o %t.no.padding.byte.a
+# RUN: obj2yaml %t.no.padding.byte.a | FileCheck %s --check-prefix=NO-PADDING-BYTE
+
+#      NO-PADDING-BYTE: --- !Arch
+# NO-PADDING-BYTE-NEXT: Members:
+# NO-PADDING-BYTE-NEXT:   - Size:    '1'
+# NO-PADDING-BYTE-NEXT:     Content: '61'
+# NO-PADDING-BYTE-NEXT: ...
+
+--- !Arch
+Members:
+  - Size:    '1'
+    Content: [[CONTENT]]
+
+## Check we dump the padding byte when the size of the content is odd and the content ends
+## before the end of a file.
+
+# RUN: yaml2obj %s --docnum=5 -DCONTENT="610A" -o %t.padding.byte.a
+# RUN: obj2yaml %t.padding.byte.a | FileCheck %s --check-prefix=PADDING-BYTE
+
+#      PADDING-BYTE: --- !Arch
+# PADDING-BYTE-NEXT: Members:
+# PADDING-BYTE-NEXT:   - Size:        '1'
+# PADDING-BYTE-NEXT:     Content:     '61'
+# PADDING-BYTE-NEXT:     PaddingByte: 0x0A
+# PADDING-BYTE-NEXT: ...

diff  --git a/llvm/test/tools/yaml2obj/Archives/regular.yaml b/llvm/test/tools/yaml2obj/Archives/regular.yaml
new file mode 100644
index 000000000000..29b0cb5eea8a
--- /dev/null
+++ b/llvm/test/tools/yaml2obj/Archives/regular.yaml
@@ -0,0 +1,132 @@
+## Check how yaml2obj creates archives.
+
+## Check we create an empty archive when neither "Members" nor "Content" are specified.
+
+# RUN: yaml2obj --docnum=1 %s -o %t.empty.a
+# RUN: llvm-ar t %t.empty.a | FileCheck %s --allow-empty --implicit-check-not={{.}}
+# RUN: wc -c < %t.empty.a | FileCheck %s --check-prefix=EMPTY-SIZE
+# RUN: od -t x1 -v %t.empty.a | FileCheck %s --ignore-case --check-prefix=EMPTY-DATA
+
+# EMPTY-SIZE: 8{{$}}
+# EMPTY-DATA: 21 3c 61 72 63 68 3e 0a
+
+--- !Arch
+Magic:   "[[MAGIC=!<arch>\n]]"
+Content: [[CONTENT=<none>]]
+Members: [[MEMBERS=<none>]]
+
+## Check we report an error when both "Content" and "Members" keys are used together.
+
+# RUN: not yaml2obj --docnum=1 -DMEMBERS="[]" -DCONTENT="00" %s 2>&1 | FileCheck %s --check-prefix=BOTH
+
+## BOTH: error: "Content" and "Members" cannot be used together
+
+# RUN: yaml2obj --docnum=1 -DCONTENT="12" %s -o %t.content.a
+# RUN: od -t x1 -v %t.content.a | FileCheck %s --ignore-case --check-prefix=CONTENT
+
+# CONTENT: 21 3c 61 72 63 68 3e 0a 12{{$}}
+
+## Check we can specify magic bytes of size greater than the normal size (size of "!<arch>\n").
+
+# RUN: yaml2obj --docnum=1 -DMAGIC="123456789" %s -o %t.magic2.a
+# RUN: wc -c < %t.magic2.a | FileCheck %s --check-prefix=MAGIC-SIZE-GR
+# RUN: od -t x1 -v %t.magic2.a | FileCheck %s --ignore-case --check-prefix=MAGIC-DATA-GR
+
+# MAGIC-SIZE-GR: 9{{$}}
+# MAGIC-DATA-GR: 31 32 33 34 35 36 37 38 39
+
+## Check we can specify magic bytes of size less than the normal size (size of "!<arch>\n").
+
+# RUN: yaml2obj --docnum=1 -DMAGIC="1234567" %s -o %t.magic3.a
+# RUN: wc -c < %t.magic3.a | FileCheck %s --check-prefix=MAGIC-SIZE-LESS
+# RUN: od -t x1 -v %t.magic3.a | FileCheck %s --ignore-case --check-prefix=MAGIC-DATA-LESS
+
+# MAGIC-SIZE-LESS: 7{{$}}
+# MAGIC-DATA-LESS: 31 32 33 34 35 36 37
+
+## Check we can produce a valid archive with multiple members.
+## Check we are able to omit the "Magic" key and this defaults to "!<arch>\n".
+
+# RUN: yaml2obj --docnum=2 %s -o %t.two.a
+# RUN: llvm-ar -t %t.two.a | FileCheck %s --check-prefix=TWO
+# RUN: FileCheck --input-file=%t.two.a %s \
+# RUN:   --match-full-lines --strict-whitespace --check-prefix=TWO-DATA
+
+# TWO:      {{^}}bbbbbbbbbbbbbbb{{$}}
+# TWO-NEXT: {{^}}a{{$}}
+# TWO-NOT:  {{.}}
+
+#      TWO-DATA:!<arch>
+# TWO-DATA-NEXT:bbbbbbbbbbbbbbb/1234567890abqwertyasdfgh876543217         `
+# TWO-DATA-NEXT: cccc {{$}}
+# TWO-DATA-NEXT:za/              1           2     3     456     6         `
+# TWO-DATA-NEXT: aaa {{$}}
+#  TWO-DATA-NOT:{{.}}
+
+--- !Arch
+Members:
+## An arbitrary entry where each of fields has maximum allowed length.
+  - Name:         'bbbbbbbbbbbbbbb/'
+    LastModified: '1234567890ab'
+    UID:          'qwerty'
+    GID:          'asdfgh'
+    AccessMode:   '87654321'
+    Size:         '7'
+    Terminator:   "`\n"
+    Content:      "2063636363200A"
+    PaddingByte:  0x7a ## 'z'
+## An arbitrary entry to demonstrate that we use the 0x20 byte (space character)
+## to fill gaps between field values.
+  - Name:         'a/'
+    LastModified: '1'
+    UID:          '2'
+    GID:          '3'
+    AccessMode:   '456'
+    Size:         '6'
+    Terminator:   "`\n"
+    Content:      "20616161200A"
+
+## Check how we validate maximum sizes of fields.
+
+# RUN: not yaml2obj --docnum=3 -DNAME="123456789ABCDEF01" %s 2>&1 | \
+# RUN:   FileCheck %s --check-prefix=ERROR -DFIELD="Name" -DVAL=16
+# RUN: not yaml2obj --docnum=3 -DLAST="123456789ABCD" %s 2>&1 | \
+# RUN:   FileCheck %s --check-prefix=ERROR -DFIELD="LastModified" -DVAL=12
+# RUN: not yaml2obj --docnum=3 -DUID="1234567" %s 2>&1 | \
+# RUN:   FileCheck %s --check-prefix=ERROR -DFIELD="UID" -DVAL=6
+# RUN: not yaml2obj --docnum=3 -DGID="1234567" %s 2>&1 | \
+# RUN:   FileCheck %s --check-prefix=ERROR -DFIELD="GID" -DVAL=6
+# RUN: not yaml2obj --docnum=3 -DACCESSMODE="123456789" %s 2>&1 | \
+# RUN:   FileCheck %s --check-prefix=ERROR -DFIELD="AccessMode" -DVAL=8
+# RUN: not yaml2obj --docnum=3 -DSIZE="123456789AB" %s 2>&1 | \
+# RUN:   FileCheck %s --check-prefix=ERROR -DFIELD="Size" -DVAL=10
+# RUN: not yaml2obj --docnum=3 -DTERMINATOR="123" %s 2>&1 | \
+# RUN:   FileCheck %s --check-prefix=ERROR -DFIELD="Terminator" -DVAL=2
+
+# ERROR: error: the maximum length of "[[FIELD]]" field is [[VAL]]
+
+--- !Arch
+Members:
+  - Name:         '[[NAME=""]]'
+    LastModified: '[[LAST=""]]'
+    UID:          '[[UID=""]]'
+    GID:          '[[GID=""]]'
+    AccessMode:   '[[ACCESSMODE=""]]'
+    Size:         '[[SIZE=""]]'
+    Terminator:   '[[TERMINATOR=""]]'
+
+## Check that all keys are optional for members.
+
+# RUN: yaml2obj --docnum=4 %s -o %t.all.defaults.a
+# RUN: FileCheck --input-file=%t.all.defaults.a %s \
+# RUN:   --match-full-lines --strict-whitespace --check-prefix=DEFAULTS
+
+#      DEFAULTS:!<arch>
+# DEFAULTS-NEXT:                0           0     0     0       0         `
+# DEFAULTS-NEXT:                0           0     0     0       0         `
+#  DEFAULTS-NOT:{{.}}
+
+--- !Arch
+Members:
+  - {}
+  - {}

diff  --git a/llvm/tools/obj2yaml/CMakeLists.txt b/llvm/tools/obj2yaml/CMakeLists.txt
index 33cb36d9de47..9bd86a77dbe3 100644
--- a/llvm/tools/obj2yaml/CMakeLists.txt
+++ b/llvm/tools/obj2yaml/CMakeLists.txt
@@ -8,6 +8,7 @@ set(LLVM_LINK_COMPONENTS
   )
 
 add_llvm_utility(obj2yaml
+  archive2yaml.cpp
   obj2yaml.cpp
   coff2yaml.cpp
   dwarf2yaml.cpp

diff  --git a/llvm/tools/obj2yaml/archive2yaml.cpp b/llvm/tools/obj2yaml/archive2yaml.cpp
new file mode 100644
index 000000000000..c7b0ee480293
--- /dev/null
+++ b/llvm/tools/obj2yaml/archive2yaml.cpp
@@ -0,0 +1,114 @@
+//===------ utils/archive2yaml.cpp - obj2yaml conversion tool ---*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "obj2yaml.h"
+#include "llvm/BinaryFormat/Magic.h"
+#include "llvm/ObjectYAML/ArchiveYAML.h"
+
+using namespace llvm;
+
+namespace {
+
+class ArchiveDumper {
+public:
+  Expected<ArchYAML::Archive *> dump(MemoryBufferRef Source) {
+    StringRef Buffer = Source.getBuffer();
+    assert(file_magic::archive == identify_magic(Buffer));
+
+    std::unique_ptr<ArchYAML::Archive> Obj =
+        std::make_unique<ArchYAML::Archive>();
+
+    StringRef Magic = "!<arch>\n";
+    if (!Buffer.startswith(Magic))
+      return createStringError(std::errc::not_supported,
+                               "only regular archives are supported");
+    Obj->Magic = Magic;
+    Buffer = Buffer.drop_front(Magic.size());
+
+    Obj->Members.emplace();
+    while (!Buffer.empty()) {
+      uint64_t Offset = Buffer.data() - Source.getBuffer().data();
+      if (Buffer.size() < sizeof(ArchiveHeader))
+        return createStringError(
+            std::errc::illegal_byte_sequence,
+            "unable to read the header of a child at offset 0x%" PRIx64,
+            Offset);
+
+      const ArchiveHeader &Hdr =
+          *reinterpret_cast<const ArchiveHeader *>(Buffer.data());
+      Buffer = Buffer.drop_front(sizeof(ArchiveHeader));
+
+      auto ToString = [](ArrayRef<char> V) {
+        // We don't want to dump excessive spaces.
+        return StringRef(V.data(), V.size()).rtrim(' ');
+      };
+
+      ArchYAML::Archive::Child C;
+      C.Fields["Name"].Value = ToString(Hdr.Name);
+      C.Fields["LastModified"].Value = ToString(Hdr.LastModified);
+      C.Fields["UID"].Value = ToString(Hdr.UID);
+      C.Fields["GID"].Value = ToString(Hdr.GID);
+      C.Fields["AccessMode"].Value = ToString(Hdr.AccessMode);
+      StringRef SizeStr = ToString(Hdr.Size);
+      C.Fields["Size"].Value = SizeStr;
+      C.Fields["Terminator"].Value = ToString(Hdr.Terminator);
+
+      uint64_t Size;
+      if (SizeStr.getAsInteger(10, Size))
+        return createStringError(
+            std::errc::illegal_byte_sequence,
+            "unable to read the size of a child at offset 0x%" PRIx64
+            " as integer: \"%s\"",
+            Offset, SizeStr.str().c_str());
+      if (Buffer.size() < Size)
+        return createStringError(
+            std::errc::illegal_byte_sequence,
+            "unable to read the data of a child at offset 0x%" PRIx64
+            " of size %" PRId64 ": the remaining archive size is %zu",
+            Offset, Size, Buffer.size());
+      if (!Buffer.empty())
+        C.Content = arrayRefFromStringRef(Buffer.take_front(Size));
+
+      const bool HasPaddingByte = (Size & 1) && Buffer.size() > Size;
+      if (HasPaddingByte)
+        C.PaddingByte = Buffer[Size];
+
+      Obj->Members->push_back(C);
+      // If the size is odd, consume a padding byte.
+      Buffer = Buffer.drop_front(HasPaddingByte ? Size + 1 : Size);
+    }
+
+    return Obj.release();
+  }
+
+private:
+  struct ArchiveHeader {
+    char Name[16];
+    char LastModified[12];
+    char UID[6];
+    char GID[6];
+    char AccessMode[8];
+    char Size[10];
+    char Terminator[2];
+  };
+};
+
+} // namespace
+
+Error archive2yaml(raw_ostream &Out, MemoryBufferRef Source) {
+  ArchiveDumper Dumper;
+  Expected<ArchYAML::Archive *> YAMLOrErr = Dumper.dump(Source);
+  if (!YAMLOrErr)
+    return YAMLOrErr.takeError();
+
+  std::unique_ptr<ArchYAML::Archive> YAML(YAMLOrErr.get());
+  yaml::Output Yout(Out);
+  Yout << *YAML;
+
+  return Error::success();
+}

diff  --git a/llvm/tools/obj2yaml/obj2yaml.cpp b/llvm/tools/obj2yaml/obj2yaml.cpp
index 16fc428755e2..da70450503f3 100644
--- a/llvm/tools/obj2yaml/obj2yaml.cpp
+++ b/llvm/tools/obj2yaml/obj2yaml.cpp
@@ -7,6 +7,7 @@
 //===----------------------------------------------------------------------===//
 
 #include "obj2yaml.h"
+#include "llvm/BinaryFormat/Magic.h"
 #include "llvm/Object/Archive.h"
 #include "llvm/Object/COFF.h"
 #include "llvm/Object/Minidump.h"
@@ -34,16 +35,26 @@ static Error dumpObject(const ObjectFile &Obj) {
 }
 
 static Error dumpInput(StringRef File) {
-  Expected<OwningBinary<Binary>> BinaryOrErr = createBinary(File);
-  if (!BinaryOrErr)
-    return BinaryOrErr.takeError();
+  ErrorOr<std::unique_ptr<MemoryBuffer>> FileOrErr =
+      MemoryBuffer::getFileOrSTDIN(File, /*FileSize=*/-1,
+                                   /*RequiresNullTerminator=*/false);
+  if (std::error_code EC = FileOrErr.getError())
+    return errorCodeToError(EC);
+  std::unique_ptr<MemoryBuffer> &Buffer = FileOrErr.get();
+  MemoryBufferRef MemBuf = Buffer->getMemBufferRef();
+  if (file_magic::archive == identify_magic(MemBuf.getBuffer()))
+    return archive2yaml(outs(), MemBuf);
 
-  Binary &Binary = *BinaryOrErr.get().getBinary();
+  Expected<std::unique_ptr<Binary>> BinOrErr =
+      createBinary(MemBuf, /*Context=*/nullptr);
+  if (!BinOrErr)
+    return BinOrErr.takeError();
+
+  Binary &Binary = *BinOrErr->get();
   // Universal MachO is not a subclass of ObjectFile, so it needs to be handled
   // here with the other binary types.
   if (Binary.isMachO() || Binary.isMachOUniversalBinary())
     return macho2yaml(outs(), Binary);
-  // TODO: If this is an archive, then burst it and dump each entry
   if (ObjectFile *Obj = dyn_cast<ObjectFile>(&Binary))
     return dumpObject(*Obj);
   if (MinidumpFile *Minidump = dyn_cast<MinidumpFile>(&Binary))

diff  --git a/llvm/tools/obj2yaml/obj2yaml.h b/llvm/tools/obj2yaml/obj2yaml.h
index c41010f111b6..e31205027106 100644
--- a/llvm/tools/obj2yaml/obj2yaml.h
+++ b/llvm/tools/obj2yaml/obj2yaml.h
@@ -17,6 +17,7 @@
 #include "llvm/Object/Wasm.h"
 #include "llvm/Object/XCOFFObjectFile.h"
 #include "llvm/Support/raw_ostream.h"
+#include "llvm/Support/MemoryBufferRef.h"
 #include <system_error>
 
 std::error_code coff2yaml(llvm::raw_ostream &Out,
@@ -31,6 +32,7 @@ std::error_code xcoff2yaml(llvm::raw_ostream &Out,
                            const llvm::object::XCOFFObjectFile &Obj);
 std::error_code wasm2yaml(llvm::raw_ostream &Out,
                           const llvm::object::WasmObjectFile &Obj);
+llvm::Error archive2yaml(llvm::raw_ostream &Out, llvm::MemoryBufferRef Source);
 
 // Forward decls for dwarf2yaml
 namespace llvm {


        


More information about the llvm-commits mailing list