diff --git a/docs/design/method-query-source.md b/docs/design/method-query-source.md index 1f200c46d4..eadda00a56 100644 --- a/docs/design/method-query-source.md +++ b/docs/design/method-query-source.md @@ -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 @@ -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 | @@ -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: @@ -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. diff --git a/src/ILInspector.Analysis/MemberIdentity.cs b/src/ILInspector.Analysis/MemberIdentity.cs index 2bd1aefc75..226ff03526 100644 --- a/src/ILInspector.Analysis/MemberIdentity.cs +++ b/src/ILInspector.Analysis/MemberIdentity.cs @@ -163,6 +163,156 @@ public override int GetHashCode() } } +/// +/// Detached structural identity for one MethodDef. Caller-unsafe policy is a +/// separately owned fact and is supplied only when constructing the current +/// . +/// +public sealed record MethodSemanticIdentity( + string AssemblyName, + Guid ModuleVersionId, + TypeRef DeclaringType, + string Name, + ImmutableArray ParameterTypes, + TypeRef ReturnType, + int MetadataToken, + bool IsStatic, + bool IsExtension, + int GenericArity, + ImmutableArray GenericParameterNames, + byte SignatureHeader, + int RequiredParameterCount, + bool HasInvalidGenericParameterDeclaration, + bool IsVirtualDispatchOpen) +{ + ImmutableArray _parameterTypes = + ImmutableArrayValueEquality.RequireInitialized( + ParameterTypes, + nameof(ParameterTypes)); + ImmutableArray _genericParameterNames = + ImmutableArrayValueEquality.EmptyIfDefault( + GenericParameterNames, + nameof(GenericParameterNames)); + + public ImmutableArray ParameterTypes + { + get => _parameterTypes; + init => _parameterTypes = + ImmutableArrayValueEquality.RequireInitialized( + value, + nameof(ParameterTypes)); + } + + public ImmutableArray GenericParameterNames + { + get => _genericParameterNames; + init => _genericParameterNames = + ImmutableArrayValueEquality.EmptyIfDefault( + value, + nameof(GenericParameterNames)); + } + + /// Composes this structural identity with its separately owned caller-safety fact. + 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(); + } +} + +/// Why semantic MethodDef identity could not be decoded. +public enum MethodSemanticIdentityUnavailableKind +{ + /// A guarded metadata shape is outside the supported decoding contract. + Unsupported, + + /// The admitted row references malformed metadata. + Malformed, +} + +/// Typed semantic-identity result for one admitted MethodDef. +public abstract record MethodSemanticIdentityResult +{ + private MethodSemanticIdentityResult() + { + } + + /// The semantic identity was decoded. + public sealed record Available(MethodSemanticIdentity Identity) + : MethodSemanticIdentityResult; + + /// The row was admitted but its semantic identity was unavailable. + public sealed record Unavailable( + MethodSemanticIdentityUnavailableKind Kind) + : MethodSemanticIdentityResult; +} + public sealed record MemberRef( TypeRef DeclaringType, string Name, diff --git a/src/ILInspector.Analysis/Planning/MethodDefinitionExecution.cs b/src/ILInspector.Analysis/Planning/MethodDefinitionExecution.cs index 96be4e11d9..2be7815262 100644 --- a/src/ILInspector.Analysis/Planning/MethodDefinitionExecution.cs +++ b/src/ILInspector.Analysis/Planning/MethodDefinitionExecution.cs @@ -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; @@ -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, @@ -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) @@ -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 { @@ -1711,7 +1741,12 @@ void CompleteWithoutUnits() void PublishSourceCoverage() { - SourceCoverage = _sourceCoverage.Build(); + SourceCoverage = _sourceCoverage.Build() with + { + SemanticIdentityWork = + _gate?.SemanticIdentityWorkReceipt + ?? MethodSemanticIdentityWorkReceipt.Empty, + }; } /// How many times the execution's gate looked up a classifier's cache. @@ -1724,7 +1759,7 @@ WorkReceipt CreateReceipt(int unitsVisited, MethodRowGate? gate) _states.Length); foreach (ProducerState state in _states) { - var layers = ImmutableArray.CreateBuilder(2); + var layers = ImmutableArray.CreateBuilder(3); if (state.HasBodyLayer) { layers.Add(new ProducerLayerParticipation( @@ -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( @@ -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, }; } @@ -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; @@ -1955,10 +2003,15 @@ public virtual bool CompleteInstructionShapes( /// Units in which the module lookup was used. public int LookupUses; + /// Units in which semantic MethodDef identity was requested. + public int SemanticIdentityAcquisitions; + public int LastBodyUnit; public int LastLookupUnit; + public int LastSemanticIdentityUnit; + /// Units in which memory-safety contract support was queried. public int MemorySafetyUses; diff --git a/src/ILInspector.Analysis/Planning/MethodDefinitionProducers.cs b/src/ILInspector.Analysis/Planning/MethodDefinitionProducers.cs index 9a1a932c55..a2420334b6 100644 --- a/src/ILInspector.Analysis/Planning/MethodDefinitionProducers.cs +++ b/src/ILInspector.Analysis/Planning/MethodDefinitionProducers.cs @@ -76,6 +76,13 @@ public enum MethodDefinitionLayers /// metadata work. It does not imply . /// MemorySafetyContracts = 1024, + + /// + /// Tier 2: bounded detached structural identity for one admitted MethodDef. + /// This is independent from and excludes the + /// separately owned caller-unsafe contract. + /// + SemanticIdentity = 2048, } /// @@ -521,6 +528,21 @@ public MethodRowIdentity Identity } } + /// Tier 2: detached structural identity for the admitted MethodDef. + public MethodSemanticIdentityResult SemanticIdentity + { + get + { + Require(MethodDefinitionLayers.SemanticIdentity); + MethodDefinitionExecution.ProducerState producer = Producer; + producer.CountUnit( + ref producer.LastSemanticIdentityUnit, + Token, + ref producer.SemanticIdentityAcquisitions); + return _unit.Gate.SemanticIdentity(); + } + } + /// Tier 2: the row's P/Invoke import module, or null. public InertString? PInvokeModuleName { diff --git a/src/ILInspector.Analysis/Planning/MethodQuerySource.cs b/src/ILInspector.Analysis/Planning/MethodQuerySource.cs index 6aa5e37b3d..82c4f36a64 100644 --- a/src/ILInspector.Analysis/Planning/MethodQuerySource.cs +++ b/src/ILInspector.Analysis/Planning/MethodQuerySource.cs @@ -907,6 +907,9 @@ public MethodDefinitionTerminalWorkCoverage TerminalWork public MethodDefinitionInstructionWorkCoverage InstructionWork { get; init; } = MethodDefinitionInstructionWorkCoverage.Empty; + public MethodSemanticIdentityWorkReceipt SemanticIdentityWork + { get; init; } = MethodSemanticIdentityWorkReceipt.Empty; + public MemorySafetyContractCoverage MemorySafetyContracts { get; init; } = MemorySafetyContractCoverage.Empty; } diff --git a/src/ILInspector.Analysis/Planning/MethodRowGate.cs b/src/ILInspector.Analysis/Planning/MethodRowGate.cs index e9343e53ed..6f9dee5f16 100644 --- a/src/ILInspector.Analysis/Planning/MethodRowGate.cs +++ b/src/ILInspector.Analysis/Planning/MethodRowGate.cs @@ -194,9 +194,9 @@ internal sealed class ProducerAbortException(CriticalFailure failure) /// The method-row gate: part of the method-definition source. It is the only /// path by which a producer reads a row's fields. Tier 1 accessors charge no /// budget and memoize every structural walk per handle and per blob for the -/// execution, with a per-row cap that aborts. Tier 2 identity text goes -/// through one budget per execution, armed only when the plan declares -/// . +/// execution, with a per-row cap that aborts. Tier 2 identity text and +/// semantic identity use independent execution-scoped budgets, armed only +/// when the plan declares their respective layers. /// internal sealed class MethodRowGate { @@ -205,6 +205,7 @@ internal sealed class MethodRowGate internal const string TypeSpecificationGuard = "TypeSpecificationGuard"; internal const string IdentityWork = "IdentityWork"; internal const string IdentityDecodeFailures = "IdentityDecodeFailures"; + internal const string SemanticIdentityWork = "SemanticIdentityWork"; internal const string AttributeTypeChain = "AttributeTypeChain"; readonly SignatureShapeWalker _signatures; @@ -212,6 +213,7 @@ internal sealed class MethodRowGate readonly Dictionary<(EntityHandle Constructor, MetadataTypeNameTarget Target), bool> _attributeConstructors = []; readonly Dictionary<(EntityHandle Type, MetadataTypeNameTarget Target), bool> _attributeTypes = []; readonly Dictionary<(BlobHandle Signature, MetadataTypeNameTarget Target), bool> _attributeTypeSpecs = []; + readonly Dictionary _extensionTypes = []; // The identity budget: legacy's per-scan budgets, per execution. int _identityWorkRemaining = MetadataSafetyPolicy.MaxClassificationScanWorkChars; @@ -231,11 +233,42 @@ internal sealed class MethodRowGate BudgetedStringDecoder? _identityDecoder; bool _identityExhausted; - internal MethodRowGate(PEReader peReader, MetadataReader reader, bool identityBudgetArmed) + readonly string _sourceName; + readonly TypeRefDecoder? _semanticTypeDecoder; + bool _semanticIdentityRead; + MethodSemanticIdentityResult? _semanticIdentity; + string? _semanticAssemblyName; + Guid _semanticModuleVersionId; + bool _semanticModuleIdentityRead; + int _semanticIdentitiesDecoded; + int _semanticUnsupportedResults; + int _semanticMalformedResults; + long _semanticSignatureBytes; + long _semanticTypeWork; + long _semanticGenericParameterRows; + long _semanticStringBytes; + long _semanticAssociationRows; + MethodSemanticIdentityWorkLimitKind? _semanticReachedLimit; + + internal MethodRowGate( + string sourceName, + PEReader peReader, + MetadataReader reader, + bool identityBudgetArmed, + bool semanticIdentityBudgetArmed) { + ArgumentException.ThrowIfNullOrWhiteSpace(sourceName); + _sourceName = sourceName; _peReader = peReader; Reader = reader; IdentityBudgetArmed = identityBudgetArmed; + SemanticIdentityBudgetArmed = semanticIdentityBudgetArmed; + _semanticTypeDecoder = semanticIdentityBudgetArmed + ? new TypeRefDecoder( + amount => ChargeSemanticIdentityWork( + MethodSemanticIdentityWorkLimitKind.TypeWork, + amount)) + : null; _signatures = new SignatureShapeWalker(this); } @@ -243,11 +276,26 @@ internal MethodRowGate(PEReader peReader, MetadataReader reader, bool identityBu public bool IdentityBudgetArmed { get; } + public bool SemanticIdentityBudgetArmed { get; } + public long IdentityWorkCharged => MetadataSafetyPolicy.MaxClassificationScanWorkChars - (long)_identityWorkRemaining; public long SignatureShapeNodesWalked => _signatures.TotalNodes; + public MethodSemanticIdentityWorkReceipt SemanticIdentityWorkReceipt => + new( + SemanticIdentityBudgetArmed, + _semanticIdentitiesDecoded, + _semanticUnsupportedResults, + _semanticMalformedResults, + _semanticSignatureBytes, + _semanticTypeWork, + _semanticGenericParameterRows, + _semanticStringBytes, + _semanticAssociationRows, + _semanticReachedLimit); + internal void MoveTo( TypeDefinitionHandle typeHandle, TypeDefinition typeDefinition, @@ -265,6 +313,8 @@ internal void MoveTo( _rowToken = MetadataTokens.GetToken(methodHandle); _identityRead = false; _identity = null; + _semanticIdentityRead = false; + _semanticIdentity = null; } internal static ProducerContractException Undeclared( @@ -803,6 +853,322 @@ anchor is null return _identity; } + /// + /// Decodes one admitted MethodDef's detached structural identity through + /// independent semantic-identity bounds. It never acquires a body or arms + /// the identity-text budget. + /// + internal MethodSemanticIdentityResult SemanticIdentity() + { + if (_semanticIdentityRead) + return _semanticIdentity!; + + try + { + MethodDefinition method = _methodDefinition; + TypeDefinition type = _typeDefinition; + BlobReader signatureBlob = + Reader.GetBlobReader(method.Signature); + ChargeSemanticIdentityWork( + MethodSemanticIdentityWorkLimitKind.SignatureBytes, + signatureBlob.Length); + if (!SignatureBlobGuard.IsSafeToDecode( + Reader, + method.Signature, + SignatureBlobGuard.Kind.Method)) + { + return CacheSemanticIdentity( + new MethodSemanticIdentityResult.Unavailable( + MethodSemanticIdentityUnavailableKind.Unsupported)); + } + + GenericParameterHandleCollection typeParameters = + type.GetGenericParameters(); + GenericParameterHandleCollection methodParameters = + method.GetGenericParameters(); + ImmutableArray typeParameterNames = + ReadGenericParameterNames(typeParameters); + ImmutableArray methodParameterNames = + ReadGenericParameterNames(methodParameters); + var scope = new GenericScope( + typeParameterNames, + methodParameterNames); + TypeRefDecoder decoder = + _semanticTypeDecoder + ?? throw new ProducerContractException( + "Semantic identity was read without arming its budget."); + MethodSignature signature = + method.DecodeSignature(decoder, scope); + + ChargeSemanticIdentityWork( + MethodSemanticIdentityWorkLimitKind.AssociationRows, + 1); + TypeRef declaringType = + decoder.GetTypeFromDefinition( + Reader, + _typeHandle, + 0); + if (ContainsUnsupportedType(declaringType) + || ContainsUnsupportedType( + signature.ReturnType) + || signature.ParameterTypes.Any( + ContainsUnsupportedType)) + { + return CacheSemanticIdentity( + new MethodSemanticIdentityResult.Unavailable( + MethodSemanticIdentityUnavailableKind.Unsupported)); + } + + (string assemblyName, Guid moduleVersionId) = + SemanticModuleIdentity(); + string methodName = ReadSemanticString(method.Name); + bool invalidGenericDeclaration = + !MemberResolver.HasExactGenericParameters( + Reader, + methodParameters, + signature.GenericParameterCount, + () => ChargeSemanticIdentityWork( + MethodSemanticIdentityWorkLimitKind + .GenericParameterRows, + 1)); + bool isExtension = + IsSemanticExtensionMethod(type, method); + + var identity = new MethodSemanticIdentity( + assemblyName, + moduleVersionId, + declaringType, + methodName, + signature.ParameterTypes, + signature.ReturnType, + _rowToken, + (method.Attributes & MethodAttributes.Static) != 0, + isExtension, + methodParameters.Count, + methodParameterNames, + signature.Header.RawValue, + signature.RequiredParameterCount, + invalidGenericDeclaration, + LibraryBodyPrimaryMetadataResolver + .DispatchCanTargetOverride(type, method)); + _semanticIdentitiesDecoded++; + return CacheSemanticIdentity( + new MethodSemanticIdentityResult.Available(identity)); + } + catch (ProducerAbortException) + { + throw; + } + catch (Exception ex) + when (LibraryMethodAnalysisRunner + .IsRecoverableMethodFailure(ex)) + { + _semanticMalformedResults++; + return CacheSemanticIdentity( + new MethodSemanticIdentityResult.Unavailable( + MethodSemanticIdentityUnavailableKind.Malformed)); + } + } + + MethodSemanticIdentityResult CacheSemanticIdentity( + MethodSemanticIdentityResult result) + { + if (result is MethodSemanticIdentityResult.Unavailable + { + Kind: MethodSemanticIdentityUnavailableKind.Unsupported, + }) + { + _semanticUnsupportedResults++; + } + + _semanticIdentity = result; + _semanticIdentityRead = true; + return result; + } + + ImmutableArray ReadGenericParameterNames( + GenericParameterHandleCollection handles) + { + if (handles.Count == 0) + return []; + + var names = + ImmutableArray.CreateBuilder(handles.Count); + foreach (GenericParameterHandle handle in handles) + { + ChargeSemanticIdentityWork( + MethodSemanticIdentityWorkLimitKind.GenericParameterRows, + 1); + GenericParameter parameter = + Reader.GetGenericParameter(handle); + names.Add(ReadSemanticString(parameter.Name)); + } + + return names.MoveToImmutable(); + } + + (string AssemblyName, Guid ModuleVersionId) + SemanticModuleIdentity() + { + if (!_semanticModuleIdentityRead) + { + ChargeSemanticIdentityWork( + MethodSemanticIdentityWorkLimitKind.AssociationRows, + Reader.IsAssembly ? 2 : 1); + _semanticAssemblyName = + Reader.IsAssembly + ? ReadSemanticString( + Reader.GetAssemblyDefinition().Name) + : Path.GetFileNameWithoutExtension(_sourceName); + _semanticModuleVersionId = + Reader.GetGuid( + Reader.GetModuleDefinition().Mvid); + _semanticModuleIdentityRead = true; + } + + return ( + _semanticAssemblyName!, + _semanticModuleVersionId); + } + + bool IsSemanticExtensionMethod( + TypeDefinition type, + MethodDefinition method) + { + if ((type.Attributes & TypeAttributes.Abstract) == 0 + || (type.Attributes & TypeAttributes.Sealed) == 0 + || (method.Attributes & MethodAttributes.Static) == 0) + { + return false; + } + + if (!_extensionTypes.TryGetValue( + _typeHandle, + out bool extensionType)) + { + extensionType = + HasSemanticExtensionAttribute( + type.GetCustomAttributes()); + _extensionTypes.Add(_typeHandle, extensionType); + } + + return extensionType + && HasSemanticExtensionAttribute( + method.GetCustomAttributes()); + } + + bool HasSemanticExtensionAttribute( + CustomAttributeHandleCollection attributes) + { + foreach (CustomAttributeHandle handle in attributes) + { + ChargeSemanticIdentityWork( + MethodSemanticIdentityWorkLimitKind.AssociationRows, + 1); + CustomAttribute attribute = + Reader.GetCustomAttribute(handle); + string? name = + AttributeReader.GetAttributeTypeName( + Reader, + attribute.Constructor, + amount => ChargeSemanticIdentityWork( + MethodSemanticIdentityWorkLimitKind.StringBytes, + amount)); + AbortIfSemanticIdentityWorkExhausted(); + if (name == KnownAttributeNames.ExtensionAttribute) + return true; + } + + return false; + } + + string ReadSemanticString(StringHandle handle) + { + if (handle.IsNil) + return string.Empty; + + ChargeSemanticIdentityWork( + MethodSemanticIdentityWorkLimitKind.StringBytes, + Reader.GetBlobReader(handle).Length); + return MetadataSafetyPolicy.ReadStructuralString( + Reader, + handle); + } + + static bool ContainsUnsupportedType(TypeRef type) + { + if (type.Kind == TypeRefKind.Unsupported) + return true; + if (type.ElementType is { } element + && ContainsUnsupportedType(element)) + { + return true; + } + foreach (TypeRef argument in type.TypeArguments) + { + if (ContainsUnsupportedType(argument)) + return true; + } + + return false; + } + + void ChargeSemanticIdentityWork( + MethodSemanticIdentityWorkLimitKind kind, + int amount) + { + ArgumentOutOfRangeException.ThrowIfNegative(amount); + ref long charged = + ref SemanticIdentityCounter(kind); + charged += amount; + long limit = + kind is MethodSemanticIdentityWorkLimitKind + .GenericParameterRows + or MethodSemanticIdentityWorkLimitKind + .AssociationRows + ? MetadataSafetyPolicy + .MaxMemorySafetyProjectionIntegrityRows + : MetadataSafetyPolicy + .MaxClassificationScanWorkChars; + if (charged <= limit) + return; + + _semanticReachedLimit ??= kind; + Abort( + SemanticIdentityWork, + $"The semantic-identity {kind} bound is exhausted."); + } + + void AbortIfSemanticIdentityWorkExhausted() + { + if (_semanticReachedLimit is { } reached) + { + Abort( + SemanticIdentityWork, + $"The semantic-identity {reached} bound is exhausted."); + } + } + + ref long SemanticIdentityCounter( + MethodSemanticIdentityWorkLimitKind kind) + { + switch (kind) + { + case MethodSemanticIdentityWorkLimitKind.SignatureBytes: + return ref _semanticSignatureBytes; + case MethodSemanticIdentityWorkLimitKind.TypeWork: + return ref _semanticTypeWork; + case MethodSemanticIdentityWorkLimitKind.GenericParameterRows: + return ref _semanticGenericParameterRows; + case MethodSemanticIdentityWorkLimitKind.StringBytes: + return ref _semanticStringBytes; + case MethodSemanticIdentityWorkLimitKind.AssociationRows: + return ref _semanticAssociationRows; + default: + throw new ArgumentOutOfRangeException(nameof(kind)); + } + } + readonly Dictionary _moduleNames = []; /// diff --git a/src/ILInspector.Analysis/Planning/ProducerResults.cs b/src/ILInspector.Analysis/Planning/ProducerResults.cs index f041f696ba..bd6726b4e6 100644 --- a/src/ILInspector.Analysis/Planning/ProducerResults.cs +++ b/src/ILInspector.Analysis/Planning/ProducerResults.cs @@ -77,6 +77,33 @@ public sealed record ProducerLayerParticipation( string Layer, int Acquired); +/// Semantic MethodDef identity work performed by one source-gate execution. +public sealed record MethodSemanticIdentityWorkReceipt( + bool BudgetArmed, + int IdentitiesDecoded, + int UnsupportedResults, + int MalformedResults, + long SignatureBytes, + long TypeWork, + long GenericParameterRows, + long StringBytes, + long AssociationRows, + MethodSemanticIdentityWorkLimitKind? ReachedLimit) +{ + public static MethodSemanticIdentityWorkReceipt Empty { get; } = + new(false, 0, 0, 0, 0, 0, 0, 0, 0, null); +} + +/// A semantic-identity work dimension that reached its execution bound. +public enum MethodSemanticIdentityWorkLimitKind +{ + SignatureBytes, + TypeWork, + GenericParameterRows, + StringBytes, + AssociationRows, +} + /// /// What actually happened for one producer, recorded by the executor where /// work started and ended. @@ -106,6 +133,10 @@ public sealed record WorkReceipt( /// Signature-shape nodes the gate walked across the execution, memoized walks counted once. public long SignatureShapeNodesWalked { get; init; } + /// Bounded structural MethodDef identity work performed by the source gate. + public MethodSemanticIdentityWorkReceipt SemanticIdentityWork { get; init; } = + MethodSemanticIdentityWorkReceipt.Empty; + public ProducerParticipation For(ProducerDeclaration producer) { ArgumentNullException.ThrowIfNull(producer); diff --git a/tests/ILInspector.Analysis.Tests/GateFixtureImage.cs b/tests/ILInspector.Analysis.Tests/GateFixtureImage.cs index 07ffee8ed6..5639b19c10 100644 --- a/tests/ILInspector.Analysis.Tests/GateFixtureImage.cs +++ b/tests/ILInspector.Analysis.Tests/GateFixtureImage.cs @@ -118,6 +118,17 @@ public ImmutableArray Build(Machine machine = Machine.Unknown) _metadata.AddCustomAttribute(handle, constructor, default); foreach ((EntityHandle constructor, BlobBuilder value) in type.ValuedAttributes) _metadata.AddCustomAttribute(handle, constructor, _metadata.GetOrAddBlob(value)); + for (int genericIndex = 0; + genericIndex < type.GenericParameterNames.Count; + genericIndex++) + { + _metadata.AddGenericParameter( + handle, + GenericParameterAttributes.None, + _metadata.GetOrAddString( + type.GenericParameterNames[genericIndex]), + genericIndex); + } if (type.Enclosing is { } enclosing) _metadata.AddNestedType(handle, typeHandles[enclosing]); @@ -131,6 +142,17 @@ public ImmutableArray Build(Machine machine = Machine.Unknown) -1, MetadataTokens.ParameterHandle(nextParameter)); nextMethod++; + for (int genericIndex = 0; + genericIndex < method.GenericParameterNames.Length; + genericIndex++) + { + _metadata.AddGenericParameter( + methodHandle, + GenericParameterAttributes.None, + _metadata.GetOrAddString( + method.GenericParameterNames[genericIndex]), + genericIndex); + } for (int p = 0; p < method.ParameterNames.Length; p++) { _metadata.AddParameter( @@ -360,6 +382,13 @@ public FixtureType MethodAttribute(EntityHandle constructor, BlobBuilder value) public StringHandle? NamespaceOverride { get; init; } public List Methods { get; } = []; public List AttributeConstructors { get; } = []; + public List GenericParameterNames { get; } = []; + + public FixtureType GenericParameters(params string[] names) + { + GenericParameterNames.AddRange(names); + return this; + } /// Attaches custom attributes to the type itself. public FixtureType Attributes(params EntityHandle[] attributeConstructors) @@ -387,6 +416,16 @@ public FixtureType Method( parameterNames ?? [])); return this; } + + /// Adds generic parameters to the most recently added method. + public FixtureType MethodGenericParameters(params string[] names) + { + Methods[^1] = Methods[^1] with + { + GenericParameterNames = names, + }; + return this; + } } internal sealed record FixtureMethod( @@ -399,6 +438,7 @@ internal sealed record FixtureMethod( string[]? ParameterNames = null) { public string[] ParameterNames { get; init; } = ParameterNames ?? []; + public string[] GenericParameterNames { get; init; } = []; public List<(EntityHandle Constructor, BlobBuilder Value)> ValuedAttributes { get; } = []; } } diff --git a/tests/ILInspector.Analysis.Tests/MethodRowGateTests.cs b/tests/ILInspector.Analysis.Tests/MethodRowGateTests.cs index c3d5fba4f5..04c383ffd4 100644 --- a/tests/ILInspector.Analysis.Tests/MethodRowGateTests.cs +++ b/tests/ILInspector.Analysis.Tests/MethodRowGateTests.cs @@ -100,6 +100,324 @@ public void IdentityBudget_IdentityTextArmsAndCharges() Assert.Equal(ProducerOutcome.Complete, execution.ResultOf(rows).Outcome); } + [Fact] + public void SemanticIdentity_MatchesCurrentProjectionWithoutIdentityTextOrBody() + { + ImmutableArray image = SemanticIdentities(); + var rows = new GateProducer( + "Semantic", + MethodDefinitionLayers.SemanticIdentity); + SemanticIdentityPredicate.Seen.Clear(); + + MethodDefinitionExecution execution = + Run( + image, + Plan(new ProducerRequest(rows)), + MethodDefinitionSourceBreadth.ExactMethods( + MethodHandles(image))); + + ImmutableArray results = + [.. SemanticIdentityPredicate.Seen]; + Assert.Equal(5, results.Length); + Assert.All( + results, + result => + { + MethodSemanticIdentity semantic = + Assert.IsType< + MethodSemanticIdentityResult.Available>( + result).Identity; + MethodIdentity current = + CurrentMethodIdentity( + image, + semantic.MetadataToken); + Assert.Equal( + current, + semantic.ToMethodIdentity( + current.CallerUnsafeMode)); + Assert.Equal( + current.IsVirtualDispatchOpen, + semantic.IsVirtualDispatchOpen); + }); + + MethodSemanticIdentity generic = + AvailableNamed(results, "Generic"); + Assert.Equal(1, generic.GenericArity); + Assert.Equal(["TMethod"], generic.GenericParameterNames); + Assert.False(generic.HasInvalidGenericParameterDeclaration); + + MethodSemanticIdentity invalidGeneric = + AvailableNamed(results, "InvalidGeneric"); + Assert.True( + invalidGeneric.HasInvalidGenericParameterDeclaration); + + MethodSemanticIdentity vararg = + AvailableNamed(results, "Vararg"); + Assert.Equal(0x05, vararg.SignatureHeader & 0x0F); + Assert.Equal(1, vararg.RequiredParameterCount); + + MethodSemanticIdentity extension = + AvailableNamed(results, "Extension"); + Assert.True(extension.IsExtension); + + MethodSemanticIdentity nested = + AvailableNamed(results, "NestedVirtual"); + Assert.Equal(TypeRefKind.Definition, nested.DeclaringType.Kind); + Assert.Contains("Outer`1+Inner`1", nested.DeclaringType.Name); + Assert.True(nested.IsVirtualDispatchOpen); + + Assert.False(execution.Receipt.IdentityBudgetArmed); + Assert.Equal(0, execution.Receipt.IdentityWorkCharged); + Assert.True( + execution.Receipt.SemanticIdentityWork.BudgetArmed); + Assert.Equal( + 5, + execution.Receipt.SemanticIdentityWork + .IdentitiesDecoded); + Assert.True( + execution.Receipt.SemanticIdentityWork.SignatureBytes > 0); + Assert.True( + execution.Receipt.SemanticIdentityWork.TypeWork > 0); + Assert.True( + execution.Receipt.SemanticIdentityWork + .GenericParameterRows > 0); + Assert.True( + execution.Receipt.SemanticIdentityWork.StringBytes > 0); + Assert.True( + execution.Receipt.SemanticIdentityWork.AssociationRows > 0); + Assert.Equal( + execution.Receipt.SemanticIdentityWork, + execution.SourceCoverage.SemanticIdentityWork); + Assert.Equal(0, execution.SourceCoverage.BodiesAttempted.Count); + Assert.Equal(0, execution.SourceCoverage.BodiesAcquired.Count); + ProducerLayerParticipation semanticLayer = + Assert.Single( + execution.Receipt.For(rows).Layers, + layer => layer.Layer + == nameof( + MethodDefinitionLayers.SemanticIdentity)); + Assert.Equal(5, semanticLayer.Acquired); + } + + [Fact] + public void SemanticIdentity_CoreLibExactMethodStaysSparse() + { + ImmutableArray image = + [.. File.ReadAllBytes( + typeof(System.Text.StringBuilder) + .Assembly.Location)]; + MethodDefinitionHandle target; + using (var peReader = new PEReader(image)) + { + MetadataReader reader = + peReader.GetMetadataReader(); + target = reader.MethodDefinitions + .First( + handle => + { + MethodDefinition method = + reader.GetMethodDefinition(handle); + if (reader.GetString(method.Name) + != "AppendFormat") + { + return false; + } + + TypeDefinition type = + reader.GetTypeDefinition( + method.GetDeclaringType()); + return reader.GetString(type.Namespace) + == "System.Text" + && reader.GetString(type.Name) + == "StringBuilder"; + }); + } + + var rows = new GateProducer( + "Semantic", + MethodDefinitionLayers.SemanticIdentity); + SemanticIdentityPredicate.Seen.Clear(); + MethodDefinitionExecution execution = + Run( + image, + Plan(new ProducerRequest(rows)), + MethodDefinitionSourceBreadth + .ExactMethods(target)); + + MethodSemanticIdentity semantic = + Assert.IsType< + MethodSemanticIdentityResult.Available>( + Assert.Single( + SemanticIdentityPredicate.Seen)) + .Identity; + Assert.Equal("AppendFormat", semantic.Name); + Assert.Equal( + "System.Text", + semantic.DeclaringType.Namespace); + Assert.Equal( + "StringBuilder", + semantic.DeclaringType.Name); + Assert.Equal( + 1, + execution.SourceCoverage + .DefinitionsExamined.Count); + Assert.Equal( + 1, + execution.SourceCoverage.MethodsSelected.Count); + Assert.Equal( + 0, + execution.SourceCoverage.BodiesAttempted.Count); + Assert.False(execution.Receipt.IdentityBudgetArmed); + } + + [Fact] + public void SemanticIdentity_MalformedAndUnsupportedAreTyped() + { + var malformedBuilder = new GateFixtureImage(); + malformedBuilder.Type( + "N", + "Malformed", + namespaceOverride: + MetadataTokens.StringHandle(1)) + .Method("M"); + ( + ImmutableArray + malformedResults, + MethodSemanticIdentityWorkReceipt malformedWork) = + ReadSemanticIdentities( + malformedBuilder.Build()); + MethodSemanticIdentityResult malformed = + Assert.Single(malformedResults); + Assert.Equal( + MethodSemanticIdentityUnavailableKind.Malformed, + Assert.IsType< + MethodSemanticIdentityResult.Unavailable>( + malformed).Kind); + Assert.Equal(1, malformedWork.MalformedResults); + + var unsupportedBuilder = new GateFixtureImage(); + var signature = new BlobBuilder(); + signature.WriteByte(0x00); + int parameters = + MetadataSafetyPolicy.MaxSignatureTypeNodes + 1; + signature.WriteCompressedInteger(parameters); + signature.WriteByte(0x01); + for (int i = 0; i < parameters; i++) + signature.WriteByte(0x08); + unsupportedBuilder.Type("N", "Unsupported") + .Method("M", signature); + ( + ImmutableArray + unsupportedResults, + MethodSemanticIdentityWorkReceipt unsupportedWork) = + ReadSemanticIdentities( + unsupportedBuilder.Build()); + MethodSemanticIdentityResult unsupported = + Assert.Single(unsupportedResults); + Assert.Equal( + MethodSemanticIdentityUnavailableKind.Unsupported, + Assert.IsType< + MethodSemanticIdentityResult.Unavailable>( + unsupported).Kind); + Assert.Equal( + 1, + unsupportedWork.UnsupportedResults); + } + + [Fact] + public void SemanticIdentity_InvalidExactTargetExaminesNoMethodRow() + { + ImmutableArray image = Ordinary().Build(); + var rows = new GateProducer( + "Semantic", + MethodDefinitionLayers.SemanticIdentity); + WorkDescription work = + Plan(new ProducerRequest(rows)); + var breadth = + MethodDefinitionSourceBreadth.ExactMethods( + MetadataTokens.MethodDefinitionHandle(10_000)); + SemanticIdentityPredicate.Seen.Clear(); + + MethodDefinitionExecution execution = + Run(image, work, breadth); + + Assert.Empty(SemanticIdentityPredicate.Seen); + Assert.Equal( + 0, + execution.SourceCoverage.DefinitionsExamined.Count); + Assert.Equal( + ProducerOutcome.Failed, + execution.ResultOf(rows).Outcome); + Assert.Equal( + 0, + execution.Receipt.SemanticIdentityWork + .IdentitiesDecoded); + } + + [Fact] + public void SemanticIdentity_RejectsBroadOrExpandedBreadth() + { + ImmutableArray image = Ordinary().Build(); + var rows = new GateProducer( + "Semantic", + MethodDefinitionLayers.SemanticIdentity); + WorkDescription work = + Plan(new ProducerRequest(rows)); + + Assert.Throws( + () => Run( + image, + work, + MethodDefinitionSourceBreadth + .AllDefinitions)); + Assert.Throws( + () => Run( + image, + work, + MethodDefinitionSourceBreadth + .ExactMethods( + MethodHandles(image)) + .IncludeGeneratedExecutionBodies())); + } + + [Fact] + public void SemanticIdentity_ExhaustedBoundIsTypedAndReceipted() + { + var builder = new GateFixtureImage(); + var signature = new BlobBuilder(); + signature.WriteByte(0x00); + signature.WriteBytes( + new byte[ + MetadataSafetyPolicy + .MaxClassificationScanWorkChars]); + builder.Type("N", "OverBudget") + .Method("M", signature); + ImmutableArray image = builder.Build(); + var rows = new GateProducer( + "Semantic", + MethodDefinitionLayers.SemanticIdentity); + + MethodDefinitionExecution execution = + Run( + image, + Plan(new ProducerRequest(rows)), + MethodDefinitionSourceBreadth.ExactMethods( + MethodHandles(image))); + + ProducerResult result = + execution.ResultOf(rows); + Assert.Equal(ProducerOutcome.Aborted, result.Outcome); + Assert.Equal( + MethodRowGate.SemanticIdentityWork, + result.Critical!.Budget); + Assert.Equal( + MethodSemanticIdentityWorkLimitKind.SignatureBytes, + execution.Receipt.SemanticIdentityWork.ReachedLimit); + Assert.Equal( + execution.Receipt.SemanticIdentityWork, + execution.SourceCoverage.SemanticIdentityWork); + } + [Fact] public void IdentityBudget_HostileIdentitiesAbortRowsButNotCount() { @@ -1106,6 +1424,156 @@ static GateFixtureImage Ordinary() return builder; } + static ImmutableArray SemanticIdentities() + { + var builder = new GateFixtureImage(); + TypeReferenceHandle extensionAttribute = + builder.TypeRef( + "System.Runtime.CompilerServices", + "ExtensionAttribute"); + MemberReferenceHandle extensionConstructor = + builder.AttributeConstructor( + extensionAttribute); + GateFixtureImage.FixtureType extensions = + builder.Type("N", "Extensions") + .Attributes(extensionConstructor) + .Method( + "Generic", + GenericIdentitySignature()) + .MethodGenericParameters("TMethod") + .Method( + "InvalidGeneric", + InvalidGenericIdentitySignature()) + .MethodGenericParameters("TOnly") + .Method( + "Vararg", + VarargIdentitySignature()) + .Method( + "Extension", + GateFixtureImage.VoidSignature( + parameter => parameter.Int32()), + attributeConstructors: + extensionConstructor); + _ = extensions; + + GateFixtureImage.FixtureType outer = + builder.Type("N", "Outer`1") + .GenericParameters("TOuter"); + builder.Type("N", "Inner`1", outer) + .GenericParameters("TOuter") + .WithShape(TypeAttributes.NestedPublic) + .Method( + "NestedVirtual", + attributes: + MethodAttributes.Public + | MethodAttributes.Virtual); + return builder.Build(); + } + + static BlobBuilder GenericIdentitySignature() + { + var signature = new BlobBuilder(); + signature.WriteByte(0x10); + signature.WriteCompressedInteger(1); + signature.WriteCompressedInteger(1); + signature.WriteByte(0x1E); + signature.WriteCompressedInteger(0); + signature.WriteByte(0x1E); + signature.WriteCompressedInteger(0); + return signature; + } + + static BlobBuilder InvalidGenericIdentitySignature() + { + var signature = GenericIdentitySignature(); + byte[] bytes = signature.ToArray(); + bytes[1] = 2; + return Bytes(bytes); + } + + static BlobBuilder VarargIdentitySignature() + { + var signature = new BlobBuilder(); + signature.WriteByte(0x05); + signature.WriteCompressedInteger(2); + signature.WriteByte(0x01); + signature.WriteByte(0x08); + signature.WriteByte(0x41); + signature.WriteByte(0x0E); + return signature; + } + + static MethodSemanticIdentity AvailableNamed( + ImmutableArray results, + string name) => + Assert.Single( + results + .OfType< + MethodSemanticIdentityResult.Available>() + .Select(result => result.Identity), + identity => identity.Name == name); + + static ( + ImmutableArray Results, + MethodSemanticIdentityWorkReceipt Work) + ReadSemanticIdentities( + ImmutableArray image) + { + var rows = new GateProducer( + "Semantic", + MethodDefinitionLayers.SemanticIdentity); + SemanticIdentityPredicate.Seen.Clear(); + MethodDefinitionExecution execution = + Run( + image, + Plan(new ProducerRequest(rows)), + MethodDefinitionSourceBreadth.ExactMethods( + MethodHandles(image))); + return ( + [.. SemanticIdentityPredicate.Seen], + execution.Receipt.SemanticIdentityWork); + } + + static ImmutableArray MethodHandles( + ImmutableArray image) + { + using var peReader = new PEReader(image); + return [ + .. peReader.GetMetadataReader() + .MethodDefinitions, + ]; + } + + static MethodIdentity CurrentMethodIdentity( + ImmutableArray image, + int token) + { + using var peReader = new PEReader(image); + MetadataReader reader = + peReader.GetMetadataReader(); + var methodHandle = + (MethodDefinitionHandle) + MetadataTokens.EntityHandle(token); + MethodDefinition method = + reader.GetMethodDefinition(methodHandle); + TypeDefinitionHandle typeHandle = + method.GetDeclaringType(); + TypeDefinition type = + reader.GetTypeDefinition(typeHandle); + using var builder = + new LibraryBodyAnalysisBuilder( + "GateFixture.dll", + reader, + peReader); + var infrastructure = + (ILibraryMethodAnalysisInfrastructure)builder; + return infrastructure.CreateMethodIdentity( + typeHandle, + methodHandle, + method, + infrastructure.CreateScope(type, method)); + } + static ImmutableArray HostileIdentities(int methodCount, int parameterCount, int genericArity) { var builder = new GateFixtureImage(); @@ -1186,6 +1654,20 @@ static MethodDefinitionExecution Run(ImmutableArray image, WorkDescription return MethodDefinitionExecution.Execute(description, "GateFixture.dll", peReader); } + static MethodDefinitionExecution Run( + ImmutableArray image, + WorkDescription description, + MethodDefinitionSourceBreadth breadth) + { + using var peReader = new PEReader(image); + return MethodDefinitionExecution.Execute( + description, + "GateFixture.dll", + peReader, + breadth, + MethodDefinitionTerminalWorkLimits.Unbounded); + } + // ---- Producers ---- sealed class GateProducer( @@ -1287,6 +1769,19 @@ struct IdentityPredicate : IMethodDefinitionPredicate public readonly bool Test(scoped MethodDefinitionView view) => view.Identity.Signature.ToString().Length > 0; } + struct SemanticIdentityPredicate : IMethodDefinitionPredicate + { + public static readonly List< + MethodSemanticIdentityResult> Seen = []; + + public readonly bool Test( + scoped MethodDefinitionView view) + { + Seen.Add(view.SemanticIdentity); + return true; + } + } + struct LoggingPredicate : IMethodDefinitionPredicate { public static readonly List Tokens = [];