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 double GetObjectData implementation for badly implemented serializable classes #554

Merged
merged 6 commits into from Jan 20, 2021
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -6,6 +6,7 @@ Enhancements:
- .NET Standard 2.0 and 2.1 support (@lg2de, #485)

Bugfixes:
- Proxying certain `[Serializable]` classes produces proxy types that fail PEVerify test (@stakx, #367)
- `private protected` methods are not intercepted (@CrispyDrone, #535)
- `System.UIntPtr` unsupported (@stakx, #546)

Expand Down
Expand Up @@ -591,6 +591,19 @@ public void SimpleProxySerialization()
Assert.AreEqual(current, otherProxy.Current);
}

[Test]
public void BadSerializable()
{
const int expectedValue = 13;

var proxy = generator.CreateClassProxy<BadSerializable>();
proxy.Value = expectedValue;

var otherProxy = SerializeAndDeserialize(proxy);

Assert.AreEqual(expectedValue, otherProxy.Value);
}

public override void TearDown()
{
base.TearDown();
Expand Down
@@ -0,0 +1,31 @@
// Copyright 2004-2021 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.

#if FEATURE_SERIALIZATION

namespace Castle.DynamicProxy.Tests.Serialization
{
using System;
using System.Runtime.Serialization;

[Serializable]
public abstract class BadSerializable
{
public int Value { get; set; }

public abstract void GetObjectData(SerializationInfo info, StreamingContext context);
}
}

#endif
Expand Up @@ -18,9 +18,12 @@ namespace Castle.DynamicProxy.Contributors
{
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;

using Castle.DynamicProxy.Generators;
using Castle.DynamicProxy.Generators.Emitters;
using Castle.DynamicProxy.Generators.Emitters.CodeBuilders;
using Castle.DynamicProxy.Generators.Emitters.SimpleAST;
Expand All @@ -29,29 +32,55 @@ namespace Castle.DynamicProxy.Contributors

internal class ClassProxySerializableContributor : SerializableContributor
{
private readonly bool delegateToBaseGetObjectData;
private readonly bool implementISerializable;
private bool delegateToBaseGetObjectData;
private ConstructorInfo serializationConstructor;
private readonly IList<FieldReference> serializedFields = new List<FieldReference>();

public ClassProxySerializableContributor(Type targetType, IList<MethodInfo> methodsToSkip, Type[] interfaces,
string typeId)
public ClassProxySerializableContributor(Type targetType, Type[] interfaces, string typeId)
: base(targetType, interfaces, typeId)
{
if (targetType.IsSerializable)
Debug.Assert(targetType.IsSerializable, "This contributor is intended for serializable types only.");
jonorossi marked this conversation as resolved.
Show resolved Hide resolved
}

public override void CollectElementsToProxy(IProxyGenerationHook hook, MetaType model)
{
delegateToBaseGetObjectData = VerifyIfBaseImplementsGetObjectData(targetType, model, out var getObjectData);

// This contributor is going to add a `GetObjectData` method to the proxy type.
// If a method with the same name and signature exists in the proxied class type,
// and another contributor has decided to proxy it, we need to tell it not to.
// Otherwise, we'll end up with two implementations!

if (getObjectData == null)
{
implementISerializable = true;
delegateToBaseGetObjectData = VerifyIfBaseImplementsGetObjectData(targetType, methodsToSkip);
// `VerifyIfBaseImplementsGetObjectData` only searches for `GetObjectData`
// in the implementation map for `ISerializable`. In the best case, it was
// already found there. If not, we need to look again, since *any* method
// with the same signature is a problem.

var getObjectDataMethod = targetType.GetMethod(
"GetObjectData",
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance,
null,
new[] { typeof(SerializationInfo), typeof(StreamingContext) },
null);

if (getObjectDataMethod != null)
{
getObjectData = model.FindMethod(getObjectDataMethod);
}
}

if (getObjectData != null && getObjectData.Proxyable)
{
getObjectData.Ignore = true;
}
}

public override void Generate(ClassEmitter @class)
{
if (implementISerializable)
{
ImplementGetObjectData(@class);
Constructor(@class);
}
ImplementGetObjectData(@class);
Constructor(@class);
}

protected override void AddAddValueInvocation(ArgumentReference serializationInfo, MethodEmitter getObjectData,
Expand Down Expand Up @@ -162,8 +191,10 @@ private void GenerateSerializationConstructor(ClassEmitter emitter)
ctor.CodeBuilder.AddStatement(new ReturnStatement());
}

private bool VerifyIfBaseImplementsGetObjectData(Type baseType, IList<MethodInfo> methodsToSkip)
private bool VerifyIfBaseImplementsGetObjectData(Type baseType, MetaType model, out MetaMethod getObjectData)
{
getObjectData = null;

if (!typeof(ISerializable).IsAssignableFrom(baseType))
{
return false;
Expand Down Expand Up @@ -192,7 +223,7 @@ private bool VerifyIfBaseImplementsGetObjectData(Type baseType, IList<MethodInfo
throw new ArgumentException(message);
}

methodsToSkip.Add(getObjectDataMethod);
getObjectData = model.FindMethod(getObjectDataMethod);

serializationConstructor = baseType.GetConstructor(
BindingFlags.Instance | BindingFlags.Public |
Expand Down
Expand Up @@ -29,14 +29,12 @@ namespace Castle.DynamicProxy.Contributors

internal class ClassProxyTargetContributor : CompositeTypeContributor
{
private readonly IList<MethodInfo> methodsToSkip;
private readonly Type targetType;

public ClassProxyTargetContributor(Type targetType, IList<MethodInfo> methodsToSkip, INamingScope namingScope)
public ClassProxyTargetContributor(Type targetType, INamingScope namingScope)
: base(namingScope)
{
this.targetType = targetType;
this.methodsToSkip = methodsToSkip;
}

protected override IEnumerable<MembersCollector> CollectElementsToProxyInternal(IProxyGenerationHook hook)
Expand All @@ -59,7 +57,7 @@ protected override IEnumerable<MembersCollector> CollectElementsToProxyInternal(
protected override MethodGenerator GetMethodGenerator(MetaMethod method, ClassEmitter @class,
OverrideMethodDelegate overrideMethod)
{
if (methodsToSkip.Contains(method.Method))
if (method.Ignore)
{
return null;
}
Expand Down
Expand Up @@ -27,15 +27,12 @@ namespace Castle.DynamicProxy.Contributors

internal class ClassProxyWithTargetTargetContributor : CompositeTypeContributor
{
private readonly IList<MethodInfo> methodsToSkip;
private readonly Type targetType;

public ClassProxyWithTargetTargetContributor(Type targetType, IList<MethodInfo> methodsToSkip,
INamingScope namingScope)
public ClassProxyWithTargetTargetContributor(Type targetType, INamingScope namingScope)
: base(namingScope)
{
this.targetType = targetType;
this.methodsToSkip = methodsToSkip;
}

protected override IEnumerable<MembersCollector> CollectElementsToProxyInternal(IProxyGenerationHook hook)
Expand All @@ -58,7 +55,7 @@ protected override IEnumerable<MembersCollector> CollectElementsToProxyInternal(
protected override MethodGenerator GetMethodGenerator(MetaMethod method, ClassEmitter @class,
OverrideMethodDelegate overrideMethod)
{
if (methodsToSkip.Contains(method.Method))
if (method.Ignore)
{
return null;
}
stakx marked this conversation as resolved.
Show resolved Hide resolved
Expand Down
Expand Up @@ -150,7 +150,7 @@ protected void ImplementGetObjectData(ClassEmitter emitter)
protected abstract void CustomizeGetObjectData(AbstractCodeBuilder builder, ArgumentReference serializationInfo,
ArgumentReference streamingContext, ClassEmitter emitter);

public void CollectElementsToProxy(IProxyGenerationHook hook, MetaType model)
public virtual void CollectElementsToProxy(IProxyGenerationHook hook, MetaType model)
{
}
}
Expand Down
Expand Up @@ -34,10 +34,10 @@ protected BaseClassProxyGenerator(ModuleScope scope, Type targetType, Type[] int
protected abstract FieldReference TargetField { get; }

#if FEATURE_SERIALIZATION
protected abstract SerializableContributor GetSerializableContributor(List<MethodInfo> methodsToSkip);
protected abstract SerializableContributor GetSerializableContributor();
#endif

protected abstract CompositeTypeContributor GetProxyTargetContributor(List<MethodInfo> methodsToSkip, INamingScope namingScope);
protected abstract CompositeTypeContributor GetProxyTargetContributor(INamingScope namingScope);

protected abstract ProxyTargetAccessorContributor GetProxyTargetAccessorContributor();

Expand Down Expand Up @@ -109,15 +109,14 @@ protected sealed override Type GenerateType(string name, INamingScope namingScop
private IEnumerable<Type> GetTypeImplementerMapping(out IEnumerable<ITypeContributor> contributors, INamingScope namingScope)
{
var contributorsList = new List<ITypeContributor>(capacity: 5);
var methodsToSkip = new List<MethodInfo>(); // TODO: the trick with methodsToSkip is not very nice...
var targetInterfaces = targetType.GetAllInterfaces();
var typeImplementerMapping = new Dictionary<Type, ITypeContributor>();

// Order of interface precedence:

// 1. first target
// target is not an interface so we do nothing
var targetContributor = GetProxyTargetContributor(methodsToSkip, namingScope);
var targetContributor = GetProxyTargetContributor(namingScope);
contributorsList.Add(targetContributor);

// 2. then mixins
Expand Down Expand Up @@ -183,7 +182,7 @@ private IEnumerable<Type> GetTypeImplementerMapping(out IEnumerable<ITypeContrib
#if FEATURE_SERIALIZATION
if (targetType.IsSerializable)
{
var serializableContributor = GetSerializableContributor(methodsToSkip);
var serializableContributor = GetSerializableContributor();
contributorsList.Add(serializableContributor);
AddMappingForISerializable(typeImplementerMapping, serializableContributor);
}
Expand Down
Expand Up @@ -37,15 +37,15 @@ protected override CacheKey GetCacheKey()
}

#if FEATURE_SERIALIZATION
protected override SerializableContributor GetSerializableContributor(List<MethodInfo> methodsToSkip)
protected override SerializableContributor GetSerializableContributor()
{
return new ClassProxySerializableContributor(targetType, methodsToSkip, interfaces, ProxyTypeConstants.Class);
return new ClassProxySerializableContributor(targetType, interfaces, ProxyTypeConstants.Class);
}
#endif

protected override CompositeTypeContributor GetProxyTargetContributor(List<MethodInfo> methodsToSkip, INamingScope namingScope)
protected override CompositeTypeContributor GetProxyTargetContributor(INamingScope namingScope)
{
return new ClassProxyTargetContributor(targetType, methodsToSkip, namingScope) { Logger = Logger };
return new ClassProxyTargetContributor(targetType, namingScope) { Logger = Logger };
}

protected override ProxyTargetAccessorContributor GetProxyTargetAccessorContributor()
Expand Down
Expand Up @@ -51,15 +51,15 @@ protected override void CreateFields(ClassEmitter emitter)
}

#if FEATURE_SERIALIZATION
protected override SerializableContributor GetSerializableContributor(List<MethodInfo> methodsToSkip)
protected override SerializableContributor GetSerializableContributor()
{
return new ClassProxySerializableContributor(targetType, methodsToSkip, interfaces, ProxyTypeConstants.ClassWithTarget);
return new ClassProxySerializableContributor(targetType, interfaces, ProxyTypeConstants.ClassWithTarget);
}
#endif

protected override CompositeTypeContributor GetProxyTargetContributor(List<MethodInfo> methodsToSkip, INamingScope namingScope)
protected override CompositeTypeContributor GetProxyTargetContributor(INamingScope namingScope)
{
return new ClassProxyWithTargetTargetContributor(targetType, methodsToSkip, namingScope) { Logger = Logger };
return new ClassProxyWithTargetTargetContributor(targetType, namingScope) { Logger = Logger };
}

protected override ProxyTargetAccessorContributor GetProxyTargetAccessorContributor()
Expand Down
2 changes: 2 additions & 0 deletions src/Castle.Core/DynamicProxy/Generators/MetaMethod.cs
Expand Up @@ -52,6 +52,8 @@ public string Name
get { return name; }
}

public bool Ignore { get; internal set; }

public bool Proxyable { get; private set; }

public bool Standalone { get; private set; }
stakx marked this conversation as resolved.
Show resolved Hide resolved
Expand Down
8 changes: 8 additions & 0 deletions src/Castle.Core/DynamicProxy/Generators/MetaType.cs
Expand Up @@ -15,11 +15,13 @@
namespace Castle.DynamicProxy.Generators
{
using System.Collections.Generic;
using System.Reflection;

internal class MetaType
{
private readonly ICollection<MetaEvent> events = new TypeElementCollection<MetaEvent>();
private readonly ICollection<MetaMethod> methods = new TypeElementCollection<MetaMethod>();
private readonly Dictionary<MethodInfo, MetaMethod> methodsIndex = new Dictionary<MethodInfo, MetaMethod>();
private readonly ICollection<MetaProperty> properties = new TypeElementCollection<MetaProperty>();

public IEnumerable<MetaEvent> Events
Expand All @@ -46,11 +48,17 @@ public void AddEvent(MetaEvent @event)
public void AddMethod(MetaMethod method)
{
methods.Add(method);
methodsIndex.Add(method.Method, method); // shouldn't get added twice
}

public void AddProperty(MetaProperty property)
{
properties.Add(property);
}

public MetaMethod FindMethod(MethodInfo method)
{
return methodsIndex.TryGetValue(method, out var metaMethod) ? metaMethod : null;
}
}
}