From 12727a9a353bf7dead4aba1086b58e05a9a92cdf Mon Sep 17 00:00:00 2001 From: Uyiosa Iyekekpolor Date: Tue, 17 Mar 2026 15:29:38 -0400 Subject: [PATCH 01/19] [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 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 clone() const override { + return std::make_unique(*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 getRawName() const override; + Expected getName(uint64_t Size) const override; + StringRef getRawAccessMode() const override; + StringRef getRawLastModified() const override; + StringRef getRawUID() const override; + StringRef getRawGID() const override; + Expected getSize() const override; + Expected 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 BigArchiveMemberHeader::getSize() const { return *SizeOrErr + alignTo(*NameLenOrErr, 2); } +template +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 ZOSArchiveMemberHeader::getSize() const { + SmallString<64> Dst; + return getArchiveMemberDecField( + "size", getFieldRawStringE2A(ArMemHdr->Size, Dst), Parent, this); +} + +Expected ZOSArchiveMemberHeader::getRawName() const { + return StringRef(RawMemberName); +} + +Expected 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(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 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 BigArchiveMemberHeader::getRawNameSize() const { return getArchiveMemberDecField( "NameLen", getFieldRawString(ArMemHdr->NameLen), Parent, this); @@ -668,6 +788,8 @@ Expected> Archive::create(MemoryBufferRef Source) { if (Buffer.starts_with(BigArchiveMagic)) Ret = std::make_unique(Source, Err); + else if (Buffer.starts_with(ZOSArchiveMagic)) + Ret = std::make_unique(Source, Err); else Ret = std::make_unique(Source, Err); @@ -680,6 +802,10 @@ std::unique_ptr Archive::createArchiveMemberHeader(const char *RawHeaderPtr, uint64_t Size, Error *Err) const { ErrorAsOutParameter ErrAsOutParam(Err); + + if (kind() == K_ZOS) + return std::make_unique(this, RawHeaderPtr, Size, + Err); if (kind() != K_AIXBIG) return std::make_unique(this, RawHeaderPtr, Size, Err); return std::make_unique(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("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::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 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 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@CN_~6a^|C{$_CfR809e>;ZS~ku4;Qm*)_d}P& zLrFsF>Z;8`M$R_v%WhBL5f-&cGL@2>Q6d)2 zr1VFdOe=};nH8%Og=yFywE_j(+}*vp>Ue08Ba18=r(j5!J0Vt`=!&L+Ch&6g=F!of zC%~w$9CEV1no#pg z{1?tk8aBy3QKp5wi7xHn&Y3k_r?CRAwGMWvy$GcyxQ)Z23r)w(RBjC3@!LGKx+sSOv Xf{c`};-bXq^`{Wi Date: Thu, 9 Apr 2026 10:51:04 -0400 Subject: [PATCH 02/19] 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 BigArchiveMemberHeader::getSize() const { return *SizeOrErr + alignTo(*NameLenOrErr, 2); } -template -StringRef getFieldRawStringE2A(const T (&Field)[N], SmallString<64> &Dst) { +template +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 Date: Thu, 9 Apr 2026 13:36:25 -0400 Subject: [PATCH 03/19] 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 BigArchiveMemberHeader::getSize() const { template 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::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 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 Date: Thu, 9 Apr 2026 13:51:06 -0400 Subject: [PATCH 04/19] 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::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 Date: Thu, 9 Apr 2026 15:05:54 -0400 Subject: [PATCH 05/19] 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 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 Date: Mon, 13 Apr 2026 09:53:02 -0400 Subject: [PATCH 06/19] 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::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 Date: Mon, 13 Apr 2026 11:36:44 -0400 Subject: [PATCH 07/19] 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@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@52ikvKY3d>$e4>-(qzCe{Wu3ZeVqbGO9 z^4zU;@a!FMD(w}hG{D %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 Date: Wed, 15 Apr 2026 16:46:27 -0400 Subject: [PATCH 08/19] 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 BigArchiveMemberHeader::getSize() const { } template -StringRef getFieldRawStringE2A(const char (&Field)[N], SmallString<64> &Dst) { +StringRef ebcdicFieldToASCII(const char (&Field)[N], SmallVectorImpl &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 ZOSArchiveMemberHeader::getSize() const { SmallString<64> Dst; return getArchiveMemberDecField( - "size", getFieldRawStringE2A(ArMemHdr->Size, Dst), Parent, this); + "size", ebcdicFieldToASCII(ArMemHdr->Size, Dst), Parent, this); } Expected ZOSArchiveMemberHeader::getRawName() const { @@ -420,18 +422,18 @@ void ZOSArchiveMemberHeader::setMemberHeaderStrings(Error *Err, uint64_t Size) { uint64_t Offset = reinterpret_cast(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 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 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 Date: Wed, 15 Apr 2026 16:48:54 -0400 Subject: [PATCH 09/19] 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 BigArchiveMemberHeader::getSize() const { } template -StringRef ebcdicFieldToASCII(const char (&Field)[N], SmallVectorImpl &Dst) { +StringRef ebcdicFieldToASCII(const char (&Field)[N], + SmallVectorImpl &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 Date: Tue, 21 Apr 2026 20:03:30 -0400 Subject: [PATCH 10/19] 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 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: "!\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: - decode hex bytes + - 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@CN_~6a^|C{$_CfR809e>;ZS~ku4;Qm*)_d}P& zLrFsF>Z;8`M$R_v%WhBL5f-&cGL@2>Q6d)2 zr1VFdOe=};nH8%Og=yFywE_j(+}*vp>Ue08Ba18=r(j5!J0Vt`=!&L+Ch&6g=F!of zC%~w$9CEV1no#pg z{1?tk8aBy3QKp5wi7xHn&Y3k_r?CRAwGMWvy$GcyxQ)Z23r)w(RBjC3@!LGKx+sSOv Xf{c`};-bXq^`{WiCjT{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@52ikvKY3d>$e4>-(qzCe{Wu3ZeVqbGO9 z^4zU;@a!FMD(w}hG{D %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 Date: Tue, 21 Apr 2026 20:06:35 -0400 Subject: [PATCH 11/19] 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 Date: Tue, 21 Apr 2026 20:19:01 -0400 Subject: [PATCH 12/19] 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: "!\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): - 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 Date: Tue, 5 May 2026 22:36:06 -0400 Subject: [PATCH 13/19] 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: "!\n" in EBCDIC +# z/OS archive magic: "!\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 Date: Tue, 5 May 2026 22:39:23 -0400 Subject: [PATCH 14/19] 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 Date: Wed, 6 May 2026 22:22:08 -0400 Subject: [PATCH 15/19] 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 ZOSArchiveMemberHeader::getRawName() const { } Expected 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 BigArchiveMemberHeader::getRawNameSize() const { @@ -1628,14 +1629,11 @@ ZOSArchive::ZOSArchive(MemoryBufferRef Source, Error &Err) StringRef Name = NameOrErr.get(); if (Name == "__.SYMDEF") { - Expected 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. From 37d19d463c919b0be639410fb5a6a71894d96ea5 Mon Sep 17 00:00:00 2001 From: Uyiosa Iyekekpolor Date: Thu, 7 May 2026 22:52:08 -0400 Subject: [PATCH 16/19] Address review comments --- llvm/lib/Object/Archive.cpp | 34 +++++++++++++++------------------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/llvm/lib/Object/Archive.cpp b/llvm/lib/Object/Archive.cpp index bf09240ac2fbd..18ed6688f98c3 100644 --- a/llvm/lib/Object/Archive.cpp +++ b/llvm/lib/Object/Archive.cpp @@ -378,12 +378,11 @@ Expected BigArchiveMemberHeader::getSize() const { } template -StringRef ebcdicFieldToASCII(const char (&Field)[N], - SmallVectorImpl &Dst) { - Dst.clear(); +std::string ebcdicFieldToASCII(const char (&Field)[N]) { + SmallString<64> Dst; StringRef Src = StringRef(Field, N); ConverterEBCDIC::convertToUTF8(Src, Dst); - return StringRef(Dst.data(), Dst.size()).rtrim(" "); + return Dst.str().rtrim(" ").str(); } ZOSArchiveMemberHeader::ZOSArchiveMemberHeader(const Archive *Parent, @@ -395,17 +394,15 @@ ZOSArchiveMemberHeader::ZOSArchiveMemberHeader(const Archive *Parent, } Expected ZOSArchiveMemberHeader::getSize() const { - SmallString<64> Dst; - return getArchiveMemberDecField( - "size", ebcdicFieldToASCII(ArMemHdr->Size, Dst), Parent, this); + return getArchiveMemberDecField("size", ebcdicFieldToASCII(ArMemHdr->Size), + Parent, this); } Expected ZOSArchiveMemberHeader::getRawName() const { return StringRef(RawMemberName); } -Expected ZOSArchiveMemberHeader::getName(uint64_t Size) const { - (void)Size; +Expected ZOSArchiveMemberHeader::getName(uint64_t /*Size*/) const { return StringRef(MemberName); } @@ -422,12 +419,11 @@ 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(ArMemHdr) - Parent->getData().data(); // Set RawMemberName - StringRef RawNameRef = ebcdicFieldToASCII(ArMemHdr->Name, Dst); + std::string RawNameRef = ebcdicFieldToASCII(ArMemHdr->Name); if (RawNameRef.empty() || RawNameRef[0] == ' ') { *Err = malformedError("name contains a leading space for archive member " "header at offset " + @@ -437,22 +433,22 @@ void ZOSArchiveMemberHeader::setMemberHeaderStrings(Error *Err, uint64_t Size) { RawMemberName.assign(RawNameRef); // Set MemberName. - if (RawNameRef.starts_with("#1/")) { + if (StringRef(RawNameRef).starts_with("#1/")) { Expected NameOrErr = ArchiveMemberHeader::getName(Size); if (!NameOrErr) { *Err = NameOrErr.takeError(); return; } StringRef Name = NameOrErr.get(); - Dst.clear(); - ConverterEBCDIC::convertToUTF8(Name, Dst); - MemberName.append(Dst.str()); + SmallString<64> ConvertedName; + ConverterEBCDIC::convertToUTF8(Name, ConvertedName); + MemberName.assign(ConvertedName.str()); } else { MemberName = RawMemberName; } // LastModified - StringRef LastModifiedRef = ebcdicFieldToASCII(ArMemHdr->LastModified, Dst); + std::string LastModifiedRef = ebcdicFieldToASCII(ArMemHdr->LastModified); if (LastModifiedRef.empty()) { *Err = malformedError("LastModified field is empty or contains only spaces in " @@ -463,7 +459,7 @@ void ZOSArchiveMemberHeader::setMemberHeaderStrings(Error *Err, uint64_t Size) { LastModified.assign(LastModifiedRef); // UID - StringRef UIDRef = ebcdicFieldToASCII(ArMemHdr->UID, Dst); + std::string UIDRef = ebcdicFieldToASCII(ArMemHdr->UID); if (UIDRef.empty()) { *Err = malformedError("UID field is empty or contains only spaces in " "archive member header at offset " + @@ -473,7 +469,7 @@ void ZOSArchiveMemberHeader::setMemberHeaderStrings(Error *Err, uint64_t Size) { UID.assign(UIDRef); // GID - StringRef GIDRef = ebcdicFieldToASCII(ArMemHdr->GID, Dst); + std::string GIDRef = ebcdicFieldToASCII(ArMemHdr->GID); if (GIDRef.empty()) { *Err = malformedError("GID field is empty or contains only spaces in " "archive member header at offset " + @@ -483,7 +479,7 @@ void ZOSArchiveMemberHeader::setMemberHeaderStrings(Error *Err, uint64_t Size) { GID.assign(GIDRef); // AccessMode - StringRef AccessModeRef = ebcdicFieldToASCII(ArMemHdr->AccessMode, Dst); + std::string AccessModeRef = ebcdicFieldToASCII(ArMemHdr->AccessMode); if (AccessModeRef.empty()) { *Err = malformedError("AccessMode field is empty or contains only spaces in " From d439b067f7a5f68fc8745a0392cc7b237960afa1 Mon Sep 17 00:00:00 2001 From: Uyiosa Iyekekpolor Date: Mon, 11 May 2026 16:09:33 -0400 Subject: [PATCH 17/19] Address review comments --- llvm/lib/Object/Archive.cpp | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/llvm/lib/Object/Archive.cpp b/llvm/lib/Object/Archive.cpp index 18ed6688f98c3..07819b63d452a 100644 --- a/llvm/lib/Object/Archive.cpp +++ b/llvm/lib/Object/Archive.cpp @@ -430,7 +430,7 @@ void ZOSArchiveMemberHeader::setMemberHeaderStrings(Error *Err, uint64_t Size) { Twine(Offset)); return; } - RawMemberName.assign(RawNameRef); + RawMemberName = RawNameRef; // Set MemberName. if (StringRef(RawNameRef).starts_with("#1/")) { @@ -442,52 +442,48 @@ void ZOSArchiveMemberHeader::setMemberHeaderStrings(Error *Err, uint64_t Size) { StringRef Name = NameOrErr.get(); SmallString<64> ConvertedName; ConverterEBCDIC::convertToUTF8(Name, ConvertedName); - MemberName.assign(ConvertedName.str()); + MemberName = std::string(ConvertedName); } else { MemberName = RawMemberName; } // LastModified - std::string LastModifiedRef = ebcdicFieldToASCII(ArMemHdr->LastModified); - if (LastModifiedRef.empty()) { + LastModified = ebcdicFieldToASCII(ArMemHdr->LastModified); + if (LastModified.empty()) { *Err = malformedError("LastModified field is empty or contains only spaces in " "archive member header at offset " + Twine(Offset)); return; } - LastModified.assign(LastModifiedRef); // UID - std::string UIDRef = ebcdicFieldToASCII(ArMemHdr->UID); - if (UIDRef.empty()) { + UID = ebcdicFieldToASCII(ArMemHdr->UID); + if (UID.empty()) { *Err = malformedError("UID field is empty or contains only spaces in " "archive member header at offset " + Twine(Offset)); return; } - UID.assign(UIDRef); // GID - std::string GIDRef = ebcdicFieldToASCII(ArMemHdr->GID); - if (GIDRef.empty()) { + GID = ebcdicFieldToASCII(ArMemHdr->GID); + if (GID.empty()) { *Err = malformedError("GID field is empty or contains only spaces in " "archive member header at offset " + Twine(Offset)); return; } - GID.assign(GIDRef); // AccessMode - std::string AccessModeRef = ebcdicFieldToASCII(ArMemHdr->AccessMode); - if (AccessModeRef.empty()) { + AccessMode = ebcdicFieldToASCII(ArMemHdr->AccessMode); + if (AccessMode.empty()) { *Err = malformedError("AccessMode field is empty or contains only spaces in " "archive member header at offset " + Twine(Offset)); return; } - AccessMode.assign(AccessModeRef); } Expected BigArchiveMemberHeader::getRawNameSize() const { @@ -1633,7 +1629,7 @@ 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 " + + "symbol table size is " + Twine(EbcdicSymbolTable.size())); return; } From 0ed1d1fc391030d25414d38964b1fb2c154fe87d Mon Sep 17 00:00:00 2001 From: Uyiosa Iyekekpolor Date: Mon, 11 May 2026 18:13:54 -0400 Subject: [PATCH 18/19] Address review comments --- llvm/lib/Object/Archive.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/llvm/lib/Object/Archive.cpp b/llvm/lib/Object/Archive.cpp index 07819b63d452a..d95f9103747aa 100644 --- a/llvm/lib/Object/Archive.cpp +++ b/llvm/lib/Object/Archive.cpp @@ -423,17 +423,16 @@ void ZOSArchiveMemberHeader::setMemberHeaderStrings(Error *Err, uint64_t Size) { reinterpret_cast(ArMemHdr) - Parent->getData().data(); // Set RawMemberName - std::string RawNameRef = ebcdicFieldToASCII(ArMemHdr->Name); - if (RawNameRef.empty() || RawNameRef[0] == ' ') { + RawMemberName = ebcdicFieldToASCII(ArMemHdr->Name); + if (RawMemberName.empty() || RawMemberName[0] == ' ') { *Err = malformedError("name contains a leading space for archive member " "header at offset " + Twine(Offset)); return; } - RawMemberName = RawNameRef; // Set MemberName. - if (StringRef(RawNameRef).starts_with("#1/")) { + if (StringRef(RawMemberName).starts_with("#1/")) { Expected NameOrErr = ArchiveMemberHeader::getName(Size); if (!NameOrErr) { *Err = NameOrErr.takeError(); From c375f503f71b8a8039a400e154953e9600838344 Mon Sep 17 00:00:00 2001 From: Uyiosa Iyekekpolor Date: Mon, 11 May 2026 19:06:47 -0400 Subject: [PATCH 19/19] Update test to match modified error message --- llvm/test/Object/zos-archive-read.test | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/llvm/test/Object/zos-archive-read.test b/llvm/test/Object/zos-archive-read.test index ba58cb990421f..8f01d13a09f36 100644 --- a/llvm/test/Object/zos-archive-read.test +++ b/llvm/test/Object/zos-archive-read.test @@ -35,8 +35,7 @@ # 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 archive symbol table is too small to read the symbol count, size is 2 - +# ERR-TRUNC: z/OS archive symbol table is too small to read the symbol count, symbol table size is 2 ## 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