Using Roslyn, my aim is to check if a method parameter is checked for not being null before the parameter is dereferenced. This check can be in a submethod of course.
My approach is to get the first dereferencing of the parameter and search the syntax tree between that and the method start for null checks. How can I do some kind of control flow analysis to determine if the first dereferencing of the parameter can be reached with the parameter being null?
This is way too broad question, with a little explanation what is your final goal. Are you trying to detect null-pointer exceptions before they even happen, 100%? (Pretty much impossible)
I have written static analysis myself few months ago, I didn't use roslyn, but this doesn't matter.
Check this out to get you possibly started - it's reporting warnings when there are unused variables:
internal class UnUsedVariableWarningDefinition : ICodeIssue
{
public IEnumerable<IssueReport> Analyze()
{
var usageMap = new Dictionary<string, int>(StringComparer.InvariantCultureIgnoreCase);
var variableMap = new Dictionary<string, IdentifierNode>(StringComparer.InvariantCultureIgnoreCase);
foreach (var node in NodeAnalyzerHelper.FindNodesDfs(Root))
{
var assignmentNode = node as AssignmentNode;
if (assignmentNode != null)
{
var variableNode = assignmentNode.Identifier;
int usages;
if (!usageMap.TryGetValue(variableNode.Identifier, out usages))
{
usageMap[variableNode.Identifier] = 0;
variableMap[variableNode.Identifier] = variableNode;
}
}
else
{
// not really an assignmentNode,
// let's see if we have detected the usage of IdentifierNode somewhere.
var variableNode = node as IdentifierNode;
if (variableNode != null)
{
if (usageMap.ContainsKey(variableNode.Identifier))
usageMap[variableNode.Identifier]++;
}
}
}
foreach (var node in usageMap.Where(x => x.Value == 0).Select(x => variableMap[x.Key]))
{
yield return node.ConstructWarning("No usages of this variable found. Are you sure this is needed?");
}
}
}
Notice that FindNodesDfs() is basically a syntax tree walker, which walks syntax nodes depth-first style. What it does is just scans AssigfnmentNodes and puts them to Dictionary, as soon as it identifies IdentifierNode, it checks the dictionary if it has previously encountered assignment, or not. It's a bit similiar what you're trying to do, I guess.
Related
Some people decided to close my previous question, but the question they linked (What is a NullReferenceException, and how do I fix it?) did not have an answer. This question is fundamentally different since the enumerable is populated. It is not null. Just as the first answer stated, I placed "strategic breakpoints" and checked the variables.
I'm debugging a XUnit test and it turns out that in my business logic the iteration variable in the foreach loop is throws an exception "Object Reference not set to instance of object". However, the list over which the iteration is happening is NOT null. I can see that when I'm debugging. Here is the code:
Business logic:
List<string> regionArray = new List<string>();
if (someCondition)
{
regionArray = _utils.GetRegions(someParam); // this is not returning null
}
foreach (var region in regionArray)
{
var query = from dataSet in myDataSets
where dataSet.Location == region
select dataSet;
var queryResult = query.FirstOrDefault();
if (queryResult == null)
{
// do stuff
} else if (queryResult.State != States.Provisioned)
{
// do stuff
}
}
Here is how I am mocking the _utils.GetRegions call, but I dont think thats the problem.
private Mock<IUtils> _mockRegionUtils;
[Fact]
public void ItWorks()
{
// do stuff
_mockRegionUtils = new Mock<IUtils>();
_mockRegionUtils.Setup(utils => utils.GetRegions(It.IsAny<ISomeParam>())).Returns(new List<string>() {"america", "china"});
// call business logic
}
I have checked all the types in the debugger. regionArray.GetType() returns {System.Collections.Generic.List`1[System.String]}. when I type region into the console however, i get:
region
'region' threw an exception of type 'System.NullReferenceException'
how is this possible?
EDIT: fixed a typo above, sorry about that. Something weird though, so if I reassign the value of regionArray to be an inline list, it still fails. But if I define a new inline list and iterate over that, the looping works fine.
List<string> regionArray = new List<string>();
if (someCondition)
{
regionArray = _utils.GetRegions(someParam); // this is not returning null
}
regionArray = new List<string>() {"china", "america"};
List<string> temp = new List<string>() {"foo", "bar"}
foreach (var region in regionArray)
{
// region still throws null reference exception
foreach (var tempVar in temp)
{
var c = tempVar; // this works. tempvar is never null.
}
var query = from dataSet in myDataSets
where dataSet.Location == region
select dataSet;
var queryResult = query.FirstOrDefault();
if (queryResult == null)
{
// do stuff
} else if (queryResult.State != States.Provisioned)
{
// do stuff
}
}
EDIT 2: So I tried iterating over the regionArray in the same way just before the logic above, and it worked fine.
List<string> regionArray = new List<string>();
if (someCondition)
{
regionArray = _utils.GetRegions(someParam); // this is not returning null
}
foreach (var region in regionArray)
{
var c = region; // this works
}
foreach (var region in regionArray)
{
// region throws null reference exception
var query = from dataSet in myDataSets
where dataSet.Location == region
select dataSet;
var queryResult = query.FirstOrDefault();
if (queryResult == null)
{
// do stuff
} else if (queryResult.State != States.Provisioned)
{
// do stuff
}
}
so most likely, it is not a problem with the moq object. based on #Iliar's suggestion, I will see if regionArray gets modified, but at first glance since regionArray is not used within the loop, my answer would be "no".
Update: I got around this issue by renaming the region looping variable to a different name. As it turns out, I was doing another foreach (var region ...) loop earlier in my code. I spoke to some senior colleagues as to why these 2 names would conflict with each other, and they said maybe it was some issue with symbols in VSCode and not really with my actual code. Thank you all for your help!
There was a lot of info in this thread, so just to summarize here are a few bulletpoints in case it is helpful to someone else in the future:
When debugging an XUnit test, I was seeing my looping variable in my foreach displaying the following info in the tooltip 'region' threw an exception of type 'System.NullReferenceException' Data [IDictionary]:{System.Collections.ListDictionaryInternal} HResult [int]:-2147467261 HelpLink [string]:null InnerException [Exception]:null Message [string]:"Object reference not set to an instance of an object." Source [string]:"9dd66c33104045bba27ad3fc9fb95185" StackTrace [string]:" at <>x.<>m0(<IngestEvents>d__13 <>4__this)" TargetSite [MethodBase]:{System.String <>m0(<IngestEvents>d__13)} Static members ....
even as I stepped INTO the loop, the tooltip for region was still showing the above, and when I typed region into the console, I got 'region' threw an exception of type 'System.NullReferenceException'.
The above 2 points led me to believe region was null. However, through #IVSoftware 's help, I verified that region was not actually null, because the assertion was passing.
I then looked at the rest of my code, and as a random guess, I tried renaming the looping variable region to something else. When I did, region was correctly set to the elements of the list.
Hi I really hope to be helpful. First I will answer your question "how is this possible?" and I think I can explain why your edited question with the inline list works. True, the GetRegions method returns a list that is not null. Sure, if you call GetType() on this it correctly identifies it as a "list of strings". I believe however, that the GetRegions method is returning a list that contains at least one null value. And you prove it out yourself when you added the edit, because you say this works:
regionArray = new List<string>() {"china", "america"};
But try making a list with three values like this where one of them is null and probably the loop will fail again.
regionArray = new List<string>() {"china", null, "america"};
This suggests a bug inside the GetRegions method is putting a null value into the list that it is returning. Adding these two lines at the beginning of your loop might go a long way to identifying the issue:
foreach (var region in regionArray)
{
// Look for this in the Visual Studio 'Output' window
System.Diagnostics.Debug.WriteLine(region == null ? "Null" : region);
// You believe that region is not null. So 'assert' that
// this evaluates to True and if it doesn't the debugger will break here.
System.Diagnostics.Debug.Assert(region != null, "Break on this line if region is null");
...
From what I can tell, I would look inside your GetRegions(someParam) method and see if it's inserting a null into the list somewhere. Good luck!
List<string> regionArray = new List<string>();
if (someCondition)
{
regionArray = _utils.GetRegions(someParam); // this is not returning null
}
this will override the regionArray instance, to the GetRegions instance, so creating new List<string> instance is useless.
What you want is :
List<string> regionArray = new List<string>();
if (someCondition)
{
regionArray.AddRange(_utils.GetRegions(someParam));
}
which is using this new instance, and add the resulted elements inside it.
If _utils.GetRegions(someParam) returns empty set, then regionArray will not be null, but it'll be empty regionArray.Count == 0.
this is also can be done using ToList as well:
var regionArray = _utils.GetRegions(someParam).ToList();
now , you need to check regionArray after that :
if(regionArray.Count == 0)
{
// do something
}
Or using Linq
if(!regionArray.Any())
{
// do something
}
if the collection is not empty then you can iterate through the list and validate each string inside this list before you process it:
foreach (var region in regionArray)
{
// check if the element is null or empty
// if true, will skip this element and go to the next one
if(string.IsNullOrEmpty(region)) { continue; } // go to the next iteration
// get the results
var queryResult = myDataSets.FirstOrDefault(x=> x.Location == region);
if (queryResult == null)
{
// do stuff
}
else if (queryResult.State != States.Provisioned)
{
// do stuff
}
}
I'm trying to use Roslyn to determine the publically-exposed API of a project (and then do some further processing using this information, so I can't just use reflection). I'm using a SyntaxWalker to visit declaration syntax nodes, and calling IModel.GetDeclaredSymbol for each. This seems to work well for Methods, Properties, and Types, but it doesn't seem to work on fields. My question is, how do I get the FieldSymbol for a FieldDeclarationSyntax node?
Here's the code I'm working with:
public override void VisitFieldDeclaration(FieldDeclarationSyntax node)
{
var model = this._compilation.GetSemanticModel(node.SyntaxTree);
var symbol = model.GetDeclaredSymbol(node);
if (symbol != null
&& symbol.CanBeReferencedByName
// this is my own helper: it just traverses the publ
&& symbol.IsExternallyPublic())
{
this._gatherer.RegisterPublicDeclaration(node, symbol);
}
base.VisitFieldDeclaration(node);
}
You need to remember that a field declaration syntax can declare multiple fields. So you want:
foreach (var variable in node.Declaration.Variables)
{
var fieldSymbol = model.GetDeclaredSymbol(variable);
// Do stuff with the symbol here
}
I have code to detect when an exception is created inside of a method like so:
foreach (var instr in body.Instructions)
{
if (instr.OpCode.Code == Code.Newobj)
{
var methRef = (Mono.Cecil.MethodReference)instr.Operand;
var type = Type.GetType(methRef.DeclaringType.FullName);
if (typeof(System.Exception).IsAssignableFrom(type))
// do stuff
}
}
full code here.
So far this is working well for straight forward code, but in the case where I'm dealing with a closure it obviously won't work because the exception is inside of a method on the object generated for the closure, not in the method that I am currently testing. The exception in:
private static int DataAccessMethod(int value)
{
int r = 0;
System.Threading.ManualResetEvent evt = new System.Threading.ManualResetEvent(false);
var workItem = System.Threading.ThreadPool.QueueUserWorkItem((_) =>
{
if (r == 0)
{
throw new System.InvalidOperationException("I canr spell.");
}
r = value * value;
evt.Set();
});
evt.WaitOne();
return r;
}
for instance will go undetected. My question is:
Is there anyway to detect that the operand to newobj is referring to a closure type?
As #usr noted in comments closures are just normal classes, they doesn't exist distinctly in IL. Only difference is you've not created it; compiler did it for you.
I believe you should be looking at unique compiler generated names to find them. EricLippert describes naming conventions followed by compiler here, Do note that it is subject to change in future versions of compiler.
Also after coming to the conclusion that the Type is a closure you can verify that using CompilerGenerated attribute.
Example
Have a look at the following code:
private void DeDuplicateOrganisations()
{
var profileOrgs = _organisations.Where(o => o.ExistsInProfile).ToList();
var kvkOrgs = _organisations.Where(o => !o.ExistsInProfile).ToList();
profileOrgs.ForEach(o =>
{
var duplicate = kvkOrgs.FirstOrDefault(k => k.KvK == o.KvK || k.Title == o.Title);
if (duplicate != null)
{
o.CompanyInfoOrganisation = duplicate.CompanyInfoOrganisation;
o.ExistsInBoth = true;
kvkOrgs.Remove(duplicate);
}
});
_organisations = profileOrgs.Concat(kvkOrgs).OrderBy(o => o.Title).ToList();
}
In this example the property CompanyInfoOrganisation (simply a get; set; property) is copied when an organisation is considered a duplicate. This all works as expected, duplicates are nicely deduplicated.
Also this is true inside this message:
_organisations.First(o => o.ExistsInBoth).CompanyInfoOrganisation != null;
Problem
Now I bind the _organisations list to a listbox
lbxCompanies.DataSource = null;
lbxCompanies.DataSource = _organisations;
lbxCompanies.DisplayMember = "Title";
lbxCompanies.SelectedIndex = -1;
and later on get the selected value:
var org = lbxCompanies.SelectedValue as Organisation;
gbxCompanyInfo.Visible = org != null;
if (gbxCompanyInfo.Visible)
if (org.CompanyInfoOrganisation != null)
// NEVER GETS HERE (but gbxComanpyInfo is visible)
If I try to read the CompanyInfoOrganisation property I always get null while I know the property was set.
Question
What is happening here? How come the property reference is destroyed? How can I prevent this from happening?
The reference you're using only has immediate scope and as soon as the query ends it exits scope and your reference disappears. So when you bind later, the reference is exactly right -- null.
profileOrgs.ForEach(o =>
{
// Right here -- var duplicate has scope ONLY within your query.
// As soon as the query is executed it leaves scope and the reference
// pointer will be null
var duplicate = kvkOrgs.FirstOrDefault(k => k.KvK == o.KvK || k.Title == o.Title);
if (duplicate != null)
{
o.CompanyInfoOrganisation = duplicate.CompanyInfoOrganisation;
o.ExistsInBoth = true;
kvkOrgs.Remove(duplicate);
}
});
Because you're using a class, you need to perform a deep MemberwiseClone on it to get a NEW copy of the object:
o.CompanyInfoOrganisation = (YourInfoType)duplicate.CompanyInfoOrganisation.MemberwiseClone();
When you load the data, load the CompanyInfoOrganisation property along with the root entity; that way it will be already loaded into memory. If using LINQ to SQL, you load via DataLoadOptions, and pass this to the context. If using Entity Framework, you use the Include method in the LINQ query.
It might have to do with capturing of variables inside the lambda. Try substituting the .ForEach to a regular foreach().
Or maybe the CompanyInfoOrganisation in duplicate was null to begin with.
The problem was I used string.Join() to show the values, and the first value to join was null (which is really annoying), resulting in an empty string, leaving me thinking the property was null. However it turned out the property was not null, but has a perfectly valid reference to the object needed. Using the debugger with a little more care would have saved me an hour or so...
Sorry!
i get this error when i try to compile my program using Visual Studio and Enterprise Architect.
I'm writing a tool for Enterprise Architect, and i have to make a graph, and i continue to get this error, i don't know what to do.
The code that i have problem with is:
public Graph(EA.Repository repository)
{
EA.Diagram maindiagram;
this.modelRepository = repository;
maindiagram = repository.GetCurrentDiagram(); //recupero del diagramma
this.diagramId = maindiagram.DiagramID; //identificativo del diagramma
//inizializzazione nodi
Collection nodeCollection = maindiagram.DiagramObjects;
nodeList = new ArrayList();
foreach (DiagramObject diagram in maindiagram.DiagramObjects)
{
diagramList.Add(diagram);
foreach (Element element in diagramList)
{
if (element.Type == "Class"|| element.Type == "Component"||element.Type == "Package")
{ nodeList.Add(new Node(diagram, ref repository)); }
}
}
//inizializzazione archi
Collection linkCollection = maindiagram.DiagramLinks;
linkList = new ArrayList();
foreach (DiagramLink edge in maindiagram.DiagramLinks)
{
edgeList.Add(edge);
foreach(Connector connector in edgeList)
if (connector.Type == "Association" || connector.Type == "Aggregation" || connector.Type == "Compose" || connector.Type == "Dependency"
|| connector.Type == "Generalization" || connector.Type == "Realization")
{ linkList.Add(new Link (edge, ref repository));}
}
Please help if you know how.
Thank you a lot!
It's impossible to understand what is going on in this code, just by message provided.
look on lines number provided by exception message, and go to the line
or Enable first chance exception in Visual Studio and on exception the code will be breaked exactly on the line that generates the problem.
repository could be null - you should be doing null checks on parameter arguments to determine whether you can proceed or not; also maindiagram could be null (for all we know, if repository is something, then GetCurrentDiagram might return null.
Both of these things are accessed in a way that could cause your problem.
diagramList isn't present in the scope of the method so assuming it has a more liberal scope: this could also be nothing, yet you call Add on it. You also iterate through this as if it is something, and without checking that an element is null or not, try to access its properties.
In short, there are numerous places where this might occur in the code you posted. You should be more specific about where the error is actually occurring, however, the answer will be the same: something is nothing.