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

Replicate custom attributes on constructor parameters #355

Merged
merged 3 commits into from
May 14, 2018

Conversation

stakx
Copy link
Member

@stakx stakx commented May 2, 2018

This is an attempt at resolving #341.

The gist of this PR is to change code in the following location where custom attribute reproduction happens (currently only for ParamArrayAttribute):

if (baseConstructorParams != null && baseConstructorParams.Length != 0)
{
var last = baseConstructorParams.Last();
if (last.ParameterType.GetTypeInfo().IsArray && last.IsDefined(typeof(ParamArrayAttribute)))
{
var parameter = constructor.ConstructorBuilder.DefineParameter(args.Length, ParameterAttributes.None, last.Name);
var info = AttributeUtil.CreateInfo<ParamArrayAttribute>();
parameter.SetCustomAttribute(info.Builder);
}
}

This gets changed to replicate all custom attributes of all parameters.

There's a catch, however: Because constructors in the generated proxy type have different signatures (additional parameters e.g. for the interceptor array), the constructors' signatures do not match with those in the base class. This means that custom attribute inheritance would never work with these constructor parameters, which is why we need to disregard [AttributeTargets(Inherited = true)] and reproduce even those attributes.

Can you think of any scenario(s) where replicating additional, "inheritable" custom attributes on ctors could cause negative side effects in downstream libraries?

Finally, take note that AFAIK even the updated code does not yet reproduce custom modifiers (modopts, modreqs) because that would have to happen in a different code location and because that's not what #341 is about. Eventually, we should reproduce custom modifiers as well, otherwise we might get confronted with something like #339, but for ctors.

Copy link
Member Author

@stakx stakx left a comment

Choose a reason for hiding this comment

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

Just a couple of explanatory notes.

src/Castle.Core/DynamicProxy/Internal/AttributeUtil.cs Outdated Show resolved Hide resolved
var info = AttributeUtil.CreateInfo<ParamArrayAttribute>();
parameter.SetCustomAttribute(info.Builder);
var parameterBuilder = constructor.ConstructorBuilder.DefineParameter(offset + i, baseConstructorParams[i].Attributes, baseConstructorParams[i].Name);
foreach (var attribute in baseConstructorParams[i].GetCustomAttributesToBeReproducedOnProxyCtor())
Copy link
Member Author

Choose a reason for hiding this comment

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

For regular methods, we'd call baseMethodParams[i].GetNonInheritableAttributes(), but we cannot do that here because no attributes would ever be returned by it due to the non-matching ctor signatures. We therefore need a new method that does all the work AttributeUtil.GetNonInheritableAttributes() does, except for excluding inheritable custom attributes.

@@ -153,7 +153,7 @@ public static IEnumerable<CustomAttributeInfo> GetNonInheritableAttributes(this
#else
var attributeType = attribute.AttributeType;
#endif
if (ShouldSkipAttributeReplication(attributeType))
if (ShouldSkipAttributeReplication(attributeType, ignoreInheritance: false))
Copy link
Member Author

Choose a reason for hiding this comment

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

ignoreInheritance: false will result in the same behavior that was there before this parameter was added.

@stakx stakx mentioned this pull request May 2, 2018
@@ -204,7 +224,7 @@ public static IEnumerable<CustomAttributeInfo> GetNonInheritableAttributes(this
var attributeType = attribute.AttributeType;
#endif

if (ShouldSkipAttributeReplication(attributeType))
if (ShouldSkipAttributeReplication(attributeType, ignoreInheritance))
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 use (parameter.Member as ConstructorInfo) != null rather than those new methods.

Not sure why I wrote that inheritance could be the problem in the issue, because obviously constructors are not part of an class inheritance contract and must be redefined, but maybe I was thinking it could be this code excluding them because they are marked inheritable 😉.

Copy link
Member Author

@stakx stakx May 2, 2018

Choose a reason for hiding this comment

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

Could you please clarify which new methods you'd like to avoid and where a condition such as parameter.Member is ConstructorInfo should go?

I'll quickly lay out how I ended up with my proposed changes:

  • Your check by itself would not be sufficient. For a ctor parameter attribute to get replicated, it must be of a public type, and it must not be in AttributesToAvoidReplicating. Since ShouldSkipAttributeReplication already performs these checks and has a fitting name, I thought I'd reuse it.

  • But to prevent ShouldSkipAttributeReplication from doing too much for ctor parameters, it needs the additional parameter to decide when to bypass checking AttributeUsageAttribute.Inherited. It has no access to a ParameterInfo, so parameter.Member is ConstructorInfo cannot go in here.

  • parameter.Member is ConstructorInfo can go in the original GetNonInheritableAttributes and so avoid the two new forwarding methods. However, doing this makes GetNonInheritableAttributes return inheritable attributes in some circumstances, which might seem like a bug, given its name.

Copy link
Member

Choose a reason for hiding this comment

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

Yep, I would like to avoid the 2 new forwarding methods as they seem unnecessary. I was thinking you could determine if you are handling a constructor in GetNonInheritableAttributes, pretty much thinking:

bool ignoreInheritance = (parameter.Member is ConstructorInfo);

Since constructors in .NET are not inherited (not that you'd want them to be), I don't see any problem returning all attributes via this named method because no constructor parameters can ever be inherited.

Copy link
Member Author

Choose a reason for hiding this comment

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

Ah, gotcha. Makes sense now. Done.

CHANGELOG.md Outdated Show resolved Hide resolved
@stakx stakx force-pushed the ctor-params-attributes branch from 4370cdc to befc287 Compare May 13, 2018 17:25
gliljas and others added 3 commits May 14, 2018 14:41
This takes the unit test from castleproject#345 and applies some necessary changes:

 * Bring it in line with the recently changed directory and namespace
   structure of the test project.

 * The LINQ query fails with a `NullReferenceException` on .NET Core.
   Therefore rewrite the test such that this test bug doesn't surface.
   It's still the same test in spirit.
The only custom attribute that DynamicProxy currently reproduces
on a proxy class' parameters is [ParamArray] (if present on the last
parameter of the base class' ctor).

This commit changes this so DynamicProxy replicates *all* non-
inheritable custom attributes on ctor parameters.

Regarding "non-inheritable": Because proxy ctors have a different
signature than the proxied class' ctors, parameters can never inherit
custom attributes from the base ctor. This means that inheritability
of custom attributes needs to be ignored in the case of proxy ctors.

Also some minor cleanups in `AttributeUtil` while we're looking at it.
@stakx stakx force-pushed the ctor-params-attributes branch from befc287 to 38b1357 Compare May 14, 2018 12:43
@stakx
Copy link
Member Author

stakx commented May 14, 2018

Squashed a little and rebased on top of current master, should be good to go.

@jonorossi
Copy link
Member

All good, merging.

@jonorossi jonorossi merged commit 1bc7fae into castleproject:master May 14, 2018
@jonorossi jonorossi added this to the vNext milestone May 14, 2018
@stakx stakx deleted the ctor-params-attributes branch May 14, 2018 13:18
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.

3 participants