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

Prevent member name collision when proxy implements same generic interface more than twice #285

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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(']');
}
}
}
}