aelij / IgnoresAccessChecksToGenerator

Generates reference assemblies where all the internal types & members become public, and applies the IgnoresAccessChecksTo attribute
MIT License
167 stars 21 forks source link

Doesn't work with WPF projects #6

Closed KirillOsenkov closed 3 years ago

KirillOsenkov commented 5 years ago

We have tried using the tool with a WPF project (consuming an internal type from Roslyn) and it failed because MarkupCompilePass1 attempted to instantiate an attribute from the rewritten assembly. Since the rewriter deletes method bodies it fails.

We have spent some time trying to come up with a workaround but abandoning it for now. Hopefully one day we'll return and revisit this.

Thanks for the amazing tool!

hughbe commented 5 years ago

Can't seem to get this working with System.Windows.Forms

weltkante commented 5 years ago

Hmm, I've been using this sucessfully from WinForms applications in Desktop Framework in the past (we prefer it over reflection where possible when we need to implement workarounds)

I think its trying to imitate how reference assemblies look like, so the fix would probably to reproduce the msbuild state of just redirecting reference assemblies and leaving the implementing assemblies untouched? Just guessing here though.

KirillOsenkov commented 5 years ago

Yes, we've tried to go this route in this branch: https://github.com/KirillOsenkov/IgnoresAccessChecksToGenerator/tree/wpf1

but got confused and ran out of time. Maybe next time.

aelij commented 5 years ago

@KirillOsenkov

Additionally, can you provide a repro? I just tried and it worked:

<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">

  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>netcoreapp3.0</TargetFramework>
    <UseWPF>true</UseWPF>
    <InternalsAssemblyNames>Microsoft.CodeAnalysis.CSharp</InternalsAssemblyNames>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.3.1" />
    <PackageReference Include="IgnoresAccessChecksToGenerator" Version="0.4.0" PrivateAssets="All" />
  </ItemGroup>
</Project>
var x = typeof(Microsoft.CodeAnalysis.CSharp.AwaitableInfo);

And it compiled just fine.

weltkante commented 5 years ago

(in lack of a concrete repro) you'll probably have to use something in XAML from the same assembly as you made internally visible, otherwise the markup compile pass has no reason to load it. Try not to do it with builtin types because they have a shortcut and may not trigger the bug, so maybe refer to something like the ribbon which doesn't live in a core assembly and then apply the generator on it as well.

KirillOsenkov commented 5 years ago

@aelij yes, an option to retain method bodies would be very helpful. If I can set an MSBuild property in my project that would be ideal.

hughbe commented 4 years ago

OK here's my repro


- `WebBrowserBaseTests.cs`
```cs
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

namespace System.Windows.Forms.Tests
{
    public class WebBrowserBaseTests
    {
        private class SubWebBrowserBase : WebBrowserBase
        {
            public SubWebBrowserBase(string clsidString) : base(clsidString)
            {
            }
        }
    }
}

And I get the error

error CS1729: 'WebBrowserBase' does not contain a constructor that takes 1 arguments

I'd expect this to work as WebBrowserBase has this internal constructor:

        internal WebBrowserBase(string clsidString) : base() {
            if (Application.OleRequired() != ApartmentState.STA) {
                throw new ThreadStateException(SR.GetString(SR.AXMTAThread, clsidString));
            }

            this.SetStyle(ControlStyles.UserPaint, false);

            this.clsid = new Guid(clsidString);
            this.webBrowserBaseChangingSize.Width = -1;  // Invalid value. Use WebBrowserBase.Bounds instead, when this is the case.
            this.SetAXHostState(WebBrowserHelper.isMaskEdit, this.clsid.Equals(WebBrowserHelper.maskEdit_Clsid));
        }

Any ideas?

weltkante commented 4 years ago

I think thats a different problem than what is discussed here. What is happening for you is that IgnoresAccessChecksToGenerator is running against the System.Windows.Forms reference assembly instead of the real one and the reference assembly does not have the internal constructor, so it does not appear in the rewritten version either.

hughbe commented 4 years ago

Oh! Is there a way to alter the ref assembly?

aelij commented 4 years ago

@KirillOsenkov Can you provide a repro for your error? I want to test the fix.

@hughbe, @weltkante is correct, that's a different scenario. You can work around it by specifying the reference to the implementation assembly, e.g.:

<Reference Include="System.Windows.Forms" 
       HintPath="$(WinDir)\Microsoft.NET\Framework64\v4.0.30319\System.Windows.Forms.dll" />

Don't forget to clean up the obj directory where the generated assembly is cached.

hughbe commented 4 years ago

Hero! Didn't know HintPath existed

This one worked better

C:\windows\Microsoft.Net\assembly\GAC_MSIL\System.Windows.Forms\v4.0_4.0.0.0__b77a5c561934e089\System.Windows.Forms.dll

weltkante commented 4 years ago

Didn't know HintPath existed

Its a leftover from old VS project system and doesn't work correctly anymore (produces unreliable results and conflicts with the nuget package resolution, if a different copy of the same DLL is referenced by or contained in a nuget package -possibly indirectly- VS/msbuild is not making consistent decisions about which DLL ends up getting used).

Might work as a temporary workaround, just warning you of the trouble it can cause you long term. We're having a lot of issues in our own project builds due to historic HintPath usage which we can't get rid of easily.

aelij commented 3 years ago

I've changed the task to generate throw null; instead of empty method bodies. If that doesn't work for WPF, you can also try to set the a property to keep the original bodies:

  <PropertyGroup>
    <InternalsAssemblyUseEmptyMethodBodies>false</InternalsAssemblyUseEmptyMethodBodies>
  </PropertyGroup>