Browse Source

Add support for C# 11 parameter null checks

pull/2679/head
Siegfried Pammer 3 years ago
parent
commit
9e462b53ad
  1. 2
      ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs
  2. 7
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/YieldReturn.cs
  3. 9
      ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
  4. 4
      ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs
  5. 1
      ICSharpCode.Decompiler/CSharp/Syntax/Roles.cs
  6. 30
      ICSharpCode.Decompiler/CSharp/Syntax/TypeMembers/ParameterDeclaration.cs
  7. 24
      ICSharpCode.Decompiler/DecompilerSettings.cs
  8. 1
      ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
  9. 15
      ICSharpCode.Decompiler/IL/ILVariable.cs
  10. 88
      ICSharpCode.Decompiler/IL/Transforms/ParameterNullCheckTransform.cs

2
ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs

@ -675,7 +675,7 @@ namespace ICSharpCode.Decompiler.Tests @@ -675,7 +675,7 @@ namespace ICSharpCode.Decompiler.Tests
[Test]
public async Task YieldReturn([ValueSource(nameof(defaultOptionsWithMcs))] CompilerOptions cscOptions)
{
await RunForLibrary(cscOptions: cscOptions);
await RunForLibrary(cscOptions: cscOptions | CompilerOptions.Preview);
}
[Test]

7
ICSharpCode.Decompiler.Tests/TestCases/Pretty/YieldReturn.cs

@ -428,6 +428,13 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -428,6 +428,13 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
Console.WriteLine("normal exit");
}
#if CS110
public IEnumerable<object> YieldBangBang(object x!!)
{
yield return x;
}
#endif
internal IEnumerable<int> ForLoopWithYieldReturn(int end, int evil)
{
// This loop needs to pick the implicit "yield break;" as exit point

9
ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs

@ -91,6 +91,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -91,6 +91,7 @@ namespace ICSharpCode.Decompiler.CSharp
new InlineReturnTransform(), // must run before DetectPinnedRegions
new RemoveInfeasiblePathTransform(),
new DetectPinnedRegions(), // must run after inlining but before non-critical control flow transforms
new ParameterNullCheckTransform(), // must run after inlining but before yield/async
new YieldReturnDecompiler(), // must run after inlining but before loop detection
new AsyncAwaitDecompiler(), // must run after inlining but before loop detection
new DetectCatchWhenConditionBlocks(), // must run after inlining but before loop detection
@ -1687,6 +1688,14 @@ namespace ICSharpCode.Decompiler.CSharp @@ -1687,6 +1688,14 @@ namespace ICSharpCode.Decompiler.CSharp
RemoveAttribute(entityDecl, KnownAttribute.AsyncStateMachine);
RemoveAttribute(entityDecl, KnownAttribute.DebuggerStepThrough);
}
foreach (var parameter in entityDecl.GetChildrenByRole(Roles.Parameter))
{
var variable = parameter.Annotation<ILVariableResolveResult>()?.Variable;
if (variable != null && variable.HasNullCheck)
{
parameter.HasNullCheck = true;
}
}
}
internal static bool RemoveAttribute(EntityDeclaration entityDecl, KnownAttribute attributeType)

4
ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs

@ -2574,6 +2574,10 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -2574,6 +2574,10 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
{
WriteIdentifier(parameterDeclaration.NameToken);
}
if (parameterDeclaration.HasNullCheck)
{
WriteToken(Roles.DoubleExclamation);
}
if (!parameterDeclaration.DefaultExpression.IsNull)
{
Space(policy.SpaceAroundAssignment);

1
ICSharpCode.Decompiler/CSharp/Syntax/Roles.cs

@ -68,6 +68,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -68,6 +68,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
public static readonly TokenRole Colon = new TokenRole(":");
public static readonly TokenRole DoubleColon = new TokenRole("::");
public static readonly TokenRole Arrow = new TokenRole("=>");
public static readonly TokenRole DoubleExclamation = new TokenRole("!!");
public static readonly Role<Comment> Comment = new Role<Comment>("Comment", null);
public static readonly Role<PreProcessorDirective> PreProcessorDirective = new Role<PreProcessorDirective>("PreProcessorDirective", null);

30
ICSharpCode.Decompiler/CSharp/Syntax/TypeMembers/ParameterDeclaration.cs

@ -24,6 +24,7 @@ @@ -24,6 +24,7 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#nullable enable
namespace ICSharpCode.Decompiler.CSharp.Syntax
{
@ -46,7 +47,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -46,7 +47,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
public static readonly TokenRole InModifierRole = new TokenRole("in");
#region PatternPlaceholder
public static implicit operator ParameterDeclaration(PatternMatching.Pattern pattern)
public static implicit operator ParameterDeclaration?(PatternMatching.Pattern pattern)
{
return pattern != null ? new PatternPlaceholder(pattern) : null;
}
@ -79,7 +80,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -79,7 +80,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
return visitor.VisitPatternPlaceholder(this, child, data);
}
protected internal override bool DoMatch(AstNode other, PatternMatching.Match match)
protected internal override bool DoMatch(AstNode? other, PatternMatching.Match match)
{
return child.DoMatch(other, match);
}
@ -154,6 +155,26 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -154,6 +155,26 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
}
}
bool hasNullCheck;
public CSharpTokenNode DoubleExclamationToken {
get {
if (hasNullCheck)
{
return GetChildByRole(Roles.DoubleExclamation);
}
return CSharpTokenNode.Null;
}
}
public bool HasNullCheck {
get { return hasNullCheck; }
set {
ThrowIfFrozen();
hasNullCheck = value;
}
}
public CSharpTokenNode AssignToken {
get { return GetChildByRole(Roles.Assign); }
}
@ -178,11 +199,12 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -178,11 +199,12 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
return visitor.VisitParameterDeclaration(this, data);
}
protected internal override bool DoMatch(AstNode other, PatternMatching.Match match)
protected internal override bool DoMatch(AstNode? other, PatternMatching.Match match)
{
ParameterDeclaration o = other as ParameterDeclaration;
var o = other as ParameterDeclaration;
return o != null && this.Attributes.DoMatch(o.Attributes, match) && this.ParameterModifier == o.ParameterModifier
&& this.Type.DoMatch(o.Type, match) && MatchString(this.Name, o.Name)
&& this.HasNullCheck == o.HasNullCheck
&& this.DefaultExpression.DoMatch(o.DefaultExpression, match);
}

24
ICSharpCode.Decompiler/DecompilerSettings.cs

@ -146,10 +146,16 @@ namespace ICSharpCode.Decompiler @@ -146,10 +146,16 @@ namespace ICSharpCode.Decompiler
{
fileScopedNamespaces = false;
}
if (languageVersion < CSharp.LanguageVersion.CSharp11_0)
{
parameterNullCheck = false;
}
}
public CSharp.LanguageVersion GetMinimumRequiredVersion()
{
if (parameterNullCheck)
return CSharp.LanguageVersion.CSharp11_0;
if (fileScopedNamespaces)
return CSharp.LanguageVersion.CSharp10_0;
if (nativeIntegers || initAccessors || functionPointers || forEachWithGetEnumeratorExtension
@ -347,6 +353,24 @@ namespace ICSharpCode.Decompiler @@ -347,6 +353,24 @@ namespace ICSharpCode.Decompiler
}
}
bool parameterNullCheck = true;
/// <summary>
/// Use C# 11 parameter null-checking.
/// </summary>
[Category("C# 11.0 / VS 2022.1")]
[Description("DecompilerSettings.ParameterNullCheck")]
public bool ParameterNullCheck {
get { return parameterNullCheck; }
set {
if (parameterNullCheck != value)
{
parameterNullCheck = value;
OnPropertyChanged();
}
}
}
bool anonymousMethods = true;
/// <summary>

1
ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj

@ -117,6 +117,7 @@ @@ -117,6 +117,7 @@
<Compile Include="IL\ControlFlow\AwaitInFinallyTransform.cs" />
<Compile Include="IL\Transforms\IntroduceNativeIntTypeOnLocals.cs" />
<Compile Include="IL\Transforms\LdLocaDupInitObjTransform.cs" />
<Compile Include="IL\Transforms\ParameterNullCheckTransform.cs" />
<Compile Include="IL\Transforms\PatternMatchingTransform.cs" />
<Compile Include="IL\Transforms\RemoveInfeasiblePathTransform.cs" />
<Compile Include="Instrumentation\DecompilerEventSource.cs" />

15
ICSharpCode.Decompiler/IL/ILVariable.cs

@ -438,6 +438,21 @@ namespace ICSharpCode.Decompiler.IL @@ -438,6 +438,21 @@ namespace ICSharpCode.Decompiler.IL
/// </summary>
internal bool RemoveIfRedundant;
private bool hasNullCheck;
/// <summary>
/// Gets/sets whether a parameter has an auto-generated null check, i.e., the !! modifier.
/// Returns false for all variables except parameters.
/// </summary>
public bool HasNullCheck {
get => hasNullCheck;
set {
if (Kind != VariableKind.Parameter && value)
throw new InvalidOperationException("Cannot set HasNullCheck on local variables!");
hasNullCheck = value;
}
}
public ILVariable(VariableKind kind, IType type, int? index = null)
{
if (type == null)

88
ICSharpCode.Decompiler/IL/Transforms/ParameterNullCheckTransform.cs

@ -0,0 +1,88 @@ @@ -0,0 +1,88 @@
// Copyright (c) 2017 Siegfried Pammer
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
#nullable enable
using System.Diagnostics.CodeAnalysis;
using ICSharpCode.Decompiler.TypeSystem;
namespace ICSharpCode.Decompiler.IL.Transforms
{
/// <summary>
/// Implements transforming &lt;PrivateImplementationDetails&gt;.ThrowIfNull(name, "name");
/// </summary>
class ParameterNullCheckTransform : IILTransform
{
void IILTransform.Run(ILFunction function, ILTransformContext context)
{
if (!context.Settings.ParameterNullCheck)
return;
// we only need to look at the entry-point as parameter null-checks
// do not produce any IL control-flow instructions
Block entryPoint = ((BlockContainer)function.Body).EntryPoint;
int index = 0;
// Early versions of this pattern produced call ThrowIfNull instructions after
// state-machine initialization instead of right at the start of the method.
// In order to support both patterns, we scan all instructions,
// if the current function is decorated with a state-machine attribute.
bool scanFullBlock = function.Method != null
&& (function.Method.HasAttribute(KnownAttribute.IteratorStateMachine)
|| function.Method.HasAttribute(KnownAttribute.AsyncIteratorStateMachine)
|| function.Method.HasAttribute(KnownAttribute.AsyncStateMachine));
// loop over all instructions
while (index < entryPoint.Instructions.Count)
{
// The pattern does not match for the current instruction
if (!MatchThrowIfNullCall(entryPoint.Instructions[index], out ILVariable? parameterVariable))
{
if (scanFullBlock)
{
// continue scanning
index++;
continue;
}
else
{
// abort
break;
}
}
// remove the call to ThrowIfNull
entryPoint.Instructions.RemoveAt(index);
// remember to generate !! when producing the final output.
parameterVariable.HasNullCheck = true;
}
}
// call <PrivateImplementationDetails>.ThrowIfNull(ldloc parameterVariable, ldstr "parameterVariable")
private bool MatchThrowIfNullCall(ILInstruction instruction, [NotNullWhen(true)] out ILVariable? parameterVariable)
{
parameterVariable = null;
if (instruction is not Call call)
return false;
if (call.Arguments.Count != 2)
return false;
if (!call.Method.IsStatic || !call.Method.FullNameIs("<PrivateImplementationDetails>", "ThrowIfNull"))
return false;
if (!(call.Arguments[0].MatchLdLoc(out parameterVariable) && parameterVariable.Kind == VariableKind.Parameter))
return false;
return true;
}
}
}
Loading…
Cancel
Save