本规范以Google C++ Style Guide为基础,参考华为通用编码规范、安全编程规范,并结合业界共识整理而成,参与MindSpore社区的开发者首先需要遵循本规范内容,其余遵循Google C++ Style Guide规范; 如果对规则异议,建议提交issue并说明理由,经MindSpore社区运营团队评审后可接纳并修改生效;
MindSpore开源社区
C++文件使用小写+下划线的方式命名,以.cc
作为后缀,头文件以.h
作为后缀,单元测试文件以_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_
开头,后接变量名称,如:void set_count(int count) {this->count_ = count;}
void FindPattern(...);
如果超过120个字符,请选择合理的方式进行换行。
&
、*
跟随变量名,另外一边留空格 char *c;
const std::string &str;
// 即使if分支代码只有一行,也必须使用大括号
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;
所有h文件、cc文件,均需包含如下版权声明:
/**
* 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.
*/
关于版权说明,应注意: 2020年新建的文件,应该是
Copyright 2020 Huawei Technologies Co., Ltd
2019年创建年份,2020年修改年份,应该是Copyright 2019-2020 Huawei Technologies Co., Ltd
//
,而不是/**/
// this is multi-
// line comment
int foo; // this single-line comment
并不是所有的函数都需要函数头注释,函数尽量通过函数名自注释,按需写函数头注释;函数原型无法表达的,却又希望读者知道的信息,才需要加函数头注释辅助说明。 不要写无用、信息冗余的函数头,函数头注释内容可选,但不限于:功能说明、返回值,性能约束、用法、内存约定、算法实现、可重入的要求等。
FooBar *Func(const std::string &in);
虽然大多数现代编译器在许多情况下可以对无效或从不执行的代码告警,响应告警应识别并清除告警; 应该主动识别无效的语句或表达式,并将其从代码中删除。
// 错误示范
try {
// do something;
} catch (...) {
// do something;
}
// 正确示范
try {
// do something;
} catch (const std::bad_alloc &e) {
// do something;
}
// 正确示范
#include <cstdlib>
// 错误示范
#include <stdlib.h>
头文件循环依赖,指a.h包含b.h,b.h包含c.h,c.h包含a.h之类导致任何一个头文件修改,都导致所有包含了a.h/b.h/c.h的代码全部重新编译一遍。 头文件循环依赖直接体现了架构设计上的不合理,可通过优化架构去避免。
// 正确示范
using FooBarPtr = std::shared_ptr<FooBar>;
// 错误示范
typedef std::shared_ptr<FooBar> FooBarPtr;
namespace foo {
int kGlobalVar;
class Bar {
private:
static int static_member_var_;
};
}
// 正确示范
if (ret != SUCCESS) {
...
}
// 错误示范
if (SUCCESS != ret) {
...
}
// 正确示范
if (cond1 || (cond2 && cond3)) {
...
}
// 错误示范
if (cond1 || cond2 && cond3) {
...
}
memcpy_s
、memset_s
初始化非POD对象// 错误示范
const char * a = std::to_string(12345).c_str();
// 正确示范
std::shared_ptr<FooBar> foo = std::make_shared<FooBar>();
// 错误示范
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;
};
// 正确示范
{
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 &
,出参用 T *
bool Func(const std::string &in, FooBar *out1, FooBar *out2);
// 正确示范
bool Func(const FooBar &in);
// 错误示范
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(...); // 调用kill强行终止其他进程(如kill -9),会导致其他进程的资源得不到清理。
TerminateProcess(); // 调用erminateProcess函数强行终止其他进程,会导致其他进程的资源得不到清理。
pthread_exit(); // 严禁在线程内主动终止自身线程,线程函数在执行完毕后会自动、安全地退出;
ExitThread(); // 严禁在线程内主动终止自身线程,线程函数在执行完毕后会自动、安全地退出;
exit(); // main函数以外,禁止任何地方调用,程序应该安全退出;
ExitProcess(); // main函数以外,禁止任何地方调用,程序应该安全退出;
abort(); // 禁用,abort会导致程序立即退出,资源得不到清;
}
C标准库rand()函数生成的是伪随机数,请使用/dev/random生成随机数。
string类是C++内部定义的字符串管理类,如果口令等敏感信息通过string进行操作,在程序运行过程中,敏感信息可 能会散落到内存的各个地方,并且无法清0。
以下代码,Foo函数中获取密码,保存到string变量password中,随后传递给VerifyPassword函数,在这个过程中, password实际上在内存中出现了2份。
int VerifyPassword(string password) {
//...
}
int Foo() {
string password = GetPassword();
VerifyPassword(password);
...
}
应该使用char或unsigned char保存敏感信息,如下代码:
int VerifyPassword(const char *password) {
//...
}
int Foo() {
char password[MAX_PASSWORD] = {0};
GetPassword(password, sizeof(password));
VerifyPassword(password);
...
}
口令、密钥等敏感信息使用完毕后立即清0,避免被攻击者获取。
内存分配失败后,那么后续的操作存在未定义的行为风险。比如malloc申请失败返回了空指针,对空指针的解引用是一种未定义行为。
malloc、new分配出来的内存没有被初始化为0,要确保内存被引用前是被初始化的。
随着参数的不同,realloc函数行为也不同,这不是一个设计良好的函数。虽然在编码中提供了一些便利性,但是却极易引发各种bug。
POSIX和C99均未定义alloca()的行为,在有些平台下不支持该函数,使用alloca会降低程序的兼容性和可移植性,该函数在栈帧里申请内存,申请的大小很可能超过栈的边界,影响后续的代码执行。
当文件路径来自外部数据时,需要先将文件路径规范化,如果没有作规范化处理,攻击者就有机会通过恶意构造文件路径进行文件的越权访问: 例如,攻击者可以构造“../../../etc/passwd”的方式进行任意文件访问。 在linux下,使用realpath函数,在windows下,使用PathCanonicalize函数进行文件路径的规范化。
【错误代码示例】 以下代码从外部获取到文件名称,拼接成文件路径后,直接对文件内容进行读取,导致攻击者可以读取到任意文件的内容:
char *fileName = GetMsgFromRemote();
...
sprintf_s(untrustPath, sizeof(untrustPath), "/tmp/%s", fileName);
char *text = ReadFileContent(untrustPath); // Bad,读取前未检查untrustPath是否允许访问
【正确代码示例】 正确的做法是,对路径进行规范化后,再判断路径是否是本程序所认为的合法的路径:
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,检查文件位置是否正确
//error
...
}
char *text = ReadFileContent(untrustPath);
【例外】 运行于控制台的命令行程序,通过控制台手工输入文件路径,可以作为本建议例外。
程序的临时文件应当是程序自身独享的,任何将自身临时文件置于共享目录的做法,将导致其他共享用户获得该程序的额外信息,产生信息泄露。因此,不要在任何共享目录创建仅由程序自身使用的临时文件。 如Linux下的/tmp目录是一个所有用户都可以访问的共享目录,不应在该目录下创建仅由程序自身使用的临时文件。
安全函数类型 | 说明 | 备注 |
---|---|---|
xxx_s | Huawei Secure C库的安全函数API | 集成Huawei Secure C库即可使用 |
xxx_sp | Huawei Secure C库的安全函数性能优化API(宏实现) | 性能优化宏接口对count、destMax、strSrc为常量时有优化效果,如果是变量则优化效果不明显.宏接口使用策略:默认使用_s接口,在性能敏感的调用点受限使用_sp接口,受限场景如下: a) memset_sp/memcpy_sp使用场景:destMax和count为常量 b) strcpy_sp/strcat_sp使用场景:destMax为常量且strSrc为字面量 c) strncpy_sp/strncat_sp使用场景:destMax和count为常量且strSrc为字面量 |
函数类别 | 危险函数 | 安全替代函数 |
---|---|---|
内存拷贝 | memcpy或bcopy | memcpy_s |
wmemcpy | wmemcpy_s | |
memmove | memmove_s | |
wmemmove | wmemmove_s | |
字符串拷贝 | strcpy | strcpy_s |
wcscpy | wcscpy_s | |
strncpy | strncpy_s | |
wcsncpy | wcsncpy_s | |
字符串串接 | strcat | strcat_s |
wcscat | wcscat_s | |
strncat | strncat_s | |
wcsncat | wcsncat_s | |
格式化输出 | sprintf | sprintf_s |
swprintf | swprintf_s | |
vsprintf | vsprintf_s | |
vswprintf | vswprintf_s | |
snprintf | snprintf_s 或 snprintf_truncated_s | |
vsnprintf | vsnprintf_s 或 vsnprintf_truncated_s | |
格式化输入 | 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 | |
标准输入流输入 | gets | gets_s |
内存初始化 | 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))
使用宏重命名安全函数不利于静态代码扫描工具(非编译型)定制针对安全函数误用的规则,同时,由于命名风格多 样,也不利于提示代码开发者函数的真实用途,容易造成对代码的误解及重命名安全函数的误用。重命名安全函数不 会改变安全函数本身的检查能力。
void MemcpySafe(void *dest, unsigned int destMax, const void *src, unsigned int count) {
...
}
原则上,如果使用了安全函数,需要进行返回值检查。如果返回值!=EOK, 那么本函数一般情况下应该立即返回,不 能继续执行。 安全函数有多个错误返回值,如果安全函数返回失败,在本函数返回前,根据产品具体场景,可以做如下操作(执行 其中一个或多个措施): (1)记录日志 (2)返回错误 (3)调用abort立即退出程序
{
...
err = memcpy_s(destBuff, destMax, src, srcLen);
if (err != EOK) {
MS_LOG("memcpy_s failed, err = %d\n", err);
return FALSE;
}
...
}
信号处理例程应尽可能简化。在信号处理例程中如果调用非异步安全函数,可能会导致函数的执行不符合预期的结 果。 下列代码中的信号处理程序通过调用fprintf()写日志,但该函数不是异步安全函数。
void Handler(int sigNum) {
...
fprintf(stderr, "%s\n", info);
}
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。