This is an archive of the discontinued LLVM Phabricator instance.

Adding parsing ability for .res file, building the resource directory tree
ClosedPublic

Authored by ecbeckmann on May 25 2017, 2:38 PM.

Event Timeline

ecbeckmann created this revision.May 25 2017, 2:38 PM
ecbeckmann retitled this revision from Adding parsing ability for .res file. to Adding parsing ability for .res file, building the resource directory tree.May 25 2017, 3:05 PM

Some minor formatting changes.

Another format change.

zturner added inline comments.May 25 2017, 3:47 PM
llvm/include/llvm/Object/WindowsResource.h
53–61

I don't think we need to propagate these Error values out of the function. The constructor should assert that HeaderBytes is a good size, and at that point none of the reads can fail and you can just return the values instead of taking them by reference.

118

You can just say = default;

119

No need to explicitly initialize Name, its default constructor will get invoked implicitly.

120

Same as above, but this time for ID, and you can initialize Name directly I believe. Name(NameRef)

185

Can you do an inline initialization here? uint16_t ID = 0xFFFF;

llvm/lib/Object/WindowsResource.cpp
96–98

Instead of doing this seeking and reading on every function call, can you just initialize all these values in the constructor and then just return them? It makes the logic simpler to follow if you just read everything out all at once, because then there's no seeking going on. It's just read / read / read / read.

ruiu added inline comments.May 25 2017, 3:52 PM
llvm/include/llvm/Object/WindowsResource.h
51–54

I'd move this out of namespace llvm as namespaces have no effect on C macros.

55

isString -> IsString

116

Move function definitions for this class to the .cpp file.

123–124

(You want to add newlines between multi-line inline functions so that they don't look like a single blob of some code.)

137

This is a question not only for this but for all the other functions you added in this patch.

Can you do error checking only when you are reading files?

Currently, you are doing error checking both when reading files and writing to files. However, as long as you verify that input files, and you don't generate malformed data in your code (if it does, it is a bug), you don't need to check for errors when you are writing.

The reason why I'm saying is because the code is not easy to read because that contains probably too much error checking snippets. Other file reading/writing tools don't have this much ErrorOr or Expected internally, so they can be reduced.

ecbeckmann marked 7 inline comments as done.

Move implementation to source file, removed a lot of unnecessary error checking.

llvm/include/llvm/Object/WindowsResource.h
51–54

I moved it to the source file since it is no longer necessary in header.

55

returns bool instead of Error now

137

All the error checking is now done when the file is initially read.

185

What is the purpose of this initialization? We only ever check the ID if it is in the IDChildren list, and before adding to this list we always give the ID that was written in the .res file.

zturner added inline comments.May 26 2017, 4:52 PM
llvm/include/llvm/Object/WindowsResource.h
185

You were already initializing it in the constructor to -1, but the initialization was repeated each time there was a new overload of the constructor. If you initialize it this way, it's functionally equivalent to the code you had before, but you're just not repeating the specification of the value -1.

ecbeckmann added inline comments.May 26 2017, 5:51 PM
llvm/include/llvm/Object/WindowsResource.h
185

Ah okay. I actually decided we don't really need to initialize the numeric ID.

zturner added inline comments.May 26 2017, 9:21 PM
llvm/include/llvm/Object/WindowsResource.h
113

Should this function be const?

124

Can this argument be a StringRef?

llvm/lib/Object/WindowsResource.cpp
98

I think this function would be a lot clearer if we had some structs that we could just read out in one or two lines rather than reading some fields, ignoring others, skipping sometimes, reading sometimes, etc. How about this?

class ResourceEntryRef {
  struct HeaderPrefix {
    ulittle32_t DataSize;
    ulittle32_t HeaderSize;
    ulittle16_t Unknown;
    ulittle16_t TypeID;
    ulittle16_t IDFlag;
    ulittle16_t NameID;
  };

  struct HeaderSuffix {
    ulittle32_t DataVersion;
    ulittle16_t MemoryFlags;
    ulittle16_t LanguageID;
    ulittle32_t Version;
    ulittle32_t Characteristics;
  };

  const HeaderPrefix *Prefix = nullptr;
  const HeaderSuffix *Suffix = nullptr;
};

...

Error ResourceEntryRef::loadNext() {
  RETURN_IF_ERROR(Reader.readObject(Prefix));
  if (Prefix->IDFlag != 0xFFFF) {
    Reader.setOffset(Reader.getOffset() - 4);
    RETURN_IF_ERROR(Reader.readWideString(Name));
  }

  RETURN_IF_ERROR(Reader.readObject(Suffix));
  RETURN_IF_ERROR(Reader.readArray(Data, Prefix->DataSize);
  RETURN_IF_ERROR(Reader.padToAlignment(sizeof(uint32_t));
  return Error::success();
}
111

Is this math actually necessary? It sounds like we just want to read until the first null terminator. Maybe add a function to BinaryStreamReader similar to readCString but called readWideString?

171–174

Can you try this?

StringChildren.emplace(NameString, std::move(NewChild));
ecbeckmann marked 5 inline comments as done.

Read some header entries into struct instead of reading individually.

Create readWideString function in BinaryStreamReader.

Be able to read user-defined types and add test case for this.

llvm/lib/Object/WindowsResource.cpp
98

Hmmm this issue is actually pretty difficult because we can't really use a HeaderPrefix structure at all. I didn't realize this when I first implemented this but actually both the Type and the Name can either be numeric IDs or variable length strings; therefore there isn't really any "standard" length that we can read into a structure easily. Perhaps we can have a HeaderSuffix structure though....

zturner added inline comments.May 29 2017, 6:15 PM
llvm/lib/Object/WindowsResource.cpp
98

I see. In that case, can you add a function

Error ResourceEntryRef::readStringOrId(BinaryStreamReader &Reader, uint16_t &ID, ArrayRef<UTF16> &Str);

and then write:

RETURN_IF_ERROR(readStringOrId(Reader, TypeID, Type));
RETURN_IF_ERROR(readStringOrId(Reader, NameID, Name));

Add helper function for readability.

ecbeckmann added inline comments.May 29 2017, 6:48 PM
llvm/lib/Object/WindowsResource.cpp
98

Okay. I feel like this function shouldn't be a member function of ResourceEntryRef because it only really has a use in this specific instance and it's precondition is that the offset of the reader has been directed to a specific point.

ecbeckmann marked an inline comment as done.May 29 2017, 6:49 PM
zturner accepted this revision.May 29 2017, 6:54 PM

lgtm with the last two suggested changes.

llvm/lib/Object/WindowsResource.cpp
97–112

It looks like this is no longer being used, so we can probably delete this line.

98

Seems reasonable. But if it's going to be global, make it static so that its linkage doesn't leak outside of this TU.

This revision is now accepted and ready to land.May 29 2017, 6:54 PM
ecbeckmann marked 2 inline comments as done.

Last minor changes.

This revision was automatically updated to reflect the committed changes.
ecbeckmann reopened this revision.May 30 2017, 3:26 PM

Still some build bot errors that need to be fixed.

This revision is now accepted and ready to land.May 30 2017, 3:26 PM
  • This patch should fix various clang warnings and a use of to_string
ecbeckmann closed this revision.EditedMay 30 2017, 8:57 PM

Fixed in rL304255.