diff --git a/.github/workflows/codechecker.yaml b/.github/workflows/codechecker.yaml new file mode 100644 index 000000000..8856d9359 --- /dev/null +++ b/.github/workflows/codechecker.yaml @@ -0,0 +1,30 @@ +name: Clang Static Analyzer + +on: [push, pull_request] + +jobs: + clang_static_analyzer: + runs-on: ubuntu-24.04 + + steps: + - name: Checkout code including full history and submodules + uses: actions/checkout@v3 + with: + submodules: true + fetch-depth: 0 + + - name: Install CodeChecker + run: | + sudo apt-get update + sudo apt-get install clang-tools-18 cmake cppcheck libcunit1-dev ninja-build unzip wget + pip3 install codechecker + + - name: Run CodeChecker + run: | + run: tools/ci/run_ci.sh --run-build --run-code-checker + + - name: Upload CodeChecker reports + uses: actions/upload-artifact@v1 + with: + name: CodeChecker Reports + path: build-wakaama/codechecker_report diff --git a/README.md b/README.md index 2aa471a2a..428b33251 100644 --- a/README.md +++ b/README.md @@ -133,12 +133,12 @@ Wakaama provides a simple CLI library. It can be enabled with: - Unit testing: CUnit On Ubuntu 20.04, used in CI, the dependencies can be installed as such: -- `apt install build-essential clang-format clang-format-14 clang-tools-14 cmake gcovr git libcunit1-dev ninja-build python3-pip` +- `apt install build-essential clang-format clang-format-14 clang-tools-14 cmake cppcheck gcovr git libcunit1-dev ninja-build python3-pip` - `pip3 install -r tools/requirements-compliance.txt` For macOS the development dependencies can be installed as such: -`brew install automake clang-format cmake cunit gcc gitlint gnu-getopt make ninja` +`brew install automake clang-format cmake cppcheck cunit gcc gitlint gnu-getopt make ninja` ### Code formatting #### C diff --git a/tools/ci/run_ci.sh b/tools/ci/run_ci.sh index a7edcbef5..d5038bcf4 100755 --- a/tools/ci/run_ci.sh +++ b/tools/ci/run_ci.sh @@ -31,6 +31,7 @@ OPT_SONARQUBE="" OPT_SOURCE_DIRECTORY="${REPO_ROOT_DIR}" OPT_BUILD_DIRECTORY="build-wakaama" OPT_TEST_COVERAGE_REPORT="" +OPT_CODE_CHECKER="full" OPT_VERBOSE=0 OPT_WRAPPER_CMD="" RUN_BUILD=0 @@ -41,6 +42,7 @@ RUN_GITLINT=0 RUN_GIT_BLAME_IGNORE=0 RUN_TESTS=0 RUN_DOXYGEN=0 +RUN_CODE_CHECKER=0 HELP_MSG="usage: ${SCRIPT_NAME} ... Runs build and test steps in CI. @@ -72,6 +74,9 @@ Options: (WRAPPER: path to build-wrapper) --test-coverage REPORT Enable code coverage measurement, output REPORT (REPORT: xml html text none) + --code-checker ACTION Run the CodeChecker code analyzer to create a baseline, + do a full check or a PR check (show just difference to baseline) + (TYPE: full, pr, baseline) -v, --verbose Verbose output -a, --all Run all steps required for a MR -h, --help Display this help and exit @@ -85,6 +90,7 @@ Available steps (executed by --all): --run-build Build all targets --run-tests Execute tests (works only for top level project) --run-doxygen Build the Doxygen documentation of the code + --run-code-checker Run the CodeChecker code analyzer " function usage() { @@ -228,6 +234,43 @@ function run_doxygen() { GIT_REVISION=$(git rev-parse @) WORKING_DIR=$(pwd) DOXYGEN_OUT_DIR=build-wakaama/doxygen \ doxygen doc/doxygen/Doxyfile } + +function run_code_checker() { + readonly config_file="${REPO_ROOT_DIR}/tools/code_checker/config.json" + readonly ignore_file="${REPO_ROOT_DIR}/tools/code_checker/ignore.txt" + readonly baseline_file="${REPO_ROOT_DIR}/tools/code_checker/reports.baseline" + readonly code_checker_result_dir="build-wakaama/code_checker_result/" + readonly code_checker_report="build-wakaama/code_checker_report/" + + CodeChecker check --logfile build-wakaama/compile_commands.json \ + --config "$config_file" \ + --ignore "$ignore_file" \ + --output $code_checker_result_dir \ + || true # Currently failing with found issues + + + if [ "${OPT_CODE_CHECKER}" = "pr" ]; then + CodeChecker cmd diff -b "$baseline_file" \ + -n $code_checker_result_dir \ + --new + else + if [ "${OPT_CODE_CHECKER}" = "baseline" ]; then + output_format="baseline" + output_location="$baseline_file" + else + output_format="html" + output_location="$code_checker_report" + fi + + CodeChecker parse -e "$output_format" \ + -o "$output_location" \ + --config "$config_file" \ + --ignore "$ignore_file" \ + --trim-path-prefix="${REPO_ROOT_DIR}" \ + "$code_checker_result_dir" + fi +} + # Parse Options if [[ "$OSTYPE" == "darwin"* ]]; then @@ -259,12 +302,14 @@ if ! PARSED_OPTS=$($getopt -o vah \ -l run-git-blame-ignore \ -l run-tests \ -l run-doxygen \ + -l run-code-checker \ -l sanitizer: \ -l scan-build: \ -l sonarqube: \ -l source-directory: \ -l build-directory: \ -l test-coverage: \ + -l code-checker: \ -l verbose \ --name "${SCRIPT_NAME}" -- "$@"); then @@ -327,6 +372,12 @@ while true; do RUN_DOXYGEN=1 shift ;; + --run-code-checker) + RUN_CODE_CHECKER=1 + # Analyzing works only when code gets actually built + RUN_CLEAN=1 + shift + ;; --sanitizer) OPT_SANITIZER=$2 shift 2 @@ -355,6 +406,10 @@ while true; do OPT_TEST_COVERAGE_REPORT=$2 shift 2 ;; + --code-checker) + OPT_CODE_CHECKER=$2 + shift 2 + ;; --) shift break @@ -410,6 +465,11 @@ if [ -n "${OPT_SCAN_BUILD}" ] && [ -n "${OPT_SONARQUBE}" ]; then exit 1 fi +if [ "${RUN_CODE_CHECKER}" = "1" ] && [ -n "${OPT_SONARQUBE}" ]; then + echo "--sonarqube and --code-checker can not be enabled at the same time" + exit 1 +fi + if [ -n "${OPT_SONARQUBE}" ]; then OPT_TEST_COVERAGE_REPORT="${OPT_TEST_COVERAGE_REPORT:-none}" OPT_WRAPPER_CMD="${OPT_SONARQUBE} \ @@ -430,6 +490,10 @@ if [ -n "${OPT_SCAN_BUILD}" ]; then --exclude examples/shared/tinydtls" fi +if [ "${RUN_CODE_CHECKER}" = "1" ]; then + CMAKE_ARGS="${CMAKE_ARGS} -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_BUILD_TYPE=Debug" +fi + # Run Steps if [ "${RUN_GITLINT}" -eq 1 ]; then @@ -463,3 +527,8 @@ fi if [ "${RUN_DOXYGEN}" -eq 1 ]; then run_doxygen fi + +if [ "${RUN_CODE_CHECKER}" = "1" ]; then + run_code_checker +fi + diff --git a/tools/code_checker/config.json b/tools/code_checker/config.json new file mode 100644 index 000000000..45379acc3 --- /dev/null +++ b/tools/code_checker/config.json @@ -0,0 +1,12 @@ +{ + "analyze": [ + "--disable=cplusplus", + "--disable=cppcheck-exceptDeallocThrow", + "--disable=cppcheck-exceptThrowInDestructor", + "--disable=cppcheck-leakUnsafeArgAlloc", + "--disable=cppcheck-rethrowNoCurrentException", + "--disable=cppcheck-thisSubtraction", + "--disable=cppcheck-throwInNoexceptFunction", + "--clean" + ] +} \ No newline at end of file diff --git a/tools/code_checker/ignore.txt b/tools/code_checker/ignore.txt new file mode 100644 index 000000000..e7815e8ed --- /dev/null +++ b/tools/code_checker/ignore.txt @@ -0,0 +1,2 @@ +-*/examples/shared/tinydtls/* +-*/tests/* diff --git a/tools/code_checker/reports.baseline b/tools/code_checker/reports.baseline new file mode 100644 index 000000000..0e935bb0d --- /dev/null +++ b/tools/code_checker/reports.baseline @@ -0,0 +1,345 @@ +18de243b3d7cb55a83fe53a63b63a0dd +18f7f770b0a1a19c537df36decb4d80e +19307ae966a67b6ae9caa20ef427cb31 +1a38266ad76c20eb86b86db9fcd7d78b +1a51d8971080a661d87fa6d14add8146 +1ab9eaf3cde993dea438ba21b483cbe4 +1aec267d92cc9994f24463ed73a0577a +1b625265b6313b4a3a236deefb56e40c +1c515560d932fd84a081c6efdf36110c +1cdb6bf3d72f68f90191dff88973ec89 +1ce6b9bda4aca255f47c32ccebc08588 +1d38c18679aa258c4e88c94560f9b221 +1dbcd7f2794b2157e9dc2bfbc2e1bf47 +1f609ca27b9d7cd5aeec2dd4cf4a6e31 +1f8f6b116e14be36722282ecb335417e +1fa5139150c5b5448352a689ec458bad +2028037337f4def2303e2cb9c3c82d22 +204d4210285988ce8d9478cd7bf3fc7c +216f5358b9656c4dcb4d33e458324f76 +236ef778fbea452c6554f3e783b76e3c +242678131487b2aac2bb45512374c8c2 +24cf86e7a415b8e7b57df10ec5ee63f8 +24d9a10e17b2fcc5190f276149f547b6 +29815beca13eae69bcb17d8568eabb49 +2a4c31bfb93ede65204dd2ec736dc6c6 +2b37f309f218b9ea14d73eebc0742b97 +2bc7c86c848a7c8c1747c559f2c78080 +2bef0a1b8917f2d92fa2382f8da88e69 +2ca4c0c3f7c2a4ed62e853e033cb580c +2edeb1f3a58a8c7214cc208f58c1f622 +2f3e57af36038793d3f7f05287695bb8 +2f4db509bc545c49534f1c7d878c3ad2 +2fcd29fba6edce3eca2c1bcf311462e1 +304bd8b8c73fe11393cc4967c1a4cb2b +30ae94b2e35f03c6c947f6bf6be2033c +30f8d75e7ca1e318cbe18fbb1db36499 +31cb231d4ddff688c6f62857af1aa771 +3229d2ba3292eb787f6654e39b4d7b92 +32ed43f0b02f1ff022b60310e2ad9590 +33abb5faf9054598a2cc5157e571bd17 +34094a2031e3066d8185b5ee33a613ad +3421ad9227836280e1a44afba4c437ff +343b9e4cca7ac7474f0ab3018c3a7da2 +349170e695e10c46d0404f9c3c8cf56d +3561f8a532ee77fe5bc5de4d7d3b33a4 +35c38a1f17a5d8d08edd34928d7d9ce4 +35f02056ddfff5d7cf2e48edb8b955a3 +3646136c8a064ed087e86fa410eac27c +369d4b34700282c26d576d591e9ce9ac +37ae86a1c7ac488e456e5b7dab0160c0 +37d1b304d87cbb2bcb859a7764862403 +388092ecbc87458d5291e73194f9f234 +3992d1bd2296f75669a926459b438665 +39dc4dea73501b8b1f675a7d33212d36 +3a14a5025f0ec8d01eeca5e5d6eb7529 +3aad98263805709f1638a7bc6329686d +3bbd84d5d601283a87842af6e8d1b478 +3bf6f7546057c8b5bf34409f3eeef154 +3c26edf1c44f3012bcee5a980c6d45d1 +3e3de287fdfcfa3a2d6717613b1e1342 +3e43fce55704bd826a6d3402ffe08fb6 +3e9cb7db1a51f40fa2cb108d3082089c +3ebd6e32d4fe9c810179e6c48d7c1f51 +3f999ffe7e519688f44594c027c65868 +418f7dda0517968984cee7f6196c7c3f +420c5982bdb819c0182ab18b6c975e3f +437a211f55b3e77b9c01b10f344d7b13 +44737bffb137737074a4820d67a3f9f8 +451086ca191682e1e04fae4ce4b4fde4 +45b27847e1f92a9c0dceafd0622ef148 +461e06450fe6c6bc329e5ca7f01d3807 +4636a9e83195ead7c09ae4eed3604c26 +47f43d0d443d3d22e0040cd165ddfbf0 +484689e1f65094765fed0ee83988ce90 +4973222c575f7b248f69bdabb57f3934 +4a0c9d74f189b57ab074618da9db7d4f +4a62885d580255849f5c31213ed1fc76 +4f3fa007043f745a645e85e33d4d1e15 +4f84f5e694d328a7a512edcab51dda4c +4fd0e7ad0c373d3f0ce96e0b4536386c +5035613e03cfcd5036711c0c16fbb07d +504e92b77b4c9600dd42684c9c46ac8f +50a2f82c2112401d5c10b86067e989dc +51499745b5b033bc3609525b719e70ba +51c61fecebde1dec41268ad983b9af7b +532cda15a7e123137ec08a73e51ef657 +5361aa26c15a7b58c5f3f4ad4f7d8fdd +53e32a1bd573331a851fb82d5ef40c92 +541d5605e3bd398c77c07af0d188306d +5468ae6aae56f09acd05cc0de55dc847 +55d9599aebb58773c222fd4b5528faa6 +56421fad8b54a65b51fe708d4e87a2bb +5712eb6e7a802631b4064d7ee8d017b2 +573ce81720ca0600309ad1ff6aaab252 +5791c9f2014288f1bd14a81400764388 +58ce7ad9118ad5d8076eea4eb841c124 +58d29835648338d1b191d69c37af3005 +5923d83b6ef37c15a99bd568563a91a0 +598bb49275f4e8a429c95b22594c5b65 +5a35a9f0f0885dbb40f8130270a69438 +5a6603532732fcc8ecc5a1bc6cf5b0e0 +5c7975ba201f77be34b269b971688e39 +5d15855cb8acf73412196c6886429135 +5deae9580c5490efe0f2fab199c0afae +5e826efb8f586a3ecbacbd8ff0b16cee +6102e4f9e872c6d99a05a699956b496b +62d678ffa4099b494eea7e902326a155 +63c0dd9db4e60047d31223ab9974913a +64685e70b2804fac615063e47cb96caf +6484d2d39e8b4d3ea5c17eec0b0be6a9 +649df33823354d9b9fd0b1e5748ec8ec +65142dbd901833dc27fdaf090bd3e05c +65d162cde3f3462b78e81b503589dac7 +67393c7b37dc2f3396057181e4971ff1 +68f834d4e9e8f8bfd01e6b83dc2ad7cc +693fcefb8241c6554274e972241ef197 +69c93a0c8fe6e0f50e85800ccd4e3d59 +6ac45115bf60db0cd544aa270a587cc0 +6b378745e373dc04536923b8ff2ee6ac +6cc1ed12cdbd07028f7000478ab4c268 +6cfaeeb90d7d3bd4286c327cba36950d +6d1531f98443a6332c433f0282f4f1ff +7000ac1e72099352987818d692b69c98 +70fb05e0bf7a8dad62cd9cf8034f2800 +71cb48398b8d85e8fd7c87c15cf76499 +734b105ff0498d5d48ce4cdf9e442f01 +73e4e2a239154890491bdf18fad8b775 +74ebc86f5746f097fc5ba88f23077836 +75ac354ebb22204f57e6114731148e4c +764a51b28b23dcd54628a5bfdde2c640 +7662b13807b64e8d7aac352c4017b7c3 +767f1a8146a2cab71123bea6ac9a42fe +76c378c7b4e1e9dc31e4c5564284c502 +76f01d895c381ece3b254d158adee758 +76ff40de5de2682880d1a340364fb717 +787a102d65a2ce89ea36384358a96db2 +7a6abc86de7f815813f5cf2fba87a802 +7acb19556f12afdaff32487c7b40a253 +7b09e1936b91806fea47c46dfe450acd +7cc193fda3149652557c457f094ba9e6 +7d2c11fcb0491e573722c492f1e9133b +7e2f64627b283ef8055cde1ebfef7879 +7f5ca3d2481c195f4cfe18e952567fd1 +81226c5370b63a17458a0283aece5f1d +81a17efff945707081ffd96141398947 +83345dbb7705ab37ace0241678514bf5 +840943c87b94cf80870110ae9e91ab8a +848343fc7a34f93f91639da1daaaf352 +84f103cca761ee409770219d011acbf8 +851eb38a0ed2cf188ee41a614cc72b8a +8733eb6c27872f2274c76f0d25d51f41 +87c040f950dd2cbdfae1bb91c0e02745 +88481c08dd77404f0b2670ccd7e8b4c2 +8880915cba140caa11e086067834fcfe +88c51ca99633beabd0408afac36afb1e +88d16bb20eb8f933ca43eff04b291bea +8b370ff5a9d898dfa8c5e9423b8c3aa7 +8ba06e36e4145c873fd433966307ac0d +8d37cf8bca1a164796ed00b6d4818132 +8da67fd457248c8d1c6646a158157d5a +8ee36113f25b2a61f4a50a0c0be1a8b9 +8fb6106ccdea19b4a696e6e5d1c88b85 +903fca3e091fbcd20981ba71121e59b5 +90bd57599de3682d496fc134a6ca7784 +918a06659910a12e453c6a9b0f8af6b4 +92ce33b10d9f8c27811ed83cce568aa7 +93a3da398f4ebee9eb45f0f172510d9a +93a4ad1c2ec6455c7ec0e9f1d7632906 +94a5ca514fd13d7d852d491cb3de6be9 +9563ef148ed129d9bd8e86222827289d +9609b6fc03530eade2372d87cc630379 +976a5921e18156ebb7ab165de8a00ad8 +97f72274dbe839850cb3f48cfa5c563f +980102c730b942fde3d5d200dd25b7a9 +992126862a097e73df8723719bd47dc8 +99b41718c36c6e4f6b759ad7cef55fb8 +99fc18bb20d2258e918819edf8dd8f4d +9a834ddd93d8270abe7e78b95ab11a11 +9b131e8da1d660230c7013d6ebb1ae7c +9b8645b1e59bb33f56cf24d315cba7db +9be00052713a91ed180a782595d6c4fa +9c28971dcb6717650db490e3b31e4ad6 +9df322cf45bad9320f0c967d5391006b +9fc04d0cd12169859a2ba9cefdd2f8f0 +a0178ba1262d1dc60da1b65598baaa99 +a06aed8676bef85de1ca21fe3b325b32 +a0d1cb7c32a87985616198d2846b51b9 +a1c2da46c5666986bb753e42fbc8fb58 +a1edb64b942769ef7aad7f0cfcce2b3c +a224a7376e6c7a24182f12f92c63ef3a +a337b7a832c73d7ffcc7feebecef68e0 +a3dc34c677b1033fb94f48a10a466fb3 +a425d5a080289601a16d88b8a5e50218 +a44697cf0a4f4f73444e0f31200ab72b +a45c21ef3f207f9f7b041a63ed9f906c +a47da877bf24204a73a632c43660f2b0 +a4b05f06fea4444b86387c65a19ffb70 +a4e220668a1dd536e55c71ca3ee2b1e1 +a80492b3aa5765a94a32816684b37c3c +a844d2d36284289c47a5b40024deec58 +ac3c57ac8531b399a4fbc53e7e016345 +acc0c5168247900484c962952889d885 +ad15c971c60a23a4b4d87a91f37fc93a +ad9a070626201459f0afab25e1d6204f +add5badbe08dc1179897b5d2aa911128 +ae3d2852abb019268338e512d83cac0d +ae5ca8cbb52075dc74af54436bb2391d +af3916afc69791d838949515e8afafc1 +af54b8bafd2db837b549a452654548a2 +af9b1f09bacb6f1f6c46dc769050b5c3 +b0b9b9c718ede6aaf793a935a57ec721 +b0e6c372d3906553fb3ee9dcbb395805 +b155abcc7dea170ee1cdbf6d30a2b6dc +b172f5177c79cd45fba1b4ac4ad756e3 +b172f5aab0cb5518cc16f83a34454460 +b2304dea4c8f5c5d6ec94f4869ff7cc1 +b4984630bd9a8787c1d5e4db849628a2 +b55e64c27b10856f60b1387743705bc9 +b5bbc95742a92b5206ea1b113b769a64 +b5c6b70f7cba868d80632ed0359a2c00 +b61fa174679164d43fc4090330431ac1 +b77e673797fef285a75ab0c62fb7c1a1 +b81181be2bd1e2962a599cb123621c75 +b89c104b8d04c4f5acc10d882abe3cb9 +b8e7aba8eb559a534e010ffc904b7d93 +b8e7f1ffb94f2cd1fa7bfda2d69902b1 +ba06e2720d113fea7bc193e304f1b670 +ba77d46e76874e969dc563830e93b8a3 +bb4a79690b7245d048ea73fbee0361a6 +bb902756783fc13dc3dfd20e997a9936 +bba3f622160c0d7ae2c48136fce426a1 +bbe84eb05d04b55e0103ea6774d688f8 +bcee13a7b46a704e921472dc6679bf51 +bd00a2011518a78d5e54062730275568 +bd6f34d92804fc16a764137238dc918f +bdd659e0dccef938a1c6b48f87762088 +be666d57a410db4b0d7010b73ecb7238 +bec2dc50c6ebadc677a4fe90b52bf53d +c0e7c9b580f0bb9531ad9c34d440ec3a +c1ef48dcf2a708036832a3c2d894f7be +c22ee7527b8cf6c531ed60515187042e +c26975b00ca28afa2f6f8e0ac82f53d8 +c298d2854367a93fcb06abfce3321101 +c350bdad4ba6b227671edfea496ae718 +c3bc9b3315920a96c33315f60748a6c0 +c3f6a2b9622f04725589e4e5a7440b65 +c4a02ccbbae552696f91b483cd05a550 +c4bba2d3e82d3ffd38c922a504e7d923 +c6ea84b4ac082ce35448e0b63935a521 +c74e262c7247f5ecc5dd04b8e1a671ac +c7b08bbc50497a3fed7f7af19172b946 +c7dfc553b19b50bf15a2300d883705da +c9179cef6dcbe775a256c0eb896634fc +c93e051d2f1d7e8aea9e8edbbe4e29bb +c93febb21fa274594792b88c4044d074 +c945f5230bbe40449a0840af139fa202 +c98749c8486bca89a8a6fc4d5cd36188 +c9f248f7384650766f7f049830b97bee +ca8deb7ad94b8bdb25efd906477ec911 +cbcae9eeaabb4e090fc3d56d958b91a7 +cce723e5178cad25ba99858ddaffa82b +cdf47569859ddda5cfe1a2a863cd0b9c +ce5c153614f1fe4aa22f89dae79460bb +cf579db8e452130d2ff19ec145d1fa3b +cf8616bd1dece7501ab781d1000f94fa +cfe45d314b3c7bc00f1666097d5f249c +d0188c6fccdaa63d9d384f712a98a7f9 +d10300633acbffc845efa109d0257431 +d1ec36db5834566772636a3573b1eb39 +d25cd7dbb1251de33bff1b98bab851ee +d3889f686679c1dbd3301f86d0c5b9ad +d3e8c47c90237ab06a0696ca962200c8 +d43580094a091630c33d7f7f2b4a6ef6 +d4e7d476c915ee45849080b437a51dc3 +d4f771d60f0b9be1aee3f2e9abbf95ff +d575f737c70466cd9cd1c3cdc19d7efa +d5de0917326936622511a6dcf3ebd002 +d6b010c94c0780b390aa3cc4ca4a5fda +d788f83cd13a4ca8cd335c3b41cd3611 +d886f197056dade1824a822df0bab087 +d95ab562243aa9c9d855c3e0febd980d +d9f4df1960eeace7d1b5b49857fba94d +db609579af7daa92cedcb9d7857939bd +dc0f2adc255012950070b555d3ae8aa0 +dc76229d09eac7a4806245a50018354d +dd8d11ee7835e6335b61bcef143223b4 +de258a391092134a43bff7dc8e4d8099 +dec13e5378d49626fd506334b351bdab +dfb8f78f1473094554b998023935d685 +e0a6e036a5016235879d2bc599d8c747 +e1f8965216ddde6e1491e6e9888fa96d +e254ea5a9e4cd021648174de43d09473 +e363b18b83d5b78d9317b8af69cee913 +e411959bc4c2d975a6e8e50e38359e48 +e4c8bed7dec8ccde3a65cc19d390c1b4 +e4eb09c77f6ec5d82df1fbb3dbcf1e39 +e521f5e903d3b201b0831b0eb808cc1b +e5e6ea3d0fa9c665cae3c8bb2003febe +e67333466d0043c12ebf22c17e4d040f +e6eff74504f84d862ae2302b883a77ae +e6f96942696a169a8a63c9c8646b8404 +e725a1cdafbeb3fe7c9ea12c67fa39a6 +e74deee71932cc56dce52597e1acbaa0 +e7aef47b9c967424f226b2680dc98bdd +e7dca84e19788bf2a7bc017ad0cdce9e +e800d08588e36e86862d1fb61baa34d7 +e80a7402cbccbac0d2f62dd6d462ab21 +e8e75c205f1cb9afd38a091d85083799 +e8f36d03873fb7d65cb6a8af8b25abcb +e902c993bfda905cf8be9499a6bca724 +e91c7954dd547f4efaab699faa0fc609 +ea5afaa9f963f39ec5f0c336b6f67388 +eaa51f7e1d4dc5ad36939fde4a4fe320 +ec891dc017caeb187825a7aabb78a73c +ed533bd7c925a38a8b0b11df5eed62e5 +eddb6cb3b9780be0085d2e8c62ba7d61 +ee1a768df7f3c51c95705a93941ece00 +ee37d0284ca2774152709b617f871e65 +ee9a27f45aec5a2b5188c634a4c51d32 +ee9dcb8f69d5f9aa43bc79eb458a53e0 +ef488a1cfd781eea80d9c73ef32d995d +efac461e8baf6d31652ce5c3c443e438 +efafe9feaa666b91084a267210b607d8 +f11fe21b35e2a04b2aab8bed9bcb00d0 +f35782a7d0a07604b14864af1eb1855c +f39dd98fe1e8d021fbca3d24a0699f35 +f42c31c26bf7edc3852dc3444daebaab +f43585d5fe8f1f38ef04cfca37f7ebb4 +f536d24831ab75f64fbab4b7ed2722a3 +f58056e48d2417a1d5e7c39192f45aba +f5a3730e37160e0e5731b7f45ea41d4d +f64652f9ed00aa01c5ffd214e242951e +f776b17a38b82e683fe1ed648f271895 +f82a8c421898108cd619369e9fa070d7 +f861c5553f23f97fca25788a0a775915 +f87f57ba6eb47be30418dfcb6ac44131 +f88a8a20ad393b9eab5cca1de9e0f129 +f8a4c8e2cbf4660ece7a2b68d78acb3e +f8d93f23f459f985082530485b55217b +f9407e36e43f76a56661e2616eeaa543 +f94641df7ab2041b66b41fca939f0e96 +fa414bf69558ef4bea57cde61af4e748 +fb803806a358234dd933bcb51bde09e0 +fc9b5549bdc9a04e57b9bd3034da7c42 +ff975027954f4a69e0813471984e2269 \ No newline at end of file