Skip to content

Commit 8b1d06a

Browse files
authored
Avoid new constraint with structs in ILLink analyzer (#102784)
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.
1 parent 2bdabf1 commit 8b1d06a

File tree

5 files changed

+45
-9
lines changed

5 files changed

+45
-9
lines changed

src/tools/illink/src/ILLink.RoslynAnalyzer/DataFlow/FeatureContextLattice.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,15 @@ public FeatureContext Union (FeatureContext other)
5252
{
5353
return new FeatureContext (ValueSet<string>.Union (EnabledFeatures, other.EnabledFeatures));
5454
}
55+
56+
public override string ToString ()
57+
{
58+
if (EnabledFeatures.IsUnknown ())
59+
return "All";
60+
if (EnabledFeatures.IsEmpty ())
61+
return "None";
62+
return string.Join (", ", EnabledFeatures.GetKnownValues ());
63+
}
5564
}
5665

5766
public readonly struct FeatureContextLattice : ILattice<FeatureContext>

src/tools/illink/src/ILLink.RoslynAnalyzer/DataFlow/LocalDataFlowAnalysis.cs

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ public abstract class LocalDataFlowAnalysis<TValue, TContext, TLattice, TContext
2929
>
3030
where TValue : struct, IEquatable<TValue>
3131
where TContext : struct, IEquatable<TContext>
32-
where TLattice : ILattice<TValue>, new()
33-
where TContextLattice : ILattice<TContext>, new()
32+
where TLattice : ILattice<TValue>
33+
where TContextLattice : ILattice<TContext>
3434
where TTransfer : LocalDataFlowVisitor<TValue, TContext, TLattice, TContextLattice, TConditionValue>
3535
where TConditionValue : struct, INegate<TConditionValue>
3636
{
@@ -39,18 +39,25 @@ public abstract class LocalDataFlowAnalysis<TValue, TContext, TLattice, TContext
3939
readonly IOperation OperationBlock;
4040

4141
static LocalStateAndContextLattice<TValue, TContext, TLattice, TContextLattice> GetLatticeAndEntryValue(
42+
TLattice lattice,
43+
TContextLattice contextLattice,
4244
TContext initialContext,
4345
out LocalStateAndContext<TValue, TContext> entryValue)
4446
{
45-
LocalStateAndContextLattice<TValue, TContext, TLattice, TContextLattice> lattice = new (new (new TLattice ()), new TContextLattice ());
47+
LocalStateAndContextLattice<TValue, TContext, TLattice, TContextLattice> localStateAndContextLattice = new (new (lattice), contextLattice);
4648
entryValue = new LocalStateAndContext<TValue, TContext> (default (LocalState<TValue>), initialContext);
47-
return lattice;
49+
return localStateAndContextLattice;
4850
}
4951

5052
// The initial value of the local dataflow is the empty local state (no tracked assignments),
5153
// with an initial context that must be specified by the derived class.
52-
protected LocalDataFlowAnalysis (OperationBlockAnalysisContext context, IOperation operationBlock, TContext initialContext)
53-
: base (GetLatticeAndEntryValue (initialContext, out var entryValue), entryValue)
54+
protected LocalDataFlowAnalysis (
55+
OperationBlockAnalysisContext context,
56+
IOperation operationBlock,
57+
TLattice lattice,
58+
TContextLattice contextLattice,
59+
TContext initialContext)
60+
: base (GetLatticeAndEntryValue (lattice, contextLattice, initialContext, out var entryValue), entryValue)
5461
{
5562
Context = context;
5663
OperationBlock = operationBlock;

src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimDataFlowAnalysis.cs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,12 @@ public TrimDataFlowAnalysis (
4141
OperationBlockAnalysisContext context,
4242
DataFlowAnalyzerContext dataFlowAnalyzerContext,
4343
IOperation operationBlock)
44-
: base (context, operationBlock, initialContext: FeatureContext.None)
44+
: base (
45+
context,
46+
operationBlock,
47+
default (ValueSetLattice<SingleValue>),
48+
new FeatureContextLattice (),
49+
initialContext: FeatureContext.None)
4550
{
4651
TrimAnalysisPatterns = new TrimAnalysisPatternStore (lattice.LocalStateLattice.Lattice.ValueLattice, lattice.ContextLattice);
4752
_dataFlowAnalyzerContext = dataFlowAnalyzerContext;
@@ -172,6 +177,18 @@ static void WriteIndented (string? s, int level)
172177
}
173178
}
174179

180+
public override void TraceEdgeInput (
181+
IControlFlowGraph<BlockProxy, RegionProxy>.ControlFlowBranch branch,
182+
LocalStateValue state
183+
) {
184+
if (trace && showStates) {
185+
var source = branch.Source.Block.Ordinal;
186+
var target = branch.Destination?.Block.Ordinal;
187+
WriteIndented ($"--- Edge from [{source}] to [{target}] ---", 1);
188+
WriteIndented (state.ToString (), 2);
189+
}
190+
}
191+
175192
public override void TraceBlockInput (
176193
LocalStateValue normalState,
177194
LocalStateValue? exceptionState,

src/tools/illink/src/ILLink.Shared/DataFlow/ForwardDataFlowAnalysis.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,9 @@ public virtual void TraceStart (TControlFlowGraph cfg) { }
163163
[Conditional ("DEBUG")]
164164
public virtual void TraceVisitBlock (TBlock block) { }
165165

166+
[Conditional ("DEBUG")]
167+
public virtual void TraceEdgeInput (IControlFlowGraph<TBlock, TRegion>.ControlFlowBranch branch, TValue state) { }
168+
166169
[Conditional ("DEBUG")]
167170
public virtual void TraceBlockInput (TValue normalState, TValue? exceptionState, TValue? exceptionFinallyState) { }
168171

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

282285
FlowStateThroughExitedFinallys (predecessor, ref predecessorState);
283-
286+
TraceEdgeInput (predecessor, predecessorState);
284287
currentState = lattice.Meet (currentState, predecessorState);
285288
}
286289
// State at start of a catch also includes the exceptional state from

src/tools/illink/src/ILLink.Shared/DataFlow/IControlFlowGraph.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ public interface IControlFlowGraph<TBlock, TRegion>
4545
public readonly struct ControlFlowBranch : IEquatable<ControlFlowBranch>
4646
{
4747
public readonly TBlock Source;
48-
private readonly TBlock? Destination;
48+
public readonly TBlock? Destination;
4949

5050
// The finally regions exited when control flows through this edge.
5151
// For example:

0 commit comments

Comments
 (0)