diff --git a/ci/style.sh b/ci/style.sh index 5b200796a8c53..44e371583e84e 100755 --- a/ci/style.sh +++ b/ci/style.sh @@ -60,7 +60,8 @@ done < "$tmpfile" rm "$tmpfile" if shellcheck --version ; then - find . -name '*.sh' -print0 | xargs -0 shellcheck + # FIXME(ctest): update ctest scripts so we don't need to exclude them + find . -name '*.sh' -not -path './ctest/*' -print0 | xargs -0 shellcheck else echo "shellcheck not found" exit 1 diff --git a/ctest/.github/workflows/linux.yml b/ctest/.github/workflows/linux.yml new file mode 100644 index 0000000000000..e80048efa35c1 --- /dev/null +++ b/ctest/.github/workflows/linux.yml @@ -0,0 +1,46 @@ +name: CI (Linux) + +on: + pull_request: + push: + branches: + - master + +jobs: + build_and_test: + strategy: + fail-fast: false + matrix: + version: + - 1.63.0 # MSRV + - stable + - beta + - nightly + target: + - x86_64-unknown-linux-gnu + + name: ${{ matrix.version }} - ${{ matrix.target }} + runs-on: ubuntu-24.04 + + steps: + - uses: actions/checkout@v4 + + - name: Install ${{ matrix.version }} + run: TOOLCHAIN=${{ matrix.version }} TARGET=${{ matrix.target }} sh ./ci/install-rust.sh + + - name: Check MSRV + if: matrix.version == '1.63.0' + run: cargo check + + # FIXME: Some symbols cause multiple definitions error on the same line: + # /usr/bin/ld: /home/runner/work/ctest2/ctest2/target/debug/deps/libtestcrate-a072d428f9532abb.rlib(t1gen.o): + # /home/runner/work/ctest2/ctest2/testcrate/src/t1.h:65: multiple definition of `T1_static_mut_u8'; + # /home/runner/work/ctest2/ctest2/target/debug/deps/libtestcrate-a072d428f9532abb.rlib(t1.o): + # /home/runner/work/ctest2/ctest2/testcrate/src/t1.h:65: first defined here + # - name: Run tests + # if: matrix.version != '1.63.0' + # run: cargo test --all -- --nocapture + + - name: Run libc tests + if: matrix.version != '1.63.0' + run: sh ./ci/run-docker.sh ${{ matrix.target }} diff --git a/ctest/.github/workflows/macos.yml b/ctest/.github/workflows/macos.yml new file mode 100644 index 0000000000000..e893535b2da94 --- /dev/null +++ b/ctest/.github/workflows/macos.yml @@ -0,0 +1,36 @@ +name: CI (macOS) + +on: + pull_request: + push: + branches: + - master + +jobs: + build_and_test: + strategy: + fail-fast: false + matrix: + version: + - stable + - beta + - nightly + target: + - x86_64-apple-darwin + + name: ${{ matrix.version }} - ${{ matrix.target }} + runs-on: macos-14 + + steps: + - uses: actions/checkout@v4 + + - name: Install ${{ matrix.version }} + run: TOOLCHAIN=${{ matrix.version }} TARGET=${{ matrix.target }} sh ./ci/install-rust.sh + + - name: Run tests + run: cargo test --all -- --nocapture + + - name: Run libc tests + env: + TARGET: ${{ matrix.target }} + run: sh ./ci/run.sh ${{ matrix.target }} diff --git a/ctest/.github/workflows/windows.yml b/ctest/.github/workflows/windows.yml new file mode 100644 index 0000000000000..3eeed6deb700a --- /dev/null +++ b/ctest/.github/workflows/windows.yml @@ -0,0 +1,66 @@ +name: CI (Windows) + +on: + pull_request: + push: + branches: + - master + +jobs: + build_and_test: + strategy: + fail-fast: false + matrix: + version: + - nightly + target: + #- x86_64-pc-windows-gnu + - x86_64-pc-windows-msvc + #- i686-pc-windows-gnu + - i686-pc-windows-msvc + include: + #- target: x86_64-pc-windows-gnu + # arch: x86_64 + - target: x86_64-pc-windows-msvc + arch: x86_64 + #- target: i686-pc-windows-gnu + # arch: i686 + - target: i686-pc-windows-msvc + arch: i686 + + name: ${{ matrix.version }} - ${{ matrix.target }} + runs-on: windows-2022 + + steps: + - uses: actions/checkout@v4 + + - name: Install MinGW (i686) + if: matrix.arch == 'i686' + run: | + choco install mingw --x86 --force + + - name: Find GCC libraries + run: | + set -ex + gcc -print-search-dirs + find "C:\ProgramData\Chocolatey" -name "crt2*" + find "C:\ProgramData\Chocolatey" -name "dllcrt2*" + find "C:\ProgramData\Chocolatey" -name "libmsvcrt*" + shell: bash + + - name: Fix MinGW + run: | + set -ex + if [[ -n ${ARCH_BITS} ]]; then + for i in crt2.o dllcrt2.o libmingwex.a libmsvcrt.a ; do + cp -f "/C/ProgramData/Chocolatey/lib/mingw/tools/install/mingw${ARCH_BITS}/${ARCH}-w64-mingw32/lib/$i" "`rustc --print sysroot`/lib/rustlib/${TARGET}/lib" + done + fi + shell: bash + + - name: Install ${{ matrix.version }} + run: TOOLCHAIN=${{ matrix.version }} TARGET=${{ matrix.target }} sh ./ci/install-rust.sh + shell: bash + + - name: Run tests + run: cargo test --all -- --nocapture diff --git a/ctest/.gitignore b/ctest/.gitignore new file mode 100644 index 0000000000000..a9d37c560c6ab --- /dev/null +++ b/ctest/.gitignore @@ -0,0 +1,2 @@ +target +Cargo.lock diff --git a/ctest/CHANGELOG.md b/ctest/CHANGELOG.md new file mode 100644 index 0000000000000..15791bdd6efb2 --- /dev/null +++ b/ctest/CHANGELOG.md @@ -0,0 +1,615 @@ +# Changelog + +## 0.4.7 (2023-06-10) + +### Commit Statistics + + + + - 4 commits contributed to the release over the course of 7 calendar days. + - 35 days passed between releases. + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). + - 0 issues like '(#ID)' were seen in commit messages + +### Commit Details + + + +
view details + + * **Uncategorized** + - Merge pull request #53 from JohnTitor/forgetting_copy_types ([`4c5c32f`](https://github.com/JohnTitor/ctest2/commit/4c5c32fcde07c3ccaed61f42dc434f0f90682a3f)) + - Address `forgetting_copy_types` lint ([`adcc889`](https://github.com/JohnTitor/ctest2/commit/adcc889d30550c92e135d74820477f022ddb5bbd)) + - Merge pull request #52 from samkearney/add-x86-qnx-support ([`a40b8af`](https://github.com/JohnTitor/ctest2/commit/a40b8afcbd55fbca30911f07b406157c848cdaeb)) + - Add a config default for QNX 7.0 ([`2af1537`](https://github.com/JohnTitor/ctest2/commit/2af153709b41386aa664ef78d1d9acb35455880b)) +
+ + +## 0.4.6 (2023-05-06) + +### Bug Fixes + + - don't use periods in target names + See https://github.com/rust-lang/rust/pull/104523 + +### Commit Statistics + + + + - 6 commits contributed to the release over the course of 150 calendar days. + - 167 days passed between releases. + - 1 commit was understood as [conventional](https://www.conventionalcommits.org). + - 0 issues like '(#ID)' were seen in commit messages + +### Commit Details + + + +
view details + + * **Uncategorized** + - Merge pull request #32 from zhaixiaojuan/master ([`b26e82d`](https://github.com/JohnTitor/ctest2/commit/b26e82da33d004184f251a61ae1eb3de3cf75b6c)) + - Add loongarch64 support ([`64cc233`](https://github.com/JohnTitor/ctest2/commit/64cc2337bc38b4654480a9c0adabaf67e9f6817f)) + - Merge pull request #49 from Amanieu/ohos ([`e6b5909`](https://github.com/JohnTitor/ctest2/commit/e6b59094cd18ec6e044f3d2564044fde04c21cb7)) + - Add support for OpenHarmony ([`1d1af22`](https://github.com/JohnTitor/ctest2/commit/1d1af22b80cb4812dbb65cedc063f234603c7553)) + - Merge pull request #47 from flba-eb/remove_period_in_target_name ([`dbc023a`](https://github.com/JohnTitor/ctest2/commit/dbc023a7fc538eb86a62a9d282a7946a4c7312cd)) + - Don't use periods in target names ([`7b3154c`](https://github.com/JohnTitor/ctest2/commit/7b3154cca43bb0f90a0dd7a6206559f190d625b9)) +
+ +## 0.4.5 (2022-11-19) + +### Commit Statistics + + + + - 12 commits contributed to the release over the course of 133 calendar days. + - 190 days passed between releases. + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). + - 0 issues like '(#ID)' were seen in commit messages + +### Commit Details + + + +
view details + + * **Uncategorized** + - Merge pull request #46 from flba-eb/add_nto_support ([`6287035`](https://github.com/JohnTitor/ctest2/commit/6287035dd465536f7799516f6954bc454d1d0f17)) + - Fix QNX/nto support of 7.1 ([`494273a`](https://github.com/JohnTitor/ctest2/commit/494273ae59a776f8bf771778232290c6fa3e3116)) + - Merge pull request #41 from wesleywiser/master ([`f419830`](https://github.com/JohnTitor/ctest2/commit/f419830d09cefb31e2b076039b47b925e53ce6b9)) + - Merge pull request #42 from flba-eb/add_qnx_support ([`0db6046`](https://github.com/JohnTitor/ctest2/commit/0db6046dd6c5cf816d8cdc5aec5415e535ea4db9)) + - Add basic QNX support ([`3d3a0b1`](https://github.com/JohnTitor/ctest2/commit/3d3a0b19a7312f6557a32b80f68981d2092aa198)) + - Merge pull request #44 from JohnTitor/fix-typos ([`2c609e2`](https://github.com/JohnTitor/ctest2/commit/2c609e285c16fdc8dde02509c82786c4c48a959f)) + - Fix some typos ([`1fa9856`](https://github.com/JohnTitor/ctest2/commit/1fa985693300df5597b7e333f293e4660dd02593)) + - Swap size and value args in roundtrip tests ([`ed9b5d6`](https://github.com/JohnTitor/ctest2/commit/ed9b5d675545dcafe5f13f95f2dada2812932cf2)) + - Merge pull request #39 from joshtriplett/code-block-terminators ([`ce8fc54`](https://github.com/JohnTitor/ctest2/commit/ce8fc54e5f1b0a0fc2356601acdc8cdc56a22b76)) + - Fix missing code block terminators in docstrings ([`3ea3965`](https://github.com/JohnTitor/ctest2/commit/3ea396523425e5f3948146bde869f5d0ccd0e2ca)) + - Merge pull request #37 from flba-eb/master ([`ec09955`](https://github.com/JohnTitor/ctest2/commit/ec09955c349bb6a999ecf1ce4492d7284c024b98)) + - Print out errors to stderr ([`aacc9e6`](https://github.com/JohnTitor/ctest2/commit/aacc9e64d2a372e74d7d8e9066881645bb444827)) +
+ +## v0.4.4 (2022-05-12) + +### Commit Statistics + + + + - 4 commits contributed to the release over the course of 15 calendar days. + - 164 days passed between releases. + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). + - 0 issues like '(#ID)' were seen in commit messages + +### Commit Details + + + +
view details + + * **Uncategorized** + - Merge pull request #35 from JohnTitor/fix-offset-of-again ([`6d2dd7d`](https://github.com/JohnTitor/ctest2/commit/6d2dd7dbfda82207ba151ab277af38cae585cf45)) + - Remove the use of `mem::zeroed()` on generated code ([`e8b215c`](https://github.com/JohnTitor/ctest2/commit/e8b215c54a6c2784e475790a0c1bda18cab4169c)) + - Merge pull request #33 from jessicah/cpp-linkage ([`2054b80`](https://github.com/JohnTitor/ctest2/commit/2054b803d038916abc834856cbe177ffa18e8455)) + - Specify linkage for `__test_fn...()`. ([`5684414`](https://github.com/JohnTitor/ctest2/commit/5684414735b58683451e8a364c7ede94f101969e)) +
+ +## v0.4.3 (2021-11-28) + +### Commit Statistics + + + + - 2 commits contributed to the release. + - 42 days passed between releases. + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). + - 0 issues like '(#ID)' were seen in commit messages + +### Commit Details + + + +
view details + + * **Uncategorized** + - Merge pull request #30 from GuillaumeGomez/long-double ([`1769ea1`](https://github.com/JohnTitor/ctest2/commit/1769ea1772db11147b8cfcc1c0b9095865d2d1de)) + - Add support for long double ([`76bcbd5`](https://github.com/JohnTitor/ctest2/commit/76bcbd5fa20e86d7d39ecf8300744b4e008f5f18)) +
+ +## v0.4.2 (2021-10-16) + +### Commit Statistics + + + + - 2 commits contributed to the release. + - 146 days passed between releases. + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). + - 0 issues like '(#ID)' were seen in commit messages + +### Commit Details + + + +
view details + + * **Uncategorized** + - Merge pull request #28 from GuillaumeGomez/subtract ([`aaf6759`](https://github.com/JohnTitor/ctest2/commit/aaf6759f061a398b59bd2abca59ce8ba7414e3d4)) + - Allow to subtract in constants ([`17df391`](https://github.com/JohnTitor/ctest2/commit/17df3916cd84b0ba626b438e18eb3812ba7340ec)) +
+ +## v0.4.1 (2021-05-23) + +### Commit Statistics + + + + - 4 commits contributed to the release. + - 115 days passed between releases. + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). + - 0 issues like '(#ID)' were seen in commit messages + +### Commit Details + + + +
view details + + * **Uncategorized** + - Merge pull request #25 from JohnTitor/fix-unaligned-references ([`aab0ed6`](https://github.com/JohnTitor/ctest2/commit/aab0ed632207eb984b33d8ff9585d504ff22b9d7)) + - Fix `unaligned_references` warning ([`58fe3e5`](https://github.com/JohnTitor/ctest2/commit/58fe3e5dc1fec08d067cc41149bb7d75048077eb)) + - Merge pull request #24 from JohnTitor/fix-dereference-null-ptr ([`64fc427`](https://github.com/JohnTitor/ctest2/commit/64fc4270a50427f322351543fbf6f0fc151586d6)) + - Fix the `deref_nullptr` warning ([`5d5b425`](https://github.com/JohnTitor/ctest2/commit/5d5b425979b49e37d4ee5629caf2723b10886679)) +
+ +## v0.4.0 (2021-01-28) + +### Commit Statistics + + + + - 7 commits contributed to the release over the course of 159 calendar days. + - 240 days passed between releases. + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). + - 0 issues like '(#ID)' were seen in commit messages + +### Commit Details + + + +
view details + + * **Uncategorized** + - Merge pull request #20 from nielx/master ([`f2842c7`](https://github.com/JohnTitor/ctest2/commit/f2842c73a6c04476701a88ad1edc8b0e943634b8)) + - Add support for the Haiku target ([`193a72f`](https://github.com/JohnTitor/ctest2/commit/193a72fb5beb8008bb2256709bac8c240685d079)) + - Merge pull request #16 from JohnTitor/edition-2018 ([`82ef3da`](https://github.com/JohnTitor/ctest2/commit/82ef3da023da04a5f9ed401e3671a881b8db6d07)) + - Fix Clippy warnings ([`4aafd94`](https://github.com/JohnTitor/ctest2/commit/4aafd946470e016fff02af26bb6dba2c49d1c683)) + - Update crate to edition 2018 ([`1de5a4c`](https://github.com/JohnTitor/ctest2/commit/1de5a4cac5a33921135a0e7503240299bcc9f295)) + - Merge pull request #13 from JohnTitor/use-or ([`bb83079`](https://github.com/JohnTitor/ctest2/commit/bb830791b08edd5589874f70cb3bab4cf551e307)) + - Use `OR` keyword instead of deprecated `/` ([`76dab1c`](https://github.com/JohnTitor/ctest2/commit/76dab1cef5d4dbb81147f9ea3a45b0b2c3ce7354)) +
+ +## v0.3.0 (2020-06-01) + + + +### Other + + - :uninitialized is deprecated + +### Commit Statistics + + + + - 45 commits contributed to the release over the course of 551 calendar days. + - 553 days passed between releases. + - 1 commit was understood as [conventional](https://www.conventionalcommits.org). + - 0 issues like '(#ID)' were seen in commit messages + +### Commit Details + + + +
view details + + * **Uncategorized** + - Merge pull request #11 from JohnTitor/garando ([`fc6621f`](https://github.com/JohnTitor/ctest2/commit/fc6621f5199a45108af5035eda86996ef88aaeaf)) + - Use garando_syntax ([`732c7b5`](https://github.com/JohnTitor/ctest2/commit/732c7b531073a57f186321c7799d3b4cda639856)) + - Merge pull request #8 from JohnTitor/ctest2 ([`1432332`](https://github.com/JohnTitor/ctest2/commit/1432332bdeda9e64c519ce72734170af2bb61d96)) + - Rename to ctest2 ([`bdadd0b`](https://github.com/JohnTitor/ctest2/commit/bdadd0b4c0c6db84abcb856cfa6267ecdfca8bf9)) + - Merge pull request #5 from JohnTitor/riscv64gc ([`8ac57fb`](https://github.com/JohnTitor/ctest2/commit/8ac57fb0dfb90b12f41d598449adc80dd7a00156)) + - Add `riscv64gc` arch support ([`fd7bfb2`](https://github.com/JohnTitor/ctest2/commit/fd7bfb29820fcbe6457c97cb4ae761e6fb02d56e)) + - Merge pull request #4 from JohnTitor/maybe-uninit ([`4787a04`](https://github.com/JohnTitor/ctest2/commit/4787a04dacf80ea7d59e89d2414daef9cea5f4d8)) + - Use MaybeUninit instead of uninitialized ([`f8ada39`](https://github.com/JohnTitor/ctest2/commit/f8ada39ce4e8c596650bcaa184d83fae488f2b2c)) + - Merge pull request #2 from pfmooney/target-error ([`466af76`](https://github.com/JohnTitor/ctest2/commit/466af761070a82f97ccec6c6f2d36ab84462eb6f)) + - Merge pull request #1 from pfmooney/illumos-target ([`3c1bb06`](https://github.com/JohnTitor/ctest2/commit/3c1bb06272e4797a8d1ed86c08a0008c88f0eb7d)) + - Fix typo in "unknown target" error message ([`04bac2c`](https://github.com/JohnTitor/ctest2/commit/04bac2c7c25bbeea0f05b78e11c39c904d4a2005)) + - Add illumos target ([`1b4efff`](https://github.com/JohnTitor/ctest2/commit/1b4efff47d23624bbad42a5741e4cc4476560e8e)) + - Add supporting for vxWorks ([`570e058`](https://github.com/JohnTitor/ctest2/commit/570e0584f0dc4aeab6ff4e9c07190c346a45beeb)) + - Improve error output ([`cf096e1`](https://github.com/JohnTitor/ctest2/commit/cf096e1a0f24072c8016dd649f1349a6785aa15b)) + - Improve errors of roundtrip tests ([`ddf07ea`](https://github.com/JohnTitor/ctest2/commit/ddf07ea5759f9605da968c391007117847553a0a)) + - Fix roundtrip tests for structs larger than 252 bytes ([`2d3be56`](https://github.com/JohnTitor/ctest2/commit/2d3be569b07f413a7639419d84391d1d04f4d6fe)) + - Merge pull request #78 from gnzlbg/unknown_warning ([`e9b8697`](https://github.com/JohnTitor/ctest2/commit/e9b8697b4eceae69712dda6e4c55c9c42971b887)) + - :uninitialized is deprecated ([`0bbbc85`](https://github.com/JohnTitor/ctest2/commit/0bbbc85fbd211f33815b39d9f472cf22d088846f)) + - Avoid errors on unknown warnings ([`57da2c3`](https://github.com/JohnTitor/ctest2/commit/57da2c3b2e74a2ff135d0369647c1fe2a3b3bf96)) + - Fix alignment computation once and for all ([`c870733`](https://github.com/JohnTitor/ctest2/commit/c87073322872c61371997666752e159f5e582d11)) + - Use a custom alignof implementation ([`0880937`](https://github.com/JohnTitor/ctest2/commit/0880937f3550c3f88ed6f5da237144241e9aed9c)) + - Temporarily remove MaybeUninit ([`f8c43dc`](https://github.com/JohnTitor/ctest2/commit/f8c43dce0f71758c8c8467b44217beca582714b7)) + - Workaround mingw being broken: rust-lang/47048 ([`79dc2a0`](https://github.com/JohnTitor/ctest2/commit/79dc2a0ec63a1fc991457cdafda8e9a42abb238a)) + - Use MaybeUninit to store invalid representations and only check non-padding bytes ([`5daf96b`](https://github.com/JohnTitor/ctest2/commit/5daf96b3597ef89993e402021be98f0ad58fb40c)) + - Silence spurous warnings on MSVC ([`ffe2573`](https://github.com/JohnTitor/ctest2/commit/ffe25732319b983966d3886c795494c053eac014)) + - Add ABI roundtrip test ([`5ab52f3`](https://github.com/JohnTitor/ctest2/commit/5ab52f3b8f6086f23b48ae176fd42d8dd85c6f5d)) + - Add support for transparent and packed(N) types ([`6e1f066`](https://github.com/JohnTitor/ctest2/commit/6e1f066e673ef12576554016de768ae37d552e46)) + - Add a repr(packed(N)) test ([`2460886`](https://github.com/JohnTitor/ctest2/commit/24608866b5543787dddf5bdb78a1097610b51e8d)) + - Silence another msvc warning ([`99e89ab`](https://github.com/JohnTitor/ctest2/commit/99e89abd8259f836300ea4b1de3466058b6e7a6f)) + - Allow taking references to packed struct fields in MSVC ([`6f1a656`](https://github.com/JohnTitor/ctest2/commit/6f1a656c1eb2c0fb864139f75b00fa308382f634)) + - Add redox target ([`835506d`](https://github.com/JohnTitor/ctest2/commit/835506db973410dfed4385be127eea1d9dcd0bea)) + - Fix build by running rustfmt ([`b645216`](https://github.com/JohnTitor/ctest2/commit/b64521631710dc950ffde055973fc92c02a3b120)) + - Add a way to specify that a function argument is an array in C ([`f551ca7`](https://github.com/JohnTitor/ctest2/commit/f551ca713949dd3c70a22bea4c7c04fc06c759ce)) + - Improve support for arrays ([`fb76cbc`](https://github.com/JohnTitor/ctest2/commit/fb76cbc2bdaed2d0efc6ffada50a7c8003fd3ee6)) + - Clippy ([`cc5690a`](https://github.com/JohnTitor/ctest2/commit/cc5690a180a1f713d6cf2f587eb06eebe8a728eb)) + - Formatting ([`5dd2909`](https://github.com/JohnTitor/ctest2/commit/5dd2909a2801c65e90b4d781bce87f1dc7e238fb)) + - Add support for verifying volatile pointers ([`a349575`](https://github.com/JohnTitor/ctest2/commit/a3495758a023f0a8f53dab22d8d2029ce3aaf6f1)) + - Add wasi definitions ([`c908271`](https://github.com/JohnTitor/ctest2/commit/c9082716c4438c9b71438ec8da8f604f53695bd2)) + - Add an option to print skipped items ([`9b8a31e`](https://github.com/JohnTitor/ctest2/commit/9b8a31e78884255476a37b3de7c504b8427ab579)) + - Check that the tests do not emit warnings ([`af6d9a3`](https://github.com/JohnTitor/ctest2/commit/af6d9a3edc798336dc7cf9e10e24cfc6c0d07de1)) + - Merge pull request #58 from gnzlbg/const_name ([`8111142`](https://github.com/JohnTitor/ctest2/commit/8111142a7226e20821e73b4685cb5f86b7b39932)) + - Fix clippy and formatting ([`3548657`](https://github.com/JohnTitor/ctest2/commit/3548657cd38e0b947ca2bc62cff201ded098fa70)) + - Add C++ support ([`413f843`](https://github.com/JohnTitor/ctest2/commit/413f8439166979e8884065dee0ec157036e08820)) + - Add const_cname API to map Rust const names to C names ([`75722df`](https://github.com/JohnTitor/ctest2/commit/75722dfbceb8d5cd3a1aa7135ec2aa5282598869)) + - Add support for packed structs ([`3e2ac3a`](https://github.com/JohnTitor/ctest2/commit/3e2ac3ab9c785866c6e4eb37df903d3defd38d17)) +
+ +## v0.2.7 (2018-11-25) + +### Commit Statistics + + + + - 5 commits contributed to the release over the course of 27 calendar days. + - 27 days passed between releases. + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). + - 0 issues like '(#ID)' were seen in commit messages + +### Commit Details + + + +
view details + + * **Uncategorized** + - Fix generates tests with C function conflict ([`96ca3fa`](https://github.com/JohnTitor/ctest2/commit/96ca3fa81605aa9a7d55a74f64c6b2df9af9ded1)) + - Support statics of Option types ([`ee2fd33`](https://github.com/JohnTitor/ctest2/commit/ee2fd33d94fd4f7640b4b989e1f3b9b876cdbbc3)) + - Support non-mut statics with Option<&T> ([`31cae7d`](https://github.com/JohnTitor/ctest2/commit/31cae7d94f86bc90015a25a4c28c4cf499b42ae6)) + - Fix clippy issues ([`e17d4ae`](https://github.com/JohnTitor/ctest2/commit/e17d4ae67c3d9b380053b20644b701bcf67dec18)) + - Re-format ([`eab052c`](https://github.com/JohnTitor/ctest2/commit/eab052cb32b6c7cfbaef3bfc48a998a26a98693a)) +
+ +## v0.2.4 (2018-10-29) + +### Commit Statistics + + + + - 3 commits contributed to the release over the course of 8 calendar days. + - 9 days passed between releases. + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). + - 0 issues like '(#ID)' were seen in commit messages + +### Commit Details + + + +
view details + + * **Uncategorized** + - Merge pull request #46 from gnzlbg/fb2 ([`dae2780`](https://github.com/JohnTitor/ctest2/commit/dae27809d8f2abf692d8d6b8a7885b2ab9ff77b4)) + - Add support for extern static references and optional references ([`07c96c6`](https://github.com/JohnTitor/ctest2/commit/07c96c674233970eaf1e8674039b07b4c8a9645b)) + - Merge pull request #43 from johnschug/skip-sign ([`9480ee3`](https://github.com/JohnTitor/ctest2/commit/9480ee376ef3b24567ca0b208b7e9b9faa19f82b)) +
+ +## v0.2.3 (2018-10-20) + +### Commit Statistics + + + + - 6 commits contributed to the release over the course of 3 calendar days. + - 37 days passed between releases. + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). + - 0 issues like '(#ID)' were seen in commit messages + +### Commit Details + + + +
view details + + * **Uncategorized** + - Merge pull request #42 from gnzlbg/arrs ([`f0fea68`](https://github.com/JohnTitor/ctest2/commit/f0fea68bf69c27e5e08b5a10528387ba367fa254)) + - Skip signedness checks for non-integer type aliases ([`2099f61`](https://github.com/JohnTitor/ctest2/commit/2099f6179f476aef903ca6a3131e043e75c06cec)) + - Add support for arrays in extern statics ([`d389610`](https://github.com/JohnTitor/ctest2/commit/d389610b9ece689a2b3e602eacb9cd19af8af50f)) + - Merge pull request #39 from gnzlbg/rs2c ([`f8bd332`](https://github.com/JohnTitor/ctest2/commit/f8bd33266921636ce99c1026ddb429095b896921)) + - Add support for nested functions ([`30f5187`](https://github.com/JohnTitor/ctest2/commit/30f5187a6a456942015f6361bae045ff06736dca)) + - Minimal support for fn types in extern statics ([`9375d57`](https://github.com/JohnTitor/ctest2/commit/9375d57cf2151069306ea04222bcad2d61862e9c)) +
+ +## v0.2.2 (2018-09-12) + +### Commit Statistics + + + + - 2 commits contributed to the release. + - 8 days passed between releases. + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). + - 0 issues like '(#ID)' were seen in commit messages + +### Commit Details + + + +
view details + + * **Uncategorized** + - Merge pull request #37 from tbu-/pr_test_statics2 ([`5daab1d`](https://github.com/JohnTitor/ctest2/commit/5daab1daf0a361f81f83d98b7def856ff289b01b)) + - Revert bump in the minimum supported Rust version ([`92bc00e`](https://github.com/JohnTitor/ctest2/commit/92bc00eecc1b8725ecaa318df860664a91846fe4)) +
+ +## v0.2.1 (2018-09-04) + +### Commit Statistics + + + + - 2 commits contributed to the release over the course of 22 calendar days. + - 22 days passed between releases. + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). + - 0 issues like '(#ID)' were seen in commit messages + +### Commit Details + + + +
view details + + * **Uncategorized** + - Add static testing ([`f665acc`](https://github.com/JohnTitor/ctest2/commit/f665accfdc52b3f887cc59511aaf2c3009b902b1)) + - Merge pull request #35 from gnzlbg/doc0 ([`e8cd0c2`](https://github.com/JohnTitor/ctest2/commit/e8cd0c2f341fe5211a9a25f646df0a3352a8eec8)) +
+ +## v0.2.0 (2018-08-12) + +### Commit Statistics + + + + - 9 commits contributed to the release over the course of 190 calendar days. + - 192 days passed between releases. + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). + - 0 issues like '(#ID)' were seen in commit messages + +### Commit Details + + + +
view details + + * **Uncategorized** + - Improve docs of cfg method ([`e9355e7`](https://github.com/JohnTitor/ctest2/commit/e9355e738e3e25c5b51114f9bc7974da83040c6c)) + - Merge pull request #34 from afdw/master ([`bf780a0`](https://github.com/JohnTitor/ctest2/commit/bf780a0e62caf4fb4747bd683713864b444bd6fb)) + - Add support for unions without typedefs ([`8078823`](https://github.com/JohnTitor/ctest2/commit/80788238ecaca2a610efea3f5c46ea9f23f88121)) + - Merge pull request #29 from glandium/target_endian ([`94815ca`](https://github.com/JohnTitor/ctest2/commit/94815cadb00ab7bcee8b2f9b903c4db7f14c6fa1)) + - Add target_endian to the set of #[cfg()] items that are considered. ([`25b5b64`](https://github.com/JohnTitor/ctest2/commit/25b5b64e36147aab36a83cb1ffd5432c09a317ce)) + - Merge pull request #28 from gnzlbg/deprecated ([`954f493`](https://github.com/JohnTitor/ctest2/commit/954f493d482a0873866ba335bee75ce2936e5415)) + - Allow deprecated declarations on non-msvc targets ([`a24ccd2`](https://github.com/JohnTitor/ctest2/commit/a24ccd2ad9995915f6be99dcf6e548f2bdfafe14)) + - Merge pull request #27 from gnzlbg/fix_bug ([`d3a5248`](https://github.com/JohnTitor/ctest2/commit/d3a5248c49ced9e5d42ebd74ee494db2988e7bad)) + - Panic on non-repr(C) structs only if the struct should not be skipped ([`c79cfd9`](https://github.com/JohnTitor/ctest2/commit/c79cfd9d02aa6e5f6026e79df8b47bcdd34ec597)) +
+ +## v0.1.7 (2018-02-01) + +### Commit Statistics + + + + - 5 commits contributed to the release over the course of 106 calendar days. + - 134 days passed between releases. + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). + - 0 issues like '(#ID)' were seen in commit messages + +### Commit Details + + + +
view details + + * **Uncategorized** + - Fix checking structs with `#[derive]` ([`b590c29`](https://github.com/JohnTitor/ctest2/commit/b590c2958b010d0a2512b5b81abccd8879f65604)) + - Merge pull request #25 from bgermann/master ([`29d33e2`](https://github.com/JohnTitor/ctest2/commit/29d33e26d8f1297736c3716f4f2495dd068849ef)) + - Solaris support ([`8182b0c`](https://github.com/JohnTitor/ctest2/commit/8182b0cb0e73cc5b314886adeed9ebdf32d6e769)) + - Merge pull request #22 from malbarbo/x32 ([`621f64e`](https://github.com/JohnTitor/ctest2/commit/621f64e78e25e71aca65f023591508dec188ae92)) + - Add support for linux x32 ([`8739c4b`](https://github.com/JohnTitor/ctest2/commit/8739c4bfe720e994184a95c7ebec773183f255f9)) +
+ +## v0.1.6 (2017-09-20) + +### Commit Statistics + + + + - 1 commit contributed to the release. + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). + - 0 issues like '(#ID)' were seen in commit messages + +### Commit Details + + + +
view details + + * **Uncategorized** + - Remove syntax items we're not checking ([`4d509e6`](https://github.com/JohnTitor/ctest2/commit/4d509e6c1d618c726a30a9cb5a5df5bdbfbfe70a)) +
+ +## v0.1.5 (2017-09-19) + +### Commit Statistics + + + + - 9 commits contributed to the release over the course of 76 calendar days. + - 88 days passed between releases. + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). + - 0 issues like '(#ID)' were seen in commit messages + +### Commit Details + + + +
view details + + * **Uncategorized** + - Bump dep on gcc ([`ada43df`](https://github.com/JohnTitor/ctest2/commit/ada43df485686af8d88b79234e533ec13c10f41e)) + - Try to ease the Rust compiler's pain ([`4447cfb`](https://github.com/JohnTitor/ctest2/commit/4447cfbc169d9c3c8d30f30ef98a963d77df137f)) + - Add support for unions ([`f4e0e2f`](https://github.com/JohnTitor/ctest2/commit/f4e0e2fba07638afe3903a6b655547effcd888a9)) + - Upgrade syntex_syntax dependency ([`391ac85`](https://github.com/JohnTitor/ctest2/commit/391ac85ba8d855b18d78336cb627f7b14d5cd249)) + - Add support for fixed-size array arguments ([`f574bfb`](https://github.com/JohnTitor/ctest2/commit/f574bfb76096de30597089a1e9b6c8c69f5a6454)) + - Add support for double-arrays in struct fields ([`466d365`](https://github.com/JohnTitor/ctest2/commit/466d365f593ad5d39fa3fac066123370495b0a4a)) + - Support explicit `-> ()` ([`2bcacb0`](https://github.com/JohnTitor/ctest2/commit/2bcacb0d87f072015c72e39aff6b8c82a743e5af)) + - Add support for testing `str` consts ([`9771b45`](https://github.com/JohnTitor/ctest2/commit/9771b45e0ead3fff4187b1674de9dc84d39dbda2)) + - Increase the default recursion limit ([`de371b4`](https://github.com/JohnTitor/ctest2/commit/de371b4fbf7efec032aea375bceed83ad1191b6d)) +
+ +## v0.1.4 (2017-06-23) + +### Commit Statistics + + + + - 5 commits contributed to the release over the course of 15 calendar days. + - 28 days passed between releases. + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). + - 0 issues like '(#ID)' were seen in commit messages + +### Commit Details + + + +
view details + + * **Uncategorized** + - Merge pull request #19 from sfackler/master ([`08db942`](https://github.com/JohnTitor/ctest2/commit/08db9429293a53c2fa8d39f0cc43a04add955100)) + - Extern blocks can't be public ([`1254f45`](https://github.com/JohnTitor/ctest2/commit/1254f45225d62d6699bd56e40c3d3aaacdd9c2b6)) + - Handle ABIs in fields ([`73b72c1`](https://github.com/JohnTitor/ctest2/commit/73b72c1fa6b07f6da95fb035b1e92f82fc149236)) + - Merge pull request #18 from malbarbo/emscripten ([`f4835aa`](https://github.com/JohnTitor/ctest2/commit/f4835aa2a14411beebc67e9a900858978d03cc92)) + - Add support emscripten targets ([`514a2f2`](https://github.com/JohnTitor/ctest2/commit/514a2f21990d83aa6fb4186a7220bf073558d87d)) +
+ +## v0.1.2 (2017-05-25) + +### Commit Statistics + + + + - 7 commits contributed to the release over the course of 145 calendar days. + - 189 days passed between releases. + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). + - 0 issues like '(#ID)' were seen in commit messages + +### Commit Details + + + +
view details + + * **Uncategorized** + - Merge pull request #17 from petrochenkov/master ([`ae0bbf4`](https://github.com/JohnTitor/ctest2/commit/ae0bbf4e89de49b588d251769a5af8adcceb00b4)) + - Bump syntex_syntax to 0.27.0 to fix build on nightly rustc ([`9c81b92`](https://github.com/JohnTitor/ctest2/commit/9c81b927f3e1400f6558a65eeaffa126940ecf8c)) + - Merge pull request #16 from cactorium/master ([`0b7f7b7`](https://github.com/JohnTitor/ctest2/commit/0b7f7b707add9d10c85bd3911030f7a739c349fa)) + - Add -uknown-linux-uclibc as target ([`376f59a`](https://github.com/JohnTitor/ctest2/commit/376f59a67cdae9d0f162f03b25c3b39f138b5eb3)) + - Support cast expressions in array lengths ([`80c0e5d`](https://github.com/JohnTitor/ctest2/commit/80c0e5d8dfa6d95c1b1fd7a313ba08ece18fac2f)) + - Merge pull request #13 from japaric/sparc64 ([`b703b23`](https://github.com/JohnTitor/ctest2/commit/b703b23c69afe0f4939e0c7f6540e40e2f4a12e0)) + - Sparc64 support ([`6d24033`](https://github.com/JohnTitor/ctest2/commit/6d24033d97a8b559245397102a3d751168050672)) +
+ +## v0.1.1 (2016-11-16) + +### Commit Statistics + + + + - 43 commits contributed to the release over the course of 427 calendar days. + - 0 commits were understood as [conventional](https://www.conventionalcommits.org). + - 0 issues like '(#ID)' were seen in commit messages + +### Commit Details + + + +
view details + + * **Uncategorized** + - Fix powerpc64le target_arch ([`d5aac51`](https://github.com/JohnTitor/ctest2/commit/d5aac516d895556d652c15134ba1ad6cec5e38be)) + - Merge pull request #10 from japaric/i586 ([`2839e49`](https://github.com/JohnTitor/ctest2/commit/2839e49847a6adca6e96cc81c46a1f03f8562ac0)) + - Add support for i586 targets ([`723f739`](https://github.com/JohnTitor/ctest2/commit/723f73993f5b70e12f48d26ffde1659b2b7dbd7a)) + - Merge pull request #9 from japaric/s390x ([`b7e6a3b`](https://github.com/JohnTitor/ctest2/commit/b7e6a3bca9ffe26b3c026e1255b3d4f0467485b2)) + - Add support for s390x ([`bf56085`](https://github.com/JohnTitor/ctest2/commit/bf560858ef6f199b953d0d7a5568124908742057)) + - Merge pull request #8 from japaric/mips64 ([`f3e6b73`](https://github.com/JohnTitor/ctest2/commit/f3e6b73310165a39cb4f463f3a66d4f98d243ffa)) + - Add support for mips64 ([`9109572`](https://github.com/JohnTitor/ctest2/commit/910957269ba81f096ef60727ed104436557bec85)) + - Merge pull request #7 from polachok/flags ([`a6becb6`](https://github.com/JohnTitor/ctest2/commit/a6becb6d7fd23d9863cba86eac31d1ffc4082734)) + - Allow user-specified flags override default ([`ff477ba`](https://github.com/JohnTitor/ctest2/commit/ff477ba55454af2b9837b9a60ad036912c1d57d2)) + - Add custom flags ([`965d657`](https://github.com/JohnTitor/ctest2/commit/965d6571ef4cfa0a911e89cee11e2f3cb211d6f0)) + - Print out rerun-if-changed keys to properly recompile ([`a2cf6be`](https://github.com/JohnTitor/ctest2/commit/a2cf6bec049efcaaee820631bd652c6fe52dfc1e)) + - Hide a weird API for now ([`c30b0cf`](https://github.com/JohnTitor/ctest2/commit/c30b0cfbc03af606e5a23e4496d0b6940ee1ea7f)) + - Add lots of API docs ([`2a95ee9`](https://github.com/JohnTitor/ctest2/commit/2a95ee9197c0bd3dab9dd95d6d0cd1a03e05bfb7)) + - Revert "Update dependency on syntex_syntax" ([`50ac771`](https://github.com/JohnTitor/ctest2/commit/50ac771acb7bb45cf0c182a5a9c8188a15c89efc)) + - Update dependency on syntex_syntax ([`d0c658d`](https://github.com/JohnTitor/ctest2/commit/d0c658d38d6db8f2a1ff1c5fae38d5a026fcaab2)) + - Revert "Correct test names" ([`7703b51`](https://github.com/JohnTitor/ctest2/commit/7703b51086cce2d9a703b103d0695b36653b8cab)) + - Separate a function for compiling and generating ([`4b8a0cb`](https://github.com/JohnTitor/ctest2/commit/4b8a0cb2907eb006a138fb3d7e0f03eafaf86921)) + - Correct test names ([`2fa4c8b`](https://github.com/JohnTitor/ctest2/commit/2fa4c8b17551af7a4ebd799ca6332ef2cace8213)) + - Merge pull request #4 from antonblanchard/powerpc64_merge ([`903b7f1`](https://github.com/JohnTitor/ctest2/commit/903b7f130e954d8583777255850d433b36f61f00)) + - Add powerpc, powerpc64 and powerpc64le support ([`7058f68`](https://github.com/JohnTitor/ctest2/commit/7058f6898a428589520803b4d1e3d24887698284)) + - Merge pull request #3 from mneumann/dragonfly ([`2a0524d`](https://github.com/JohnTitor/ctest2/commit/2a0524de7542aebdb7d6a2f104780c0bdfaa3b56)) + - Fix for DragonFly ([`ea100e2`](https://github.com/JohnTitor/ctest2/commit/ea100e21377055b7fe4f032d9868c21cf97d06a6)) + - Merge pull request #2 from semarie/openbsd ([`30ccebc`](https://github.com/JohnTitor/ctest2/commit/30ccebc2565e7325a045afa6189e00223471c421)) + - Add openbsd support ([`702791e`](https://github.com/JohnTitor/ctest2/commit/702791e663dbd783d23e9f3e2acc78ae853766d0)) + - Don't use link_name in C by default ([`4b29e7d`](https://github.com/JohnTitor/ctest2/commit/4b29e7d8cf64370a3e38c9a2f31fc577a953689b)) + - Add os detection for netbsd ([`c78f4af`](https://github.com/JohnTitor/ctest2/commit/c78f4af3206d420bac0b88abab8b5d51bf4c8084)) + - Add option to skip an entire struct ([`07fe948`](https://github.com/JohnTitor/ctest2/commit/07fe948a45d7ed665ed0b0405f7d2c4db3140e44)) + - Bump dep on syntex_syntax ([`68c6cd6`](https://github.com/JohnTitor/ctest2/commit/68c6cd690391948b10b5fc02427c52c05272fbc8)) + - Don't test non-public constants ([`ec5cda4`](https://github.com/JohnTitor/ctest2/commit/ec5cda4ba0a0d2a8c991f53a669893fb06df17ca)) + - Detect i386 as well ([`45136e5`](https://github.com/JohnTitor/ctest2/commit/45136e50f5c3c103b3edcb28367f957471a3afe1)) + - Add iOS detection ([`43ba64e`](https://github.com/JohnTitor/ctest2/commit/43ba64e0e188c9e65fcd4b5c2b488eefc1e2a7b2)) + - Add aarch64 detection ([`7d4b14f`](https://github.com/JohnTitor/ctest2/commit/7d4b14fe8000f21176802d07851af4108bcd0fdf)) + - Add the ability to skip fields ([`ff49248`](https://github.com/JohnTitor/ctest2/commit/ff49248fc09ab51bc48ecdbfa5af73735babf341)) + - Support testing constants which are structs ([`a2bea04`](https://github.com/JohnTitor/ctest2/commit/a2bea04e6177dccf53f78ee9bac0f4517f4b9f5f)) + - Ignore a few more windows msvc warnings ([`6ac333e`](https://github.com/JohnTitor/ctest2/commit/6ac333e6e64c4c470a8026dd73d3a59f67845b34)) + - Add skipping a struct field's type ([`4fb81c4`](https://github.com/JohnTitor/ctest2/commit/4fb81c4e62e9a73d43d4e6b1f485a611cfb597ab)) + - Fix fixed-size arrays and function pointers ([`72ed6f6`](https://github.com/JohnTitor/ctest2/commit/72ed6f6efeb40ee25abb7c2d39d5af1fc172c170)) + - Test struct field types ([`8e619cd`](https://github.com/JohnTitor/ctest2/commit/8e619cdace5126c5f216479abdd486975f5167ae)) + - Use FFI safe type ([`dbc5d24`](https://github.com/JohnTitor/ctest2/commit/dbc5d24a724d31ab7f4afdcf47666ea0dc161f32)) + - Add test for all possible error messages ([`3e7a6a9`](https://github.com/JohnTitor/ctest2/commit/3e7a6a949dae934ed62924389f17e791feafae8c)) + - Use __alignof on MSVC ([`63ff2b4`](https://github.com/JohnTitor/ctest2/commit/63ff2b4f37c0ab98639e6b6c3b0cf7ef82d60581)) + - Add an option to skip the pointer check ([`b1d0e03`](https://github.com/JohnTitor/ctest2/commit/b1d0e03b10d7087c0d41b3cb00ac31a0ca471a6b)) + - Initial commit ([`d15487c`](https://github.com/JohnTitor/ctest2/commit/d15487c557d6deb6a05e426da3e2bf11ee5cb936)) +
+ diff --git a/ctest/Cargo.toml b/ctest/Cargo.toml new file mode 100644 index 0000000000000..87746a7ffa472 --- /dev/null +++ b/ctest/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "ctest" +version = "0.4.10" +license = "MIT OR Apache-2.0" +readme = "README.md" +repository = "https://github.com/rust-lang/libc" +homepage = "https://github.com/rust-lang/libc" +documentation = "https://docs.rs/ctest" +description = """ +Automated tests of FFI bindings. +""" +include = ["src/lib.rs", "LICENSE-*", "README.md"] +edition = "2021" +rust-version = "1.63.0" + +[dependencies] +garando_syntax = "0.1" +cc = "1.0.1" +rustc_version = "0.4" + +[workspace] +members = ["testcrate"] diff --git a/ctest/LICENSE-APACHE b/ctest/LICENSE-APACHE new file mode 100644 index 0000000000000..1b5ec8b78e237 --- /dev/null +++ b/ctest/LICENSE-APACHE @@ -0,0 +1,176 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS diff --git a/ctest/LICENSE-MIT b/ctest/LICENSE-MIT new file mode 100644 index 0000000000000..d02eed54b2b22 --- /dev/null +++ b/ctest/LICENSE-MIT @@ -0,0 +1,25 @@ +Copyright (c) 2014-2020 Alex Crichton, Gonzalo Brito Gadeschi, Yuki Okushi + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/ctest/README.md b/ctest/README.md new file mode 100644 index 0000000000000..ed876860a809c --- /dev/null +++ b/ctest/README.md @@ -0,0 +1,112 @@ +# ctest2 + +[Documentation][dox] + +[dox]: https://docs.rs/ctest2 + +**Note: This is a fork of [`ctest`], intended as a temporary replacement until maintenance of [`ctest`] resumes.** + +[`ctest`]: https://crates.io/crates/ctest + +Automated testing of FFI bindings in Rust. This repository is intended to +validate the `*-sys` crates that can be found on crates.io to ensure that the +APIs in Rust match the APIs defined in C. + +## MSRV (Minimum Supported Rust Version) + +The MSRV is 1.63.0 because of the transitive dependencies. +Note that MSRV may be changed anytime by dependencies. + +## Example + +Unfortunately the usage today is a little wonky, but to use this library, first, +create a new Cargo project in your repo: + +``` +$ cargo new --bin systest +``` + +Then, edit `systest/Cargo.toml` to add these dependencies: + +```toml +[package] +# ... +build = "build.rs" + +[dependencies] +mylib-sys = { path = "../mylib-sys" } +libc = "0.2" + +[build-dependencies] +ctest2 = "0.4" +``` + +Next, add a build script to `systest/build.rs`: + +```rust +fn main() { + let mut cfg = ctest2::TestGenerator::new(); + + // Include the header files where the C APIs are defined + cfg.header("foo.h") + .header("bar.h"); + + // Include the directory where the header files are defined + cfg.include("path/to/include"); + + // Generate the tests, passing the path to the `*-sys` library as well as + // the module to generate. + cfg.generate("../mylib-sys/lib.rs", "all.rs"); +} +``` + +Next, add this to `src/main.rs` + +```rust +#![allow(bad_style)] + +use libc::*; +use mylib_sys::*; + +include!(concat!(env!("OUT_DIR"), "/all.rs")); +``` + +And you're good to go! To run the tests execute `cargo run` in the `systest` +directory, and everything should be kicked into action! + +## How it works + +This library will parse the `*-sys` crate to learn about all extern fn +definitions within. It will then generate a test suite to ensure that all +function function signatures, constant values, struct layout/alignment, type +size/alignment, etc, all match their C equivalent. + +The generated tests come in two forms. One is a Rust file which contains the +`main` function (hence the `include!` above), and another is a C file which is +compiled as part of the build script. The C file is what includes all headers +and returns information about the C side of things (which is validated in Rust). + +A large amount of configuration can be applied to how the C file is generated, +you can browse [the documentation][dox]. + +## Projects using ctest2 + +- [libc](https://github.com/rust-lang/libc) +- [libz-sys](https://github.com/rust-lang/libz-sys) + +## License + +This project is licensed under either of + +- Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or + https://www.apache.org/licenses/LICENSE-2.0) +- MIT license ([LICENSE-MIT](LICENSE-MIT) or + https://opensource.org/licenses/MIT) + +at your option. + +## Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in ctest2 by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. diff --git a/ctest/ci/docker/x86_64-unknown-linux-gnu/Dockerfile b/ctest/ci/docker/x86_64-unknown-linux-gnu/Dockerfile new file mode 100644 index 0000000000000..d6dea145ed4ff --- /dev/null +++ b/ctest/ci/docker/x86_64-unknown-linux-gnu/Dockerfile @@ -0,0 +1,9 @@ +FROM ubuntu:24.04 +RUN apt-get update +RUN apt-get install -y --no-install-recommends \ + gcc libc6-dev ca-certificates linux-headers-generic git + +RUN apt search linux-headers +RUN ls /usr/src + +ENV PATH=$PATH:/rust/bin diff --git a/ctest/ci/install-rust.sh b/ctest/ci/install-rust.sh new file mode 100644 index 0000000000000..776190a8b07a1 --- /dev/null +++ b/ctest/ci/install-rust.sh @@ -0,0 +1,69 @@ +#!/usr/bin/env sh +# This is intended to be used in CI only. + +set -ex + +echo "Setup toolchain" +toolchain= +if [ -n "$TOOLCHAIN" ]; then + toolchain=$TOOLCHAIN +else + toolchain=nightly +fi +if [ "$OS" = "Windows_NT" ]; then + : "${TARGET?The TARGET environment variable must be set.}" + rustup self update + rustup set profile minimal + rustup update --force $toolchain-"$TARGET" + rustup default $toolchain-"$TARGET" +else + rustup set profile minimal + rustup update --force $toolchain + rustup default $toolchain +fi + +if [ -n "$TARGET" ]; then + echo "Install target" + rustup target add "$TARGET" +fi + +if [ "$OS" = "Windows_NT" ]; then + if [ "$ARCH_BITS" = "i686" ]; then + echo "Install MinGW32" + choco install mingw --x86 --force + fi + + echo "Find GCC libraries" + gcc -print-search-dirs + /usr/bin/find "C:\ProgramData\Chocolatey" -name "crt2*" + /usr/bin/find "C:\ProgramData\Chocolatey" -name "dllcrt2*" + /usr/bin/find "C:\ProgramData\Chocolatey" -name "libmsvcrt*" + + if [ -n "$ARCH_BITS" ]; then + echo "Fix MinGW" + for i in crt2.o dllcrt2.o libmingwex.a libmsvcrt.a ; do + cp -f "/C/ProgramData/Chocolatey/lib/mingw/tools/install/mingw$ARCH_BITS/$ARCH-w64-mingw32/lib/$i" "$(rustc --print sysroot)/lib/rustlib/$TARGET/lib" + done + fi +fi + +echo "Query rust and cargo versions" +command -v rustc +command -v cargo +command -v rustup +rustc -Vv +cargo -V +rustup -Vv +rustup show + +echo "Generate lockfile" +N=5 +n=0 +until [ $n -ge $N ] +do + if cargo generate-lockfile; then + break + fi + n=$((n+1)) + sleep 1 +done diff --git a/ctest/ci/run-docker.sh b/ctest/ci/run-docker.sh new file mode 100755 index 0000000000000..a16b50dff39f7 --- /dev/null +++ b/ctest/ci/run-docker.sh @@ -0,0 +1,35 @@ +# Small script to run tests for a target (or all targets) inside all the +# respective docker images. + +set -ex + +run() { + echo "Building docker container for TARGET=${1}" + docker build -t ctest2 -f ci/docker/$1/Dockerfile ci/ + mkdir -p target + target=$1 + echo "Running docker" + docker run \ + --user `id -u`:`id -g` \ + --rm \ + --init \ + --volume $HOME/.cargo:/cargo-h \ + --env CARGO_HOME=/cargo-h \ + --volume `rustc --print sysroot`:/rust:ro \ + --env TARGET=$target \ + --volume `pwd`:/checkout:ro \ + --volume `pwd`/target:/checkout/target \ + --workdir /checkout \ + --privileged \ + ctest2 \ + bash \ + -c 'PATH=/rust/bin:$PATH exec ci/run.sh' +} + +if [ -z "$1" ]; then + for d in `ls ci/docker/`; do + run $d + done +else + run $1 +fi diff --git a/ctest/ci/run.sh b/ctest/ci/run.sh new file mode 100755 index 0000000000000..1ac2e7bc221fb --- /dev/null +++ b/ctest/ci/run.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env sh + +# Builds and runs tests for a particular target passed as an argument to this +# script. + +set -ex + +: ${TARGET?"The TARGET environment variable must be set."} + +mkdir -p target +rm -rf target/libc || true +git clone --depth=1 https://github.com/rust-lang/libc target/libc +mkdir -p target/libc/target/ctest2 + +case $TARGET in + *linux*) + sed -E -i 's@ctest2 = "[0-9\.]+"@ctest2 = { path = "../../.." }@g' target/libc/libc-test/Cargo.toml + ;; + *apple*) + sed -E -i '' 's@ctest2 = "[0-9\.]+"@ctest2 = { path = "../../.." }@g' target/libc/libc-test/Cargo.toml + ;; +esac + +cargo test --release --manifest-path target/libc/libc-test/Cargo.toml --target $TARGET diff --git a/ctest/renovate.json b/ctest/renovate.json new file mode 100644 index 0000000000000..5db72dd6a94fc --- /dev/null +++ b/ctest/renovate.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ + "config:recommended" + ] +} diff --git a/ctest/src/lib.rs b/ctest/src/lib.rs new file mode 100644 index 0000000000000..e65a33aebad73 --- /dev/null +++ b/ctest/src/lib.rs @@ -0,0 +1,2551 @@ +//! # ctest2 - an FFI binding validator +//! +//! This library is intended to be used as a build dependency in a separate +//! project from the main repo to generate tests which can be used to validate +//! FFI bindings in Rust against the headers from which they come from. +//! +//! For example usage, see the [main `README.md`][project] for how to set it +//! up. +//! +//! [project]: https://github.com/JohnTitor/ctest2 + +#![deny(missing_docs)] + +use garando_syntax as syntax; + +use std::collections::{HashMap, HashSet}; +use std::env; +use std::fs::File; +use std::io::prelude::*; +use std::io::BufWriter; +use std::path::{Path, PathBuf}; +use std::rc::Rc; + +use syntax::abi::Abi; +use syntax::ast; +use syntax::ast::Attribute; +use syntax::ast::Name; +use syntax::attr::{self, ReprAttr}; +use syntax::codemap::FilePathMapping; +use syntax::config::StripUnconfigured; +use syntax::errors::Handler as SpanHandler; +use syntax::ext::base::{Determinacy, ExtCtxt, MacroKind, Resolver, SyntaxExtension}; +use syntax::ext::expand::{Expansion, ExpansionConfig, Invocation, InvocationKind}; +use syntax::ext::hygiene::Mark; +use syntax::ext::tt::macro_rules; +use syntax::feature_gate::Features; +use syntax::fold::{self, Folder}; +use syntax::parse::{self, ParseSess}; +use syntax::ptr::P; +use syntax::util::small_vector::SmallVector; +use syntax::visit::{self, Visitor}; + +macro_rules! t { + ($e:expr) => { + match $e { + Ok(e) => e, + Err(e) => panic!("{} failed with {}", stringify!($e), e), + } + }; +} + +/// Programming language +#[derive(Debug)] +pub enum Lang { + /// The C programming language. + C, + /// The C++ programming language. + CXX, +} + +/// A kind of item to which the C volatile qualifier could apply. +#[derive(Debug)] +#[allow(clippy::manual_non_exhaustive)] // FIXME: Use `#[non_exhaustive]` in the future. +pub enum VolatileItemKind { + /// A struct field (struct_name, field_name) + StructField(String, String), + /// An extern static + Static(String), + /// N-th function argument + FunctionArg(String, usize), + /// Function return type + FunctionRet(String), + #[doc(hidden)] + __Other, +} + +/// A builder used to generate a test suite. +/// +/// This builder has a number of configuration options which modify how the +/// generated tests are emitted, and it is also the main entry point for parsing +/// an FFI header crate for definitions. +pub struct TestGenerator { + headers: Vec, + includes: Vec, + lang: Lang, + flags: Vec, + target: Option, + out_dir: Option, + defines: Vec<(String, Option)>, + cfg: Vec<(String, Option)>, + verbose_skip: bool, + volatile_item: Box bool>, + array_arg: Box bool>, + skip_fn: Box bool>, + skip_fn_ptrcheck: Box bool>, + skip_static: Box bool>, + skip_field: Box bool>, + skip_field_type: Box bool>, + skip_const: Box bool>, + skip_signededness: Box bool>, + skip_type: Box bool>, + skip_struct: Box bool>, + skip_roundtrip: Box bool>, + field_name: Box String>, + type_name: Box String>, + fn_cname: Box) -> String>, + const_cname: Box String>, + rust_version: rustc_version::Version, +} + +struct TyFinder { + structs: HashSet, + unions: HashSet, + aliases: HashMap>, +} + +struct Generator<'a> { + target: &'a str, + rust: Box, + c: Box, + sh: &'a SpanHandler, + structs: HashSet, + unions: HashSet, + aliases: HashMap>, + files: HashSet, + abi: Abi, + tests: Vec, + sess: &'a ParseSess, + opts: &'a TestGenerator, +} + +impl TestGenerator { + /// Creates a new blank test generator. + /// + /// This won't actually be that useful until functions like `header` are + /// called, but the main "finalization method" is the `generate` method. + pub fn new() -> Self { + Self { + headers: Vec::new(), + includes: Vec::new(), + lang: Lang::C, + flags: Vec::new(), + target: None, + out_dir: None, + defines: Vec::new(), + cfg: Vec::new(), + verbose_skip: false, + volatile_item: Box::new(|_| false), + array_arg: Box::new(|_, _| false), + skip_fn: Box::new(|_| false), + skip_fn_ptrcheck: Box::new(|_| false), + skip_static: Box::new(|_| false), + skip_const: Box::new(|_| false), + skip_signededness: Box::new(|_| false), + skip_type: Box::new(|_| false), + skip_struct: Box::new(|_| false), + skip_roundtrip: Box::new(|_| false), + field_name: Box::new(|_, f| f.to_string()), + skip_field: Box::new(|_, _| false), + skip_field_type: Box::new(|_, _| false), + fn_cname: Box::new(|a, _| a.to_string()), + type_name: Box::new(|f, is_struct, is_union| { + if is_struct { + format!("struct {}", f) + } else if is_union { + format!("union {}", f) + } else { + f.to_string() + } + }), + const_cname: Box::new(std::string::ToString::to_string), + rust_version: rustc_version::version().unwrap(), + } + } + + /// Add a header to be included as part of the generated C file. + /// + /// The generate C test will be compiled by a C compiler, and this can be + /// used to ensure that all the necessary header files are included to test + /// all FFI definitions. + /// + /// # Examples + /// + /// ```no_run + /// use std::env; + /// use std::path::PathBuf; + /// + /// use ctest2::TestGenerator; + /// + /// let mut cfg = TestGenerator::new(); + /// cfg.header("foo.h") + /// .header("bar.h"); + /// ``` + pub fn header(&mut self, header: &str) -> &mut Self { + self.headers.push(header.to_string()); + self + } + + /// Target Rust version: `major`.`minor`.`patch` + /// + /// # Examples + /// + /// ```no_run + /// use ctest2::TestGenerator; + /// + /// let mut cfg = TestGenerator::new(); + /// cfg.rust_version(1, 0, 1); + /// ``` + pub fn rust_version(&mut self, major: u64, minor: u64, patch: u64) -> &mut Self { + self.rust_version = rustc_version::Version::new(major, minor, patch); + self + } + + /// Add a path to the C compiler header lookup path. + /// + /// This is useful for if the C library is installed to a nonstandard + /// location to ensure that compiling the C file succeeds. + /// + /// # Examples + /// + /// ```no_run + /// use std::env; + /// use std::path::PathBuf; + /// + /// use ctest2::TestGenerator; + /// + /// let mut cfg = TestGenerator::new(); + /// let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap()); + /// cfg.include(out_dir.join("include")); + /// ``` + pub fn include>(&mut self, p: P) -> &mut Self { + self.includes.push(p.as_ref().to_owned()); + self + } + + /// Sets the programming language. + /// + /// # Examples + /// + /// ```no_run + /// use std::env; + /// use std::path::PathBuf; + /// + /// use ctest2::{TestGenerator, Lang}; + /// + /// let mut cfg = TestGenerator::new(); + /// cfg.language(Lang::CXX); + /// ``` + pub fn language(&mut self, lang: Lang) -> &mut Self { + self.lang = lang; + self + } + + /// Add a flag to the C compiler invocation. + /// + /// This can be useful for tweaking the warning settings of the underlying + /// compiler. + /// + /// # Examples + /// + /// ```no_run + /// use std::env; + /// use std::path::PathBuf; + /// + /// use ctest2::TestGenerator; + /// + /// let mut cfg = TestGenerator::new(); + /// + /// // if msvc + /// cfg.flag("/wd4820"); + /// + /// // if gnu + /// cfg.flag("-Wno-type-limits"); + /// ``` + pub fn flag(&mut self, flag: &str) -> &mut Self { + self.flags.push(flag.to_string()); + self + } + + /// Configures the output directory of the generated Rust and C code. + /// + /// Note that for Cargo builds this defaults to `$OUT_DIR` and it's not + /// necessary to call. + /// + /// # Examples + /// + /// ```no_run + /// use ctest2::TestGenerator; + /// + /// let mut cfg = TestGenerator::new(); + /// cfg.out_dir("path/to/output"); + /// ``` + pub fn out_dir>(&mut self, p: P) -> &mut Self { + self.out_dir = Some(p.as_ref().to_owned()); + self + } + + /// Configures the target to compile C code for. + /// + /// Note that for Cargo builds this defaults to `$TARGET` and it's not + /// necessary to call. + /// + /// # Examples + /// + /// ```no_run + /// use ctest2::TestGenerator; + /// + /// let mut cfg = TestGenerator::new(); + /// cfg.target("x86_64-unknown-linux-gnu"); + /// ``` + pub fn target(&mut self, target: &str) -> &mut Self { + self.target = Some(target.to_string()); + self + } + + /// Set a `-D` flag for the C compiler being called. + /// + /// This can be used to define various variables to configure how header + /// files are included or what APIs are exposed from header files. + /// + /// # Examples + /// + /// ```no_run + /// use ctest2::TestGenerator; + /// + /// let mut cfg = TestGenerator::new(); + /// cfg.define("_GNU_SOURCE", None) + /// .define("_WIN32_WINNT", Some("0x8000")); + /// ``` + pub fn define(&mut self, k: &str, v: Option<&str>) -> &mut Self { + self.defines + .push((k.to_string(), v.map(std::string::ToString::to_string))); + self + } + + /// Set a `--cfg` option with which to expand the Rust FFI crate. + /// + /// By default the Rust code is run through expansion to determine what C + /// APIs are exposed (to allow differences across platforms). + /// + /// The `k` argument is the `#[cfg]` value to define, while `v` is the + /// optional value of `v`: + /// + /// * `k == "foo"` and `v == None` makes `#[cfg(foo)]` expand. That is, + /// `cfg!(foo)` expands to `true`. + /// + /// * `k == "bar"` and `v == Some("baz")` makes `#[cfg(bar = "baz")]` + /// expand. That is, `cfg!(bar = "baz")` expands to `true`. + /// + /// # Examples + /// + /// ```no_run + /// use ctest2::TestGenerator; + /// + /// let mut cfg = TestGenerator::new(); + /// cfg.cfg("foo", None) // cfg!(foo) + /// .cfg("bar", Some("baz")); // cfg!(bar = "baz") + /// ``` + pub fn cfg(&mut self, k: &str, v: Option<&str>) -> &mut Self { + self.cfg + .push((k.to_string(), v.map(std::string::ToString::to_string))); + self + } + + /// Skipped item names are printed to `stderr` if `v` is `true`. + pub fn verbose_skip(&mut self, v: bool) -> &mut Self { + self.verbose_skip = v; + self + } + + /// Configures how a Rust type name is translated to a C type name. + /// + /// The closure is given a Rust type name as well as a boolean indicating + /// whether it's a struct or not. + /// + /// The default behavior is that `struct foo` in Rust is translated to + /// `struct foo` in C, and `type foo` in Rust is translated to `foo` in C. + /// Some header files, however, have the convention that `struct foo_t` in + /// Rust should be `foo_t` in C, for example. + /// + /// # Examples + /// + /// ```no_run + /// use ctest2::TestGenerator; + /// + /// let mut cfg = TestGenerator::new(); + /// cfg.type_name(|ty, is_struct, is_union| { + /// if is_struct { + /// format!("{}_t", ty) + /// } else { + /// ty.to_string() + /// } + /// }); + /// ``` + pub fn type_name(&mut self, f: F) -> &mut Self + where + F: Fn(&str, bool, bool) -> String + 'static, + { + self.type_name = Box::new(f); + self + } + + /// Configures how a Rust struct field is translated to a C struct field. + /// + /// The closure is given a Rust struct name as well as a field within that + /// struct. The name of the corresponding field in C is then returned. + /// + /// By default the field name in C just matches the field name in Rust, but + /// this is useful for fields which otherwise are named after keywords in + /// Rust (such as a field name of `type`). + /// + /// # Examples + /// + /// ```no_run + /// use ctest2::TestGenerator; + /// + /// let mut cfg = TestGenerator::new(); + /// cfg.field_name(|_s, field| { + /// field.replace("foo", "bar") + /// }); + /// ``` + pub fn field_name(&mut self, f: F) -> &mut Self + where + F: Fn(&str, &str) -> String + 'static, + { + self.field_name = Box::new(f); + self + } + + /// Is volatile? + /// + /// The closure given takes a `VolatileKind` denoting a particular item that + /// could be volatile, and returns whether this is the case. + /// + /// # Examples + /// + /// ```no_run + /// use ctest2::{TestGenerator, VolatileItemKind::StructField}; + /// + /// let mut cfg = TestGenerator::new(); + /// cfg.volatile_item(|i| { + /// match i { + /// StructField(ref s, ref f) + /// if s == "foo_struct" && f == "foo_field" + /// => true, + /// _ => false, + /// }}); + /// ``` + pub fn volatile_item(&mut self, f: F) -> &mut Self + where + F: Fn(VolatileItemKind) -> bool + 'static, + { + self.volatile_item = Box::new(f); + self + } + + /// Is argument of function an array? + /// + /// The closure denotes whether particular argument of a function is an array. + /// + /// # Examples + /// + /// ```no_run + /// use ctest2::{TestGenerator}; + /// + /// let mut cfg = TestGenerator::new(); + /// cfg.array_arg(|i, n| { + /// match (i, n) { + /// ("foo", 0) => true, + /// _ => false, + /// }}); + /// ``` + pub fn array_arg(&mut self, f: F) -> &mut Self + where + F: Fn(&str, usize) -> bool + 'static, + { + self.array_arg = Box::new(f); + self + } + + /// Configures how Rust `const`s names are translated to C. + /// + /// The closure is given a Rust `const` name. The name of the corresponding + /// `const` in C is then returned. + /// + /// By default the `const` name in C just matches the `const` name in Rust. + /// + /// # Examples + /// + /// ```no_run + /// use ctest2::TestGenerator; + /// + /// let mut cfg = TestGenerator::new(); + /// cfg.const_cname(|c| { + /// c.replace("FOO", "foo") + /// }); + /// ``` + pub fn const_cname(&mut self, f: F) -> &mut Self + where + F: Fn(&str) -> String + 'static, + { + self.const_cname = Box::new(f); + self + } + + /// Configures whether all tests for a field are skipped or not. + /// + /// The closure is given a Rust struct name as well as a field within that + /// struct. A flag indicating whether the field should be tested for type, + /// size, offset, and alignment should be skipped or not. + /// + /// By default all field properties are tested. + /// + /// # Examples + /// + /// ```no_run + /// use ctest2::TestGenerator; + /// + /// let mut cfg = TestGenerator::new(); + /// cfg.skip_field(|s, field| { + /// s == "foo_t" || (s == "bar_t" && field == "bar") + /// }); + /// ``` + pub fn skip_field(&mut self, f: F) -> &mut Self + where + F: Fn(&str, &str) -> bool + 'static, + { + self.skip_field = Box::new(f); + self + } + + /// Configures whether tests for the type of a field is skipped or not. + /// + /// The closure is given a Rust struct name as well as a field within that + /// struct. A flag indicating whether the field's type should be tested is + /// returned. + /// + /// By default all field properties are tested. + /// + /// # Examples + /// + /// ```no_run + /// use ctest2::TestGenerator; + /// + /// let mut cfg = TestGenerator::new(); + /// cfg.skip_field_type(|s, field| { + /// s == "foo_t" || (s == "bar_t" && field == "bar") + /// }); + /// ``` + pub fn skip_field_type(&mut self, f: F) -> &mut Self + where + F: Fn(&str, &str) -> bool + 'static, + { + self.skip_field_type = Box::new(f); + self + } + + /// Configures whether a types signededness is tested or not. + /// + /// The closure is given the name of a Rust type, and returns whether the + /// type should be tested as having the right sign (positive or negative). + /// + /// By default all signededness checks are performed. + /// + /// # Examples + /// + /// ```no_run + /// use ctest2::TestGenerator; + /// + /// let mut cfg = TestGenerator::new(); + /// cfg.skip_signededness(|s| { + /// s.starts_with("foo_") + /// }); + /// ``` + pub fn skip_signededness(&mut self, f: F) -> &mut Self + where + F: Fn(&str) -> bool + 'static, + { + self.skip_signededness = Box::new(f); + self + } + + /// Configures whether tests for a function definition are generated. + /// + /// The closure is given the name of a Rust FFI function and returns whether + /// test will be generated. + /// + /// By default, a function's signature is checked along with its address in + /// memory. + /// + /// # Examples + /// + /// ```no_run + /// use ctest2::TestGenerator; + /// + /// let mut cfg = TestGenerator::new(); + /// cfg.skip_fn(|s| { + /// s.starts_with("foo_") + /// }); + /// ``` + pub fn skip_fn(&mut self, f: F) -> &mut Self + where + F: Fn(&str) -> bool + 'static, + { + self.skip_fn = Box::new(f); + self + } + + /// Configures whether tests for a static definition are generated. + /// + /// The closure is given the name of a Rust extern static definition and + /// returns whether test will be generated. + /// + /// By default, a static's type is checked along with its address in + /// memory. + /// + /// # Examples + /// + /// ```no_run + /// use ctest2::TestGenerator; + /// + /// let mut cfg = TestGenerator::new(); + /// cfg.skip_static(|s| { + /// s.starts_with("foo_") + /// }); + /// ``` + pub fn skip_static(&mut self, f: F) -> &mut Self + where + F: Fn(&str) -> bool + 'static, + { + self.skip_static = Box::new(f); + self + } + + /// Configures whether tests for a function pointer's value are generated. + /// + /// The closure is given the name of a Rust FFI function and returns whether + /// the test will be generated. + /// + /// By default generated tests will ensure that the function pointer in C + /// corresponds to the same function pointer in Rust. This can often + /// uncover subtle symbol naming issues where a header file is referenced + /// through the C identifier `foo` but the underlying symbol is mapped to + /// something like `__foo_compat`. + pub fn skip_fn_ptrcheck(&mut self, f: F) -> &mut Self + where + F: Fn(&str) -> bool + 'static, + { + self.skip_fn_ptrcheck = Box::new(f); + self + } + + /// Configures whether the tests for a constant's value are generated. + /// + /// The closure is given the name of a Rust constant and returns whether the + /// test will be generated. + /// + /// By default all constant values are verified. + /// + /// # Examples + /// + /// ```no_run + /// use ctest2::TestGenerator; + /// + /// let mut cfg = TestGenerator::new(); + /// cfg.skip_const(|s| { + /// s.starts_with("FOO_") + /// }); + /// ``` + pub fn skip_const(&mut self, f: F) -> &mut Self + where + F: Fn(&str) -> bool + 'static, + { + self.skip_const = Box::new(f); + self + } + + /// Configures whether the tests for a typedef are emitted. + /// + /// The closure is passed the name of a Rust typedef and returns whether the + /// tests are generated. + /// + /// By default existence of a typedef is checked. + /// + /// # Examples + /// + /// ```no_run + /// use ctest2::TestGenerator; + /// + /// let mut cfg = TestGenerator::new(); + /// cfg.skip_type(|s| { + /// s.starts_with("foo_") + /// }); + /// ``` + pub fn skip_type(&mut self, f: F) -> &mut Self + where + F: Fn(&str) -> bool + 'static, + { + self.skip_type = Box::new(f); + self + } + + /// Configures whether the tests for a struct are emitted. + /// + /// The closure is passed the name of a Rust struct and returns whether the + /// tests are generated. + /// + /// By default structs undergo tests such as size, alignment, existence, + /// field offset, etc. + /// + /// # Examples + /// + /// ```no_run + /// use ctest2::TestGenerator; + /// + /// let mut cfg = TestGenerator::new(); + /// cfg.skip_struct(|s| { + /// s.starts_with("foo_") + /// }); + /// ``` + pub fn skip_struct(&mut self, f: F) -> &mut Self + where + F: Fn(&str) -> bool + 'static, + { + self.skip_struct = Box::new(f); + self + } + + /// Configures whether the ABI roundtrip tests for a type are emitted. + /// + /// The closure is passed the name of a Rust type and returns whether the + /// tests are generated. + /// + /// By default all types undergo ABI roundtrip tests. Arrays cannot undergo + /// an ABI roundtrip because they cannot be returned by C functions, and + /// have to be manually skipped here. + /// + /// # Examples + /// + /// ```no_run + /// use ctest2::TestGenerator; + /// + /// let mut cfg = TestGenerator::new(); + /// cfg.skip_roundtrip(|s| { + /// s.starts_with("foo_") + /// }); + /// ``` + pub fn skip_roundtrip(&mut self, f: F) -> &mut Self + where + F: Fn(&str) -> bool + 'static, + { + self.skip_roundtrip = Box::new(f); + self + } + + /// Configures the name of a function in the generate C code. + /// + /// The closure is passed the Rust name of a function as well as any + /// optional `#[link_name]` specified. + /// + /// By default the name of the generated C reference is the same as the Rust + /// function. This is useful, however, if different naming conventions are + /// used in Rust than are present in C (which is discouraged, however). + /// + /// # Examples + /// + /// ```no_run + /// use ctest2::TestGenerator; + /// + /// let mut cfg = TestGenerator::new(); + /// cfg.fn_cname(|rust, link_name| link_name.unwrap_or(rust).to_string()); + /// ``` + pub fn fn_cname(&mut self, f: F) -> &mut Self + where + F: Fn(&str, Option<&str>) -> String + 'static, + { + self.fn_cname = Box::new(f); + self + } + + /// Generate all tests. + /// + /// This function is first given the path to the `*-sys` crate which is + /// being tested along with an output file from where to generate the Rust + /// side of the tests. + /// + /// This function does not consume the builder, but it is expected that all + /// configuration has happened prior to calling this function. + /// + /// This will also generate the corresponding C side of the tests and + /// compile it. + /// + /// # Examples + /// + /// ```no_run + /// use ctest2::TestGenerator; + /// + /// let mut cfg = TestGenerator::new(); + /// cfg.generate("../path/to/libfoo-sys/lib.rs", "all.rs"); + /// ``` + pub fn generate>(&mut self, krate: P, out_file: &str) { + self._generate(krate.as_ref(), out_file) + } + + fn _generate(&mut self, krate: &Path, out_file: &str) { + let out = self.generate_files(krate, out_file); + + let target = self + .target + .clone() + .unwrap_or_else(|| env::var("TARGET").unwrap()); + + // Compile our C shim to be linked into tests + let mut cfg = cc::Build::new(); + if let Lang::CXX = self.lang { + cfg.cpp(true); + } + let ext = match self.lang { + Lang::C => "c", + Lang::CXX => "cpp", + }; + cfg.file(&out.with_extension(ext)); + if target.contains("msvc") { + cfg.flag("/W3").flag("/Wall").flag("/WX") + // ignored warnings + .flag("/wd4820") // warning about adding padding? + .flag("/wd4100") // unused parameters + .flag("/wd4996") // deprecated functions + .flag("/wd4296") // '<' being always false + .flag("/wd4255") // converting () to (void) + .flag("/wd4668") // using an undefined thing in preprocessor? + .flag("/wd4366") // taking ref to packed struct field might be unaligned + .flag("/wd4189") // local variable initialized but not referenced + .flag("/wd4710") // function not inlined + .flag("/wd5045") // compiler will insert Spectre mitigation + .flag("/wd4514") // unreferenced inline function removed + .flag("/wd4711") // function selected for automatic inline + ; + } else { + cfg.flag("-Wall") + .flag("-Wextra") + .flag("-Werror") + .flag("-Wno-unused-parameter") + .flag("-Wno-type-limits") + // allow taking address of packed struct members: + .flag("-Wno-address-of-packed-member") + .flag("-Wno-unknown-warning-option") + .flag("-Wno-deprecated-declarations"); // allow deprecated items + } + + for flag in &self.flags { + cfg.flag(flag); + } + + for &(ref a, ref b) in &self.defines { + cfg.define(a, b.as_ref().map(|s| &s[..])); + } + for p in &self.includes { + cfg.include(p); + } + + let stem = out.file_stem().unwrap().to_str().unwrap(); + cfg.target(&target) + .out_dir(out.parent().unwrap()) + .compile(&format!("lib{}.a", stem)); + } + + #[doc(hidden)] // TODO: needs docs + pub fn generate_files>(&mut self, krate: P, out_file: &str) -> PathBuf { + self._generate_files(krate.as_ref(), out_file) + } + + fn _generate_files(&mut self, krate: &Path, out_file: &str) -> PathBuf { + // Prep the test generator + let out_dir = self + .out_dir + .clone() + .unwrap_or_else(|| PathBuf::from(env::var_os("OUT_DIR").unwrap())); + let out_file = out_dir.join(out_file); + let ext = match self.lang { + Lang::C => "c", + Lang::CXX => "cpp", + }; + let c_file = out_file.with_extension(ext); + let rust_out = BufWriter::new(t!(File::create(&out_file))); + let c_out = BufWriter::new(t!(File::create(&c_file))); + let mut sess = ParseSess::new(FilePathMapping::empty()); + let target = self + .target + .clone() + .unwrap_or_else(|| env::var("TARGET").unwrap()); + for (k, v) in default_cfg(&target).into_iter().chain(self.cfg.clone()) { + let s = |s: &str| ast::Name::intern(s); + sess.config.insert((s(&k), v.as_ref().map(|n| s(n)))); + } + + // Parse the libc crate + let krate = parse::parse_crate_from_file(krate, &sess).ok().unwrap(); + + // Remove things like functions, impls, traits, etc, that we're not + // looking at + let krate = StripUnchecked.fold_crate(krate); + + // expand macros + let features = Features::new(); + let mut ecfg = ExpansionConfig { + features: Some(&features), + ..ExpansionConfig::default("crate_name".to_string()) + }; + ecfg.recursion_limit = 128; + // let exts = vec![ + // (Interner::intern("macro_rules"), SyntaxExtension::MacroRulesTT), + // ]; + println!("-----------------------------------------"); + let mut resolver = MyResolver { + parse_sess: &sess, + map: HashMap::new(), + id: 1_000_000_000, + }; + let mut ecx = ExtCtxt::new(&sess, ecfg, &mut resolver); + let krate = ecx.monotonic_expander().expand_crate(krate); + + // Strip the crate down to just what's configured for our target + let krate = StripUnconfigured { + should_test: false, + sess: &sess, + features: None, + } + .fold_crate(krate); + + // Probe the crate to find all structs, unions and type aliases (used to convert type names + // to names in C). + let mut types = TyFinder { + structs: HashSet::new(), + unions: HashSet::new(), + aliases: HashMap::new(), + }; + visit::walk_crate(&mut types, &krate); + + let mut gen = Generator { + target: &target, + rust: Box::new(rust_out), + c: Box::new(c_out), + sh: &sess.span_diagnostic, + structs: types.structs, + unions: types.unions, + aliases: types.aliases, + abi: Abi::C, + tests: Vec::new(), + files: HashSet::new(), + sess: &sess, + opts: self, + }; + t!(writeln!(gen.c, "#include ")); + t!(writeln!(gen.c, "#include ")); + t!(writeln!(gen.c, "#include ")); + for header in &self.headers { + t!(writeln!(gen.c, "#include <{}>", header)); + } + eprintln!("rust version: {}", self.rust_version); + t!(gen.rust.write_all( + if self.rust_version < rustc_version::Version::new(1, 30, 0) { + br#" + static FAILED: AtomicBool = std::sync::atomic::ATOMIC_BOOL_INIT; + static NTESTS: AtomicUsize = std::sync::atomic::ATOMIC_USIZE_INIT; + "# + } else { + br#" + static FAILED: AtomicBool = AtomicBool::new(false); + static NTESTS: AtomicUsize = AtomicUsize::new(0); + "# + } + )); + + t!(gen.rust.write_all( + br#" + use std::mem; + use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; + + fn main() { + eprintln!("RUNNING ALL TESTS"); + run_all(); + if FAILED.load(Ordering::SeqCst) { + panic!("some tests failed"); + } else { + eprintln!("PASSED {} tests", NTESTS.load(Ordering::SeqCst)); + } + } + + trait Pretty { + fn pretty(&self) -> String; + } + + impl<'a> Pretty for &'a str { + fn pretty(&self) -> String { format!("{:?}", self) } + } + impl Pretty for *const T { + fn pretty(&self) -> String { format!("{:?}", self) } + } + impl Pretty for *mut T { + fn pretty(&self) -> String { format!("{:?}", self) } + } + macro_rules! p { + ($($i:ident)*) => ($( + impl Pretty for $i { + fn pretty(&self) -> String { + format!("{} ({:#x})", self, self) + } + } + )*) + } + p! { i8 i16 i32 i64 u8 u16 u32 u64 usize isize } + + fn same(rust: T, c: T, attr: &str) { + if rust != c { + eprintln!("bad {}: rust: {} != c {}", attr, rust.pretty(), + c.pretty()); + FAILED.store(true, Ordering::SeqCst); + } else { + NTESTS.fetch_add(1, Ordering::SeqCst); + } + } + + macro_rules! offset_of { + ($ty:ident, $field:ident) => ({ + let value = std::mem::MaybeUninit::<$ty>::uninit(); + let base_pointer = value.as_ptr(); + let offset_pointer = std::ptr::addr_of!((*base_pointer).$field); + (offset_pointer as u64) - (base_pointer as u64) + }) + } + + "# + )); + + // Walk the crate, emitting test cases for everything found + visit::walk_crate(&mut gen, &krate); + gen.emit_run_all(); + + out_file + } +} + +#[allow(clippy::cognitive_complexity)] +fn default_cfg(target: &str) -> Vec<(String, Option)> { + let mut ret = Vec::new(); + let (arch, width, endian) = if target.starts_with("x86_64") { + if target.ends_with("x32") { + ("x86_64", "32", "little") + } else { + ("x86_64", "64", "little") + } + } else if target.starts_with("i386") || target.starts_with("i586") || target.starts_with("i686") + { + ("x86", "32", "little") + } else if target.starts_with("arm") { + ("arm", "32", "little") + } else if target.starts_with("aarch64") { + ("aarch64", "64", "little") + } else if target.starts_with("mipsel") { + ("mips", "32", "little") + } else if target.starts_with("mips64el") { + ("mips64", "64", "little") + } else if target.starts_with("mips64") { + ("mips64", "64", "big") + } else if target.starts_with("mips") { + ("mips", "32", "big") + } else if target.starts_with("powerpc64le") { + ("powerpc64", "64", "little") + } else if target.starts_with("powerpc64") { + ("powerpc64", "64", "big") + } else if target.starts_with("powerpc") { + ("powerpc", "32", "big") + } else if target.starts_with("s390x") { + ("s390x", "64", "big") + } else if target.starts_with("sparc64") { + ("sparc64", "64", "big") + } else if target.starts_with("sparcv9") { + ("sparc64", "64", "big") + } else if target.starts_with("asmjs") { + ("asmjs", "32", "little") + } else if target.starts_with("wasm32") { + ("wasm32", "32", "little") + } else if target.starts_with("riscv64gc") { + ("riscv64", "64", "little") + } else if target.starts_with("loongarch64") { + ("loongarch64", "64", "little") + } else { + panic!("unknown arch/pointer width: {}", target) + }; + let (os, family, env) = if target.contains("unknown-linux-gnu") { + ("linux", "unix", "gnu") + } else if target.contains("unknown-linux-musl") { + ("linux", "unix", "musl") + } else if target.contains("unknown-linux-uclibc") { + ("linux", "unix", "uclibc") + } else if target.contains("apple-darwin") { + ("macos", "unix", "") + } else if target.contains("apple-ios") { + ("ios", "unix", "") + } else if target.contains("windows-msvc") { + ("windows", "windows", "msvc") + } else if target.contains("windows-gnu") { + ("windows", "windows", "gnu") + } else if target.contains("android") { + ("android", "unix", "") + } else if target.contains("unknown-freebsd") { + ("freebsd", "unix", "") + } else if target.contains("netbsd") { + ("netbsd", "unix", "") + } else if target.contains("openbsd") { + ("openbsd", "unix", "") + } else if target.contains("dragonfly") { + ("dragonfly", "unix", "") + } else if target.contains("solaris") { + ("solaris", "unix", "") + } else if target.contains("illumos") { + ("illumos", "unix", "") + } else if target.contains("emscripten") { + ("emscripten", "unix", "") + } else if target.contains("wasi") { + ("unknown", "", "wasi") + } else if target.contains("redox") { + ("redox", "unix", "") + } else if target.contains("vxworks") { + ("vxworks", "unix", "") + } else if target.contains("haiku") { + ("haiku", "unix", "") + } else if target.contains("nto-qnx") { + let before_env = "nto-qnx"; + let version = target + .rfind(before_env) + .map(|i| &target[i + before_env.len()..]) + .unwrap(); + let env = match version { + "700" => "nto70", + "710" => "nto71", + "710_iosock" => "nto71_iosock", + "800" => "nto80", + _ => panic!("Unknown version: {version}"), + }; + ("nto", "unix", env) + } else if target.contains("linux-ohos") { + ("linux", "unix", "ohos") + } else if target.contains("aix") { + ("aix", "unix", "") + } else if target.contains("hurd") { + ("hurd", "unix", "gnu") + } else { + panic!("unknown os/family: {}", target) + }; + + ret.push((family.to_string(), None)); + ret.push(("target_os".to_string(), Some(os.to_string()))); + ret.push(("target_family".to_string(), Some(family.to_string()))); + ret.push(("target_arch".to_string(), Some(arch.to_string()))); + ret.push(("target_pointer_width".to_string(), Some(width.to_string()))); + ret.push(("target_endian".to_string(), Some(endian.to_string()))); + ret.push(("target_env".to_string(), Some(env.to_string()))); + + ret +} + +fn linkage(lang: &Lang) -> &'static str { + match lang { + Lang::CXX => "extern \"C\"", + Lang::C => "", + } +} + +impl<'a> Generator<'a> { + fn rust2c_test(&self, ty: &str) -> bool { + let rustc_types = [ + "usize", "u8", "u16", "u32", "u64", "isize", "i8", "i16", "i32", "i64", + ]; + ty.starts_with("c_") || rustc_types.contains(&ty) + } + + fn rustmut2c(&self, mutbl: ast::Mutability) -> String { + match mutbl { + ast::Mutability::Immutable => "const ".to_string(), + ast::Mutability::Mutable => "".to_string(), + } + } + + fn rustmut2str(&self, mutbl: ast::Mutability) -> String { + match mutbl { + ast::Mutability::Immutable => "".to_string(), + ast::Mutability::Mutable => "mut ".to_string(), + } + } + + fn rust2c(&self, ty: &str) -> String { + match ty { + "c_longdouble" | "c_long_double" => format!("long double"), + t if t.starts_with("c_") => match &ty[2..].replace("long", " long")[..] { + s if s.starts_with('u') => format!("unsigned {}", &s[1..]), + "short" => "short".to_string(), + s if s.starts_with('s') => format!("signed {}", &s[1..]), + s => s.to_string(), + }, + + "usize" => "size_t".to_string(), + "isize" => "ssize_t".to_string(), + "u8" => "uint8_t".to_string(), + "u16" => "uint16_t".to_string(), + "u32" => "uint32_t".to_string(), + "u64" => "uint64_t".to_string(), + "i8" => "int8_t".to_string(), + "i16" => "int16_t".to_string(), + "i32" => "int32_t".to_string(), + "i64" => "int64_t".to_string(), + "( )" => "void".to_string(), + s => (self.opts.type_name)(s, self.structs.contains(s), self.unions.contains(s)), + } + } + + fn rust2cfield(&self, struct_: &str, field: &str) -> String { + (self.opts.field_name)(struct_, field) + } + + fn test_type(&mut self, name: &str, ty: &ast::Ty) { + if (self.opts.skip_type)(name) { + if self.opts.verbose_skip { + eprintln!("skipping type \"{}\"", name); + } + return; + } + let c = self.rust_ty_to_c_ty(name); + self.test_size_align(name, &c); + self.test_sign(name, &c, ty); + } + + fn test_struct(&mut self, ty: &str, s: &ast::VariantData) { + if (self.opts.skip_struct)(ty) { + if self.opts.verbose_skip { + eprintln!("skipping struct \"{}\"", ty); + } + return; + } + + let cty = self.rust_ty_to_c_ty(ty); + self.test_size_align(ty, &cty); + + self.tests.push(format!("field_offset_size_{}", ty)); + t!(writeln!( + self.rust, + r#" + #[allow(non_snake_case)] + #[inline(never)] + fn field_offset_size_{ty}() {{ + "#, + ty = ty + )); + for field in s.fields() { + match field.vis { + ast::Visibility::Public => {} + _ => continue, + } + let name = match field.ident { + Some(name) => name, + None => panic!("no tuple structs in FFI"), + }; + let name = name.to_string(); + + if (self.opts.skip_field)(ty, &name) { + if self.opts.verbose_skip { + eprintln!("skipping field \"{}\" of struct \"{}\"", name, ty); + } + + continue; + } + + let cfield = self.rust2cfield(ty, &name); + + t!(writeln!( + self.c, + r#" + {linkage} uint64_t __test_offset_{ty}_{rust_field}(void) {{ + return offsetof({cstructty}, {c_field}); + }} + {linkage} uint64_t __test_fsize_{ty}_{rust_field}(void) {{ + {cstructty}* foo = NULL; + return sizeof(foo->{c_field}); + }} + "#, + ty = ty, + cstructty = cty, + rust_field = name, + c_field = cfield, + linkage = linkage(&self.opts.lang) + )); + + t!(writeln!( + self.rust, + r#" + extern "C" {{ + #[allow(non_snake_case)] + fn __test_offset_{ty}_{field}() -> u64; + #[allow(non_snake_case)] + fn __test_fsize_{ty}_{field}() -> u64; + }} + unsafe {{ + let uninit_ty = std::mem::MaybeUninit::<{ty}>::uninit(); + let uninit_ty = uninit_ty.as_ptr(); + let ty_ptr = std::ptr::addr_of!((*uninit_ty).{field}); + let val = ty_ptr.read_unaligned(); + same(offset_of!({ty}, {field}), + __test_offset_{ty}_{field}(), + "field offset {field} of {ty}"); + same(mem::size_of_val(&val) as u64, + __test_fsize_{ty}_{field}(), + "field size {field} of {ty}"); + }} + "#, + ty = ty, + field = name + )); + + if (self.opts.skip_field_type)(ty, &name.to_string()) { + if self.opts.verbose_skip { + eprintln!("skipping field type \"{}\" of struct \"{}\"", name, ty); + } + + continue; + } + + let sig = format!("__test_field_type_{}_{}({}* b)", ty, name, cty); + let mut sig = self.csig_returning_ptr(&field.ty, &sig); + if (self.opts.volatile_item)(VolatileItemKind::StructField( + ty.to_string(), + name.to_string(), + )) { + sig = format!("volatile {}", sig); + } + t!(writeln!( + self.c, + r#" + {linkage} {sig} {{ + return &b->{c_field}; + }} + "#, + sig = sig, + c_field = cfield, + linkage = linkage(&self.opts.lang) + )); + t!(writeln!( + self.rust, + r#" + extern "C" {{ + #[allow(non_snake_case)] + fn __test_field_type_{ty}_{field}(a: *mut {ty}) + -> *mut u8; + }} + unsafe {{ + let mut uninit_ty = std::mem::MaybeUninit::<{ty}>::uninit(); + let uninit_ty = uninit_ty.as_mut_ptr(); + let ty_ptr_mut = std::ptr::addr_of_mut!(*uninit_ty); + let field_ptr = std::ptr::addr_of!((*uninit_ty).{field}); + same(field_ptr as *mut _, + __test_field_type_{ty}_{field}(ty_ptr_mut), + "field type {field} of {ty}"); + #[allow(unknown_lints, forgetting_copy_types)] + mem::forget(uninit_ty); + }} + "#, + ty = ty, + field = name + )); + } + t!(writeln!( + self.rust, + r#" + }} + "# + )); + } + + fn test_size_align(&mut self, rust: &str, c: &str) { + t!(writeln!( + self.c, + r#" + {linkage} uint64_t __test_size_{ty}(void) {{ return sizeof({cty}); }} + {linkage} uint64_t __test_align_{ty}(void) {{ + typedef struct {{ + unsigned char c; + {cty} v; + }} type; + type t; + size_t t_addr = (size_t)(unsigned char*)(&t); + size_t v_addr = (size_t)(unsigned char*)(&t.v); + return t_addr >= v_addr? t_addr - v_addr : v_addr - t_addr; + }} + "#, + ty = rust, + cty = c, + linkage = linkage(&self.opts.lang) + )); + t!(writeln!( + self.rust, + r#" + #[allow(non_snake_case)] + #[inline(never)] + fn size_align_{ty}() {{ + extern "C" {{ + #[allow(non_snake_case)] + fn __test_size_{ty}() -> u64; + #[allow(non_snake_case)] + fn __test_align_{ty}() -> u64; + }} + unsafe {{ + same(mem::size_of::<{ty}>() as u64, + __test_size_{ty}(), "{ty} size"); + same(mem::align_of::<{ty}>() as u64, + __test_align_{ty}(), "{ty} align"); + }} + }} + "#, + ty = rust + )); + self.tests.push(format!("size_align_{}", rust)); + } + + fn has_sign(&self, ty: &ast::Ty) -> bool { + match ty.node { + ast::TyKind::Path(_, ref path) => { + let last = path.segments.last().unwrap().identifier.to_string(); + if let Some(aliased) = self.aliases.get(&last) { + return self.has_sign(aliased); + } + match self.rust2c(&last).as_str() { + "char" | "short" | "int" | "long" | "long long" | "int8_t" | "int16_t" + | "int32_t" | "int64_t" | "uint8_t" | "uint16_t" | "uint32_t" | "uint64_t" + | "size_t" | "ssize_t" => true, + s => s.starts_with("signed ") || s.starts_with("unsigned "), + } + } + _ => false, + } + } + + fn test_sign(&mut self, rust: &str, c: &str, ty: &ast::Ty) { + if (self.opts.skip_signededness)(rust) { + if self.opts.verbose_skip { + eprintln!("skipping sign \"{}\"", rust); + } + + return; + } + if !self.has_sign(ty) { + return; + } + t!(writeln!( + self.c, + r#" + {linkage} uint32_t __test_signed_{ty}(void) {{ + return ((({cty}) -1) < 0); + }} + "#, + ty = rust, + cty = c, + linkage = linkage(&self.opts.lang) + )); + t!(writeln!( + self.rust, + r#" + #[inline(never)] + #[allow(non_snake_case)] + fn sign_{ty}() {{ + extern "C" {{ + #[allow(non_snake_case)] + fn __test_signed_{ty}() -> u32; + }} + unsafe {{ + same(((!(0 as {ty})) < (0 as {ty})) as u32, + __test_signed_{ty}(), "{ty} signed"); + }} + }} + "#, + ty = rust + )); + self.tests.push(format!("sign_{}", rust)); + } + + fn rust_ty_to_c_ty(&self, mut rust_ty: &str) -> String { + if rust_ty == "&str" { + return "char*".to_string(); + } + let mut cty = self.rust2c(&rust_ty.replace("*mut ", "").replace("*const ", "")); + while rust_ty.starts_with('*') { + if rust_ty.starts_with("*const") { + cty = format!("const {}*", cty); + rust_ty = &rust_ty[7..]; + } else { + cty = format!("{}*", cty); + rust_ty = &rust_ty[5..]; + } + } + cty + } + + #[allow(clippy::similar_names)] + fn test_const(&mut self, name: &str, rust_ty: &str) { + if (self.opts.skip_const)(name) { + if self.opts.verbose_skip { + eprintln!("skipping const \"{}\"", name); + } + + return; + } + + let c_name = (self.opts.const_cname)(name); + + let cty = self.rust_ty_to_c_ty(rust_ty); + t!(writeln!( + self.c, + r#" + static const {cty} __test_const_{name}_val = {c_name}; + {linkage} const {cty}* __test_const_{name}(void) {{ + return &__test_const_{name}_val; + }} + "#, + name = name, + c_name = c_name, + cty = cty, + linkage = linkage(&self.opts.lang) + )); + + if rust_ty == "&str" { + t!(writeln!( + self.rust, + r#" + #[inline(never)] + #[allow(non_snake_case)] + fn const_{name}() {{ + extern "C" {{ + #[allow(non_snake_case)] + fn __test_const_{name}() -> *const *const u8; + }} + let val = {name}; + unsafe {{ + let ptr = *__test_const_{name}(); + let c = ::std::ffi::CStr::from_ptr(ptr as *const _); + let c = c.to_str().expect("const {name} not utf8"); + same(val, c, "{name} string"); + }} + }} + "#, + name = name + )); + } else { + t!(writeln!( + self.rust, + r#" + #[allow(non_snake_case)] + fn const_{name}() {{ + extern "C" {{ + #[allow(non_snake_case)] + fn __test_const_{name}() -> *const {ty}; + }} + let val = {name}; + unsafe {{ + let ptr1 = &val as *const _ as *const u8; + let ptr2 = __test_const_{name}() as *const u8; + for i in 0..mem::size_of::<{ty}>() {{ + let i = i as isize; + same(*ptr1.offset(i), *ptr2.offset(i), + &format!("{name} value at byte {{}}", i)); + }} + }} + }} + "#, + ty = rust_ty, + name = name + )); + } + self.tests.push(format!("const_{}", name)); + } + + fn test_extern_fn( + &mut self, + name: &str, + c_name: &Option, + args: &[String], + ret: &str, + variadic: bool, + abi: Abi, + ) { + if (self.opts.skip_fn)(name) { + if self.opts.verbose_skip { + eprintln!("skipping fn \"{}\"", name); + } + return; + } + let c_name = (self.opts.fn_cname)(name, c_name.as_ref().map(|s| &**s)); + let args = if args.is_empty() && !variadic { + "void".to_string() + } else { + args.iter() + .enumerate() + .map(|(idx, a)| { + let mut arg = self.rust_ty_to_c_ty(a); + if (self.opts.volatile_item)(VolatileItemKind::FunctionArg( + name.to_string(), + idx, + )) { + arg = format!("volatile {}", arg); + } + if (self.opts.array_arg)(name, idx) { + if let Some(last_ptr) = arg.rfind('*') { + arg = arg[..last_ptr].to_string(); + } else { + panic!("C FFI decl `{}` contains array argument", name); + } + } + arg + }) + .map(|s| { + if let Some(i) = s.rfind(']') { + let c = s.chars().filter(|&c| c == '*').count(); + if c == 0 { + return s; + } + let postfix_idx = s.find('[').unwrap(); + let postfix = &s[postfix_idx..=i]; + let prefix = &s[..postfix_idx]; + let pointers = &s[i + 1..]; + let has_const = pointers.contains("const"); + let pointers = pointers.replace("const *", "* const"); + let prefix = prefix.replacen("const", "", if has_const { 1 } else { 0 }); + return format!("{} ({}) {}", prefix, pointers, postfix); + } + s + }) + .collect::>() + .join(", ") + + if variadic { ", ..." } else { "" } + }; + let mut c_ret = self.rust_ty_to_c_ty(ret); + if (self.opts.volatile_item)(VolatileItemKind::FunctionRet(name.to_string())) { + c_ret = format!("volatile {}", c_ret); + } + let abi = self.abi2str(abi); + t!(writeln!( + self.c, + r#" + {linkage} {ret} ({abi}*__test_fn_{name}(void))({args}) {{ + return {c_name}; + }} + "#, + name = name, + c_name = c_name, + args = args, + ret = c_ret, + abi = abi, + linkage = linkage(&self.opts.lang) + )); + t!(writeln!( + self.rust, + r#" + #[allow(non_snake_case)] + #[inline(never)] + fn fn_{name}() {{ + extern "C" {{ + #[allow(non_snake_case)] + fn __test_fn_{name}() -> *mut u32; + }} + unsafe {{ + if !{skip} {{ + same({name} as usize, + __test_fn_{name}() as usize, + "{name} function pointer"); + }} + }} + }} + "#, + name = name, + skip = (self.opts.skip_fn_ptrcheck)(name) + )); + if self.opts.verbose_skip && (self.opts.skip_fn_ptrcheck)(name) { + eprintln!("skipping fn ptr check \"{}\"", name); + } + + self.tests.push(format!("fn_{}", name)); + } + + fn test_extern_static( + &mut self, + name: &str, + c_name: Option, + rust_ty: &str, + c_ty: &str, + mutbl: bool, + ) { + if (self.opts.skip_static)(name) { + if self.opts.verbose_skip { + eprintln!("skipping static \"{}\"", name); + } + return; + } + + let c_name = c_name.unwrap_or_else(|| name.to_string()); + + if rust_ty.contains("extern fn") || rust_ty.contains("extern \"C\" fn") { + let sig = c_ty.replacen("(*)", &format!("(* __test_static_{}(void))", name), 1); + t!(writeln!( + self.c, + r#" + {sig} {{ + return {c_name}; + }} + "#, + sig = sig, + c_name = c_name + )); + t!(writeln!( + self.rust, + r#" + #[inline(never)] + #[allow(non_snake_case)] + fn static_{name}() {{ + extern "C" {{ + #[allow(non_snake_case)] + fn __test_static_{name}() -> {ty}; + }} + unsafe {{ + // We must use addr_of! here because of https://github.com/rust-lang/rust/issues/114447 + same(*(std::ptr::addr_of!({name}) as *const {ty}) as usize, + __test_static_{name}() as usize, + "{name} static"); + }} + }} + "#, + name = name, + ty = rust_ty + )); + } else if rust_ty.starts_with('[') && rust_ty.ends_with(']') { + let c_ptr_ty = c_ty.split(' ').next().unwrap(); + let mut lens = Vec::new(); + for i in c_ty.split(' ').skip(1) { + lens.push(i); + } + lens.reverse(); + let array_test_name = format!( + "{mutbl} {elem} (*__test_static_{name}(void)){lens}", + mutbl = if mutbl { "" } else { "const" }, + elem = c_ptr_ty, + name = name, + lens = lens.join("") + ); + t!(writeln!( + self.c, + r#" + {array_test_name} {{ + return &{c_name}; + }} + "#, + array_test_name = array_test_name, + c_name = c_name + )); + t!(writeln!( + self.rust, + r#" + #[inline(never)] + #[allow(non_snake_case)] + fn static_{name}() {{ + extern "C" {{ + #[allow(non_snake_case)] + fn __test_static_{name}() -> *{mutbl} {ty}; + }} + unsafe {{ + // We must use addr_of! here because of https://github.com/rust-lang/rust/issues/114447 + same(std::ptr::addr_of!({name}) as usize, + __test_static_{name}() as usize, + "{name} static"); + }} + }} + "#, + name = name, + mutbl = if mutbl { "mut" } else { "const" }, + ty = rust_ty + )); + } else { + let c_ty = if (self.opts.volatile_item)(VolatileItemKind::Static(name.to_owned())) { + format!("volatile {}", c_ty) + } else { + c_ty.to_owned() + }; + + t!(writeln!( + self.c, + r#" + {mutbl}{ty}* __test_static_{name}(void) {{ + return &{c_name}; + }} + "#, + mutbl = if mutbl || c_ty.contains("const") { + "" + } else { + "const " + }, + ty = c_ty, + name = name, + c_name = c_name + )); + t!(writeln!( + self.rust, + r#" + #[allow(non_snake_case)] + #[inline(never)] + fn static_{name}() {{ + extern "C" {{ + #[allow(non_snake_case)] + fn __test_static_{name}() -> *{mutbl} {ty}; + }} + unsafe {{ + // We must use addr_of! here because of https://github.com/rust-lang/rust/issues/114447 + same(std::ptr::addr_of!({name}) as usize, + __test_static_{name}() as usize, + "{name} static"); + }} + }} + "#, + name = name, + mutbl = if mutbl { "mut" } else { "const" }, + ty = rust_ty + )); + }; + self.tests.push(format!("static_{}", name)); + } + + fn test_roundtrip(&mut self, rust: &str, ast: Option<&ast::VariantData>) { + if (self.opts.skip_struct)(rust) { + if self.opts.verbose_skip { + eprintln!("skipping roundtrip (skip_struct) \"{}\"", rust); + } + return; + } + if (self.opts.skip_type)(rust) { + if self.opts.verbose_skip { + eprintln!("skipping roundtrip (skip_type) \"{}\"", rust); + } + return; + } + if (self.opts.skip_roundtrip)(rust) { + if self.opts.verbose_skip { + eprintln!("skipping roundtrip (skip_roundtrip)\"{}\"", rust); + } + return; + } + + let c = self.rust_ty_to_c_ty(rust); + + // Generate a function that returns a vector for a type + // that contains 1 if the byte is padding, and 0 if the byte is not + // padding: + t!(writeln!( + self.rust, + r#" + #[allow(non_snake_case, unused_mut, unused_variables, deprecated)] + #[inline(never)] + fn roundtrip_padding_{ty}() -> Vec {{ + // stores (offset, size) for each field + let mut v = Vec::<(usize, usize)>::new(); + let foo = std::mem::MaybeUninit::<{ty}>::uninit(); + let foo = foo.as_ptr(); + "#, + ty = rust + )); + + if let Some(ast) = ast { + for field in ast.fields() { + // If a field is private, we can't access it, so + // we treat that as padding.. + match field.vis { + ast::Visibility::Public => {} + _ => continue, + } + + let name = match field.ident { + Some(name) => name, + None => panic!("no tuple structs in FFI"), + }; + let name = name.to_string(); + + t!(writeln!( + self.rust, + r#" + unsafe {{ + let ty_ptr = std::ptr::addr_of!((*foo).{field}); + let val = ty_ptr.read_unaligned(); + let size = mem::size_of_val(&val); + let off = offset_of!({ty}, {field}) as usize; + v.push((off, size)); + }} + "#, + ty = rust, + field = name + )); + } + } + t!(writeln!( + self.rust, + r#" + // This vector contains `1` if the byte is padding + // and `0` if the byte is not padding. + let mut pad = Vec::::new(); + // Initialize all bytes as: + // - padding if we have fields, this means that only + // the fields will be checked + // - no-padding if we have a type alias: if this + // causes problems the type alias should be skipped + pad.resize(mem::size_of::<{ty}>(), {def}); + for (off, size) in &v {{ + for i in 0..*size {{ + pad[off + i] = 0; + }} + }} + pad + }} + "#, + ty = rust, + def = if ast.is_some() { 1 } else { 0 } + )); + + // Rust writes 1,2,3... to each byte of the type, passes + // the type to C by value exercising the call ABI. + // C verifies the bytes, writes the pattern 255,254,253... + // to it, and returns it by value. + // Rust reads it, and verifies it. The value `0` is never written + // to a byte (42 is used instead). Uninitialized memory is often + // all zeros, so for a single byte the test could return + // success even though it should have failed. + t!(writeln!( + self.c, + r#" + #ifdef _MSC_VER + // Disable signed/unsigned conversion warnings on MSVC. + // These trigger even if the conversion is explicit. + # pragma warning(disable:4365) + #endif + {linkage} {cty} __test_roundtrip_{ty}( + int32_t rust_size, {cty} value, int* error, unsigned char* pad + ) {{ + volatile unsigned char* p = (volatile unsigned char*)&value; + int size = (int)sizeof({cty}); + if (size != rust_size) {{ + fprintf( + stderr, + "size of {cty} is %d in C and %d in Rust\n", + (int)size, (int)rust_size + ); + *error = 1; + return value; + }} + int i = 0; + for (i = 0; i < size; ++i) {{ + if (pad[i]) {{ continue; }} + // fprintf(stdout, "C testing byte %d of %d of \"{ty}\"\n", i, size); + unsigned char c = (unsigned char)(i % 256); + c = c == 0? 42 : c; + if (p[i] != c) {{ + *error = 1; + fprintf( + stderr, + "rust[%d] = %d != %d (C): Rust \"{ty}\" -> C\n", + i, (int)p[i], (int)c + ); + }} + unsigned char d + = (unsigned char)(255) - (unsigned char)(i % 256); + d = d == 0? 42: d; + p[i] = d; + }} + return value; + }} + #ifdef _MSC_VER + # pragma warning(default:4365) + #endif + "#, + ty = rust, + cty = c, + linkage = linkage(&self.opts.lang), + )); + t!(writeln!( + self.rust, + r#" + #[allow(non_snake_case, deprecated)] + #[inline(never)] + fn roundtrip_{ty}() {{ + use libc::c_int; + type U = {ty}; + #[allow(improper_ctypes)] + extern "C" {{ + #[allow(non_snake_case)] + fn __test_roundtrip_{ty}( + size: i32, x: U, e: *mut c_int, pad: *const u8 + ) -> U; + }} + let pad = roundtrip_padding_{ty}(); + unsafe {{ + use std::mem::{{MaybeUninit, size_of}}; + let mut error: c_int = 0; + let mut y = MaybeUninit::::uninit(); + let mut x = MaybeUninit::::uninit(); + let x_ptr = x.as_mut_ptr().cast::(); + let y_ptr = y.as_mut_ptr().cast::(); + let sz = size_of::(); + for i in 0..sz {{ + let c: u8 = (i % 256) as u8; + let c = if c == 0 {{ 42 }} else {{ c }}; + let d: u8 = 255_u8 - (i % 256) as u8; + let d = if d == 0 {{ 42 }} else {{ d }}; + x_ptr.add(i).write_volatile(c); + y_ptr.add(i).write_volatile(d); + }} + let r: U = __test_roundtrip_{ty}(sz as i32, x.assume_init(), &mut error, pad.as_ptr()); + if error == 1 {{ + FAILED.store(true, Ordering::SeqCst); + return; + }} + for i in 0..size_of::() {{ + if pad[i] == 1 {{ continue; }} + // eprintln!("Rust testing byte {{}} of {{}} of {ty}", i, size_of::()); + let rust = (*y_ptr.add(i)) as usize; + let c = (&r as *const _ as *const u8) + .add(i).read_volatile() as usize; + if rust != c {{ + eprintln!( + "rust [{{}}] = {{}} != {{}} (C): C \"{ty}\" -> Rust", + i, rust, c + ); + FAILED.store(true, Ordering::SeqCst); + }} + }} + }} + }} + "#, + ty = rust + )); + self.tests.push(format!("roundtrip_{}", rust)); + } + + fn assert_no_generics(&self, _i: ast::Ident, generics: &ast::Generics) { + assert!(generics.lifetimes.is_empty()); + assert!(generics.ty_params.is_empty()); + assert!(generics.where_clause.predicates.is_empty()); + } + + fn ty2name(&self, ty: &ast::Ty, rust: bool) -> String { + match ty.node { + ast::TyKind::Path(_, ref path) => { + let last = path.segments.last().unwrap(); + if last.identifier.to_string() == "Option" { + match last.parameters.as_ref().map(|p| &**p) { + Some(&ast::PathParameters::AngleBracketed(ref p)) => { + self.ty2name(&p.types[0], rust) + } + _ => panic!(), + } + } else if rust { + last.identifier.to_string() + } else { + self.rust2c(&last.identifier.to_string()) + } + } + ast::TyKind::Ptr(ref t) => { + if rust { + format!( + "*{} {}", + match t.mutbl { + ast::Mutability::Immutable => "const", + ast::Mutability::Mutable => "mut", + }, + self.ty2name(&t.ty, rust) + ) + } else { + let modifier = match t.mutbl { + ast::Mutability::Immutable => "const ", + ast::Mutability::Mutable => "", + }; + match t.ty.node { + ast::TyKind::BareFn(..) => self.ty2name(&t.ty, rust), + ast::TyKind::Ptr(..) => { + format!("{} {}*", self.ty2name(&t.ty, rust), modifier) + } + ast::TyKind::Array(ref t, ref e) => { + let len = self.expr2str(e); + let ty = self.ty2name(t, rust); + format!("{} {} [{}]", modifier, ty, len) + } + _ => format!("{}{}*", modifier, self.ty2name(&t.ty, rust)), + } + } + } + ast::TyKind::BareFn(ref t) => { + if rust { + let args = t + .decl + .inputs + .iter() + .map(|a| self.ty2name(&a.ty, rust)) + .collect::>() + .join(", "); + let ret = match t.decl.output { + ast::FunctionRetTy::Default(..) => "()".to_string(), + ast::FunctionRetTy::Ty(ref t) => self.ty2name(t, rust), + }; + format!("extern \"C\" fn({}) -> {}", args, ret) + } else { + assert!(t.lifetimes.is_empty()); + let (ret, mut args, variadic) = self.decl2rust(&t.decl); + assert!(!variadic); + if args.is_empty() { + args.push("void".to_string()); + } + + if ret.contains("(*)") { + ret.replace("(*)", &format!("(*(*)({}))", args.join(", "))) + } else { + format!("{}(*)({})", ret, args.join(", ")) + } + } + } + ast::TyKind::Array(ref t, ref e) => { + if rust { + format!("[{}; {}]", self.ty2name(t, rust), self.expr2str(e)) + } else { + let len = self.expr2str(e); + let ty = self.ty2name(t, rust); + format!("{} [{}]", ty, len) + } + } + ast::TyKind::Rptr(l, ast::MutTy { ref ty, mutbl }) => { + let path = match ty.node { + ast::TyKind::Path(_, ref p) => p, + ast::TyKind::Array(ref t, _) => { + assert!(!rust); + return format!("{}{}*", self.rustmut2c(mutbl), self.ty2name(t, rust)); + } + _ => panic!("unknown ty {:?}", ty), + }; + if path.segments.len() != 1 { + panic!("unknown ty {:?}", ty) + } + match &*path.segments[0].identifier.name.as_str() { + "str" => { + if mutbl != ast::Mutability::Immutable { + panic!("unknown ty {:?}", ty) + } + if rust { + "&str".to_string() + } else { + "char*".to_string() + } + } + c if self.rust2c_test(c) => { + if rust { + match l { + Some(l) => format!( + "&{} {} {}", + l.ident.name.as_str(), + self.rustmut2str(mutbl), + self.ty2name(ty, rust) + ), + None => format!( + "&{:?} {}", + self.rustmut2str(mutbl), + self.ty2name(ty, rust) + ), + } + } else { + format!("{}{}*", self.rustmut2c(mutbl), self.rust2c(c)) + } + } + v => panic!("ref of unknown ty {:?} {:?} {:?} => {:?}", l, mutbl, ty, v), + } + } + ast::TyKind::Tup(ref v) if v.is_empty() => { + if rust { + "()".to_string() + } else { + "void".to_string() + } + } + _ => panic!("unknown ty {:?}", ty), + } + } + + fn csig_returning_ptr(&self, ty: &ast::Ty, sig: &str) -> String { + match ty.node { + ast::TyKind::Path(_, ref path) + if path.segments.last().unwrap().identifier.to_string() == "Option" => + { + let last = path.segments.last().unwrap(); + match last.parameters.as_ref().map(|s| &**s) { + Some(&ast::PathParameters::AngleBracketed(ref p)) => { + self.csig_returning_ptr(&p.types[0], sig) + } + _ => panic!(), + } + } + ast::TyKind::BareFn(ref t) => { + assert!(t.lifetimes.is_empty()); + let (ret, mut args, variadic) = self.decl2rust(&t.decl); + let abi = self.abi2str(t.abi); + if variadic { + args.push("...".to_string()); + } else if args.is_empty() { + args.push("void".to_string()); + } + format!("{}({}**{})({})", ret, abi, sig, args.join(", ")) + } + ast::TyKind::Array(ref t, ref e) => match t.node { + ast::TyKind::Array(ref t2, ref e2) => format!( + "{}(*{})[{}][{}]", + self.ty2name(t2, false), + sig, + self.expr2str(e), + self.expr2str(e2) + ), + _ => format!("{}(*{})[{}]", self.ty2name(t, false), sig, self.expr2str(e)), + }, + _ => format!("{}* {}", self.ty2name(ty, false), sig), + } + } + + fn expr2str(&self, e: &ast::Expr) -> String { + match e.node { + ast::ExprKind::Lit(ref l) => match l.node { + ast::LitKind::Int(a, _) => a.to_string(), + _ => panic!("unknown literal: {:?}", l), + }, + ast::ExprKind::Path(_, ref path) => { + path.segments.last().unwrap().identifier.to_string() + } + ast::ExprKind::Cast(ref e, _) => self.expr2str(e), + ast::ExprKind::Binary(ref op, ref e1, ref e2) => { + let e1 = self.expr2str(e1); + let e2 = self.expr2str(e2); + match op.node { + ast::BinOpKind::Add => format!("{} + {}", e1, e2), + ast::BinOpKind::Sub => format!("{} - {}", e1, e2), + _ => panic!("unknown op: {:?}", op), + } + } + _ => panic!("unknown expr: {:?}", e), + } + } + + fn abi2str(&self, abi: Abi) -> &'static str { + match abi { + Abi::C => "", + Abi::Stdcall => "__stdcall ", + Abi::System if self.target.contains("i686-pc-windows") => "__stdcall ", + Abi::System => "", + a => panic!("unknown ABI: {}", a), + } + } + + fn decl2rust(&self, decl: &ast::FnDecl) -> (String, Vec, bool) { + let args = decl + .inputs + .iter() + .map(|arg| self.ty2name(&arg.ty, false)) + .collect::>(); + let ret = match decl.output { + ast::FunctionRetTy::Default(..) => "void".to_string(), + ast::FunctionRetTy::Ty(ref t) => match t.node { + ast::TyKind::Never => "void".to_string(), + ast::TyKind::Tup(ref t) if t.is_empty() => "void".to_string(), + _ => self.ty2name(t, false), + }, + }; + (ret, args, decl.variadic) + } + + fn emit_run_all(&mut self) { + const N: usize = 1000; + let mut n = 0; + let mut tests = self.tests.clone(); + while tests.len() > N { + let name = format!("run_group{}", n); + n += 1; + t!(writeln!( + self.rust, + " + #[inline(never)] + fn {}() {{ + ", + name + )); + for test in tests.drain(..1000) { + t!(writeln!(self.rust, "{}();", test)); + } + t!(writeln!(self.rust, "}}")); + tests.push(name); + } + t!(writeln!( + self.rust, + " + #[inline(never)] + fn run_all() {{ + " + )); + for test in &tests { + t!(writeln!(self.rust, "{}();", test)); + } + t!(writeln!( + self.rust, + " + }} + " + )); + } +} + +impl<'a, 'v> Visitor<'v> for Generator<'a> { + fn visit_item(&mut self, i: &'v ast::Item) { + let prev_abi = self.abi; + let public = i.vis == ast::Visibility::Public; + match i.node { + ast::ItemKind::Ty(ref ty, ref generics) if public => { + self.assert_no_generics(i.ident, generics); + self.test_type(&i.ident.to_string(), ty); + self.test_roundtrip(&i.ident.to_string(), None); + } + + ast::ItemKind::Struct(ref s, ref generics) + | ast::ItemKind::Union(ref s, ref generics) + if public => + { + self.assert_no_generics(i.ident, generics); + let is_c = i.attrs.iter().any(|a| { + attr::find_repr_attrs(self.sh, a) + .iter() + .any(|a| *a == ReprAttr::ReprExtern || *a == ReprAttr::ReprTransparent) + }); + if !is_c && !(self.opts.skip_struct)(&i.ident.to_string()) { + panic!("{} is not marked #[repr(C)]", i.ident); + } + self.test_struct(&i.ident.to_string(), s); + self.test_roundtrip(&i.ident.to_string(), Some(s)); + } + + ast::ItemKind::Const(ref ty, _) if public => { + let ty = self.ty2name(ty, true); + self.test_const(&i.ident.to_string(), &ty); + } + + ast::ItemKind::ForeignMod(ref fm) => { + self.abi = fm.abi; + } + + _ => {} + } + let file = self.sess.codemap().span_to_filename(i.span); + if self.files.insert(file.clone()) { + println!("cargo:rerun-if-changed={}", file); + } + visit::walk_item(self, i); + self.abi = prev_abi; + } + + fn visit_foreign_item(&mut self, i: &'v ast::ForeignItem) { + match i.node { + ast::ForeignItemKind::Fn(ref decl, ref generics) => { + self.assert_no_generics(i.ident, generics); + for arg in &decl.inputs { + if let ast::TyKind::Array(_, _) = arg.ty.node { + panic!( + "Foreign Function decl `{}` uses array in C FFI", + &i.ident.to_string() + ); + } + } + + let (ret, args, variadic) = self.decl2rust(decl); + let c_name = attr::first_attr_value_str_by_name(&i.attrs, "link_name") + .map(|i| i.to_string()); + let abi = self.abi; + self.test_extern_fn(&i.ident.to_string(), &c_name, &args, &ret, variadic, abi); + } + ast::ForeignItemKind::Static(ref ty, mutbl) => { + let rust_ty = self.ty2name(&ty, true); + let c_ty = self.ty2name(&ty, false); + let c_name = attr::first_attr_value_str_by_name(&i.attrs, "link_name") + .map(|i| i.to_string()); + self.test_extern_static(&i.ident.to_string(), c_name, &rust_ty, &c_ty, mutbl); + } + } + visit::walk_foreign_item(self, i) + } + + fn visit_mac(&mut self, _mac: &'v ast::Mac) {} +} + +impl<'v> Visitor<'v> for TyFinder { + fn visit_item(&mut self, i: &'v ast::Item) { + match i.node { + ast::ItemKind::Struct(..) | ast::ItemKind::Enum(..) => { + self.structs.insert(i.ident.to_string()); + } + ast::ItemKind::Union(..) => { + self.unions.insert(i.ident.to_string()); + } + ast::ItemKind::Ty(ref ty, ..) => { + self.aliases.insert(i.ident.to_string(), ty.clone()); + } + + _ => {} + } + visit::walk_item(self, i) + } + fn visit_mac(&mut self, _mac: &'v ast::Mac) {} +} + +struct MyResolver<'a> { + parse_sess: &'a ParseSess, + id: usize, + map: HashMap>, +} + +impl<'a> Resolver for MyResolver<'a> { + fn next_node_id(&mut self) -> ast::NodeId { + self.id += 1; + ast::NodeId::new(self.id) + } + + fn get_module_scope(&mut self, _id: ast::NodeId) -> Mark { + Mark::root() + } + + fn eliminate_crate_var(&mut self, item: P) -> P { + item + } + + fn is_whitelisted_legacy_custom_derive(&self, _name: Name) -> bool { + false + } + + fn visit_expansion(&mut self, _invoc: Mark, expansion: &Expansion, _derives: &[Mark]) { + if let Expansion::Items(ref items) = expansion { + for item in items.iter() { + MyVisitor { + parse_sess: self.parse_sess, + map: &mut self.map, + } + .visit_item(item); + } + } + } + + fn add_builtin(&mut self, _ident: ast::Ident, _ext: Rc) {} + + fn resolve_imports(&mut self) {} + + fn find_legacy_attr_invoc(&mut self, attrs: &mut Vec) -> Option { + attrs.retain(|a| !a.check_name("derive")); + None + } + + fn resolve_invoc( + &mut self, + invoc: &mut Invocation, + _scope: Mark, + _force: bool, + ) -> Result>, Determinacy> { + if let InvocationKind::Bang { ref mac, .. } = invoc.kind { + if mac.node.path.segments.len() != 1 { + return Ok(None); + } + let seg = &mac.node.path.segments[0]; + if seg.parameters.is_some() { + return Ok(None); + } + return Ok(self.map.get(&seg.identifier.name).cloned()); + } + Err(Determinacy::Determined) + } + + fn resolve_macro( + &mut self, + _scope: Mark, + _path: &ast::Path, + _kind: MacroKind, + _force: bool, + ) -> Result, Determinacy> { + Err(Determinacy::Determined) + } + + fn check_unused_macros(&self) {} +} + +struct StripUnchecked; + +impl Folder for StripUnchecked { + fn fold_item(&mut self, item: P) -> SmallVector> { + match item.node { + ast::ItemKind::Mod(..) + | ast::ItemKind::ForeignMod(..) + | ast::ItemKind::Ty(..) + | ast::ItemKind::Enum(..) + | ast::ItemKind::Struct(..) + | ast::ItemKind::Union(..) + | ast::ItemKind::Mac(..) + | ast::ItemKind::MacroDef(..) + | ast::ItemKind::Use(..) + | ast::ItemKind::ExternCrate(..) + | ast::ItemKind::Const(..) => fold::noop_fold_item(item, self), + + ast::ItemKind::Static(..) + | ast::ItemKind::Fn(..) + | ast::ItemKind::GlobalAsm(..) + | ast::ItemKind::Trait(..) + | ast::ItemKind::DefaultImpl(..) + | ast::ItemKind::Impl(..) => SmallVector::default(), + } + } + + fn fold_mac(&mut self, mac: ast::Mac) -> ast::Mac { + fold::noop_fold_mac(mac, self) + } +} + +struct MyVisitor<'b> { + parse_sess: &'b ParseSess, + map: &'b mut HashMap>, +} + +impl<'a, 'b> Visitor<'a> for MyVisitor<'b> { + fn visit_item(&mut self, item: &'a ast::Item) { + if let ast::ItemKind::MacroDef(..) = item.node { + self.map.insert( + item.ident.name, + Rc::new(macro_rules::compile(self.parse_sess, item)), + ); + } + visit::walk_item(self, item); + } + + fn visit_mac(&mut self, _: &'a ast::Mac) { + /* ignore macros */ + } +} + +impl Default for TestGenerator { + fn default() -> Self { + Self::new() + } +} diff --git a/ctest/testcrate/Cargo.toml b/ctest/testcrate/Cargo.toml new file mode 100644 index 0000000000000..c3be18b5e993b --- /dev/null +++ b/ctest/testcrate/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "testcrate" +version = "0.1.0" +authors = ["Alex Crichton "] +build = "build.rs" +edition = "2021" +publish = false + +[build-dependencies] +ctest2 = { path = ".." } +cc = "1.0" + +[dependencies] +libc = "0.2" + +[lib] +name = "testcrate" +test = false +doctest = false + +[[bin]] +name = "t1" +test = false + +[[bin]] +name = "t2" +test = false + +[[bin]] +name = "t1_cxx" +test = false + +[[bin]] +name = "t2_cxx" +test = false diff --git a/ctest/testcrate/build.rs b/ctest/testcrate/build.rs new file mode 100644 index 0000000000000..7c9d554ef5148 --- /dev/null +++ b/ctest/testcrate/build.rs @@ -0,0 +1,99 @@ +fn main() { + use std::env; + let opt_level = env::var("OPT_LEVEL") + .ok() + .and_then(|s| s.parse().ok()) + .unwrap_or(0); + let profile = env::var("PROFILE").unwrap_or_default(); + if profile == "release" || opt_level >= 2 { + println!("cargo:rustc-cfg=optimized"); + } + cc::Build::new() + .include("src") + .warnings(false) + .file("src/t1.c") + .compile("libt1.a"); + println!("cargo:rerun-if-changed=src/t1.c"); + println!("cargo:rerun-if-changed=src/t1.h"); + cc::Build::new() + .warnings(false) + .file("src/t2.c") + .compile("libt2.a"); + println!("cargo:rerun-if-changed=src/t2.c"); + println!("cargo:rerun-if-changed=src/t2.h"); + ctest2::TestGenerator::new() + .header("t1.h") + .include("src") + .fn_cname(|a, b| b.unwrap_or(a).to_string()) + .type_name(move |ty, is_struct, is_union| match ty { + "T1Union" => ty.to_string(), + "Transparent" => ty.to_string(), + t if is_struct => format!("struct {}", t), + t if is_union => format!("union {}", t), + t => t.to_string(), + }) + .volatile_item(t1_volatile) + .array_arg(t1_arrays) + .skip_roundtrip(|n| n == "Arr") + .generate("src/t1.rs", "t1gen.rs"); + ctest2::TestGenerator::new() + .header("t2.h") + .include("src") + .type_name(move |ty, is_struct, is_union| match ty { + "T2Union" => ty.to_string(), + t if is_struct => format!("struct {}", t), + t if is_union => format!("union {}", t), + t => t.to_string(), + }) + .skip_roundtrip(|_| true) + .generate("src/t2.rs", "t2gen.rs"); + + ctest2::TestGenerator::new() + .header("t1.h") + .language(ctest2::Lang::CXX) + .include("src") + .fn_cname(|a, b| b.unwrap_or(a).to_string()) + .type_name(move |ty, is_struct, is_union| match ty { + "T1Union" => ty.to_string(), + "Transparent" => ty.to_string(), + t if is_struct => format!("struct {}", t), + t if is_union => format!("union {}", t), + t => t.to_string(), + }) + .volatile_item(t1_volatile) + .array_arg(t1_arrays) + .skip_roundtrip(|n| n == "Arr") + .generate("src/t1.rs", "t1gen_cxx.rs"); + ctest2::TestGenerator::new() + .header("t2.h") + .language(ctest2::Lang::CXX) + .include("src") + .type_name(move |ty, is_struct, is_union| match ty { + "T2Union" => ty.to_string(), + t if is_struct => format!("struct {}", t), + t if is_union => format!("union {}", t), + t => t.to_string(), + }) + .skip_roundtrip(|_| true) + .generate("src/t2.rs", "t2gen_cxx.rs"); +} + +fn t1_volatile(i: ctest2::VolatileItemKind) -> bool { + use ctest2::VolatileItemKind::*; + match i { + StructField(ref n, ref f) if n == "V" && f == "v" => true, + Static(ref n) if n == "vol_ptr" => true, + FunctionArg(ref n, 0) if n == "T1_vol0" => true, + FunctionArg(ref n, 1) if n == "T1_vol2" => true, + FunctionRet(ref n) if n == "T1_vol1" || n == "T1_vol2" => true, + Static(ref n) if n == "T1_fn_ptr_vol" => true, + _ => false, + } +} + +fn t1_arrays(n: &str, i: usize) -> bool { + match n { + "T1r" | "T1s" | "T1t" | "T1v" if i == 0 => true, + _ => false, + } +} diff --git a/ctest/testcrate/src/bin/t1.rs b/ctest/testcrate/src/bin/t1.rs new file mode 100644 index 0000000000000..b49f8babf6b7f --- /dev/null +++ b/ctest/testcrate/src/bin/t1.rs @@ -0,0 +1,7 @@ +#![cfg(not(test))] +#![deny(warnings)] + +use libc::*; +use testcrate::t1::*; + +include!(concat!(env!("OUT_DIR"), "/t1gen.rs")); diff --git a/ctest/testcrate/src/bin/t1_cxx.rs b/ctest/testcrate/src/bin/t1_cxx.rs new file mode 100644 index 0000000000000..f98c217362b2f --- /dev/null +++ b/ctest/testcrate/src/bin/t1_cxx.rs @@ -0,0 +1,7 @@ +#![cfg(not(test))] +#![deny(warnings)] + +use libc::*; +use testcrate::t1::*; + +include!(concat!(env!("OUT_DIR"), "/t1gen_cxx.rs")); diff --git a/ctest/testcrate/src/bin/t2.rs b/ctest/testcrate/src/bin/t2.rs new file mode 100644 index 0000000000000..80a4ab563b1d6 --- /dev/null +++ b/ctest/testcrate/src/bin/t2.rs @@ -0,0 +1,6 @@ +#![cfg(not(test))] +#![deny(warnings)] + +use testcrate::t2::*; + +include!(concat!(env!("OUT_DIR"), "/t2gen.rs")); diff --git a/ctest/testcrate/src/bin/t2_cxx.rs b/ctest/testcrate/src/bin/t2_cxx.rs new file mode 100644 index 0000000000000..982652013e627 --- /dev/null +++ b/ctest/testcrate/src/bin/t2_cxx.rs @@ -0,0 +1,6 @@ +#![cfg(not(test))] +#![deny(warnings)] + +use testcrate::t2::*; + +include!(concat!(env!("OUT_DIR"), "/t2gen_cxx.rs")); diff --git a/ctest/testcrate/src/lib.rs b/ctest/testcrate/src/lib.rs new file mode 100644 index 0000000000000..7c749733dc655 --- /dev/null +++ b/ctest/testcrate/src/lib.rs @@ -0,0 +1,2 @@ +pub mod t1; +pub mod t2; diff --git a/ctest/testcrate/src/t1.c b/ctest/testcrate/src/t1.c new file mode 100644 index 0000000000000..50c7b61864799 --- /dev/null +++ b/ctest/testcrate/src/t1.c @@ -0,0 +1,75 @@ +#include +#include +#include "t1.h" + +void T1a(void) {} +void* T1b(void) { return NULL; } +void* T1c(void* a) { return NULL; } +int32_t T1d(unsigned a ) { return 0; } +void T1e(unsigned a, const struct T1Bar* b) { } +void T1f(void) {} +void T1g(int32_t* a) {} +void T1h(const int32_t* b) {} +void T1i(int32_t a[4]) {} +void T1j(const int32_t b[4]) {} +void T1o(int32_t (*a)[4]) {} +void T1p(int32_t (*const a)[4]) {} + +void T1r(Arr a) {} +void T1s(const Arr a) {} +void T1t(Arr* a) {} +void T1v(const Arr* a) {} + +unsigned T1static = 3; + +const uint8_t T1_static_u8 = 42; +uint8_t T1_static_mut_u8 = 37; + +uint8_t foo(uint8_t a, uint8_t b) { return a + b; } +void bar(uint8_t a) { return; } +void baz(void) { return; } + +uint32_t (*nested(uint8_t arg))(uint16_t) { + return NULL; +} + +uint32_t (*nested2(uint8_t(*arg0)(uint8_t), uint16_t(*arg1)(uint16_t)))(uint16_t) { + return NULL; +} + +uint8_t (*T1_static_mut_fn_ptr)(uint8_t, uint8_t) = foo; +uint8_t (*const T1_static_const_fn_ptr_unsafe)(uint8_t, uint8_t) = foo; +void (*const T1_static_const_fn_ptr_unsafe2)(uint8_t) = bar; +void (*const T1_static_const_fn_ptr_unsafe3)(void) = baz; + +const uint8_t T1_static_right = 7; +uint8_t (*T1_static_right2)(uint8_t, uint8_t) = foo; + +uint32_t (*(*T1_fn_ptr_s)(uint8_t))(uint16_t) = nested; +uint32_t (*(*T1_fn_ptr_s2)(uint8_t(*arg0)(uint8_t), uint16_t(*arg1)(uint16_t)))(uint16_t) = nested2; + +const int32_t T1_arr0[2] = {0, 0}; +const int32_t T1_arr1[2][3] = {{0, 0, 0}, {0, 0, 0}}; +const int32_t T1_arr2[1][2][3] = {{{0, 0, 0}, {0, 0, 0}}}; + +int32_t T1_arr3[2] = {0, 0}; +int32_t T1_arr4[2][3] = {{0, 0, 0}, {0, 0, 0}}; +int32_t T1_arr5[1][2][3] = {{{0, 0, 0}, {0, 0, 0}}}; + +int32_t T1_arr42[1][2][3] = {{{0, 0, 0}, {0, 0, 0}}}; +const int16_t* T1_sref = (void*)(1337); + +const int32_t* T1_mut_opt_ref = NULL; +int32_t* T1_mut_opt_mut_ref = NULL; +const int32_t* T1_const_opt_const_ref = NULL; + +void (*const T1_opt_fn1)(void) = baz; +uint32_t (*(*T1_opt_fn2)(uint8_t))(uint16_t) = nested; +uint32_t (*(*T1_opt_fn3)(uint8_t(*arg0)(uint8_t), uint16_t(*arg1)(uint16_t)))(uint16_t) = nested2; + +volatile uint8_t* vol_ptr = NULL; +void* T1_vol0(volatile void* x, void* a) { return a? a: (void*)x; } +volatile void* T1_vol1(void* x, void* b) { return b? (volatile void*)x : (volatile void*)x; } +volatile void* T1_vol2(void* c, volatile void* x) { return c? x : x; } + +uint8_t (* volatile T1_fn_ptr_vol)(uint8_t, uint8_t) = foo; diff --git a/ctest/testcrate/src/t1.cpp b/ctest/testcrate/src/t1.cpp new file mode 120000 index 0000000000000..1627f65e030cd --- /dev/null +++ b/ctest/testcrate/src/t1.cpp @@ -0,0 +1 @@ +t1.c \ No newline at end of file diff --git a/ctest/testcrate/src/t1.h b/ctest/testcrate/src/t1.h new file mode 100644 index 0000000000000..79afebddc3290 --- /dev/null +++ b/ctest/testcrate/src/t1.h @@ -0,0 +1,179 @@ +#include + +typedef int32_t T1Foo; + +#define T1N 5 +#define T1S "foo" + +struct T1Bar { + int32_t a; + uint32_t b; + T1Foo c; + uint8_t d; + int64_t e[T1N]; + int64_t f[T1N][2]; +}; + +struct T1Baz { + uint64_t a; + struct T1Bar b; +}; + +typedef union { + uint64_t a; + uint32_t b; +} T1Union; + +union T1NoTypedefUnion { + uint64_t a; + uint32_t b; +}; + +struct T1StructWithUnion { + union T1NoTypedefUnion u; +}; + +typedef double T1TypedefDouble; +typedef int* T1TypedefPtr; +typedef struct T1Bar T1TypedefStruct; + +void T1a(void); +void* T1b(void); +void* T1c(void*); +int32_t T1d(unsigned); +void T1e(unsigned, const struct T1Bar*); +void T1f(void); +void T1g(int32_t* a); +void T1h(const int32_t* b); +void T1i(int32_t a[4]); +void T1j(const int32_t b[4]); +void T1o(int32_t (*a)[4]); +void T1p(int32_t (*const a)[4]); + +typedef int32_t (Arr)[4]; +typedef int32_t Transparent; + +void T1r(Arr a); +void T1s(const Arr a); +void T1t(Arr* a); +void T1v(const Arr* a); + +#define T1C 4 + +extern uint32_t T1static; +extern const uint8_t T1_static_u8; +uint8_t T1_static_mut_u8; +uint8_t (*T1_static_mut_fn_ptr)(uint8_t, uint8_t); +extern uint8_t (*const T1_static_const_fn_ptr_unsafe)(uint8_t, uint8_t); +extern void (*const T1_static_const_fn_ptr_unsafe2)(uint8_t); +extern void (*const T1_static_const_fn_ptr_unsafe3)(void); + +extern const uint8_t T1_static_right; +uint8_t (*T1_static_right2)(uint8_t, uint8_t); + +// T1_fn_ptr_nested: function pointer to a function, taking a uint8_t, and +// returning a function pointer to a function taking a uint16_t and returning a +// uint32_t +uint32_t (*(*T1_fn_ptr_s)(uint8_t))(uint16_t); + +// T1_fn_ptr_nested: function pointer to a function, taking a function pointer +// uint8_t -> uint8_t, and returning a function pointer to a function taking a +// uint16_t and returning a uint32_t +uint32_t (*(*T1_fn_ptr_s2)(uint8_t(*)(uint8_t), uint16_t(*)(uint16_t)))(uint16_t); + +extern const int32_t T1_arr0[2]; +extern const int32_t T1_arr1[2][3]; +extern const int32_t T1_arr2[1][2][3]; + +extern int32_t T1_arr3[2]; +extern int32_t T1_arr4[2][3]; +extern int32_t T1_arr5[1][2][3]; + +extern int32_t T1_arr42[1][2][3]; + +extern const int16_t* T1_sref; + +extern const int32_t* T1_mut_opt_ref; +extern int32_t* T1_mut_opt_mut_ref; +extern const int32_t* T1_const_opt_const_ref; + +extern void (*const T1_opt_fn1)(void); +uint32_t (*(*T1_opt_fn2)(uint8_t))(uint16_t); +uint32_t (*(*T1_opt_fn3)(uint8_t(*)(uint8_t), uint16_t(*)(uint16_t)))(uint16_t); + + +struct Q { + uint8_t* q0; + uint8_t** q1; + uint8_t q2; +}; + + +struct T1_conflict_foo { + int a; +}; + +struct T1_conflict{ + int foo; +}; + +// test packed structs +// +// on msvc there is only pragma pack +// on clang and gcc there is a packed attribute + +# pragma pack(push,1) + +struct Pack { + uint8_t a; + uint16_t b; +}; + +# pragma pack(pop) + +# pragma pack(push,4) + +struct Pack4 { + uint8_t a; + uint32_t b; +}; + +# pragma pack(pop) + +// volatile pointers in struct fields: +struct V { + volatile uint8_t* v; +}; + +// volatile pointers in externs: +extern volatile uint8_t* vol_ptr; + +// volatile pointers in function arguments: +void* T1_vol0(volatile void*, void*); +volatile void* T1_vol1(void*, void*); +volatile void* T1_vol2(void*, volatile void*); + +// volatile function pointers: +uint8_t (*volatile T1_fn_ptr_vol)(uint8_t, uint8_t); + +#define LOG_MAX_LINE_LENGTH (1400) + +typedef struct { + long tv_sec; + int tv_usec; +} timeval; + +typedef struct +{ + long level; + char const *file; + long line; + char const *module; + timeval tv; + char message[LOG_MAX_LINE_LENGTH]; +} log_record_t; + +typedef struct +{ + long double inner; +} LongDoubleWrap; diff --git a/ctest/testcrate/src/t1.rs b/ctest/testcrate/src/t1.rs new file mode 100644 index 0000000000000..74896994eade6 --- /dev/null +++ b/ctest/testcrate/src/t1.rs @@ -0,0 +1,209 @@ +#![allow(dead_code)] + +use libc::*; + +pub type T1Foo = i32; +pub const T1S: &str = "foo"; + +pub const T1N: i32 = 5; + +macro_rules! i { + ($i:item) => { + $i + }; +} + +#[repr(C)] +pub struct T1Bar { + pub a: i32, + pub b: u32, + pub c: T1Foo, + pub d: u8, + pub e: [i64; T1N as usize], + pub f: [[i64; 2]; T1N as usize], +} + +#[repr(C)] +pub struct T1Baz { + pub a: u64, + pub b: T1Bar, +} + +#[repr(C)] +pub union T1Union { + pub a: u64, + pub b: u32, +} + +#[repr(C)] +pub union T1NoTypedefUnion { + pub a: u64, + pub b: u32, +} + +#[repr(C)] +pub struct T1StructWithUnion { + pub u: T1NoTypedefUnion, +} + +#[repr(transparent)] +pub struct Transparent(i32); + +pub type T1TypedefDouble = c_double; +pub type T1TypedefPtr = *mut c_int; +pub type T1TypedefStruct = T1Bar; + +i! { + pub const T1C: u32 = 4; +} + +const NOT_PRESENT: u32 = 5; + +pub type Arr = [i32; 4]; + +extern "C" { + pub fn T1a(); + pub fn T1b() -> *mut c_void; + pub fn T1c(a: *mut c_void) -> *mut c_void; + pub fn T1d(a: c_uint) -> i32; + pub fn T1e(a: c_uint, b: *const T1Bar); + + #[link_name = "T1f"] + #[allow(clippy::unused_unit)] + pub fn f() -> (); + + pub fn T1g(a: *mut [i32; 4]); + pub fn T1h(a: *const [i32; 4]) -> !; + pub fn T1i(a: *mut [i32; 4]); + pub fn T1j(a: *const [i32; 4]) -> !; + pub fn T1o(a: *mut *mut [i32; 4]); + pub fn T1p(a: *const *const [i32; 4]) -> !; + + pub fn T1r(a: *mut Arr); + pub fn T1s(a: *const Arr) -> !; + pub fn T1t(a: *mut *mut Arr); + pub fn T1v(a: *const *const Arr) -> !; + + pub static T1static: c_uint; +} + +pub fn foo() { + assert_eq!(1, 1); +} + +extern "C" { + pub static T1_static_u8: u8; + pub static mut T1_static_mut_u8: u8; + pub static mut T1_static_mut_fn_ptr: extern "C" fn(u8, u8) -> u8; + pub static T1_static_const_fn_ptr_unsafe: unsafe extern "C" fn(u8, u8) -> u8; + pub static T1_static_const_fn_ptr_unsafe2: unsafe extern "C" fn(u8) -> (); + pub static T1_static_const_fn_ptr_unsafe3: unsafe extern "C" fn() -> (); + + #[link_name = "T1_static_right"] + pub static T1_static_wrong: u8; + #[link_name = "T1_static_right2"] + pub static mut T1_static_wrong2: extern "C" fn(u8, u8) -> u8; + + pub static T1_fn_ptr_s: unsafe extern "C" fn(u8) -> extern "C" fn(u16) -> u32; + pub static T1_fn_ptr_s2: unsafe extern "C" fn( + extern "C" fn(u8) -> u8, + extern "C" fn(u16) -> u16, + ) -> extern "C" fn(u16) -> u32; + + pub static T1_arr0: [i32; 2]; + pub static T1_arr1: [[i32; 3]; 2]; + pub static T1_arr2: [[[i32; 3]; 2]; 1]; + + pub static mut T1_arr3: [i32; 2]; + pub static mut T1_arr4: [[i32; 3]; 2]; + pub static mut T1_arr5: [[[i32; 3]; 2]; 1]; + + #[link_name = "T1_arr42"] + pub static mut T1_arr6: [[[i32; 3]; 2]; 1]; + + pub static mut T1_sref: &'static i16; + + pub static mut T1_mut_opt_ref: Option<&'static i32>; + pub static mut T1_mut_opt_mut_ref: Option<&'static mut i32>; + pub static T1_const_opt_const_ref: Option<&'static i32>; + + pub static T1_opt_fn1: Option ()>; + pub static T1_opt_fn2: Option extern "C" fn(u16) -> u32>; + pub static T1_opt_fn3: Option< + unsafe extern "C" fn( + extern "C" fn(u8) -> u8, + extern "C" fn(u16) -> u16, + ) -> extern "C" fn(u16) -> u32, + >; +} + +#[repr(C)] +pub struct Q { + pub q0: *mut u8, + pub q1: *mut *mut u8, + pub q2: u8, +} + +#[repr(C)] +pub struct T1_conflict_foo { + a: i32, +} + +#[repr(C)] +pub struct T1_conflict { + pub foo: i32, +} + +#[repr(C, packed)] +pub struct Pack { + pub a: u8, + pub b: u16, +} + +#[repr(C, packed(4))] +pub struct Pack4 { + pub a: u8, + pub b: u32, +} + +#[repr(C)] +pub struct V { + pub v: *mut u8, +} + +extern "C" { + pub static mut vol_ptr: *mut u8; + pub fn T1_vol0(arg0: *mut c_void, arg1: *mut c_void) -> *mut c_void; + pub fn T1_vol1(arg0: *mut c_void, arg1: *mut c_void) -> *mut c_void; + pub fn T1_vol2(arg0: *mut c_void, arg1: *mut c_void) -> *mut c_void; + pub static T1_fn_ptr_vol: Option u8>; +} + +pub const LOG_MAX_LINE_LENGTH: usize = 1400; + +#[repr(C)] +struct timeval { + tv_sec: c_long, + tv_usec: c_int, +} + +#[repr(C)] +struct log_record_t { + level: c_long, + file: *const c_char, + line: c_long, + module: *const c_char, + tv: timeval, + message: [c_char; LOG_MAX_LINE_LENGTH], +} + +#[cfg(not(any(target_pointer_width = "16", target_pointer_width = "32")))] +#[repr(C, align(16))] +struct LongDoubleWrap { + inner: u128, +} +#[cfg(any(target_pointer_width = "16", target_pointer_width = "32"))] +#[repr(C)] +struct LongDoubleWrap { + inner: c_double, +} diff --git a/ctest/testcrate/src/t2.c b/ctest/testcrate/src/t2.c new file mode 100644 index 0000000000000..ceeddfcf509ea --- /dev/null +++ b/ctest/testcrate/src/t2.c @@ -0,0 +1,2 @@ + +void T2a() {} diff --git a/ctest/testcrate/src/t2.cpp b/ctest/testcrate/src/t2.cpp new file mode 120000 index 0000000000000..02be41dad0810 --- /dev/null +++ b/ctest/testcrate/src/t2.cpp @@ -0,0 +1 @@ +t2.c \ No newline at end of file diff --git a/ctest/testcrate/src/t2.h b/ctest/testcrate/src/t2.h new file mode 100644 index 0000000000000..9f99e11a1e79d --- /dev/null +++ b/ctest/testcrate/src/t2.h @@ -0,0 +1,23 @@ +#include + +typedef int32_t T2Foo; +typedef int8_t T2Bar; + +typedef T2Foo T2TypedefFoo; +typedef unsigned T2TypedefInt; + +struct T2Baz { + int8_t _a; + int64_t a; + uint32_t b; +}; + +typedef struct { + uint32_t a; + int64_t b; +} T2Union; + +static void T2a(void) {} + +#define T2C 4 +#define T2S "a" diff --git a/ctest/testcrate/src/t2.rs b/ctest/testcrate/src/t2.rs new file mode 100644 index 0000000000000..bafeaef7cd897 --- /dev/null +++ b/ctest/testcrate/src/t2.rs @@ -0,0 +1,36 @@ +use libc::*; + +pub type T2Foo = u32; +pub type T2Bar = u32; + +pub type T2TypedefFoo = T2Foo; +pub type T2TypedefInt = c_int; + +macro_rules! i { + ($i:item) => { + $i + }; +} + +#[repr(C)] +#[derive(Debug)] +pub struct T2Baz { + pub a: i64, + pub b: u32, +} + +#[repr(C)] +pub union T2Union { + pub a: u32, + pub b: i64, +} + +pub const T2C: i32 = 5; + +i! { + pub const T2S: &str = "b"; +} + +extern "C" { + pub fn T2a(); +} diff --git a/ctest/testcrate/tests/all.rs b/ctest/testcrate/tests/all.rs new file mode 100644 index 0000000000000..e3cdbd245ff50 --- /dev/null +++ b/ctest/testcrate/tests/all.rs @@ -0,0 +1,124 @@ +use std::collections::HashSet; +use std::env; +use std::process::{Command, ExitStatus}; + +fn cmd(name: &str) -> Command { + let mut p = env::current_exe().unwrap(); + p.pop(); + if p.file_name().unwrap().to_str() == Some("deps") { + p.pop(); + } + p.push(name); + Command::new(p) +} + +#[test] +fn t1() { + let (o, status) = output(&mut cmd("t1")); + assert!(status.success(), "{}", o); + assert!(!o.contains("bad "), "{}", o); + eprintln!("o: {}", o); +} + +#[test] +fn t1_cxx() { + let (o, status) = output(&mut cmd("t1_cxx")); + assert!(status.success(), "{}", o); + assert!(!o.contains("bad "), "{}", o); +} + +#[test] +fn t2() { + let (o, status) = output(&mut cmd("t2")); + assert!(!status.success(), "{}", o); + let errors = [ + "bad T2Foo signed", + "bad T2TypedefFoo signed", + "bad T2TypedefInt signed", + "bad T2Bar size", + "bad T2Bar align", + "bad T2Bar signed", + "bad T2Baz size", + "bad field offset a of T2Baz", + "bad field type a of T2Baz", + "bad field offset b of T2Baz", + "bad field type b of T2Baz", + "bad T2a function pointer", + "bad T2C value at byte 0", + "bad T2S string", + "bad T2Union size", + "bad field type b of T2Union", + "bad field offset b of T2Union", + ]; + let mut errors = errors.iter().cloned().collect::>(); + + let mut bad = false; + for line in o.lines().filter(|l| l.starts_with("bad ")) { + let msg = &line[..line.find(":").unwrap()]; + if !errors.remove(&msg) { + println!("unknown error: {}", msg); + bad = true; + } + } + + for error in errors { + println!("didn't find error: {}", error); + bad = true; + } + if bad { + println!("output was:\n\n{}", o); + panic!(); + } +} + +#[test] +fn t2_cxx() { + let (o, status) = output(&mut cmd("t2_cxx")); + assert!(!status.success(), "{}", o); + let errors = [ + "bad T2Foo signed", + "bad T2TypedefFoo signed", + "bad T2TypedefInt signed", + "bad T2Bar size", + "bad T2Bar align", + "bad T2Bar signed", + "bad T2Baz size", + "bad field offset a of T2Baz", + "bad field type a of T2Baz", + "bad field offset b of T2Baz", + "bad field type b of T2Baz", + "bad T2a function pointer", + "bad T2C value at byte 0", + "bad T2S string", + "bad T2Union size", + "bad field type b of T2Union", + "bad field offset b of T2Union", + ]; + let mut errors = errors.iter().cloned().collect::>(); + + let mut bad = false; + for line in o.lines().filter(|l| l.starts_with("bad ")) { + let msg = &line[..line.find(":").unwrap()]; + if !errors.remove(&msg) { + println!("unknown error: {}", msg); + bad = true; + } + } + + for error in errors { + println!("didn't find error: {}", error); + bad = true; + } + if bad { + println!("output was:\n\n{}", o); + panic!(); + } +} + +fn output(cmd: &mut Command) -> (String, ExitStatus) { + let output = cmd.output().unwrap(); + let stdout = String::from_utf8(output.stdout).unwrap(); + let stderr = String::from_utf8(output.stderr).unwrap(); + + (stdout + &stderr, output.status) +}