[llvm-rc] Allow string table values split into multiple string literals

This can practically easily be a product of combining strings with
macros in resource files.

This fixes https://github.com/mstorsjo/llvm-mingw/issues/140.

As string literals within llvm-rc are handled as StringRefs, each
referencing an uninterpreted slice of the input file, with actual
interpretation of the input string (codepage handling, unescaping etc)
done only right before writing them out to disk, it's hard to
concatenate them other than just bundling them up in a vector,
without rearchitecting a large part of llvm-rc.

This matches how the same already is supported in VersionInfoValue,
with a std::vector<IntOrString> Values.

MS rc.exe only supports concatenated string literals in version info
values (already supported), string tables (implemented in this patch)
and user data resources (easily implemented in a separate patch, but
hasn't been requested by any end user yet), while GNU windres supports
string immediates split into multiple strings anywhere (e.g. like
(100 ICON "myicon" ".ico"). Not sure if concatenation in other
statements actually is used in the wild though, in resource files
normally built by GNU windres.

Differential Revision: https://reviews.llvm.org/D85183
This commit is contained in:
Martin Storsjö 2020-08-04 10:24:32 +03:00
parent e0d99e9aaf
commit b989fcbae6
6 changed files with 31 additions and 17 deletions

View File

@ -13,8 +13,8 @@ STRINGTABLE {
STRINGTABLE STRINGTABLE
VERSION 100 VERSION 100
LANGUAGE 4, 7 { LANGUAGE 4, 7 {
16 "hello" 16 "hel" "lo"
17 "world" 17 "wor" L"ld"
} }
STRINGTABLE STRINGTABLE

View File

@ -1246,7 +1246,8 @@ Error ResourceFileWriter::visitStringTableBundle(const RCResource *Res) {
} }
Error ResourceFileWriter::insertStringIntoBundle( Error ResourceFileWriter::insertStringIntoBundle(
StringTableInfo::Bundle &Bundle, uint16_t StringID, StringRef String) { StringTableInfo::Bundle &Bundle, uint16_t StringID,
const std::vector<StringRef> &String) {
uint16_t StringLoc = StringID & 15; uint16_t StringLoc = StringID & 15;
if (Bundle.Data[StringLoc]) if (Bundle.Data[StringLoc])
return createError("Multiple STRINGTABLE strings located under ID " + return createError("Multiple STRINGTABLE strings located under ID " +
@ -1261,13 +1262,15 @@ Error ResourceFileWriter::writeStringTableBundleBody(const RCResource *Base) {
// The string format is a tiny bit different here. We // The string format is a tiny bit different here. We
// first output the size of the string, and then the string itself // first output the size of the string, and then the string itself
// (which is not null-terminated). // (which is not null-terminated).
bool IsLongString;
SmallVector<UTF16, 128> Data; SmallVector<UTF16, 128> Data;
RETURN_IF_ERROR(processString(Res->Bundle.Data[ID].getValueOr(StringRef()), if (Res->Bundle.Data[ID]) {
NullHandlingMethod::CutAtDoubleNull, bool IsLongString;
for (StringRef S : *Res->Bundle.Data[ID])
RETURN_IF_ERROR(processString(S, NullHandlingMethod::CutAtDoubleNull,
IsLongString, Data, Params.CodePage)); IsLongString, Data, Params.CodePage));
if (AppendNull && Res->Bundle.Data[ID]) if (AppendNull)
Data.push_back('\0'); Data.push_back('\0');
}
RETURN_IF_ERROR( RETURN_IF_ERROR(
checkNumberFits<uint16_t>(Data.size(), "STRINGTABLE string size")); checkNumberFits<uint16_t>(Data.size(), "STRINGTABLE string size"));
writeInt<uint16_t>(Data.size()); writeInt<uint16_t>(Data.size());

View File

@ -103,7 +103,7 @@ public:
using BundleKey = std::pair<uint16_t, uint16_t>; using BundleKey = std::pair<uint16_t, uint16_t>;
// Each bundle is in fact an array of 16 strings. // Each bundle is in fact an array of 16 strings.
struct Bundle { struct Bundle {
std::array<Optional<StringRef>, 16> Data; std::array<Optional<std::vector<StringRef>>, 16> Data;
ObjectInfo DeclTimeInfo; ObjectInfo DeclTimeInfo;
uint16_t MemoryFlags; uint16_t MemoryFlags;
Bundle(const ObjectInfo &Info, uint16_t Flags) Bundle(const ObjectInfo &Info, uint16_t Flags)
@ -157,7 +157,8 @@ private:
Error visitStringTableBundle(const RCResource *); Error visitStringTableBundle(const RCResource *);
Error writeStringTableBundleBody(const RCResource *); Error writeStringTableBundleBody(const RCResource *);
Error insertStringIntoBundle(StringTableInfo::Bundle &Bundle, Error insertStringIntoBundle(StringTableInfo::Bundle &Bundle,
uint16_t StringID, StringRef String); uint16_t StringID,
const std::vector<StringRef> &String);
// User defined resource // User defined resource
Error writeUserDefinedBody(const RCResource *); Error writeUserDefinedBody(const RCResource *);

View File

@ -698,8 +698,14 @@ RCParser::ParseType RCParser::parseStringTableResource() {
// between, however we strictly adhere to the single statement definition. // between, however we strictly adhere to the single statement definition.
ASSIGN_OR_RETURN(IDResult, readInt()); ASSIGN_OR_RETURN(IDResult, readInt());
consumeOptionalType(Kind::Comma); consumeOptionalType(Kind::Comma);
std::vector<StringRef> Strings;
ASSIGN_OR_RETURN(StrResult, readString()); ASSIGN_OR_RETURN(StrResult, readString());
Table->addString(*IDResult, *StrResult); Strings.push_back(*StrResult);
while (isNextTokenKind(Kind::String))
Strings.push_back(read().value());
Table->addStrings(*IDResult, std::move(Strings));
} }
return std::move(Table); return std::move(Table);

View File

@ -118,8 +118,12 @@ raw_ostream &MenuResource::log(raw_ostream &OS) const {
raw_ostream &StringTableResource::log(raw_ostream &OS) const { raw_ostream &StringTableResource::log(raw_ostream &OS) const {
OS << "StringTable:\n"; OS << "StringTable:\n";
OptStatements->log(OS); OptStatements->log(OS);
for (const auto &String : Table) for (const auto &String : Table) {
OS << " " << String.first << " => " << String.second << "\n"; OS << " " << String.first << " =>";
for (const auto &S : String.second)
OS << " " << S;
OS << "\n";
}
return OS; return OS;
} }

View File

@ -583,12 +583,12 @@ public:
// Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa381050(v=vs.85).aspx // Ref: msdn.microsoft.com/en-us/library/windows/desktop/aa381050(v=vs.85).aspx
class StringTableResource : public OptStatementsRCResource { class StringTableResource : public OptStatementsRCResource {
public: public:
std::vector<std::pair<uint32_t, StringRef>> Table; std::vector<std::pair<uint32_t, std::vector<StringRef>>> Table;
StringTableResource(OptionalStmtList &&List, uint16_t Flags) StringTableResource(OptionalStmtList &&List, uint16_t Flags)
: OptStatementsRCResource(std::move(List), Flags) {} : OptStatementsRCResource(std::move(List), Flags) {}
void addString(uint32_t ID, StringRef String) { void addStrings(uint32_t ID, std::vector<StringRef> &&Strings) {
Table.emplace_back(ID, String); Table.emplace_back(ID, Strings);
} }
raw_ostream &log(raw_ostream &) const override; raw_ostream &log(raw_ostream &) const override;
Twine getResourceTypeName() const override { return "STRINGTABLE"; } Twine getResourceTypeName() const override { return "STRINGTABLE"; }