This is an archive of the discontinued LLVM Phabricator instance.

Add JSONScopedPrinter as a subclass to ScopedPrinter with history stack
Needs ReviewPublic

Authored by Jaysonyan on Nov 16 2021, 8:54 PM.

Details

Summary

This is an alternative approach to the following diff: D111658: Add JSON output skeleton to llvm-readelf

The major difference is that in this change JSONScoperPrinter inherits from ScopedPrinter rather
than creating a ScopedPrinterBase base class which both JSONScopedPrinter and ScopedPrinter
inherit from.

In comment {D111658#3121940} I cited 3 reasons for why I didn't think this was possible. I'll go over
how this change address each problem:

Problem 1: ScopedPrinter has template methods which are not able to be virtual.
Solution: Add private internal non-template methods which can be virtual. The
public template method then transforms all template parameters into concrete types
and then used to call the internal non-template method. This enables JSONScopedPrinter
to override the functionality of all of ScopedPrinter's methods.

Problem 2: ScopedPrinter provides a method startLine() which returns the underlying
raw_ostream. It would be impossible to ensure printing directly to raw_ostream results
in valid JSON.
Solution: There is still no easy solution to the problem. Though since this change is to
enable json output for llvm-readelf. The planned solution to this problem is to have a
JSONELFDumper class similar to D111658: Add JSON output skeleton to llvm-readelf and override
methods which call startLine() to use another interface.

Problem 3: A common usage pattern inside llvm-readelf (calling ListScope(W, "attr1")
and then DictScope(W, "attr2")) produces invalid json since you can't have attributes inside
of lists. (The example would product the following json output)

ListScope(W, "attr1");
DictScope(W, "attr2");

"attr1" : [
  "attr2" : { ... }
]

Solution: Add a "history" stack similar to the one inside json::OStream conditionally
output an encompassing {} if inside the context of a list. The following are code examples
and their respective outputs:

ListScope(W, "attr1");
DictScope(W, "attr2");
W.printString("attr3",  "val");

"attr1" : [
  {
    "attr2" : {
      "attr3" : "val"
    }
  }
]
DictScope(W, "attr1");
DictScope(W, "attr2");
W.printString("attr3",  "val");

"attr1" : {
  "attr2" : {
     "attr3" : "val"
  }
}

Note: There is also another approach which handles Problem 3 differently D114053: Add JSONScopedPrinter as a subclass to ScopedPrinter with array output

Diff Detail

Event Timeline

Jaysonyan created this revision.Nov 16 2021, 8:54 PM
Jaysonyan requested review of this revision.Nov 16 2021, 8:54 PM
Herald added a project: Restricted Project. · View Herald TranscriptNov 16 2021, 8:54 PM

Some general high-level comments for now:

  1. I think this patch would benefit from being split into a few separate patches:
    1. Virtualize the necessary parts of ScopedPrinter.
    2. Add the JSONScopedPrinter class.
    3. Add the llvm-readelf changes to make use of the JSONScopedPrinter class.
  2. I think JSONScopedPrinter (and possibly ScopedPrinter too, if it doesn't already have them) could do with gtest unit tests. It should be fairly straightforward to write them, and would help give confidence in the methods, especially as we may not use all of them up-front. The tests would also help with documenting what the code actually does, which will aid in future reviewing.
  3. We need to consider what will happen when dumping archives containing non-ELF objects. In such a case, it won't be possible to dump JSON output (for now) for them. Should we emit an error/warning in this case?
  4. For the *Internal functions, I think *Impl tends to be a more common naming pattern I've seen (the non-impl versions do the common work, and then call the virtual impl versions, as you've done).

I think JSONScopedPrinter (and possibly ScopedPrinter too, if it doesn't already have them) could do with gtest unit tests. It should be fairly straightforward to write them, and would help give confidence in the methods, especially as we may not use all of them up-front. The tests would also help with documenting what the code actually does, which will aid in future reviewing.

Will do! Do we want to test each method in isolation? or possibly in conjunction with each other to test things like the history stack? or possibly both?

We need to consider what will happen when dumping archives containing non-ELF objects. In such a case, it won't be possible to dump JSON output (for now) for them. Should we emit an error/warning in this case?

I think emitting a warning to the error stream seems reasonable in this case. Since it may still be desirable to output the information for remaining ELF objects inside those archives.

For the *Internal functions, I think *Impl tends to be a more common naming pattern I've seen (the non-impl versions do the common work, and then call the virtual impl versions, as you've done).

Thanks for the insight! Addressed in D114223.

@Jaysonyan has this patch been superceded now, and if so, can it be abandoned?