Roslyn: Compile expression with non-public fields from external assembly - c#

I am working on evaluating of user expressions from debugger. I want to compile expression in method context, and then inject IL-code with debugger.
Is it possible to compile expression, which contains non-public class/class-fields from external assembly to IL-code with Roslyn?
I've got 'MyNamespace.dll' with public class 'Test' and private method 'PrivateMethod', and I want to call it from Roslyn compilation.
I am trying to do it with next code:
public class TestCompilationOptions
{
public void Test()
{
var filePath = Path.Combine(Directory.GetCurrentDirectory(), "Output.dll");
Console.WriteLine("Preparing syntax tree");
string expressionString = #"
using System;
class XXX
{
public static void Main()
{
Console.WriteLine(MyNamespace.Test.PrivateMethod(2));
}
}";
//SyntaxTree targetTree = SyntaxFactory.ParseSyntaxTree(expressionString);
SyntaxTree targetTree = CSharpSyntaxTree.ParseText(expressionString);
Console.WriteLine("Preparing metadata references");
Assembly[] assemblys = new Assembly[4];
assemblys[0] = typeof(MyNamespace.Test).Assembly;
assemblys[1] = typeof(Console).Assembly;
assemblys[2] = typeof(object).Assembly;
assemblys[3] = Assembly.LoadFile(Path.Combine(Directory.GetCurrentDirectory(), "System.Runtime.dll"));
MetadataReference[] metadataReferences = MetadataFromAssembly(assemblys);
Console.WriteLine("Preparing default namespaces");
IEnumerable<string> DefaultNamespaces = new[] {"System", "System.Runtime"};
Console.WriteLine("Preparing compilation options");
CSharpCompilationOptions ReleaseDll = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, optimizationLevel: OptimizationLevel.Release);
CSharpCompilationOptions cOptions = ReleaseDll.WithUsings(DefaultNamespaces);
//.WithMetadataImportOptions(MetadataImportOptions.All);
Console.WriteLine("Getting compilation");
CSharpCompilation compilation = CSharpCompilation.Create("Output.dll", new SyntaxTree[] {targetTree}, metadataReferences, cOptions);
Console.WriteLine("Emitting compilation");
using (var dll = new FileStream(filePath, FileMode.Create, FileAccess.Write))
{
var emitRes = compilation.Emit(dll);
if (!emitRes.Success)
{
Console.WriteLine("Emited unsuccessfully!");
foreach (var d in emitRes.Diagnostics)
Console.WriteLine(d.ToString());
return;
}
}
}
public unsafe MetadataReference[] MetadataFromAssembly(Assembly[] assemblys)
{
MetadataReference[] result = new MetadataReference[assemblys.Length];
byte *b;
int length;
for (int i = 0; i < assemblys.Length; i++)
{
if (assemblys[i].TryGetRawMetadata(out b, out length))
{
var moduleMetadata = ModuleMetadata.CreateFromMetadata((IntPtr) b, length);
var assemblyMetadata = AssemblyMetadata.Create(moduleMetadata);
result[i] = assemblyMetadata.GetReference();
}
else
{
return null;
}
}
return result;
}
And got following error:
(8,44): error CS0117: 'Test' does not contain a definition for 'privateMember'
I've made 'WithMetadataImportOptions' and 'MetadataImportOptions' public inside Roslyn and uncomennted line
//.WithMetadataImportOptions(MetadataImportOptions.All);
And then got following error:
(8,44): error CS0122: 'Test.privateMember' is inaccessible due to its protection level
So may be it could be done using some Roslyn API?
P.S.
I know, that I can get non-public fields symbols using System.Reflection, but how do I compile the expression then?

If a member is private, you can't access it with normal code in another class. Nothing to do with Roslyn in particular.
If you really do actually need to access a private member in a different class, and you fully understand why it may not be a good idea, the code that accesses it must do so using reflection.

Related

Is there a way to make debugger work with modified assembly

I am trying to modify assembly before using it.
Main file:
using IlGenTestTarget;
using Lokad.ILPack;
using System.Reflection;
using Mono.Cecil;
using IlGenTest;
Assembly inAssembly = Assembly.GetAssembly(typeof(Class1));
AssemblyGenerator assemblyGenerator = new AssemblyGenerator();
byte[] b = assemblyGenerator.GenerateAssemblyBytes(inAssembly);
AssemblyDefinition assemblyDefinition = AssemblyDefinition.ReadAssembly(new MemoryStream(b));
foreach (ModuleDefinition module in assemblyDefinition.Modules) {
IlGenTestUtils.RemoveRemovedElements(module.Types);
foreach (TypeDefinition type in module.Types) {
IlGenTestUtils.RemoveRemovedElements(type.Methods);
IlGenTestUtils.RemoveRemovedElements(type.Fields);
}
}
MemoryStream ms = new MemoryStream();
assemblyDefinition.Write(ms);
byte[] res = ms.ToArray();
Assembly resAssembly = Assembly.Load(res);
Module resModule = resAssembly.GetModules()[0];
Type resType = resModule.GetType("IlGenTestTarget.Class1");
MethodInfo resMethod = resType.GetMethod("Method1");
resMethod.Invoke(null, null);
IlGenTestUtils:
using Mono.Cecil;
using Mono.Collections.Generic;
namespace IlGenTest
{
public class IlGenTestUtils
{
public static List<T> GetRemovedElements<T>(Collection<T> collection) where T : ICustomAttributeProvider
{
return collection
.Where(t => t.CustomAttributes.Any(attr => attr.AttributeType.Name == "RemovedAttribute"))
.ToList();
}
public static void RemoveRemovedElements<T>(Collection<T> collection) where T : ICustomAttributeProvider
{
foreach (T t in GetRemovedElements<T>(collection))
{
collection.Remove(t);
}
}
}
}
When I put breakpoint on Method1, everything works fine, but progam is not paused on it. When I invoke Method1 directly, without creating new assembly, program is paused on breakpoint as expected. Is there a way to make breakpoints work with dynamic assembly?
The link between "source line at which a breakpoint is placed" and "assembly file contents" is defined by the .pdb file for the assembly. After modifying the assembly, I would actually be surprised if the link between them would still work.
You would need to also rebuild the .pdb file, which seems hard if not impossible.

C# Roslyn to use previously compiled class in next compilations

I ask with example,
Lets say I have the following code.
fullcommand = #"public class oldTest
{
public static void oldTestMethod(){
Console.WriteLine(""oldTest Class"");
}
}"
var syntaxTree = CSharpSyntaxTree.ParseText(fullCommand);
var compilation = CSharpCompilation.Create(
assemblyName,
new[] {syntaxTree},
references,
options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary,allowUnsafe:true));
var ms = new MemoryStream();
var result = compilation.Emit(ms);
And I will compile the above code with Roslyn in memory.
next i want to compile another code in memory to use the above compiled class, lets say the below.
new_fullcommand = #"public class newTest
{
public static void newTest(){
oldTest.oldTestMethod();
}
}"
var syntaxTree = CSharpSyntaxTree.ParseText(new_fullcommand);
var compilation = CSharpCompilation.Create(
assemblyName,
new[] {syntaxTree},
references,
options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary,allowUnsafe:true));
var ms = new MemoryStream();
var result = compilation.Emit(ms);
how can i make the second code to use the first code as its reference? or use it?
The easiest way would probably be to pass in multiple syntaxTree objects when you create your compilation.
However, if you want to build up your compilation incrementally I believe you can use Compilation.AddSyntaxTrees to your first compilation object.

Code breaks after update to Lucene.net 4.8.0-beta00001

I have just started using Lucene.net for a project. I have based my code on the code provided here: https://github.com/synhershko/LuceneNetDemo by Itamar Syn-Hershko. After I updated to the newest NuGet, the code breaks in a couple of places. What do I need to change?
First problem:
searcherManager.ExecuteSearch(searcher =>
{
var topDocs = searcher.Search(query, 10);
_totalHits = topDocs.TotalHits;
foreach (var result in topDocs.ScoreDocs)
{
var doc = searcher.Doc(result.Doc);
l.Add(new SearchResult
{
Name = doc.GetField("name")?.StringValue,
Description = doc.GetField("description")?.StringValue,
Url = doc.GetField("url")?.StringValue,
// Results are automatically sorted by relevance
Score = result.Score,
});
}
}, exception => { Console.WriteLine(exception.ToString()); });
The errormessage:
'SearcherManager' does not contain a definition for 'ExecuteSearch' and no extension method 'ExecuteSearch' accepting a first argument of type 'SearcherManager' could be found (are you missing a using directive or an assembly reference?)
Second problem:
public class HtmlStripAnalyzerWrapper : Analyzer
{
private readonly Analyzer _wrappedAnalyzer;
public HtmlStripAnalyzerWrapper(Analyzer wrappedAnalyzer)
{
_wrappedAnalyzer = wrappedAnalyzer;
}
public override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
{
return _wrappedAnalyzer.CreateComponents(fieldName, new HTMLStripCharFilter(reader));
}
}
The errormessage:
'HtmlStripAnalyzerWrapper.CreateComponents(string, TextReader)': cannot change access modifiers when overriding 'protected internal' inherited member 'Analyzer.CreateComponents(string, TextReader)'
And
Cannot access protected member 'Analyzer.CreateComponents(string, TextReader)' via a qualifier of type 'Analyzer'; the qualifier must be of type 'HtmlStripAnalyzerWrapper' (or derived from it)
There is an update to the demo at: https://github.com/NightOwl888/LuceneNetDemo
First Problem:
The API was inadvertently removed because it was not properly marked and does not exist in Lucene 4.8.0. However, it is only a supplemental API to SearcherManager.Acquire() and SearcherManager.Release(). You can see its usage in the SearcherManager documentation of Lucene 4.8.0.
var searcher = searcherManager.Acquire();
try
{
var topDocs = searcher.Search(query, 10);
_totalHits = topDocs.TotalHits;
foreach (var result in topDocs.ScoreDocs)
{
var doc = searcher.Doc(result.Doc);
l.Add(new SearchResult
{
Name = doc.GetField("name")?.GetStringValue(),
Description = doc.GetField("description")?.GetStringValue(),
Url = doc.GetField("url")?.GetStringValue(),
// Results are automatically sorted by relevance
Score = result.Score,
});
}
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
finally
{
searcherManager.Release(searcher);
searcher = null; // Never use searcher after this point!
}
We are considering whether to bring back the original ExecuteSearch() API, or create a new one that can be used with a using block for a more .NET-friendly experience. See an example of the second option in pull request 207. Feedback welcome.
Certainly, an API that swallows exceptions by default is less than ideal.
Second Problem:
Accessibility of API members was also corrected to match Lucene. CharFilters were not intended to be used in conjunction with pre-built Analyzers for performance reasons. Instead, you must build up an Analyzer from pre-built tokenizers and filters.
using Lucene.Net.Analysis;
using Lucene.Net.Analysis.CharFilters;
using Lucene.Net.Analysis.Core;
using Lucene.Net.Analysis.Standard;
using Lucene.Net.Util;
using System.IO;
namespace LuceneNetDemo.Analyzers
{
class HtmlStripAnalyzer : Analyzer
{
private readonly LuceneVersion matchVersion;
public HtmlStripAnalyzer(LuceneVersion matchVersion)
{
this.matchVersion = matchVersion;
}
protected override TokenStreamComponents CreateComponents(string fieldName, TextReader reader)
{
StandardTokenizer standardTokenizer = new StandardTokenizer(matchVersion, reader);
TokenStream stream = new StandardFilter(matchVersion, standardTokenizer);
stream = new LowerCaseFilter(matchVersion, stream);
stream = new StopFilter(matchVersion, stream, StopAnalyzer.ENGLISH_STOP_WORDS_SET);
return new TokenStreamComponents(standardTokenizer, stream);
}
protected override TextReader InitReader(string fieldName, TextReader reader)
{
return base.InitReader(fieldName, new HTMLStripCharFilter(reader));
}
}
}
Usage:
analyzer = new PerFieldAnalyzerWrapper(new HtmlStripAnalyzer(LuceneVersion.LUCENE_48),
new Dictionary<string, Analyzer>
{
{"owner", new LowercaseKeywordAnalyzer()},
{"name", new RepositoryNamesAnalyzer()},
});

Can't retrieve type information via Roslyn

I am attempting to retrieve the type of a class syntax node in Roslyn so I can get the enclosing namespace by following along with #slaks answer: Roslyn : How to get the Namespace of a DeclarationSyntax with Roslyn C#
I have the following:
static async Task MainAsync(string[] args)
{
string projectPath = #"C:\Projects\ertp\Ertp.Mobile.csproj";
var msWorkspace = MSBuildWorkspace.Create();
var project = msWorkspace.OpenProjectAsync(projectPath).Result;
foreach (var document in project.Documents)
{
Console.WriteLine(project.Name + "\t\t\t" + document.Name);
SemanticModel model = await document.GetSemanticModelAsync();
var classes = document.GetSyntaxRootAsync().Result.DescendantNodes().OfType<ClassDeclarationSyntax>();
foreach (var klass in classes)
{
var info = model.GetTypeInfo(klass);
var isNull = info.Type == null; //TRUE
}
}
If I can't get the type I can't the namespace - any idea how I can retrieve the details I require?
For decelerators you need to call model.GetDeclaredSymbol(node) and then for the namespace, ContainingNamespace.
model.GetTypeInfo(node).Type will work for expression node.

Are there any tools that allow me to change all C# built-in types to their .NET Framework types?

One of the things that I find hard to keep consistent is the use of int vs Int32 and bool vs Boolean etcetera.
I find it simpler to identify all types by their case and color syntax highlighting...
List<int>
vs
List<Int32>
The latter is cleaner and upholds consistency. A lot of code is littered with both and I'm looking for a refactoring tool to change them all.
Are there any tools that allow me to change all C# built-in types to their .NET Framework types?
If you look at StyleCop, rule SA1121 actually enforces the opposite of what you want (asks you to change Int32 to int). It's fairly trivial to decompile that rule and create your own StyleCop rule to enforce the opposite.
This isn't automatic, but after you do your initial conversion, you can incorporate it into your builds and then flag any new uses as errors.
Using the Roslyn CTP, the following appears to work in practice:
static SyntaxTree UpdatePredefinedTypes(this SyntaxTree tree)
{
PredefinedTypeSyntax node;
var root = tree.Root;
while (null != (node = root.DescendentNodes()
.OfType<PredefinedTypeSyntax>()
.FirstOrDefault(
syn => redefineMap.ContainsKey(syn.PlainName))))
{
var ident = Syntax.IdentifierName(redefineMap[node.PlainName]);
root = root.ReplaceNode<SyntaxNode, SyntaxNode>(
node,
ident.WithLeadingTrivia(node.GetLeadingTrivia())
.WithTrailingTrivia(node.GetTrailingTrivia()));
}
return SyntaxTree.Create(
tree.FileName,
(CompilationUnitSyntax)root,
tree.Options);
}
When using a proper redefineMap (e.g. {"int","Int32"}, {"double","Double"}) the following program was converted successfully:
using System;
namespace HelloWorld {
class Program {
static void Main(string[] args) {
int x = Int32.Parse("11");
double y = x;
Console.WriteLine("Hello, World! {0}", y);
}
}
}
Output:
using System;
namespace HelloWorld {
class Program {
static void Main(String[] args) {
Int32 x = Int32.Parse("11");
Double y = x;
Console.WriteLine("Hello, World! {0}", y);
}
}
}
When compiling:
var mscorlib = new AssemblyFileReference(
typeof(object).Assembly.Location);
var newTree = UpdatePredefinedTypes(tree);
var compilation = Compilation.Create("HelloWorld")
.AddReferences(mscorlib)
.AddSyntaxTrees(new[] { newTree });
var results = compilation.Emit(File.Create("helloworld.exe"));
Console.WriteLine("Success: {0}", results.Success);
foreach (var message in results.Diagnostics)
{
Console.WriteLine("{0}", message);
}
// C:\tmp\cs>roslyn-test.exe
// Success: True
//
// C:\tmp\cs>dir /b *.exe
// roslyn-test.exe
// helloworld.exe
//
// C:\tmp\cs>helloworld.exe
// Hello, World! 11
//
You can even utilize the Workspace features to update an entire solution:
var workspace = Workspace.LoadSolution(info.FullName);
var solution = workspace.CurrentSolution;
foreach (var project in solution.Projects
.Where(prj => prj.LanguageServices.Language == "C#"))
{
foreach (var doc in project.Documents
.Where(d => d.SourceCodeKind == SourceCodeKind.Regular
&& d.LanguageServices.Language == "C#"))
{
var tree = SyntaxTree.ParseCompilationUnit(
doc.GetText(),
doc.DisplayName);
var newTree = UpdatePredefinedTypes(tree);
solution = solution.UpdateDocument(doc.Id, newTree.Text);
}
}
workspace.ApplyChanges(workspace.CurrentSolution, solution);
// when running this in VS on itself it correctly updates the project!
I do not know any tools except the search and replace function of VS.
I usually use the c# alias for type declarations and the .NET type when I call static members
int i = Int32.Parse(s);
It is just a personal preference.
I eventually wrote a macro to do this
Option Strict Off
Option Explicit Off
Imports System
Imports EnvDTE
Imports EnvDTE80
Imports EnvDTE90
Imports EnvDTE90a
Imports EnvDTE100
Imports System.Diagnostics
Public Module ReplaceCSharpBuiltInTypesWithTheirFrameworkTypes
Sub ReplaceCSharpBuiltInTypesWithTheirFrameworkTypes()
Dim dictionary As New Collections.Generic.Dictionary(Of String, String)
dictionary.Add("bool", "Boolean")
dictionary.Add("byte", "Byte")
dictionary.Add("sbyte", "SByte")
dictionary.Add("char", "Char")
dictionary.Add("decimal", "Decimal")
dictionary.Add("double", "Double")
dictionary.Add("float", "Single")
dictionary.Add("int", "Int32")
dictionary.Add("uint", "UInt32")
dictionary.Add("long", "Int64")
dictionary.Add("ulong", "UInt64")
dictionary.Add("object", "Object")
dictionary.Add("short", "Int16")
dictionary.Add("ushort", "UInt16")
dictionary.Add("string", "String")
For Each key In dictionary.Keys
DTE.Find.FindWhat = key
DTE.Find.ReplaceWith = dictionary(key)
DTE.Find.Target = vsFindTarget.vsFindTargetCurrentDocument
DTE.Find.MatchCase = True
DTE.Find.MatchWholeWord = True
DTE.Find.MatchInHiddenText = False
DTE.Find.PatternSyntax = vsFindPatternSyntax.vsFindPatternSyntaxLiteral
DTE.Find.ResultsLocation = vsFindResultsLocation.vsFindResultsNone
DTE.Find.Action = vsFindAction.vsFindActionReplaceAll
DTE.Find.Execute()
Next
End Sub
End Module

Categories