1 2# 开源编译规范 3 4## 概述 5 6**简介:** 7 8本规范包括C/C++/Java语言编译选项或系统配置,包括语言选项、警告选项、安全选项、总体选项、代码生成选项、架构选项、优化选项、编译宏等。 9 10**范围:** 11 12本规范规定了C/C++/Java语言在编译构建过程中需要添加的编译选项或系统配置,并对这些选项的作用进行了简单说明。此外,规范中对涉及到的例外场景也进行了阐释说明。 13 14无OS(如裸核、BIOS、Bootloader、BSBC等)需遵循的安全编译选项还未制定,本规范暂不做要求,在相关规范发布之前,建议实施栈保护。 15 16对于本规范未描述的例外情况,如果存在争议,可申请仲裁。 17 18**条款组织方式:** 19 20每个条款一般包含标题、级别、描述等组成部分。条款内容中的“正确示例”表示符合该条款要求的例子,“错误示例”表示不符合该条款要求的例子。 21 22**标题:** 23 24描述本条款的内容。 25 26规范条款分为原则和规则两个类别,原则可以评价规则内容制定的好坏并引导规则进行相应的调整;规则是需要遵从或参考的实践。通过标题前的编号标识出条款的类别为原则或者规则。 27 28标题前的编号规则参见《安全工程规范内容总纲》,其中'P'为单词Principle首字母,'G'为单词Guideline的首字母。原则条款的编号规则为P.Number。规则的编号方式为G.Language.Element. Number,其中Language是语言分类,Element为领域知识中关键元素(本规范中对应的一级目录)的英文字母缩略语。Number是从1开始递增的两位阿拉伯数字,不足两位时高位补0。 29 30| Language | Element | 目录 | Language | Element | 目录 | 31|----------|---------|----------|----------|---------|--------------| 32| C&C++ | LANG | 语言选项 | C&C++ | WARN | 警告选项 | 33| C&C++ | SEC | 安全选项 | C&C++ | CDG | 代码生成选项 | 34| C&C++ | OPT | 优化选项 | C&C++ | MD | 架构选项 | 35| C&C++ | OVA | 总体选项 | C&C++ | LNK | 链接选项 | 36| C&C++ | DBG | 调试选项 | C&C++ | PRE | 编译宏 | 37| C&C++ | OTH | 其他 | JAVA | JAVAC | JAVAC | 38| JAVA | MAVEN | MAVEN | | | | 39 40**级别:** 41 42规则类条款分为两个级别:要求、建议。 43 44- 要求:表示产品原则上应该遵从,但可以按照具体产品版本计划和节奏分期实现。 45 46- 建议:表示该条款属于最佳实践,有助于进一步消解风险,产品可结合业务情况考虑是否纳入。 47 48**描述:** 49 50对条款的进一步描述,描述条款的原理,配合正确和错误的代码例子作为示范。有的条款还包含一些规则不适用的例外场景。 51 52## C/C++语言编译选项 53 54### 语言选项 55 56##### G.C&C++.LANG.01 显式设置编译的语言标准 57 58**【级别】** 要求 59 60**【描述】** 按时间先后顺序,常用的ISO C标准包括:"-std=c90","-std=c99","-std=c11",对应的GNU扩展标准为"-std=gnu90","-std=gnu99","-std=gnu11"。 61 62按时间先后顺序,常用的ISO C++标准包括:"-std=c++98","-std=c++11","-std=c++14","-std=c++1z",对应的GNU扩展标准为"-std=gnu++98","-std=gnu++11","-std=gnu++14","-std=gnu++1z"。 63 64"-ansi"对应ISO C标准"-std=c90"和ISO C++标准"-std=c++98"。 65 66GNU扩展标准完全支持对应的ISO标准,并在对应的ISO标准上做了扩展。 67 68"-Wpedantic","-pedantic","-pedantic-errors"等选项用于检查是否严格符合对应的ISO标准,对不符合标准的语法进行警告,GNU扩展语法也可能产生警告。 69 70##### G.C&C++.LANG.02 采用较新的语言标准 71 72**【级别】** 建议 73 74##### G.C&C++.LANG.03 显式设置char的类型:"-fsigned-char"或"-funsigned-char" 75 76**【级别】** 建议 77 78**【描述】**"-fsigned-char":x86环境默认char是signed类型,但是ARM64下,默认char是unsigned类型;编译器适配不同平台后端的指令集,故为了考虑平台兼容性,使用该选项。 79 80部分产品可能默认char等效于unsignd char,这种情况下建议使用选项"-funsigned-char"显式设置。 81 82##### G.C&C++.LANG.04 对C++语言,禁止使用"-fpermissive"选项 83 84**【级别】** 要求 85 86**【描述】** 使用"-fpermissive"选项将C++代码中不符合标准的语法error降级成warning。不允许使用该选项,应采用符合标准的C++语法。 87 88### 警告选项 89 90#### 选项集 91 92##### G.C&C++.WARN.01 打开"-Wall"选项,检查有用的警告选项集 93 94**【级别】** 要求 95 96**【描述】** "-Wall"是gcc编译器认可的、很有用的警告选项集合,包括"-Wpointer-sign"、"-Wframe-address"、"-Wmaybe-uninitialized"、"-Wint-in-bool-context"等警告。对于这些警告,应该理解其含义,通过修改代码来消除警告。 97 98##### G.C&C++.WARN.02 打开"-Wextra"选项,检查除"-Wall"外附加的选项集;"-Wextra"中误报较多的选项,可以使用"-Wno-XXXX"屏蔽 99 100**【级别】** 要求 101 102**【描述】** "-Wextra"是除"-Wall"外的一些有用的警告选项集合,包括“-Wempty-body”、"Wmissing-field-initializers"、"-Wunused-parameter"等警告。 103 104"-Wextra"中某些警告可能存在较多误报,产品在实测的基础上,可以使用“-Wno-XXXX”屏蔽其中误报较多的警告,如某产品实测“-Wunused-parameter 105\-Wmissing-field-initializers”误报较多,可以设置“-Wextra -Wno-unused-parameter 106\-Wno-missing-field-initializers”,由产品线软件总工批准。 107 108##### G.C&C++.WARN.03 打开"-Weffc++"选项,检查Scott Meyers’ Effective C++选项 109 110**【级别】** 建议 111 112**【描述】** "-Weffc++":Scott Meyers’ Effective C++对应的警告选项集。 113 114#### 警告屏蔽 115 116##### G.C&C++.WARN.04 禁止使用"-w"选项屏蔽所有警告 117 118**【级别】** 要求 119 120**【描述】** 编译器提示的警告通常对于鉴别低劣的代码和隐晦的bug非常有用,使用-w选项会屏蔽了所有的警告。 121 122##### G.C&C++.WARN.05 禁止使用"-Wno-XXXX"抑制"-Wall"包含的所有警告选项 123 124**【级别】** 要求 125 126**【描述】** "-Wall"是gcc编译器认可的、很有用的警告选项集合,禁止使用比如"-Wno-pointer-sign"、"-Wno-frame-address"、"-Wno-maybe-uninitialized"、"-Wno-int-in-bool-context"抑制"-Wall"包含的"-Wpointer-sign"、"-Wframe-address"、"-Wmaybe-uninitialized"、"-Wint-in-bool-context"选项。 127 128##### G.C&C++.WARN.06 禁止使用"-Wno-error= XXXX"选项将已指定的升级错误的警告再次降级成警告 129 130**【级别】** 要求 131 132**【描述】** "-Werror=XXXX"把指定警告升级错误,"-Wno-error=XXXX":将指定升级成错误的警告再次降级成警告,令人困惑。 133 134##### G.C&C++.WARN.07 避免使用"-Wno-XXXX"抑制编译器缺省打开的编译警告选项 135 136**【级别】** 建议 137 138**【描述】** 编译器缺省打开的编译警告选项,是gcc编译器认可的、很有用的警告选项,如"-Wwrite-strings"、"-Wdelete-incomplete"、"-Wsizeof-array-argument"等。对于这些警告,应该理解其含义,通过修改代码来消除警告。 139 140**【错误示例】** 某组件的构建工程中使用"-Wno-write-strings"抑制"-Wwrite-strings"编译警告7749次。 141 142**例外:** 为了确保构建一致性,可以重定义 \__FILE_\_ 宏,消除绝对路径,可以使用"-Wno-builtin-macro-redefined"抑制"-Wbuiltin-macro-redefined"警告。 143 144#### 警告升级 145 146##### G.C&C++.WARN.08 使用"-Werror"、"-Werror=XXXX"选项把警告当错误处理 147 148**【级别】** 建议 149 150**【描述】** 建议打开"-Werror"、"-Werror=XXXX"选项:把警告当错误处理 151 152"-Werror":把警告当错误处理,一旦出现警告,编译就会失败,有利于在开发过程中清除所有的警告。 153 154"-Werror=XXXX":把指定警告当错误处理。使用"-Werror=XXXX"指定某些警告当错误处理,有利于在开发过程中清除所指定的警告。,如"-Werror=implicit-function-declaration"、"-Werror=format-SEC"。 155 156#### 警告管理 157 158##### G.C&C++.WARN.09 同一构建工程中,统一编译警告选项。 159 160**【级别】** 要求 161 162**【描述】** 统一的编译警告选项,确保各部分代码质量统一。 163 164#### 函数 165 166##### G.C&C++.WARN.10 打开"-Wtrampolines"选项,避免内嵌函数生成trampoline 167 168**【级别】** 建议 169 170**【描述】** 内嵌函数是定义于函数中的函数。当内嵌函数指针生成trampoline时,会触发警告。Trampoline是在运行时创建于栈区的一小段数据或代码,它包含了内嵌函数的地址信息,它被用于内嵌函数的间接调用。某些平台上,Trampoline仅仅由一些特殊处理的数据构成。但是,大多数平台上,它是由代码构成的,因此它需要栈可执行来支持。栈变成可执行栈,CPU读取栈上指令执行,攻击者可能通过缓冲区溢出攻击等手段运行栈内存上自己得代码。 171 172**【错误示例】** 在函数main内部定义并通过函数指针调用内嵌函数fun,在”-Wtrampolines”选项下编译警告。 173 174- 源程序: 175``` 176\#include \<stdio.h\> 177int main(){ 178 int ret; 179 int (\*pfunc)(int a, int b); 180 int fun(int a, int b){ 181 return a + b; 182 } 183 pfunc = fun; 184 ret = pfunc(10, 20); 185 printf("test gcc option -Wtrampolines! ret = %d\\n", ret); 186 return 0; 187} 188``` 189 190- 编译选项: 191``` 192gcc -Wtrampolines trampolines.c -o out 193``` 194 195- 编译结果: 196``` 197warning:trampoline generated for nested function ‘fun’ [-Wtrampolines] 198``` 199**例外**:"-Wtrampolines" xt-xcc和clang编译器不支持。 200 201##### G.C&C++.WARN.11 打开"-Wformat=2"选项,检查格式化输入/输出函数的安全 202 203**【级别】** 建议 204 205**【描述】** "-Wformat=2"是“-Wformat”、“-Wformat-nonliteral”、“-Wformat-SEC“、”\-Wformat-y2k“的集合。 206 2071. “-Wformat”:格式化函数的参数类型、格式错误时,警告 208 2092. “-Wformat-nonliteral”:当格式化字符串为非字符串常量时,警告 210 2113. “-Wformat-SEC“、” -Wformat-y2k“ 212 213对于产品自行封装的格式化输入输出框架函数, 应于 API 声明 format attribute 214以利用编译器检查能力, 详细参考 215[https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html\#Common-Function-Attributes](https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#Common-Function-Attributes) 216 217##### G.C&C++.WARN.12 打开"-Wstrict-prototypes "选项,避免函数在声明或定义中没有指定参数类型 218 219**【级别】** 建议 220 221**【描述】** 在函数的声明或定义时,显式指明函数参数类型,编译器检查函数调用和定义之间参数类型的不匹配情况。 222 223**【错误示例】** 224 225- 源程序: 226``` 227\#include \<stdio.h\> 228int func(param){ 229 return param; 230} 231 232``` 233- 编译选项: 234``` 235gcc -Wstrict-prototypes strict_prototypes.c -o out 236``` 237- 编译结果: 238``` 239 warning: function declaration isn't a prototype [-Wstrict-prototypes] int func(param){ 240``` 241 242相关文档:《SEI CERT C Coding Standard》DCL07-C. Include the appropriate type 243information in function declarators 244 245#### 二进制一致性 246 247##### G.C&C++.WARN.13 打开"-Wdate-time"选项,避免使用时间宏,确保二进制一致性 248 249**【级别】** 建议 250 251**【描述】** "-Wdate-time":避免代码使用__DATE__、__TIME__、__TIMESTAMP__,以确保二进制一致性。 252 253**【错误示例】** 254 255- 源程序: 256``` 257\#include \<stdio.h\> 258int main() { 259 printf ("%s %s %s\\n",_DATE_,_TIME_,_TIMESTAMP_); 260 return 0; 261} 262``` 263- 编译选项: 264``` 265gcc -Wdate-time datetime.c -o out 266``` 267- 编译结果: 268``` 269warning:macro "_DATE_" might prevent reproducible builds [-Wdate-time] warning:macro "_TIME_" might prevent reproducible builds [-Wdate-time] warning:macro "_TIMESTAMP_" might prevent reproducible builds [-Wdate-time] 270``` 271 272#### 语句 273 274##### G.C&C++.WARN.14 打开"-Wfloat-equal"选项,避免浮点数相等比较运算 275 276**【级别】** 要求 277 278**【描述】** 由于浮点数存在精度问题,大多数情况下是近似值,不能精确判断是否相等。浮点数相等或不相等比较是不安全的行为,建议通过判断两数之差的绝对值是否小于可接受误差来判断浮点数是否相等,可以使用C语言标准库函数fabs()求两浮点数之差的绝对值,然后与可接受误差比较,如果在可接受误差范围内,则相等,否则不相等。需要特别注意的是:\>、\<、\>=、\<=这四种比较运算符用于浮点数比较不会警告。 279 280**【错误示例】** 281 282- 源程序 : 283``` 284\#include \<stdio.h\> 285int main() { 286 double a = 0.3; 287 double b = 0.6; 288 ouble c = 0.9; 289 if ((a+b) == c) { 290 /\* 看似相等,实际运行时,a+b与c不相等\*/ 291 printf("double equal\\n"); 292 } 293 return 0; 294} 295``` 296 297- 编译选项: 298``` 299gcc -Wfloat-equal float_equal.c -o out 300``` 301 302上述示例中,进行双精度浮点数相等比较,编译器警告"warning:comparing floating 303point with == or != is unsafe[-Wfloat-equal]",a+b和c看似应该相等,但是程序运行时并不相等,这是因为浮点数是近似表达,a+b和c均为近似值,浮点数的相等性比较不可信,是一种不安全的行为。浮点数正确的比较方式是设定一个可接受的误差精度,如果两个浮点数差值的绝对值在这个误差范围内,就表示相等,正确方式如下: 304 305**【正确示例】** 浮点数相等比较 306 307- 源程序 : 308``` 309\#include \<stdio.h\> \#include \<math.h\> \#define EPSILON 1e-6 / 310\* 双精度比较可接受的误差 \*/ 311int main() { 312 double a = 0.3; 313 double b = 0.6; 314 double c = 0.9; 315 if (fabs((a+b)-c) \< EPSILON) { 316 printf("double equal\\n"); 317 } 318 return 0; 319} 320``` 321 322- 编译选项: 323 324``` 325gcc -Wfloat-equal float_equal.c -o out 326``` 327 328##### G.C&C++.WARN.15 打开"-Wswitch-default"选项,确保switch语句有default分支 329 330**【级别】** 建议 331 332**【描述】** 如果switch语句没有default分支,在-Wswitch-default选项下编译警告。 333 334**【错误示例】** 335 336- 源程序: 337``` 338enum TintColor{ 339 RED, DARK_RED, GREEN, LIGHT_GREEN 340}; 341void Colorize(enum TintColor Color) { 342 switch (Color) { 343 case RED: 344 /\* code \*/ 345 break; 346 case DARK_RED: 347 break; 348 } 349} 350``` 351- 编译选项: 352``` 353gcc -Wswitch-default switch_default.c -o out 354``` 355- 编译结果: 356``` 357warning: switch missing default case [-Wswitch-default] switch (Color) 358``` 359 360#### 变量 361 362##### G.C&C++.WARN.16 打开"-Wshadow"选项,检查变量覆盖 363 364**【级别】** 建议 365 366**【描述】** "-Wshadow":局部变量覆盖全局变量、函数参数等产生的警告。C++语言打开该选项警告较多,团队可以根据实际情况评估是否打开该选项。 367 368**【错误示例】** 369 370- 源程序: 371``` 372int num = 0; 373int foo(int a, int b){ 374 int num = a + b; 375 return num; 376} 377 378``` 379- 编译选项: 380``` 381gcc -Wshadow shadow.c -o out 382``` 383- 编译结果: 384``` 385warning: declaration of 'num' shadows a global declaration [-Wshadow] int num = a + b; 386``` 387 388##### G.C&C++.WARN.17 打开"-Wstack-usage=len"选项,设置栈大小,避免栈溢出 389 390**【级别】** 建议 391 392**【描述】** 如果函数使用的栈内存可能超过len个字节,编译警告。len的值,团队根据实际情况设置。 393 394**【错误示例】** 395 396- 源程序: 397``` 398void foo(void) { 399 int arr[1000] = {0}; 400 return; 401} 402``` 403- 编译选项: 404``` 405gcc -Wstack-usage=1000 stack_usage.c -o out 406``` 407- 编译结果: 408``` 409warning: stack usage is 4012 bytes [-Wstack-usage=] void foo(void) { 410``` 411 412##### G.C&C++.WARN.18 打开"-Wframe-larger-than=len" 选项,设置栈框架大小,避免栈溢出 413 414**【级别】** 建议 415 416**【描述】** 如果一个函数的栈框架超过len字节,编译警告。len的值,团队根据实际情况设置。 417 418**【错误示例】** 419 420- 源程序: 421``` 422void foo(void) { 423 int arr[1000] = {0}; 424 return; 425} 426``` 427- 编译选项: 428``` 429gcc -Wframe-larger-than=1000 stack_usage.c -o out 430``` 431- 编译结果: 432``` 433warning: the frame size of 4000 bytes is larger than 1000 bytes [-Wframe-larger-than=] 434``` 435 436##### G.C&C++.WARN.19 不建议打开“-Wno-return-local-addr“选项,检查返回局部变量地址 437 438**【级别】** 建议 439 440**【描述】** 如果函数返回局部变量的地址,编译时默认产生“-Wreturn-local-addr”警告。禁止开启“-Wno-return-local-addr”选项屏蔽这些警告。 441 442**【错误示例】** 443 444- 源程序: 445``` 446int\* foo() { 447 int a=0; 448 return \&a; 449} 450``` 451- 编译选项: 452``` 453gcc -Wreturn-local-addr return_local_addr.c -o out 454``` 455- 编译结果: 456``` 457warning: function returns address of local variable [-Wreturn-local-addr] return \&a; 458``` 459 460#### 类型转换 461 462##### G.C&C++.WARN.20 打开"-Wconversion"选项,避免隐式转换改变数值 463 464**【级别】** 建议 465 466**【描述】** 如果代码中的隐式转换会改变数值,在-Wconversion下编译警告。 467可能造成数值改变的隐式转换包括:带小数的实数转换成整数,无符号数和有符号数之间的转换,较大类型的数转换成较小类型。需要注意的是,如果代码进行显式强转,在-Wconversion下编译不会警告。 468 469**【错误示例】** 470 471- 源程序: 472``` 473int foo(void) { 474 double num = 1.2; 475 return num; 476} 477``` 478- 编译选项: 479``` 480gcc-Wconversion conversion.c -o out 481``` 482- 编译结果: 483``` 484warning: conversion from 'double' to 'int' may change value [-Wfloat-conversion] return num; 485``` 486 487不同类型的对象指针之间不应进行强制转换。 488 489##### G.C&C++.WARN.21 打开"-Wcast-qual"选项,指针类型强制转换时,避免目标类型丢失类型限定词 490 491**【级别】** 建议 492 493对指针类型强制转化,导致目标类型丢失类型限定词 494 495**【描述】** 如将const char\*指针类型强制转换为普通的char\*时会丢失const类型限定词,const修饰指针是期望该指针指向的内存不可修改,如果强制转化丢失了const类型限定词,就可以通过转换后的结果指针修改原本不期望被修改的内存,失去了对对象const约束的意义。 496 497**【错误示例】** 498 499- 源程序: 500``` 501static char buf[8]; 502void foo(){ 503 const char\* ptr = buf; 504 char\* q = (char\*)ptr; 505} 506``` 507- 编译选项: 508``` 509gcc -Wcast-qual cast_qual.c -o out 510``` 511- 编译结果: 512``` 513warning: cast discards 'const' qualifier from pointer target type [-Wcast-qual] char\* q = (char\*)ptr; 514``` 515 516##### G.C&C++.WARN.22 打开“-Wcast-align”选项,检查指针类型强制转换,避免目标所需的地址对齐字节数增加 517 518**【级别】** 建议 519 520**【描述】** 当源程序中某个指针类型强制转换导致目标所需的地址对齐字节数增加时,则产生警告。比如在整型只能以两字节或四字节边界进行访问的机器上,将 char \* 转换为 int \* 则给出警告。 521 522#### 数组 523 524##### G.C&C++.WARN.23 打开“-Wvla”选项,避免变长数组 525 526**【级别】** 建议 527 528**【描述】** 定义数组时,如果数据长度是变量而非固定值,在-Wvla选项下编译警告。 529 530**【错误示例】** 531 532- 源程序: 533``` 534void foo(int len) { 535 int arr[len]; 536} 537``` 538- 编译选项: 539``` 540gcc -Wvla val.c -o out 541``` 542- 编译结果: 543``` 544warning: ISO C90 forbids variable length array 'arr' [-Wvla] int arr[len]; 545``` 546 547#### 无效代码 548 549##### G.C&C++.WARN.24 打开“-Wunused”选项,避免无效代码 550 551**【级别】** 建议 552 553**【描述】** -Wunused选项检查代码中未使用的变量、函数、参数、别名等问题。-Wunused选项包含了多个针对某种类型对象未使用的子选项: 554 555\-Wunused-but-set-variable 556 557\-Wunused-function 558 559\-Wunused-label 560 561\-Wunused-local-typedefs 562 563\-Wunused-variable 564 565\-Wunused-value 566 567需要注意的是,需要使用-Wextra \-Wunused或者-Wunused-parameter,才能对函数中未使用的形参警告。 568 569**【错误示例】** 570 571- 源程序: 572``` 573void foo(void) { 574 int a; 575} 576``` 577- 编译选项: 578``` 579gcc -Wunused unused.c -o out 580``` 581- 编译结果: 582``` 583warning: unused variable 'a' [-Wunused-variable] int a; 584``` 585 586#### 预处理 587 588##### G.C&C++.WARN.25 打开“-Wundef ”选项,避免预编译指令\#if语句中出现未定义的标识符 589 590**【级别】** 建议 591 592**【描述】** 当一个没有定义的标识符出现在 \#if 中时,给出警告。 593 594**【错误示例】** 595 596- 源程序: 597``` 598\#if DEFINE_A_VALUE 599\#endif 600``` 601- 编译选项: 602``` 603gcc -Wunused unused.c -o out 604``` 605 606- 编译结果: 607``` 608warning: "DEFINE_A_VALUE" is not defined, evaluates to 0 [-Wundef] \#if DEFINE_A_VALUE 609``` 610 611#### 类 612 613##### G.C&C++.WARN.26 打开“-Wnon-virtual-dtor”选项,避免基类析构函数没有定义虚函数 614 615**【级别】** 建议 616 617**【描述】** 只有基类析构函数是virtual,通过多态调用的时候才能保证派生类的析构函数被调用。 618 619**【错误示例】** 620 621- 源程序 : 622``` 623class Base { 624 public: virtual void foo() const = 0;\ 625 ~Base() {} 626}; 627class Derived: public Base { 628 public: virtual void foo() const {} 629 Derived() {} 630}; 631``` 632- 编译选项: 633``` 634gcc-Wnon-virtual-dtor non_virtual_destructors.cpp -o out 635``` 636 637- 编译结果: 638``` 639warning: 'class Base' has virtual functions and accessible non-virtual destructor [-Wnon-virtual-dtor] 640``` 641 642##### G.C&C++.WARN.27 打开“-Wdelete-non-virtual-dtor”选项,当基类析构函数没有定义虚函数时,避免通过指向基类的指针来执行删除操作 643 644**【级别】 建议** 645 646**【描述】** 当基类没有定义虚析构函数,指向基类的指针来执行删除操作,可导致未定义的行为。禁止开启“-Wno-delete-non-virtual-dtor”选项屏蔽这些警告。 647 648**【错误示例】** 649 650- 源程序: 651``` 652class Base { 653 public: virtual void f(); 654}; 655class Sub: public Base { 656 public: void f(int); 657}; 658int main() { 659 Sub\ * sub = new Sub(); 660 Base\ * base = sub; 661 delete base; 662} 663``` 664- 编译选项: 665``` 666gcc--Woverloaded-virtual overloaded_virtual.cpp -o out 667``` 668- 编译结果: 669``` 670warning: deleting object of polymorphic class type 'Base' which has non-virtual destructor might cause undefined behavior [-Wdelete-non-virtual-dtor] delete base; 671``` 672 673##### G.C&C++.WARN.28 打开"-Woverloaded-virtual"选项,避免隐藏基类虚函数 674 675**【级别】** 建议 676 677**【描述】** 派生类重新定义基类的虚函数,导致基类的虚函数被隐藏。 678 679**【错误示例】** 680 681- 源程序: 682``` 683class Base { 684 public: virtual void f(); 685}; 686class Sub: public Base { 687 public: void f(int); 688}; 689``` 690- 编译选项: 691``` 692gcc--Woverloaded-virtual overloaded_virtual.cpp -o out 693``` 694- 编译结果: 695``` 696warning: by 'void Sub::f(int)' [-Woverloaded-virtual] void f(int); 697``` 698### 安全选项 699 700#### 选项集 701 702##### G.C&C++.SEC.01 打开栈保护选项 703 704**【级别】** 要求 705 706**【描述】** 707 708**Linux平台用户态** 709 710作用阶段:编译选项 711 712作用范围:可重定位文件(.o)、动态库、可执行程序 713 714用法:-fstack-protector-all/-fstack-protector-strong 715 716**说明:** 当存在缓冲区溢出攻击漏洞时,攻击者可以覆盖栈上的返回地址来劫持程序控制流。启用栈保护后,在缓冲区和控制信息间插入一个canary word。攻击者在覆盖返回地址的时候,往往也会覆盖canary word。通过检查canary word的值是否被修改,就可以判断是否发生了溢出攻击。 717 7181\. GCC4.9版本及以上落地-fstack-protector-strong; 719 7202\. GCC4.9版本以下落地-fstack-protector-all。 721 7223\. windriver linux 4.3 + MIPS的环境不支持该特性。 723 724**Linux平台内核** 725 726作用阶段:编译选项 727 728作用范围:Linux平台内核态 729 730用法:内核编译前打开配置CONFIG_CC_STACKPROTECTOR/CONFIG_CC_STACKPROTECTOR_STRONG 731 732**说明:** 733 734内核3.14及以上版本可支持CONFIG_CC_STACKPROTECTOR_STRONG,原有CONFIG_CC_STACKPROTECTOR(对应-fstack-protector)修改为CONFIG_CC_STACKPROTECTOR_REGULAR,内核4.18及以上版本CONFIG_CC_STACKPROTECTOR_REGULAR(对应-fstack-protector)修改为CONFIG_STACKPROTECTOR,CONFIG_CC_STACKPROTECTOR_STRONG(对应-fstack-protector-strong)修改为CONFIG_STACKPROTECTOR_STRONG 735 736受限于OS内核不支持本选项,导致驱动程序也无法使能本选项的情况可例外。其中,所使用的OS内核必须是下列情况中的一种或多种: 737 7381.官方发布的最新版本或者公司推荐的OS版本; 739 7402.由于产品配套(如,兼容客户OS)或兼容现网存量产品,而必须选择的OS版本; 741 7423.由于商务原因(如,实体名单)无法与OS厂商联系并获取新版本,只能使用老版本的场景; 743 744**LiteOS平台** 745 746作用阶段:编译选项 747 748作用范围:LiteOS V200R003C00及之后的版本 749 750用法:-fstack-protector-all/-fstack-protector-strong 751 752**说明:** 1.GCC4.9版本及以上落地-fstack-protector-strong; 753 7542.GCC4.9版本以下落地-fstack-protector-all。 755 756受限于编译器版本不支持本选项或硬件提供类似栈保护的情况可例外。如以下两种情况: 757 7581.由于IAR 8.20版本以下版本不支持任务栈保护,不作要求。 759 7602.使用硬件栈保护的不作要求(如ARC架构下部分产品能够提供硬件栈保护机制,栈溢出时能够触发硬件异常)。 761 762##### G.C&C++.SEC.02 打开地址随机化选项 763 764**【级别】** 要求 765 766windows平台HighASLR & ForceASLR选项实施级别为建议 767 768**【描述】** 769 770**Linux(用户态)** 771 772**a. 使用命令 echo 2 \>/proc/sys/kernel/randomize_va_space 打开系统随机化配置** 773 774**作用阶段:** 运行系统配置 775 776**作用范围:** 堆、栈、内存映射区(mmap基址、shared libraries、vdso页) 777 778**用法:** echo 2 \>/proc/sys/kernel/randomize_va_space 779 780**说明**: 781 782ASLR是一种针对缓冲区溢出的安全保护技术,通过对堆、栈、共享库映射等线性区布局的随机化,增加攻击者预测目的地址的难度,防止攻击者直接定位攻击代码位置,达到阻止溢出攻击的目的。randomize_va_space等于1时,栈、数据段、VDSO会随机化,randomize_va_space等于2时堆地址也会随机化。 783 784需要ASLR开启的级别为最高级别,即randomize_va_space等于2 785 786**b. 打开PIC选项实现动态库随加载** 787 788**作用阶段**:编译选项 789 790**作用范围**:动态库 791 792**用法:** –fPIC(-fpic) 793 794**说明:** 795 796地址无关选项将发生在代码段的重定位移到数据段实现,so文件加载时代码段不会发生任何变化,做到所有进程共用一个代码段副本。 797 798\-fPIC和-fpic均指示GCC产生地址无关代码,唯一的区别是-fPIC产生代码稍大,-fpic产生代码相对较小。 799 800**c. 打开PIE选项实现可执行文件随机加载** 801 802**作用阶段:** 编译链接选项 803 804**作用范围:** 可执行程序 805 806**用法:** –fPIE(-fpie)-pie 807 808**说明:** 809 810具备PIE的可执行文件,在加载执行时可像共享库一样随机加载。有研究表明:PIE可有效降低固定地址类攻击、缓冲溢出类攻击的成功概率。 811 812(1)关注对应的热补丁版本是否支持PIE选项,不支持的场景下不建议使用该选项 813 814(2)-fPIE编译选项,-pie链接选项。 815 816(3)-fPIE产生代码稍大,-fpie产生代码相对较小。 817 818**LiteOS平台** 819 820**a. 配置代码段、数据段随机加载** 821 822**作用阶段:** 编译、链接选项以及运行系统配置 823 824**作用范围:** LiteOS V200R003C00及之后的版本 825 826**用法:** 先编译成可随机的镜像;然后镜像加载时,对地址进行随机修正 827 828**说明:** 1.依赖支持随机地址加载的bootloader,依赖MMU、DDR空间。 829 8302.开启后性能下降10%左右。 831 8323.方案:-fPIE -pie借助GOT表实现地址随机,gcc和自研HCC编译器均可提供支持; 833 8344.开销较大无法落地时,需要业务提供具体的数据到TMG审核。 835 836受限于产品硬件设计或启动流程不支持的情况可例外。如以下三种情况: 837 8381.XIP场景,即系统直接运行于Flash。 839 8402.rom化场景,即全部或部分代码rom化,无法重新加载的场景。 841 8423.bootloader不支持随机地址加载。 843 844**b.配置动态库随机加载** 845 846**作用阶段:** 编译选项 847 848**作用范围:** LiteOS V200R003C00及之后的版本 849 850**用法:** -fPIC 851 852**说明:** 1.动态库编译阶段采用-fPIC。 853 854##### G.C&C++.SEC.03 打开GOT表重定位只读选项 855 856**【级别】** 要求 857 858**【描述】** 859 860**Linux平台-用户态** 861 862**a.部分重定向只读选项:** 863 864**作用阶段:** 链接选项 865 866**作用范围:** 动态库、可执行程序 867 868**用法:** -Wl,-z,relro 869 870**说明**: 871 872动态链接的ELF二进制程序使用称为全局偏移表(GOT)的查找表去动态解析位于共享库中的函数。攻击者通过缓冲区溢出修改GOT表项的函数地址值来达到攻击的目的。通过增加RELRO选项,可以防止GOT表被恶意重写。 873 874**b.全部重定向只读选项:** 875 876**作用阶段:** 链接选项 877 878**作用范围:** 动态库、可执行程序 879 880**用法:** -Wl,-z,now 881 882**说明**: 883 884开启部分重定项只读保护后,再开启立即绑定可实现全部重定向只读保护,即:全部重定向只读(GOT表全保护):-Wl,-z,relro,-z,now 可较好对ret2plt的攻击进行防护,而对诸如缓冲区溢出等攻击无法防范。 885 886对于大量使用共享库中的函数代码产品,在一定程会导致程序装载(启动)阶段缓慢,而运行时性能不会有影响。 887 888##### G.C&C++.SEC.04 打开堆栈不可执行/数据执行保护选项实现堆栈不可执行保护 889 890**【级别】** 要求 891 892**【描述】** 893 894**Linux平台-用户态** 895 896**作用阶段:** 链接选项 897 898**作用范围:** 动态库、可执行程序 899 900**用法:** -Wl,-z,noexecstack 901 902**说明**: 903 9041.如果有内嵌函数,会导致功能错误,需要先用-Wtrampolines进行检测,GCC4.6.4版本及以上。 9052.windriver linux 4.3普通版本不支持该特性。 9063.windriver linux 6 + MIPS不支持该特性。 907 908**LiteOS平台** 909 910**作用阶段:** 运行系统配置 911 912**作用范围:** LiteOS V200R003C00及之后的版本 913 914**用法:** 运行时配置堆栈不可执行、数据段(BSS,DATA)不可执行 915 916**说明:** 1.依赖硬件支持MMU/MPU/PMP等内存保护单元。 917 918##### G.C&C++.SEC.05 使用-s选项或者strip工具去除符号表 919 920**【级别】** linux平台-用户态:要求,其它平台:建议。 921 922**【描述】** 923 924**Linux平台-用户态** 925 926**作用阶段:** 链接选项 927 928**作用范围:** 动态库、可执行程序 929 930**用法:** -s(strip工具) 931 932**说明:** 933 934符号在链接过程中,发挥着至关重要的作用,链接过程的本质就是把多个不同的目标文件“粘”到一起,符号可看作链接的粘合剂,整个链接过程正是基于符号才正确完成的。链接完成后,符号表对可执行文件运行已经无任何作用,反而会成为攻击者构造攻击的工具,因此删除符号表可防御黑客攻击。事实上删除符号表除防攻击外,还可对文件减肥,降低文件大小。 935 9361.对于静态库,可重定位文件(.o)不能strip,否则出现编译错误,只涉及ELF可执行文件和动态库交付的产品才可以去除符号表 937 9382.仅交付给产品,并不直接参与公司外部发布的组件和平台,需提供正式机制通知下游产品在发布阶段统一执行删除符号表操作 939 9403.因为strip会影响产品定位网上问题和热补丁,构建流程上需要保证strip前后的版本同步,即需要产品本地保留未strip符号表的版本供补丁制作和网上调测使用。如可采用以下方案: 941 9423.1 执行机在编译的时候,生成未剥离符号表的可执行文件和动态库的版本,版本归档到VMP上(CMC)供产品做热补丁 943 9443.2 使用strip工具对动态库和可执行文件删除符号表 945 9463.3 剥离符号的可执行文件和动态库压缩到启动大包 947 9484.strip工具和-s选项可达到一样的去除符号表的效果,基于-s选项会造成版本两次编译构建,建议发布前直接使用strip工具,strip级别为默认,如stripbin.out。 949 950**LiteOS平台** 951 952**作用阶段:** 链接选项 953 954**作用范围:** LiteOS V200R003C00及之后的版本 955 956**用法:** -s(strip) 957 958**说明:** 1.LiteOS产品最终用于烧录的编译结果为bin文件,本身不存在符号表信息,建议不开启。 959 960##### G.C&C++.SEC.06 禁止使用Run-time Search Path选项 961 962**【级别】** 要求 963 964**【描述】** 965 966**Linux平台-用户态** 967 968**作用阶段:** 链接选项 969 970**作用范围:** 动态库、可执行程序 971 972**用法:** -Wl,--disable-new-dtags,--rpath,/libpath1:/libpath2;-Wl,--enable-new-dtags,--rpath,/libpath1:/libpath2 973 974**说明**: 975 976主要用于防护LD_LIBRARY_PATH替换同名动态库的攻击。通过加入此选项可以指定一个运行时动态库搜索的路径,该路径的搜索优先级高于LD_LIBRARY_PATH指定的路径。可执行文件在运行阶段进行动态库搜索时会首先在--rpath指定的路径查找动态库,然后才会到LD_LIBRARY_PATH指定的路径搜索。因此可以有效防御LD_LIBRARY_PATH=[attackpath]来替换同名动态库的攻击。但是该选项有也很多局限性,如指向的路径不安全,若普通用户可以在这些目录中使用恶意程序替换正常程序,造成权限提升,引发不安全路径漏洞。 977 978##### G.C&C++.SEC.07 打开代码段/数据段写保护选项 979 980**【级别】** 建议 981 982**【描述】** 983 984**LiteOS平台** 985 986**a. 配置代码段、只读数据段写保护** 987 988**作用阶段:** 运行系统配置 989 990**作用范围:** LiteOS V200R003C00及之后的版本 991 992**用法:** 运行时配置代码段、ReadOnly Data段不可修改 993 994**说明:** 1.依赖硬件支持MMU/MPU/PMP等内存保护单元。 995 996##### G.C&C++.SEC.10 启用FORTIFY_SOURCE编译宏来打开FS选项 997 998**【级别】** 建议 999 1000**【描述】** 1001 1002**Linux平台-用户态** 1003 1004**作用阶段:** 编译选项 1005 1006**用法:** -D_FORTIFY_SOURCE=2 -O2 1007 1008**说明**: 1009 1010程序中使用到静态的固定大小的缓冲区,增加了该选项之后,编译器或运行时库会对相关函数的调用在编译时或运行时进行检查。 1011 1012原则上推荐级别为-O2(基于性能优化效果优于O1),若产品基于O2的风险性允许使用-O1。 1013 1014先在分支版本添加,重点做性能测试,根据测试结果取舍。 1015 1016**LiteOS平台** 1017 1018**作用阶段:** 编译选项 1019 1020**作用范围:** LiteOS V200R003C00及之后的版本 1021 1022**用法:** -D_FORTIFY_SOURCE=2 -O2 1023 1024**说明:** 1. 选项收益和lib库实现有关。 1025 10262.当前LiteOS使用musl库,如果产品替换支持相关功能的lib库,需按需开启。 1027 1028受限于lib库不支持情况可例外。如以下情况: 1029 10301.musl库配置D_FORTIFY_SOURCE=2没有作用,容易对用户造成误导,可不开启。 1031 1032##### G.C&C++.SEC.11 打开ftrapv选项来检测整数溢出 1033 1034**【级别】** 建议 1035 1036**【描述】** 1037 1038**Linux平台-用户态、LiteOS平台** 1039 1040**作用阶段:** 编译选项 1041 1042**用法**:-ftrapv 1043 1044使用了-ftrapv选项后,执行带符号的整数间的加、减、乘运算时,不是通过CPU的指令,而是用包含在GCC附属库libgcc.c里的函数来实现。 1045 1046性能影响较大,建议在Release版本不实施。 1047 1048##### G.C&C++.SEC.13 打开栈检查选项 1049 1050**【级别】** 1051 1052Linux平台-用户态:建议 1053 1054LiteOS平台:要求(禁用) 1055 1056**【描述】** 1057 1058**Linux平台-用户态** 1059 1060**作用阶段:** 编译选项 1061 1062**作用范围:** 可重定位文件、动态库、可执行程序 1063 1064**用法:** -fstack-check 1065 1066**说明:** 1067 1068stack-check在编译时检查程序中栈空间,如果超过编译告警阀值则产生告警;然后在程序中生成额外的指令来检查运行时栈不会被溢出,stack-check选项会在每个栈空间最低底部设置一个安全的缓冲区,如果函数中申请的栈空间进入安全缓冲区,则触发一个Storage_Error异常。但它所生成的代码实际上并不处理异常,如果检测到异常则会发出一个消息,通知操作系统处理。它只保证操作系统可以检测到栈扩展。 1069 1070性能影响较大,建立在Debug版本中实施,Release版本不实施 1071 1072**实施建议:** 可选 1073 1074**LiteOS平台** 1075 1076**作用阶段:** 编译选项 1077 1078**作用范围:** LiteOS V100R003C00及之后的版本 1079 1080**用法:** -fstack-check 1081 1082**说明:** 1.开启后程序会访问非法地址,导致执行异常,因此LiteOS平台下禁止打开栈检查选项。 1083 1084### 优化选项 1085 1086#### 选项集 1087 1088##### P.C&C++.01 在实测的基础上,选择合适优化等级和各种优化选项 1089 1090**【描述】** 在实测的基础上,尝试各种代码优化选项,以查看它们是否确实为生成程序更快。 1091 1092##### G.C&C++.OPT.01 优化等级建议选"-O2"、"-Os"、"-O3" 1093 1094**【级别】** 建议 1095 1096##### G.C&C++.OPT.02 当代码中存在较多的不同类型指针互转时,使用"-fno-strict-aliasing"选项关闭严格别名优化 1097 1098**【级别】** 建议 1099 1100**【描述】** GCC的"-O2"打开"-fstrict-aliasing"严格别名规则优化:编译器假定相同的内存地址绝不会存放不同类型的数据,该优化选项相对激进。为了避免代码中不同类型指针互转导致优化问题,可以使用"-fno-strict-aliasing"关闭优化;最好的方式是修改代码,遵守严格别名规则。 1101 1102注意使用"-fno-strict-aliasing"选项可能会影响产品性能,如某产品一个性能敏感组件实测,"-O2 1103\-fno-strict-aliasing"相比"-O2"会有性能下降,测的数据最多下降有9%。 1104 1105##### G.C&C++.OPT.03 X86/ARM架构下,基于DOPRA平台的产品建议使用"-fno-omit-frame-pointer"选项关闭去SFP(Stack Frame Pointer)优化 1106 1107**【级别】** 建议 1108 1109**【描述】** "-fno-omit-frame-pointer":GCC的“-O” 1110("-O1")会打开"-fomit-frame-pointer"优化选项,也就是去掉函数调用时的frame 1111pointer,优化会导致代码难以调试,建议通过选项"-fno-omit-frame-pointer"禁止该项优化。 1112 1113产品需要在性能优化和保留调试信息进行权衡。 1114 1115### 代码生成选项 1116 1117#### 选项集 1118 1119##### G.C&C++.CDG.01 未初始化的全局变量放置在目标文件的数据段:"-fno-common" 1120 1121**【级别】** 要求 1122 1123**【描述】** "-fno-common":未初始化的全局变量放置在目标文件的数据段,两个不同的编译单元中声明了同一个全局变量导致警告。多个临时的全局变量定义会增加代码维护难度,降低链接速度和增加空间消耗。 1124 1125##### G.C&C++.CDG.02 将结构体放在寄存器中直接返回:"-freg-struct-return" 1126 1127**【级别】** 建议 1128 1129**【描述】** "-freg-struct-return":采用寄存器返回结构与联合值。 1130 1131“-fpcc-struct-return”:在返回短的结构和联合值时,与较长的值一样,使用内存而非寄存器。 1132 1133尽可能在寄存器中返回结构和联合值。对小结构而言,这比“-fpcc-struct-return”效率更高。 1134如果既未使用“-fpcc-struct-return”, 又未使用相反的“-freg-struct-return ”, GNU 1135CC缺省使用目标机器指定的标准规则。如果没有标准规则, 除了在GNUCC为主要编译器的机器上,GNU CC缺省采用“-fpcc-struct-return”,在可以选择标准的情况下, 我们选择了更高效的寄存器返 回方式。 1136 1137注意,此选项影响二进制兼容性,应整个产品统一。 1138 1139##### G.C&C++.CDG.03 设置默认的ELF镜像中符号的可见性为隐藏:"-fvisibility=hidden" 1140 1141**【级别】** 建议 1142 1143**【描述】** "-fvisibility=hidden":可以让动态库中仅API外部可见,有效实现二进制的模块化。使用该选项可以提高动态库链接和加载的速度,防止符号冲突。但该选项加上后,需要考虑对该模块函数打补丁的成本,因为原来的全局符号变成LOCAL属性,对其打补丁时需要重新组名(DOPRA补丁规范有详细的组名规则),构建补丁的成本会增加。是否打开该选项,需要权衡。 1144 1145##### G.C&C++.CDG.04 启用表达式计算顺序强化规则: “-fstrong-eval-order” 1146 1147**【级别】** 建议 1148 1149**【描述】** "-fstrong-eval-order":按C++17的规格确定子表达式之间的计算顺序,比如表达式 1150T().m_i = A().B() 在未开启时可能生成指令的求值顺序时 A() T() B() ,不符合常规预期;该选项当启用 "-std=c++17" 时自动开启, 但当前 gcc7.3默认"-std=c++14",建议显式开启以降低不可预期行为。 1151 1152### 总体选项 1153 1154#### 选项集 1155 1156##### G.C&C++.OVA.01 打开总体选项:"-pipe" 1157 1158**【级别】** 建议 1159 1160**【描述】** "-pipe" :编译过程中多管道并发,节省编译时间 1161 1162### 架构选项 1163 1164#### 选项集 1165 1166##### G.C&C++.MD.01 对于嵌入式软件,显式指明如下架构选项 1167 11681. 软硬浮点(按照CPU支持类型进行添加或者不添加) 1169 11702. 指令集 (如:march=armv7-a/ march=armv8-a) 1171 1172**【级别】** 要求 1173 1174### 链接选项 1175 1176#### 选项集 1177 1178##### G.C&C++.LNK.01 打开如下链接选项:"-Wl,-Bsymbolic"、"-rdynamic"、" -Wl,--no-undefined" 1179 1180**【级别】** 建议 1181 1182**【描述】** -Wl,-Bsymbolic:同名符号优先使用本so,减少got表调转 1183"-rdynamic":解决dlopen反向依赖的问题;BIN文件通过地址返回符号名称,需要加,否则backtrace_symbol返回的是地址,不能定位;影响:产品BIN文件增大。 1184 1185"-Wl,--no-undefined":可以将运行时加载错误,在链接期提前识别出来。打开该选项,导致链接时间会变长,因为链接期要进行依赖关系校验。如果-l指定依赖库不全,会有功能问题,需要产品权衡。 1186 1187 1188### 调试选项 1189#### 选项集 1190 1191##### G.C&C++.DBG.01 对于版本发布构建,禁止携带调试信息 1192 1193**【级别】** 要求 1194 1195**【描述】** 调试信息指 符号表 和 1196详细调试信息表,根据当前安全规定,调试信息,不是运行所必须,要求发布件删除这些信息,包括符号表,以提升攻击难度;热补丁、perf分析、抓堆栈等维测场景受影响。 1197 1198使用 "-s" 链接选项可完全不生成调试信息,需注意此方法生成的组件与不加 "-s" 1199后重新构建生成的组件 build-id 是不一致的,不能直接用来 gdb定位问题;也可以链接后使用 objcopy --only-keep-debug \<target\> \<xxx.dbg\> 加上objcopy objcopy --strip-unneeded \<target\> 方式分离调试符号,同样可达成交付件不含有符号表等调试信息。 1200 1201如果编译阶段启用了 -g 生成详细调试信息表, 会因含有源代码绝对路径信息造成不同目录下构建的二进制差异, 此时可使用 -fdebug-prefix-map=old=new来将绝对路径映射成相对路径,达成 BEP 要求。 1202 1203### 编译宏 1204#### 选项集 1205 1206##### G.C&C++.PRE.01 明确-D编译宏的具体用途,建立-D编译宏的清单 1207 1208**【级别】** 要求 1209 1210**【描述】** 每增加一个-D编译宏,就需要对它进行额外的测试。为每一种软件-D编译宏所作的代码修改,必须验证能否适用于其他-D编译宏。首先必须针对所有的-D编译宏,对软件进行构建,以确保没有编译错误;其次必须针对所有的-D编译宏进行完整的测试。 1211 1212对于未使用的-D编译宏,应该直接删除。 1213 1214### 其他 1215#### 选项集 1216 1217##### G.C&C++.OTH.01 同一构建工程中,避免使用重复的或包含关系的编译选项 1218 1219**【级别】** 建议 1220 1221**【描述】** 重复的编译选项是冗余信息,不利于维护。如果编译选项具有不同的参数,可能导致与初始预期不同的方式编译源文件。 1222 1223编译选项之间存在包含关系时,同时使用会导致冗余。譬如"-Wall"包含40多个子警告选项,"-O"包含40多个子优化选项,当它们与子选项同时使用时就会导致冗余。 1224 1225**【错误示例】** 某组件对编译优化选项"-O"取值达到7055次,其中同一构建工程中出现多个"-O",如"-O2...-O6"、"-O2...-O3"。 1226 1227\# "-Wall"包含"-Waddress",同时使用产生冗余 1228 1229gcc -Wall -Waddress -c test.c -o test.o 1230 1231\# "-O"包含"-fauto-inc-dec",同时使用产生冗余 1232 1233gcc -O -fauto-inc-dec -c test.c -o test.o 1234 1235##### G.C&C++.OTH.02 避免使用相反冲突的选项 1236 1237**【级别】** 建议 1238 1239**【描述】** 大多数'-f'和'-W'有两个相反的互相否定的选项: 1240\-fname/-fno-name和-Wname/-Wno-name,同时引用导致冲突,令人疑惑,不利于维护。 1241 1242**【错误示例】** 1243 1244\#同时引用-fomit-frame-pointer和-fno-omit-frame-pointer 1245 1246``` 1247set(CMAKE_C_FLAGS "-MD -MF -Wall -save-temps -fverbose-asm -fsigned-char 1248\-fomit-frame-pointer -fno-stack-protector \\ 1249 1250\-fno-delete-null-pointer-checks -fno-common -freg-struct-return -O2 1251\-fno-omit-frame-pointer -fno-strength-reduce" ) 1252``` 1253 1254##### G.C&C++.OTH.03 编译选项的编写顺序:优化等级(如-O2)+总体选项+警告选项+语言选项+代码生成选项+架构选项(MD-Dependent Options)+优化选项+安全编译选项+自定义宏 1255 1256**【级别】** 建议 1257 1258**【描述】** 有选项集的,先写选项集,例如"-Wall"应该写到"-Wformat=2"前。 1259 1260**【正确示例】** 1261 1262``` 1263\# Copyright (c) Huawei Technologies Co., Ltd. 2019. All rights reserved. 1264\# toolchain for ARMA15(without FPU)HI1381/HI1215 1265\# cpu_family = arm 1266\# bit_width_in_run = 32 1267\# cpu_core = a15 1268\# compile flags 1269set(CC_OPT_LEVEL "-O2") 1270set(CC_OVERALL_FLAGS "-pipe") 1271set(CC_WARN_FLAGS "-Wall -Wextra -Wdate-time -Wtrampolines -Wfloat-equal 1272\-Wshadow -Wformat=2") 1273set(CC_LANGUAGE_FLAGS "-fsigned-char") 1274set(CC_CDG_FLAGS "-fno-common -freg-struct-return") 1275set(CC_MD_DEPENDENT_FLAGS "-mfloat-abi=soft -march=armv7-a -mtune=cortex-a15") 1276set(CC_OPT_FLAGS "-fno-strict-aliasing -fno-omit-frame-pointer") 1277set(CC_SEC_FLAGS "-fPIC -fstack-protector-strong --param=ssp-buffer-size=4") 1278set(CC_DEFINE_FLAGS "-DXXXXX") 1279set(CC_ALL_OPTIONS "\${CC_OPT_LEVEL} \${CC_OVERALL_FLAGS} \${CC_WARN_FLAGS} 1280\${CC_LANNGUAGE_FLAGS} \\ 1281\${CC_CDG_FLAGS} \${CC_MD_DEPENDENT_FLAGS} \${CC_OPT_FLAGS} \${CC_SEC_FLAGS} 1282\${CC_DEFINE_FLAGS}") 1283 1284\# public link flags 1285set(PUBLIC_LNK_FLAGS "-rdynamic -Wl,-z,noexecstack -Wl,-z,relro -Wl,-z,now") 1286 1287\# link flag for module 1288set(SHARED_LNK_FLAGS "-shared \${PUBLIC_LNK_FLAGS}") 1289set(PIE_EXE_LNK_FLAGS "-pie \${PUBLIC_LNK_FLAGS}") 1290 1291``` 1292 1293## Java语言编译选项 1294 1295### 语言级别 1296#### 选项集 1297 1298##### G.JAVA.LANG.01 每个交付单元使用的Java编译语言级别必须一致,且必须与使用的Java版本对应的编译语言级别一致。 1299 1300**【级别】** 要求 1301 1302**【描述】** 不同模块的编译语言级别不一致,这些模块需要配置不同的编译选项,导致构建脚本不一致。 1303 1304使用与Java版本对应的编译语言级别,可以在编译阶段提示对应Java版本不推荐的编码实践,如Java 8版本将Java 7版本部分可用的API标记为 @Deprecated,推荐用更好的API来替换。当使用编译语言级别8时就会在编译阶段发出警告,代码中使用了将被弃用的API。 1305 1306### MAVEN 1307#### 选项集 1308 1309##### G.JAVA.MAVEN.01 版本发布构建时禁止使用maven 编译选项-X,避免输出大量的debug日志。 1310 1311**【级别】** 要求 1312 1313**【描述】** -X是debug选项,会输出大量的debug日志。 1314 1315### JAVAC 1316#### 选项集 1317 1318##### G.JAVA.JAVAC.01 禁止使用的javac编译选项:-nowarn/-Xlint:none/-Xlint:name 选项关闭所有或部分javac编译告警;-g:none/-g:[keyword list]选项关闭全部或指定生成部分调试信息 1319 1320**【级别】** 要求 1321 1322**【描述】** 编译告警能够帮助提前发现代码存在的缺陷和风险,关闭编译告警会给代码质量带来隐患;使用-g:none或-g:[keywordlist]会导致生成过少或过多的调试信息,影响可维护性或降低运行效率。 1323 1324**例外**:-Xlint:all,-processing 1325运行时处理的注解不需要注解处理器,产生编译告警可以通过-Xlint的参数-processing进行抑制。 1326 1327##### G.JAVA.JAVAC.02 必须使用的javac编译选项:-source,-target,-Xlint:all。 同时maven-compiler-plugin的showWarnings属性必须设置为true。 1328 1329**【级别】** 求 1330 1331**【描述】** 1332 1333\-source 指定编译器接受的java源文件版本 1334 1335\-target 指定编译器生成的class文件版本 1336 1337\-Xlint:all 使能所有推荐编译告警 1338 1339showWarnings 属性必须设置为true,不设置或者设置为false时部分编译告警无法检查出来 1340 1341**【正确示例】** 1342 1343``` 1344\<plugin\> 1345 1346\<groupId\>org.apache.maven.plugins\</groupId\> 1347 1348\<artifactId\>maven-compiler-plugin\</artifactId\> 1349 1350\<configuration\> 1351 1352\<source\>1.8\</source\> 1353 1354\<target\>1.8\</target\> 1355 1356\<showWarnings\>true\</showWarnings\> 1357 1358\<compilerArgs\> 1359 1360\<arg\>-Xlint:all\</arg\> 1361 1362\</compilerArgs\> 1363 1364\</configuration\> 1365 1366\</plugin\> 1367 1368``` 1369 1370