Skip to content

Commit

Permalink
Avoid new constraint with structs in ILLink analyzer (#102784)
Browse files Browse the repository at this point in the history
This fixes an issue where the trim analyzer was producing
warnings inside Visual Studio, but not from a command-line build,
for code similar to the following:

```csharp
var typeName = Console.ReadLine() ?? string.Empty;

if (RuntimeFeature.IsEnabled)
{
    var type = Type.GetType(typeName); // warning IL2057: Unrecognized value passed to the parameter 'typeName'...'System.Type.GetType'...
}
else
{
    Console.WriteLine($"Cannot lookup {typeName} because the feature id disabled.");
}

internal static class RuntimeFeature
{
#pragma warning disable IL4000
    [FeatureGuard(typeof(RequiresUnreferencedCodeAttribute))]
    internal static bool IsEnabled => AppContext.TryGetSwitch("RuntimeFeature.IsEnabled", out bool enabled) ? enabled : true;
#pragma warning restore IL4000
}
```

This was due to #6536
which was fixed in .NET Core, but still exists in .NET
Framework. It means that we can't rely the parameterless struct
constructor being called for struct types used through a generic
parameter with a `new()` constraint.

This fixes the issue by avoiding use of the `new()` constraint
for generic parameters that might be structs. With the fix, the
warning is no longer reported when running in Visual Studio
locally, but there are no tests because we don't run tests on
full framework.

Includes some unrelated debug helpers that were useful while
investigating this.
  • Loading branch information
sbomer authored May 29, 2024
1 parent 2bdabf1 commit 8b1d06a
Show file tree
Hide file tree
Showing 5 changed files with 45 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,15 @@ public FeatureContext Union (FeatureContext other)
{
return new FeatureContext (ValueSet<string>.Union (EnabledFeatures, other.EnabledFeatures));
}

public override string ToString ()
{
if (EnabledFeatures.IsUnknown ())
return "All";
if (EnabledFeatures.IsEmpty ())
return "None";
return string.Join (", ", EnabledFeatures.GetKnownValues ());
}
}

public readonly struct FeatureContextLattice : ILattice<FeatureContext>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ public abstract class LocalDataFlowAnalysis<TValue, TContext, TLattice, TContext
>
where TValue : struct, IEquatable<TValue>
where TContext : struct, IEquatable<TContext>
where TLattice : ILattice<TValue>, new()
where TContextLattice : ILattice<TContext>, new()
where TLattice : ILattice<TValue>
where TContextLattice : ILattice<TContext>
where TTransfer : LocalDataFlowVisitor<TValue, TContext, TLattice, TContextLattice, TConditionValue>
where TConditionValue : struct, INegate<TConditionValue>
{
Expand All @@ -39,18 +39,25 @@ public abstract class LocalDataFlowAnalysis<TValue, TContext, TLattice, TContext
readonly IOperation OperationBlock;

static LocalStateAndContextLattice<TValue, TContext, TLattice, TContextLattice> GetLatticeAndEntryValue(
TLattice lattice,
TContextLattice contextLattice,
TContext initialContext,
out LocalStateAndContext<TValue, TContext> entryValue)
{
LocalStateAndContextLattice<TValue, TContext, TLattice, TContextLattice> lattice = new (new (new TLattice ()), new TContextLattice ());
LocalStateAndContextLattice<TValue, TContext, TLattice, TContextLattice> localStateAndContextLattice = new (new (lattice), contextLattice);
entryValue = new LocalStateAndContext<TValue, TContext> (default (LocalState<TValue>), initialContext);
return lattice;
return localStateAndContextLattice;
}

// The initial value of the local dataflow is the empty local state (no tracked assignments),
// with an initial context that must be specified by the derived class.
protected LocalDataFlowAnalysis (OperationBlockAnalysisContext context, IOperation operationBlock, TContext initialContext)
: base (GetLatticeAndEntryValue (initialContext, out var entryValue), entryValue)
protected LocalDataFlowAnalysis (
OperationBlockAnalysisContext context,
IOperation operationBlock,
TLattice lattice,
TContextLattice contextLattice,
TContext initialContext)
: base (GetLatticeAndEntryValue (lattice, contextLattice, initialContext, out var entryValue), entryValue)
{
Context = context;
OperationBlock = operationBlock;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,12 @@ public TrimDataFlowAnalysis (
OperationBlockAnalysisContext context,
DataFlowAnalyzerContext dataFlowAnalyzerContext,
IOperation operationBlock)
: base (context, operationBlock, initialContext: FeatureContext.None)
: base (
context,
operationBlock,
default (ValueSetLattice<SingleValue>),
new FeatureContextLattice (),
initialContext: FeatureContext.None)
{
TrimAnalysisPatterns = new TrimAnalysisPatternStore (lattice.LocalStateLattice.Lattice.ValueLattice, lattice.ContextLattice);
_dataFlowAnalyzerContext = dataFlowAnalyzerContext;
Expand Down Expand Up @@ -172,6 +177,18 @@ static void WriteIndented (string? s, int level)
}
}

public override void TraceEdgeInput (
IControlFlowGraph<BlockProxy, RegionProxy>.ControlFlowBranch branch,
LocalStateValue state
) {
if (trace && showStates) {
var source = branch.Source.Block.Ordinal;
var target = branch.Destination?.Block.Ordinal;
WriteIndented ($"--- Edge from [{source}] to [{target}] ---", 1);
WriteIndented (state.ToString (), 2);
}
}

public override void TraceBlockInput (
LocalStateValue normalState,
LocalStateValue? exceptionState,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,9 @@ public virtual void TraceStart (TControlFlowGraph cfg) { }
[Conditional ("DEBUG")]
public virtual void TraceVisitBlock (TBlock block) { }

[Conditional ("DEBUG")]
public virtual void TraceEdgeInput (IControlFlowGraph<TBlock, TRegion>.ControlFlowBranch branch, TValue state) { }

[Conditional ("DEBUG")]
public virtual void TraceBlockInput (TValue normalState, TValue? exceptionState, TValue? exceptionFinallyState) { }

Expand Down Expand Up @@ -280,7 +283,7 @@ public void Fixpoint (TControlFlowGraph cfg, TTransfer transfer)
TValue predecessorState = cfgState.Get (predecessor).Current;

FlowStateThroughExitedFinallys (predecessor, ref predecessorState);

TraceEdgeInput (predecessor, predecessorState);
currentState = lattice.Meet (currentState, predecessorState);
}
// State at start of a catch also includes the exceptional state from
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public interface IControlFlowGraph<TBlock, TRegion>
public readonly struct ControlFlowBranch : IEquatable<ControlFlowBranch>
{
public readonly TBlock Source;
private readonly TBlock? Destination;
public readonly TBlock? Destination;

// The finally regions exited when control flows through this edge.
// For example:
Expand Down

0 comments on commit 8b1d06a

Please sign in to comment.