Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add analyzer for flagging single-use of local JsonSerializerOptions #6850

Merged
merged 7 commits into from Aug 14, 2023

Conversation

Jozkee
Copy link
Member

@Jozkee Jozkee commented Aug 11, 2023

Closes dotnet/runtime#65396

This analyzer doesn't use data flow analysis.
It tracks the local references on the left-hand side of the new JsonSerializerOptions().
If any of the locals potentially escapes current block scope, we will bail.

Escaping current block scope encompasses:

  • Field or property assignment.
  • Closure on lambda or local function.
  • Argument pass to another method.

@Jozkee Jozkee requested a review from a team as a code owner August 11, 2023 16:24
@codecov
Copy link

codecov bot commented Aug 11, 2023

Codecov Report

Merging #6850 (4c2fcc6) into main (2a2b5f6) will increase coverage by 0.00%.
Report is 1 commits behind head on main.
The diff coverage is 95.75%.

Additional details and impacted files
@@           Coverage Diff            @@
##             main    #6850    +/-   ##
========================================
  Coverage   96.39%   96.39%            
========================================
  Files        1401     1403     +2     
  Lines      330341   330954   +613     
  Branches    10845    10889    +44     
========================================
+ Hits       318437   319029   +592     
- Misses       9181     9191    +10     
- Partials     2723     2734    +11     

JsonSerializerOptions opt = new JsonSerializerOptions();
Action lambda = () =>
{
JsonSerializer.Serialize(value, opt);
Copy link
Member

@buyaa-n buyaa-n Aug 11, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT: Wonder why it should not warn here ...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because opt is initialized outside of lambda and once used in lambda it can escape current scope.

This is mentioned in the PR description:

If any of the locals potentially escapes current block scope, we will bail.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, this just looks like false negative to me, I guess I am not understanding the point of this analyzer.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't want the analyzer to catch all possible single uses, just the 80% of cases that are very common.

Comment on lines +188 to +237
private static bool IsTupleForDeconstructionTargetingFieldOrProperty(IOperation operation)
{
IOperation? current = operation.Parent;

if (current is not ITupleOperation tuple)
{
return false;
}

Stack<int> depth = new Stack<int>();
depth.Push(tuple.Elements.IndexOf(operation));

// walk-up right-hand nested tuples.
while (tuple.Parent is ITupleOperation parent)
{
depth.Push(parent.Elements.IndexOf(tuple));
tuple = parent;
}

current = tuple.WalkUpConversion().Parent;
if (current is not IDeconstructionAssignmentOperation deconstruction)
{
return false;
}

// walk-down left-hand nested tuples and see if it targets a field or property.
if (deconstruction.Target is not ITupleOperation deconstructionTarget)
{
return false;
}

tuple = deconstructionTarget;

IOperation? target = null;
while (depth.Count > 0)
{
int idx = depth.Pop();
target = tuple.Elements[idx];

if (target is ITupleOperation targetAsTuple)
{
tuple = targetAsTuple;
}
else if (depth.Count > 0)
{
return false;
}
}

return target is IFieldReferenceOperation or IPropertyReferenceOperation;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT: Lots if effort for checking if the tuple targeting field or property, did not see a test case that flags a usage with tuple, is there any case that should be flagged? isn't it better to just bail out if the option is used within an tuple?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it is probably too much effort for a rare case. But it doesn't hurt to have it. I can add the missing test you are referring to.

@Jozkee Jozkee requested a review from buyaa-n August 14, 2023 15:54
Copy link
Member

@buyaa-n buyaa-n left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left a NIT, overall LGTM, thank you!

{
static void Main(string[] args)
{
string json = JsonSerializer.Serialize(args, {|CA1869:new JsonSerializerOptions { AllowTrailingCommas = true }|});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT: there is only one diagnostic exit for this analyzer, so could use only [| |] brackets without the diagnostic ID, here and everywhere else

Suggested change
string json = JsonSerializer.Serialize(args, {|CA1869:new JsonSerializerOptions { AllowTrailingCommas = true }|});
string json = JsonSerializer.Serialize(args, [|new JsonSerializerOptions { AllowTrailingCommas = true }|]);

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couldn't be mixed-up in the future if another analyzer hits the same operation?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope, separate analyzers do not interfere each other in the tests.

@Jozkee Jozkee merged commit 2af4cd8 into dotnet:main Aug 14, 2023
11 checks passed
@Jozkee Jozkee deleted the json-single-use-options branch August 14, 2023 17:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add an analyzer that warns on single-use JsonSerializerOptions instances
5 participants