Modify rule S6147: refactor directory structure, set quickfix and code-snippet language

This commit is contained in:
Arseniy Zaostrovnykh 2024-09-04 16:08:15 +02:00 committed by GitHub
parent d0757e5066
commit 0b9b390141
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 143 additions and 148 deletions

View File

@ -1,3 +1,35 @@
{
"title": "Use discriminated unions or \"std::variant\"",
"type": "CODE_SMELL",
"code": {
"impacts": {
"MAINTAINABILITY": "HIGH"
},
"attribute": "COMPLETE"
},
"status": "ready",
"remediation": {
"func": "Constant\/Issue",
"constantCost": "20min"
},
"tags": [
"clumsy",
"pitfall"
],
"extra": {
"replacementRules": [
],
"legacyKeys": [
]
},
"defaultSeverity": "Critical",
"ruleSpecification": "RSPEC-6147",
"sqKey": "S6147",
"scope": "All",
"defaultQualityProfiles": [
"Sonar way"
],
"quickfix": "infeasible"
}

View File

@ -1 +1,110 @@
include::../rule.adoc[]
== Why is this an issue?
In order to save memory, unions allow you to use the same memory to store objects from a list of possible types as long as one object is stored at a time. Unions are not inherently safe, as they expect you to externally keep track of the type of value they currently hold.
Wrong tracking has the potential to corrupt memory or to trigger undefined behaviors.
A straightforward way to avoid it is storing the information about the currently active alternative along with the union. Here follow suggested patterns to do that:
[source, cpp]
----
typedef int altType1;
typedef float altType2;
// Pattern 1
union alternativesCommonStartingFieldPattern {
struct {
bool isAlt1;
altType1 a1;
} one;
struct {
bool isAlt1;
altType2 a2;
} two;
};
double getValueAsDouble(alternativesCommonStartingFieldPattern *pattern1) {
return pattern1->one.isAlt1?pattern1->one.a1:pattern1->two.a2;
}
----
This pattern uses the fact that when two alternatives of a standard layout union are standard-layout-structs that share a common initial sequence, it is allowed to read this common initial sequence on one alternative even if the other alternative is the one currently active. This is commonly used to limit the number of bits required to store the discriminant.
[source, cpp]
----
// Pattern 2
struct wrappedUnionPattern {
enum {ALTTYPE1, ALTTYPE2} type;
union {
altType1 a1;
altType2 a2;
};
};
double getValueAsDouble(wrappedUnionPattern *pattern2) {
return (pattern2->type==wrappedUnionPattern::ALTTYPE1)?pattern2->a1:pattern2->a2;
}
----
This pattern is more straightforward, and wraps the union inside a structure that will also store the discriminant. Note that in this case, the union itself can be anonymous.
[source, cpp]
----
// Pattern 3 (C++17)
using stdVariantPattern = std::variant<altType1, altType2>;
double getValueAsDouble(stdVariantPattern *pattern3) {
return std::visit([](auto&& alternative) -> double { return alternative;}, *pattern3);
}
----
This pattern relies on {cpp}17s ``++std::variant++`` to store the alternative.
In general, ``++std::variant++`` is:
* Safer as the type of the current value is always known and checked before usage.
* More practical as it can have members of any type, including non trivial types (see S6025). It also supports redundant types, which is useful when alternatives have the same type with different semantic meanings.
* Easier to use as it provides many member/helper functions.
One noticeable difference with unions is that the alternatives in a ``++std::variant++`` do not have a name. You can access them by type or by index, using ``++std::get++`` (throws if the wrong alternative is accessed) or ``++std::get_if++`` (returns a null pointer if the wrong alternative is used). But very often, instead of accessing a specific alternative, visitors are used to distinguish cases of the variant.
This rule raises an issue when unions are used outside of the 3 suggested patterns.
=== Noncompliant code example
[source,cpp,diff-id=1,diff-type=noncompliant]
----
void rawUnion() {
union IntOrDouble { // Noncompliant: union is not wrapped
int i;
double d;
};
IntOrDouble intOrDouble;
intOrDouble.d = 10.5;
}
----
=== Compliant solution
[source,cpp,diff-id=1,diff-type=compliant]
----
struct IntOrChar {
enum { INT, CHAR } tag;
union { // Compliant
int i;
char c;
};
};
void simpleVariant() {
std::variant<int, double> intOrDouble = 10.5; // Compliant
}{code}
----

View File

@ -1,35 +1,2 @@
{
"title": "Use discriminated unions or \"std::variant\"",
"type": "CODE_SMELL",
"code": {
"impacts": {
"MAINTAINABILITY": "HIGH"
},
"attribute": "COMPLETE"
},
"status": "ready",
"remediation": {
"func": "Constant\/Issue",
"constantCost": "20min"
},
"tags": [
"clumsy",
"pitfall"
],
"extra": {
"replacementRules": [
],
"legacyKeys": [
]
},
"defaultSeverity": "Critical",
"ruleSpecification": "RSPEC-6147",
"sqKey": "S6147",
"scope": "All",
"defaultQualityProfiles": [
"Sonar way"
],
"quickfix": "unknown"
}

View File

@ -1,113 +0,0 @@
== Why is this an issue?
In order to save memory, unions allow you to use the same memory to store objects from a list of possible types as long as one object is stored at a time. Unions are not inherently safe, as they expect you to externally keep track of the type of value they currently hold.
Wrong tracking has the potential to corrupt memory or to trigger undefined behaviors.
A straightforward way to avoid it is storing the information about the currently active alternative along with the union. Here follow suggested patterns to do that:
 
----
typedef int altType1;
typedef float altType2;
// Pattern 1
union alternativesCommonStartingFieldPattern {
struct {
bool isAlt1;
altType1 a1;
} one;
struct {
bool isAlt1;
altType2 a2;
} two;
};
double getValueAsDouble(alternativesCommonStartingFieldPattern *pattern1) {
return pattern1->one.isAlt1?pattern1->one.a1:pattern1->two.a2;
}
----
This pattern uses the fact that when two alternatives of a standard layout union are standard-layout-structs that share a common initial sequence, it is allowed to read this common initial sequence on one alternative even if the other alternative is the one currently active. This is commonly used to limit the number of bits required to store the discriminant.
 
----
// Pattern 2
struct wrappedUnionPattern {
enum {ALTTYPE1, ALTTYPE2} type;
union {
altType1 a1;
altType2 a2;
};
};
double getValueAsDouble(wrappedUnionPattern *pattern2) {
return (pattern2->type==wrappedUnionPattern::ALTTYPE1)?pattern2->a1:pattern2->a2;
}
----
This pattern is more straightforward, and wraps the union inside a structure that will also store the discriminant. Note that in this case, the union itself can be anonymous.
----
// Pattern 3 (C++17)
using stdVariantPattern = std::variant<altType1, altType2>;
double getValueAsDouble(stdVariantPattern *pattern3) {
return std::visit([](auto&& alternative) -> double { return alternative;}, *pattern3);
}
----
This pattern relies on {cpp}17s ``++std::variant++`` to store the alternative.
In general, ``++std::variant++`` is:
* Safer as the type of the current value is always known and checked before usage.
* More practical as it can have members of any type, including non trivial types (see S6025). It also supports redundant types, which is useful when alternatives have the same type with different semantic meanings.
* Easier to use as it provides many member/helper functions.
One noticeable difference with unions is that the alternatives in a ``++std::variant++`` do not have a name. You can access them by type or by index, using ``++std::get++`` (throws if the wrong alternative is accessed) or ``++std::get_if++`` (returns a null pointer if the wrong alternative is used). But very often, instead of accessing a specific alternative, visitors are used to distinguish cases of the variant.
This rule raises an issue when unions are used outside of the 3 suggested patterns.
=== Noncompliant code example
[source,text]
----
void rawUnion() {
union IntOrDouble { // Noncompliant: union is not wrapped
int i;
double d;
};
IntOrDouble intOrDouble;
intOrDouble.d = 10.5;
}
----
=== Compliant solution
[source,text]
----
struct IntOrChar {
  enum { INT, CHAR } tag;
  union { // Compliant
    int i;
    char c;
  };
};
void simpleVariant() {
  std::variant<int, double> intOrDouble = 10.5; // Compliant
}{code}
 
----