Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 54 additions & 5 deletions docs/design/method-query-source.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,11 @@ MethodDefs. The same source serves
`System.Text.Json.JsonDocument.Parse`, whose seven-body scope provides a
smaller real-package case.

The pinned repository SDK's CoreLib is also the real-asset gate for semantic
MethodDef identity: one exact `StringBuilder.AppendFormat` MethodDef must
produce the same structural identity as the current `MethodIdentity`
projection while examining one MethodDef and acquiring no body.

The current legacy body service spends most of a scoped request on
whole-assembly identity construction, generated-body discovery,
state-machine mapping, and method-map construction. One body can therefore
Expand Down Expand Up @@ -271,8 +276,9 @@ Depth is expressed as source capabilities rather than one broad `Body` bit:

| Layer | Source-owned acquisition | Does not define |
| --- | --- | --- |
| Declaration | MethodDef, declaring TypeDef, attributes, flags, name comparison, and bounded signature-shape access | Display identity or body content |
| Identity | Detached inert method identity and anchor fields | Consumer ordering or presentation |
| Declaration | MethodDef, declaring TypeDef, attributes, flags, name comparison, and bounded signature-shape access | Semantic or display identity, or body content |
| Semantic identity | Detached structural identity for an admitted MethodDef | Caller-unsafe policy, display spelling, or body content |
| Identity text | Detached inert method identity and anchor fields | Semantic identity, consumer ordering, or presentation |
| Body | Managed body header, IL bytes, exception regions, and local-signature handle | Instruction meaning |
| Instructions | One bounded instruction decode | Analysis of those instructions |
| Control flow | The owner-issued CFG over the decoded instructions | Analysis-specific graph interpretation |
Expand All @@ -281,8 +287,50 @@ Depth is expressed as source capabilities rather than one broad `Body` bit:

Each layer includes only its prerequisites. For example, `Calls` requires
`Instructions` and the declared shared lookup support, but it does not require
`Control flow` unless the request asks for both. `Identity` is projection work
and remains absent from Count or Exists plans that do not need it.
`Control flow` unless the request asks for both. Semantic identity and Identity
text are independent projection layers. Either remains absent from Count or
Exists plans that do not need it.

### Semantic MethodDef identity

`SemanticIdentity` is a distinct source-gate layer. For one admitted MethodDef
it returns either a detached `MethodSemanticIdentity` or a typed unavailable
result. The detached value contains:

- assembly name and module MVID;
- declaring `TypeRef`, method name, MethodDef token, and static and extension
facts;
- parameter and return `TypeRef` values;
- method generic arity and parameter names;
- raw signature header and vararg required-parameter count; and
- invalid-generic-declaration and virtual-dispatch-open facts.

`MethodSemanticIdentity.ToMethodIdentity` requires the separately produced
`CallerUnsafeMode`. Semantic identity neither computes nor defaults that
contract. A producer publishing the current `MethodIdentity` therefore
declares both prerequisites, and both remain visible in its plan and receipts.

This focused layer is legal only with direct exact-MethodDef breadth and no
expansion. All-definition, exact-TypeDef, predicate, and expanded plans are
rejected rather than becoming whole-assembly identity materialization.

The gate validates an exact MethodDef row number against the admitted MethodDef
table before recording or reading a row. Invalid exact coordinates fail source
admission with zero definitions examined. A valid bodiless MethodDef is an
ordinary semantic-identity unit and never causes body acquisition.

Signature bytes, type-decoder work, generic-parameter rows, string bytes, and
attribute/relationship associations have independent execution-scoped bounds.
The source receipt and `WorkReceipt` publish every counter and the first bound
reached. Bound exhaustion aborts the execution through the source gate.
Unsafe-to-decode shapes return typed unsupported identity; malformed metadata
returns typed malformed identity. Both are ordinary producer facts and are
counted in the semantic-identity receipt.

Identity text remains the existing independently declared, independently
budgeted projection to inert `MethodRowIdentity`. Semantic identity does not
arm or charge that budget and never uses display text or an anchor as a join
key.

Instruction demand has two independent source-owned facets:

Expand Down Expand Up @@ -340,7 +388,8 @@ The current `MethodDefinitionLayers` maps into this vocabulary during
migration:

- its declaration facets remain Declaration demand;
- `IdentityText` becomes Identity;
- `SemanticIdentity` becomes Semantic identity;
- `IdentityText` becomes Identity text;
- `Body` becomes Body or Instructions according to the producer's actual read;
and
- `ModuleLookup` becomes declared shared lookup support.
Expand Down
150 changes: 150 additions & 0 deletions src/ILInspector.Analysis/MemberIdentity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,156 @@ public override int GetHashCode()
}
}

/// <summary>
/// Detached structural identity for one MethodDef. Caller-unsafe policy is a
/// separately owned fact and is supplied only when constructing the current
/// <see cref="MethodIdentity"/>.
/// </summary>
public sealed record MethodSemanticIdentity(
string AssemblyName,
Guid ModuleVersionId,
TypeRef DeclaringType,
string Name,
ImmutableArray<TypeRef> ParameterTypes,
TypeRef ReturnType,
int MetadataToken,
bool IsStatic,
bool IsExtension,
int GenericArity,
ImmutableArray<string> GenericParameterNames,
byte SignatureHeader,
int RequiredParameterCount,
bool HasInvalidGenericParameterDeclaration,
bool IsVirtualDispatchOpen)
{
ImmutableArray<TypeRef> _parameterTypes =
ImmutableArrayValueEquality.RequireInitialized(
ParameterTypes,
nameof(ParameterTypes));
ImmutableArray<string> _genericParameterNames =
ImmutableArrayValueEquality.EmptyIfDefault(
GenericParameterNames,
nameof(GenericParameterNames));

public ImmutableArray<TypeRef> ParameterTypes
{
get => _parameterTypes;
init => _parameterTypes =
ImmutableArrayValueEquality.RequireInitialized(
value,
nameof(ParameterTypes));
}

public ImmutableArray<string> GenericParameterNames
{
get => _genericParameterNames;
init => _genericParameterNames =
ImmutableArrayValueEquality.EmptyIfDefault(
value,
nameof(GenericParameterNames));
}

/// <summary>Composes this structural identity with its separately owned caller-safety fact.</summary>
public MethodIdentity ToMethodIdentity(CallerUnsafeMode callerUnsafeMode) =>
new(
AssemblyName,
ModuleVersionId,
DeclaringType,
Name,
ParameterTypes,
ReturnType,
MetadataToken,
IsStatic,
IsExtension,
callerUnsafeMode,
GenericArity,
GenericParameterNames)
{
SignatureHeader = SignatureHeader,
RequiredParameterCount = RequiredParameterCount,
HasInvalidGenericParameterDeclaration =
HasInvalidGenericParameterDeclaration,
IsVirtualDispatchOpen = IsVirtualDispatchOpen,
};

public bool Equals(MethodSemanticIdentity? other) =>
other is not null
&& AssemblyName == other.AssemblyName
&& ModuleVersionId == other.ModuleVersionId
&& Equals(DeclaringType, other.DeclaringType)
&& Name == other.Name
&& ImmutableArrayValueEquality.SequenceEqual(
ParameterTypes,
other.ParameterTypes)
&& Equals(ReturnType, other.ReturnType)
&& MetadataToken == other.MetadataToken
&& IsStatic == other.IsStatic
&& IsExtension == other.IsExtension
&& GenericArity == other.GenericArity
&& ImmutableArrayValueEquality.SequenceEqual(
GenericParameterNames,
other.GenericParameterNames)
&& SignatureHeader == other.SignatureHeader
&& RequiredParameterCount
== other.RequiredParameterCount
&& HasInvalidGenericParameterDeclaration
== other.HasInvalidGenericParameterDeclaration
&& IsVirtualDispatchOpen
== other.IsVirtualDispatchOpen;

public override int GetHashCode()
{
var hash = new HashCode();
hash.Add(AssemblyName);
hash.Add(ModuleVersionId);
hash.Add(DeclaringType);
hash.Add(Name);
ImmutableArrayValueEquality.AddToHash(
ref hash,
ParameterTypes);
hash.Add(ReturnType);
hash.Add(MetadataToken);
hash.Add(IsStatic);
hash.Add(IsExtension);
hash.Add(GenericArity);
ImmutableArrayValueEquality.AddToHash(
ref hash,
GenericParameterNames);
hash.Add(SignatureHeader);
hash.Add(RequiredParameterCount);
hash.Add(HasInvalidGenericParameterDeclaration);
hash.Add(IsVirtualDispatchOpen);
return hash.ToHashCode();
}
}

/// <summary>Why semantic MethodDef identity could not be decoded.</summary>
public enum MethodSemanticIdentityUnavailableKind
{
/// <summary>A guarded metadata shape is outside the supported decoding contract.</summary>
Unsupported,

/// <summary>The admitted row references malformed metadata.</summary>
Malformed,
}

/// <summary>Typed semantic-identity result for one admitted MethodDef.</summary>
public abstract record MethodSemanticIdentityResult
{
private MethodSemanticIdentityResult()
{
}

/// <summary>The semantic identity was decoded.</summary>
public sealed record Available(MethodSemanticIdentity Identity)
: MethodSemanticIdentityResult;

/// <summary>The row was admitted but its semantic identity was unavailable.</summary>
public sealed record Unavailable(
MethodSemanticIdentityUnavailableKind Kind)
: MethodSemanticIdentityResult;
}

public sealed record MemberRef(
TypeRef DeclaringType,
string Name,
Expand Down
61 changes: 57 additions & 4 deletions src/ILInspector.Analysis/Planning/MethodDefinitionExecution.cs
Original file line number Diff line number Diff line change
Expand Up @@ -153,9 +153,12 @@ static MethodDefinitionExecution Execute(
}

execution._gate = new MethodRowGate(
sourceName,
peReader,
reader,
(FieldsRead(description) & MethodDefinitionLayers.IdentityText) != 0);
(FieldsRead(description) & MethodDefinitionLayers.IdentityText) != 0,
(FieldsRead(description)
& MethodDefinitionLayers.SemanticIdentity) != 0);

bool lookupDeclared = false;
bool referenceBindingDeclared = false;
Expand Down Expand Up @@ -332,6 +335,18 @@ static MethodDefinitionExecution CreateExecution(
bool tracksSourceCoverage,
MethodBodyAnalyzerPlan? instructionPlan)
{
if ((FieldsRead(description)
& MethodDefinitionLayers.SemanticIdentity) != 0
&& (breadth.Kind
!= MethodDefinitionSourceBreadthKind.ExactMethods
|| breadth.Expansion
!= MethodDefinitionSourceExpansion.None))
{
throw new ProducerContractException(
"Semantic MethodDef identity requires direct exact-method "
+ "breadth with no expansion.");
}

var execution = new MethodDefinitionExecution(
description,
breadth,
Expand Down Expand Up @@ -446,10 +461,13 @@ internal static MethodDefinitionSharedExecution ExecuteShared(
MethodDefinitionExecution lane = lanes[laneIndex];
WorkDescription description = descriptions[laneIndex];
lane._gate = new MethodRowGate(
sourceName,
peReader,
reader,
(FieldsRead(description)
& MethodDefinitionLayers.IdentityText) != 0);
& MethodDefinitionLayers.IdentityText) != 0,
(FieldsRead(description)
& MethodDefinitionLayers.SemanticIdentity) != 0);

bool lookupDeclared = false;
foreach (ProducerState state in lane._states)
Expand Down Expand Up @@ -1117,6 +1135,18 @@ void VisitExactMethods(
if (!AnyActive(visiting))
return;

int methodRow = MetadataTokens.GetRowNumber(methodHandle);
if (methodRow > reader.GetTableRowCount(TableIndex.MethodDef))
{
FailActiveSource(
MetadataTokens.GetToken(methodHandle),
"(method source)",
new BadImageFormatException(
"The exact MethodDef handle is outside the admitted "
+ "MethodDef table."));
continue;
}

MethodDefinition methodDefinition;
try
{
Expand Down Expand Up @@ -1711,7 +1741,12 @@ void CompleteWithoutUnits()

void PublishSourceCoverage()
{
SourceCoverage = _sourceCoverage.Build();
SourceCoverage = _sourceCoverage.Build() with
{
SemanticIdentityWork =
_gate?.SemanticIdentityWorkReceipt
?? MethodSemanticIdentityWorkReceipt.Empty,
};
}

/// <summary>How many times the execution's gate looked up a classifier's cache.</summary>
Expand All @@ -1724,7 +1759,7 @@ WorkReceipt CreateReceipt(int unitsVisited, MethodRowGate? gate)
_states.Length);
foreach (ProducerState state in _states)
{
var layers = ImmutableArray.CreateBuilder<ProducerLayerParticipation>(2);
var layers = ImmutableArray.CreateBuilder<ProducerLayerParticipation>(3);
if (state.HasBodyLayer)
{
layers.Add(new ProducerLayerParticipation(
Expand All @@ -1741,6 +1776,13 @@ WorkReceipt CreateReceipt(int unitsVisited, MethodRowGate? gate)
state.LookupUses));
}

if (state.HasSemanticIdentityLayer)
{
layers.Add(new ProducerLayerParticipation(
nameof(MethodDefinitionLayers.SemanticIdentity),
state.SemanticIdentityAcquisitions));
}

if (state.HasMemorySafetyLayer)
{
layers.Add(new ProducerLayerParticipation(
Expand All @@ -1765,6 +1807,9 @@ WorkReceipt CreateReceipt(int unitsVisited, MethodRowGate? gate)
IdentityBudgetArmed = gate?.IdentityBudgetArmed ?? false,
IdentityWorkCharged = gate?.IdentityWorkCharged ?? 0,
SignatureShapeNodesWalked = gate?.SignatureShapeNodesWalked ?? 0,
SemanticIdentityWork =
gate?.SemanticIdentityWorkReceipt
?? MethodSemanticIdentityWorkReceipt.Empty,
};
}

Expand Down Expand Up @@ -1930,6 +1975,9 @@ public virtual bool CompleteInstructionShapes(
public bool HasReferenceBindingLayer { get; } =
(layers & MethodDefinitionLayers.ReferenceBinding) != 0;

public bool HasSemanticIdentityLayer { get; } =
(layers & MethodDefinitionLayers.SemanticIdentity) != 0;

public bool HasMemorySafetyLayer { get; } =
(layers & MethodDefinitionLayers.MemorySafetyContracts) != 0;

Expand All @@ -1955,10 +2003,15 @@ public virtual bool CompleteInstructionShapes(
/// <summary>Units in which the module lookup was used.</summary>
public int LookupUses;

/// <summary>Units in which semantic MethodDef identity was requested.</summary>
public int SemanticIdentityAcquisitions;

public int LastBodyUnit;

public int LastLookupUnit;

public int LastSemanticIdentityUnit;

/// <summary>Units in which memory-safety contract support was queried.</summary>
public int MemorySafetyUses;

Expand Down
Loading