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

CompilerVisibleProperty fails to escape special characters (# and ;) #51692

Open
m0sa opened this issue Mar 5, 2021 · 9 comments
Open

CompilerVisibleProperty fails to escape special characters (# and ;) #51692

m0sa opened this issue Mar 5, 2021 · 9 comments
Assignees
Milestone

Comments

@m0sa
Copy link

m0sa commented Mar 5, 2021

Version Used:
5.0.103

Steps to Reproduce:

  1. In a csproj which consumes a configurable analyzer / generator, add
    <PropertyGroup>
      <Foo>Foo;Bar</Foo>
     <!-- I've also tried this, w/o success
       <Foo_>Foo;Bar</Foo>
       <Foo>$([MSBuild]::Escape('$(Foo_)'))</Foo>
      -->
    </PropertyGroup>
    <ItemGroup>
     <CompilerVisibleProperty Include="Foo" />
    </ItemGroup>
  2. In the analyzer
    AnalyzerConfigOptions.GlobalOptions.TryGetValue("build_property.Foo", out string value)

Expected Behavior:
value should contain the whole value - "Foo;Bar"

Actual Behavior:
value contains only "Foo", ;Bar is escaped as an .INI comment


I've dug around a bit, and the issue seems to be in how the parameters are being passed to WriteAllLines here:

<WriteLinesToFile Lines="$(_GeneratedEditorConfigFileContent)" File="$(GeneratedMSBuildEditorConfigFile)" Overwrite="True" WriteOnlyWhenDifferent="True" />

It unescapes the _GeneratedEditorConfigFileContent value even if it's (manually) escaped:

Binlog:
build

Generated editorconfig:
editorconfig content

Analyzer execution:
analyzer

@dotnet-issue-labeler dotnet-issue-labeler bot added Area-Compilers untriaged Issues and PRs which have not yet been triaged by a lead labels Mar 5, 2021
@sharwell
Copy link
Member

sharwell commented Mar 5, 2021

This is effectively a duplicate of #51625, but is occurring under very different circumstances.

@m0sa
Copy link
Author

m0sa commented Mar 5, 2021

@sharwell indeed, although I stopped looking at this point. Seems like in order for a full round-trip with special characters to work, there would need to be a well defined escaping spec (which seems to be missing now, the value is passed on and parsed as-is).

There are actually 2 bugs here:

  1. the msbuild escaped value is unescaped before being written to the editorconfig. In my example above, I'm manually escaping a semicolon to %3b, but the the WriteLinesToFile unescapes it. The editorconfig should contain %3bs instead of a semicolons.
  2. There is no escaping happening automatically in msbuild, and there's no unescaping happening when the values are parsed here:
    var value = propMatches[0].Groups[2].Value;
    Debug.Assert(!string.IsNullOrEmpty(key));
    Debug.Assert(key == key.Trim());
    Debug.Assert(value == value?.Trim());
    key = CaseInsensitiveComparison.ToLower(key);
    if (ReservedKeys.Contains(key) || ReservedValues.Contains(value))
    {
    value = CaseInsensitiveComparison.ToLower(value);
    }
    activeSectionProperties[key] = value ?? "";
    continue;

    e.g. It could get base64 encoded in msbuild and decoded in AnalyzerConfig.cs to support a full roundtrip.

@jaredpar jaredpar added Need More Info The issue needs more information to proceed. and removed untriaged Issues and PRs which have not yet been triaged by a lead labels Mar 8, 2021
@jaredpar
Copy link
Member

jaredpar commented Mar 8, 2021

@mavasani, @chsienki can u all elaborate on this part?

Seems like in order for a full round-trip with special characters to work, there would need to be a well defined escaping spec (which seems to be missing now, the value is passed on and parsed as-is).

@m0sa
Copy link
Author

m0sa commented Mar 8, 2021

Here's the workaround I have, so I can pass strings containing arbitrary characters from csproj to analyzers:

Analyzer:

var foo =
    context.AnalyzerConfigOptions.GlobalOptions.TryGetValue("build_property.Foo", out var foo64) 
    && !string.IsNullOrWhiteSpace(foo64)
        ? System.Text.Encoding.UTF8.GetString(System.Convert.FromBase64String(foo64))
        : null;

.csproj consuming (and configuring) the analyzer:

  <ItemGroup>
    <CompilerVisibleProperty Include="Foo" />
  </ItemGroup>
  <UsingTask TaskName="ToBase64" TaskFactory="RoslynCodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll">
    <ParameterGroup>
      <In ParameterType="System.String" Required="true" />
      <Out ParameterType="System.String" Output="true" />
    </ParameterGroup>
    <Task>
      <Code Type="Fragment" Language="C#">Out = System.Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(In));</Code>
    </Task>
  </UsingTask>
  <Target Name="RoslynIssue51692Workaround" BeforeTargets="BeforeCompile" Condition=" '$(Foo)' != '' ">
    <ToBase64 In="$(Foo)">
      <Output TaskParameter="Out" PropertyName="Foo" />
    </ToBase64>
  </Target>

@chsienki
Copy link
Contributor

chsienki commented Mar 8, 2021

@m0sa Funnily enough base64 encoding was one of the options we considered for how to route everything through the various layers correctly.

@jaredpar The problem is that we take an msbuild value -> write that to .globalconfig -> read it into the compiler. There is an impedence mismatch between the way msbuild + .globalconfig handles escaping of values, meaning its possible to loose data. A simple example of this is multi-line values in msbuild. We'll lose everything after the initial line.

@ghost
Copy link

ghost commented Sep 15, 2021

Closing this issue as we've seen no reply to the request for more information. If you are able to get the requested information, please add it to the issue and we will retriage it.

@jaredpar jaredpar reopened this Sep 15, 2021
@ghost ghost closed this as completed Sep 26, 2021
@ghost
Copy link

ghost commented Sep 26, 2021

Closing this issue as we've seen no reply to the request for more information. If you are able to get the requested information, please add it to the issue and we will retriage it.

@sharwell sharwell reopened this Sep 29, 2021
@sharwell sharwell removed the Need More Info The issue needs more information to proceed. label Sep 29, 2021
@sharwell sharwell changed the title CompilerVisibleProperty semicolon escaping bug CompilerVisibleProperty fails to escape special characters (# and ;) Sep 29, 2021
@sharwell sharwell added untriaged Issues and PRs which have not yet been triaged by a lead and removed fabric-bot-test Testing the impact of changes to the fabric bot labels Sep 29, 2021
@jaredpar jaredpar added Bug and removed untriaged Issues and PRs which have not yet been triaged by a lead labels Oct 5, 2021
@jaredpar jaredpar added this to the 17.1 milestone Oct 5, 2021
@jcouv jcouv modified the milestones: 17.1, 17.2 Mar 17, 2022
@jcouv jcouv removed this from the 17.2 milestone May 14, 2022
@Sebazzz
Copy link

Sebazzz commented Dec 21, 2023

I also found that having a newline in your compiler property causes the rest of the contents to spill over into the GlobalOptions - effectively becoming visible.

kzu added a commit to devlooped/ThisAssembly that referenced this issue Jun 19, 2024
This is a workaround (fix?) for dotnet/roslyn#51692, basically.

We lose all content after the first semicolon in constant and metadata values (presumably Git too).

Project properties also suffer from this, but merits unifying it with ThisAssembly.Constants to continue to streamline the implementation.
kzu added a commit to devlooped/ThisAssembly that referenced this issue Jun 19, 2024
This is a workaround (fix?) for dotnet/roslyn#51692, basically.

We lose all content after the first semicolon in constant and metadata values (presumably Git too).

Project properties also suffer from this, but merits unifying it with ThisAssembly.Constants to continue to streamline the implementation.
@AlexeyRaga
Copy link

I am having the same (similar?) issue when using multiline text as a value.
I have a property made visible:

<CompilerVisibleProperty Include="Avro_LogicalTypes" />

and then use it as:

<PropertyGroup>
    <Avro_LogicalTypes>
        foo : bar,
        user-id:Contrib.Avro.CodeGen.Tests.UserId ,
        duration:System.TimeSpan
    </Avro_LogicalTypes>
</PropertyGroup>

When using TryGetValue, it ignores all the multiline text.

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

No branches or pull requests

7 participants