Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
<TargetFramework>$(NetCoreAppToolCurrent)</TargetFramework>
<Nullable>enable</Nullable>
<IsTestProject>true</IsTestProject>
<JsonSerializerIsReflectionEnabledByDefault>false</JsonSerializerIsReflectionEnabledByDefault>
<AssemblyName>Microsoft.Diagnostics.DataContractReader.Usage.Tests</AssemblyName>
</PropertyGroup>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,13 @@ public sealed class UsageGraphFixture
public UsageGraphFixture()
{
CdacRoot = FindCdacRoot();
Usage = UsageGraphAnalyzer.Analyze(CdacRoot.FullName);
Usage = UsageGraphAnalyzer.Analyze(new UsageGraphAnalysisOptions(
Path.Combine(
CdacRoot.FullName,
"Microsoft.Diagnostics.DataContractReader.Contracts",
"Microsoft.Diagnostics.DataContractReader.Contracts.csproj"),
"Microsoft.Diagnostics.DataContractReader.Contracts.CoreCLRContracts",
CdacRoot.FullName));
}

public DirectoryInfo CdacRoot { get; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,10 @@ private void HandleInvocation(
}

IMethodSymbol callee = inv.TargetMethod;
if (_symbols.IsGeneratedLayoutSet(callee.ContainingType))
return;
if (TryHandleContractLookup(callee, label, subst))
return;
if (TryHandleDataDescriptorDependencies(callee, label, subst))
return;
if (TryHandleStaticReference(callee, label))
Expand Down Expand Up @@ -153,6 +157,30 @@ private void HandleInvocation(
}
}

private bool TryHandleContractLookup(
IMethodSymbol method,
ContractVersion label,
Dictionary<ITypeParameterSymbol, ITypeSymbol> subst)
{
if (!_symbols.IsContractRegistry(method.ContainingType) ||
method.TypeArguments.Length != 1 ||
method.Name is not CdacSymbols.GetContractMethodName and
not CdacSymbols.TryGetContractMethodName)
{
return false;
}

ITypeSymbol contractType = GenericDispatch.Resolve(method.TypeArguments[0], subst);
if (contractType is not INamedTypeSymbol contractInterface ||
!_symbols.IsContract(contractInterface))
{
return false;
}

_collector.RecordContractUsed(label, new ContractInterface(contractInterface.Name));
return true;
}

private bool TryHandleDataDescriptorDependencies(
IMethodSymbol method,
ContractVersion label,
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -25,28 +25,35 @@ internal sealed class AnalysisPipeline
new JsonReportWriter(),
];

private readonly AnalysisOptions _options;
private readonly UsageGraphAnalysisOptions _options;

public AnalysisPipeline(AnalysisOptions options) => _options = options;
public AnalysisPipeline(UsageGraphAnalysisOptions options) => _options = options;

/// <summary>
/// Builds the usage graph for the cDAC source rooted at <paramref name="cdacRoot"/> (phases A-D).
/// Shared by the report pipeline, the <c>docs</c> command and the tests so they all analyze the
/// same way.
/// </summary>
public static UsageGraph BuildGraph(string cdacRoot)
public static UsageGraph BuildGraph(UsageGraphAnalysisOptions options)
{
if (!Directory.Exists(Path.Combine(cdacRoot, CdacSymbols.ContractsProjectDirectory)))
throw new InvalidOperationException($"Could not find the cDAC Contracts project under '{cdacRoot}'; pass --cdac-root.");
ArgumentNullException.ThrowIfNull(options);
if (!File.Exists(options.ContractsProjectPath))
{
throw new InvalidOperationException(
$"Could not find the cDAC Contracts project at '{options.ContractsProjectPath}'.");
}

return BuildGraph(CdacCompilationLoader.Load(cdacRoot), cdacRoot);
return BuildGraph(
CdacCompilationLoader.LoadProject(options.ContractsProjectPath),
options.SourceRoot,
options.ContractRegistrationTypeName);
}

private static UsageGraph BuildGraph(CSharpCompilation compilation, string cdacRoot)
private static UsageGraph BuildGraph(
CSharpCompilation compilation,
string cdacRoot,
string contractRegistrationTypeName)
{
// Phase B: discovery.
DataTypeIndex index = DataTypeDiscovery.BuildIndex(compilation);
IReadOnlyList<ContractRegistration> registrations = ContractRegistrationParser.Parse(compilation);
IReadOnlyList<ContractRegistration> registrations = ContractRegistrationParser.Parse(
compilation,
contractRegistrationTypeName);

// Sanity guard: if discovery found no Data types or no registrations, the compilation
// input has drifted (renamed anchor types, missing source) -- fail fast rather than emit
Expand All @@ -62,15 +69,15 @@ private static UsageGraph BuildGraph(CSharpCompilation compilation, string cdacR

public int Run()
{
string cdacRoot = _options.CdacRoot.FullName;

// Phases A-D.
UsageGraph graph = BuildGraph(cdacRoot);
UsageGraph graph = BuildGraph(_options);
Console.WriteLine($"Discovered {graph.DataTypeCount} Data types.");
Console.WriteLine($"Parsed {graph.Contracts.Count} contract versions.");

// Phase E: emit.
string outDir = _options.OutputDirectory.FullName;
string outDir = _options.OutputDirectory
?? throw new InvalidOperationException(
"An output directory is required to emit usage graph reports.");
Directory.CreateDirectory(outDir);
Console.WriteLine($"Wrote outputs to {outDir}");
foreach (IReportWriter writer in s_writers)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ internal static class CdacSymbols
"Microsoft.Diagnostics.DataContractReader.Contracts.CoreCLRContracts";
public const string IDataMetadataName =
"Microsoft.Diagnostics.DataContractReader.Data.IData`1";
public const string GeneratedLayoutSetMetadataName =
"Microsoft.Diagnostics.DataContractReader.Generated.LayoutSet";
public const string TargetMetadataName =
"Microsoft.Diagnostics.DataContractReader.Target";

Expand All @@ -36,6 +38,8 @@ internal static class CdacSymbols
public const string StaticReferenceAttributeMetadataName =
"Microsoft.Diagnostics.DataContractReader.StaticReferenceAttribute";
public const string ContractRegistrationMethodName = "Register";
public const string GetContractMethodName = "GetContract";
public const string TryGetContractMethodName = "TryGetContract";
public const string ReadGlobalMethodName = "ReadGlobal";
public const string TryReadGlobalMethodName = "TryReadGlobal";
public const string ReadGlobalPointerMethodName = "ReadGlobalPointer";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,22 @@

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>$(NetCoreAppToolCurrent)</TargetFramework>
<TargetFramework>$(CDacTfm)</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsShipping>false</IsShipping>
<IsPackable>false</IsPackable>
<IsPackable>true</IsPackable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<AssemblyName>CdacUsageGraph</AssemblyName>
<AssemblyName>Microsoft.Diagnostics.DataContractReader.UsageGraph</AssemblyName>
<!-- The tool lives under src/native/managed/cdac, whose Directory.Build.props marks assemblies
with [Experimental("NETCDAC0001")]; the tool has no external consumers, so suppress it. -->
<NoWarn>$(NoWarn);NETCDAC0001</NoWarn>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Build" Version="$(MicrosoftBuildVersion)" ExcludeAssets="runtime" />
<PackageReference Include="Microsoft.Build.Framework" Version="$(MicrosoftBuildFrameworkVersion)" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="$(MicrosoftCodeAnalysisVersion_LatestVS)" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="$(MicrosoftCodeAnalysisVersion_LatestVS)" />
<PackageReference Include="Microsoft.CodeAnalysis.Workspaces.MSBuild" Version="$(MicrosoftCodeAnalysisVersion_LatestVS)" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="$(MicrosoftCodeAnalysisVersion)" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="$(MicrosoftCodeAnalysisVersion)" />
<PackageReference Include="Microsoft.CodeAnalysis.Workspaces.MSBuild" Version="$(MicrosoftCodeAnalysisVersion)" />
<PackageReference Include="System.CommandLine" Version="$(SystemCommandLineVersion)" />
</ItemGroup>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,15 @@ public static RootCommand Create()
root.Options.Add(outputOption);
root.SetAction(parseResult =>
{
AnalysisOptions options = new AnalysisOptions(
parseResult.GetValue(cdacRootOption)!,
parseResult.GetValue(outputOption)!);
DirectoryInfo cdacRoot = parseResult.GetValue(cdacRootOption)!;
UsageGraphAnalysisOptions options = new(
Path.Combine(
cdacRoot.FullName,
CdacSymbols.ContractsProjectDirectory,
CdacSymbols.ContractsProjectFile),
CdacSymbols.CoreCLRContractsMetadataName,
cdacRoot.FullName,
parseResult.GetValue(outputOption)!.FullName);
return new AnalysisPipeline(options).Run();
});

Expand Down Expand Up @@ -71,7 +77,13 @@ private static Command CreateDocsCommand()
FileInfo meanings = parseResult.GetValue(meaningsOption) ?? Locator.MeaningsFile(cdacRoot);
FileInfo overrides = parseResult.GetValue(overridesOption) ?? Locator.OverridesFile(cdacRoot);

UsageGraph graph = AnalysisPipeline.BuildGraph(cdacRoot.FullName);
UsageGraph graph = UsageGraphAnalyzer.Analyze(new UsageGraphAnalysisOptions(
Path.Combine(
cdacRoot.FullName,
CdacSymbols.ContractsProjectDirectory,
CdacSymbols.ContractsProjectFile),
CdacSymbols.CoreCLRContractsMetadataName,
cdacRoot.FullName));
DocGenerator generator = new DocGenerator(
graph,
DocDescriptorMeanings.Load(meanings.FullName),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,8 @@ namespace CdacUsageGraph.Compilation;
/// </summary>
internal static class CdacCompilationLoader
{
public static CSharpCompilation Load(string cdacRoot)
public static CSharpCompilation LoadProject(string projectPath)
{
string projectPath = Path.Combine(
cdacRoot,
CdacSymbols.ContractsProjectDirectory,
CdacSymbols.ContractsProjectFile);
if (!File.Exists(projectPath))
throw new InvalidOperationException($"Could not find the cDAC Contracts project at '{projectPath}'.");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ namespace CdacUsageGraph.Discovery;
/// </summary>
internal static class ContractRegistrationParser
{
public static IReadOnlyList<ContractRegistration> Parse(CSharpCompilation compilation)
public static IReadOnlyList<ContractRegistration> Parse(
CSharpCompilation compilation,
string contractRegistrationTypeName)
{
SymbolEqualityComparer comparer = SymbolEqualityComparer.Default;
List<ContractRegistration> registrations = new List<ContractRegistration>();
Expand All @@ -26,7 +28,7 @@ public static IReadOnlyList<ContractRegistration> Parse(CSharpCompilation compil
CdacSymbols.IContractMetadataName);

INamedTypeSymbol? coreContracts = compilation.GetTypeByMetadataName(
CdacSymbols.CoreCLRContractsMetadataName);
contractRegistrationTypeName);
if (coreContracts is not null && contractRegistry is not null && iContract is not null)
{
foreach (IMethodSymbol reg in coreContracts.GetMembers().OfType<IMethodSymbol>())
Expand Down Expand Up @@ -77,6 +79,22 @@ create.Constructor is IMethodSymbol constructor &&
constructor));
}
}

foreach (IDefaultValueOperation defaultValue in inv.Descendants().OfType<IDefaultValueOperation>())
{
if (defaultValue.Type is INamedTypeSymbol impl &&
comparer.Equals(impl.ContainingAssembly, compilation.Assembly) &&
compilation.IsAssignableTo(impl, iface) &&
compilation.IsAssignableTo(impl, iContract))
{
registrations.Add(new ContractRegistration(
new ContractVersion(new ContractInterface(iface.Name), version),
iface,
impl,
impl.InstanceConstructors.Single(
candidate => candidate.Parameters.Length == 0)));
}
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ private static void ReadStringMap(

private static void ValidateDescriptorKey(string key, string context)
{
int dot = key.LastIndexOf('.', StringComparison.Ordinal);
int dot = key.LastIndexOf('.');
if (dot <= 0 || dot == key.Length - 1)
throw new JsonException($"Invalid data-descriptor key '{key}' in {context}; expected 'Type.Field'.");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ private static List<string> ReadList(JsonElement entries, string context)

private static void ValidateDescriptorKey(string key, string context)
{
int dot = key.LastIndexOf('.', StringComparison.Ordinal);
int dot = key.LastIndexOf('.');
if (dot <= 0 || dot == key.Length - 1)
throw new JsonException($"Invalid data-descriptor key '{key}' in {context}; expected 'Type.Field'.");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -403,7 +403,7 @@ private ContractVersionUsage GetContract(ContractVersion label) =>

private static (string Type, string Field) SplitDescriptorKey(string key)
{
int dot = key.LastIndexOf('.', StringComparison.Ordinal);
int dot = key.LastIndexOf('.');
return (key.Substring(0, dot), key.Substring(dot + 1));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ namespace CdacUsageGraph.Semantic;
internal sealed class CdacSymbolMatcher
{
private readonly INamedTypeSymbol? _contractRegistry;
private readonly INamedTypeSymbol? _generatedLayoutSet;
private readonly INamedTypeSymbol? _iContract;
private readonly IMethodSymbol? _readGlobalPointer;
private readonly IMethodSymbol? _tryReadGlobalPointer;
Expand All @@ -22,6 +23,8 @@ public CdacSymbolMatcher(CSharpCompilation compilation)
{
_contractRegistry = compilation.GetTypeByMetadataName(
CdacSymbols.ContractRegistryMetadataName);
_generatedLayoutSet = compilation.GetTypeByMetadataName(
CdacSymbols.GeneratedLayoutSetMetadataName);
_iContract = compilation.GetTypeByMetadataName(
CdacSymbols.IContractMetadataName);

Expand Down Expand Up @@ -62,6 +65,9 @@ public CdacSymbolMatcher(CSharpCompilation compilation)
public bool IsContractRegistry(ITypeSymbol? type) =>
Matches(type, _contractRegistry);

public bool IsGeneratedLayoutSet(ITypeSymbol? type) =>
Matches(type, _generatedLayoutSet);

public bool IsContract(ITypeSymbol? type) =>
Matches(type, _iContract) ||
type is INamedTypeSymbol named &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,26 @@ namespace CdacUsageGraph;
/// <summary>Analyzes cDAC source and returns its contract usage graph.</summary>
public static class UsageGraphAnalyzer
{
/// <summary>Builds the cDAC contract usage graph rooted at <paramref name="cdacRoot"/>.</summary>
/// <param name="cdacRoot">The path to the cDAC source root.</param>
/// <summary>
/// Builds a contract usage graph for a cDAC contracts project and registration type.
/// </summary>
/// <param name="options">The project and semantic anchors for the analysis.</param>
/// <returns>The analyzed contract usage graph.</returns>
public static UsageGraph Analyze(string cdacRoot) => AnalysisPipeline.BuildGraph(cdacRoot);
public static UsageGraph Analyze(UsageGraphAnalysisOptions options) =>
AnalysisPipeline.BuildGraph(options);
}

/// <summary>Inputs needed to analyze a cDAC contracts project.</summary>
/// <param name="ContractsProjectPath">Full path to the contracts project to load with MSBuild.</param>
/// <param name="ContractRegistrationTypeName">
/// Metadata name of the type containing calls to <c>ContractRegistry.Register</c>.
/// </param>
/// <param name="SourceRoot">Source root recorded in the resulting usage graph.</param>
/// <param name="OutputDirectory">
/// Optional report output directory used by the command-line pipeline.
/// </param>
public sealed record UsageGraphAnalysisOptions(
string ContractsProjectPath,
string ContractRegistrationTypeName,
string SourceRoot,
string? OutputDirectory = null);
Loading
Loading