Skip to content

Commit

Permalink
Merge pull request #285 from stakx/generic-interfaces-name-collisions
Browse files Browse the repository at this point in the history
Prevent member name collision when proxy implements same generic interface more than twice
  • Loading branch information
jonorossi committed Jul 7, 2017
2 parents 3facfe2 + d5c941f commit 5c947cb
Show file tree
Hide file tree
Showing 9 changed files with 236 additions and 3 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## Unreleased

Bugfixes:
- Prevent member name collision when proxy implements same generic interface more than twice (@stakx, #88)
- Fix incorrect replication (reversed order) of custom modifiers (modopts and modreqs) on the CLR, does not work yet on Mono (@stakx, #277)
- Fix COM interface proxy error case throwing exceptions trying to release null pointer from QueryInterface (@stakx, #281)

Expand Down
104 changes: 104 additions & 0 deletions src/Castle.Core.Tests/BasicInterfaceProxyTestCase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,110 @@ public void Should_implement_explicitly_duplicate_interface_members()
Assert.IsNotNull(method2);
}

[Test]
public void Should_choose_noncolliding_method_names_when_implementing_same_generic_interface_several_times()
{
var boolInterfaceType = typeof(IGenericWithNonGenericMethod<bool>);
var intInterfaceType = typeof(IGenericWithNonGenericMethod<int>);
var nestedGenericBoolInterfaceType = typeof(IGenericWithNonGenericMethod<IGenericWithNonGenericMethod<bool>>);

var proxy = generator.CreateInterfaceProxyWithoutTarget(
boolInterfaceType,
new[] { intInterfaceType, nestedGenericBoolInterfaceType });

var type = proxy.GetType().GetTypeInfo();

var boolMethod = type.GetRuntimeInterfaceMap(boolInterfaceType).TargetMethods[0];
Assert.AreEqual("SomeMethod", boolMethod.Name);

var intMethod = type.GetRuntimeInterfaceMap(intInterfaceType).TargetMethods[0];
Assert.AreEqual("IGenericWithNonGenericMethod`1[Int32].SomeMethod", intMethod.Name);

var nestedGenericBoolMethod = type.GetRuntimeInterfaceMap(nestedGenericBoolInterfaceType).TargetMethods[0];
Assert.AreEqual("IGenericWithNonGenericMethod`1[IGenericWithNonGenericMethod`1[Boolean]].SomeMethod", nestedGenericBoolMethod.Name);
}

[Test]
public void Should_choose_noncolliding_property_accessor_names_when_implementing_same_generic_interface_several_times()
{
var boolInterfaceType = typeof(IGenericWithProperty<bool>);
var intInterfaceType = typeof(IGenericWithProperty<int>);
var nestedGenericBoolInterfaceType = typeof(IGenericWithProperty<IGenericWithProperty<bool>>);

var proxy = generator.CreateInterfaceProxyWithoutTarget(
boolInterfaceType,
new[] { intInterfaceType, nestedGenericBoolInterfaceType });

var type = proxy.GetType().GetTypeInfo();

var boolGetter = type.GetRuntimeInterfaceMap(boolInterfaceType).TargetMethods[0];
var boolSetter = type.GetRuntimeInterfaceMap(boolInterfaceType).TargetMethods[1];
Assert.AreEqual("get_SomeProperty", boolGetter.Name);
Assert.AreEqual("set_SomeProperty", boolSetter.Name);

var intGetter = type.GetRuntimeInterfaceMap(intInterfaceType).TargetMethods[0];
var intSetter = type.GetRuntimeInterfaceMap(intInterfaceType).TargetMethods[1];
Assert.AreEqual("IGenericWithProperty`1[Int32].get_SomeProperty", intGetter.Name);
Assert.AreEqual("IGenericWithProperty`1[Int32].set_SomeProperty", intSetter.Name);

var nestedGenericBoolGetter = type.GetRuntimeInterfaceMap(nestedGenericBoolInterfaceType).TargetMethods[0];
var nestedGenericBoolSetter = type.GetRuntimeInterfaceMap(nestedGenericBoolInterfaceType).TargetMethods[1];
Assert.AreEqual("IGenericWithProperty`1[IGenericWithProperty`1[Boolean]].get_SomeProperty", nestedGenericBoolGetter.Name);
Assert.AreEqual("IGenericWithProperty`1[IGenericWithProperty`1[Boolean]].set_SomeProperty", nestedGenericBoolSetter.Name);
}

[Test]
public void Should_choose_noncolliding_event_accessor_names_when_implementing_same_generic_interface_several_times()
{
var boolInterfaceType = typeof(IGenericWithEvent<bool>);
var intInterfaceType = typeof(IGenericWithEvent<int>);
var nestedGenericBoolInterfaceType = typeof(IGenericWithEvent<IGenericWithEvent<bool>>);

var proxy = generator.CreateInterfaceProxyWithoutTarget(
boolInterfaceType,
new[] { intInterfaceType, nestedGenericBoolInterfaceType });

var type = proxy.GetType().GetTypeInfo();

var boolAdder = type.GetRuntimeInterfaceMap(boolInterfaceType).TargetMethods[0];
var boolRemover = type.GetRuntimeInterfaceMap(boolInterfaceType).TargetMethods[1];
Assert.AreEqual("add_SomeEvent", boolAdder.Name);
Assert.AreEqual("remove_SomeEvent", boolRemover.Name);

var intAdder = type.GetRuntimeInterfaceMap(intInterfaceType).TargetMethods[0];
var intRemover = type.GetRuntimeInterfaceMap(intInterfaceType).TargetMethods[1];
Assert.AreEqual("IGenericWithEvent`1[Int32].add_SomeEvent", intAdder.Name);
Assert.AreEqual("IGenericWithEvent`1[Int32].remove_SomeEvent", intRemover.Name);

var nestedGenericBoolAdder = type.GetRuntimeInterfaceMap(nestedGenericBoolInterfaceType).TargetMethods[0];
var nestedGenericBoolRemover = type.GetRuntimeInterfaceMap(nestedGenericBoolInterfaceType).TargetMethods[1];
Assert.AreEqual("IGenericWithEvent`1[IGenericWithEvent`1[Boolean]].add_SomeEvent", nestedGenericBoolAdder.Name);
Assert.AreEqual("IGenericWithEvent`1[IGenericWithEvent`1[Boolean]].remove_SomeEvent", nestedGenericBoolRemover.Name);
}

[Test]
public void Should_choose_noncolliding_member_names_when_implementing_same_generic_interface_with_two_type_args_several_times()
{
var boolIntInterfaceType = typeof(IGenericWithNonGenericMethod<bool, int>);
var intBoolInterfaceType = typeof(IGenericWithNonGenericMethod<int, bool>);
var intNestedGenericBoolInterfaceType = typeof(IGenericWithNonGenericMethod<int, IGenericWithNonGenericMethod<bool>>);

var proxy = generator.CreateInterfaceProxyWithoutTarget(
boolIntInterfaceType,
new[] { intBoolInterfaceType, intNestedGenericBoolInterfaceType });

var type = proxy.GetType().GetTypeInfo();

var boolIntMethod = type.GetRuntimeInterfaceMap(boolIntInterfaceType).TargetMethods[0];
Assert.AreEqual("SomeMethod", boolIntMethod.Name);

var intBoolMethod = type.GetRuntimeInterfaceMap(intBoolInterfaceType).TargetMethods[0];
Assert.AreEqual("IGenericWithNonGenericMethod`2[Int32,Boolean].SomeMethod", intBoolMethod.Name);

var intNestedGenericBoolMethod = type.GetRuntimeInterfaceMap(intNestedGenericBoolInterfaceType).TargetMethods[0];
Assert.AreEqual("IGenericWithNonGenericMethod`2[Int32,IGenericWithNonGenericMethod`1[Boolean]].SomeMethod", intNestedGenericBoolMethod.Name);
}

private ParameterInfo[] GetMyTestMethodParams(Type type)
{
MethodInfo methodInfo = type.GetMethod("MyTestMethod", BindingFlags.Instance | BindingFlags.Public);
Expand Down
23 changes: 23 additions & 0 deletions src/Castle.Core.Tests/Interfaces/IGenericWithEvent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright 2004-2010 Castle Project - http://www.castleproject.org/
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

namespace Castle.DynamicProxy.Tests.Interfaces
{
using System;

public interface IGenericWithEvent<T>
{
event EventHandler SomeEvent;
}
}
26 changes: 26 additions & 0 deletions src/Castle.Core.Tests/Interfaces/IGenericWithNonGenericMethod.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright 2004-2010 Castle Project - http://www.castleproject.org/
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

namespace Castle.DynamicProxy.Tests.Interfaces
{
public interface IGenericWithNonGenericMethod<T>
{
void SomeMethod();
}

public interface IGenericWithNonGenericMethod<T1, T2>
{
void SomeMethod();
}
}
21 changes: 21 additions & 0 deletions src/Castle.Core.Tests/Interfaces/IGenericWithProperty.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright 2004-2010 Castle Project - http://www.castleproject.org/
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

namespace Castle.DynamicProxy.Tests.Interfaces
{
public interface IGenericWithProperty<T>
{
object SomeProperty { get; set; }
}
}
2 changes: 1 addition & 1 deletion src/Castle.Core/DynamicProxy/Generators/MetaEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ public bool Equals(MetaEvent other)

internal override void SwitchToExplicitImplementation()
{
name = string.Format("{0}.{1}", sourceType.Name, name);
name = MetaTypeElementUtil.CreateNameForExplicitImplementation(sourceType, name);
adder.SwitchToExplicitImplementation();
remover.SwitchToExplicitImplementation();
}
Expand Down
2 changes: 1 addition & 1 deletion src/Castle.Core/DynamicProxy/Generators/MetaMethod.cs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ internal override void SwitchToExplicitImplementation()
Attributes |= MethodAttributes.SpecialName;
}

name = string.Format("{0}.{1}", Method.DeclaringType.Name, Method.Name);
name = MetaTypeElementUtil.CreateNameForExplicitImplementation(sourceType, Method.Name);
}

private MethodAttributes ObtainAttributes()
Expand Down
2 changes: 1 addition & 1 deletion src/Castle.Core/DynamicProxy/Generators/MetaProperty.cs
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ public bool Equals(MetaProperty other)

internal override void SwitchToExplicitImplementation()
{
name = string.Format("{0}.{1}", sourceType.Name, name);
name = MetaTypeElementUtil.CreateNameForExplicitImplementation(sourceType, name);
if (setter != null)
{
setter.SwitchToExplicitImplementation();
Expand Down
58 changes: 58 additions & 0 deletions src/Castle.Core/DynamicProxy/Generators/MetaTypeElementUtil.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// Copyright 2004-2011 Castle Project - http://www.castleproject.org/
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

namespace Castle.DynamicProxy.Generators
{
using System;
using System.Reflection;
using System.Text;

internal static class MetaTypeElementUtil
{
public static string CreateNameForExplicitImplementation(Type sourceType, string name)
{
if (sourceType.GetTypeInfo().IsGenericType)
{
var nameBuilder = new StringBuilder();
nameBuilder.AppendNameOf(sourceType);
nameBuilder.Append('.');
nameBuilder.Append(name);
return nameBuilder.ToString();
}
else
{
return string.Concat(sourceType.Name, ".", name);
}
}

private static void AppendNameOf(this StringBuilder nameBuilder, Type type)
{
nameBuilder.Append(type.Name);
if (type.GetTypeInfo().IsGenericType)
{
nameBuilder.Append('[');
var genericTypeArguments = type.GetGenericArguments();
for (int i = 0, n = genericTypeArguments.Length; i < n; ++i)
{
if (i > 0)
{
nameBuilder.Append(',');
}
nameBuilder.AppendNameOf(genericTypeArguments[i]);
}
nameBuilder.Append(']');
}
}
}
}

0 comments on commit 5c947cb

Please sign in to comment.