This is an archive of the discontinued LLVM Phabricator instance.

[SanitizerCommon] Print the current value of options when printing out help.
ClosedPublic

Authored by delcypher on Oct 28 2019, 7:17 PM.

Details

Summary

Previously it wasn't obvious what the default value of various sanitizer
options were. A very close approximation of the "default values" for the
options are the current value of the options at the time of printing the
help output.

In the case that no other options are provided then the current values
are the default values (apart from help).

ASAN_OPTIONS=help=1 ./program

This patch causes the current option values to be printed when the
help output is enabled. The original intention for this patch was to append
(Default: <value>) to an option's help text. However because this
is technically wrong (and misleading) I've opted to append
(Current Value: <value>) instead.

When trying to implement a way of displaying the default value of the
options I tried another solution where the default value used in *.inc files
were used to create compile time strings that where used when printing
the help output. This solution was not satisfactory for several reasons:

  • Stringifying the default values with the preprocessor did not work very

well in several cases. Some options contain boolean operators which no
amount of macro expansion can get rid of.

  • It was much more invasive than this patch. Every sanitizer had to be changed.
  • The settings of __<sanitizer>_default_options() are ignored.

For those reasons I opted for the solution in this patch.

rdar://problem/42567204

Diff Detail

Event Timeline

delcypher created this revision.Oct 28 2019, 7:17 PM
Herald added projects: Restricted Project, Restricted Project. · View Herald TranscriptOct 28 2019, 7:17 PM
Herald added a subscriber: Restricted Project. · View Herald Transcript
eugenis added inline comments.Oct 29 2019, 10:48 AM
compiler-rt/lib/sanitizer_common/sanitizer_flag_parser.cpp
70

How about printing a truncated value in this case, maybe replacing the last few characters with dots (...)? At least for string flags.

compiler-rt/lib/sanitizer_common/sanitizer_flag_parser.h
89

const char *str = *t_ ? "true" : "false;
uptr copy_count = internal_strnlen(str, size);

104

default integer promotions apply to varargs

118

all of this can be replaced with internal_strncpy

compiler-rt/test/sanitizer_common/TestCases/options-help-current-values.cpp
2 ↗(On Diff #226832)

Remove options-help.cpp test, this new one is strictly better.

Note there is a way to modify this patch so that it does actually show the defaults in the .inc files. This would require adding storage for the default values and assigning to it in CommonFlags::SetDefaults() (and all other similar sanitizer flag parsers). Then the CurrentValueAsString() functions could be renamed to DefaultValueAsString() and could be modified to read the default value rather than the current value.

This would be more invasive than this patch and adds additional size to the runtime library, just so we can print out help=1 in a nicer way which seems wasteful. If this is something that reviewers think we should do then I would suggest doing it as a follow up patch to try to keep this change small.

delcypher marked 5 inline comments as done.Oct 29 2019, 11:09 AM
delcypher added inline comments.
compiler-rt/lib/sanitizer_common/sanitizer_flag_parser.cpp
70

In which case? Currently success being false is meant to indicate any type of failure (e.g. the current value is a nullptr which we can't print). A failure doesn't imply that truncation happened. Currently I interpret failure to mean that the buffer should not be read from because it's not in a defined state.

We could change the semantics to be that failure means truncation if that's what you want. At this point in the code though we don't know what the type of the flag is though so we can't make a string vs not string distinction.

I'd prefer to keep the current semantics of failure and just change FlagHandler<const char *>::CurrentValueAsString(...) to replace the last few characters with ... in the case of truncation.

compiler-rt/lib/sanitizer_common/sanitizer_flag_parser.h
104

I didn't know that. I'll fix this.

118

I'll fix this.

156

@eugenis I'd like to add an implementation here but I'm unsure what format string to use for the sanitizer's internal implementation of snprintf. Normally I'd use the PRId64 macro from inttypes.h but we probably can't rely on that header in sanitizer_common. Do we have an equivalent or a known format string that means "signed 64-bit integer" for all platforms?

compiler-rt/test/sanitizer_common/TestCases/options-help-current-values.cpp
2 ↗(On Diff #226832)

Sure.

delcypher marked 5 inline comments as not done.Oct 29 2019, 11:10 AM
delcypher added inline comments.
compiler-rt/lib/sanitizer_common/sanitizer_flag_parser.h
89

That's a lot more concise. I'll go with that.

yln added a comment.Oct 29 2019, 11:22 AM

Thanks for this patch, I am pretty sure I will find this useful! :)

compiler-rt/lib/sanitizer_common/sanitizer_flag_parser.cpp
70

+1 for truncation. I think we can always print a value and never fail.

Completely unnecessary, but for bonus points we could do the following.

With a buffer[n+4]: <n/2 characters>...<n/2 characters>:
/super/long/path/to/something -> /super/lo...mething

Or even smarter, with some heuristic for path components:
/super/long/path/to/something -> /super/.../to/something

This would be just "nice to have". I am fine with just truncating at the end.

compiler-rt/lib/sanitizer_common/sanitizer_flag_parser.h
25

Parsing can fail, but formatting a correct (truncated) value into a buffer should always be possible. So I think the return value of this can be void.

89

+1

compiler-rt/test/sanitizer_common/TestCases/options-help-current-values.cpp
4 ↗(On Diff #226832)

Can you extend the test to show that we display the current value, i.e, in separate RUN and CHECK lines provide some option and assert that the printed value is different from the default.

eugenis added inline comments.Oct 29 2019, 11:32 AM
compiler-rt/lib/sanitizer_common/sanitizer_flag_parser.cpp
70

I agree with all of the above. It only makes sense to do this for string arguments, and smart truncation is nice to have but not necessary.

compiler-rt/lib/sanitizer_common/sanitizer_flag_parser.h
156

If you look in sanitizer_printf.cpp, "ll" stands for s64 / u64, so "%lld".

delcypher updated this revision to Diff 227530.Nov 1 2019, 1:58 PM
  • CurrentValueAsString return code now indicates truncation
  • Print "Truncated" if printed current value was truncated.
  • Clean up CurrentValueAsString implementations.
  • Add Support in FlagHandlerInclude for showing current value (not quite right but good enough).

@yln @eugenis Can we do another round of review? I made some slightly different design choices to what was previously discussed. Here's my reasoning:

  • ... is not appended to truncated strings. I thought that might be ambiguous (Is ... part of the string or is the string truncated?). So instead every time an option gets truncated it prints (Current Value Truncated: <value>) rather than (Current Value: <value>) which is not ambiguous.
  • Due to the above CurrentValueAsString keeps its boolean return value but now returning false means truncating occurred.
  • Due to implementing the above I discovered I hadn't implemented support in FlagHandlerInclude. This is now done.
delcypher marked 7 inline comments as done.Nov 1 2019, 2:04 PM
delcypher marked 2 inline comments as done.
yln added a comment.EditedNov 1 2019, 5:19 PM

I thought that might be ambiguous (Is ... part of the string or is the string truncated?).

Do you anticipate this to be an issue? My feeling is that truncation would only happen with file paths?! I have a slight preference towards the other because it seems simpler (simple, not easy), but I am not blocking on this.

  • Due to implementing the above I discovered I hadn't implemented support in FlagHandlerInclude. This is now done.

There is another one: FlagHandlerKeepGoing

compiler-rt/lib/sanitizer_common/sanitizer_flag_parser.cpp
65

Trust yourself! ;)

compiler-rt/lib/sanitizer_common/sanitizer_flag_parser.h
25

I think this should really be an abstract method to force subclasses to implement it. Or if we can't make it abstract/pure virtual because compiler-rt is too special, then "not implemented". Same thing holds for Parse, I guess.

54

Maybe we can have a more ergonomic method name? Format or FormatInto to act as a counterpart of Parse.

83

Unnecessary when source is a \0 terminated string and copy_count is correct.

compiler-rt/lib/sanitizer_common/sanitizer_flags.cpp
82

If I understand correctly, this is a special FlagHandler, that reads a file and sets multiple other options based on the contents of the file.

The question is: does it make sense to show the file path as the current value?
I would assume that as a result of this parser, that other flags current value are updated.
If things get complicated here, I think it would be good enough for now to show nothing or some string "<options from file>".

Can we add a test for this parser type?

101

See above.

compiler-rt/test/sanitizer_common/TestCases/options-help-include-current-value.cpp
1 ↗(On Diff #227530)

This test checks the truncation, but the file name is "options-help-include-current-value". I think we can move the truncation test into the the "options-help" test file.

delcypher marked 5 inline comments as done.Nov 2 2019, 11:36 AM
delcypher added inline comments.
compiler-rt/lib/sanitizer_common/sanitizer_flag_parser.cpp
65

This is some defensive programming in case any of the implementations leave the buffer unterminated which would cause Printf(...) to read beyond the buffer.

I don't think the performance cost of doing this is significant so I'd rather keep it unless we have a good reason to remove it.

compiler-rt/lib/sanitizer_common/sanitizer_flag_parser.h
54

I guess that's a bit clearer because the method name has the verb`Format` is a verb here. So sure let's make that change.

83

That is not true. If *t_ is a string that has size or more printable characters then internal_strncpy will just copy over the size characters without adding a null terminator.

Given how error prone this is perhaps we should use internal_strlcpy(...) instead? It seems to handle this case.

compiler-rt/lib/sanitizer_common/sanitizer_flags.cpp
82

I think it makes sense to show the path to where the options were read from. I would prefer if we showed the versions where the substitutions were expanded but I refrained from doing that here because that would require us to not free the mmaped memory.

The test is options-help-include-current-value.cpp.

compiler-rt/test/sanitizer_common/TestCases/options-help-include-current-value.cpp
1 ↗(On Diff #227530)

I'm not a fan of bundling checking of lots of different things into the same test. When the test fails it can make it much harder to understand exactly what is failing.

However. Given that I've bundled the other checks I guess we could move this into the options-help.cpp too.

delcypher marked an inline comment as done.Nov 2 2019, 11:51 AM
delcypher added inline comments.
compiler-rt/lib/sanitizer_common/sanitizer_flag_parser.h
25

I'm just copying the style of code that was there before. I'm not against what you suggest but I'd rather fix this in a follow-up patch.

delcypher updated this revision to Diff 227965.Nov 5 2019, 2:23 PM
  • Rename CurrentValueAsString to Format.
  • Implement support for FlagHandlerKeepGoing.
  • Use Mmap to allocate memory for string representation of option value.
  • Switch to using internal_strlcpy instead of internal_strncpy.
  • Remove separate test for include_if_exists
delcypher updated this revision to Diff 227966.Nov 5 2019, 2:27 PM
  • Change path name in test.
delcypher marked 2 inline comments as done.Nov 5 2019, 2:28 PM
Harbormaster completed remote builds in B40547: Diff 227966.

@yln @eugenis This ready for another round of review.

yln added a comment.Nov 6 2019, 9:12 AM
In D69546#1731024, @yln wrote:

I thought that might be ambiguous (Is ... part of the string or is the string truncated?).

Do you anticipate this to be an issue? My feeling is that truncation would only happen with file paths?! I have a slight preference towards the other because it seems simpler (simple, not easy), but I am not blocking on this.

^ Please comment, thanks.

compiler-rt/lib/msan/msan.cpp
126

// keep_going is an old name for halt_on_error, and it has inverse meaning.

:(

Maybe flip operands and remove negation to make it easier to follow.

compiler-rt/lib/sanitizer_common/sanitizer_flag_parser.h
112–115

const char *str = *t_ ? *t_ : "(nullptr)";

More consistent with the other implementations.

compiler-rt/lib/sanitizer_common/sanitizer_flags.cpp
82

Makes sense.

compiler-rt/test/sanitizer_common/TestCases/options-help.cpp
7

I think we dropped the test for truncation?

delcypher marked an inline comment as done.Nov 6 2019, 11:11 AM
delcypher added inline comments.
compiler-rt/test/sanitizer_common/TestCases/options-help.cpp
7

Yes. I've made the buffer much larger which makes testing truncation a little trickier so I just dropped it. I could add it back but I think I'll have to set the option via code rather than an environment variable because I don't think windows likes long command lines.

delcypher updated this revision to Diff 228325.Nov 7 2019, 3:07 PM
  • Add trunation test.
  • Style clean up in FlagHandler<const char *>::Format(char *buffer, uptr size).
  • Style clean up in FlagHandlerKeepGoing.
delcypher marked 3 inline comments as done.Nov 7 2019, 3:09 PM
In D69546#1731024, @yln wrote:

I thought that might be ambiguous (Is ... part of the string or is the string truncated?).

Do you anticipate this to be an issue? My feeling is that truncation would only happen with file paths?! I have a slight preference towards the other because it seems simpler (simple, not easy), but I am not blocking on this.

Well visually ... is very similar to .. which actually has a meaning in paths. In the current implementation truncation is very unlikely to happen because we allocate a much larger buffer to store strings representing the current value.

That said. I'm pretty happy with the implementation now. Only in extreme cases will truncation happen and we handle it is a way that is unambiguous and hopefully robust.

delcypher updated this revision to Diff 228335.Nov 7 2019, 4:22 PM
  • Go back to a small buffer for printing values. Mmap is probably overkill here.
  • Remove defensive check.
delcypher updated this revision to Diff 228341.Nov 7 2019, 5:02 PM

Put in a proper defensive check rather than "defensive programming" so that we crash if someone screws up.

yln added inline comments.Nov 11 2019, 11:05 AM
compiler-rt/lib/msan/msan.cpp
126

Does this compile? halt_on_error seems to be missing _.

delcypher marked an inline comment as done.Nov 11 2019, 1:21 PM
delcypher added inline comments.
compiler-rt/lib/msan/msan.cpp
126

Good catch. MSan doesn't build on darwin so I missed this.

delcypher updated this revision to Diff 228770.Nov 11 2019, 2:14 PM
  • Fix MSan compilation error.
delcypher updated this revision to Diff 229178.Nov 13 2019, 1:37 PM
  • Clean up test case to not use compiled in default options.
  • Adopt a few testing tweaks suggested by @yln offline.
  • Refactor string printing code into helper method to reduce code duplication.
  • Drop some comments. There are comments on internal_snprintf that explain the same thing.
  • Use internal_snprintf in FormatString. It handles the nullptr case already.
yln accepted this revision.Nov 14 2019, 11:35 AM
This revision is now accepted and ready to land.Nov 14 2019, 11:35 AM
This revision was automatically updated to reflect the committed changes.