Skip to content

Commit 854d69e

Browse files
Merge pull request FakeItEasy#1389 from blairconrad/immutable-in
Reject attempts to assign values to in parameters
2 parents c40c8fd + eebd68b commit 854d69e

File tree

7 files changed

+92
-17
lines changed

7 files changed

+92
-17
lines changed

src/FakeItEasy/Configuration/BuildableCallRule.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ private static ICollection<int> GetIndexesOfOutAndRefParameters(IInterceptedFake
204204
var arguments = fakeObjectCall.Method.GetParameters();
205205
for (var i = 0; i < arguments.Length; i++)
206206
{
207-
if (arguments[i].ParameterType.IsByRef)
207+
if (arguments[i].IsOutOrRef())
208208
{
209209
indexes.Add(i);
210210
}

src/FakeItEasy/Expressions/ExpressionArgumentConstraintFactory.cs

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,10 @@ public virtual IArgumentConstraint GetArgumentConstraint(ParsedArgumentExpressio
2828
return this.CreateParamArrayConstraint((NewArrayExpression)argument.Expression, parameterType);
2929
}
3030

31-
var isByRefArgument = IsByRefArgument(argument);
31+
var isOutOrRefArgument = argument.ArgumentInformation.IsOutOrRef();
3232

3333
var constraint = this.GetArgumentConstraintFromExpression(argument.Expression, parameterType, out var argumentValue);
34-
if (isByRefArgument)
34+
if (isOutOrRefArgument)
3535
{
3636
if (IsOutArgument(argument))
3737
{
@@ -66,11 +66,6 @@ private static bool IsOutArgument(ParsedArgumentExpression argument)
6666
return argument.ArgumentInformation.IsOut;
6767
}
6868

69-
private static bool IsByRefArgument(ParsedArgumentExpression argument)
70-
{
71-
return argument.ArgumentInformation.ParameterType.IsByRef;
72-
}
73-
7469
private static IArgumentConstraint CreateEqualityConstraint(object expressionValue)
7570
{
7671
return new EqualityArgumentConstraint(expressionValue);

src/FakeItEasy/FakeItEasy.csproj

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@
1515
<PackageTags>TDD;unittesting;mocks;mocking;fakes;faking;stubs;stubbing;spy;spies;doubles;isolation;substitutes;substitution</PackageTags>
1616
</PropertyGroup>
1717

18+
<PropertyGroup Label="Common referenced package versions">
19+
<CastleCoreVersion>4.3.1</CastleCoreVersion>
20+
<ILMergeVersion>2.14.1208</ILMergeVersion>
21+
</PropertyGroup>
22+
1823
<ItemGroup>
1924
<Compile Include="../*.cs" />
2025
</ItemGroup>
@@ -35,11 +40,11 @@
3540
<!-- .NET Standard 1.6 -->
3641

3742
<PropertyGroup Condition="'$(TargetFramework)' == 'netstandard1.6'">
38-
<DefineConstants>$(DefineConstants);FEATURE_NETCORE_REFLECTION;FEATURE_EXCEPTION_DISPATCH_INFO;FEATURE_ARRAY_EMPTY</DefineConstants>
43+
<DefineConstants>$(DefineConstants);FEATURE_NETCORE_REFLECTION;FEATURE_EXCEPTION_DISPATCH_INFO;FEATURE_ARRAY_EMPTY;FEATURE_PARAMETERINFO_CUSTOMATTRIBUTES_PROPERTY</DefineConstants>
3944
</PropertyGroup>
4045

4146
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard1.6'">
42-
<PackageReference Include="Castle.Core" Version="4.3.1" />
47+
<PackageReference Include="Castle.Core" Version="$(CastleCoreVersion)" />
4348
<PackageReference Include="Microsoft.Extensions.DependencyModel" Version="1.0.0" />
4449
<PackageReference Include="System.Collections.Concurrent" Version="4.3.0" />
4550
<PackageReference Include="System.Runtime.Loader" Version="4.0.0" />
@@ -48,16 +53,16 @@
4853
<!-- .NET 4.5 -->
4954

5055
<PropertyGroup Condition="'$(TargetFramework)' == 'net45'">
51-
<DefineConstants>$(DefineConstants);FEATURE_BINARY_SERIALIZATION;FEATURE_REFLECTION_GETASSEMBLIES</DefineConstants>
56+
<DefineConstants>$(DefineConstants);FEATURE_BINARY_SERIALIZATION;FEATURE_REFLECTION_GETASSEMBLIES;FEATURE_PARAMETERINFO_CUSTOMATTRIBUTES_PROPERTY</DefineConstants>
5257
</PropertyGroup>
5358

5459
<ItemGroup Condition="'$(TargetFramework)' == 'net45'">
55-
<PackageReference Include="ILMerge" Version="2.14.1208" PrivateAssets="all" />
56-
<PackageReference Include="Castle.Core" Version="4.2.1" PrivateAssets="all" />
60+
<PackageReference Include="ILMerge" Version="$(ILMergeVersion)" PrivateAssets="all" />
61+
<PackageReference Include="Castle.Core" Version="$(CastleCoreVersion)" PrivateAssets="all" />
5762
</ItemGroup>
5863

5964
<Target Name="ILMerge45" AfterTargets="Build" Condition="'$(TargetFramework)' == 'net45'">
60-
<Exec Command="&quot;$(NuGetPackageRoot)\ilmerge\2.14.1208\tools\ILmerge.exe&quot; /keyfile:..\FakeItEasy.snk /lib:$(OutputPath) /targetplatform:&quot;v4,$(MSBuildProgramFiles32)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.5&quot; /internalize:&quot;..\ILMerge.Internalize.Exclude.txt&quot; /out:@(MainAssembly) /log:$(OutputPath)ILMerge.log &quot;@(IntermediateAssembly)&quot; &quot;$(OutputPath)Castle.Core.dll&quot;" />
65+
<Exec Command="&quot;$(NuGetPackageRoot)\ilmerge\$(ILMergeVersion)\tools\ILmerge.exe&quot; /keyfile:..\FakeItEasy.snk /lib:$(OutputPath) /targetplatform:&quot;v4,$(MSBuildProgramFiles32)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.5&quot; /internalize:&quot;..\ILMerge.Internalize.Exclude.txt&quot; /out:@(MainAssembly) /log:$(OutputPath)ILMerge.log &quot;@(IntermediateAssembly)&quot; &quot;$(OutputPath)Castle.Core.dll&quot;" />
6166
</Target>
6267

6368
<!-- .NET 4.0 -->
@@ -67,12 +72,12 @@
6772
</PropertyGroup>
6873

6974
<ItemGroup Condition="'$(TargetFramework)' == 'net40'">
70-
<PackageReference Include="ILMerge" Version="2.14.1208" PrivateAssets="all" />
71-
<PackageReference Include="Castle.Core" Version="4.2.1" PrivateAssets="all" />
75+
<PackageReference Include="ILMerge" Version="$(ILMergeVersion)" PrivateAssets="all" />
76+
<PackageReference Include="Castle.Core" Version="$(CastleCoreVersion)" PrivateAssets="all" />
7277
</ItemGroup>
7378

7479
<Target Name="ILMerge40" AfterTargets="Build" Condition="'$(TargetFramework)' == 'net40'">
75-
<Exec Command="&quot;$(NuGetPackageRoot)\ilmerge\2.14.1208\tools\ILmerge.exe&quot; /keyfile:..\FakeItEasy.snk /lib:$(OutputPath) /targetplatform:&quot;v4,$(MSBuildProgramFiles32)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0&quot; /internalize:&quot;..\ILMerge.Internalize.Exclude.txt&quot; /out:@(MainAssembly) /log:$(OutputPath)ILMerge.log &quot;@(IntermediateAssembly)&quot; &quot;$(OutputPath)Castle.Core.dll&quot;" />
80+
<Exec Command="&quot;$(NuGetPackageRoot)\ilmerge\$(ILMergeVersion)\tools\ILmerge.exe&quot; /keyfile:..\FakeItEasy.snk /lib:$(OutputPath) /targetplatform:&quot;v4,$(MSBuildProgramFiles32)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0&quot; /internalize:&quot;..\ILMerge.Internalize.Exclude.txt&quot; /out:@(MainAssembly) /log:$(OutputPath)ILMerge.log &quot;@(IntermediateAssembly)&quot; &quot;$(OutputPath)Castle.Core.dll&quot;" />
7681
</Target>
7782

7883

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
namespace FakeItEasy
2+
{
3+
using System.Linq;
4+
using System.Reflection;
5+
6+
/// <summary>
7+
/// Provides extension methods for <see cref="ParameterInfo"/>.
8+
/// </summary>
9+
internal static class ParameterInfoExtensions
10+
{
11+
private const string IsReadOnlyAttributeFullName = "System.Runtime.CompilerServices.IsReadOnlyAttribute";
12+
13+
public static bool IsOutOrRef(this ParameterInfo parameterInfo)
14+
{
15+
if (!parameterInfo.ParameterType.IsByRef)
16+
{
17+
return false;
18+
}
19+
20+
#if FEATURE_PARAMETERINFO_CUSTOMATTRIBUTES_PROPERTY
21+
var parameterAttributes = parameterInfo.CustomAttributes;
22+
return parameterAttributes == null ||
23+
parameterAttributes.All(customAttributeData => customAttributeData.AttributeType.FullName != IsReadOnlyAttributeFullName);
24+
#else
25+
var parameterAttributes = parameterInfo.GetCustomAttributesData();
26+
return parameterAttributes == null ||
27+
parameterAttributes.All(customAttributeData => customAttributeData.Constructor.DeclaringType?.FullName != IsReadOnlyAttributeFullName);
28+
#endif
29+
}
30+
}
31+
}

tests/FakeItEasy.Specs/InParametersSpecs.cs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ public interface IGenericInParam<T>
1919
int Foo(in int x);
2020
}
2121

22+
public delegate void DelegateWithInParam(in int i);
23+
2224
[Scenario]
2325
public void FakingInParam(IInParam fake, int argument, int result)
2426
{
@@ -35,6 +37,46 @@ public void FakingInParam(IInParam fake, int argument, int result)
3537
.x(() => result.Should().Be(42));
3638
}
3739

40+
[Scenario]
41+
public void SettingInParamInterface(IInParam fake, Exception exception)
42+
{
43+
"Given a faked interface with a method that takes an 'in' parameter"
44+
.x(() => fake = A.Fake<IInParam>());
45+
46+
"And a call to this method is configured to set a new value for the parameter"
47+
.x(() => A.CallTo(() => fake.Foo(A<int>._)).AssignsOutAndRefParameters(19));
48+
49+
"When the method is called"
50+
.x(() => exception = Record.Exception(() => fake.Foo(A.Dummy<int>())));
51+
52+
"Then it throws an exception"
53+
.x(() => exception.Should().BeAnExceptionOfType<InvalidOperationException>());
54+
55+
"And the exception message indicates that the out and ref parameter counts don't agree"
56+
.x(() => exception.Message.Should()
57+
.Be("The number of values for out and ref parameters specified does not match the number of out and ref parameters in the call."));
58+
}
59+
60+
[Scenario]
61+
public void SettingInParamDelegate(DelegateWithInParam fake, Exception exception)
62+
{
63+
"Given a faked delegate with a method that takes an 'in' parameter"
64+
.x(() => fake = A.Fake<DelegateWithInParam>());
65+
66+
"And a call to this method is configured to set a new value for the parameter"
67+
.x(() => A.CallTo(() => fake.Invoke(A<int>._)).AssignsOutAndRefParameters(19));
68+
69+
"When the method is called"
70+
.x(() => exception = Record.Exception(() => fake.Invoke(A.Dummy<int>())));
71+
72+
"Then it throws an exception"
73+
.x(() => exception.Should().BeAnExceptionOfType<InvalidOperationException>());
74+
75+
"And the exception message indicates that the out and ref parameter counts don't agree"
76+
.x(() => exception.Message.Should()
77+
.Be("The number of values for out and ref parameters specified does not match the number of out and ref parameters in the call."));
78+
}
79+
3880
// A characterization test, representing the current capabilities of the code, not the desired state.
3981
// If it start failing, update it and fix the "What can be faked" documentation page.
4082
//

tests/FakeItEasy.Tests.Approval/ApiApproval.ApproveApi40.approved.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ namespace Castle.DynamicProxy.Internal
7979
public static bool IsFinalizer(this System.Reflection.MethodInfo methodInfo) { }
8080
public static bool IsGetType(this System.Reflection.MethodInfo methodInfo) { }
8181
public static bool IsMemberwiseClone(this System.Reflection.MethodInfo methodInfo) { }
82+
public static bool IsNullableType(this System.Type type) { }
8283
public static void SetStaticField(this System.Type type, string fieldName, System.Reflection.BindingFlags additionalFlags, object value) { }
8384
public static System.Reflection.MemberInfo[] Sort(System.Reflection.MemberInfo[] members) { }
8485
}

tests/FakeItEasy.Tests.Approval/ApiApproval.ApproveApi45.approved.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ namespace Castle.DynamicProxy.Internal
7979
public static bool IsFinalizer(this System.Reflection.MethodInfo methodInfo) { }
8080
public static bool IsGetType(this System.Reflection.MethodInfo methodInfo) { }
8181
public static bool IsMemberwiseClone(this System.Reflection.MethodInfo methodInfo) { }
82+
public static bool IsNullableType(this System.Type type) { }
8283
public static void SetStaticField(this System.Type type, string fieldName, System.Reflection.BindingFlags additionalFlags, object value) { }
8384
public static System.Reflection.MemberInfo[] Sort(System.Reflection.MemberInfo[] members) { }
8485
}

0 commit comments

Comments
 (0)