diff --git a/llvm/test/tools/llvm-rc/Inputs/tag-stringtable-basic.rc b/llvm/test/tools/llvm-rc/Inputs/tag-stringtable-basic.rc new file mode 100644 index 000000000000..9847dabb7ca0 --- /dev/null +++ b/llvm/test/tools/llvm-rc/Inputs/tag-stringtable-basic.rc @@ -0,0 +1,45 @@ +STRINGTABLE +VERSION 32 +CHARACTERISTICS 0x32 { + 0 "a" +} + +STRINGTABLE { + 1 "b" + 16 "bb" +} + +STRINGTABLE +VERSION 100 +LANGUAGE 4, 7 { + 16 "hello" + 17 "world" +} + +STRINGTABLE +VERSION 50 +CHARACTERISTICS 0x32 { + 17 "cc" + 32 "ccc" + 2 "c" +} + +STRINGTABLE { + 3 "d" + 4 "" + 8 "" +} + +STRINGTABLE +VERSION 101 +LANGUAGE 4, 7 { + -1 & 65535 "minus one" +} + +STRINGTABLE +CHARACTERISTICS 10 +LANGUAGE 4, 7 { + 23 "something else" + 65529 "large number" +} + diff --git a/llvm/test/tools/llvm-rc/Inputs/tag-stringtable-same-ids.rc b/llvm/test/tools/llvm-rc/Inputs/tag-stringtable-same-ids.rc new file mode 100644 index 000000000000..c256a32f2658 --- /dev/null +++ b/llvm/test/tools/llvm-rc/Inputs/tag-stringtable-same-ids.rc @@ -0,0 +1,5 @@ +STRINGTABLE { + 1 "Hello" + 2 "World" + 1 "Repeat" +} \ No newline at end of file diff --git a/llvm/test/tools/llvm-rc/tag-stringtable.test b/llvm/test/tools/llvm-rc/tag-stringtable.test new file mode 100644 index 000000000000..43b5f5c96580 --- /dev/null +++ b/llvm/test/tools/llvm-rc/tag-stringtable.test @@ -0,0 +1,170 @@ +; RUN: llvm-rc /FO %t %p/Inputs/tag-stringtable-basic.rc +; RUN: llvm-readobj %t | FileCheck %s + +; CHECK: Resource type (int): 6 +; CHECK-NEXT: Resource name (int): 1 +; CHECK-NEXT: Data version: 0 +; CHECK-NEXT: Memory flags: 0x1030 +; CHECK-NEXT: Language ID: 1033 +; CHECK-NEXT: Version (major): 0 +; CHECK-NEXT: Version (minor): 32 +; CHECK-NEXT: Characteristics: 50 +; CHECK-NEXT: Data size: 40 +; CHECK-NEXT: Data: ( +; CHECK-NEXT: 0000: 01006100 01006200 01006300 01006400 |..a...b...c...d.| +; CHECK-NEXT: 0010: 00000000 00000000 00000000 00000000 |................| +; CHECK-NEXT: 0020: 00000000 00000000 |........| +; CHECK-NEXT: ) + +; CHECK-DAG: Resource type (int): 6 +; CHECK-NEXT: Resource name (int): 2 +; CHECK-NEXT: Data version: 0 +; CHECK-NEXT: Memory flags: 0x1030 +; CHECK-NEXT: Language ID: 1033 +; CHECK-NEXT: Version (major): 0 +; CHECK-NEXT: Version (minor): 0 +; CHECK-NEXT: Characteristics: 0 +; CHECK-NEXT: Data size: 40 +; CHECK-NEXT: Data: ( +; CHECK-NEXT: 0000: 02006200 62000200 63006300 00000000 |..b.b...c.c.....| +; CHECK-NEXT: 0010: 00000000 00000000 00000000 00000000 |................| +; CHECK-NEXT: 0020: 00000000 00000000 |........| +; CHECK-NEXT: ) + +; CHECK-DAG: Resource type (int): 6 +; CHECK-NEXT: Resource name (int): 2 +; CHECK-NEXT: Data version: 0 +; CHECK-NEXT: Memory flags: 0x1030 +; CHECK-NEXT: Language ID: 7172 +; CHECK-NEXT: Version (major): 0 +; CHECK-NEXT: Version (minor): 100 +; CHECK-NEXT: Characteristics: 0 +; CHECK-NEXT: Data size: 80 +; CHECK-NEXT: Data: ( +; CHECK-NEXT: 0000: 05006800 65006C00 6C006F00 05007700 |..h.e.l.l.o...w.| +; CHECK-NEXT: 0010: 6F007200 6C006400 00000000 00000000 |o.r.l.d.........| +; CHECK-NEXT: 0020: 00000E00 73006F00 6D006500 74006800 |....s.o.m.e.t.h.| +; CHECK-NEXT: 0030: 69006E00 67002000 65006C00 73006500 |i.n.g. .e.l.s.e.| +; CHECK-NEXT: 0040: 00000000 00000000 00000000 00000000 |................| +; CHECK-NEXT: ) + +; CHECK-DAG: Resource type (int): 6 +; CHECK-NEXT: Resource name (int): 3 +; CHECK-NEXT: Data version: 0 +; CHECK-NEXT: Memory flags: 0x1030 +; CHECK-NEXT: Language ID: 1033 +; CHECK-NEXT: Version (major): 0 +; CHECK-NEXT: Version (minor): 50 +; CHECK-NEXT: Characteristics: 50 +; CHECK-NEXT: Data size: 38 +; CHECK-NEXT: Data: ( +; CHECK-NEXT: 0000: 03006300 63006300 00000000 00000000 |..c.c.c.........| +; CHECK-NEXT: 0010: 00000000 00000000 00000000 00000000 |................| +; CHECK-NEXT: 0020: 00000000 0000 |......| +; CHECK-NEXT: ) + +; CHECK-DAG: Resource type (int): 6 +; CHECK-NEXT: Resource name (int): 4096 +; CHECK-NEXT: Data version: 0 +; CHECK-NEXT: Memory flags: 0x1030 +; CHECK-NEXT: Language ID: 7172 +; CHECK-NEXT: Version (major): 0 +; CHECK-NEXT: Version (minor): 101 +; CHECK-NEXT: Characteristics: 0 +; CHECK-NEXT: Data size: 74 +; CHECK-NEXT: Data: ( +; CHECK-NEXT: 0000: 00000000 00000000 00000000 00000000 |................| +; CHECK-NEXT: 0010: 00000C00 6C006100 72006700 65002000 |....l.a.r.g.e. .| +; CHECK-NEXT: 0020: 6E007500 6D006200 65007200 00000000 |n.u.m.b.e.r.....| +; CHECK-NEXT: 0030: 00000000 00000900 6D006900 6E007500 |........m.i.n.u.| +; CHECK-NEXT: 0040: 73002000 6F006E00 6500 |s. .o.n.e.| +; CHECK-NEXT: ) + + +; RUN: llvm-rc /N /FO %t0 %p/Inputs/tag-stringtable-basic.rc +; RUN: llvm-readobj %t0 | FileCheck %s --check-prefix=NULL + +; NULL: Resource type (int): 6 +; NULL-NEXT: Resource name (int): 1 +; NULL-NEXT: Data version: 0 +; NULL-NEXT: Memory flags: 0x1030 +; NULL-NEXT: Language ID: 1033 +; NULL-NEXT: Version (major): 0 +; NULL-NEXT: Version (minor): 32 +; NULL-NEXT: Characteristics: 50 +; NULL-NEXT: Data size: 52 +; NULL-NEXT: Data: ( +; NULL-NEXT: 0000: 02006100 00000200 62000000 02006300 |..a.....b.....c.| +; NULL-NEXT: 0010: 00000200 64000000 01000000 00000000 |....d...........| +; NULL-NEXT: 0020: 00000100 00000000 00000000 00000000 |................| +; NULL-NEXT: 0030: 00000000 |....| +; NULL-NEXT: ) + +; NULL-DAG: Resource type (int): 6 +; NULL-NEXT: Resource name (int): 2 +; NULL-NEXT: Data version: 0 +; NULL-NEXT: Memory flags: 0x1030 +; NULL-NEXT: Language ID: 1033 +; NULL-NEXT: Version (major): 0 +; NULL-NEXT: Version (minor): 0 +; NULL-NEXT: Characteristics: 0 +; NULL-NEXT: Data size: 44 +; NULL-NEXT: Data: ( +; NULL-NEXT: 0000: 03006200 62000000 03006300 63000000 |..b.b.....c.c...| +; NULL-NEXT: 0010: 00000000 00000000 00000000 00000000 |................| +; NULL-NEXT: 0020: 00000000 00000000 00000000 |............| +; NULL-NEXT: ) + +; NULL-DAG: Resource type (int): 6 +; NULL-NEXT: Resource name (int): 2 +; NULL-NEXT: Data version: 0 +; NULL-NEXT: Memory flags: 0x1030 +; NULL-NEXT: Language ID: 7172 +; NULL-NEXT: Version (major): 0 +; NULL-NEXT: Version (minor): 100 +; NULL-NEXT: Characteristics: 0 +; NULL-NEXT: Data size: 86 +; NULL-NEXT: Data: ( +; NULL-NEXT: 0000: 06006800 65006C00 6C006F00 00000600 |..h.e.l.l.o.....| +; NULL-NEXT: 0010: 77006F00 72006C00 64000000 00000000 |w.o.r.l.d.......| +; NULL-NEXT: 0020: 00000000 00000F00 73006F00 6D006500 |........s.o.m.e.| +; NULL-NEXT: 0030: 74006800 69006E00 67002000 65006C00 |t.h.i.n.g. .e.l.| +; NULL-NEXT: 0040: 73006500 00000000 00000000 00000000 |s.e.............| +; NULL-NEXT: 0050: 00000000 0000 |......| +; NULL-NEXT: ) + +; NULL-DAG: Resource type (int): 6 +; NULL-NEXT: Resource name (int): 3 +; NULL-NEXT: Data version: 0 +; NULL-NEXT: Memory flags: 0x1030 +; NULL-NEXT: Language ID: 1033 +; NULL-NEXT: Version (major): 0 +; NULL-NEXT: Version (minor): 50 +; NULL-NEXT: Characteristics: 50 +; NULL-NEXT: Data size: 40 +; NULL-NEXT: Data: ( +; NULL-NEXT: 0000: 04006300 63006300 00000000 00000000 |..c.c.c.........| +; NULL-NEXT: 0010: 00000000 00000000 00000000 00000000 |................| +; NULL-NEXT: 0020: 00000000 00000000 |........| +; NULL-NEXT: ) + +; NULL-DAG: Resource type (int): 6 +; NULL-NEXT: Resource name (int): 4096 +; NULL-NEXT: Data version: 0 +; NULL-NEXT: Memory flags: 0x1030 +; NULL-NEXT: Language ID: 7172 +; NULL-NEXT: Version (major): 0 +; NULL-NEXT: Version (minor): 101 +; NULL-NEXT: Characteristics: 0 +; NULL-NEXT: Data size: 78 +; NULL-NEXT: Data: ( +; NULL-NEXT: 0000: 00000000 00000000 00000000 00000000 |................| +; NULL-NEXT: 0010: 00000D00 6C006100 72006700 65002000 |....l.a.r.g.e. .| +; NULL-NEXT: 0020: 6E007500 6D006200 65007200 00000000 |n.u.m.b.e.r.....| +; NULL-NEXT: 0030: 00000000 00000000 0A006D00 69006E00 |..........m.i.n.| +; NULL-NEXT: 0040: 75007300 20006F00 6E006500 0000 |u.s. .o.n.e...| +; NULL-NEXT: ) + + +; RUN: not llvm-rc /FO %t %p/Inputs/tag-stringtable-same-ids.rc 2>&1 | FileCheck %s --check-prefix SAMEIDS +; SAMEIDS: llvm-rc: Multiple STRINGTABLE strings located under ID 1 diff --git a/llvm/tools/llvm-rc/ResourceFileWriter.cpp b/llvm/tools/llvm-rc/ResourceFileWriter.cpp index e6480f126eff..f41ffcc8ac54 100644 --- a/llvm/tools/llvm-rc/ResourceFileWriter.cpp +++ b/llvm/tools/llvm-rc/ResourceFileWriter.cpp @@ -129,8 +129,6 @@ enum class NullHandlingMethod { // For identifiers, this is no-op. static Error processString(StringRef Str, NullHandlingMethod NullHandler, bool &IsLongString, SmallVectorImpl &Result) { - assert(NullHandler == NullHandlingMethod::CutAtNull); - bool IsString = stripQuotes(Str, IsLongString); convertUTF8ToUTF16String(Str, Result); @@ -143,11 +141,24 @@ static Error processString(StringRef Str, NullHandlingMethod NullHandler, return Error::success(); } - // We don't process the string contents. Only cut at '\0'. + switch (NullHandler) { + case NullHandlingMethod::CutAtNull: + for (size_t Pos = 0; Pos < Result.size(); ++Pos) + if (Result[Pos] == '\0') + Result.resize(Pos); + break; - for (size_t Pos = 0; Pos < Result.size(); ++Pos) - if (Result[Pos] == '\0') - Result.resize(Pos); + case NullHandlingMethod::CutAtDoubleNull: + for (size_t Pos = 0; Pos + 1 < Result.size(); ++Pos) + if (Result[Pos] == '\0' && Result[Pos + 1] == '\0') + Result.resize(Pos); + if (Result.size() > 0 && Result.back() == '\0') + Result.pop_back(); + break; + + case NullHandlingMethod::UserResource: + break; + } return Error::success(); } @@ -258,6 +269,35 @@ Error ResourceFileWriter::visitMenuResource(const RCResource *Res) { return writeResource(Res, &ResourceFileWriter::writeMenuBody); } +Error ResourceFileWriter::visitStringTableResource(const RCResource *Base) { + const auto *Res = cast(Base); + + ContextKeeper RAII(this); + RETURN_IF_ERROR(Res->applyStmts(this)); + + for (auto &String : Res->Table) { + RETURN_IF_ERROR(checkNumberFits(String.first, "String ID")); + uint16_t BundleID = String.first >> 4; + StringTableInfo::BundleKey Key(BundleID, ObjectData.LanguageInfo); + auto &BundleData = StringTableData.BundleData; + auto Iter = BundleData.find(Key); + + if (Iter == BundleData.end()) { + // Need to create a bundle. + StringTableData.BundleList.push_back(Key); + auto EmplaceResult = + BundleData.emplace(Key, StringTableInfo::Bundle(ObjectData)); + assert(EmplaceResult.second && "Could not create a bundle"); + Iter = EmplaceResult.first; + } + + RETURN_IF_ERROR( + insertStringIntoBundle(Iter->second, String.first, String.second)); + } + + return Error::success(); +} + Error ResourceFileWriter::visitVersionInfoResource(const RCResource *Res) { return writeResource(Res, &ResourceFileWriter::writeVersionInfoBody); } @@ -939,6 +979,75 @@ Error ResourceFileWriter::writeMenuBody(const RCResource *Base) { return writeMenuDefinitionList(cast(Base)->Elements); } +// --- StringTableResource helpers. --- // + +class BundleResource : public RCResource { +public: + using BundleType = ResourceFileWriter::StringTableInfo::Bundle; + BundleType Bundle; + + BundleResource(const BundleType &StrBundle) : Bundle(StrBundle) {} + IntOrString getResourceType() const override { return 6; } + + ResourceKind getKind() const override { return RkStringTableBundle; } + static bool classof(const RCResource *Res) { + return Res->getKind() == RkStringTableBundle; + } +}; + +Error ResourceFileWriter::visitStringTableBundle(const RCResource *Res) { + return writeResource(Res, &ResourceFileWriter::writeStringTableBundleBody); +} + +Error ResourceFileWriter::insertStringIntoBundle( + StringTableInfo::Bundle &Bundle, uint16_t StringID, StringRef String) { + uint16_t StringLoc = StringID & 15; + if (Bundle.Data[StringLoc]) + return createError("Multiple STRINGTABLE strings located under ID " + + Twine(StringID)); + Bundle.Data[StringLoc] = String; + return Error::success(); +} + +Error ResourceFileWriter::writeStringTableBundleBody(const RCResource *Base) { + auto *Res = cast(Base); + for (size_t ID = 0; ID < Res->Bundle.Data.size(); ++ID) { + // The string format is a tiny bit different here. We + // first output the size of the string, and then the string itself + // (which is not null-terminated). + bool IsLongString; + SmallVector Data; + RETURN_IF_ERROR(processString(Res->Bundle.Data[ID].getValueOr(StringRef()), + NullHandlingMethod::CutAtDoubleNull, + IsLongString, Data)); + if (AppendNull && Res->Bundle.Data[ID]) + Data.push_back('\0'); + RETURN_IF_ERROR( + checkNumberFits(Data.size(), "STRINGTABLE string size")); + writeObject(ulittle16_t(Data.size())); + for (auto Char : Data) + writeObject(ulittle16_t(Char)); + } + return Error::success(); +} + +Error ResourceFileWriter::dumpAllStringTables() { + for (auto Key : StringTableData.BundleList) { + auto Iter = StringTableData.BundleData.find(Key); + assert(Iter != StringTableData.BundleData.end()); + + // For a moment, revert the context info to moment of bundle declaration. + ContextKeeper RAII(this); + ObjectData = Iter->second.DeclTimeInfo; + + BundleResource Res(Iter->second); + // Bundle #(k+1) contains keys [16k, 16k + 15]. + Res.setName(Key.first + 1); + RETURN_IF_ERROR(visitStringTableBundle(&Res)); + } + return Error::success(); +} + // --- VersionInfoResourceResource helpers. --- // Error ResourceFileWriter::writeVersionInfoBlock(const VersionInfoBlock &Blk) { diff --git a/llvm/tools/llvm-rc/ResourceFileWriter.h b/llvm/tools/llvm-rc/ResourceFileWriter.h index 0d248c0349e6..905f56f5381f 100644 --- a/llvm/tools/llvm-rc/ResourceFileWriter.h +++ b/llvm/tools/llvm-rc/ResourceFileWriter.h @@ -37,6 +37,7 @@ public: Error visitIconResource(const RCResource *) override; Error visitMenuResource(const RCResource *) override; Error visitVersionInfoResource(const RCResource *) override; + Error visitStringTableResource(const RCResource *) override; Error visitCaptionStmt(const CaptionStmt *) override; Error visitCharacteristicsStmt(const CharacteristicsStmt *) override; @@ -45,6 +46,12 @@ public: Error visitStyleStmt(const StyleStmt *) override; Error visitVersionStmt(const VersionStmt *) override; + // Stringtables are output at the end of .res file. We need a separate + // function to do it. + Error dumpAllStringTables(); + + bool AppendNull; // Append '\0' to each existing STRINGTABLE element? + struct ObjectInfo { uint16_t LanguageInfo; uint32_t Characteristics; @@ -64,6 +71,21 @@ public: ObjectInfo() : LanguageInfo(0), Characteristics(0), VersionInfo(0) {} } ObjectData; + struct StringTableInfo { + // Each STRINGTABLE bundle depends on ID of the bundle and language + // description. + using BundleKey = std::pair; + // Each bundle is in fact an array of 16 strings. + struct Bundle { + std::array, 16> Data; + ObjectInfo DeclTimeInfo; + Bundle(const ObjectInfo &Info) : DeclTimeInfo(Info) {} + }; + std::map BundleData; + // Bundles are listed in the order of their first occurence. + std::vector BundleList; + } StringTableData; + private: Error handleError(Error &&Err, const RCResource *Res); @@ -99,6 +121,12 @@ private: Error writeMenuDefinitionList(const MenuDefinitionList &List); Error writeMenuBody(const RCResource *); + // StringTableResource + Error visitStringTableBundle(const RCResource *); + Error writeStringTableBundleBody(const RCResource *); + Error insertStringIntoBundle(StringTableInfo::Bundle &Bundle, + uint16_t StringID, StringRef String); + // VersionInfoResource Error writeVersionInfoBody(const RCResource *); Error writeVersionInfoBlock(const VersionInfoBlock &); diff --git a/llvm/tools/llvm-rc/ResourceScriptStmt.h b/llvm/tools/llvm-rc/ResourceScriptStmt.h index 2457d831b1ea..915d4f108b5d 100644 --- a/llvm/tools/llvm-rc/ResourceScriptStmt.h +++ b/llvm/tools/llvm-rc/ResourceScriptStmt.h @@ -124,6 +124,7 @@ enum ResourceKind { RkSingleIcon = 3, RkMenu = 4, RkDialog = 5, + RkStringTableBundle = 6, RkAccelerators = 9, RkCursorGroup = 12, RkIconGroup = 14, @@ -138,9 +139,10 @@ enum ResourceKind { RkBase, RkCursor, RkIcon, + RkStringTable, RkUser, RkSingleCursorOrIconRes, - RkCursorOrIconGroupRes + RkCursorOrIconGroupRes, }; // Non-zero memory flags. @@ -488,14 +490,18 @@ public: // // Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa381050(v=vs.85).aspx class StringTableResource : public OptStatementsRCResource { +public: std::vector> Table; -public: using OptStatementsRCResource::OptStatementsRCResource; void addString(uint32_t ID, StringRef String) { Table.emplace_back(ID, String); } raw_ostream &log(raw_ostream &) const override; + Twine getResourceTypeName() const override { return "STRINGTABLE"; } + Error visit(Visitor *V) const override { + return V->visitStringTableResource(this); + } }; // -- DIALOG(EX) resource and its helper classes -- diff --git a/llvm/tools/llvm-rc/ResourceVisitor.h b/llvm/tools/llvm-rc/ResourceVisitor.h index c0d00287e8a9..328200bc00fa 100644 --- a/llvm/tools/llvm-rc/ResourceVisitor.h +++ b/llvm/tools/llvm-rc/ResourceVisitor.h @@ -37,6 +37,7 @@ public: virtual Error visitHTMLResource(const RCResource *) = 0; virtual Error visitIconResource(const RCResource *) = 0; virtual Error visitMenuResource(const RCResource *) = 0; + virtual Error visitStringTableResource(const RCResource *) = 0; virtual Error visitVersionInfoResource(const RCResource *) = 0; virtual Error visitCaptionStmt(const CaptionStmt *) = 0; diff --git a/llvm/tools/llvm-rc/llvm-rc.cpp b/llvm/tools/llvm-rc/llvm-rc.cpp index c62a037874b3..2a4faeb2d2e4 100644 --- a/llvm/tools/llvm-rc/llvm-rc.cpp +++ b/llvm/tools/llvm-rc/llvm-rc.cpp @@ -154,6 +154,7 @@ int main(int argc_, const char *argv_[]) { fatalError("Error opening output file '" + OutArgsInfo[0] + "': " + EC.message()); Visitor = llvm::make_unique(std::move(FOut)); + Visitor->AppendNull = InputArgs.hasArg(OPT_ADD_NULL); ExitOnErr(NullResource().visit(Visitor.get())); @@ -170,5 +171,9 @@ int main(int argc_, const char *argv_[]) { ExitOnErr(Resource->visit(Visitor.get())); } + // STRINGTABLE resources come at the very end. + if (!IsDryRun) + ExitOnErr(Visitor->dumpAllStringTables()); + return 0; }