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

Detect whitespace in property name #5672

Merged
merged 12 commits into from
Nov 4, 2020
Merged

Conversation

mfkl
Copy link
Contributor

@mfkl mfkl commented Aug 23, 2020

Attempt at #5615

  • It is mentioned in MSBuild should error for invalid properties in conditional expression #5615 that MSBuild should both error and warn about this so not sure which is the correct approach here. An error might break a lot of existing code, but it might be the correct thing to do (?).
  • Also not sure whether this warrants a new string error message, though I did create one. If so, should I commit my untranslated xlf files? I am not familiar with the i18n process in MSBuild.

Any feedback welcome.

Copy link
Member

@Forgind Forgind left a comment

Choose a reason for hiding this comment

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

Thanks for these changes! Would you mind making it more general by including all invalid characters rather than just whitespace?

src/Build.UnitTests/Scanner_Tests.cs Outdated Show resolved Hide resolved
src/Build/Evaluation/Conditionals/Scanner.cs Outdated Show resolved Hide resolved
src/Build/Resources/Strings.resx Outdated Show resolved Hide resolved
@Forgind
Copy link
Member

Forgind commented Aug 24, 2020

Looking at the error, you might need to target it more precisely when someone uses property functions. $([MSBuild]::ValueOrDefault('%(FullPath)', '')... is expected.

@mfkl
Copy link
Contributor Author

mfkl commented Aug 28, 2020

Thank you both for the feedback!

Taken most of your remarks into account, now working on

Looking at the error, you might need to target it more precisely when someone uses property functions. $([MSBuild]::ValueOrDefault('%(FullPath)', '')... is expected.

@mfkl
Copy link
Contributor Author

mfkl commented Aug 28, 2020

Would you mind making it more general by including all invalid characters rather than just whitespace?

I have looked around a bit in docs and code, but I could not find such a list.

What would you consider invalid characters? https://docs.microsoft.com/en-us/visualstudio/msbuild/msbuild-special-characters?view=vs-2019?

@Forgind
Copy link
Member

Forgind commented Aug 28, 2020

What would you consider invalid characters? https://docs.microsoft.com/en-us/visualstudio/msbuild/msbuild-special-characters?view=vs-2019?

@danmosemsft, is there a list somewhere of all the invalid characters?

@rainersigwald
Copy link
Member

I think this is the check to use:

/// <summary>
/// Indicates if the given name is valid as the name of an item, property or metadatum.
/// </summary>
/// <remarks>
/// Note that our restrictions are more stringent than those of the XML Standard.
/// </remarks>
/// <param name="name"></param>
/// <returns>true, if name is valid</returns>
internal static bool IsValidElementName(string name)
{
return LocateFirstInvalidElementNameCharacter(name) == -1;
}

@mfkl
Copy link
Contributor Author

mfkl commented Aug 31, 2020

Hmmm IsValidSubsequentElementNameCharacter won't allow for expressions like

"'$(CSharpCoreTargetsPath)' == '' or ('$(DesignTimeBuild)' == 'true' and $(CSharpCoreTargetsPath.Contains('Microsoft.Net.Compilers.1.0.0')))"

It fails on the dot .

@Forgind
Copy link
Member

Forgind commented Aug 31, 2020

Good point. Maybe exclude ., [, ], and :? I think those are the only relevant ones.

Unless you have nested properties/items, in which case also @, $, %, (, and )...

And also !?

@mfkl
Copy link
Contributor Author

mfkl commented Sep 4, 2020

Including these extra valid character checks is failing a lot of tests, and the scope of the original PR is also quite larger than initially. There is an added perf consideration as well with this as mentioned in LocateFirstInvalidElementNameCharacter's comments, which is called a lot. I'm a bit unsure on how to proceed.

@danmoseley
Copy link
Member

I would limit the fix to just whitespace since that’s the common mistake. I haven’t looked at the code recently, but there should be a place in the parser/scanner where encountering a close paren would complete a property name; in that place, it should reject space.

@Forgind
Copy link
Member

Forgind commented Sep 18, 2020

We noticed that this only runs when looking at properties inside conditions. Would you mind making it work for all properties? After that, I think it's good.

@mfkl
Copy link
Contributor Author

mfkl commented Sep 19, 2020

We noticed that this only runs when looking at properties inside conditions. Would you mind making it work for all properties?

Could you please share an example of such a property that I could use as a test case?

I may be wrong, but my understanding is that the whitespace check here is done from ParsePropertyOrItemMetadata which is called for all MSBuild properties? If so, IllFormedPropertyWhitespaceInCondition should probably be renamed.

@stackedsax
Copy link

@Forgind, any feedback for @mfkl on this one?

@benvillalobos
Copy link
Member

This is supposed to throw an error when seeing whitespace in a property correct? I'm not seeing it (for properties inside or outside conditions), unless you have to build a specific way? I tried building a bootstrap of this PR and building this (taken from #5615):

<Project>
  <Target Name="t">
    <Warning Text="should print" Condition="'$(MSBuildProjectFullPath)' != ''"/>
    <Warning Text="should warn on this line" Condition="'$(MSBuildProjectFullPath )' != ''"/>
  </Target>
</Project>

Output when using standard msbuild and the bootstrapped msbuild are the same

Project "C:\src\repros\MSBuildPlayground\proj.csproj" on node 1 (default targets).
C:\src\repros\MSBuildPlayground\proj.csproj(6,5): warning : should print
Done Building Project "C:\src\repros\MSBuildPlayground\proj.csproj" (default targets).

No error is being thrown.

Same for building within a test:

        [Fact]
        public void MyTest()
        {

            string project = $"" +
                $"<Project>" +
                $"  <Target Name=\"Build\">" +
                $"    <Warning Text=\"should print\" Condition=\"'$(MSBuildProjectFullPath)' != ''\"/>" +
                $"    <Warning Text=\"should warn on this line\" Condition=\"'$(MSBuildProjectFullPath )' != ''\"/>" +
                $"  </Target>" +
                $"</Project>";

            MockLogger logger = ObjectModelHelpers.BuildProjectExpectSuccess(project);
            logger.AssertLogContains("MSB4151");
        }

Am I missing something?

AdvanceToScannerError(lexer);
Assert.Equal("IllFormedPropertyWhitespaceInCondition", lexer.GetErrorResource());

lexer = new Scanner("$( x)", ParserOptions.AllowProperties);
Copy link
Member

Choose a reason for hiding this comment

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

is it worth adding a test for "$(x yz" and "$(x y z")?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

do you really mean "$(x yz" and "$(x y z") or actually "$(x yz)" and "$(x y z)"?

Copy link
Member

Choose a reason for hiding this comment

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

The latter 😊

@mfkl
Copy link
Contributor Author

mfkl commented Oct 1, 2020

Thanks for your message.

This is supposed to throw an error when seeing whitespace in a property correct?

Yes.

I'm not seeing it (for properties inside or outside conditions), unless you have to build a specific way? I tried building a bootstrap of this PR and building this (taken from #5615):
No error is being thrown.

Indeed, I observe the same behavior with running msbuild.

Same for building within a test

For me it works when building within a unit test e624312, I can't explain yet why this test passes but msbuild.exe doesn't behave the same.
Building in diagnostics verbosity reveals

Task "Warning" skipped, due to false condition; ('$(MSBuildProjectFullPath )' != '') was evaluated as ('' != '').

@Forgind
Copy link
Member

Forgind commented Oct 1, 2020

Interesting! So this is actually far more complicated than I'd imagined when I last commented here. Your unit test works unless you put quotes around the property, i.e.,

Condition=`'$(MSBuildProjectFullPath )' != ''`

Noting that there are also quotes around every instance in which @benvillalobos used a malformed property, I suspect that this doesn't look inside strings, and that there are two ways MSBuild handles properties. (Why? I have no idea.)

We may have to talk internally about what the best way forward is. I would say this PR is a clear improvement on what previously existed, but on the other hand, it doesn't cover all cases, and I don't know the easiest way to extend it to cover all cases. I imagine you could put something in Expander.cs to look at properties as they're expanded, but I don't think there's an easy way (or any way?) to get the position of the space in that case.

@stackedsax
Copy link

@Forgind any updates from your internal discussions about this one? Would love to hear about what you've discovered.

@Forgind
Copy link
Member

Forgind commented Oct 16, 2020

After talking it over, we think it best to take it roughly as-is. We think it should go behind a change wave (talk to @benvillalobos if you need help with that), and we should change the error code to something that isn't taken, but otherwise, this is about the best we can do.

Does that sound reasonable to you, @mfkl?

Comment on lines 482 to 483
<value>MSB4151: Unexpected whitespace at position "{1}" of condition "{0}". Did you forget to remove a whitespace?</value>
<comment>{StrBegin="MSB4151: "}</comment>
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
<value>MSB4151: Unexpected whitespace at position "{1}" of condition "{0}". Did you forget to remove a whitespace?</value>
<comment>{StrBegin="MSB4151: "}</comment>
<value>MSB4259: Unexpected whitespace at position "{1}" of condition "{0}". Did you forget to remove a whitespace?</value>
<comment>{StrBegin="MSB4259: "}</comment>

Copy link
Member

Choose a reason for hiding this comment

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

(4151 was deprecated, but we should still avoid reusing error codes so people can search for them effectively online.)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok

@benvillalobos
Copy link
Member

On change waves, here's a dev doc: https://github.com/dotnet/msbuild/blob/master/documentation/wiki/ChangeWaves-Dev.md any feedback on it would be appreciated!

@mfkl
Copy link
Contributor Author

mfkl commented Oct 18, 2020

On change waves, here's a dev doc: https://github.com/dotnet/msbuild/blob/master/documentation/wiki/ChangeWaves-Dev.md any feedback on it would be appreciated!

Thanks!

I guess the doc is clear, though my initial reaction when reading it was "why was the opt-out strategy chosen over opt-in?".

I picked Wave17_0 a bit randomly. Maybe some guidance on how to choose whether to create a new Wave or use an existing one would be a good idea? I don't know, that may be tightly related to the feature itself (e.g. what are the chances it breaks customers builds?) and your way of versioning msbuild and roadmap, not sure that's relevant in that doc.

@benvillalobos
Copy link
Member

benvillalobos commented Oct 20, 2020

Thanks for the feedback!

"why was the opt-out strategy chosen over opt-in?"

Fair question. Part of the drive for that is understanding the impact for some of these changes. If we hit a particularly hot path, it allows us to reevaluate the situation knowing that folks have a way out. It also gives us a better picture of how MSBuild is being used.

I picked Wave17_0 a bit randomly

Wave17_0 works. Note that this PR will have to wait a while to be merged until master becomes 17.0.

Comment on lines 122 to 131
using TestEnvironment env = TestEnvironment.Create();
env.SetChangeWave(ChangeWaves.Wave17_0);

Scanner lexer = new Scanner("$(x )", ParserOptions.AllowProperties);
AdvanceToScannerError(lexer);
Assert.Null(lexer.UnexpectedlyFound);

lexer = new Scanner("$( x)", ParserOptions.AllowProperties);
AdvanceToScannerError(lexer);
Assert.Null(lexer.UnexpectedlyFound);
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
using TestEnvironment env = TestEnvironment.Create();
env.SetChangeWave(ChangeWaves.Wave17_0);
Scanner lexer = new Scanner("$(x )", ParserOptions.AllowProperties);
AdvanceToScannerError(lexer);
Assert.Null(lexer.UnexpectedlyFound);
lexer = new Scanner("$( x)", ParserOptions.AllowProperties);
AdvanceToScannerError(lexer);
Assert.Null(lexer.UnexpectedlyFound);
using (TestEnvironment env = TestEnvironment.Create())
{
env.SetChangeWave(ChangeWaves.Wave17_0);
Scanner lexer = new Scanner("$(x )", ParserOptions.AllowProperties);
AdvanceToScannerError(lexer);
Assert.Null(lexer.UnexpectedlyFound);
lexer = new Scanner("$( x)", ParserOptions.AllowProperties);
AdvanceToScannerError(lexer);
Assert.Null(lexer.UnexpectedlyFound);
}

Having TestEnvironment call Dispose resets state for CI builds.

Copy link
Member

Choose a reason for hiding this comment

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

The previous way actually calls Dispose, too, when env goes out of scope, and I think it's technically recommended, although it isn't something we use in MSBuild particularly.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I can change it back to the old syntax if you folks prefer.

Copy link
Member

Choose a reason for hiding this comment

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

So long as dispose is called I'm okay with its current state 👍

@Forgind
Copy link
Member

Forgind commented Oct 20, 2020

I'd suggest moving to an earlier wave, probably 16.8 or 16.10.

We don't expect this to cause a problem 99% of the time, but someone theoretically could have properties with spaces that silently return empty strings and work around that, in which case this could break their build. If it were opt-in, we wouldn't find any customers like that, and we would never know whether there actually is anyone who relies on properties with spaces being turned into empty strings.

@benvillalobos
Copy link
Member

I'd suggest moving to an earlier wave, probably 16.8 or 16.10.

Oh whoops, I meant 16.10 there. 16.8 works as well but we should decide soon. @marcpopMSFT @rainersigwald thoughts?

@@ -490,6 +490,10 @@
<value>MSB4110: Expected a property at position {1} in condition "{0}". Did you forget the opening parenthesis after the '$'? To use a literal '$', use '%24' instead.</value>
<comment>{StrBegin="MSB4110: "}</comment>
</data>
<data name="IllFormedPropertyWhitespaceInCondition" xml:space="preserve">
<value>MSB4259: Unexpected whitespace at position "{1}" of condition "{0}". Did you forget to remove a whitespace?</value>
Copy link
Member

Choose a reason for hiding this comment

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

Nit, I don't think we normally would say a whitespace. Maybe a space. Suggest just ...forget to remove whitespace?

Copy link
Member

@Forgind Forgind left a comment

Choose a reason for hiding this comment

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

LGTM! Thanks!

@mfkl
Copy link
Contributor Author

mfkl commented Oct 27, 2020

I'm having trouble analyzing the CI result, I can't find what is failing exactly or why. Is it something I did?

@Forgind
Copy link
Member

Forgind commented Oct 27, 2020

Doesn't look like it. I kicked off a new run. Since it was mono that failed, I'm guessing this flakiness will be fixed by #5831.

@benvillalobos benvillalobos merged commit 0ad5f83 into dotnet:master Nov 4, 2020
@rainersigwald
Copy link
Member

Thanks, @mfkl!

@benvillalobos
Copy link
Member

Thanks for the change @mfkl

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.

7 participants