-
Notifications
You must be signed in to change notification settings - Fork 228
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
S3900: ShouldExecute method #6942
S3900: ShouldExecute method #6942
Conversation
db02661
to
62dbf70
Compare
...SonarAnalyzer.CSharp/SymbolicExecution/Roslyn/PublicMethodArgumentsShouldBeCheckedForNull.cs
Outdated
Show resolved
Hide resolved
...SonarAnalyzer.CSharp/SymbolicExecution/Roslyn/PublicMethodArgumentsShouldBeCheckedForNull.cs
Outdated
Show resolved
Hide resolved
...SonarAnalyzer.CSharp/SymbolicExecution/Roslyn/PublicMethodArgumentsShouldBeCheckedForNull.cs
Outdated
Show resolved
Hide resolved
...SonarAnalyzer.CSharp/SymbolicExecution/Roslyn/PublicMethodArgumentsShouldBeCheckedForNull.cs
Outdated
Show resolved
Hide resolved
...SonarAnalyzer.CSharp/SymbolicExecution/Roslyn/PublicMethodArgumentsShouldBeCheckedForNull.cs
Outdated
Show resolved
Hide resolved
...SonarAnalyzer.CSharp/SymbolicExecution/Roslyn/PublicMethodArgumentsShouldBeCheckedForNull.cs
Outdated
Show resolved
Hide resolved
...SonarAnalyzer.CSharp/SymbolicExecution/Roslyn/PublicMethodArgumentsShouldBeCheckedForNull.cs
Outdated
Show resolved
Hide resolved
...st/TestCases/SymbolicExecution/Roslyn/PublicMethodArgumentsShouldBeCheckedForNull.CSharp8.cs
Outdated
Show resolved
Hide resolved
...r.UnitTest/TestCases/SymbolicExecution/Roslyn/PublicMethodArgumentsShouldBeCheckedForNull.cs
Outdated
Show resolved
Hide resolved
...r.UnitTest/TestCases/SymbolicExecution/Roslyn/PublicMethodArgumentsShouldBeCheckedForNull.cs
Outdated
Show resolved
Hide resolved
...r.UnitTest/TestCases/SymbolicExecution/Roslyn/PublicMethodArgumentsShouldBeCheckedForNull.cs
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, just a couple of suggestions.
...SonarAnalyzer.CSharp/SymbolicExecution/Roslyn/PublicMethodArgumentsShouldBeCheckedForNull.cs
Outdated
Show resolved
Hide resolved
...SonarAnalyzer.CSharp/SymbolicExecution/Roslyn/PublicMethodArgumentsShouldBeCheckedForNull.cs
Outdated
Show resolved
Hide resolved
...SonarAnalyzer.CSharp/SymbolicExecution/Roslyn/PublicMethodArgumentsShouldBeCheckedForNull.cs
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added additional notes to the unresolved conversations.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/cc @pavel-mikula-sonarsource regarding the proposal on how to test "ShouldExecute":
&& (IsRelevantMethod(Node) || IsRelevantPropertyAccessor(Node)); | ||
|
||
static bool IsRelevantMethod(SyntaxNode node) => | ||
node is BaseMethodDeclarationSyntax { ParameterList.Parameters.Count: > 0 } method |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is all of this language specific? Where is the common base class?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we have exceptions regarding this
parameters (extension methods)? Do we need to take this into account here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If all parameters are out
parameters, ShouldExecute should return false.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I initially added the common base class, but got a comment for it. So it will be added later with the VB.NET implementation.
Invocation of an extension method will not raise an issue. However, this will not be dealt with in this PR, as ShouldExecute()
only operates on the syntax level and can't check whether a method call is referring to an extension method.
I added a filter for out
params.
public override void VisitIdentifierName(IdentifierNameSyntax node) => | ||
DereferencesMethodArguments |= | ||
argumentNames.Contains(node.GetName()) | ||
&& node.Ancestors().Any(x => x.IsAnyKind( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't it be enough to check the parent only? What do you consider a dereference? I would say
p.Invocation();
but notp?.Invocation();
p.PropertyAccess
, but notp?.PropertyAccess;
p[0];
but notp?[0];
p->Deref();
// Unsafe code only. Can p null here null? I don't know. I think p must be a struct but I'm not sure.await p;
// FYI: there is anawait? p
language proposal also Champion "Null-conditional await" dotnet/csharplang#35foreach(var i in p)
throw p
p
+p
No. I think all unary and binary operators like + or ! are okayM(p)
No. Passing around is okay.
Is there something else?
I think in all cases we just need to look at the parent.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is more relevant for VB as there is no casing difference, but we might also add another check, where we also check, that the identifier is indeed referring to a parameter:
In a case like so:
void M(int Length)
{
someList.Length.ToString(); // Length can not be a parameter here
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I changed the syntax walker to check the parents instead of all ancestors.
I'm not sure about the additional check. Is there an easy way to do it without using the semantic model?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add a test case for a PointerMemberAccess
@pavel-mikula-sonarsource Do we want to support dereferences in unsafe code like in the linked example?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure about the additional check. Is there an easy way to do it without using the semantic model?
You just need to make sure, that there is nothing left to the parameter name:
- parameter.Invoke() // <- okay
- somethingLeft.parameter.Invoke() // <- not okay
@Tim-Pohlmann added GetLeftOfDot
or something like that recently. You can use that to get the left-hand side of what you have found as a candidate.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried to find GetLeftOfDot
and similar methods in Tim's PR, but couldn't find them. Any chance you remember where it is?
Does it take more complex expressions into consideration? Like:
(parameter as object).ToString(); // Noncompliant
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not implemented yet: #6863
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
GetLeftOfDot
is in Roslyn, but we don't have it in our code base yet. I have used a workaround and created an issue to add it. Maybe this helps you:
#6753 (comment)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As it's not yet available in the project, I'll address this in another PR.
modifiers.AnyOfKind(SyntaxKind.PublicKeyword) | ||
|| (modifiers.AnyOfKind(SyntaxKind.ProtectedKeyword) && !modifiers.AnyOfKind(SyntaxKind.PrivateKeyword)); | ||
|
||
static bool HasNoDeclaredAccessabilityModifier(SyntaxTokenList modifiers) => |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
static bool HasNoDeclaredAccessabilityModifier(SyntaxTokenList modifiers) => | |
static bool HasNoDeclaredAccessibilityModifier(SyntaxTokenList modifiers) => |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops 🤣 I mistook switched the lines in the Commit suggestion and thought yours was the wrong one. Mea culpa! Corrected it.
...SonarAnalyzer.CSharp/SymbolicExecution/Roslyn/PublicMethodArgumentsShouldBeCheckedForNull.cs
Show resolved
Hide resolved
...SonarAnalyzer.CSharp/SymbolicExecution/Roslyn/PublicMethodArgumentsShouldBeCheckedForNull.cs
Show resolved
Hide resolved
a0bdec5
to
1daac54
Compare
Kudos, SonarCloud Quality Gate passed! |
Kudos, SonarCloud Quality Gate passed! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ITs: Summary for Automapper, Ember-MM, Net5
"message": "Refactor this method to add validation of parameter 'converter' before using it.", | ||
"location": { | ||
"uri": "sources\Automapper\src\AutoMapper\Configuration\MappingExpressionBase.cs", | ||
"region": { | ||
"startLine": 477, | ||
"startColumn": 26, | ||
"endLine": 477, | ||
"endColumn": 35 | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lost TP
public void ConvertUsing(ITypeConverter<TSource, TDestination> converter)
{
ConvertUsing(converter.Convert);
}
"region": { | ||
"startLine": 327, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lost TP
public void Configure(TypeMap typeMap)
{
var destMember = DestinationMember;
if(destMember.DeclaringType.ContainsGenericParameters)
{
destMember = typeMap.DestinationSetters.Single(m => m.Name == destMember.Name);
}
var propertyMap = typeMap.FindOrCreatePropertyMapFor(destMember, typeof(TMember) == typeof(object) ? destMember.GetMemberType() : typeof(TMember));
Apply(propertyMap);
}
"location": { | ||
"uri": "sources\Automapper\src\AutoMapper\Execution\ExpressionBuilder.cs", | ||
"region": { | ||
"startLine": 232, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lost foreach
"location": { | ||
"uri": "sources\Automapper\src\AutoMapper\QueryableExtensions\QueryMapperVisitor.cs", | ||
"region": { | ||
"startLine": 231, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks like FP to me, we should have learned NotNull on the 1st line.
public static PropertyMap GetPropertyMap(this IGlobalConfiguration config, MemberInfo sourceMemberInfo, Type destinationMemberType)
{
var typeMap = config.CheckIfMapExists(sourceMemberInfo.DeclaringType, destinationMemberType);
var propertyMap = typeMap.PropertyMaps
.FirstOrDefault(pm => pm.CanResolveValue &&
pm.SourceMember != null && pm.SourceMember.Name == sourceMemberInfo.Name);
if (propertyMap == null)
throw PropertyConfigurationException(typeMap, sourceMemberInfo.Name); // Noncompliant FP
return propertyMap;
}
"uri": "sources\Ember-MM\Ember.Plugins\PluginManager.cs", | ||
"region": { | ||
"startLine": 343, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting FP, we don't support Object.ReferenceEquals
public bool Equals(EmberPlugin other)
{
if (Object.ReferenceEquals(this, other))
return true;
if (Object.ReferenceEquals(other, null))
return false;
return this.Plugin.GetType() == other.Plugin.GetType();
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ITs: Dump for Nancy
"startLine": 40, | ||
"startColumn": 20, | ||
"endLine": 40, | ||
"endColumn": 28 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lost TP: We don't run for operators?
public static implicit operator Func<NancyContext, CancellationToken, Task>(AfterPipeline pipeline)
{
return pipeline.Invoke;
}
"uri": "sources\Nancy\src\Nancy\DefaultTypeCatalog.cs", | ||
"region": { | ||
"startLine": 36, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lost TP: Delegate invocation, or nested lambda
public IReadOnlyCollection<Type> GetTypesAssignableTo(Type type, TypeResolveStrategy strategy)
{
return this.cache.GetOrAdd(type, t => this.GetTypesAssignableTo(type)).Where(strategy.Invoke).ToArray();
}
"uri": "sources\Nancy\src\Nancy\Owin\NancyMiddleware.cs", | ||
"region": { | ||
"startLine": 54, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting FP, there is ??
null check before
public static MidFunc UseNancy(NancyOptions options = null)
{
options = options ?? new NancyOptions();
options.Bootstrapper.Initialise();
"startLine": 32, | ||
"startColumn": 129, | ||
"endLine": 32, | ||
"endColumn": 137 | ||
} | ||
} | ||
}, | ||
{ | ||
"id": "S3900", | ||
"message": "Refactor this method to add validation of parameter 'response' before using it.", | ||
"location": { | ||
"uri": "sources\Nancy\src\Nancy.Testing\BrowserResponseExtensions.cs", | ||
"region": { | ||
"startLine": 35, | ||
"startColumn": 18, | ||
"endLine": 35, | ||
"endColumn": 26 | ||
} | ||
} | ||
}, | ||
{ | ||
"id": "S3900", | ||
"message": "Refactor this method to add validation of parameter 'response' before using it.", | ||
"location": { | ||
"uri": "sources\Nancy\src\Nancy.Testing\BrowserResponseExtensions.cs", | ||
"region": { | ||
"startLine": 37, | ||
"startColumn": 114, | ||
"endLine": 37, | ||
"endColumn": 122 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is interesting method - FPs and FNs
public static void ShouldHaveRedirectedTo(this BrowserResponse response, string location, StringComparison stringComparer = StringComparison.Ordinal)
{
var validRedirectStatuses = new[]
{
HttpStatusCode.MovedPermanently,
HttpStatusCode.SeeOther,
HttpStatusCode.TemporaryRedirect
};
if (!validRedirectStatuses.Any(x => x == response.StatusCode)) // FN here due to lambda
{
throw new AssertException(
string.Format("Status code should be one of 'MovedPermanently, SeeOther, TemporaryRedirect', but was {0}.", response.StatusCode));
}
if (!response.Headers["Location"].Equals(location, stringComparer)) // TP considering the lambda not being supported
{
throw new AssertException(string.Format("Location should have been: {0}, but was {1}", location, response.Headers["Location"])); // FP here, the `if` should have learned NotNull
}
}
@@ -19,19 +19,6 @@ | |||
"location": { | |||
"uri": "sources\Nancy\src\Nancy.Validation.DataAnnotations\DataAnnotationsValidatorAdapter.cs", | |||
"region": { | |||
"startLine": 48, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lost TP, delegate reference
public virtual IEnumerable<ModelValidationRule> GetRules(ValidationAttribute attribute, PropertyDescriptor descriptor)
{
yield return new ModelValidationRule(ruleType, attribute.FormatErrorMessage, new [] { descriptor == null ? string.Empty : descriptor.Name });
}
"uri": "sources\Nancy\src\Nancy.ViewEngines.DotLiquid\DotLiquidViewEngine.cs", | ||
"region": { | ||
"startLine": 90, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This 2nd issue looks like FP, the 1st dereference should have learned NotNull.
public Response RenderView(ViewLocationResult viewLocationResult, dynamic model, IRenderContext renderContext)
{
Template parsed;
Hash hashedModel;
HttpStatusCode status;
try
{
// Set the parsed template
parsed = renderContext.ViewCache.GetOrAdd(
viewLocationResult,
x =>
{
using (var reader = viewLocationResult.Contents.Invoke())
return Template.Parse(reader.ReadToEnd());
});
hashedModel = Hash.FromAnonymousObject(new
{
Model = new DynamicDrop(model),
ViewBag = new DynamicDrop(renderContext.Context.ViewBag)
});
"message": "Refactor this method to add validation of parameter 'templateContent' before using it.", | ||
"location": { | ||
"uri": "sources\Nancy\src\Nancy.ViewEngines.Markdown\MarkdownViewengineRender.cs", | ||
"region": { | ||
"startLine": 32, | ||
"startColumn": 20, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a TP, but why didn't the one above didn't go away?
public static string RenderMasterPage(string templateContent)
{
var second =
templateContent.Substring( // Old FP? the `IndexOf` below is called first and should have infered `NotNull`
templateContent.IndexOf("<!DOCTYPE html>", StringComparison.OrdinalIgnoreCase), // New TP here
templateContent.IndexOf("<body", StringComparison.OrdinalIgnoreCase));
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ITs: Dump for akka.net
"uri": "sources\akka.net\src\core\Akka\Actor\ActorRef.Extensions.cs", | ||
"region": { | ||
"startLine": 37, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is a new FP, because it raises on extension methods. We generally consider invocations of extension methods safe:
public static IActorRef GetOrElse(this IActorRef actorRef, Func<IActorRef> elseValue)
{
return actorRef.IsNobody() ? elseValue() : actorRef;
}
"uri": "sources\akka.net\src\core\Akka\Actor\ActorRefFactoryShared.cs", | ||
"region": { | ||
"startLine": 55, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a lost FP, that is good.
Also there's interesting FN above it:
public static ActorSelection ActorSelection(string path, ActorSystem system, IActorRef lookupRoot)
{
var provider = ((ActorSystemImpl)system).Provider; // FN
"message": "Refactor this method to add validation of parameter 'extension' before using it.", | ||
"location": { | ||
"uri": "sources\akka.net\src\core\Akka\Actor\Internal\ActorSystemImpl.cs", | ||
"region": { | ||
"startLine": 338, | ||
"startColumn": 34, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This FP is interesting, because it's inside lambda after a null check in outer method
public override object RegisterExtension(IExtensionId extension)
{
if (extension == null) return null;
_extensions.GetOrAdd(extension.ExtensionType, t => new Lazy<object>(() => extension.CreateExtension(this), LazyThreadSafetyMode.ExecutionAndPublication));
return extension.Get(this);
}
"message": "Refactor this constructor to avoid using members of parameter 'system' because it could be null.", | ||
"location": { | ||
"uri": "sources\akka.net\src\core\Akka\Serialization\NewtonSoftJsonSerializer.cs", | ||
"region": { | ||
"startLine": 162, | ||
"startColumn": 37, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FP after a null check
public NewtonSoftJsonSerializer(ExtendedActorSystem system, NewtonSoftJsonSerializerSettings settings)
: base(system)
{
Settings = new JsonSerializerSettings
{
PreserveReferencesHandling = settings.PreserveObjectReferences
? PreserveReferencesHandling.Objects
: PreserveReferencesHandling.None,
NullValueHandling = NullValueHandling.Ignore,
DefaultValueHandling = DefaultValueHandling.Ignore,
MissingMemberHandling = MissingMemberHandling.Ignore,
ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor,
TypeNameHandling = settings.EncodeTypeNames
? TypeNameHandling.All
: TypeNameHandling.None,
};
if (system != null)
{
var settingsSetup = system.Settings.Setup.Get<NewtonSoftJsonSerializerSetup>()
.GetOrElse(NewtonSoftJsonSerializerSetup.Create(s => {}));
settingsSetup.ApplySettings(Settings);
}
"message": "Refactor this constructor to avoid using members of parameter 'system' because it could be null.", | ||
"location": { | ||
"uri": "sources\akka.net\src\core\Akka\Serialization\Serialization.cs", | ||
"region": { | ||
"startLine": 175, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FP inside foreach?
public Serialization(ExtendedActorSystem system)
{
System = system;
_nullSerializer = new NullSerializer(system);
AddSerializer("null", _nullSerializer);
var serializersConfig = system.Settings.Config.GetConfig("akka.actor.serializers").AsEnumerable().ToList(); // True positive here
var serializerBindingConfig = system.Settings.Config.GetConfig("akka.actor.serialization-bindings").AsEnumerable().ToList();
var serializerSettingsConfig = system.Settings.Config.GetConfig("akka.actor.serialization-settings");
_serializerDetails = system.Settings.Setup.Get<SerializationSetup>()
.Select(x => x.CreateSerializers(system)).GetOrElse(ImmutableHashSet<SerializerDetails>.Empty);
foreach (var kvp in serializersConfig)
{
var serializerTypeName = kvp.Value.GetString();
var serializerType = Type.GetType(serializerTypeName);
if (serializerType == null)
{
system.Log.Warning("The type name for serializer '{0}' did not resolve to an actual Type: '{1}'", kvp.Key, serializerTypeName); // FP here, was the state lost after AddSerializer below?
continue;
}
var serializerConfig = serializerSettingsConfig.GetConfig(kvp.Key);
var serializer = !serializerConfig.IsNullOrEmpty()
? (Serializer)Activator.CreateInstance(serializerType, system, serializerConfig)
: (Serializer)Activator.CreateInstance(serializerType, system);
AddSerializer(kvp.Key, serializer);
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, the changed ITs are expected at this stage
Part of #6793
Task 4