This document is developed based on Google C++ Style Guide, Huawei C++ Coding Style Guide, Huawei secure coding standards, and industry consensus. To participate in the MindSpore community, please comply with this style guide, and then the Google C++ Style Guide.
If you disagree with the rules, you are advised to submit an issue and provide reasons. The issue can take effect after being reviewed, accepted, and modified by the MindSpore community operation team.
MindSpore open source community
C++ files are named in the format of lowercase letters + underscores (_). The file name extension is .cc
. The header file name extension is .h
. The unit test file name ends with _test.cc
.
a_b_c.h
a_b_c.cc
a_b_c_test.cc
void FooBar(int func_param) {
int local_var;
}
class FooBar {
public:
int mamber_var_;
};
#define MS_LOG(...)
const int kDaysInAWeek = 7;
enum UrlTableErrors {
kOk = 0,
kErrorOutOfMemory,
kErrorMalformedInput,
};
int count() {return this->count_;}
set_
and followed by variables or parameters name, such as:void set_count(int count) {this->count_ = count;}
void FindPattern(...);
If a line contains more than 120 characters, start a new line properly.
&
and *
and place a space on the other side char *c;
const std::string &str;
// Even if the if branch code is within one line, braces are required.
if (cond) {
single line code;
}
int a = a_very_long_expression +
a_very_very_long_expression +
a_very_very_very_long_expression;
a = 1;
b = 2;
c = 3;
All .h and .cc files must contain the following copyright statements:
/**
* Copyright 2019 Huawei Technologies Co., Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
Notes:
Files created in 2020 should containCopyright 2020 Huawei Technologies Co., Ltd
.
Files created in 2019 and modified in 2020 should containCopyright 2019-2020 Huawei Technologies Co., Ltd
.
//
, not /**/
// this is multi-
// line comment
int foo; // this single-line comment
Not all functions require header comments. You are advised to use the name of the function as its comment and write header comments if there is the need. For the information that cannot be expressed by the function prototype but is expected to be known by readers, function header comments are required.
Do not write useless or redundant function headers. The function header comments are optional, including but not limited to function description, return values, performance constraints, usage, memory conventions, algorithm implementation, and reentrant requirements.
FooBar *Func(const std::string &in);
Although most modern compilers in many cases can alert you to invalid or never executed code, responding alarms should be identified and cleared.
Identify and delete invalid statements or expressions from the code.
// Incorrect
try {
// do something;
} catch (...) {
// do something;
}
// Correct
try {
// do something;
} catch (const std::bad_alloc &e) {
// do something;
}
// Correct
#include <cstdlib>
// Incorrect
#include <stdlib.h>
An example of cyclic dependency (also known as circular dependency) is: a.h contains b.h, b.h contains c.h, and c.h contains a.h. If any of these header files is modified, all code containing a.h, b.h, and c.h needs to be recompiled.
The cyclic dependency of header files reflects an obviously unreasonable architecture design, which can be avoided through optimization.
// Correct
using FooBarPtr = std::shared_ptr<FooBar>;
// Incorrect
typedef std::shared_ptr<FooBar> FooBarPtr;
namespace foo {
int kGlobalVar;
class Bar {
private:
static int static_member_var_;
};
}
// Correct
if (ret != SUCCESS) {
...
}
// Incorrect
if (SUCCESS != ret) {
...
}
// Correct
if (cond1 || (cond2 && cond3)) {
...
}
// Incorrect
if (cond1 || cond2 && cond3) {
...
}
memcpy_s
or memset_s
to initialize non-POD objects// Incorrect
const char * a = std::to_string(12345).c_str();
unique_ptr
to shared_ptr
shared_ptr
by using std::make_shared
instead of new
// Correct
std::shared_ptr<FooBar> foo = std::make_shared<FooBar>();
// Incorrect
std::shared_ptr<FooBar> foo(new FooBar());
int ParseMsg(BYTE *msg, size_t msgLen) {
...
}
const int kSize = 5;
int *number_array = new int[kSize];
int *number = new int();
...
delete[] number_array;
number_array = nullptr;
delete number;
number = nullptr;
class Base {
public:
virtual void Func();
};
class Derived : public Base {
public:
void Func() override;
};
class FinalDerived : public Derived {
public:
void Func() final;
};
// Correct
{
std::lock_guard<std::mutex> lock(mutex_);
...
}
{
int local_var = 1;
auto func = [&]() { ...; std::cout << local_var << std::endl; };
thread_pool.commit(func);
}
bool Func(const std::string &in, FooBar *out1, FooBar *out2);
const T &
as the input parameter and T *
as the output parameter for function transfer bool Func(const std::string &in, FooBar *out1, FooBar *out2);
// Correct
bool Func(const FooBar &in);
// Incorrect
bool Func(std::shared_ptr<FooBar> in);
class Foo {
public:
explicit Foo(shared_ptr<T> x):x_(std::move(x)){}
private:
shared_ptr<T> x_;
};
explicit Foo(int x); //good :white_check_mark:
explicit Foo(int x, int y=0); //good :white_check_mark:
Foo(int x, int y=0); //bad :x:
explicit Foo(int x, int y); //bad :x:
class Foo {
private:
Foo(const Foo&) = default;
Foo& operator=(const Foo&) = default;
Foo(Foo&&) = delete;
Foo& operator=(Foo&&) = delete;
};
{
Kill(...); // If you invoke kill to forcibly terminate other processes (such as kill -9), the resources of other processes cannot be cleared.
TerminateProcess(); // If you call the erminateProcess function to forcibly terminate other processes, the resources of other processes cannot be cleared.
pthread_exit(); // Do not terminate a thread. The thread functions will exit automatically and safely after the execution is complete.
ExitThread(); // Do not terminate a thread. The thread functions will exit automatically and safely after the execution is complete.
exit(); // Do not call any function except the main function. The program must exit safely.
ExitProcess(); // Do not call any function except the main function. The program must exit safely.
abort(); //Forbidden. If abort is used, the program exits immediately and resources cannot be cleared.
}
The rand() function in the C standard library generates pseudo-random numbers. To generate random numbers, use /dev/random.
The string class is a character string management class defined in C++. If sensitive information such as passwords is operated through the string class, the sensitive information can be
scattered in various places of the memory and cannot be cleared.
In the following code, the Foo function obtains the password, saves it to the string variable password, and then transfers it to the VerifyPassword function. In this process,
two copies of the password exist in the memory.
int VerifyPassword(string password) {
//...
}
int Foo() {
string password = GetPassword();
VerifyPassword(password);
...
}
Sensitive information must be stored using char or unsigned char. For example:
int VerifyPassword(const char *password) {
//...
}
int Foo() {
char password[MAX_PASSWORD] = {0};
GetPassword(password, sizeof(password));
VerifyPassword(password);
...
}
Sensitive information, such as passwords and keys, must be cleared immediately after being used to prevent attackers from obtaining the information.
If the memory allocation fails, the subsequent operations may have undefined behavior risks. For example, if malloc fails to be applied for and a null pointer is returned, dereference of the null pointer is an undefined behavior.
The memory allocated by malloc and new is not initialized to 0. Ensure that the memory is initialized before being referenced.
The behavior of the realloc function varies with parameters. This is not a well-designed function. Although it provides some convenience in coding, it can easily cause various bugs.
Neither POSIX nor C99 defines the alloca() behavior. Some platforms do not support this function. Using alloca() reduces program compatibility and portability. This function requests memory in the stack frame. The requested size may exceed the stack boundary, affecting code execution.
A file path that comes from external data must be canonicalized first. If the file path is not canonicalized, attackers can construct a malicious file path to access the file without authorization.
For example, an attacker can construct “../../../etc/passwd” to access any file.
Use the realpath() function in Linux and the PathCanonicalize() function in Windows for file path canonicalization.
[Noncompliant Code Example]
The following code obtains the file name from an external system, concatenates the file name into a file path, and directly reads the file content. As a result, the attacker can read the content of any file.
char *fileName = GetMsgFromRemote();
...
sprintf_s(untrustPath, sizeof(untrustPath), "/tmp/%s", fileName);
char *text = ReadFileContent(untrustPath); // Bad: Did not check whether the untrustPath can be accessed before the data is read.
[Compliant Code Example]
Canonicalize the file path and then check whether the path is valid in the program.
char *fileName = GetMsgFromRemote();
...
sprintf_s(untrustPath, sizeof(untrustPath), "/tmp/%s", fileName);
char path[PATH_MAX] = {0};
if (realpath(untrustPath, path) == NULL) {
//error
...
}
if (!IsValidPath(path)) { // Good: Check whether the file location is correct.
//error
...
}
char *text = ReadFileContent(untrustPath);
Exceptions:
Command line programs that run on the console, or file paths that are manually entered on the console are exempted from this rule.
Temporary files of a program must be exclusively used by itself. Otherwise, other users of the shared directory may obtain additional information about the program, resulting in information leakage. Therefore, do not create temporary files that should be used only by the program itself in any shared directory.
For example, the /tmp directory in Linux is a shared directory that all users can access. Do not create temporary files that should be used only by the program itself in this directory.
Secure Function Type | Description | Remarks |
---|---|---|
xxx_s | Secure function API of Huawei Secure C library | It can be used when the Huawei Secure C library is integrated. |
xxx_sp | API of Huawei Secure C library with optimized secure function performance (macro implementation) | If count, destMax, and strSrc are constants, the performance-optimized macro interface displays its effect. If they are variables, the performance optimization effect is not obvious. The macro interface usage policy is as follows: The _s interface is used by default. The _sp interface is restricted in performance-sensitive call sites. The restriction scenarios are as follows: a) memset_sp and memcpy_sp: destMax and count are constants. b) strcpy_sp or strcat_sp: destMax is a constant and strSrc is a literal. c) strncpy_sp or strncat_sp: destMax and count are constants and strSrc is a literal. |
Function Type | Dangerous Function | Secure Surrogate Function |
---|---|---|
Memory copy | memcpy or bcopy | memcpy_s |
wmemcpy | wmemcpy_s | |
memmove | memmove_s | |
wmemmove | wmemmove_s | |
String copy | strcpy | strcpy_s |
wcscpy | wcscpy_s | |
strncpy | strncpy_s | |
wcsncpy | wcsncpy_s | |
Character string concatenation | strcat | strcat_s |
wcscat | wcscat_s | |
strncat | strncat_s | |
wcsncat | wcsncat_s | |
Format output | sprintf | sprintf_s |
swprintf | swprintf_s | |
vsprintf | vsprintf_s | |
vswprintf | vswprintf_s | |
snprintf | snprintf_s or snprintf_truncated_s | |
vsnprintf | vsnprintf_s or vsnprintf_truncated_s | |
Format input | scanf | scanf_s |
wscanf | wscanf_s | |
vscanf | vscanf_s | |
vwscanf | vwscanf_s | |
fscanf | fscanf_s | |
fwscanf | fwscanf_s | |
vfscanf | vfscanf_s | |
vfwscanf | vfwscanf_s | |
sscanf | sscanf_s | |
swscanf | swscanf_s | |
vsscanf | vsscanf_s | |
vswscanf | vswscanf_s | |
Standard input stream input | gets | gets_s |
Memory initialization | memset | memset_s |
#define XXX_memcpy_s memcpy_s
#define SEC_MEM_CPY memcpy_s
#define XX_memset_s(dst, dstMax, val, n) memset_s((dst), (dstMax), (val), (n))
Using macros to rename secure functions does not help static code scanning tools (non-compiled) customize rules for the misuse of secure functions. In addition, there are various naming styles.
In addition, it is not conducive to reminding the code developer of the real usage of functions, and may easily cause misunderstanding of the code and misuse of the renamed secure functions. Renaming secure functions will not
affect the checking capability of the secure functions.
void MemcpySafe(void *dest, unsigned int destMax, const void *src, unsigned int count) {
...
}
In principle, if a secure function is used, its return value must be checked. If the return value is ! = EOK, this function should be returned immediately,
and cannot be continued.
A secure function may have multiple erroneous return values. If a secure function returns a failure, perform the following operations (one or more) based on specific product scenario before it is returned
:
(1) Record logs.
(2) Return an error.
(3) Call abort to exit the program immediately.
{
...
err = memcpy_s(destBuff, destMax, src, srcLen);
if (err != EOK) {
MS_LOG("memcpy_s failed, err = %d\n", err);
return FALSE;
}
...
}
Signal processing routines should be as simple as possible. If a non-asynchronous secure function is called in a signal processing routine, the execution of the function may not generate expected results.
The signal handler in the following code writes logs by calling fprintf(), but the function is not asynchronous secure function.
void Handler(int sigNum) {
...
fprintf(stderr, "%s\n", info);
}