万众期待方舟终于在8月31日放出了源码,这个已经快被吹上天的杀手级开源项目体质怎么样呢?源伞科技出于职业习惯,忍不住用Pinpoint对代码进行了一轮静态摸查。最终证明,方舟的代码品控还是可以吹一吹的,虽然还没到上天的水平,仍然是个人类体质的项目,凡间的Bug也都还是有的。希望这样有分量国产开源项目能够越来越多,让大家能一起来学习和抓虫。

分析目标:

master分支,最新修改2019年9月2日17:05

commit 13e1bc6 6d1c76fea74f1ab686088e9101331d395

C++ 14? C++11? 能编过就行嘛~

C++是一门一直在进化的语言,虽然不像JavaScript一样每年需要重新学习,但也一直在出C++ 11, C++ 14, C++ 17的不同版本。

那么方舟使用的标准究竟是什么呢?我们考察了方舟的编译命令:

clang++

-I../src/maple_ir/include -I../src/mempool/include

-I../src/huawei_secure_c/include -I../src/maple_me/include

-I../src/maple_ipa/include -I../src/mpl2mpl/include -I../src/maple_util/include

-I../src/maple_phase/include -O3 -Wall

-fstack-protector-strong -fPIC -fPIE -m64 -DDYNAMICLANG -DRC_NO_MMAP

-DMIR_FEATURE_FULL=1 -DMIR_JAVA=1 -std=c++11

-Werror -MD -MT obj/src/maple_me/src/libmplme.me_rc_lowering.o -MF

obj/src/maple_me/src/libmplme.me_rc_lowering.o.d -o

obj/src/maple_me/src/libmplme.me_rc_lowering.o -c

../src/maple_me/src/me_rc_lowering.cpp

ok,也就是说它是一个c++ 11项目。

然而仔细一看~ 惊了

https://github.com/harmonyos-mirror/OpenArkCompiler/blob/13e1bc6/src/maple_me/include/ssa_mir_nodes.h#L413

src/maple_me/include/ssa_mir_nodes.h文件中写了如下代码,在类型定义中偏特化了SetSSAPartOf模板函数,如下所示:

class
StmtsSSAPart {
...

template

void SetSSAPartOf(const StmtNode *s, T *p) {

ssaPart[s->GetStmtID()] = p;

}

template <>

void SetSSAPartOf(const StmtNode *s, VersionSt *vst) {

VersionStPart *vStSSAPart = GetSSAPartMp()->New();

vStSSAPart->SetSSAVar(vst);

ssaPart[s->GetStmtID()] = vStSSAPart;

}
...
};

类型定义中偏特化模板函数在C++14标准才有。

那么它是怎么编译通过的呢?难道是什么菊花厂黑魔法吗?

菊厂黑魔法.png

经过一段时间的怀疑人生,我们发现由于方舟编译器默认使用llvm8.0编译,而clang8.0强制开启该C++14 feature。

所以严格讲,方舟是个C++ 14项目。抑或,菊厂本来是要求用C++11标准,但是有人不小心手滑了?LoL

代码好不好,咱先用最严格的编码规范过一遍

在找Bug之前,我们先用Pinpoint支持的电子工业C++代码标准SJ/T 11682-2017和汽车行业最严格的Misra编码标准来测试了一下。这次应用的标准是Misra C++ 2008规范,也是最常用的C++规范。

从结果看起来,方舟编译器的质量非常不错,电子工业行标检测得出缺陷密度很低。当然由于不是运行时的平台代码,而是工具链,所以在Misra标准的检测中还是发现了无数的违反点。整整刷了八千多页的报告,看来车载代码的规范还真是严格啊。

合规报告下载地址:

链接:https://pan.baidu.com/s/1S_54QUaZGxvRKOcuf-FwHw

提取码:bwbn

虽然不能要求编译器跟关乎生命安全的车载系统代码一样严格书写,但是用行业标准来加强下代码规范性,方舟团队也是可以考虑的。毕竟编译器也是个大型团队工程,也是对正确性要求较高的软件系统。

看得出来,方舟代码是专门做过质量保证的

众所周知,华为内部一直使用Coverity静态分析工具来做代码安全保障。正好我们也拿到了一份Coverity分析方舟代码的报告,并对这份报告做了详细的分析。发现报告里面95%以上的都是误报,同时从误报中能明确看出针对性修复的痕迹,例如以下NPD误报:

coverity_npd_noark.png

已经通过ASSERT约束过MirConst不为nullptr。

总体上来说,虽然从Misra检测来看,方舟的代码规范度有待改善,但方舟的代码缺陷控制还是做的不错。当然一个工具Coverity肯定不能找到所有问题,接下来就看看Pinpoint找到的额外值得修复问题。

变量名太长懒得打怎么办?敲个开头按Tab

现在IDE都很智能了,基本上敲两下就能补全出一长串的变量名和代码,随之而来的缺点也是长得太相近的变量容易看花眼。

举个例子.png

https://github.com/harmonyos-mirror/OpenArkCompiler/blob/13e1bc6/src/maple_ir/src/lexer.cpp#L587

相同表达式.png

这里程序员同学明显是想检查上界和下界,但是因为俩方法长太像了,名字又长,所以Tab或者拷贝了一下,检查了两遍上界……

语句太长的确会导致超过人类的能力范围,一般的说法是人一般能同时记住5-9个东西。

https://github.com/harmonyos-mirror/OpenArkCompiler/blob/13e1bc6/src/mpl2mpl/src/class_hierarchy.cpp#L835

抄错变量名.png

这里显然程序员同学被一堆java开头的变量弄晕了,抄变量名抄错了一个。

内存泄漏,不知不觉

通常编译器一类的命令行程序对于内存的管理还是比较粗暴的,由于都是一次性的短暂执行,所以大部分内存漏了就漏了,反正程序结束了系统就会回收。但是当泄露的位置是在for循环里时,就相对严重了一些,漏得再少,顶不住循环泄露啊~ 何况是当你的源文件名是叫SafeXXX的时候。于是我们决定批判一个。

https://github.com/harmonyos-mirror/OpenArkCompiler/blob/13e1bc6/src/maple_driver/include/safe_exe.h#L58

内存泄漏.png

这里在使用maple::SafeExe::Exe()函数开新进程的时候会开一段内存存储参数信息,但是该存储在新进程开启后没有释放,导致每次开新进程的时候泄漏(tmpArgs.size()+1)

*sizeof(char*)字节的内存。内存泄漏问题动态测试时很难发现,因为没有明显的运行出错警示

每逢发版必出幺蛾的诅咒

你是否遇到程序在你的机器上运行得好好的,到了别人机器上一跑就崩进而怀疑人生的经历?我们来看个栗子~

https://github.com/harmonyos-mirror/OpenArkCompiler/blob/13e1bc6/src/maple_me/src/me_emit.cpp#L35

npd_pinpoint.png

这里layoutBBs变量是GetAnalysisResult函数的返回值,GetAnalysisResult是可能返回空值的,这样在第52行或者第57行可能出现空指针问题。这里35行有一个对layoutBBs为空值的保护,如果layoutBBs为空值则程序退出,后续代码不会执行。然而35行的ASSERT保护并不能在所有产品线保护好这个变量,然后发布的编译器运行的时候就有可能崩了。

ASSERT宏定义如下(src/maple_util/include/mpl_logging.h:244行)

assert.png

这里我们看到,只有在ENABLE_ASSERT开启的时候ASSERT宏才有效,而方舟编译器默认编译配置ENABLE_ASSERT是关闭的,所以上图Src/maple_me/me_emit.cpp:35行的保护在很多情况无效。

总结

方舟编译器初次体检感觉身体素质不错,比源伞科技评判过的大多数开源项目素质要好。希望菊厂继续给力,放出更多大招来推进新的国产编译器生态发展。我们也会持续关注,放出更多体检报告。