Skip to content

Commit

Permalink
usings and methodCalls (#9)
Browse files Browse the repository at this point in the history
* Track using directives

Track method calls

Ignore .g.cs files

Commit missing files

Rename UsingDirective to Dependency

Fix tests

Fix tests

Fix line numbers

Fix counts in tests

Track assembly and module name for dependency

Signed-off-by: Prabhu Subramanian <prabhu@appthreat.com>

Fix tests

Signed-off-by: Prabhu Subramanian <prabhu@appthreat.com>

Fix tests

Signed-off-by: Prabhu Subramanian <prabhu@appthreat.com>

Use relative path

Signed-off-by: Prabhu Subramanian <prabhu@appthreat.com>

* Fix Tests

* Additional files for fix tests

---------

Signed-off-by: Prabhu Subramanian <prabhu@appthreat.com>
Co-authored-by: Tim Messing <141575989+timmyteo@users.noreply.github.com>
  • Loading branch information
prabhu and timmyteo authored Dec 11, 2023
1 parent ec354de commit 906ddd2
Show file tree
Hide file tree
Showing 12 changed files with 389 additions and 1,081 deletions.
10 changes: 10 additions & 0 deletions Dosai.TestData/Dosai.TestData.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

</Project>
25 changes: 25 additions & 0 deletions Dosai.TestData/FooBar.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System.Text.Json;

namespace FooBar
{
public class Foo
{
public static void Main(string[] args)
{

}
}

public class Bar
{
public void bar()
{

}

private void PrivateMethod()
{

}
}
}
28 changes: 3 additions & 25 deletions Dosai.Tests/Assembly.cs → Dosai.TestData/HelloWorld.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
// Example assembly that can be used in unit tests
using System.Globalization;
using System.Reflection;

namespace HelloWorld
{
public class Hello
Expand All @@ -24,30 +26,6 @@ public void shout()
private void PrivateMethod()
{

}
}
}

namespace FooBar
{
public class Foo
{
public void foo()
{

}
}

public class Bar
{
public void bar()
{

}

private void PrivateMethod()
{

}
}
}
6 changes: 5 additions & 1 deletion Dosai.Tests/Dosai.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,16 @@

<ItemGroup>
<None
Include="*.cs"
Include="./../Dosai.TestData/FooBar.cs"
CopyToOutputDirectory="PreserveNewest" />
<None
Include="./../Dosai.TestData/HelloWorld.cs"
CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Dosai\Dosai.csproj" />
<ProjectReference Include="..\Dosai.TestData\Dosai.TestData.csproj" />
</ItemGroup>

</Project>
1,185 changes: 149 additions & 1,036 deletions Dosai.Tests/DosaiTests.cs

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions Dosai.sln
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dosai", "Dosai\Dosai.csproj
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dosai.Tests", "Dosai.Tests\Dosai.Tests.csproj", "{8F0B1E8B-7EDC-468E-8659-C03CD85185D5}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dosai.TestData", "Dosai.TestData\Dosai.TestData.csproj", "{B998E4D6-ABAE-4A97-902A-4CDE572D4F65}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -21,6 +23,10 @@ Global
{8F0B1E8B-7EDC-468E-8659-C03CD85185D5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8F0B1E8B-7EDC-468E-8659-C03CD85185D5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8F0B1E8B-7EDC-468E-8659-C03CD85185D5}.Release|Any CPU.Build.0 = Release|Any CPU
{B998E4D6-ABAE-4A97-902A-4CDE572D4F65}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B998E4D6-ABAE-4A97-902A-4CDE572D4F65}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B998E4D6-ABAE-4A97-902A-4CDE572D4F65}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B998E4D6-ABAE-4A97-902A-4CDE572D4F65}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
14 changes: 14 additions & 0 deletions Dosai/Dependency.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
namespace Depscan;

public class Dependency
{
public string? Path { get; set; }
public string? FileName { get; set; }
public string? Name { get; set; }
public string? Assembly { get; set; }
public string? Module { get; set; }
public string? Namespace { get; set; }
public int LineNumber { get; set; }
public int ColumnNumber { get; set; }
public List<string>? NamespaceMembers { get; set; }
}
164 changes: 147 additions & 17 deletions Dosai/Dosai.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ public static class Dosai
private static readonly JsonSerializerOptions options = new()
{
WriteIndented = true,
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull
};

/// <summary>
Expand Down Expand Up @@ -136,9 +137,10 @@ potentialNamespaceParent is not NamespaceDeclarationSyntax &&
public static string GetMethods(string path)
{
var methods = GetAssemblyMethods(path);
methods.AddRange(GetCSharpSourceMethods(path));
var (sourceMethods, usings, methodCalls) = GetCSharpSourceMethods(path);
methods.AddRange(sourceMethods);

return JsonSerializer.Serialize(methods, options);
return JsonSerializer.Serialize(new MethodsSlice { Dependencies = usings, Methods = methods, MethodCalls = methodCalls }, options);
}

/// <summary>
Expand All @@ -155,6 +157,7 @@ private static List<Method> GetAssemblyMethods(string path)
foreach(var assemblyFilePath in assembliesToInspect)
{
var fileName = Path.GetFileName(assemblyFilePath);

try
{
var assembly = Assembly.LoadFrom(assemblyFilePath);
Expand All @@ -170,22 +173,24 @@ private static List<Method> GetAssemblyMethods(string path)
{
assemblyMethods.Add(new Method
{
Path = assemblyFilePath,
FileName = fileName,
Module = method.DeclaringType?.Module.ToString(),
Namespace = method.DeclaringType?.Namespace,
Class = type.Name,
ClassName = type.Name,
Attributes = method.Attributes.ToString(),
Name = method.Name,
ReturnType = method.ReturnType.Name,
Parameters = method.GetParameters().Select(p => new Parameter {
Name = p.Name,
Type = p.ParameterType.Name
Type = p.ParameterType.FullName
}).ToList()
});
}
}
}
}
catch (Exception e) when (e is System.IO.FileLoadException || e is System.IO.FileNotFoundException || e is System.BadImageFormatException)
catch (Exception e) when (e is FileLoadException || e is FileNotFoundException || e is BadImageFormatException)
{
failedAssemblies.Add(assemblyFilePath);
}
Expand All @@ -202,53 +207,173 @@ private static List<Method> GetAssemblyMethods(string path)
/// Get all source methods for the given path to C# source or directory of C# source
/// </summary>
/// <param name="path">Filesystem path to C# source file or directory containing C# source files</param>
/// <returns>List of source methods</returns>
private static List<Method> GetCSharpSourceMethods(string path)
/// <returns>Tuple with List of source methods and using directives</returns>
private static (List<Method>, List<Dependency>, List<MethodCalls>) GetCSharpSourceMethods(string path)
{
var assembliesToInspect = GetFilesToInspect(path, Constants.AssemblyExtension);
var sourcesToInspect = GetFilesToInspect(path, Constants.CSharpSourceExtension);
var sourceMethods = new List<Method>();
var allUsingDirectives = new List<Dependency>();
var allMethodCalls = new List<MethodCalls>();
#pragma warning disable IL3000
var Mscorlib = MetadataReference.CreateFromFile(Path.Combine(AppContext.BaseDirectory, typeof(object).Assembly.Location));
#pragma warning restore IL3000
var metadataReferences = new List<PortableExecutableReference>
{
Mscorlib
};

foreach (var externalAssembly in assembliesToInspect)
{
metadataReferences.Add(MetadataReference.CreateFromFile(externalAssembly));
}

foreach(var sourceFilePath in sourcesToInspect)
foreach (var sourceFilePath in sourcesToInspect)
{
var fileName = Path.GetFileName(sourceFilePath);
var fileContent = File.ReadAllText(sourceFilePath);
var tree = CSharpSyntaxTree.ParseText(fileContent);
var root = tree.GetCompilationUnitRoot();
var compilation = CSharpCompilation.Create("Source").AddSyntaxTrees(tree);
var compilation = CSharpCompilation.Create(fileName, syntaxTrees: new[] { tree }, references: metadataReferences);
var model = compilation.GetSemanticModel(tree);
var methodDeclarations = root.DescendantNodes().OfType<MethodDeclarationSyntax>();
var usingDirectives = root.Usings;
var methodCalls = root.DescendantNodes().OfType<InvocationExpressionSyntax>();

// method declarations
foreach(var methodDeclaration in methodDeclarations)
{
var modifiers = methodDeclaration.Modifiers;
var method = model.GetDeclaredSymbol(methodDeclaration);

var codeSpan = methodDeclaration.SyntaxTree.GetLineSpan(methodDeclaration.Span);
var lineNumber = codeSpan.StartLinePosition.Line + 1;
var columnNumber = codeSpan.Span.Start.Character;
var columnNumber = codeSpan.Span.Start.Character + 1;

if (method != null && method.DeclaredAccessibility == Accessibility.Public)
if (method != null && method.DeclaredAccessibility != Accessibility.Private)
{
sourceMethods.Add(new Method
{
Path = Path.GetRelativePath(path, sourceFilePath),
FileName = fileName,
Assembly = method.ContainingAssembly.ToDisplayString(),
Module = method.ContainingModule.ToDisplayString(),
Namespace = method.ContainingNamespace.ToDisplayString(),
Class = method.ContainingType.Name,
ClassName = method.ContainingType.Name,
Attributes = CultureInfo.CurrentCulture.TextInfo.ToTitleCase(string.Join(", ", modifiers)),
Name = method?.Name,
ReturnType = method?.ReturnType.Name,
LineNumber = lineNumber,
ColumnNumber = columnNumber,
Parameters = method?.Parameters.Select(p => new Parameter {
Name = p.Name,
Type = p.Type?.ToString()
Type = CultureInfo.CurrentCulture.TextInfo.ToTitleCase(p.Type?.ToString()!)
}).ToList()
});
}
}

// using declarations
foreach(var usingDirective in usingDirectives)
{
var name = usingDirective.Name?.ToFullString();
var namespaceType = usingDirective.NamespaceOrType?.ToFullString();
var location = usingDirective.GetLocation().GetLineSpan().StartLinePosition;
var lineNumber = location.Line + 1;
var columnNumber = location.Character + 1;
var namespaceMembers = new List<String>();
var Assembly = "";
var Module = "";
if (usingDirective.Name != null)
{
var nameInfo = model.GetSymbolInfo(usingDirective.Name);
INamespaceSymbol? nsSymbol = null;
if (nameInfo.Symbol is not null and INamespaceSymbol)
{
nsSymbol = (INamespaceSymbol)nameInfo.Symbol;
}
else if (nameInfo.CandidateSymbols.Length > 0)
{
nsSymbol = (INamespaceSymbol)nameInfo.CandidateSymbols.First();
}
if (nsSymbol != null)
{
var nsMembers = nsSymbol.GetNamespaceMembers();
namespaceMembers.AddRange(nsMembers.Select(m => m.Name));
Assembly = nsSymbol.ContainingAssembly?.ToDisplayString();
Module = nsSymbol.ContainingModule?.ToDisplayString();
namespaceType = nsSymbol.ContainingNamespace?.ToDisplayString();
}
}

allUsingDirectives.Add(new Dependency
{
Path = Path.GetRelativePath(path, sourceFilePath),
FileName = fileName,
Assembly = Assembly,
Module = Module,
Namespace = namespaceType,
Name = name,
LineNumber = lineNumber,
ColumnNumber = columnNumber,
NamespaceMembers = namespaceMembers
});
}

// method calls
foreach(var methodCall in methodCalls)
{
var callArguments = methodCall.ArgumentList;
var callExpression = methodCall.Expression;
var location = methodCall.GetLocation().GetLineSpan().StartLinePosition;
var lineNumber = location.Line + 1;
var columnNumber = location.Character + 1;
var fullName = callExpression.ToFullString();
var memberName = callExpression.TryGetInferredMemberName();
var callArgsTypes = callArguments.Arguments.Select(a => a.TryGetInferredMemberName() ?? a.ToFullString()).ToList();
var exprInfo = model.GetSymbolInfo(callExpression);
var calledMethod = memberName;
var isInMetadata = false;
var isInSource = false;
var Assembly = string.Empty;
var Module = string.Empty;
var Namespace = string.Empty;
var ClassName = string.Empty;

if (exprInfo.Symbol != null)
{
var methodSymbol = exprInfo.Symbol;
calledMethod = methodSymbol.ToDisplayString();
isInMetadata = methodSymbol.Locations.Any(loc => loc.IsInMetadata);
isInSource = methodSymbol.Locations.Any(loc => loc.IsInSource);
Assembly = methodSymbol.ContainingAssembly.ToDisplayString();
Module = methodSymbol.ContainingModule.ToDisplayString();
Namespace = methodSymbol.ContainingNamespace.ToDisplayString();
ClassName = methodSymbol.ContainingType.ToDisplayString();
}

var IsInternal = isInSource || !isInMetadata;

if (!IsInternal)
{
allMethodCalls.Add(new MethodCalls
{
Path = Path.GetRelativePath(path, sourceFilePath),
FileName = fileName,
Assembly = Assembly,
Module = Module,
Namespace = Namespace,
ClassName = ClassName,
CalledMethod = calledMethod,
LineNumber = lineNumber,
ColumnNumber = columnNumber,
Arguments = callArgsTypes
});
}
}

}

return sourceMethods;
return (sourceMethods, allUsingDirectives, allMethodCalls);
}

/// <summary>
Expand All @@ -264,7 +389,11 @@ private static List<string> GetFilesToInspect(string path, string fileExtension)
{
foreach (var inputFile in new DirectoryInfo(path).EnumerateFiles($"*{fileExtension}", SearchOption.AllDirectories))
{
filesToInspect.Add(inputFile.FullName);
// ignore generated cs files
if (!inputFile.FullName.EndsWith(".g.cs"))
{
filesToInspect.Add(inputFile.FullName);
}
}
}
else
Expand All @@ -281,6 +410,7 @@ private static List<string> GetFilesToInspect(string path, string fileExtension)
filesToInspect.Add(path);
}
}

return filesToInspect;
}
}
Loading

0 comments on commit 906ddd2

Please sign in to comment.