Modify rule S2095: Expand and adjust for LaYC
## Review A dedicated reviewer checked the rule description successfully for: - [ ] logical errors and incorrect information - [ ] information gaps and missing content - [ ] text style and tone - [ ] PR summary and labels follow [the guidelines](https://github.com/SonarSource/rspec/#to-modify-an-existing-rule)
This commit is contained in:
parent
6c7a0f2fea
commit
1595dcd062
@ -1,43 +1,112 @@
|
|||||||
|
A function call to ``++fopen++`` or ``++open++`` must be matched with a call to ``++fclose++`` or ``++close++``, respectively.
|
||||||
|
|
||||||
== Why is this an issue?
|
== Why is this an issue?
|
||||||
|
|
||||||
A call to the ``++fopen()++``/``++open()++`` function must be matched with a call to ``++fclose()++``/``++close++``. Otherwise, you run the risk of using up all the OS's file handles, which could lock up not just your program but potentially everything on the box.
|
The standard C library provides ``++fopen++`` and the system call ``++open++`` to open and possibly create files.
|
||||||
|
Each call to one of these functions must be matched with a respective call to ``++fclose++`` or ``++close++``.
|
||||||
|
|
||||||
=== Noncompliant code example
|
Failing to close files that have been opened may lead to using up all of the OS's file handles.
|
||||||
|
|
||||||
[source,cpp]
|
|
||||||
|
== What is the potential impact?
|
||||||
|
|
||||||
|
If a program does not properly close or release file handles after using them, it will leak resources.
|
||||||
|
In that case, the program will continue to hold onto file handles even when they are no longer needed, eventually exhausting the available file handles.
|
||||||
|
|
||||||
|
If a program has run out of file handles and tries to open yet another file, the file opening operation will fail.
|
||||||
|
This can result in errors or unexpected behavior in the program.
|
||||||
|
|
||||||
|
The program may not be able to read or write to files anymore, which can cause data loss, corruption, or incomplete operations.
|
||||||
|
In some cases, when a program runs out of file handles, it may crash or hang indefinitely.
|
||||||
|
This can happen if the program does not handle the error condition properly or if it enters an infinite loop trying to open files, for instance.
|
||||||
|
In the worst case, a resource leak can lock up everything that runs on the machine.
|
||||||
|
|
||||||
|
|
||||||
|
== How to fix it
|
||||||
|
|
||||||
|
Make sure that each call to ``++fopen++`` and ``++open++`` has a matching call to ``++fclose++`` and ``++close++``, respectively.
|
||||||
|
|
||||||
|
=== Code examples
|
||||||
|
|
||||||
|
==== Noncompliant code example
|
||||||
|
|
||||||
|
[source,cpp,diff-id=1,diff-type=noncompliant]
|
||||||
----
|
----
|
||||||
int fun() {
|
#include <stdio.h>
|
||||||
FILE *f = fopen("file", "r");
|
#include <stdlib.h>
|
||||||
if (f == NULL) {
|
|
||||||
return -1;
|
int process_file(int print) {
|
||||||
|
FILE *f = fopen("example.txt", "r");
|
||||||
|
if (!f) {
|
||||||
|
perror("fopen() failed");
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
// ...
|
|
||||||
return 0; // Noncompliant, file f has not been closed
|
if (print) {
|
||||||
|
char buffer[256];
|
||||||
|
while (fgets(buffer, 256, f)) {
|
||||||
|
printf("%s", buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0; // Noncompliant: file `f` has not been closed
|
||||||
}
|
}
|
||||||
----
|
----
|
||||||
|
|
||||||
=== Compliant solution
|
==== Compliant solution
|
||||||
|
|
||||||
[source,cpp]
|
[source,cpp,diff-id=1,diff-type=compliant]
|
||||||
----
|
----
|
||||||
int fun() {
|
#include <stdio.h>
|
||||||
FILE *f = fopen("file", "r");
|
#include <stdlib.h>
|
||||||
if (f == NULL) {
|
|
||||||
return -1;
|
int process_file(int print) {
|
||||||
|
FILE *f = fopen("example.txt", "r");
|
||||||
|
if (!f) {
|
||||||
|
perror("fopen() failed");
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
// ...
|
|
||||||
|
if (print) {
|
||||||
|
char buffer[256];
|
||||||
|
while (fgets(buffer, 256, f)) {
|
||||||
|
printf("%s", buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fclose(f);
|
fclose(f);
|
||||||
return 0;
|
return 0; // Compliant: file `f` has been closed
|
||||||
}
|
}
|
||||||
----
|
----
|
||||||
|
|
||||||
|
=== Going the extra mile
|
||||||
|
|
||||||
|
Using {cpp}'s _RAII_ idiom can mitigate unmatched calls to ``++fopen++`` and ``++open++``.
|
||||||
|
|
||||||
|
include::../../../shared_content/cfamily/c_file_io_raii_extra_mile.adoc[]
|
||||||
|
|
||||||
|
With the design shown above, calls to ``++fopen++`` will be automatically matched with a corresponding call to ``++fclose++`` by default.
|
||||||
|
The associated file will be automatically closed when the ``++File++`` typed file handle object goes out of scope and its destructor is called.
|
||||||
|
However, it is still possible to leak a resource, if ``++File++``'s function member ``++File::release++`` is used inappropriately.
|
||||||
|
If this is a concern, this function member should be removed.
|
||||||
|
|
||||||
|
If falling back to low-level file operations is not necessary, one should prefer ``++std::fstream++``.
|
||||||
|
|
||||||
|
|
||||||
== Resources
|
== Resources
|
||||||
|
|
||||||
* https://cwe.mitre.org/data/definitions/459[MITRE, CWE-459] - Incomplete Cleanup
|
=== Standards
|
||||||
* https://cwe.mitre.org/data/definitions/772[MITRE, CWE-772] - Missing Release of Resource after Effective Lifetime
|
|
||||||
* https://wiki.sei.cmu.edu/confluence/x/vjdGBQ[CERT, FIO04-J.] - Release resources when they are no longer needed
|
* CERT - https://wiki.sei.cmu.edu/confluence/x/vjdGBQ[FIO04-J. Release resources when they are no longer needed]
|
||||||
* https://wiki.sei.cmu.edu/confluence/x/QtUxBQ[CERT, FIO42-C.] - Close files when they are no longer needed
|
* CERT - https://wiki.sei.cmu.edu/confluence/x/QtUxBQ[FIO42-C. Close files when they are no longer needed]
|
||||||
* https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html[Try With Resources]
|
* CWE - https://cwe.mitre.org/data/definitions/459[459 Incomplete Cleanup]
|
||||||
|
* CWE - https://cwe.mitre.org/data/definitions/772[772 Missing Release of Resource after Effective Lifetime]
|
||||||
|
|
||||||
|
=== Related rules
|
||||||
|
|
||||||
|
* S3588 ensures that ``++FILE*++`` typed variables are not accessed after the associated file has been closed
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
ifdef::env-github,rspecator-view[]
|
ifdef::env-github,rspecator-view[]
|
||||||
|
|
||||||
|
@ -38,5 +38,5 @@
|
|||||||
"defaultQualityProfiles": [
|
"defaultQualityProfiles": [
|
||||||
"Sonar way"
|
"Sonar way"
|
||||||
],
|
],
|
||||||
"quickfix": "unknown"
|
"quickfix": "infeasible"
|
||||||
}
|
}
|
||||||
|
@ -46,83 +46,6 @@ The application might just crash, but in the worst case, the application may app
|
|||||||
|
|
||||||
Do not use the value of a pointer to a ``++FILE++`` object after the associated file has been closed.
|
Do not use the value of a pointer to a ``++FILE++`` object after the associated file has been closed.
|
||||||
|
|
||||||
=== Going the extra mile
|
|
||||||
|
|
||||||
Using {cpp}'s _RAII_ idiom can mitigate these "double-close" issues.
|
|
||||||
|
|
||||||
Following this idiom, one would create a class that manages the underlying file by opening it when the object is constructed and closing it when the object is destroyed, effectively using a constructor-destructor pair as a "do-undo"-mechanism.
|
|
||||||
|
|
||||||
An exemplary class that manages a pointer to a file is shown in what follows.
|
|
||||||
|
|
||||||
[source,cpp]
|
|
||||||
----
|
|
||||||
#include <cstdio>
|
|
||||||
#include <fstream>
|
|
||||||
#include <string>
|
|
||||||
#include <utility>
|
|
||||||
|
|
||||||
// Although `std::fstream` should be preferred, if available, a file stream
|
|
||||||
// managed by this `File` class cannot suffer from "double-close" issues.
|
|
||||||
class File {
|
|
||||||
FILE *f;
|
|
||||||
|
|
||||||
public:
|
|
||||||
// Opens file stream on construction.
|
|
||||||
File(std::string const &path, std::string const &modes)
|
|
||||||
: f(fopen(path.c_str(), modes.c_str())) {
|
|
||||||
if (!f) {
|
|
||||||
throw std::ios_base::failure("fopen() failed");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Will close the file stream upon destruction.
|
|
||||||
~File() {
|
|
||||||
// Here we are fine with `std::terminate` being called here in case `close`
|
|
||||||
// throws and exception.
|
|
||||||
close();
|
|
||||||
}
|
|
||||||
// Allow only one owner of a file, disallow copy operations.
|
|
||||||
File(const File &other) = delete;
|
|
||||||
File &operator=(const File &other) = delete;
|
|
||||||
// Moving a file to a different scope shall be allowed.
|
|
||||||
File(File &&other) : f(std::exchange(other.f, nullptr)) {}
|
|
||||||
File &operator=(File &&other) {
|
|
||||||
if (this != &other) {
|
|
||||||
// In case of non-self-assignment, close the currently managed file and
|
|
||||||
// "steal" the other's file.
|
|
||||||
close();
|
|
||||||
f = std::exchange(other.f, nullptr);
|
|
||||||
}
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
// Allow file to be closed explicitly.
|
|
||||||
void close() {
|
|
||||||
if (f != nullptr && fclose(std::exchange(f, nullptr)) == EOF) {
|
|
||||||
throw std::ios_base::failure("fclose() failed");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Allow access to underlying file via `f`.
|
|
||||||
FILE *handle() { return f; }
|
|
||||||
// Release `f`, i.e., stop managing it.
|
|
||||||
FILE *release() { return std::exchange(f, nullptr); }
|
|
||||||
};
|
|
||||||
|
|
||||||
void file_user() {
|
|
||||||
File fh{"example.txt", "r"};
|
|
||||||
FILE *f = fh.handle();
|
|
||||||
// Use `f` for the desired file operation(s).
|
|
||||||
//
|
|
||||||
// The file stream managed by `fh` will be automatically closed when `fh` goes
|
|
||||||
// out of scope at the end of this function.
|
|
||||||
}
|
|
||||||
----
|
|
||||||
|
|
||||||
With the design shown above, it is still possible to "double-close" a file by passing the raw ``++FILE++`` pointer obtained by a call to ``++File::handle++`` to ``++fclose++`` (e.g. ``++fclose(f.handle())++``).
|
|
||||||
However, this design reduces the risk of such occurrence by eliminating the need for manually closing files.
|
|
||||||
If even the reduced possibility of "double-close" is still a concern, the function member ``++File::handle++`` should be removed and any required file operations should be wrapped by the ``++File++`` class.
|
|
||||||
|
|
||||||
If falling back to low-level file operations is not necessary, one should prefer ``++std::fstream++``.
|
|
||||||
|
|
||||||
|
|
||||||
=== Code examples
|
=== Code examples
|
||||||
|
|
||||||
==== Noncompliant code example
|
==== Noncompliant code example
|
||||||
@ -180,6 +103,18 @@ int process_file(int print) {
|
|||||||
}
|
}
|
||||||
----
|
----
|
||||||
|
|
||||||
|
=== Going the extra mile
|
||||||
|
|
||||||
|
Using {cpp}'s _RAII_ idiom can mitigate these "double-close" issues.
|
||||||
|
|
||||||
|
include::../../../shared_content/cfamily/c_file_io_raii_extra_mile.adoc[]
|
||||||
|
|
||||||
|
With the design shown above, it is still possible to "double-close" a file by passing the raw ``++FILE++`` pointer obtained by a call to ``++File::handle++`` to ``++fclose++`` (e.g. ``++fclose(f.handle())++``).
|
||||||
|
However, this design reduces the risk of such occurrence by eliminating the need for manually closing files.
|
||||||
|
If even the reduced possibility of "double-close" is still a concern, the function member ``++File::handle++`` should be removed and any required file operations should be wrapped by the ``++File++`` class.
|
||||||
|
|
||||||
|
If falling back to low-level file operations is not necessary, one should prefer ``++std::fstream++``.
|
||||||
|
|
||||||
|
|
||||||
== Resources
|
== Resources
|
||||||
|
|
||||||
|
65
shared_content/cfamily/c_file_io_raii_extra_mile.adoc
Normal file
65
shared_content/cfamily/c_file_io_raii_extra_mile.adoc
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
Following this idiom, one would create a class that manages the underlying file by opening it when the object is constructed and closing it when the object is destroyed, effectively using a constructor-destructor pair as a "do-undo"-mechanism.
|
||||||
|
|
||||||
|
An exemplary class that manages a pointer to a file is shown in what follows.
|
||||||
|
|
||||||
|
[source,cpp]
|
||||||
|
----
|
||||||
|
#include <cstdio>
|
||||||
|
#include <fstream>
|
||||||
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
// Although `std::fstream` should be preferred, if available, a file stream
|
||||||
|
// managed by this `File` class cannot suffer from "double-close" issues.
|
||||||
|
class File {
|
||||||
|
FILE *f;
|
||||||
|
|
||||||
|
public:
|
||||||
|
// Opens file stream on construction.
|
||||||
|
File(std::string const &path, std::string const &modes)
|
||||||
|
: f(fopen(path.c_str(), modes.c_str())) {
|
||||||
|
if (!f) {
|
||||||
|
throw std::ios_base::failure("fopen() failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Will close the file stream upon destruction.
|
||||||
|
~File() {
|
||||||
|
// Here we are fine with `std::terminate` being called here in case `close`
|
||||||
|
// throws and exception.
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
// Allow only one owner of a file, disallow copy operations.
|
||||||
|
File(const File &other) = delete;
|
||||||
|
File &operator=(const File &other) = delete;
|
||||||
|
// Moving a file to a different scope shall be allowed.
|
||||||
|
File(File &&other) : f(std::exchange(other.f, nullptr)) {}
|
||||||
|
File &operator=(File &&other) {
|
||||||
|
if (this != &other) {
|
||||||
|
// In case of non-self-assignment, close the currently managed file and
|
||||||
|
// "steal" the other's file.
|
||||||
|
close();
|
||||||
|
f = std::exchange(other.f, nullptr);
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
// Allow file to be closed explicitly.
|
||||||
|
void close() {
|
||||||
|
if (f != nullptr && fclose(std::exchange(f, nullptr)) == EOF) {
|
||||||
|
throw std::ios_base::failure("fclose() failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Allow access to underlying file via `f`.
|
||||||
|
FILE *handle() { return f; }
|
||||||
|
// Release `f`, i.e., stop managing it.
|
||||||
|
FILE *release() { return std::exchange(f, nullptr); }
|
||||||
|
};
|
||||||
|
|
||||||
|
void file_user() {
|
||||||
|
File fh{"example.txt", "r"};
|
||||||
|
FILE *f = fh.handle();
|
||||||
|
// Use `f` for the desired file operation(s).
|
||||||
|
//
|
||||||
|
// The file stream managed by `fh` will be automatically closed when `fh` goes
|
||||||
|
// out of scope at the end of this function.
|
||||||
|
}
|
||||||
|
----
|
Loading…
x
Reference in New Issue
Block a user