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

Make ProxyUtil.IsAccessible(MethodBase) take into account declaring type's accessibility #290

Merged
merged 5 commits into from
Jul 25, 2017

Conversation

stakx
Copy link
Member

@stakx stakx commented Jul 19, 2017

This should fix #289.

I tried to follow @jonorossi's suggestions as closely as possible, but I might've done a few things too many here, or misunderstood something. If so, please let me know. See the commit notes for details. A few points regarding the code moves:

  • I moved everything from InternalsUtil to ProxyUtil. The former class now only contains "forwarding" methods which are hidden from IntelliSense, but not marked as [Obsolete]; none of them has an exact correspondence with a public method in ProxyUtil. (I chose not to make the moved methods public right away as that would've led to additional duplicate methods in the public API.)

  • Those three methods from DefaultProxyBuilder become a single method ProxyUtil.IsAccessibleType. (One is a helper method, and the other two only ever get called together. Combining them also allows optimizing away repeated calls to GetTypeInfo.)

  • Moving extension methods into the public /* non-static */ class ProxyUtil means they can no longer be extension methods. (I opted not to re-declare ProxyUtil as static right away.)

The preexisting tests for `ProxyUtil.IsAccessible(MethodBase)` only
cover methods that are declared in a class that is accessible to
DynamicProxy. Let's add one more test case where a public method is
declared inside an inaccessible type. This will make some tests fail
and thus reveal a bug.
This commit fixes the bug revealed in the preceding commit by making
`ProxyUtil.IsAccessible(MethodBase)` take into account the declaring
type's accessibility, as well.

That method is currently a forward to the identically named method in
`InternalsUtils` which also receives internals calls from DynamicProxy
itself, so we need to clearly separate the public API and the internal
API before we change any behaviour. This is done as follows:

 * The relationship between `ProxyUtil` and `InternalsUtil` is revers-
   ed, i.e. the actual implementations are moved to `ProxyUtil`, and
   `InternalsUtil`'s public methods are reduced to forwards.

 * In `ProxyUtil`, we now have internal methods (e.g. `IsAccessible-
   Method` and `IsAccessibleType`) which get called by DynamicProxy.
   We also have a separate public method group (`IsAccessible`) which
   makes use of the internal methods.

Finally, we move in suitable preexisting code from `DefaultProxyBuil-
der` that allows checking a declaring type's visibility.
Copy link
Member

@jonorossi jonorossi left a comment

Choose a reason for hiding this comment

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

@stakx great work, exactly what I was after and a little more which in this case is good. I do have a thought about where the implementation should go, see the comment.

Because you've touched a bunch of existing code it is fair game for me to add a few review comments to it as well, hope you don't mind make those changes too 😉 .

I definitely agree that those moved methods shouldn't be public, we'll make them public as required in the future, but we need to make the InternalsUtil ones Obsolete otherwise people won't stop using them.

/// visible to DynamicProxyGenAssembly2.</summary>
/// <param name="targetAssembly">The assembly containing the type or method.</param>
/// <returns>Instructions that a user could follow to make a type or method visible to DynamicProxyGenAssembly2.</returns>
internal static string CreateInstructionsToMakeVisible(Assembly targetAssembly)
Copy link
Member

Choose a reason for hiding this comment

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

I think this method should go into ExceptionMessageBuilder instead, otherwise ProxyUtil is going to get big quickly.

The other option would be to keep InternalsUtil which can hold the implementation and ProxyUtil just forwards through, we could keep the obsolete public methods at the top of the file for now forwarding to internal methods below. Thoughts?

Copy link
Member Author

@stakx stakx Jul 21, 2017

Choose a reason for hiding this comment

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

Agreed. I think ExceptionMessageBuilder' would be a good and fitting new home for CreateInstructionsToMakeVisible.

(Given its somewhat vague name, InternalsUtil could easily become a Kitchen Sink Of Lost Methods, and it would perhaps be better to stop putting new stuff in there and get rid of it as soon as is practical. Especially since other utility classes already have internal things in them.)


string GetAssemblyName(Assembly ta)
{
return ta.GetName().Name;
Copy link
Member

Choose a reason for hiding this comment

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

I'd just inline this, the method was only used once in the old code.

#else
// .NET Core does not provide an API to do this, so we just fall back to the solution that will definitely work.
// After all it is just an exception message.
return false;
Copy link
Member

Choose a reason for hiding this comment

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

Indentation.

strongNamedOrNotIndicator = "";
assemblyToBeVisibleTo = ReferencesCastleCore(targetAssembly)
? "InternalsVisible.ToDynamicProxyGenAssembly2"
: '"' + InternalsVisible.ToDynamicProxyGenAssembly2 + '"';
Copy link
Member

Choose a reason for hiding this comment

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

I know this was in the old code exactly as is, however do we actually need this, what does the message look like with the extra double quotes?

Copy link
Contributor

Choose a reason for hiding this comment

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

I'd submit that we do need it. Okay, maybe not "need". The bit after ? provides a convenience mechanism for assemblies that reference Castle.Core. They can just add

[assembly: InternalsVisibleTo("InternalsVisible.ToDynamicProxyGenAssembly2")]

instead of providing the full reference to the assembly themselves. The bit after the : gives a message like

Can not create proxy for type FluentAssertions.Common.NullReflector because it is not accessible. Make it public, or internal and mark your assembly with [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] attribute, because assembly FluentAssertions.Core is strong-named.

a

Copy link
Member

Choose a reason for hiding this comment

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

Thanks @blairconrad, I think I was starring at that code too long, completely missed that it was referencing that field with the strong name key and not just a constant string literal.

All good, it can stay as is.

/// <returns><c>true</c> if the method is accessible to DynamicProxy, <c>false</c> otherwise.</returns>
internal static bool IsAccessibleMethod(MethodBase method)
{
// Accessibility supported by the full framework and CoreCLR
Copy link
Member

Choose a reason for hiding this comment

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

This comment can be deleted, "CoreCLR" in this context referred to Silverlight's "CoreCLR".

return true;
}

if (AreInternalsVisibleToDynamicProxy(method.DeclaringType.GetTypeInfo().Assembly) && method.IsAssembly)
Copy link
Member

Choose a reason for hiding this comment

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

The method.IsAssembly check should be first as it is much less expensive.


bool VisibleToDynamicProxy(InternalsVisibleToAttribute attribute)
{
return attribute.AssemblyName.Contains(ModuleScope.DEFAULT_ASSEMBLY_NAME);
Copy link
Member

Choose a reason for hiding this comment

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

I know this was its own method before, but this should just be an inline lambda.

/// Determines whether this assembly has internals visible to dynamic proxy.
/// </summary>
/// <param name = "asm">The assembly to inspect.</param>
internal static bool AreInternalsVisibleToDynamicProxy(Assembly asm)
Copy link
Member

Choose a reason for hiding this comment

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

Could you move this internal function further down, with the methods for accessibility.

#if FEATURE_REMOTING
using System.Runtime.Remoting;
#endif

using Castle.Core.Internal;
using Castle.DynamicProxy.Generators.Emitters;

public class ProxyUtil
Copy link
Member

Choose a reason for hiding this comment

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

Let's go ahead and make this static like it should have been, however don't change the functions back to extension methods.

/// <summary>
/// Determines whether the specified method is internal.
/// </summary>
/// <param name = "method">The method.</param>
/// <returns>
/// <c>true</c> if the specified method is internal; otherwise, <c>false</c>.
/// </returns>
[EditorBrowsable(EditorBrowsableState.Never)]
Copy link
Member

Choose a reason for hiding this comment

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

We should just mark these obsolete, your call if you want to leave EditorBrowsable on there too.

Copy link
Member Author

@stakx stakx Jul 21, 2017

Choose a reason for hiding this comment

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

Added the [Obsolete] attributes. I left [EditorBrowsable] in place though, hope you really don't mind. It could in theory even be put on the whole class, but perhaps you're still going to use that class in the future so I didn't do this.

Copy link
Member

Choose a reason for hiding this comment

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

I left [EditorBrowsable] in place though, hope you really don't mind.

Nope, it's all good.

(This commit includes a minor bug fix regarding visibility checks for
methods that are declared with `FamilyAndAssembly` accessibility,
which requires an additional check for `[InternalsVisibleTo]`.)

Bugfixes:
- Make ProxyUtil.IsAccessible(MethodBase) take into account declaring type's accessibility so it doesn't report false negatives for e.g. public methods in inaccessible classes. (@stakx, #289)

Copy link
Member Author

@stakx stakx Jul 21, 2017

Choose a reason for hiding this comment

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

Did you want the following mentioned as well?

  1. The ProxyUtil class having become static. (Possibly a breaking change, even though it's very unlikely.)
  2. Members of InternalsUtil now being obsolete.

If so, under what heading do you usually list non-breaking changes? Just Changes: above Bugfixes:? (I didn't see any previous example.)

Copy link
Member

Choose a reason for hiding this comment

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

  1. I wouldn't, if anyone was inheriting this class, then bad luck 😉
  2. It could be breaking if someone re-compiles and has warnings as errors, however I think we just mention it when we actually remove them so we don't bloat the changelog.

If so, under what heading do you usually list non-breaking changes?

I think I've used "Enhancements" in one of the changelogs but that isn't applicable here obviously. Something to decide when it comes up.

if (method.IsAssembly || method.IsFamilyAndAssembly)
{
return AreInternalsVisibleToDynamicProxy(method.DeclaringType.GetTypeInfo().Assembly);
}
Copy link
Member Author

Choose a reason for hiding this comment

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

Instead of merging the check for method.IsFamilyAndAssembly with the first if block, I merged it with the latter (as per your comment).

Copy link
Member

@jonorossi jonorossi left a comment

Choose a reason for hiding this comment

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

All good, let's get this merged!

/// <summary>
/// Determines whether the specified method is internal.
/// </summary>
/// <param name = "method">The method.</param>
/// <returns>
/// <c>true</c> if the specified method is internal; otherwise, <c>false</c>.
/// </returns>
[EditorBrowsable(EditorBrowsableState.Never)]
Copy link
Member

Choose a reason for hiding this comment

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

I left [EditorBrowsable] in place though, hope you really don't mind.

Nope, it's all good.


Bugfixes:
- Make ProxyUtil.IsAccessible(MethodBase) take into account declaring type's accessibility so it doesn't report false negatives for e.g. public methods in inaccessible classes. (@stakx, #289)

Copy link
Member

Choose a reason for hiding this comment

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

  1. I wouldn't, if anyone was inheriting this class, then bad luck 😉
  2. It could be breaking if someone re-compiles and has warnings as errors, however I think we just mention it when we actually remove them so we don't bloat the changelog.

If so, under what heading do you usually list non-breaking changes?

I think I've used "Enhancements" in one of the changelogs but that isn't applicable here obviously. Something to decide when it comes up.

@jonorossi jonorossi merged commit 29e5eb2 into castleproject:master Jul 25, 2017
@jonorossi jonorossi added this to the vNext milestone Jul 25, 2017
@stakx stakx deleted the proxyutil-isaccessible branch July 25, 2017 13:12
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.

ProxyUtil.IsAccessible(MethodBase) reports false positive for method declared in internal class
3 participants