valgrindを使ってメモリチェックをGitLab CIで行う

# はじめに

今時メモリ管理を自分できっちりとしなきゃいけないことは少ないですね。
C++ならスマートポインタありますし（横道にそれますが、C++のスマートポインタは[この記事]( https://qiita.com/hmito/items/db3b14917120b285112f )とかが分かりやすいです）。
それでもメモリ管理を自分でやらなきゃいけない場面も出てくるんです。
そんな時にメモリチェックをCIで自動化しましょう、という記事です。

# サンプル

https://gitlab.com/fockl-public-samples/valgrind_sample

ここで公開してあります。
テストが落ちてますが、「ちゃんとテストが落ちる」ことをテストしているので、これは正しいです。

## ディレクトリ構成

```
.
├samples/
│   ├sample1.cpp
│   ├sample2.cpp
│   ├sample3.cpp
│   ├sample4.cpp
│   └sample5.cpp
├src/
|   ├sample_lib.cpp
|   └sample_lib.hpp
├ci/
|   ├build.yml
|   └test.yml
├CMakeLists.txt
├sample5.sup
└.gitlab-ci.yml
```

## valgrindの各種エラーを出すサンプルコード

これらの記事を参考にしました

https://wagavulin.hatenablog.com/entry/2016/08/28/231547 

https://pyopyopyo.hatenablog.com/entry/20070104/p1


### definitely lost

``` c++ sample1.cpp
#include<cstdlib>

int main(void){
  char* p = new char[128];
  return 0;
}
```

### indirectly lost

```c++ sample2.cpp
#include<cstdlib>

class B{
};

class A{
  private:
    B* m_b;
  public:
    A() : m_b(new B) {}
    ~A() { delete m_b; }
};

int main(void){
  A* a = new A;
  return 0;
}
```

### still reachable

```c++ sample3.cpp
#include<cstdlib>

char* g_ptr1;

int main(void){
  g_ptr1 = new char[128];
  return 0;
}
```

### possibly lost

```c++ sample4.cpp
#include<cstdlib>

char* g_ptr2;

int main(void){
  char* p = new char[128];
  g_ptr2 = &p[10];
  return 0;
}
```

### suppressed

外部ライブラリにおけるメモリリークを無視する時に発生するため、再現できるようにライブラリと.supファイルも作成します。

```c++ sample5.cpp
#include"../src/sample_lib.hpp"

int main(void){
  core();
  return 0;
}
```

```c++ sample_lib.hpp
void core(void);
```

```c++ sample_lib.cpp
#include<cstdlib>

void core(void){
  char *p = new char[128];
  return;
}
```

``` sample5.sup
{
  <insert_a_suppression_name_here>
  Memcheck:Leak
  obj:*vgpreload_memcheck-amd64-linux.so
  obj:*libsample_lib.so
}

{
  <insert_a_suppression_name_here>
  Memcheck:Cond
  obj:*vgpreload_memcheck-amd64-linux.so
  obj:*libsample_lib.so
}

{
  <insert_a_suppression_name_here>
  Memcheck:Free
  obj:*vgpreload_memcheck-amd64-linux.so
  obj:*libsample_lib.so
}
```

## CMakeLists.txt

前半で sample5.cpp 用にライブラリを作成し、後半でsample*.cppをコンパイルします。
オプションに注意。

```cmake
cmake_minimum_required(VERSION 3.11)

project(valgrind_sample)

set(SAMPLE_LIB sample_lib)
add_library(${SAMPLE_LIB} SHARED "")
target_sources(${SAMPLE_LIB}
  PRIVATE
  src/sample_lib.cpp
  )
target_compile_options(${SAMPLE_LIB}
  PRIVATE -O0 -Wall
  )

file(GLOB_RECURSE TEST_SOURCE_FILES
  RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
  samples/*.cpp
  )

foreach(ALL_ITEM ${TEST_SOURCE_FILES})
  get_filename_component(MAIN_ITEM ${ALL_ITEM} NAME_WE)
  set(TARGET_NAME ${MAIN_ITEM}.out)

  add_executable(${TARGET_NAME} ${ALL_ITEM})
  if(${MAIN_ITEM} STREQUAL "sample5")
    target_link_libraries(${TARGET_NAME} PRIVATE ${SAMPLE_LIB})
  endif()
  target_compile_options(${TARGET_NAME} PRIVATE -g -O0 -Wall)
endforeach()
```


## yamlファイル

build用とtest用にymlファイルを分割してあります。

.gitlab-ci.yml
```yml
image: docker:latest

stages:
  - build
  - test

services:
  - docker:dind

include:
  - local: ci/build.yml
  - local: ci/test.yml
```

build.yml
```yml
build:
  needs: []
  stage: build
  image: ubuntu:22.04
  before_script:
    - apt-get update
    - apt-get install -y build-essential gcc-11 g++-11 make cmake valgrind 
  script:
    - cmake -Bbuild
    - cmake --build build
  artifacts:
    paths:
      - build
```

test.yml
valgrindの引数 `--error-exitcode=`でエラー時の返り値が指定できます。（今は1）
```yml
.test:
  stage: test
  needs:
    - build
  image: ubuntu:22.04
  before_script:
    - apt-get update
    - apt-get install -y build-essential valgrind

test1:
  extends:
    - .test
  script:
    - valgrind -v --leak-check=full --error-exitcode=1 ./build/sample1.out

test2:
  extends:
    - .test
  script:
    - valgrind -v --leak-check=full --error-exitcode=1 ./build/sample2.out

test3:
  extends:
    - .test
  script:
    - valgrind -v --leak-check=full --error-exitcode=1 ./build/sample3.out

test4:
  extends:
    - .test
  script:
    - valgrind -v --leak-check=full --error-exitcode=1 ./build/sample4.out

test5:
  extends:
    - .test
  script:
    - valgrind -v --leak-check=full --suppressions=sample5.sup --error-exitcode=1 ./build/sample5.out

```

# CI 実行結果

![スクリーンショット 2022-12-11 162135.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/328786/902e268e-fa66-7c09-fa6d-4b7667576635.png)

definitely lost, indirectly lost, possibly lost はエラーで落ちました。
still reachable, suppressed はエラーで落ちませんでした。
still reachable は valgrind が修正する必要が無い場合が多いためエラーにはならず、suppressed は使っているライブラリ側の責任のためこちらもエラーにはならない、という期待通りの結果になりました。

これでメモリ管理もGitLab-CIで出来ますね！！！

# おわりに

アドベントカレンダー書こうと思ったけどネタが無かったので、これで許してください。