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

Validate C# DefineConstants input #9612

Open
wants to merge 6 commits into
base: main
Choose a base branch
from

Conversation

adamint
Copy link
Member

@adamint adamint commented Dec 5, 2024

As can be seen in this bug, you can add arbitrary semicolons in the DefineConstants control and these will be saved. This PR

  1. Adds tests for KeyValuePairListEncoding
  2. Allows for different separators
  3. Since the CPS controls hands the property interceptor a comma-separated list of values, but each individual value may itself contain a semicolon-separated list of constants, the property interceptor is changed to first convert the comma-separated list to a semicolon-separated list, then parse it again and trim semicolons from each value
Microsoft Reviewers: Open in CodeFlow

…nvert DefineConstants input to semicolon-separated list before saving
@adamint adamint requested a review from a team as a code owner December 5, 2024 08:59
Copy link
Member

@drewnoakes drewnoakes left a comment

Choose a reason for hiding this comment

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

IIRC VB handles these properties differently to C#, so we should make sure to test both languages. @melytc do you recall the details? I think VB allows you to have values (i.e. A=1) for define constants, rather than just names (i.e. A).

[InlineData("key1=value1;key2=value2;key3=value3", new[] { "key1", "value1", "key2", "value2", "key3", "value3" })]
[InlineData("key1;key2=value2", new[] { "key1", "", "key2", "value2" })]
[InlineData("key1;key2;key3=value3", new[] { "key1", "", "key2", "", "key3", "value3" })]
[InlineData("key1;;;key3;;", new[] { "key1", "", "key3", "" })]
Copy link
Member

Choose a reason for hiding this comment

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

Would be good to cover some more inputs:

  • ""
  • " "
  • "="
  • ";"

If there are invalid inputs, a test that ensures that Parse throws would be good. For example, "==", or null.

Copy link
Member Author

@adamint adamint Dec 6, 2024

Choose a reason for hiding this comment

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

I added test cases on those inputs except null, as the method accepts a non-nullable input string. But I wouldn't necessarily agree that there are invalid inputs possible here, since == should parse to an empty key, value =.

Copy link
Member

Choose a reason for hiding this comment

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

Thanks for adding more tests.

since == should parse to an empty key, value =

I'm less confident about that than you. I feel like == should be an invalid and ambiguous input that throws. If values are expected to contain delimiters, then they should be escaped.

Copy link
Member Author

Choose a reason for hiding this comment

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

I feel like == should be an invalid and ambiguous input that throws.

I'm not entirely in agreement; the difference between = and the other delimiters is that = has no meaning other than as part of a value after already encountering an =

Copy link
Member

Choose a reason for hiding this comment

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

I mean that it's ambiguous whether the name or value is =. Values also cannot contain commas without escaping.

What happens if the Format method is given names/values that contain = or ,? My guess is that these values would not round-trip correctly.

For robustness, we should either throw on invalid inputs, or implement escaping so that these values are handled correctly.

Copy link
Member

Choose a reason for hiding this comment

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

What happens if you enter strings in the UI containing = and ,?

@adamint
Copy link
Member Author

adamint commented Dec 7, 2024

@drewnoakes I've updated this now and paired with CPS changes to bring this control to a state I feel happy with.

Specifically, the additional change I made here, other than adding more tests, is to in OnGetUnevaluatedPropertyValueAsync also return values that have been removed (ie, in Constant1;Constant2, we will individually show the values Constant1 and Constant2 in the control, but not Constant1;Constant2, so we want to remove that value). I've made two gifs to show the before and after of the control. You can see how messed-up the state can get in the before gif.

before:
defineconstants-before

after:
defineconstants-after

@adamint adamint requested a review from drewnoakes December 7, 2024 06:26
@melytc melytc added the Feature-Project-Properties-Designer The new project property pages which replace the legacy AppDesigner label Dec 11, 2024
@melytc
Copy link
Contributor

melytc commented Dec 11, 2024

IIRC VB handles these properties differently to C#, so we should make sure to test both languages. @melytc do you recall the details? I think VB allows you to have values (i.e. A=1) for define constants, rather than just names (i.e. A).

that's right! VB uses this syntax: symbol1 = value1, symbol2 = value2.

In the project properties UI, we have a different control for VB:
image

@adamint are these changes that you are proposing only for the C# control?

@adamint
Copy link
Member Author

adamint commented Dec 11, 2024

@adamint are these changes that you are proposing only for the C# control?

yes the vb property has a different control and property interceptor! this is only for c#


internal const string DefineConstantsRecursivePrefix = "$(DefineConstants)";

private HashSet<string> _removedValues = [];
Copy link
Member

Choose a reason for hiding this comment

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

I'm not sure it's safe to store this state here. These providers are generally safe to call concurrently. By tracking this state here, that's no longer the case. It might work most of the time, but it makes me a little nervous. I'll spend some more time here to see if I can come up with any suggestions.

Copy link
Member Author

Choose a reason for hiding this comment

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

I've removed this state and instead handle collapsing multiple values down into one inside the CPS control, as provided by a regex rule in editor metadata. Thanks, your comment prompted me to rethink how this worked.

Adam Ratzman added 3 commits December 12, 2024 12:11
# Conflicts:
#	tests/Microsoft.VisualStudio.ProjectSystem.Managed.UnitTests/Mocks/ConfiguredProjectFactory.cs
@adamint adamint requested a review from drewnoakes December 12, 2024 17:19
@haileymck
Copy link
Contributor

@adamint are these changes that you are proposing only for the C# control?

yes the vb property has a different control and property interceptor! this is only for c#

this is a blast from the past!

Comment on lines +26 to +27
if ((allowsEmptyKey && !string.IsNullOrEmpty(decodedEntryValue))
|| !string.IsNullOrEmpty(decodedEntryKey) || !string.IsNullOrEmpty(decodedEntryValue))
Copy link
Member

Choose a reason for hiding this comment

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

Is this right?

Logically, (A && B) || B is the same as just B.

Where A is allowsEmptyKey and B is !string.IsNullOrEmpty(decodedEntryValue).

If I'm not mistaken, allowsEmptyKey does nothing here.

I think a ! should be removed:

Suggested change
if ((allowsEmptyKey && !string.IsNullOrEmpty(decodedEntryValue))
|| !string.IsNullOrEmpty(decodedEntryKey) || !string.IsNullOrEmpty(decodedEntryValue))
if ((allowsEmptyKey && string.IsNullOrEmpty(decodedEntryValue))
|| !string.IsNullOrEmpty(decodedEntryKey) || !string.IsNullOrEmpty(decodedEntryValue))

But the fact that this passed the unit tests makes me wonder if the tests need improving or if the check can just be removed here altogether.

Comment on lines 88 to 89
// Copied from ActiveLaunchProfileEnvironmentVariableValueProvider in the .NET Project System.
// In future, EnvironmentVariablesNameValueListEncoding should be exported from that code base and imported here.
Copy link
Member

Choose a reason for hiding this comment

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

Is this comment still correct? With the changes here, these implementations have diverged.

@@ -32,4 +33,6 @@ public static ConfiguredProject ImplementUnconfiguredProject(UnconfiguredProject

return mock.Object;
}

internal interface ITestConfiguredProjectImpl : ConfiguredProject, ConfiguredProject2;
Copy link
Member

@drewnoakes drewnoakes Dec 13, 2024

Choose a reason for hiding this comment

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

Rather than introducing a new interface, we can use Moq's built-in support for this.

https://github.com/devlooped/moq/wiki/Quickstart#advanced-features

From memory I think it's something like (those docs aren't super clear):

var mock = new Mock<ConfiguredProject>();
mock.Setup(...);
...
var mock2 = mock.As<ConfiguredProject2>();
mock2.Setup(c => c.EnsureProjectEvaluatedAsync()).Returns(Task.CompletedTask);

return mock.Object;

Copy link
Member

Choose a reason for hiding this comment

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

Though creating a new interface was a pretty clever way to achieve the same thing. Use whichever you prefer. I don't think there's much material difference.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Feature-Project-Properties-Designer The new project property pages which replace the legacy AppDesigner
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants