|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#nullable disable
using System;
using System.Collections.Generic;
using System.Diagnostics;
using EmitContext = Microsoft.CodeAnalysis.Emit.EmitContext;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Emit;
namespace Microsoft.Cci
{
internal abstract class ReferenceIndexerBase : MetadataVisitor
{
private readonly HashSet<IReferenceOrISignature> _alreadySeen = new();
private readonly HashSet<IReferenceOrISignature> _alreadyHasToken = new();
/// <summary>
/// Set true before a type reference is visited but only if a token needs to be created for the type reference.
/// E.g. not set before return type of a method is visited since the type reference is encoded in the signature blob of the method.
/// On the other hand, it is true before the type of an event definition is visited since Event table stores the type of the event as a coded token (TypeDef/Ref/Spec).
/// </summary>
protected bool typeReferenceNeedsToken;
internal ReferenceIndexerBase(EmitContext context)
: base(context)
{
}
public override void Visit(IAssemblyReference assemblyReference)
{
if (assemblyReference != Context.Module.GetContainingAssembly(Context))
{
RecordAssemblyReference(assemblyReference);
}
}
protected abstract void RecordAssemblyReference(IAssemblyReference assemblyReference);
public override void Visit(ICustomModifier customModifier)
{
this.typeReferenceNeedsToken = true;
this.Visit(customModifier.GetModifier(Context));
Debug.Assert(!this.typeReferenceNeedsToken);
}
public override void Visit(IEventDefinition eventDefinition)
{
this.typeReferenceNeedsToken = true;
this.Visit(eventDefinition.GetType(Context));
Debug.Assert(!this.typeReferenceNeedsToken);
}
public override void Visit(IFieldReference fieldReference)
{
if (!_alreadySeen.Add(new IReferenceOrISignature(fieldReference)))
{
return;
}
IUnitReference definingUnit = MetadataWriter.GetDefiningUnitReference(fieldReference.GetContainingType(Context), Context);
if (definingUnit != null && ReferenceEquals(definingUnit, Context.Module))
{
return;
}
this.Visit(fieldReference.RefCustomModifiers);
this.Visit((ITypeMemberReference)fieldReference);
this.Visit(fieldReference.GetType(Context));
ReserveFieldToken(fieldReference);
}
protected abstract void ReserveFieldToken(IFieldReference fieldReference);
public override void Visit(IFileReference fileReference)
{
RecordFileReference(fileReference);
}
protected abstract void RecordFileReference(IFileReference fileReference);
public override void Visit(IGenericMethodInstanceReference genericMethodInstanceReference)
{
this.Visit(genericMethodInstanceReference.GetGenericArguments(Context));
this.Visit(genericMethodInstanceReference.GetGenericMethod(Context));
}
public override void Visit(IGenericParameter genericParameter)
{
if (genericParameter.IsEncDeleted)
{
// Attributes and constraints do not contribute to a method signature and
// are not available for deleted generic parameters.
return;
}
this.Visit(genericParameter.GetAttributes(Context));
this.VisitTypeReferencesThatNeedTokens(genericParameter.GetConstraints(Context));
}
public override void Visit(IGenericTypeInstanceReference genericTypeInstanceReference)
{
// ^ ensures this.path.Count == old(this.path.Count);
INestedTypeReference nestedType = genericTypeInstanceReference.AsNestedTypeReference;
if (nestedType != null)
{
ITypeReference containingType = nestedType.GetContainingType(Context);
if (containingType.AsGenericTypeInstanceReference != null ||
containingType.AsSpecializedNestedTypeReference != null)
{
this.Visit(nestedType.GetContainingType(Context));
}
}
this.Visit(genericTypeInstanceReference.GetGenericType(Context));
this.Visit(genericTypeInstanceReference.GetGenericArguments(Context));
}
public override void Visit(IMarshallingInformation marshallingInformation)
{
// The type references in the marshalling information do not end up in tables, but are serialized as strings.
}
public override void Visit(IMethodDefinition method)
{
base.Visit(method);
ProcessMethodBody(method);
}
protected abstract void ProcessMethodBody(IMethodDefinition method);
public override void Visit(IMethodReference methodReference)
{
IGenericMethodInstanceReference genericMethodInstanceReference = methodReference.AsGenericMethodInstanceReference;
if (genericMethodInstanceReference != null)
{
this.Visit(genericMethodInstanceReference);
return;
}
if (!_alreadySeen.Add(new IReferenceOrISignature(methodReference)))
{
return;
}
// If we have a ref to a varargs method then we always generate an entry in the MethodRef table,
// even if it is a method in the current module. (Note that we are not *required* to do so if
// in fact the number of extra arguments passed is zero; in that case we are permitted to use
// an ordinary method def token. We consistently choose to emit a method ref regardless.)
IUnitReference definingUnit = MetadataWriter.GetDefiningUnitReference(methodReference.GetContainingType(Context), Context);
if (definingUnit != null && ReferenceEquals(definingUnit, Context.Module) && !methodReference.AcceptsExtraArguments)
{
return;
}
this.Visit((ITypeMemberReference)methodReference);
VisitSignature(methodReference.AsSpecializedMethodReference?.UnspecializedVersion ?? methodReference);
if (methodReference.AcceptsExtraArguments)
{
this.Visit(methodReference.ExtraParameters);
}
ReserveMethodToken(methodReference);
}
public void VisitSignature(ISignature signature)
{
this.Visit(signature.GetType(Context));
this.Visit(signature.GetParameters(Context));
this.Visit(signature.RefCustomModifiers);
this.Visit(signature.ReturnValueCustomModifiers);
}
protected abstract void ReserveMethodToken(IMethodReference methodReference);
public abstract override void Visit(CommonPEModuleBuilder module);
public override void Visit(IModuleReference moduleReference)
{
if (moduleReference != Context.Module)
{
RecordModuleReference(moduleReference);
}
}
protected abstract void RecordModuleReference(IModuleReference moduleReference);
public abstract override void Visit(IPlatformInvokeInformation platformInvokeInformation);
public override void Visit(INamespaceTypeReference namespaceTypeReference)
{
if (!this.typeReferenceNeedsToken && namespaceTypeReference.TypeCode != PrimitiveTypeCode.NotPrimitive)
{
return;
}
RecordTypeReference(namespaceTypeReference);
var unit = namespaceTypeReference.GetUnit(Context);
var assemblyReference = unit as IAssemblyReference;
if (assemblyReference != null)
{
this.Visit(assemblyReference);
}
else
{
var moduleReference = unit as IModuleReference;
if (moduleReference != null)
{
// If this is a module from a referenced multi-module assembly,
// the assembly should be used as the resolution scope.
assemblyReference = moduleReference.GetContainingAssembly(Context);
if (assemblyReference != null && assemblyReference != Context.Module.GetContainingAssembly(Context))
{
this.Visit(assemblyReference);
}
else
{
this.Visit(moduleReference);
}
}
}
}
protected abstract void RecordTypeReference(ITypeReference typeReference);
public override void Visit(INestedTypeReference nestedTypeReference)
{
if (!this.typeReferenceNeedsToken && nestedTypeReference.AsSpecializedNestedTypeReference != null)
{
return;
}
RecordTypeReference(nestedTypeReference);
}
public override void Visit(IPropertyDefinition propertyDefinition)
{
this.Visit(propertyDefinition.RefCustomModifiers);
this.Visit(propertyDefinition.ReturnValueCustomModifiers);
this.Visit(propertyDefinition.GetType(Context));
this.Visit(propertyDefinition.Parameters);
}
public override void Visit(ManagedResource resourceReference)
{
this.Visit(resourceReference.Attributes);
IFileReference file = resourceReference.ExternalFile;
if (file != null)
{
this.Visit(file);
}
}
public override void Visit(SecurityAttribute securityAttribute)
{
this.Visit(securityAttribute.Attribute);
}
public void VisitTypeDefinitionNoMembers(ITypeDefinition typeDefinition)
{
this.Visit(typeDefinition.GetAttributes(Context));
var baseType = typeDefinition.GetBaseClass(Context);
if (baseType != null)
{
this.typeReferenceNeedsToken = true;
this.Visit(baseType);
Debug.Assert(!this.typeReferenceNeedsToken);
}
this.Visit(typeDefinition.GetExplicitImplementationOverrides(Context));
if (typeDefinition.HasDeclarativeSecurity)
{
this.Visit(typeDefinition.SecurityAttributes);
}
this.VisitTypeReferencesThatNeedTokens(typeDefinition.Interfaces(Context));
if (typeDefinition.IsGeneric)
{
this.Visit(typeDefinition.GenericParameters);
}
}
public override void Visit(ITypeDefinition typeDefinition)
{
VisitTypeDefinitionNoMembers(typeDefinition);
this.Visit(typeDefinition.GetEvents(Context));
this.Visit(typeDefinition.GetFields(Context));
this.Visit(typeDefinition.GetMethods(Context));
this.VisitNestedTypes(typeDefinition.GetNestedTypes(Context));
this.Visit(typeDefinition.GetProperties(Context));
}
public void VisitTypeReferencesThatNeedTokens(IEnumerable<TypeReferenceWithAttributes> refsWithAttributes)
{
foreach (var refWithAttributes in refsWithAttributes)
{
this.Visit(refWithAttributes.Attributes);
VisitTypeReferencesThatNeedTokens(refWithAttributes.TypeRef);
}
}
private void VisitTypeReferencesThatNeedTokens(ITypeReference typeReference)
{
this.typeReferenceNeedsToken = true;
this.Visit(typeReference);
Debug.Assert(!this.typeReferenceNeedsToken);
}
public override void Visit(ITypeMemberReference typeMemberReference)
{
RecordTypeMemberReference(typeMemberReference);
//This code was in CCI, but appears wrong to me. There is no need to visit attributes of members that are
//being referenced, only those being defined. This code causes additional spurious typerefs and memberrefs to be
//emitted. If the attributes can't be resolved, it causes a NullReference.
//
//if ((typeMemberReference.AsDefinition(Context) == null))
//{
// this.Visit(typeMemberReference.GetAttributes(Context));
//}
this.typeReferenceNeedsToken = true;
this.Visit(typeMemberReference.GetContainingType(Context));
Debug.Assert(!this.typeReferenceNeedsToken);
}
protected abstract void RecordTypeMemberReference(ITypeMemberReference typeMemberReference);
// Array and pointer types might cause deep recursions; visit them iteratively
// rather than recursively.
public override void Visit(IArrayTypeReference arrayTypeReference)
{
// We don't visit the current array type; it has already been visited.
// We go straight to the element type and visit it.
ITypeReference current = arrayTypeReference.GetElementType(Context);
while (true)
{
bool mustVisitChildren = VisitTypeReference(current);
if (!mustVisitChildren)
{
return;
}
else if (current is IArrayTypeReference)
{
// The element type is itself an array type, and we must visit *its* element type.
// Iterate rather than recursing.
current = ((IArrayTypeReference)current).GetElementType(Context);
continue;
}
else
{
// The element type is not an array type and we must visit its children.
// Dispatch the type in order to visit its children.
DispatchAsReference(current);
return;
}
}
}
// Array and pointer types might cause deep recursions; visit them iteratively
// rather than recursively.
public override void Visit(IPointerTypeReference pointerTypeReference)
{
// We don't visit the current pointer type; it has already been visited.
// We go straight to the target type and visit it.
ITypeReference current = pointerTypeReference.GetTargetType(Context);
while (true)
{
bool mustVisitChildren = VisitTypeReference(current);
if (!mustVisitChildren)
{
return;
}
else if (current is IPointerTypeReference)
{
// The target type is itself a pointer type, and we must visit *its* target type.
// Iterate rather than recursing.
current = ((IPointerTypeReference)current).GetTargetType(Context);
continue;
}
else
{
// The target type is not a pointer type and we must visit its children.
// Dispatch the type in order to visit its children.
DispatchAsReference(current);
return;
}
}
}
public override void Visit(ITypeReference typeReference)
{
if (VisitTypeReference(typeReference))
{
DispatchAsReference(typeReference);
}
}
// Returns true if we need to look at the children, false otherwise.
private bool VisitTypeReference(ITypeReference typeReference)
{
if (!_alreadySeen.Add(new IReferenceOrISignature(typeReference)))
{
if (!this.typeReferenceNeedsToken)
{
return false;
}
this.typeReferenceNeedsToken = false;
if (!_alreadyHasToken.Add(new IReferenceOrISignature(typeReference)))
{
return false;
}
RecordTypeReference(typeReference);
return false;
}
INestedTypeReference/*?*/ nestedTypeReference = typeReference.AsNestedTypeReference;
if (this.typeReferenceNeedsToken || nestedTypeReference != null ||
(typeReference.TypeCode == PrimitiveTypeCode.NotPrimitive && typeReference.AsNamespaceTypeReference != null))
{
ISpecializedNestedTypeReference/*?*/ specializedNestedTypeReference = nestedTypeReference?.AsSpecializedNestedTypeReference;
if (specializedNestedTypeReference != null)
{
INestedTypeReference unspecializedNestedTypeReference = specializedNestedTypeReference.GetUnspecializedVersion(Context);
if (_alreadyHasToken.Add(new IReferenceOrISignature(unspecializedNestedTypeReference)))
{
RecordTypeReference(unspecializedNestedTypeReference);
}
}
if (this.typeReferenceNeedsToken && _alreadyHasToken.Add(new IReferenceOrISignature(typeReference)))
{
RecordTypeReference(typeReference);
}
if (nestedTypeReference != null)
{
this.typeReferenceNeedsToken = (typeReference.AsSpecializedNestedTypeReference == null);
this.Visit(nestedTypeReference.GetContainingType(Context));
}
}
//This code was in CCI, but appears wrong to me. There is no need to visit attributes of types that are
//being referenced, only those being defined. This code causes additional spurious typerefs and memberrefs to be
//emitted. If the attributes can't be resolved, it causes a NullReference.
//
//if ((typeReference.AsTypeDefinition(Context) == null))
//{
// this.Visit(typeReference.GetAttributes(Context));
//}
this.typeReferenceNeedsToken = false;
return true;
}
}
}
|