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

S3900: ShouldExecute method #6942

Merged
merged 39 commits into from
Mar 28, 2023

Conversation

zsolt-kolbay-sonarsource
Copy link
Contributor

Part of #6793
Task 4

Copy link
Contributor

@antonioaversa antonioaversa left a 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.

Copy link
Contributor

@Tim-Pohlmann Tim-Pohlmann left a 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.

Copy link
Contributor

@martin-strecker-sonarsource martin-strecker-sonarsource left a 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":

#6942 (comment)

&& (IsRelevantMethod(Node) || IsRelevantPropertyAccessor(Node));

static bool IsRelevantMethod(SyntaxNode node) =>
node is BaseMethodDeclarationSyntax { ParameterList.Parameters.Count: > 0 } method

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?

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?

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.

Copy link
Contributor Author

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(

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 not p?.Invocation();
  • p.PropertyAccess, but not p?.PropertyAccess;
  • p[0]; but not p?[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 an await? p language proposal also Champion "Null-conditional await" dotnet/csharplang#35
  • foreach(var i in p)
  • throw p
  • p + p No. I think all unary and binary operators like + or ! are okay
  • M(p) No. Passing around is okay.

Is there something else?
I think in all cases we just need to look at the parent.

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
}

Copy link
Contributor Author

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?

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?

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.

Copy link
Contributor Author

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

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

Copy link
Contributor

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)

Copy link
Contributor Author

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) =>

Choose a reason for hiding this comment

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

Suggested change
static bool HasNoDeclaredAccessabilityModifier(SyntaxTokenList modifiers) =>
static bool HasNoDeclaredAccessibilityModifier(SyntaxTokenList modifiers) =>

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Choose a reason for hiding this comment

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

image

Copy link
Contributor Author

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.

@sonarcloud
Copy link

sonarcloud bot commented Mar 27, 2023

Kudos, SonarCloud Quality Gate passed!    Quality Gate passed

Bug A 0 Bugs
Vulnerability A 0 Vulnerabilities
Security Hotspot A 0 Security Hotspots
Code Smell A 0 Code Smells

No Coverage information No Coverage information
No Duplication information No Duplication information

@sonarcloud
Copy link

sonarcloud bot commented Mar 27, 2023

Kudos, SonarCloud Quality Gate passed!    Quality Gate passed

Bug A 0 Bugs
Vulnerability A 0 Vulnerabilities
Security Hotspot A 0 Security Hotspots
Code Smell A 0 Code Smells

100.0% 100.0% Coverage
0.0% 0.0% Duplication

Copy link
Contributor

@pavel-mikula-sonarsource pavel-mikula-sonarsource left a 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

Comment on lines -460 to -468
"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
}
Copy link
Contributor

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,
Copy link
Contributor

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,
Copy link
Contributor

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,
Copy link
Contributor

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;
        }

Comment on lines +7 to +9
"uri": "sources\Ember-MM\Ember.Plugins\PluginManager.cs",
"region": {
"startLine": 343,
Copy link
Contributor

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();
            }

Copy link
Contributor

@pavel-mikula-sonarsource pavel-mikula-sonarsource left a 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

Comment on lines -9 to -12
"startLine": 40,
"startColumn": 20,
"endLine": 40,
"endColumn": 28
Copy link
Contributor

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;
        }

Comment on lines -735 to -737
"uri": "sources\Nancy\src\Nancy\DefaultTypeCatalog.cs",
"region": {
"startLine": 36,
Copy link
Contributor

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();
        }

Comment on lines +2347 to +2349
"uri": "sources\Nancy\src\Nancy\Owin\NancyMiddleware.cs",
"region": {
"startLine": 54,
Copy link
Contributor

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();

Comment on lines +269 to +298
"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
Copy link
Contributor

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,
Copy link
Contributor

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 });
        }

Comment on lines +20 to +22
"uri": "sources\Nancy\src\Nancy.ViewEngines.DotLiquid\DotLiquidViewEngine.cs",
"region": {
"startLine": 90,
Copy link
Contributor

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)
                });

Comment on lines +44 to +49
"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,
Copy link
Contributor

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));

Copy link
Contributor

@pavel-mikula-sonarsource pavel-mikula-sonarsource left a 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

Comment on lines +202 to +204
"uri": "sources\akka.net\src\core\Akka\Actor\ActorRef.Extensions.cs",
"region": {
"startLine": 37,
Copy link
Contributor

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;
        }

Comment on lines -228 to -230
"uri": "sources\akka.net\src\core\Akka\Actor\ActorRefFactoryShared.cs",
"region": {
"startLine": 55,
Copy link
Contributor

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

Comment on lines +889 to +894
"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,
Copy link
Contributor

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);
        }

Comment on lines +2878 to +2883
"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,
Copy link
Contributor

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);
            }

Comment on lines +2943 to +2947
"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,
Copy link
Contributor

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);
            }

Copy link
Contributor

@pavel-mikula-sonarsource pavel-mikula-sonarsource left a 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

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.

5 participants