[llvm] [z/OS] Add z/OS archive reading support (PR #187110)
Uyiosa Iyekekpolor via llvm-commits
llvm-commits at lists.llvm.org
Wed May 6 19:22:37 PDT 2026
https://github.com/uyoyo0 updated https://github.com/llvm/llvm-project/pull/187110
>From 12727a9a353bf7dead4aba1086b58e05a9a92cdf Mon Sep 17 00:00:00 2001
From: Uyiosa Iyekekpolor <uyiosaben at gmail.com>
Date: Tue, 17 Mar 2026 15:29:38 -0400
Subject: [PATCH 01/15] [z/OS] Add z/OS archive reading support
---
llvm/include/llvm/Object/Archive.h | 53 +++++-
llvm/lib/Object/Archive.cpp | 211 ++++++++++++++++++++-
llvm/lib/Object/ArchiveWriter.cpp | 3 +
llvm/test/Object/Inputs/zos-archive-test.a | Bin 0 -> 1424 bytes
llvm/test/Object/zos-archive-read.test | 9 +
5 files changed, 273 insertions(+), 3 deletions(-)
create mode 100644 llvm/test/Object/Inputs/zos-archive-test.a
create mode 100644 llvm/test/Object/zos-archive-read.test
diff --git a/llvm/include/llvm/Object/Archive.h b/llvm/include/llvm/Object/Archive.h
index c97018d3231d5..9a72460846502 100644
--- a/llvm/include/llvm/Object/Archive.h
+++ b/llvm/include/llvm/Object/Archive.h
@@ -158,6 +158,35 @@ class LLVM_ABI BigArchiveMemberHeader
Expected<bool> isThin() const override { return false; }
};
+// Define file member header of z/OS archive.
+class ZOSArchiveMemberHeader : public ArchiveMemberHeader {
+public:
+ ZOSArchiveMemberHeader(Archive const *Parent, const char *RawHeaderPtr,
+ uint64_t Size, Error *Err);
+ std::unique_ptr<AbstractArchiveMemberHeader> clone() const override {
+ return std::make_unique<ZOSArchiveMemberHeader>(*this);
+ }
+
+ // Converted EBCDIC to ASCII header string fields.
+ std::string RawMemberName;
+ std::string MemberName;
+ std::string LastModified;
+ std::string UID;
+ std::string GID;
+ std::string AccessMode;
+
+ void setMemberHeaderStrings(Error *Err, uint64_t Size);
+
+ Expected<StringRef> getRawName() const override;
+ Expected<StringRef> getName(uint64_t Size) const override;
+ StringRef getRawAccessMode() const override;
+ StringRef getRawLastModified() const override;
+ StringRef getRawUID() const override;
+ StringRef getRawGID() const override;
+ Expected<uint64_t> getSize() const override;
+ Expected<bool> isThin() const override { return false; }
+};
+
class LLVM_ABI Archive : public Binary {
virtual void anchor();
@@ -343,7 +372,16 @@ class LLVM_ABI Archive : public Binary {
/// Size field is 10 decimal digits long
static const uint64_t MaxMemberSize = 9999999999;
- enum Kind { K_GNU, K_GNU64, K_BSD, K_DARWIN, K_DARWIN64, K_COFF, K_AIXBIG };
+ enum Kind {
+ K_GNU,
+ K_GNU64,
+ K_BSD,
+ K_DARWIN,
+ K_DARWIN64,
+ K_COFF,
+ K_AIXBIG,
+ K_ZOS
+ };
Kind kind() const { return (Kind)Format; }
bool isThin() const { return IsThin; }
@@ -434,6 +472,19 @@ class BigArchive : public Archive {
bool has64BitGlobalSymtab() { return Has64BitGlobalSymtab; }
};
+class ZOSArchive : public Archive {
+public:
+ // Fixed-Length header.
+ struct FixLenHdr {
+ char Magic[sizeof(ZOSArchiveMagic) - 1]; ///< ZOS archive magic string.
+ };
+
+public:
+ ZOSArchive(MemoryBufferRef Source, Error &Err);
+
+private:
+ std::string SymbolTableBuf; // __.SYMDEF strings converted to ASCII.
+};
} // end namespace object
} // end namespace llvm
diff --git a/llvm/lib/Object/Archive.cpp b/llvm/lib/Object/Archive.cpp
index 17c926e621f36..c7a3e75f0f05e 100644
--- a/llvm/lib/Object/Archive.cpp
+++ b/llvm/lib/Object/Archive.cpp
@@ -17,6 +17,7 @@
#include "llvm/Object/Binary.h"
#include "llvm/Object/Error.h"
#include "llvm/Support/Chrono.h"
+#include "llvm/Support/ConvertEBCDIC.h"
#include "llvm/Support/Endian.h"
#include "llvm/Support/EndianStream.h"
#include "llvm/Support/Error.h"
@@ -104,7 +105,11 @@ ArchiveMemberHeader::ArchiveMemberHeader(const Archive *Parent,
*Err = createMemberHeaderParseError(this, RawHeaderPtr, Size);
return;
}
- if (ArMemHdr->Terminator[0] != '`' || ArMemHdr->Terminator[1] != '\n') {
+ if ((ArMemHdr->Terminator[0] != '`' || ArMemHdr->Terminator[1] != '\n') &&
+ (ArMemHdr->Terminator[0] != '\x79' ||
+ ArMemHdr->Terminator[1] !=
+ '\x15') // '\x79\x15' is '`\n' in EBCDIC for z/OS archive terminator.
+ ) {
if (Err) {
std::string Buf;
raw_string_ostream OS(Buf);
@@ -368,6 +373,121 @@ Expected<uint64_t> BigArchiveMemberHeader::getSize() const {
return *SizeOrErr + alignTo(*NameLenOrErr, 2);
}
+template <class T, std::size_t N>
+StringRef getFieldRawStringE2A(const T (&Field)[N], SmallString<64> &Dst) {
+ StringRef Src = StringRef(Field, N);
+ ConverterEBCDIC::convertToUTF8(Src, Dst);
+ return Dst.str().rtrim(" ");
+}
+
+ZOSArchiveMemberHeader::ZOSArchiveMemberHeader(const Archive *Parent,
+ const char *RawHeaderPtr,
+ uint64_t Size, Error *Err)
+ : ArchiveMemberHeader(Parent, RawHeaderPtr, Size, Err) {
+ ErrorAsOutParameter ErrAsOutParam(Err);
+ setMemberHeaderStrings(Err, Size);
+}
+
+Expected<uint64_t> ZOSArchiveMemberHeader::getSize() const {
+ SmallString<64> Dst;
+ return getArchiveMemberDecField(
+ "size", getFieldRawStringE2A(ArMemHdr->Size, Dst), Parent, this);
+}
+
+Expected<StringRef> ZOSArchiveMemberHeader::getRawName() const {
+ return StringRef(RawMemberName);
+}
+
+Expected<StringRef> ZOSArchiveMemberHeader::getName(uint64_t Size) const {
+ return StringRef(MemberName);
+}
+
+StringRef ZOSArchiveMemberHeader::getRawAccessMode() const {
+ return StringRef(AccessMode);
+}
+
+StringRef ZOSArchiveMemberHeader::getRawLastModified() const {
+ return StringRef(LastModified);
+}
+
+StringRef ZOSArchiveMemberHeader::getRawUID() const { return StringRef(UID); }
+
+StringRef ZOSArchiveMemberHeader::getRawGID() const { return StringRef(GID); }
+
+void ZOSArchiveMemberHeader::setMemberHeaderStrings(Error *Err, uint64_t Size) {
+ SmallString<64> Dst;
+ uint64_t Offset =
+ reinterpret_cast<const char *>(ArMemHdr) - Parent->getData().data();
+
+ // Set RawMemberName.
+ StringRef RawNameSR = getFieldRawStringE2A(ArMemHdr->Name, Dst);
+ if (RawNameSR.empty() || RawNameSR[0] == ' ') {
+ *Err = malformedError("name contains a leading space for archive member "
+ "header at offset " +
+ Twine(Offset));
+ return;
+ }
+ RawMemberName.append(RawNameSR);
+
+ // Set MemberName.
+ if (RawNameSR.starts_with("#1/")) {
+ Expected<StringRef> NameOrErr = ArchiveMemberHeader::getName(Size);
+ if (!NameOrErr) {
+ *Err = NameOrErr.takeError();
+ return;
+ }
+ StringRef Name = NameOrErr.get();
+ Dst.clear();
+ ConverterEBCDIC::convertToUTF8(Name, Dst);
+ MemberName.append(Dst.str());
+ } else
+ MemberName = RawMemberName;
+
+ // LastModified
+ Dst.clear();
+ StringRef LastModifiedSR = getFieldRawStringE2A(ArMemHdr->LastModified, Dst);
+ if (LastModifiedSR.empty()) {
+ *Err = malformedError("problem converting LastModified field in "
+ "header at offset " +
+ Twine(Offset));
+ return;
+ }
+ LastModified.append(LastModifiedSR);
+
+ // UID
+ Dst.clear();
+ StringRef UIDSR = getFieldRawStringE2A(ArMemHdr->UID, Dst);
+ if (UIDSR.empty()) {
+ *Err = malformedError("problem converting UID field in "
+ "header at offset " +
+ Twine(Offset));
+ return;
+ }
+ UID.append(UIDSR);
+
+ // GID
+ Dst.clear();
+ StringRef GIDSR = getFieldRawStringE2A(ArMemHdr->GID, Dst);
+ if (GIDSR.empty()) {
+ *Err = malformedError("problem converting GID field in "
+ "header at offset " +
+ Twine(Offset));
+ return;
+ }
+ GID.append(GIDSR);
+
+ // AccessMode
+ Dst.clear();
+ StringRef AccessModeSR = getFieldRawStringE2A(ArMemHdr->AccessMode, Dst);
+ if (AccessModeSR.empty()) {
+ *Err = malformedError("problem converting AccessMode field in "
+ "header at offset " +
+ Twine(Offset));
+ return;
+ }
+ AccessMode.append(AccessModeSR);
+}
+
Expected<uint64_t> BigArchiveMemberHeader::getRawNameSize() const {
return getArchiveMemberDecField(
"NameLen", getFieldRawString(ArMemHdr->NameLen), Parent, this);
@@ -668,6 +788,8 @@ Expected<std::unique_ptr<Archive>> Archive::create(MemoryBufferRef Source) {
if (Buffer.starts_with(BigArchiveMagic))
Ret = std::make_unique<BigArchive>(Source, Err);
+ else if (Buffer.starts_with(ZOSArchiveMagic))
+ Ret = std::make_unique<ZOSArchive>(Source, Err);
else
Ret = std::make_unique<Archive>(Source, Err);
@@ -680,6 +802,10 @@ std::unique_ptr<AbstractArchiveMemberHeader>
Archive::createArchiveMemberHeader(const char *RawHeaderPtr, uint64_t Size,
Error *Err) const {
ErrorAsOutParameter ErrAsOutParam(Err);
+
+ if (kind() == K_ZOS)
+ return std::make_unique<ZOSArchiveMemberHeader>(this, RawHeaderPtr, Size,
+ Err);
if (kind() != K_AIXBIG)
return std::make_unique<ArchiveMemberHeader>(this, RawHeaderPtr, Size, Err);
return std::make_unique<BigArchiveMemberHeader>(this, RawHeaderPtr, Size,
@@ -695,7 +821,6 @@ uint64_t Archive::getArchiveMagicLen() const {
return sizeof(ArchiveMagic) - 1;
}
-
void Archive::setFirstRegular(const Child &C) {
FirstRegularData = C.Data;
FirstRegularStartOfFile = C.StartOfFile;
@@ -714,6 +839,10 @@ Archive::Archive(MemoryBufferRef Source, Error &Err)
Format = K_AIXBIG;
IsThin = false;
return;
+ } else if (Buffer.starts_with(ZOSArchiveMagic)) {
+ Format = K_ZOS;
+ IsThin = false;
+ return;
} else {
Err = make_error<GenericBinaryError>("file too small to be an archive",
object_error::invalid_file_type);
@@ -971,6 +1100,8 @@ object::Archive::Kind Archive::getDefaultKindForTriple(const Triple &T) {
return object::Archive::K_AIXBIG;
if (T.isOSWindows())
return object::Archive::K_COFF;
+ if (T.isOSzOS())
+ return object::Archive::K_ZOS;
return object::Archive::K_GNU;
}
@@ -1042,6 +1173,8 @@ Expected<Archive::Child> Archive::Symbol::getMember() const {
// the archive of the member that defines the symbol. Which is what
// is needed here.
Offset = read64le(Offsets + SymbolIndex * 16 + 8);
+ } else if (Parent->kind() == K_ZOS) {
+ Offset = read32be(Offsets + SymbolIndex * 8);
} else {
// Skip offsets.
uint32_t MemberCount = read32le(Buf);
@@ -1171,6 +1304,9 @@ Archive::symbol_iterator Archive::symbol_begin() const {
buf += ran_strx;
} else if (kind() == K_AIXBIG) {
buf = getStringTable().begin();
+ } else if (kind() == K_ZOS) {
+ uint32_t symbol_count = read32be(buf);
+ buf += sizeof(uint32_t) + (symbol_count * (sizeof(uint64_t)));
} else {
uint32_t member_count = 0;
uint32_t symbol_count = 0;
@@ -1244,6 +1380,9 @@ uint32_t Archive::getNumberOfSymbols() const {
return read32le(buf) / 8;
if (kind() == K_DARWIN64)
return read64le(buf) / 16;
+ if (kind() == K_ZOS) {
+ return read32be(buf);
+ }
uint32_t member_count = 0;
member_count = read32le(buf);
buf += 4 + (member_count * 4); // Skip offsets.
@@ -1448,3 +1587,71 @@ BigArchive::BigArchive(MemoryBufferRef Source, Error &Err)
setFirstRegular(*I);
Err = Error::success();
}
+
+ZOSArchive::ZOSArchive(MemoryBufferRef Source, Error &Err)
+ : Archive(Source, Err) {
+ ErrorAsOutParameter ErrAsOutParam(&Err);
+
+ // Get the special members.
+ child_iterator I = child_begin(Err, false);
+ if (Err)
+ return;
+ child_iterator E = child_end();
+
+ // See if this is a valid empty archive and if so return.
+ if (I == E) {
+ Err = Error::success();
+ return;
+ }
+ const Child *C = &*I;
+
+ auto Increment = [&]() {
+ ++I;
+ if (Err)
+ return true;
+ C = &*I;
+ return false;
+ };
+
+ Expected<StringRef> NameOrErr = C->getRawName();
+ if (!NameOrErr) {
+ Err = NameOrErr.takeError();
+ return;
+ }
+ StringRef Name = NameOrErr.get();
+
+ if (Name == "__.SYMDEF") {
+ // We know that the symbol table is not an external file, but we still must
+ // check any Expected<> return value.
+ Expected<StringRef> BufOrErr = C->getBuffer();
+ if (!BufOrErr) {
+ Err = BufOrErr.takeError();
+ return;
+ }
+
+ // Copy symbol table converting embedded EBCDIC names to ASCII.
+ StringRef ESymbolTable = BufOrErr.get();
+ uint32_t ESymbolCount = read32be(ESymbolTable.data());
+ uint32_t OffsetToENames =
+ sizeof(uint32_t) + (ESymbolCount * (sizeof(uint64_t)));
+ uint32_t ENamesSize = (uint32_t)ESymbolTable.size() - OffsetToENames;
+ const char *ENamesPtr = (const char *)ESymbolTable.data() + OffsetToENames;
+ StringRef ENames(ENamesPtr, ENamesSize);
+
+ SmallString<64> Dst;
+ ConverterEBCDIC::convertToUTF8(ENames, Dst);
+ SymbolTableBuf.append(ESymbolTable.data(), OffsetToENames);
+ SymbolTableBuf.append(Dst.str());
+ SymbolTable = StringRef(SymbolTableBuf.data(), SymbolTableBuf.size());
+ if (Increment())
+ return;
+ setFirstRegular(*C);
+
+ Err = Error::success();
+ return;
+ }
+
+ setFirstRegular(*C);
+ Err = Error::success();
+ return;
+}
\ No newline at end of file
diff --git a/llvm/lib/Object/ArchiveWriter.cpp b/llvm/lib/Object/ArchiveWriter.cpp
index 6d2bbca179836..4610fb4303274 100644
--- a/llvm/lib/Object/ArchiveWriter.cpp
+++ b/llvm/lib/Object/ArchiveWriter.cpp
@@ -192,6 +192,7 @@ static bool isBSDLike(object::Archive::Kind Kind) {
case object::Archive::K_GNU64:
case object::Archive::K_AIXBIG:
case object::Archive::K_COFF:
+ case object::Archive::K_ZOS:
return false;
case object::Archive::K_BSD:
case object::Archive::K_DARWIN:
@@ -287,6 +288,7 @@ static bool is64BitKind(object::Archive::Kind Kind) {
case object::Archive::K_BSD:
case object::Archive::K_DARWIN:
case object::Archive::K_COFF:
+ case object::Archive::K_ZOS:
return false;
case object::Archive::K_AIXBIG:
case object::Archive::K_DARWIN64:
@@ -517,6 +519,7 @@ getSymbolicFile(MemoryBufferRef Buf, LLVMContext &Context,
case object::Archive::K_COFF:
case object::Archive::K_DARWIN:
case object::Archive::K_DARWIN64:
+ case object::Archive::K_ZOS:
return ObjOrErr.takeError();
}
}
diff --git a/llvm/test/Object/Inputs/zos-archive-test.a b/llvm/test/Object/Inputs/zos-archive-test.a
new file mode 100644
index 0000000000000000000000000000000000000000..0244f176c64478cec3918b1ca6bed78b897eb2c1
GIT binary patch
literal 1424
zcmbtUPfO!K6n}C0?;uOvZJ{h8rFa%ULOkuEMS4&O;=wPFy^9_q3yVkbQ1K`T{Twyz
zQZEwNMe!n`dT>uWjcN2`_q~}U9b at CN_~6a^|C{$_CfR809e>;ZS~ku4;Qm*)_d}P&
zLrFsF>Z<F*AWGOS1AvCSM*O|i0_1ut3di-*p`rx7FPuZ~Dco+m%s8U_AJN{a#AM{n
zoYZ1~EioBD7H8VAF%j_yYX&)q*;jEiOSM9sK>;8`M$R_v%WhBL5f-&cGL at 2>Q6d)2
zr1VFdOe=};nH8%Og=yFywE_j(+}*vp>Ue08Ba18=r(j5!J0Vt`=!&L+Ch&6g=F!of
zC%~w$9CEV1no<YT&#zbjOX8iG=iTd{0+0I2Pr*~+UTr3V49`zaRj_-#!ENXI7SZV*
zXtwA+mR|scMzVQwi(1hhF!~OJZ%&8^OBlHH8PsH^^i-mN$Gw0*3?GMqKk|J3z>#pg
z{1?tk8aBy3QKp5wi7xHn&Y3k_r?CRAwGMWvy$GcyxQ)Z23r)w(RBjC3@!L<J8IF^|
z%vnQs7PAWmx92OX*+0NO5?<RM-+#V1$&JTjhisx}Dd2#l4n<s-Fn$`krHJQ at yo~bX
zdx=~mU%VAH at Gl+lqW7Yv*6^?wM!{(G`*J{k7(A!M$#FG(qO&UM%XGQ&;>GKx+sSOv
Xf{c`};-bXq^`{Wi<G_bldx!EDV(Pt2
literal 0
HcmV?d00001
diff --git a/llvm/test/Object/zos-archive-read.test b/llvm/test/Object/zos-archive-read.test
new file mode 100644
index 0000000000000..7adf2a5256697
--- /dev/null
+++ b/llvm/test/Object/zos-archive-read.test
@@ -0,0 +1,9 @@
+## Test reading a z/OS archive.
+
+# RUN: llvm-ar t %p/Inputs/zos-archive-test.a | FileCheck %s --check-prefix=LIST
+# RUN: llvm-nm --print-armap %p/Inputs/zos-archive-test.a | FileCheck %s --check-prefix=SYMS
+
+# LIST: foo.o
+
+# SYMS: Archive map
+# SYMS-NEXT: foo in foo.o
\ No newline at end of file
>From 12e3e4b6314642020707919ab0218bd31803d6ba Mon Sep 17 00:00:00 2001
From: Uyiosa Iyekekpolor <uyiosaben at gmail.com>
Date: Thu, 9 Apr 2026 10:51:04 -0400
Subject: [PATCH 02/15] Address review comments
---
llvm/include/llvm/Object/Archive.h | 1 -
llvm/lib/Object/Archive.cpp | 6 +++---
2 files changed, 3 insertions(+), 4 deletions(-)
diff --git a/llvm/include/llvm/Object/Archive.h b/llvm/include/llvm/Object/Archive.h
index 9a72460846502..b45a8e4673402 100644
--- a/llvm/include/llvm/Object/Archive.h
+++ b/llvm/include/llvm/Object/Archive.h
@@ -479,7 +479,6 @@ class ZOSArchive : public Archive {
char Magic[sizeof(ZOSArchiveMagic) - 1]; ///< ZOS archive magic string.
};
-public:
ZOSArchive(MemoryBufferRef Source, Error &Err);
private:
diff --git a/llvm/lib/Object/Archive.cpp b/llvm/lib/Object/Archive.cpp
index c7a3e75f0f05e..1554161eec34b 100644
--- a/llvm/lib/Object/Archive.cpp
+++ b/llvm/lib/Object/Archive.cpp
@@ -373,8 +373,8 @@ Expected<uint64_t> BigArchiveMemberHeader::getSize() const {
return *SizeOrErr + alignTo(*NameLenOrErr, 2);
}
-template <class T, std::size_t N>
-StringRef getFieldRawStringE2A(const T (&Field)[N], SmallString<64> &Dst) {
+template <std::size_t N>
+StringRef getFieldRawStringE2A(const char (&Field)[N], SmallString<64> &Dst) {
StringRef Src = StringRef(Field, N);
ConverterEBCDIC::convertToUTF8(Src, Dst);
return Dst.str().rtrim(" ");
@@ -1654,4 +1654,4 @@ ZOSArchive::ZOSArchive(MemoryBufferRef Source, Error &Err)
setFirstRegular(*C);
Err = Error::success();
return;
-}
\ No newline at end of file
+}
>From 1d9da96ac58bab96f1f247d86b85c202e8b29502 Mon Sep 17 00:00:00 2001
From: Uyiosa Iyekekpolor <uyiosaben at gmail.com>
Date: Thu, 9 Apr 2026 13:36:25 -0400
Subject: [PATCH 03/15] Address additional review comments
---
llvm/include/llvm/Object/Archive.h | 10 ++++++++++
llvm/lib/Object/Archive.cpp | 17 ++++++++++-------
2 files changed, 20 insertions(+), 7 deletions(-)
diff --git a/llvm/include/llvm/Object/Archive.h b/llvm/include/llvm/Object/Archive.h
index b45a8e4673402..59eca4babd60d 100644
--- a/llvm/include/llvm/Object/Archive.h
+++ b/llvm/include/llvm/Object/Archive.h
@@ -159,6 +159,16 @@ class LLVM_ABI BigArchiveMemberHeader
};
// Define file member header of z/OS archive.
+// The fixed part of the member header (in EBCDIC) is:
+// struct ar_hdr {
+// char ar_name[16]; /* blank terminated member name */
+// char ar_date[12]; /* date (decimal) */
+// char ar_uid[6]; /* user id (decimal) */
+// char ar_gid[6]; /* group id (decimal) */
+// char ar_mode[8]; /* access mode (octal) */
+// char ar_size[10]; /* length in bytes (decimal) */
+// char ar_fmag[2]; /* contains backtick (X'79'), followed by new line (X'15') */
+// };
class ZOSArchiveMemberHeader : public ArchiveMemberHeader {
public:
ZOSArchiveMemberHeader(Archive const *Parent, const char *RawHeaderPtr,
diff --git a/llvm/lib/Object/Archive.cpp b/llvm/lib/Object/Archive.cpp
index 1554161eec34b..98690a554356f 100644
--- a/llvm/lib/Object/Archive.cpp
+++ b/llvm/lib/Object/Archive.cpp
@@ -375,6 +375,7 @@ Expected<uint64_t> BigArchiveMemberHeader::getSize() const {
template <std::size_t N>
StringRef getFieldRawStringE2A(const char (&Field)[N], SmallString<64> &Dst) {
+ Dst.clear();
StringRef Src = StringRef(Field, N);
ConverterEBCDIC::convertToUTF8(Src, Dst);
return Dst.str().rtrim(" ");
@@ -444,7 +445,6 @@ void ZOSArchiveMemberHeader::setMemberHeaderStrings(Error *Err, uint64_t Size) {
MemberName = RawMemberName;
// LastModified
- Dst.clear();
StringRef LastModifiedSR = getFieldRawStringE2A(ArMemHdr->LastModified, Dst);
if (LastModifiedSR.empty()) {
*Err = malformedError("problem converting LastModified field in "
@@ -455,7 +455,6 @@ void ZOSArchiveMemberHeader::setMemberHeaderStrings(Error *Err, uint64_t Size) {
LastModified.append(LastModifiedSR);
// UID
- Dst.clear();
StringRef UIDSR = getFieldRawStringE2A(ArMemHdr->UID, Dst);
if (UIDSR.empty()) {
*Err = malformedError("problem converting UID field in "
@@ -466,7 +465,6 @@ void ZOSArchiveMemberHeader::setMemberHeaderStrings(Error *Err, uint64_t Size) {
UID.append(UIDSR);
// GID
- Dst.clear();
StringRef GIDSR = getFieldRawStringE2A(ArMemHdr->GID, Dst);
if (GIDSR.empty()) {
*Err = malformedError("problem converting GID field in "
@@ -477,7 +475,6 @@ void ZOSArchiveMemberHeader::setMemberHeaderStrings(Error *Err, uint64_t Size) {
GID.append(GIDSR);
// AccessMode
- Dst.clear();
StringRef AccessModeSR = getFieldRawStringE2A(ArMemHdr->AccessMode, Dst);
if (AccessModeSR.empty()) {
*Err = malformedError("problem converting AccessMode field in "
@@ -1174,6 +1171,12 @@ Expected<Archive::Child> Archive::Symbol::getMember() const {
// is needed here.
Offset = read64le(Offsets + SymbolIndex * 16 + 8);
} else if (Parent->kind() == K_ZOS) {
+ // The contents of the symbol table member in order are:
+ // 1. The number of symbols, NS (4 byte integer).
+ // 2. NS pairs of integers (the first in each pair of integers is the offset
+ // to the header of the entry... the second being coded attributes).
+ // Length is NS*(4+4) bytes.
+ // 3. NS null terminated strings of corresponding symbol names.
Offset = read32be(Offsets + SymbolIndex * 8);
} else {
// Skip offsets.
@@ -1608,9 +1611,9 @@ ZOSArchive::ZOSArchive(MemoryBufferRef Source, Error &Err)
auto Increment = [&]() {
++I;
if (Err)
- return true;
+ return false;
C = &*I;
- return false;
+ return true;
};
Expected<StringRef> NameOrErr = C->getRawName();
@@ -1643,7 +1646,7 @@ ZOSArchive::ZOSArchive(MemoryBufferRef Source, Error &Err)
SymbolTableBuf.append(ESymbolTable.data(), OffsetToENames);
SymbolTableBuf.append(Dst.str());
SymbolTable = StringRef(SymbolTableBuf.data(), SymbolTableBuf.size());
- if (Increment())
+ if (!Increment())
return;
setFirstRegular(*C);
>From 1b28b03be296581ef447824bf68c68acb920e015 Mon Sep 17 00:00:00 2001
From: Uyiosa Iyekekpolor <uyiosaben at gmail.com>
Date: Thu, 9 Apr 2026 13:51:06 -0400
Subject: [PATCH 04/15] Run clang-format
---
llvm/include/llvm/Object/Archive.h | 3 ++-
llvm/lib/Object/Archive.cpp | 4 ++--
2 files changed, 4 insertions(+), 3 deletions(-)
diff --git a/llvm/include/llvm/Object/Archive.h b/llvm/include/llvm/Object/Archive.h
index 59eca4babd60d..cff8367e891ab 100644
--- a/llvm/include/llvm/Object/Archive.h
+++ b/llvm/include/llvm/Object/Archive.h
@@ -167,7 +167,8 @@ class LLVM_ABI BigArchiveMemberHeader
// char ar_gid[6]; /* group id (decimal) */
// char ar_mode[8]; /* access mode (octal) */
// char ar_size[10]; /* length in bytes (decimal) */
-// char ar_fmag[2]; /* contains backtick (X'79'), followed by new line (X'15') */
+// char ar_fmag[2]; /* contains backtick (X'79'), followed by new line
+// (X'15') */
// };
class ZOSArchiveMemberHeader : public ArchiveMemberHeader {
public:
diff --git a/llvm/lib/Object/Archive.cpp b/llvm/lib/Object/Archive.cpp
index 98690a554356f..375a57126bfad 100644
--- a/llvm/lib/Object/Archive.cpp
+++ b/llvm/lib/Object/Archive.cpp
@@ -1173,8 +1173,8 @@ Expected<Archive::Child> Archive::Symbol::getMember() const {
} else if (Parent->kind() == K_ZOS) {
// The contents of the symbol table member in order are:
// 1. The number of symbols, NS (4 byte integer).
- // 2. NS pairs of integers (the first in each pair of integers is the offset
- // to the header of the entry... the second being coded attributes).
+ // 2. NS pairs of integers (the first in each pair of integers is the offset
+ // to the header of the entry... the second being coded attributes).
// Length is NS*(4+4) bytes.
// 3. NS null terminated strings of corresponding symbol names.
Offset = read32be(Offsets + SymbolIndex * 8);
>From 8cef225f1d00739788ada50e8b506ea7f993057c Mon Sep 17 00:00:00 2001
From: Uyiosa Iyekekpolor <uyiosaben at gmail.com>
Date: Thu, 9 Apr 2026 15:05:54 -0400
Subject: [PATCH 05/15] Move Increment lambda closer to its first use
---
llvm/lib/Object/Archive.cpp | 17 +++++++++--------
1 file changed, 9 insertions(+), 8 deletions(-)
diff --git a/llvm/lib/Object/Archive.cpp b/llvm/lib/Object/Archive.cpp
index 375a57126bfad..90479b0330cbf 100644
--- a/llvm/lib/Object/Archive.cpp
+++ b/llvm/lib/Object/Archive.cpp
@@ -1608,14 +1608,6 @@ ZOSArchive::ZOSArchive(MemoryBufferRef Source, Error &Err)
}
const Child *C = &*I;
- auto Increment = [&]() {
- ++I;
- if (Err)
- return false;
- C = &*I;
- return true;
- };
-
Expected<StringRef> NameOrErr = C->getRawName();
if (!NameOrErr) {
Err = NameOrErr.takeError();
@@ -1646,6 +1638,15 @@ ZOSArchive::ZOSArchive(MemoryBufferRef Source, Error &Err)
SymbolTableBuf.append(ESymbolTable.data(), OffsetToENames);
SymbolTableBuf.append(Dst.str());
SymbolTable = StringRef(SymbolTableBuf.data(), SymbolTableBuf.size());
+
+ auto Increment = [&]() {
+ ++I;
+ if (Err)
+ return false;
+ C = &*I;
+ return true;
+ };
+
if (!Increment())
return;
setFirstRegular(*C);
>From 6fd2aae260b150d60f1d4f1d1c0e82fe1cf75fa1 Mon Sep 17 00:00:00 2001
From: Uyiosa Iyekekpolor <uyiosaben at gmail.com>
Date: Mon, 13 Apr 2026 09:53:02 -0400
Subject: [PATCH 06/15] Clarify z/OS symbol table offset math and layout
comments
---
llvm/lib/Object/Archive.cpp | 16 ++++++++++------
1 file changed, 10 insertions(+), 6 deletions(-)
diff --git a/llvm/lib/Object/Archive.cpp b/llvm/lib/Object/Archive.cpp
index 90479b0330cbf..694a054aedae5 100644
--- a/llvm/lib/Object/Archive.cpp
+++ b/llvm/lib/Object/Archive.cpp
@@ -1171,12 +1171,10 @@ Expected<Archive::Child> Archive::Symbol::getMember() const {
// is needed here.
Offset = read64le(Offsets + SymbolIndex * 16 + 8);
} else if (Parent->kind() == K_ZOS) {
- // The contents of the symbol table member in order are:
- // 1. The number of symbols, NS (4 byte integer).
- // 2. NS pairs of integers (the first in each pair of integers is the offset
- // to the header of the entry... the second being coded attributes).
- // Length is NS*(4+4) bytes.
- // 3. NS null terminated strings of corresponding symbol names.
+ // Each entry in the offset array is 8 bytes long:
+ // A 4-byte offset followed by 4 bytes of coded attributes.
+ // We multiply the SymbolIndex by 8 to reach the correct entry,
+ // and read the first 4 bytes (the offset).
Offset = read32be(Offsets + SymbolIndex * 8);
} else {
// Skip offsets.
@@ -1308,6 +1306,12 @@ Archive::symbol_iterator Archive::symbol_begin() const {
} else if (kind() == K_AIXBIG) {
buf = getStringTable().begin();
} else if (kind() == K_ZOS) {
+ // The contents of the z/OS symbol table member are:
+ // 1. The number of symbols, NS (4-byte integer).
+ // 2. NS pairs of 4-byte integers (offset and attributes). Length is NS*8
+ // bytes.
+ // 3. NS null terminated strings of corresponding symbol names.
+ // Here we skip parts 1 and 2 to reach the start of the string table.
uint32_t symbol_count = read32be(buf);
buf += sizeof(uint32_t) + (symbol_count * (sizeof(uint64_t)));
} else {
>From bda2dff8bd163e4f31d5a6f03dee6dab1d3e5b4f Mon Sep 17 00:00:00 2001
From: Uyiosa Iyekekpolor <uyiosaben at gmail.com>
Date: Mon, 13 Apr 2026 11:36:44 -0400
Subject: [PATCH 07/15] Add test for extracting z/OS archive member
---
llvm/test/Object/Inputs/zos-expected-foo.o | Bin 0 -> 1280 bytes
llvm/test/Object/zos-archive-read.test | 6 +++++-
2 files changed, 5 insertions(+), 1 deletion(-)
create mode 100644 llvm/test/Object/Inputs/zos-expected-foo.o
diff --git a/llvm/test/Object/Inputs/zos-expected-foo.o b/llvm/test/Object/Inputs/zos-expected-foo.o
new file mode 100644
index 0000000000000000000000000000000000000000..4caf23315e4c9f140c3a0c11b92af7e0dfc77c7c
GIT binary patch
literal 1280
zcmbtSy-EW?5T4EE=L(UiDT0WEU at bnvSy?EOM!3QvFOVnD;%uzNLTf?DbHp>CjT{I{
z8abkcR<4)3Xl0z)y`S|=BI3aA{D1rH?r;Hr6*jGbtK`(0Dg8}QorS}blRXCu5sxA{
zTv^O39f#Sv7ug&PfIK-xr{&yTb|oHR5lzw)C3_eZVpDDcKRRSwOM=hs#7qs&kRMS2
z!)fjB-`{t9^e9lkC5clq60A*y*HT-v7%+)f+-#hl9{3WBcolJF`PH>P82^4{18jwN
z(Yn6u-AX*-RT_h*!~Hl+CON*Jx|QK{yZy&b?-9}IALwrJf2 at 52ikvKY3d>$e4>-<?
ztmJc2#aPn8?TSNPRjNoePWZwfiox6a+b{_IKnw&5UazzvM1>(qzCe{Wu3ZeVqbGO9
z^4zU;@a!FMD(w}hG{D<BE_={$y?ph-5}ly^0nBpUJXX$Frn^#DwuG}(-7dTWpC`O_
zFnV6RIV+AvBbR)VPo?09v<YS0S22Gcg_TO~8AmzQsd<S*q`%FIp7_m1ym(&p(mFo&
zaySgbm%F}cn)FVYmlJw<YV#V}YxKG5<K=YI at 8p}b;X*0ZaMSWM{S$<nNn$p!{$YFp
DKWm9X
literal 0
HcmV?d00001
diff --git a/llvm/test/Object/zos-archive-read.test b/llvm/test/Object/zos-archive-read.test
index 7adf2a5256697..2afcb1b79ee22 100644
--- a/llvm/test/Object/zos-archive-read.test
+++ b/llvm/test/Object/zos-archive-read.test
@@ -3,7 +3,11 @@
# RUN: llvm-ar t %p/Inputs/zos-archive-test.a | FileCheck %s --check-prefix=LIST
# RUN: llvm-nm --print-armap %p/Inputs/zos-archive-test.a | FileCheck %s --check-prefix=SYMS
+## Test extracting a member and verifying its content.
+# RUN: llvm-ar p %p/Inputs/zos-archive-test.a foo.o > %t.foo.o
+# RUN: cmp %t.foo.o %p/Inputs/zos-expected-foo.o
+
# LIST: foo.o
# SYMS: Archive map
-# SYMS-NEXT: foo in foo.o
\ No newline at end of file
+# SYMS-NEXT: foo in foo.o
>From bd6299787174aa6677e04263578bf4e37da0201b Mon Sep 17 00:00:00 2001
From: Uyiosa Iyekekpolor <uyiosaben at gmail.com>
Date: Wed, 15 Apr 2026 16:46:27 -0400
Subject: [PATCH 08/15] Address reviewer comments
---
llvm/include/llvm/Object/Archive.h | 2 +-
llvm/lib/Object/Archive.cpp | 125 +++++++++++++++--------------
2 files changed, 64 insertions(+), 63 deletions(-)
diff --git a/llvm/include/llvm/Object/Archive.h b/llvm/include/llvm/Object/Archive.h
index cff8367e891ab..cdb7d32311e4a 100644
--- a/llvm/include/llvm/Object/Archive.h
+++ b/llvm/include/llvm/Object/Archive.h
@@ -161,7 +161,7 @@ class LLVM_ABI BigArchiveMemberHeader
// Define file member header of z/OS archive.
// The fixed part of the member header (in EBCDIC) is:
// struct ar_hdr {
-// char ar_name[16]; /* blank terminated member name */
+// char ar_name[16]; /* space-padded member name */
// char ar_date[12]; /* date (decimal) */
// char ar_uid[6]; /* user id (decimal) */
// char ar_gid[6]; /* group id (decimal) */
diff --git a/llvm/lib/Object/Archive.cpp b/llvm/lib/Object/Archive.cpp
index 694a054aedae5..62cbca5cd37b3 100644
--- a/llvm/lib/Object/Archive.cpp
+++ b/llvm/lib/Object/Archive.cpp
@@ -105,11 +105,12 @@ ArchiveMemberHeader::ArchiveMemberHeader(const Archive *Parent,
*Err = createMemberHeaderParseError(this, RawHeaderPtr, Size);
return;
}
- if ((ArMemHdr->Terminator[0] != '`' || ArMemHdr->Terminator[1] != '\n') &&
- (ArMemHdr->Terminator[0] != '\x79' ||
- ArMemHdr->Terminator[1] !=
- '\x15') // '\x79\x15' is '`\n' in EBCDIC for z/OS archive terminator.
- ) {
+ bool ValidTerminator =
+ Parent->kind() == Archive::K_ZOS ? (ArMemHdr->Terminator[0] == '\x79' &&
+ ArMemHdr->Terminator[1] == '\x15')
+ : (ArMemHdr->Terminator[0] == '`' &&
+ ArMemHdr->Terminator[1] == '\n');
+ if (!ValidTerminator) {
if (Err) {
std::string Buf;
raw_string_ostream OS(Buf);
@@ -123,8 +124,9 @@ ArchiveMemberHeader::ArchiveMemberHeader(const Archive *Parent,
consumeError(NameOrErr.takeError());
uint64_t Offset = RawHeaderPtr - Parent->getData().data();
*Err = malformedError(Msg + "at offset " + Twine(Offset));
- } else
+ } else {
*Err = malformedError(Msg + "for " + NameOrErr.get());
+ }
}
return;
}
@@ -374,11 +376,11 @@ Expected<uint64_t> BigArchiveMemberHeader::getSize() const {
}
template <std::size_t N>
-StringRef getFieldRawStringE2A(const char (&Field)[N], SmallString<64> &Dst) {
+StringRef ebcdicFieldToASCII(const char (&Field)[N], SmallVectorImpl<char> &Dst) {
Dst.clear();
StringRef Src = StringRef(Field, N);
ConverterEBCDIC::convertToUTF8(Src, Dst);
- return Dst.str().rtrim(" ");
+ return StringRef(Dst.data(), Dst.size()).rtrim(" ");
}
ZOSArchiveMemberHeader::ZOSArchiveMemberHeader(const Archive *Parent,
@@ -392,7 +394,7 @@ ZOSArchiveMemberHeader::ZOSArchiveMemberHeader(const Archive *Parent,
Expected<uint64_t> ZOSArchiveMemberHeader::getSize() const {
SmallString<64> Dst;
return getArchiveMemberDecField(
- "size", getFieldRawStringE2A(ArMemHdr->Size, Dst), Parent, this);
+ "size", ebcdicFieldToASCII(ArMemHdr->Size, Dst), Parent, this);
}
Expected<StringRef> ZOSArchiveMemberHeader::getRawName() const {
@@ -420,18 +422,18 @@ void ZOSArchiveMemberHeader::setMemberHeaderStrings(Error *Err, uint64_t Size) {
uint64_t Offset =
reinterpret_cast<const char *>(ArMemHdr) - Parent->getData().data();
- // Set RawMemberName.
- StringRef RawNameSR = getFieldRawStringE2A(ArMemHdr->Name, Dst);
- if (RawNameSR.empty() || RawNameSR[0] == ' ') {
+ // Set RawMemberName
+ StringRef RawNameRef = ebcdicFieldToASCII(ArMemHdr->Name, Dst);
+ if (RawNameRef.empty() || RawNameRef[0] == ' ') {
*Err = malformedError("name contains a leading space for archive member "
"header at offset " +
Twine(Offset));
return;
}
- RawMemberName.append(RawNameSR);
+ RawMemberName.append(RawNameRef);
// Set MemberName.
- if (RawNameSR.starts_with("#1/")) {
+ if (RawNameRef.starts_with("#1/")) {
Expected<StringRef> NameOrErr = ArchiveMemberHeader::getName(Size);
if (!NameOrErr) {
*Err = NameOrErr.takeError();
@@ -441,48 +443,45 @@ void ZOSArchiveMemberHeader::setMemberHeaderStrings(Error *Err, uint64_t Size) {
Dst.clear();
ConverterEBCDIC::convertToUTF8(Name, Dst);
MemberName.append(Dst.str());
- } else
+ } else {
MemberName = RawMemberName;
+ }
// LastModified
- StringRef LastModifiedSR = getFieldRawStringE2A(ArMemHdr->LastModified, Dst);
- if (LastModifiedSR.empty()) {
- *Err = malformedError("problem converting LastModified field in "
- "header at offset " +
- Twine(Offset));
+ StringRef LastModifiedRef = ebcdicFieldToASCII(ArMemHdr->LastModified, Dst);
+ if (LastModifiedRef.empty()) {
+ *Err = malformedError("LastModified field is empty or contains only spaces in "
+ "archive member header at offset " + Twine(Offset));
return;
}
- LastModified.append(LastModifiedSR);
+ LastModified.append(LastModifiedRef);
// UID
- StringRef UIDSR = getFieldRawStringE2A(ArMemHdr->UID, Dst);
- if (UIDSR.empty()) {
- *Err = malformedError("problem converting UID field in "
- "header at offset " +
- Twine(Offset));
+ StringRef UIDRef = ebcdicFieldToASCII(ArMemHdr->UID, Dst);
+ if (UIDRef.empty()) {
+ *Err = malformedError("UID field is empty or contains only spaces in "
+ "archive member header at offset " + Twine(Offset));
return;
}
- UID.append(UIDSR);
+ UID.append(UIDRef);
// GID
- StringRef GIDSR = getFieldRawStringE2A(ArMemHdr->GID, Dst);
- if (GIDSR.empty()) {
- *Err = malformedError("problem converting GID field in "
- "header at offset " +
- Twine(Offset));
+ StringRef GIDRef = ebcdicFieldToASCII(ArMemHdr->GID, Dst);
+ if (GIDRef.empty()) {
+ *Err = malformedError("GID field is empty or contains only spaces in "
+ "archive member header at offset " + Twine(Offset));
return;
}
- GID.append(GIDSR);
+ GID.append(GIDRef);
// AccessMode
- StringRef AccessModeSR = getFieldRawStringE2A(ArMemHdr->AccessMode, Dst);
- if (AccessModeSR.empty()) {
- *Err = malformedError("problem converting AccessMode field in "
- "header at offset " +
- Twine(Offset));
+ StringRef AccessModeRef = ebcdicFieldToASCII(ArMemHdr->AccessMode, Dst);
+ if (AccessModeRef.empty()) {
+ *Err = malformedError("AccessMode field is empty or contains only spaces in "
+ "archive member header at offset " + Twine(Offset));
return;
}
- AccessMode.append(AccessModeSR);
+ AccessMode.append(AccessModeRef);
}
Expected<uint64_t> BigArchiveMemberHeader::getRawNameSize() const {
@@ -818,6 +817,7 @@ uint64_t Archive::getArchiveMagicLen() const {
return sizeof(ArchiveMagic) - 1;
}
+
void Archive::setFirstRegular(const Child &C) {
FirstRegularData = C.Data;
FirstRegularStartOfFile = C.StartOfFile;
@@ -1312,8 +1312,8 @@ Archive::symbol_iterator Archive::symbol_begin() const {
// bytes.
// 3. NS null terminated strings of corresponding symbol names.
// Here we skip parts 1 and 2 to reach the start of the string table.
- uint32_t symbol_count = read32be(buf);
- buf += sizeof(uint32_t) + (symbol_count * (sizeof(uint64_t)));
+ uint32_t SymbolCount = read32be(buf);
+ buf += sizeof(uint32_t) + (SymbolCount * (sizeof(uint64_t)));
} else {
uint32_t member_count = 0;
uint32_t symbol_count = 0;
@@ -1387,9 +1387,8 @@ uint32_t Archive::getNumberOfSymbols() const {
return read32le(buf) / 8;
if (kind() == K_DARWIN64)
return read64le(buf) / 16;
- if (kind() == K_ZOS) {
+ if (kind() == K_ZOS)
return read32be(buf);
- }
uint32_t member_count = 0;
member_count = read32le(buf);
buf += 4 + (member_count * 4); // Skip offsets.
@@ -1629,32 +1628,34 @@ ZOSArchive::ZOSArchive(MemoryBufferRef Source, Error &Err)
}
// Copy symbol table converting embedded EBCDIC names to ASCII.
- StringRef ESymbolTable = BufOrErr.get();
- uint32_t ESymbolCount = read32be(ESymbolTable.data());
- uint32_t OffsetToENames =
- sizeof(uint32_t) + (ESymbolCount * (sizeof(uint64_t)));
- uint32_t ENamesSize = (uint32_t)ESymbolTable.size() - OffsetToENames;
- const char *ENamesPtr = (const char *)ESymbolTable.data() + OffsetToENames;
- StringRef ENames(ENamesPtr, ENamesSize);
+ StringRef EbcdicSymbolTable = BufOrErr.get();
+ if (EbcdicSymbolTable.size() < sizeof(uint32_t)) {
+ Err = malformedError("z/OS symbol table is too small to read the symbol count");
+ return;
+ }
+ uint64_t EbcdicSymbolCount = read32be(EbcdicSymbolTable.data());
+ uint64_t OffsetToEbcdicNames =
+ sizeof(uint32_t) + (EbcdicSymbolCount * (sizeof(uint64_t)));
+ if (OffsetToEbcdicNames > EbcdicSymbolTable.size()) {
+ Err = malformedError("z/OS symbol table count exceeds buffer size");
+ return;
+ }
+ uint64_t EbcdicNamesSize = EbcdicSymbolTable.size() - OffsetToEbcdicNames;
+ const char *EbcdicNamesPtr = EbcdicSymbolTable.data() + OffsetToEbcdicNames;
+ StringRef EbcdicNames(EbcdicNamesPtr, EbcdicNamesSize);
SmallString<64> Dst;
- ConverterEBCDIC::convertToUTF8(ENames, Dst);
- SymbolTableBuf.append(ESymbolTable.data(), OffsetToENames);
+ ConverterEBCDIC::convertToUTF8(EbcdicNames, Dst);
+ SymbolTableBuf.append(EbcdicSymbolTable.data(), OffsetToEbcdicNames);
SymbolTableBuf.append(Dst.str());
SymbolTable = StringRef(SymbolTableBuf.data(), SymbolTableBuf.size());
- auto Increment = [&]() {
- ++I;
- if (Err)
- return false;
- C = &*I;
- return true;
- };
-
- if (!Increment())
+ ++I;
+ if (Err)
return;
+ C = &*I;
+
setFirstRegular(*C);
-
Err = Error::success();
return;
}
>From 40363d970ffe9c26957faba02752f352125b56bc Mon Sep 17 00:00:00 2001
From: Uyiosa Iyekekpolor <uyiosaben at gmail.com>
Date: Wed, 15 Apr 2026 16:48:54 -0400
Subject: [PATCH 09/15] Address reviewer comments
---
llvm/lib/Object/Archive.cpp | 36 ++++++++++++++++++++++--------------
1 file changed, 22 insertions(+), 14 deletions(-)
diff --git a/llvm/lib/Object/Archive.cpp b/llvm/lib/Object/Archive.cpp
index 62cbca5cd37b3..b9f0674423000 100644
--- a/llvm/lib/Object/Archive.cpp
+++ b/llvm/lib/Object/Archive.cpp
@@ -106,10 +106,10 @@ ArchiveMemberHeader::ArchiveMemberHeader(const Archive *Parent,
return;
}
bool ValidTerminator =
- Parent->kind() == Archive::K_ZOS ? (ArMemHdr->Terminator[0] == '\x79' &&
- ArMemHdr->Terminator[1] == '\x15')
- : (ArMemHdr->Terminator[0] == '`' &&
- ArMemHdr->Terminator[1] == '\n');
+ Parent->kind() == Archive::K_ZOS
+ ? (ArMemHdr->Terminator[0] == '\x79' &&
+ ArMemHdr->Terminator[1] == '\x15')
+ : (ArMemHdr->Terminator[0] == '`' && ArMemHdr->Terminator[1] == '\n');
if (!ValidTerminator) {
if (Err) {
std::string Buf;
@@ -376,7 +376,8 @@ Expected<uint64_t> BigArchiveMemberHeader::getSize() const {
}
template <std::size_t N>
-StringRef ebcdicFieldToASCII(const char (&Field)[N], SmallVectorImpl<char> &Dst) {
+StringRef ebcdicFieldToASCII(const char (&Field)[N],
+ SmallVectorImpl<char> &Dst) {
Dst.clear();
StringRef Src = StringRef(Field, N);
ConverterEBCDIC::convertToUTF8(Src, Dst);
@@ -450,8 +451,10 @@ void ZOSArchiveMemberHeader::setMemberHeaderStrings(Error *Err, uint64_t Size) {
// LastModified
StringRef LastModifiedRef = ebcdicFieldToASCII(ArMemHdr->LastModified, Dst);
if (LastModifiedRef.empty()) {
- *Err = malformedError("LastModified field is empty or contains only spaces in "
- "archive member header at offset " + Twine(Offset));
+ *Err =
+ malformedError("LastModified field is empty or contains only spaces in "
+ "archive member header at offset " +
+ Twine(Offset));
return;
}
LastModified.append(LastModifiedRef);
@@ -460,7 +463,8 @@ void ZOSArchiveMemberHeader::setMemberHeaderStrings(Error *Err, uint64_t Size) {
StringRef UIDRef = ebcdicFieldToASCII(ArMemHdr->UID, Dst);
if (UIDRef.empty()) {
*Err = malformedError("UID field is empty or contains only spaces in "
- "archive member header at offset " + Twine(Offset));
+ "archive member header at offset " +
+ Twine(Offset));
return;
}
UID.append(UIDRef);
@@ -469,7 +473,8 @@ void ZOSArchiveMemberHeader::setMemberHeaderStrings(Error *Err, uint64_t Size) {
StringRef GIDRef = ebcdicFieldToASCII(ArMemHdr->GID, Dst);
if (GIDRef.empty()) {
*Err = malformedError("GID field is empty or contains only spaces in "
- "archive member header at offset " + Twine(Offset));
+ "archive member header at offset " +
+ Twine(Offset));
return;
}
GID.append(GIDRef);
@@ -477,8 +482,10 @@ void ZOSArchiveMemberHeader::setMemberHeaderStrings(Error *Err, uint64_t Size) {
// AccessMode
StringRef AccessModeRef = ebcdicFieldToASCII(ArMemHdr->AccessMode, Dst);
if (AccessModeRef.empty()) {
- *Err = malformedError("AccessMode field is empty or contains only spaces in "
- "archive member header at offset " + Twine(Offset));
+ *Err =
+ malformedError("AccessMode field is empty or contains only spaces in "
+ "archive member header at offset " +
+ Twine(Offset));
return;
}
AccessMode.append(AccessModeRef);
@@ -1387,7 +1394,7 @@ uint32_t Archive::getNumberOfSymbols() const {
return read32le(buf) / 8;
if (kind() == K_DARWIN64)
return read64le(buf) / 16;
- if (kind() == K_ZOS)
+ if (kind() == K_ZOS)
return read32be(buf);
uint32_t member_count = 0;
member_count = read32le(buf);
@@ -1630,7 +1637,8 @@ ZOSArchive::ZOSArchive(MemoryBufferRef Source, Error &Err)
// Copy symbol table converting embedded EBCDIC names to ASCII.
StringRef EbcdicSymbolTable = BufOrErr.get();
if (EbcdicSymbolTable.size() < sizeof(uint32_t)) {
- Err = malformedError("z/OS symbol table is too small to read the symbol count");
+ Err = malformedError(
+ "z/OS symbol table is too small to read the symbol count");
return;
}
uint64_t EbcdicSymbolCount = read32be(EbcdicSymbolTable.data());
@@ -1654,7 +1662,7 @@ ZOSArchive::ZOSArchive(MemoryBufferRef Source, Error &Err)
if (Err)
return;
C = &*I;
-
+
setFirstRegular(*C);
Err = Error::success();
return;
>From d18485af7108dec5bb88e4e140364caa0beb5386 Mon Sep 17 00:00:00 2001
From: Uyiosa Iyekekpolor <uyiosaben at gmail.com>
Date: Tue, 21 Apr 2026 20:03:30 -0400
Subject: [PATCH 10/15] Replace canned binaries with python helper; increase
test coverage
---
llvm/lib/Object/Archive.cpp | 3 +-
.../Object/Inputs/generate_zos_archive.py | 352 ++++++++++++++++++
llvm/test/Object/Inputs/zos-archive-test.a | Bin 1424 -> 0 bytes
llvm/test/Object/Inputs/zos-expected-foo.o | Bin 1280 -> 0 bytes
llvm/test/Object/zos-archive-read.test | 52 ++-
5 files changed, 396 insertions(+), 11 deletions(-)
create mode 100644 llvm/test/Object/Inputs/generate_zos_archive.py
delete mode 100644 llvm/test/Object/Inputs/zos-archive-test.a
delete mode 100644 llvm/test/Object/Inputs/zos-expected-foo.o
diff --git a/llvm/lib/Object/Archive.cpp b/llvm/lib/Object/Archive.cpp
index b9f0674423000..6d1323d5aa0a2 100644
--- a/llvm/lib/Object/Archive.cpp
+++ b/llvm/lib/Object/Archive.cpp
@@ -105,6 +105,7 @@ ArchiveMemberHeader::ArchiveMemberHeader(const Archive *Parent,
*Err = createMemberHeaderParseError(this, RawHeaderPtr, Size);
return;
}
+ // '\x79\x15' is the EBCDIC equivalent of '`\n' for the z/OS archive terminator.
bool ValidTerminator =
Parent->kind() == Archive::K_ZOS
? (ArMemHdr->Terminator[0] == '\x79' &&
@@ -1626,8 +1627,6 @@ ZOSArchive::ZOSArchive(MemoryBufferRef Source, Error &Err)
StringRef Name = NameOrErr.get();
if (Name == "__.SYMDEF") {
- // We know that the symbol table is not an external file, but we still must
- // check any Expected<> return value.
Expected<StringRef> BufOrErr = C->getBuffer();
if (!BufOrErr) {
Err = BufOrErr.takeError();
diff --git a/llvm/test/Object/Inputs/generate_zos_archive.py b/llvm/test/Object/Inputs/generate_zos_archive.py
new file mode 100644
index 0000000000000..a3866d23aa3e4
--- /dev/null
+++ b/llvm/test/Object/Inputs/generate_zos_archive.py
@@ -0,0 +1,352 @@
+#!/usr/bin/env python3
+"""Generate z/OS archive files
+
+z/OS archives use EBCDIC encoding for headers, magic bytes, and symbol names.
+This script generates archives in place to avoid reliance on canned binaries.
+
+Usage examples:
+ # Valid archive with one member and symbol table:
+ %python %S/Inputs/generate_zos_archive.py --output %t.a \
+ --symtab "foo:0" --member foo.o:%S/Inputs/foo.o
+
+ # Empty archive:
+ %python %S/Inputs/generate_zos_archive.py --output %t.a --empty
+
+ # Malformed member header: bad terminator
+ %python %S/Inputs/generate_zos_archive.py --output %t.a \
+ --member foo.o --bad-terminator
+
+ # Malformed __.SYMDEF header: bad terminator
+ %python %S/Inputs/generate_zos_archive.py --output %t.a \
+ --member foo.o --symtab foo:0 --malform-symtab-hdr bad-terminator
+
+ # Member with explicit hex content:
+ %python %S/Inputs/generate_zos_archive.py --output %t.a \
+ --member foo.o:hex:deadbeef
+"""
+
+import argparse
+import struct
+import sys
+import os
+
+# EBCDIC / ASCII conversion table
+ASCII_TO_EBCDIC_TABLE = (
+ 0x00,0x01,0x02,0x03,0x37,0x2D,0x2E,0x2F,0x16,0x05,0x15,0x0B,0x0C,0x0D,0x0E,0x0F,
+ 0x10,0x11,0x12,0x13,0x3C,0x3D,0x32,0x26,0x18,0x19,0x3F,0x27,0x1C,0x1D,0x1E,0x1F,
+ 0x40,0x5A,0x7F,0x7B,0x5B,0x6C,0x50,0x7D,0x4D,0x5D,0x5C,0x4E,0x6B,0x60,0x4B,0x61,
+ 0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0x7A,0x5E,0x4C,0x7E,0x6E,0x6F,
+ 0x7C,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,
+ 0xD7,0xD8,0xD9,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9,0xAD,0xE0,0xBD,0x5F,0x6D,
+ 0x79,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x91,0x92,0x93,0x94,0x95,0x96,
+ 0x97,0x98,0x99,0xA2,0xA3,0xA4,0xA5,0xA6,0xA7,0xA8,0xA9,0xC0,0x4F,0xD0,0xA1,0x07,
+)
+
+
+def ascii_to_ebcdic(s):
+ """Convert an ASCII string/bytes to EBCDIC (IBM-1047)."""
+ if isinstance(s, str):
+ s = s.encode('ascii')
+ return bytes(ASCII_TO_EBCDIC_TABLE[b] for b in s)
+
+
+def ebcdic_pad(s, width, pad_char=' '):
+ """Convert ASCII string to EBCDIC, right-padded with EBCDIC spaces."""
+ ascii_padded = s.ljust(width, pad_char)
+ return ascii_to_ebcdic(ascii_padded)
+
+
+# z/OS archive magic: "!<arch>\n" in EBCDIC
+ZOS_MAGIC = b'\x5A\x4C\x81\x99\x83\x88\x6E\x15'
+
+# Terminator: "`\n" in EBCDIC
+ZOS_TERMINATOR = b'\x79\x15'
+
+# EBCDIC newline for padding
+EBCDIC_NEWLINE = b'\x15'
+
+
+def make_member_header(name, modtime, uid, gid, mode, size,
+ bad_terminator=False, empty_name=False,
+ empty_uid=False, empty_gid=False,
+ empty_modtime=False, empty_mode=False):
+ """Build a 60-byte z/OS archive member header.
+
+ Fields (all EBCDIC, space-padded):
+ ar_name: 16 bytes
+ ar_date: 12 bytes
+ ar_uid: 6 bytes
+ ar_gid: 6 bytes
+ ar_mode: 8 bytes
+ ar_size: 10 bytes
+ ar_fmag: 2 bytes (terminator)
+ Total: 60 bytes
+ """
+ # Handle long names
+ long_name_ext = b''
+ if len(name) > 16:
+ name_ebcdic = ascii_to_ebcdic(name)
+ ext_len = len(name_ebcdic)
+ display_name = "#1/%d" % ext_len
+ long_name_ext = name_ebcdic
+ # The size field includes the extended name length.
+ size = size + ext_len
+ else:
+ display_name = name
+
+ if empty_name:
+ hdr = ebcdic_pad(' ', 16)
+ else:
+ hdr = ebcdic_pad(display_name, 16)
+
+ if empty_modtime:
+ hdr += ebcdic_pad('', 12)
+ else:
+ hdr += ebcdic_pad(str(modtime), 12)
+
+ if empty_uid:
+ hdr += ebcdic_pad('', 6)
+ else:
+ hdr += ebcdic_pad(str(uid), 6)
+
+ if empty_gid:
+ hdr += ebcdic_pad('', 6)
+ else:
+ hdr += ebcdic_pad(str(gid), 6)
+
+ if empty_mode:
+ hdr += ebcdic_pad('', 8)
+ else:
+ hdr += ebcdic_pad(str(mode), 8)
+
+ hdr += ebcdic_pad(str(size), 10)
+
+ if bad_terminator:
+ hdr += b'\x00\x00'
+ else:
+ hdr += ZOS_TERMINATOR
+
+ assert len(hdr) == 60, f"Header is {len(hdr)} bytes, expected 60"
+ return hdr + long_name_ext
+
+
+def make_symtab(symbols, member_offsets, truncated=False, bad_count=False):
+ """Build a __.SYMDEF symbol table body.
+
+ symbols: list of (symbol_name_ascii, member_index, attributes)
+ member_offsets: list of offsets for each member (indexed by member_index)
+
+ Format:
+ 4 bytes: number of symbols (big-endian)
+ For each symbol: 4 bytes offset + 4 bytes attributes (big-endian)
+ Null-terminated symbol names in EBCDIC
+ """
+ num_syms = len(symbols)
+ if bad_count:
+ # Write a count that exceeds the buffer.
+ body = struct.pack('>I', 0xFFFFFFFF)
+ else:
+ body = struct.pack('>I', num_syms)
+
+ if truncated:
+ # Return just the count, truncated before offset table.
+ return body[:2]
+
+ for sym_name, mem_idx, attrs in symbols:
+ offset = member_offsets[mem_idx]
+ body += struct.pack('>II', offset, attrs)
+
+ for sym_name, mem_idx, attrs in symbols:
+ body += ascii_to_ebcdic(sym_name) + b'\x00'
+
+ return body
+
+
+def parse_member_data(raw):
+ """Parse the data portion of a --member argument.
+
+ Supports three forms:
+ /path/to/file - read file contents
+ hex:<hexstring> - decode hex bytes
+ <ascii string> - encode as raw ASCII bytes
+ """
+ if os.path.isfile(raw):
+ with open(raw, 'rb') as f:
+ return f.read()
+ if raw.startswith('hex:'):
+ return bytes.fromhex(raw[4:])
+ return raw.encode('ascii')
+
+
+# Valid malformation names for --malform-symtab-hdr, mapped to
+# make_member_header keyword arguments.
+_SYMTAB_HDR_MALFORMATIONS = {
+ 'bad-terminator': 'bad_terminator',
+ 'empty-name': 'empty_name',
+ 'empty-uid': 'empty_uid',
+ 'empty-gid': 'empty_gid',
+ 'empty-modtime': 'empty_modtime',
+ 'empty-mode': 'empty_mode',
+}
+
+
+def build_archive(args):
+ """Build the complete archive bytes."""
+ output = bytearray()
+ output += ZOS_MAGIC
+
+ if args.empty:
+ return bytes(output)
+
+ # Parse members
+ members = []
+ if args.member:
+ for m in args.member:
+ parts = m.split(':', 1)
+ name = parts[0]
+ if len(parts) > 1:
+ data = parse_member_data(parts[1])
+ else:
+ data = b'\x00' * 16 # Dummy content
+ members.append((name, data))
+
+ # Parse symbols
+ symbols = []
+ if args.symtab:
+ for s in args.symtab:
+ parts = s.split(':')
+ sym_name = parts[0]
+ mem_idx = int(parts[1]) if len(parts) > 1 else 0
+ attrs = int(parts[2]) if len(parts) > 2 else 0
+ symbols.append((sym_name, mem_idx, attrs))
+
+ # Parse symtab header malformation flags.
+ symtab_hdr_kwargs = {}
+ if args.malform_symtab_hdr:
+ key = args.malform_symtab_hdr
+ if key not in _SYMTAB_HDR_MALFORMATIONS:
+ sys.exit(f"Unknown --malform-symtab-hdr value: {key}. "
+ f"Valid: {', '.join(_SYMTAB_HDR_MALFORMATIONS.keys())}")
+ symtab_hdr_kwargs[_SYMTAB_HDR_MALFORMATIONS[key]] = True
+
+ # Phase 1: Compute member offsets
+ # Start after magic
+ pos = len(ZOS_MAGIC)
+
+ # If we have a symbol table, it comes first
+ symtab_body = None
+ has_symtab = (symbols or args.symtab_no_symbols or args.symtab_truncated
+ or args.symtab_bad_count)
+ if has_symtab:
+ # We need to compute the symtab size, but symtab contains member
+ # offsets, which depend on symtab size so we do two passes.
+
+ # First pass: compute symtab body with placeholder offsets.
+ if args.symtab_truncated:
+ symtab_body = make_symtab([], [], truncated=True)
+ elif args.symtab_bad_count:
+ symtab_body = make_symtab([], [], bad_count=True)
+ elif args.symtab_no_symbols:
+ symtab_body = struct.pack('>I', 0) # 0 symbols
+ else:
+ placeholder_offsets = [0] * (len(members) + 1)
+ symtab_body = make_symtab(symbols, placeholder_offsets)
+
+ symtab_hdr_size = 60 # Fixed header for __.SYMDEF
+ symtab_total = symtab_hdr_size + len(symtab_body)
+ # Padding to even boundary
+ if symtab_total % 2 != 0:
+ symtab_total += 1
+ pos += symtab_total
+
+ # Compute member offsets
+ member_offsets = []
+ for name, data in members:
+ member_offsets.append(pos)
+ hdr_size = 60
+ name_ext = 0
+ if len(name) > 16:
+ name_ext = len(ascii_to_ebcdic(name))
+ total = hdr_size + name_ext + len(data)
+ if total % 2 != 0:
+ total += 1
+ pos += total
+
+ # Second pass: recompute symtab with correct offsets.
+ if symbols and not args.symtab_truncated and not args.symtab_bad_count:
+ symtab_body = make_symtab(symbols, member_offsets)
+
+ # Phase 2: Write output
+ if symtab_body is not None:
+ symtab_hdr = make_member_header(
+ '__.SYMDEF', 0, 0, 0, 0, len(symtab_body),
+ **symtab_hdr_kwargs)
+ output += symtab_hdr
+ output += symtab_body
+ # Pad to even boundary
+ if len(output) % 2 != 0:
+ output += EBCDIC_NEWLINE
+
+ for i, (name, data) in enumerate(members):
+ hdr = make_member_header(
+ name, 1234567890, 0, 0, 100644, len(data),
+ bad_terminator=args.bad_terminator,
+ empty_name=args.empty_name,
+ empty_uid=args.empty_uid,
+ empty_gid=args.empty_gid,
+ empty_modtime=args.empty_modtime,
+ empty_mode=args.empty_mode,
+ )
+ output += hdr
+ output += data
+ if len(output) % 2 != 0:
+ output += EBCDIC_NEWLINE
+
+ return bytes(output)
+
+
+def main():
+ parser = argparse.ArgumentParser(
+ description='Generate z/OS archive files for testing')
+ parser.add_argument('--output', '-o', required=True,
+ help='Output file path')
+ parser.add_argument('--empty', action='store_true',
+ help='Create an empty archive (magic only)')
+ parser.add_argument('--member', action='append',
+ help='Add member as name[:data]. '
+ 'Data can be a file path, hex:DEADBEEF, '
+ 'or a raw ASCII string. If omitted, uses '
+ '16 zero bytes as dummy content.')
+ parser.add_argument('--symtab', action='append',
+ help='Add symbol: name[:member_index[:attributes]]')
+ parser.add_argument('--symtab-no-symbols', action='store_true',
+ help='Add empty symbol table (0 symbols)')
+ parser.add_argument('--symtab-truncated', action='store_true',
+ help='Create truncated symbol table')
+ parser.add_argument('--symtab-bad-count', action='store_true',
+ help='Symbol count exceeds buffer')
+ parser.add_argument('--malform-symtab-hdr', metavar='MALFORMATION',
+ help='Apply a malformation to the __.SYMDEF header. '
+ 'Valid values: bad-terminator, empty-name, '
+ 'empty-uid, empty-gid, empty-modtime, empty-mode')
+ parser.add_argument('--bad-terminator', action='store_true',
+ help='Use invalid terminator on member headers')
+ parser.add_argument('--empty-name', action='store_true',
+ help='Empty/space-leading name on member headers')
+ parser.add_argument('--empty-uid', action='store_true',
+ help='Empty UID on member headers')
+ parser.add_argument('--empty-gid', action='store_true',
+ help='Empty GID on member headers')
+ parser.add_argument('--empty-modtime', action='store_true',
+ help='Empty LastModified on member headers')
+ parser.add_argument('--empty-mode', action='store_true',
+ help='Empty AccessMode on member headers')
+ args = parser.parse_args()
+
+ data = build_archive(args)
+ with open(args.output, 'wb') as f:
+ f.write(data)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/llvm/test/Object/Inputs/zos-archive-test.a b/llvm/test/Object/Inputs/zos-archive-test.a
deleted file mode 100644
index 0244f176c64478cec3918b1ca6bed78b897eb2c1..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 1424
zcmbtUPfO!K6n}C0?;uOvZJ{h8rFa%ULOkuEMS4&O;=wPFy^9_q3yVkbQ1K`T{Twyz
zQZEwNMe!n`dT>uWjcN2`_q~}U9b at CN_~6a^|C{$_CfR809e>;ZS~ku4;Qm*)_d}P&
zLrFsF>Z<F*AWGOS1AvCSM*O|i0_1ut3di-*p`rx7FPuZ~Dco+m%s8U_AJN{a#AM{n
zoYZ1~EioBD7H8VAF%j_yYX&)q*;jEiOSM9sK>;8`M$R_v%WhBL5f-&cGL at 2>Q6d)2
zr1VFdOe=};nH8%Og=yFywE_j(+}*vp>Ue08Ba18=r(j5!J0Vt`=!&L+Ch&6g=F!of
zC%~w$9CEV1no<YT&#zbjOX8iG=iTd{0+0I2Pr*~+UTr3V49`zaRj_-#!ENXI7SZV*
zXtwA+mR|scMzVQwi(1hhF!~OJZ%&8^OBlHH8PsH^^i-mN$Gw0*3?GMqKk|J3z>#pg
z{1?tk8aBy3QKp5wi7xHn&Y3k_r?CRAwGMWvy$GcyxQ)Z23r)w(RBjC3@!L<J8IF^|
z%vnQs7PAWmx92OX*+0NO5?<RM-+#V1$&JTjhisx}Dd2#l4n<s-Fn$`krHJQ at yo~bX
zdx=~mU%VAH at Gl+lqW7Yv*6^?wM!{(G`*J{k7(A!M$#FG(qO&UM%XGQ&;>GKx+sSOv
Xf{c`};-bXq^`{Wi<G_bldx!EDV(Pt2
diff --git a/llvm/test/Object/Inputs/zos-expected-foo.o b/llvm/test/Object/Inputs/zos-expected-foo.o
deleted file mode 100644
index 4caf23315e4c9f140c3a0c11b92af7e0dfc77c7c..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 1280
zcmbtSy-EW?5T4EE=L(UiDT0WEU at bnvSy?EOM!3QvFOVnD;%uzNLTf?DbHp>CjT{I{
z8abkcR<4)3Xl0z)y`S|=BI3aA{D1rH?r;Hr6*jGbtK`(0Dg8}QorS}blRXCu5sxA{
zTv^O39f#Sv7ug&PfIK-xr{&yTb|oHR5lzw)C3_eZVpDDcKRRSwOM=hs#7qs&kRMS2
z!)fjB-`{t9^e9lkC5clq60A*y*HT-v7%+)f+-#hl9{3WBcolJF`PH>P82^4{18jwN
z(Yn6u-AX*-RT_h*!~Hl+CON*Jx|QK{yZy&b?-9}IALwrJf2 at 52ikvKY3d>$e4>-<?
ztmJc2#aPn8?TSNPRjNoePWZwfiox6a+b{_IKnw&5UazzvM1>(qzCe{Wu3ZeVqbGO9
z^4zU;@a!FMD(w}hG{D<BE_={$y?ph-5}ly^0nBpUJXX$Frn^#DwuG}(-7dTWpC`O_
zFnV6RIV+AvBbR)VPo?09v<YS0S22Gcg_TO~8AmzQsd<S*q`%FIp7_m1ym(&p(mFo&
zaySgbm%F}cn)FVYmlJw<YV#V}YxKG5<K=YI at 8p}b;X*0ZaMSWM{S$<nNn$p!{$YFp
DKWm9X
diff --git a/llvm/test/Object/zos-archive-read.test b/llvm/test/Object/zos-archive-read.test
index 2afcb1b79ee22..d9ba95c211c9e 100644
--- a/llvm/test/Object/zos-archive-read.test
+++ b/llvm/test/Object/zos-archive-read.test
@@ -1,13 +1,47 @@
-## Test reading a z/OS archive.
+## Test reading a valid z/OS archive.
+# RUN: %python %p/Inputs/generate_zos_archive.py --output %t.valid.a \
+# RUN: --symtab "foo.txt:0" --member foo.txt:abcd
+# RUN: llvm-ar t %t.valid.a | FileCheck %s --check-prefix=LIST
+# RUN: llvm-nm --print-armap %t.valid.a | FileCheck %s --check-prefix=SYMS
+# RUN: llvm-ar p %t.valid.a foo.txt | FileCheck %s --check-prefix=CONTENT
-# RUN: llvm-ar t %p/Inputs/zos-archive-test.a | FileCheck %s --check-prefix=LIST
-# RUN: llvm-nm --print-armap %p/Inputs/zos-archive-test.a | FileCheck %s --check-prefix=SYMS
+# LIST: foo.txt
+# SYMS: Archive map
+# SYMS-NEXT: foo.txt in foo.txt
+# CONTENT: abcd
-## Test extracting a member and verifying its content.
-# RUN: llvm-ar p %p/Inputs/zos-archive-test.a foo.o > %t.foo.o
-# RUN: cmp %t.foo.o %p/Inputs/zos-expected-foo.o
+## Test malformed terminator on member header
+# RUN: %python %p/Inputs/generate_zos_archive.py --output %t.bad_term.a \
+# RUN: --member foo.txt --bad-terminator
+# RUN: not llvm-ar t %t.bad_term.a 2>&1 | FileCheck %s --check-prefix=ERR-TERM
+# ERR-TERM: terminator characters in archive member
-# LIST: foo.o
+## Test empty UID field on member header
+# RUN: %python %p/Inputs/generate_zos_archive.py --output %t.empty_uid.a \
+# RUN: --member foo.txt --empty-uid
+# RUN: not llvm-ar t %t.empty_uid.a 2>&1 | FileCheck %s --check-prefix=ERR-UID
+# ERR-UID: UID field is empty or contains only spaces
-# SYMS: Archive map
-# SYMS-NEXT: foo in foo.o
+## Test leading space in member name
+# RUN: %python %p/Inputs/generate_zos_archive.py --output %t.empty_name.a \
+# RUN: --member foo.txt --empty-name
+# RUN: not llvm-ar t %t.empty_name.a 2>&1 | FileCheck %s --check-prefix=ERR-NAME
+# ERR-NAME: name contains a leading space for archive member header
+
+## Test truncated z/OS symbol table
+# RUN: %python %p/Inputs/generate_zos_archive.py --output %t.trunc_sym.a \
+# RUN: --symtab-truncated
+# RUN: not llvm-ar t %t.trunc_sym.a 2>&1 | FileCheck %s --check-prefix=ERR-TRUNC
+# ERR-TRUNC: z/OS symbol table is too small to read the symbol count
+
+## Test z/OS symbol table where count exceeds buffer
+# RUN: %python %p/Inputs/generate_zos_archive.py --output %t.bad_count.a \
+# RUN: --symtab-bad-count
+# RUN: not llvm-ar t %t.bad_count.a 2>&1 | FileCheck %s --check-prefix=ERR-COUNT
+# ERR-COUNT: z/OS symbol table count exceeds buffer size
+
+## Test malformed __.SYMDEF header
+# RUN: %python %p/Inputs/generate_zos_archive.py --output %t.bad_sym_hdr.a \
+# RUN: --member foo.txt --symtab foo.txt:0 --malform-symtab-hdr empty-mode
+# RUN: not llvm-ar t %t.bad_sym_hdr.a 2>&1 | FileCheck %s --check-prefix=ERR-SYM-HDR
+# ERR-SYM-HDR: AccessMode field is empty or contains only spaces
\ No newline at end of file
>From 967636eac9fba232f09318ccc7094d578e02e63d Mon Sep 17 00:00:00 2001
From: Uyiosa Iyekekpolor <uyiosaben at gmail.com>
Date: Tue, 21 Apr 2026 20:06:35 -0400
Subject: [PATCH 11/15] Run clang format
---
llvm/lib/Object/Archive.cpp | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/llvm/lib/Object/Archive.cpp b/llvm/lib/Object/Archive.cpp
index 6d1323d5aa0a2..a7a72e01a6526 100644
--- a/llvm/lib/Object/Archive.cpp
+++ b/llvm/lib/Object/Archive.cpp
@@ -105,7 +105,8 @@ ArchiveMemberHeader::ArchiveMemberHeader(const Archive *Parent,
*Err = createMemberHeaderParseError(this, RawHeaderPtr, Size);
return;
}
- // '\x79\x15' is the EBCDIC equivalent of '`\n' for the z/OS archive terminator.
+ // '\x79\x15' is the EBCDIC equivalent of '`\n' for the z/OS archive
+ // terminator.
bool ValidTerminator =
Parent->kind() == Archive::K_ZOS
? (ArMemHdr->Terminator[0] == '\x79' &&
>From c44535da97cdbdd0d98ecfcdc03637f8512559c5 Mon Sep 17 00:00:00 2001
From: Uyiosa Iyekekpolor <uyiosaben at gmail.com>
Date: Tue, 21 Apr 2026 20:19:01 -0400
Subject: [PATCH 12/15] run python formatter
---
.../Object/Inputs/generate_zos_archive.py | 204 +++++++++++-------
1 file changed, 126 insertions(+), 78 deletions(-)
diff --git a/llvm/test/Object/Inputs/generate_zos_archive.py b/llvm/test/Object/Inputs/generate_zos_archive.py
index a3866d23aa3e4..124187d1fa8b4 100644
--- a/llvm/test/Object/Inputs/generate_zos_archive.py
+++ b/llvm/test/Object/Inputs/generate_zos_archive.py
@@ -31,6 +31,7 @@
import os
# EBCDIC / ASCII conversion table
+# fmt: off
ASCII_TO_EBCDIC_TABLE = (
0x00,0x01,0x02,0x03,0x37,0x2D,0x2E,0x2F,0x16,0x05,0x15,0x0B,0x0C,0x0D,0x0E,0x0F,
0x10,0x11,0x12,0x13,0x3C,0x3D,0x32,0x26,0x18,0x19,0x3F,0x27,0x1C,0x1D,0x1E,0x1F,
@@ -41,35 +42,46 @@
0x79,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x91,0x92,0x93,0x94,0x95,0x96,
0x97,0x98,0x99,0xA2,0xA3,0xA4,0xA5,0xA6,0xA7,0xA8,0xA9,0xC0,0x4F,0xD0,0xA1,0x07,
)
+# fmt: on
def ascii_to_ebcdic(s):
"""Convert an ASCII string/bytes to EBCDIC (IBM-1047)."""
if isinstance(s, str):
- s = s.encode('ascii')
+ s = s.encode("ascii")
return bytes(ASCII_TO_EBCDIC_TABLE[b] for b in s)
-def ebcdic_pad(s, width, pad_char=' '):
+def ebcdic_pad(s, width, pad_char=" "):
"""Convert ASCII string to EBCDIC, right-padded with EBCDIC spaces."""
ascii_padded = s.ljust(width, pad_char)
return ascii_to_ebcdic(ascii_padded)
# z/OS archive magic: "!<arch>\n" in EBCDIC
-ZOS_MAGIC = b'\x5A\x4C\x81\x99\x83\x88\x6E\x15'
+ZOS_MAGIC = b"\x5a\x4c\x81\x99\x83\x88\x6e\x15"
# Terminator: "`\n" in EBCDIC
-ZOS_TERMINATOR = b'\x79\x15'
+ZOS_TERMINATOR = b"\x79\x15"
# EBCDIC newline for padding
-EBCDIC_NEWLINE = b'\x15'
-
-
-def make_member_header(name, modtime, uid, gid, mode, size,
- bad_terminator=False, empty_name=False,
- empty_uid=False, empty_gid=False,
- empty_modtime=False, empty_mode=False):
+EBCDIC_NEWLINE = b"\x15"
+
+
+def make_member_header(
+ name,
+ modtime,
+ uid,
+ gid,
+ mode,
+ size,
+ bad_terminator=False,
+ empty_name=False,
+ empty_uid=False,
+ empty_gid=False,
+ empty_modtime=False,
+ empty_mode=False,
+):
"""Build a 60-byte z/OS archive member header.
Fields (all EBCDIC, space-padded):
@@ -83,7 +95,7 @@ def make_member_header(name, modtime, uid, gid, mode, size,
Total: 60 bytes
"""
# Handle long names
- long_name_ext = b''
+ long_name_ext = b""
if len(name) > 16:
name_ebcdic = ascii_to_ebcdic(name)
ext_len = len(name_ebcdic)
@@ -95,34 +107,34 @@ def make_member_header(name, modtime, uid, gid, mode, size,
display_name = name
if empty_name:
- hdr = ebcdic_pad(' ', 16)
+ hdr = ebcdic_pad(" ", 16)
else:
hdr = ebcdic_pad(display_name, 16)
if empty_modtime:
- hdr += ebcdic_pad('', 12)
+ hdr += ebcdic_pad("", 12)
else:
hdr += ebcdic_pad(str(modtime), 12)
if empty_uid:
- hdr += ebcdic_pad('', 6)
+ hdr += ebcdic_pad("", 6)
else:
hdr += ebcdic_pad(str(uid), 6)
if empty_gid:
- hdr += ebcdic_pad('', 6)
+ hdr += ebcdic_pad("", 6)
else:
hdr += ebcdic_pad(str(gid), 6)
if empty_mode:
- hdr += ebcdic_pad('', 8)
+ hdr += ebcdic_pad("", 8)
else:
hdr += ebcdic_pad(str(mode), 8)
hdr += ebcdic_pad(str(size), 10)
if bad_terminator:
- hdr += b'\x00\x00'
+ hdr += b"\x00\x00"
else:
hdr += ZOS_TERMINATOR
@@ -144,9 +156,9 @@ def make_symtab(symbols, member_offsets, truncated=False, bad_count=False):
num_syms = len(symbols)
if bad_count:
# Write a count that exceeds the buffer.
- body = struct.pack('>I', 0xFFFFFFFF)
+ body = struct.pack(">I", 0xFFFFFFFF)
else:
- body = struct.pack('>I', num_syms)
+ body = struct.pack(">I", num_syms)
if truncated:
# Return just the count, truncated before offset table.
@@ -154,10 +166,10 @@ def make_symtab(symbols, member_offsets, truncated=False, bad_count=False):
for sym_name, mem_idx, attrs in symbols:
offset = member_offsets[mem_idx]
- body += struct.pack('>II', offset, attrs)
+ body += struct.pack(">II", offset, attrs)
for sym_name, mem_idx, attrs in symbols:
- body += ascii_to_ebcdic(sym_name) + b'\x00'
+ body += ascii_to_ebcdic(sym_name) + b"\x00"
return body
@@ -171,22 +183,22 @@ def parse_member_data(raw):
<ascii string> - encode as raw ASCII bytes
"""
if os.path.isfile(raw):
- with open(raw, 'rb') as f:
+ with open(raw, "rb") as f:
return f.read()
- if raw.startswith('hex:'):
+ if raw.startswith("hex:"):
return bytes.fromhex(raw[4:])
- return raw.encode('ascii')
+ return raw.encode("ascii")
# Valid malformation names for --malform-symtab-hdr, mapped to
# make_member_header keyword arguments.
_SYMTAB_HDR_MALFORMATIONS = {
- 'bad-terminator': 'bad_terminator',
- 'empty-name': 'empty_name',
- 'empty-uid': 'empty_uid',
- 'empty-gid': 'empty_gid',
- 'empty-modtime': 'empty_modtime',
- 'empty-mode': 'empty_mode',
+ "bad-terminator": "bad_terminator",
+ "empty-name": "empty_name",
+ "empty-uid": "empty_uid",
+ "empty-gid": "empty_gid",
+ "empty-modtime": "empty_modtime",
+ "empty-mode": "empty_mode",
}
@@ -202,19 +214,19 @@ def build_archive(args):
members = []
if args.member:
for m in args.member:
- parts = m.split(':', 1)
+ parts = m.split(":", 1)
name = parts[0]
if len(parts) > 1:
data = parse_member_data(parts[1])
else:
- data = b'\x00' * 16 # Dummy content
+ data = b"\x00" * 16 # Dummy content
members.append((name, data))
# Parse symbols
symbols = []
if args.symtab:
for s in args.symtab:
- parts = s.split(':')
+ parts = s.split(":")
sym_name = parts[0]
mem_idx = int(parts[1]) if len(parts) > 1 else 0
attrs = int(parts[2]) if len(parts) > 2 else 0
@@ -225,8 +237,10 @@ def build_archive(args):
if args.malform_symtab_hdr:
key = args.malform_symtab_hdr
if key not in _SYMTAB_HDR_MALFORMATIONS:
- sys.exit(f"Unknown --malform-symtab-hdr value: {key}. "
- f"Valid: {', '.join(_SYMTAB_HDR_MALFORMATIONS.keys())}")
+ sys.exit(
+ f"Unknown --malform-symtab-hdr value: {key}. "
+ f"Valid: {', '.join(_SYMTAB_HDR_MALFORMATIONS.keys())}"
+ )
symtab_hdr_kwargs[_SYMTAB_HDR_MALFORMATIONS[key]] = True
# Phase 1: Compute member offsets
@@ -235,8 +249,12 @@ def build_archive(args):
# If we have a symbol table, it comes first
symtab_body = None
- has_symtab = (symbols or args.symtab_no_symbols or args.symtab_truncated
- or args.symtab_bad_count)
+ has_symtab = (
+ symbols
+ or args.symtab_no_symbols
+ or args.symtab_truncated
+ or args.symtab_bad_count
+ )
if has_symtab:
# We need to compute the symtab size, but symtab contains member
# offsets, which depend on symtab size so we do two passes.
@@ -247,7 +265,7 @@ def build_archive(args):
elif args.symtab_bad_count:
symtab_body = make_symtab([], [], bad_count=True)
elif args.symtab_no_symbols:
- symtab_body = struct.pack('>I', 0) # 0 symbols
+ symtab_body = struct.pack(">I", 0) # 0 symbols
else:
placeholder_offsets = [0] * (len(members) + 1)
symtab_body = make_symtab(symbols, placeholder_offsets)
@@ -279,8 +297,8 @@ def build_archive(args):
# Phase 2: Write output
if symtab_body is not None:
symtab_hdr = make_member_header(
- '__.SYMDEF', 0, 0, 0, 0, len(symtab_body),
- **symtab_hdr_kwargs)
+ "__.SYMDEF", 0, 0, 0, 0, len(symtab_body), **symtab_hdr_kwargs
+ )
output += symtab_hdr
output += symtab_body
# Pad to even boundary
@@ -289,7 +307,12 @@ def build_archive(args):
for i, (name, data) in enumerate(members):
hdr = make_member_header(
- name, 1234567890, 0, 0, 100644, len(data),
+ name,
+ 1234567890,
+ 0,
+ 0,
+ 100644,
+ len(data),
bad_terminator=args.bad_terminator,
empty_name=args.empty_name,
empty_uid=args.empty_uid,
@@ -307,46 +330,71 @@ def build_archive(args):
def main():
parser = argparse.ArgumentParser(
- description='Generate z/OS archive files for testing')
- parser.add_argument('--output', '-o', required=True,
- help='Output file path')
- parser.add_argument('--empty', action='store_true',
- help='Create an empty archive (magic only)')
- parser.add_argument('--member', action='append',
- help='Add member as name[:data]. '
- 'Data can be a file path, hex:DEADBEEF, '
- 'or a raw ASCII string. If omitted, uses '
- '16 zero bytes as dummy content.')
- parser.add_argument('--symtab', action='append',
- help='Add symbol: name[:member_index[:attributes]]')
- parser.add_argument('--symtab-no-symbols', action='store_true',
- help='Add empty symbol table (0 symbols)')
- parser.add_argument('--symtab-truncated', action='store_true',
- help='Create truncated symbol table')
- parser.add_argument('--symtab-bad-count', action='store_true',
- help='Symbol count exceeds buffer')
- parser.add_argument('--malform-symtab-hdr', metavar='MALFORMATION',
- help='Apply a malformation to the __.SYMDEF header. '
- 'Valid values: bad-terminator, empty-name, '
- 'empty-uid, empty-gid, empty-modtime, empty-mode')
- parser.add_argument('--bad-terminator', action='store_true',
- help='Use invalid terminator on member headers')
- parser.add_argument('--empty-name', action='store_true',
- help='Empty/space-leading name on member headers')
- parser.add_argument('--empty-uid', action='store_true',
- help='Empty UID on member headers')
- parser.add_argument('--empty-gid', action='store_true',
- help='Empty GID on member headers')
- parser.add_argument('--empty-modtime', action='store_true',
- help='Empty LastModified on member headers')
- parser.add_argument('--empty-mode', action='store_true',
- help='Empty AccessMode on member headers')
+ description="Generate z/OS archive files for testing"
+ )
+ parser.add_argument("--output", "-o", required=True, help="Output file path")
+ parser.add_argument(
+ "--empty", action="store_true", help="Create an empty archive (magic only)"
+ )
+ parser.add_argument(
+ "--member",
+ action="append",
+ help="Add member as name[:data]. "
+ "Data can be a file path, hex:DEADBEEF, "
+ "or a raw ASCII string. If omitted, uses "
+ "16 zero bytes as dummy content.",
+ )
+ parser.add_argument(
+ "--symtab", action="append", help="Add symbol: name[:member_index[:attributes]]"
+ )
+ parser.add_argument(
+ "--symtab-no-symbols",
+ action="store_true",
+ help="Add empty symbol table (0 symbols)",
+ )
+ parser.add_argument(
+ "--symtab-truncated", action="store_true", help="Create truncated symbol table"
+ )
+ parser.add_argument(
+ "--symtab-bad-count", action="store_true", help="Symbol count exceeds buffer"
+ )
+ parser.add_argument(
+ "--malform-symtab-hdr",
+ metavar="MALFORMATION",
+ help="Apply a malformation to the __.SYMDEF header. "
+ "Valid values: bad-terminator, empty-name, "
+ "empty-uid, empty-gid, empty-modtime, empty-mode",
+ )
+ parser.add_argument(
+ "--bad-terminator",
+ action="store_true",
+ help="Use invalid terminator on member headers",
+ )
+ parser.add_argument(
+ "--empty-name",
+ action="store_true",
+ help="Empty/space-leading name on member headers",
+ )
+ parser.add_argument(
+ "--empty-uid", action="store_true", help="Empty UID on member headers"
+ )
+ parser.add_argument(
+ "--empty-gid", action="store_true", help="Empty GID on member headers"
+ )
+ parser.add_argument(
+ "--empty-modtime",
+ action="store_true",
+ help="Empty LastModified on member headers",
+ )
+ parser.add_argument(
+ "--empty-mode", action="store_true", help="Empty AccessMode on member headers"
+ )
args = parser.parse_args()
data = build_archive(args)
- with open(args.output, 'wb') as f:
+ with open(args.output, "wb") as f:
f.write(data)
-if __name__ == '__main__':
+if __name__ == "__main__":
main()
>From ef764e2ce931d413d3cdc5d8e364d8799aeba6ad Mon Sep 17 00:00:00 2001
From: Uyiosa Iyekekpolor <uyiosaben at gmail.com>
Date: Tue, 5 May 2026 22:36:06 -0400
Subject: [PATCH 13/15] Add more test coverage, address review comments
---
llvm/lib/Object/Archive.cpp | 8 +++-
.../Object/Inputs/generate_zos_archive.py | 34 +++++++--------
llvm/test/Object/zos-archive-read.test | 42 ++++++++++++++-----
3 files changed, 55 insertions(+), 29 deletions(-)
diff --git a/llvm/lib/Object/Archive.cpp b/llvm/lib/Object/Archive.cpp
index a7a72e01a6526..ed0f6e2dec972 100644
--- a/llvm/lib/Object/Archive.cpp
+++ b/llvm/lib/Object/Archive.cpp
@@ -1638,14 +1638,18 @@ ZOSArchive::ZOSArchive(MemoryBufferRef Source, Error &Err)
StringRef EbcdicSymbolTable = BufOrErr.get();
if (EbcdicSymbolTable.size() < sizeof(uint32_t)) {
Err = malformedError(
- "z/OS symbol table is too small to read the symbol count");
+ "z/OS archive symbol table is too small to read the symbol count, "
+ "size is " + Twine(EbcdicSymbolTable.size()));
return;
}
uint64_t EbcdicSymbolCount = read32be(EbcdicSymbolTable.data());
uint64_t OffsetToEbcdicNames =
sizeof(uint32_t) + (EbcdicSymbolCount * (sizeof(uint64_t)));
if (OffsetToEbcdicNames > EbcdicSymbolTable.size()) {
- Err = malformedError("z/OS symbol table count exceeds buffer size");
+ Err = malformedError("z/OS archive symbol table names offset " +
+ Twine(OffsetToEbcdicNames) +
+ " exceeds symbol table size " +
+ Twine(EbcdicSymbolTable.size()));
return;
}
uint64_t EbcdicNamesSize = EbcdicSymbolTable.size() - OffsetToEbcdicNames;
diff --git a/llvm/test/Object/Inputs/generate_zos_archive.py b/llvm/test/Object/Inputs/generate_zos_archive.py
index 124187d1fa8b4..5489612f2633e 100644
--- a/llvm/test/Object/Inputs/generate_zos_archive.py
+++ b/llvm/test/Object/Inputs/generate_zos_archive.py
@@ -30,7 +30,7 @@
import sys
import os
-# EBCDIC / ASCII conversion table
+# EBCDIC / ASCII conversion table.
# fmt: off
ASCII_TO_EBCDIC_TABLE = (
0x00,0x01,0x02,0x03,0x37,0x2D,0x2E,0x2F,0x16,0x05,0x15,0x0B,0x0C,0x0D,0x0E,0x0F,
@@ -58,13 +58,13 @@ def ebcdic_pad(s, width, pad_char=" "):
return ascii_to_ebcdic(ascii_padded)
-# z/OS archive magic: "!<arch>\n" in EBCDIC
+# z/OS archive magic: "!<arch>\n" in EBCDIC.
ZOS_MAGIC = b"\x5a\x4c\x81\x99\x83\x88\x6e\x15"
-# Terminator: "`\n" in EBCDIC
+# Terminator: "`\n" in EBCDIC.
ZOS_TERMINATOR = b"\x79\x15"
-# EBCDIC newline for padding
+# EBCDIC newline for padding.
EBCDIC_NEWLINE = b"\x15"
@@ -94,7 +94,7 @@ def make_member_header(
ar_fmag: 2 bytes (terminator)
Total: 60 bytes
"""
- # Handle long names
+ # Handle long names.
long_name_ext = b""
if len(name) > 16:
name_ebcdic = ascii_to_ebcdic(name)
@@ -210,7 +210,7 @@ def build_archive(args):
if args.empty:
return bytes(output)
- # Parse members
+ # Parse members.
members = []
if args.member:
for m in args.member:
@@ -219,10 +219,10 @@ def build_archive(args):
if len(parts) > 1:
data = parse_member_data(parts[1])
else:
- data = b"\x00" * 16 # Dummy content
+ data = b"\x00" * 16 # Dummy content.
members.append((name, data))
- # Parse symbols
+ # Parse symbols.
symbols = []
if args.symtab:
for s in args.symtab:
@@ -243,11 +243,11 @@ def build_archive(args):
)
symtab_hdr_kwargs[_SYMTAB_HDR_MALFORMATIONS[key]] = True
- # Phase 1: Compute member offsets
- # Start after magic
+ # Phase 1: Compute member offsets.
+ # Start after magic.
pos = len(ZOS_MAGIC)
- # If we have a symbol table, it comes first
+ # If we have a symbol table, it comes first.
symtab_body = None
has_symtab = (
symbols
@@ -265,19 +265,19 @@ def build_archive(args):
elif args.symtab_bad_count:
symtab_body = make_symtab([], [], bad_count=True)
elif args.symtab_no_symbols:
- symtab_body = struct.pack(">I", 0) # 0 symbols
+ symtab_body = struct.pack(">I", 0) # 0 symbols.
else:
placeholder_offsets = [0] * (len(members) + 1)
symtab_body = make_symtab(symbols, placeholder_offsets)
- symtab_hdr_size = 60 # Fixed header for __.SYMDEF
+ symtab_hdr_size = 60 # Fixed header for __.SYMDEF.
symtab_total = symtab_hdr_size + len(symtab_body)
- # Padding to even boundary
+ # Padding to even boundary.
if symtab_total % 2 != 0:
symtab_total += 1
pos += symtab_total
- # Compute member offsets
+ # Compute member offsets.
member_offsets = []
for name, data in members:
member_offsets.append(pos)
@@ -294,14 +294,14 @@ def build_archive(args):
if symbols and not args.symtab_truncated and not args.symtab_bad_count:
symtab_body = make_symtab(symbols, member_offsets)
- # Phase 2: Write output
+ # Phase 2: Write output.
if symtab_body is not None:
symtab_hdr = make_member_header(
"__.SYMDEF", 0, 0, 0, 0, len(symtab_body), **symtab_hdr_kwargs
)
output += symtab_hdr
output += symtab_body
- # Pad to even boundary
+ # Pad to even boundary.
if len(output) % 2 != 0:
output += EBCDIC_NEWLINE
diff --git a/llvm/test/Object/zos-archive-read.test b/llvm/test/Object/zos-archive-read.test
index d9ba95c211c9e..c6b52740bb5c3 100644
--- a/llvm/test/Object/zos-archive-read.test
+++ b/llvm/test/Object/zos-archive-read.test
@@ -10,38 +10,60 @@
# SYMS-NEXT: foo.txt in foo.txt
# CONTENT: abcd
-## Test malformed terminator on member header
+## Test malformed terminator on member header.
# RUN: %python %p/Inputs/generate_zos_archive.py --output %t.bad_term.a \
# RUN: --member foo.txt --bad-terminator
# RUN: not llvm-ar t %t.bad_term.a 2>&1 | FileCheck %s --check-prefix=ERR-TERM
# ERR-TERM: terminator characters in archive member
-## Test empty UID field on member header
+## Test empty UID field on member header.
+## We know that the member header starts at offset 8 because no
+## symbol table name is generated.
# RUN: %python %p/Inputs/generate_zos_archive.py --output %t.empty_uid.a \
# RUN: --member foo.txt --empty-uid
# RUN: not llvm-ar t %t.empty_uid.a 2>&1 | FileCheck %s --check-prefix=ERR-UID
-# ERR-UID: UID field is empty or contains only spaces
+# ERR-UID: UID field is empty or contains only spaces in archive member header at offset 8
-## Test leading space in member name
+## Test leading space in member name.
# RUN: %python %p/Inputs/generate_zos_archive.py --output %t.empty_name.a \
# RUN: --member foo.txt --empty-name
# RUN: not llvm-ar t %t.empty_name.a 2>&1 | FileCheck %s --check-prefix=ERR-NAME
# ERR-NAME: name contains a leading space for archive member header
-## Test truncated z/OS symbol table
+## Test truncated z/OS symbol table.
# RUN: %python %p/Inputs/generate_zos_archive.py --output %t.trunc_sym.a \
# RUN: --symtab-truncated
# RUN: not llvm-ar t %t.trunc_sym.a 2>&1 | FileCheck %s --check-prefix=ERR-TRUNC
-# ERR-TRUNC: z/OS symbol table is too small to read the symbol count
+# ERR-TRUNC: z/OS archive symbol table is too small to read the symbol count, size is 2
-## Test z/OS symbol table where count exceeds buffer
+## Test z/OS symbol table where count exceeds buffer.
# RUN: %python %p/Inputs/generate_zos_archive.py --output %t.bad_count.a \
# RUN: --symtab-bad-count
# RUN: not llvm-ar t %t.bad_count.a 2>&1 | FileCheck %s --check-prefix=ERR-COUNT
-# ERR-COUNT: z/OS symbol table count exceeds buffer size
+# ERR-COUNT: z/OS archive symbol table names offset {{[0-9]+}} exceeds symbol table size 4
-## Test malformed __.SYMDEF header
+## Test malformed __.SYMDEF header.
# RUN: %python %p/Inputs/generate_zos_archive.py --output %t.bad_sym_hdr.a \
# RUN: --member foo.txt --symtab foo.txt:0 --malform-symtab-hdr empty-mode
# RUN: not llvm-ar t %t.bad_sym_hdr.a 2>&1 | FileCheck %s --check-prefix=ERR-SYM-HDR
-# ERR-SYM-HDR: AccessMode field is empty or contains only spaces
\ No newline at end of file
+# ERR-SYM-HDR: AccessMode field is empty or contains only spaces
+
+## Test empty LastModified field.
+# RUN: %python %p/Inputs/generate_zos_archive.py --output %t.empty_modtime.a \
+# RUN: --member foo.txt --empty-modtime
+# RUN: not llvm-ar t %t.empty_modtime.a 2>&1 | FileCheck %s --check-prefix=ERR-MODTIME
+# ERR-MODTIME: LastModified field is empty or contains only spaces in archive member header at offset 8
+
+## Test empty GID field.
+# RUN: %python %p/Inputs/generate_zos_archive.py --output %t.empty_gid.a \
+# RUN: --member foo.txt --empty-gid
+# RUN: not llvm-ar t %t.empty_gid.a 2>&1 | FileCheck %s --check-prefix=ERR-GID
+# ERR-GID: GID field is empty or contains only spaces in archive member header at offset 8
+
+## Test that a truncated archive with incomplete member header is rejected.
+## The z/OS magic is 8 bytes and a member header requires 60 bytes.
+## Writing only 3 bytes after the magic is not enough for a valid header.
+# RUN: printf '\x5A\x4C\x81\x99\x83\x88\x6E\x15' > %t.badhdr.a
+# RUN: printf '\x00\x00\x00' >> %t.badhdr.a
+# RUN: not llvm-ar t %t.badhdr.a 2>&1 | FileCheck %s --check-prefix=ERR-CHILD
+# ERR-CHILD: truncated or malformed archive
>From c2241247f621f065b7175e5715a02bcf6e32dedd Mon Sep 17 00:00:00 2001
From: Uyiosa Iyekekpolor <uyiosaben at gmail.com>
Date: Tue, 5 May 2026 22:39:23 -0400
Subject: [PATCH 14/15] run clang format
---
llvm/lib/Object/Archive.cpp | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/llvm/lib/Object/Archive.cpp b/llvm/lib/Object/Archive.cpp
index ed0f6e2dec972..9554ba5deeac7 100644
--- a/llvm/lib/Object/Archive.cpp
+++ b/llvm/lib/Object/Archive.cpp
@@ -1639,7 +1639,8 @@ ZOSArchive::ZOSArchive(MemoryBufferRef Source, Error &Err)
if (EbcdicSymbolTable.size() < sizeof(uint32_t)) {
Err = malformedError(
"z/OS archive symbol table is too small to read the symbol count, "
- "size is " + Twine(EbcdicSymbolTable.size()));
+ "size is " +
+ Twine(EbcdicSymbolTable.size()));
return;
}
uint64_t EbcdicSymbolCount = read32be(EbcdicSymbolTable.data());
@@ -1647,9 +1648,9 @@ ZOSArchive::ZOSArchive(MemoryBufferRef Source, Error &Err)
sizeof(uint32_t) + (EbcdicSymbolCount * (sizeof(uint64_t)));
if (OffsetToEbcdicNames > EbcdicSymbolTable.size()) {
Err = malformedError("z/OS archive symbol table names offset " +
- Twine(OffsetToEbcdicNames) +
- " exceeds symbol table size " +
- Twine(EbcdicSymbolTable.size()));
+ Twine(OffsetToEbcdicNames) +
+ " exceeds symbol table size " +
+ Twine(EbcdicSymbolTable.size()));
return;
}
uint64_t EbcdicNamesSize = EbcdicSymbolTable.size() - OffsetToEbcdicNames;
>From 92d74f75265ab3258fd8c78da341bb302c1004dc Mon Sep 17 00:00:00 2001
From: Uyiosa Iyekekpolor <uyiosaben at gmail.com>
Date: Wed, 6 May 2026 22:22:08 -0400
Subject: [PATCH 15/15] Address review comments
---
llvm/lib/Object/Archive.cpp | 22 ++++++++++------------
llvm/test/Object/zos-archive-read.test | 3 ++-
2 files changed, 12 insertions(+), 13 deletions(-)
diff --git a/llvm/lib/Object/Archive.cpp b/llvm/lib/Object/Archive.cpp
index 9554ba5deeac7..bf09240ac2fbd 100644
--- a/llvm/lib/Object/Archive.cpp
+++ b/llvm/lib/Object/Archive.cpp
@@ -405,6 +405,7 @@ Expected<StringRef> ZOSArchiveMemberHeader::getRawName() const {
}
Expected<StringRef> ZOSArchiveMemberHeader::getName(uint64_t Size) const {
+ (void)Size;
return StringRef(MemberName);
}
@@ -433,7 +434,7 @@ void ZOSArchiveMemberHeader::setMemberHeaderStrings(Error *Err, uint64_t Size) {
Twine(Offset));
return;
}
- RawMemberName.append(RawNameRef);
+ RawMemberName.assign(RawNameRef);
// Set MemberName.
if (RawNameRef.starts_with("#1/")) {
@@ -459,7 +460,7 @@ void ZOSArchiveMemberHeader::setMemberHeaderStrings(Error *Err, uint64_t Size) {
Twine(Offset));
return;
}
- LastModified.append(LastModifiedRef);
+ LastModified.assign(LastModifiedRef);
// UID
StringRef UIDRef = ebcdicFieldToASCII(ArMemHdr->UID, Dst);
@@ -469,7 +470,7 @@ void ZOSArchiveMemberHeader::setMemberHeaderStrings(Error *Err, uint64_t Size) {
Twine(Offset));
return;
}
- UID.append(UIDRef);
+ UID.assign(UIDRef);
// GID
StringRef GIDRef = ebcdicFieldToASCII(ArMemHdr->GID, Dst);
@@ -479,7 +480,7 @@ void ZOSArchiveMemberHeader::setMemberHeaderStrings(Error *Err, uint64_t Size) {
Twine(Offset));
return;
}
- GID.append(GIDRef);
+ GID.assign(GIDRef);
// AccessMode
StringRef AccessModeRef = ebcdicFieldToASCII(ArMemHdr->AccessMode, Dst);
@@ -490,7 +491,7 @@ void ZOSArchiveMemberHeader::setMemberHeaderStrings(Error *Err, uint64_t Size) {
Twine(Offset));
return;
}
- AccessMode.append(AccessModeRef);
+ AccessMode.assign(AccessModeRef);
}
Expected<uint64_t> BigArchiveMemberHeader::getRawNameSize() const {
@@ -1628,14 +1629,11 @@ ZOSArchive::ZOSArchive(MemoryBufferRef Source, Error &Err)
StringRef Name = NameOrErr.get();
if (Name == "__.SYMDEF") {
- Expected<StringRef> BufOrErr = C->getBuffer();
- if (!BufOrErr) {
- Err = BufOrErr.takeError();
- return;
- }
-
// Copy symbol table converting embedded EBCDIC names to ASCII.
- StringRef EbcdicSymbolTable = BufOrErr.get();
+ // getBuffer() cannot fail here because the Child constructor and
+ // getNext() already validate that the member's size fits within
+ // the archive.
+ StringRef EbcdicSymbolTable = cantFail(C->getBuffer());
if (EbcdicSymbolTable.size() < sizeof(uint32_t)) {
Err = malformedError(
"z/OS archive symbol table is too small to read the symbol count, "
diff --git a/llvm/test/Object/zos-archive-read.test b/llvm/test/Object/zos-archive-read.test
index c6b52740bb5c3..ba58cb990421f 100644
--- a/llvm/test/Object/zos-archive-read.test
+++ b/llvm/test/Object/zos-archive-read.test
@@ -1,6 +1,6 @@
## Test reading a valid z/OS archive.
# RUN: %python %p/Inputs/generate_zos_archive.py --output %t.valid.a \
-# RUN: --symtab "foo.txt:0" --member foo.txt:abcd
+# RUN: --symtab "foo.txt:0" --symtab "bar:0" --member foo.txt:abcd
# RUN: llvm-ar t %t.valid.a | FileCheck %s --check-prefix=LIST
# RUN: llvm-nm --print-armap %t.valid.a | FileCheck %s --check-prefix=SYMS
# RUN: llvm-ar p %t.valid.a foo.txt | FileCheck %s --check-prefix=CONTENT
@@ -8,6 +8,7 @@
# LIST: foo.txt
# SYMS: Archive map
# SYMS-NEXT: foo.txt in foo.txt
+# SYMS-NEXT: bar in foo.txt
# CONTENT: abcd
## Test malformed terminator on member header.
More information about the llvm-commits
mailing list