Skip to content

Commit

Permalink
fix: support to project level obfuscation in namespaces
Browse files Browse the repository at this point in the history
  • Loading branch information
mika-f committed Mar 15, 2024
1 parent 555669e commit 5020e5d
Show file tree
Hide file tree
Showing 6 changed files with 142 additions and 18 deletions.
4 changes: 2 additions & 2 deletions src/Plana.CLI/Properties/launchSettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@
"commandLineArgs": "obfuscate --workspace ./Plana.sln --output \"../dist\" --log-level verbose --plugins ./Plana.CLI/bin/Debug/net8.0/plugins --rename-symbols --retrieve-args",
"workingDirectory": "../"
},
"Plana.CLI - Obfuscate": {
"Plana.CLI - Obfuscate (Solution)": {
"commandName": "Project",
"commandLineArgs": "obfuscate --workspace ./Plana.sln --output \"../dist\" --log-level verbose --plugins ./Plana.CLI/bin/Debug/net8.0/plugins --rename-symbols --rename-namespaces --rename-classes --rename-properties --rename-fields --rename-methods --rename-variables",
"workingDirectory": "../"
},
"Plana.CLI - Obfuscate (Single Project)": {
"Plana.CLI - Obfuscate (Project)": {
"commandName": "Project",
"commandLineArgs": "obfuscate --workspace ./Plana.CLI/Plana.CLI.csproj --output \"../dist\" --log-level verbose --plugins ./Plana.CLI/bin/Debug/net8.0/plugins --rename-symbols --rename-namespaces --rename-classes --rename-properties --rename-fields --rename-methods --rename-variables",
"workingDirectory": "../"
Expand Down
47 changes: 38 additions & 9 deletions src/Plana.Composition.Extensions/ISymbolExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,14 @@

namespace Plana.Composition.Extensions;

#pragma warning disable CS8619
#pragma warning disable CS8622 // Nullability of reference types in type of parameter doesn't match the target delegate (possibly because of nullability attributes).

// ReSharper disable once InconsistentNaming
public static class ISymbolExtensions
{
#pragma warning disable CS8619
private static readonly string[] Autogenerated = ["<auto-generated", "<autogenerated"];

public static ISymbol? GetInterfaceSymbol(this ISymbol symbol)
{
if (symbol.Kind != SymbolKind.Method && symbol.Kind != SymbolKind.Property && symbol.Kind != SymbolKind.Event)
Expand All @@ -33,23 +37,48 @@ public static class ISymbolExtensions

return implementations.FirstOrDefault(w => w.Implementation?.Equals(symbol, SymbolEqualityComparer.Default) == true).Interface;
}
#pragma warning restore CS8619

#pragma warning disable CS8622 // Nullability of reference types in type of parameter doesn't match the target delegate (possibly because of nullability attributes).
public static bool IsInWorkspace(this ISymbol symbol, ISolution solution)
public static bool IsAllDeclarationIsInWorkspace(this ISymbol symbol, ISolution solution)
{
var sources = symbol.OriginalDefinition.Locations.Select(w => w.SourceTree?.FilePath).Where(w => w != null).Select(Path.GetFullPath);
var targets = solution.Projects.SelectMany(w => w.Documents).Select(w => Path.GetFullPath(w.Path));
var sources = symbol.OriginalDefinition.Locations.Where(w => !IsDocumentIsAutoGeneratedAsync(w.SourceTree))
.Select(w => w.SourceTree?.FilePath)
.Where(w => w != null)
.Select(Path.GetFullPath)
.ToList();

var targets = solution.Projects.SelectMany(w => w.Documents)
.Select(w => Path.GetFullPath(w.Path))
.ToList();

return sources.All(w => targets.Contains(w));
}
#pragma warning restore CS8622 // Nullability of reference types in type of parameter doesn't match the target delegate (possibly because of nullability attributes).

public static bool IsNotInWorkspace(this ISymbol symbol, ISolution solution)
public static bool IsAnyDeclarationIsNotInWorkspace(this ISymbol symbol, ISolution solution)
{
return !IsInWorkspace(symbol, solution);
return !IsAllDeclarationIsInWorkspace(symbol, solution);
}

private static bool IsDocumentIsAutoGeneratedAsync(SyntaxTree? tree)
{
if (tree == null)
return false;

var node = tree.GetRoot();
if (node is { HasLeadingTrivia: true })
{
var leading = node.GetLeadingTrivia();
foreach (var trivia in leading)
{
var text = trivia.ToFullString();
if (Autogenerated.Any(w => text.Contains(w)))
return true;
}
}

return false;
}


/*
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ namespace Plana.Composition.RenameSymbols.Tests;
public partial class RenameSymbolsPluginTest
{
[Fact]
public async Task RenameNamespaces()
public async Task RenameNamespacesInSolutionWorkspace()
{
var container = new PlanaContainer<RenameSymbolsPlugin>("rename-namespaces");
await container.RunAsync();
Expand All @@ -31,4 +31,24 @@ public async Task RenameNamespaces()
var usingToAbstractions = await root.GetSyntax<UsingDirectiveSyntax>();
Assert.Equal("_0xb73384b5._0x23636295._0x895054f0", usingToAbstractions.Name!.ToFullString());
}

[Fact]
public async Task RenameNamespacesInProjectWorkspace()
{
var container = new PlanaContainer<RenameSymbolsPlugin>("rename-namespaces");
await container.RunAsync("../../../../Plana.CLI/Plana.CLI.csproj");

var source = await container.GetSourceByPathAsync("Commands/ObfuscateCommand.cs");

// Plana.CLI.Commands
var @namespace = await source.GetFirstSyntax<FileScopedNamespaceDeclarationSyntax>();
Assert.Equal("_0xb73384b5._0x23636295._0x0a849839", @namespace.Name.ToFullString());

// Plana -> Plana


// Plana.Composition.Abstractions -> Plana.Composition.Abstractions

// Plana.CLI.Bindings ->
}
}
77 changes: 76 additions & 1 deletion src/Plana.Composition.RenameSymbols/CSharpSymbolsRewriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,78 @@

namespace Plana.Composition.RenameSymbols;

internal class CSharpSymbolsRewriter(IDocument document, bool keepNameOnInspector, IReadOnlyDictionary<ISymbol, string> dict) : CSharpSyntaxRewriter
internal class CSharpSymbolsRewriter(ISolution solution, IDocument document, bool keepNameOnInspector, IReadOnlyDictionary<ISymbol, string> dict) : CSharpSyntaxRewriter
{
private readonly List<INamedTypeSymbol> _symbols = [];

public override SyntaxNode? VisitCompilationUnit(CompilationUnitSyntax node)
{
var newNode = base.VisitCompilationUnit(node);
if (newNode is CompilationUnitSyntax compilation)
{
var originalUsings = node.Usings;
var currentUsings = compilation.Usings;
var usings = new List<UsingDirectiveSyntax>();

foreach (var us in originalUsings.Select((w, i) => (Syntax: w, Index: i)))
{
var si = document.SemanticModel.GetSymbolInfo(us.Syntax.Name!);
var ns = si.Symbol;

if (ns != null)
{
// if any parts does not rewrite, keep original name
if (currentUsings[us.Index].Name!.ToFullString().Split(".").Any(w => ns.ToDisplayString().Contains(w)))
{
usings.Add(us.Syntax);
continue;
}

// if any declaration in external source, keep both original and rewrite
if (ns.IsAnyDeclarationIsNotInWorkspace(solution))
{
usings.Add(us.Syntax);
usings.Add(currentUsings[us.Index]);
continue;
}

usings.Add(currentUsings[us.Index]);
}
}

var namespaces = node.DescendantNodes().OfType<BaseNamespaceDeclarationSyntax>().ToList();
foreach (var decl in namespaces)
{
var si = document.SemanticModel.GetSymbolInfo(decl.Name);
var ns = si.Symbol as INamespaceSymbol;

if (ns == null)
continue;


while (true)
{
if (ns == null || ns.IsGlobalNamespace)
break;

if (ns.IsAnyDeclarationIsNotInWorkspace(solution))
{
// nothing to do
}

if (_symbols.Any(w => w.ContainingNamespace.ToDisplayString().Contains(ns.ToDisplayString())))
usings.Add(SyntaxFactory.UsingDirective(SyntaxFactory.IdentifierName(ns.ToDisplayString())));

ns = ns.ContainingNamespace;
}
}

return compilation.WithUsings(SyntaxFactory.List(usings));
}

return newNode;
}

public override SyntaxNode? VisitClassDeclaration(ClassDeclarationSyntax node)
{
var newNode = base.VisitClassDeclaration(node);
Expand Down Expand Up @@ -184,6 +254,8 @@ internal class CSharpSymbolsRewriter(IDocument document, bool keepNameOnInspecto
var s = t.ConstructedFrom;
if (dict.TryGetValue(s, out var value))
return generic.WithIdentifier(SyntaxFactory.Identifier(value));

_symbols.Add(t);
}

if (symbol is IMethodSymbol m)
Expand Down Expand Up @@ -225,6 +297,9 @@ internal class CSharpSymbolsRewriter(IDocument document, bool keepNameOnInspecto

if (dict.TryGetValue(symbol.OriginalDefinition, out var value2))
return identifier.WithIdentifier(SyntaxFactory.Identifier(value2));

if (symbol is INamedTypeSymbol t)
_symbols.Add(t);
}
}
}
Expand Down
8 changes: 4 additions & 4 deletions src/Plana.Composition.RenameSymbols/CSharpSymbolsWalker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -279,14 +279,14 @@ private string SetMethodIdentifier(IMethodSymbol symbol)
if (isExternalDefinition)
return KeepOriginalName(symbol, overridden.Name);

if (overridden.IsNotInWorkspace(solution))
if (overridden.IsAnyDeclarationIsNotInWorkspace(solution))
return KeepOriginalName(symbol, overridden.Name);
}

if (original.Locations.Any(w => w.IsInMetadata))
return KeepOriginalName(symbol);

if (original.IsNotInWorkspace(solution))
if (original.IsAnyDeclarationIsNotInWorkspace(solution))
return KeepOriginalName(symbol);

var @interface = symbol.GetInterfaceSymbol();
Expand Down Expand Up @@ -344,7 +344,7 @@ private string SetPropertyIdentifier(IPropertySymbol symbol)

var original = symbol.OriginalDefinition;

if (original.IsNotInWorkspace(solution))
if (original.IsAnyDeclarationIsNotInWorkspace(solution))
return KeepOriginalName(original);

var @interface = symbol.GetInterfaceSymbol();
Expand Down Expand Up @@ -403,7 +403,7 @@ private void SetNamespaceIdentifier(INamespaceSymbol symbol)
if (dict.ContainsKey(symbol))
return;

if (symbol.IsNotInWorkspace(solution))
if (symbol.IsAnyDeclarationIsNotInWorkspace(solution))
return;

if (symbol.ContainingNamespace.IsGlobalNamespace)
Expand Down
2 changes: 1 addition & 1 deletion src/Plana.Composition.RenameSymbols/RenameSymbolsPlugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ public async Task ObfuscateAsync(IPlanaPluginRunContext context)

foreach (var document in context.Solution.Projects.SelectMany(w => w.Documents))
{
var rewriter = new CSharpSymbolsRewriter(document, KeepOriginalNameInInspector, _dict);
var rewriter = new CSharpSymbolsRewriter(context.Solution, document, KeepOriginalNameInInspector, _dict);

var oldNode = await document.SyntaxTree.GetRootAsync(context.CancellationToken);
var newNode = (CSharpSyntaxNode)rewriter.Visit(oldNode);
Expand Down

0 comments on commit 5020e5d

Please sign in to comment.