Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Check progress status format string validity first and fall back to default if it's invalid #2496

Open
wants to merge 4 commits into
base: master
Choose a base branch
from

Conversation

adamjoer
Copy link

@adamjoer adamjoer commented Sep 8, 2024

In my opinion it shouldn't be fatal if the progress format string configured with $NINJA_STATUS is invalid. Especially now that we have placeholders that have only been available since Ninja 1.12.

This PR therefore changes the way the progress status format string is validated, so that it is checked in the StatusPrinter constructor, and if it's invalid the default "[%f/%t] " format is used instead. It also adds a small unit test to ensure this functionality.

@mcprat
Copy link
Contributor

mcprat commented Sep 9, 2024

I don't think it's necessary to make a new function for this... there should be a simple way to do it

@adamjoer
Copy link
Author

adamjoer commented Sep 9, 2024

I don't think it's necessary to make a new function for this... there should be a simple way to do it

I agree that it's a clunky way to do it.

Would it make more sense to add a boolean out parameter to StatusPrinter::FormatProgressStatus that indicates if the format is invalid? Then the check could look something like this:

progress_status_format_ = getenv("NINJA_STATUS");
bool is_format_valid = true;
FormatProgressStatus(progress_status_format_, 0, &is_format_valid)
if (!progress_status_format_ || !is_format_valid)
  progress_status_format_ = "[%f/%t] ";

virtual void Info(const char* msg, ...) = 0;
virtual void Warning(const char* msg, ...) = 0;
virtual void Error(const char* msg, ...) = 0;
virtual void Info(const char* msg, ...) const = 0;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are unrelated changes, they are useful but please move them to their own commit instead. Thanks.

void Warning(const char* msg, ...) const override;
void Error(const char* msg, ...) const override;

bool IsValidProgressStatus(const char* progress_status_format) const;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Please make this method static.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, document it properly and make it return an error string, instead of calling Warning(), i.e. let the caller decide what to do, e.g.:

/// Validate the status format string passed as an argument to verify it only contains
/// valid escape sequences. On success return true, on failure, return false and sets \arg error.
static bool IsValidProgressStatusFormat(const char* status_format, std::string* err);

EXPECT_FALSE(status_.IsValidProgressStatus("[%f/%X] "));
EXPECT_EQ("", status_.FormatProgressStatus("[%f/%X] ", 0));
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would make more sense to move this to a new status_printer_test.cc file.

@adamjoer adamjoer force-pushed the status-format-check branch 3 times, most recently from e75fabb to 5406a36 Compare September 10, 2024 22:25
@mcprat
Copy link
Contributor

mcprat commented Sep 11, 2024

I have not looked deeper into this but at first glance, the goal should be able to be achieved with less than 10 lines changed... I don't understand how we are passing 100 lines...

Find the Fatal() call, turn it into a Warning(), and then set the format value.

am I wrong?

@mcprat
Copy link
Contributor

mcprat commented Sep 17, 2024

@digit-google would it be acceptable to make FormatProgressStatus() non-const? that would make it take only 9 lines to do this.

@@ -49,8 +49,14 @@ StatusPrinter::StatusPrinter(const BuildConfig& config)
printer_.set_smart_terminal(false);

progress_status_format_ = getenv("NINJA_STATUS");
if (!progress_status_format_)

string error_output;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: use std::string as we are trying to remove using namespace std from the sources gradually.

progress_status_format_ = "[%f/%t] ";
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: For clarity use two conditions and a common variable such as:

std::string error;
if (progress_status_format_ && !IsValidProgressStatus(progress_status_format_, &error)) {
    Warning("%s", error.c_str());
    progress_status_format_ = nullptr;
}
if (!progress_status_format_) {
  // Enforce default value if unspecified or invalid.
  progress_status_format_ = kDefaultStatusFormat;
}

@@ -2258,47 +2258,6 @@ TEST_F(BuildTest, DepsGccWithEmptyDepfileErrorsOut) {
ASSERT_EQ(1u, command_runner_.commands_ran_.size());
}

TEST_F(BuildTest, StatusFormatElapsed_e) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good, but can you put this commit before the previous one, this will make the addition of BuildTest.StatusFormatValidator clearer.

@digit-google
Copy link
Contributor

@mcprat, to be honest, I don't think it would be a good idea here. A formatting function that changes the state of its object is always confusing for the reader.

Also a single validation function that is called once before anything heavy work is done is very simple to understand.
Trying to detect the invalid value on each status output, and them modifying the formatting string there is introducing un-needed mutations. And unexpected mutations are the source of ... lots of bugs.

const char* progress_status_format_ = nullptr;

/// The progress status format to use by default
static constexpr const char* kDefaultProgressStatusFormat = "[%f/%t] ";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: static constexpr members need a definition in the corresponding .cc file or some compilers will complain that it is missing (especially when compiling with -O0). In this case you can simply add constexpr const char* StatusPrinter::kDefaultProgressStatusFormat; (with no value) to status_printer.cc.

However, we do not need to expose the value publicly, so for simplicity, just define it as a const variable in the constructor that uses it.

Also const char kName[] = "..." is preferable to static const char* kName = "..." because the latter introduces an unnecessary relocation in the generated code / data segment.

StatusPrinter::StatusPrinter(...) {
    ...
   const char kDefaultProgressStatusFormat[] = "[%f/%t] ";
   ...
}

Sorry for the nitpicks, this is a great PR :-)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's no problem at all, this is great feedback. :)

@adamjoer
Copy link
Author

adamjoer commented Oct 2, 2024

@jhasse @digit-google Any chance that this will get merged?

@digit-google
Copy link
Contributor

The code LGTM as-is, though I would appreciate if you could address the latest review commits. I'll let @jhasse decide on the merge itself :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants