microsoft/qdk
Publicmirrored from https://github.com/microsoft/qdkAvailable
.github/workflows/fuzz.yml
226lines · modeblame
589390eaRobin Kuzmin3 years ago | 1 | name: fuzz |
| 2 | run-name: Fuzz | |
| 3 | env: | |
e7bd0d97Stefan J. Wernli9 months ago | 4 | OWNER_RDPATH: ./source # Rel path to the dir that contains the fuzzing infra (contains "fuzz" dir). |
| 5 | DURATION_SEC: 7200 # Fuzzing run duration in seconds. | |
| 6 | STDERR_LOG_FNAME: fuzz.stderr.log # File name to redirect the fuzzing run's stderr to. | |
| 7 | TMIN_LOG_FNAME: fuzz.tmin.log # File name to redirect the fuzzing input minimization log to. | |
| 8 | GH_ISSUE_TEMPLATE_RFPATH: | |
| 9 | .github/ISSUE_TEMPLATE/fuzz_bug_report.md | |
| 10 | # GitHub issue template rel file path. | |
| 11 | ARTIFACTS_RDPATH: fuzz/artifacts # Fuzzing artifacts rel dir path. | |
| 12 | SEEDS_RDPATH: fuzz/seed_inputs # Fuzzing seed inputs rel dir path. | |
| 13 | SEEDS_FNAME: list.txt # Fuzzing seed inputs list file name. | |
589390eaRobin Kuzmin3 years ago | 14 | on: |
e7bd0d97Stefan J. Wernli9 months ago | 15 | workflow_dispatch: # Manual runs. |
5c010e21Stefan J. Wernli3 years ago | 16 | push: |
| 17 | branches: | |
e7bd0d97Stefan J. Wernli9 months ago | 18 | - main # Development runs against main branch. |
5c010e21Stefan J. Wernli3 years ago | 19 | paths: |
e7bd0d97Stefan J. Wernli9 months ago | 20 | - "source/compiler/**" # Run if the compiler was changed. |
| 21 | - "source/fuzz/**" # Run if the fuzzing infra was changed. | |
| 22 | - ".github/ISSUE_TEMPLATE/fuzz_bug_report.md" | |
| 23 | # Run if the GitHub issue template was changed. | |
| 24 | - ".github/workflows/fuzz.yml" # Run if the workflow itself was changed. | |
| 25 | - "!source/compiler/qsc_eval/**" # Exclude the qsc_eval dir. | |
| 26 | - "!source/compiler/qsc_codegen/**" # Exclude the qsc_codegen dir. | |
589390eaRobin Kuzmin3 years ago | 27 | |
| 28 | jobs: | |
| 29 | fuzz: | |
| 30 | name: Fuzzing | |
| 31 | strategy: | |
8d7ffb4fIan Davis1 years ago | 32 | fail-fast: false |
589390eaRobin Kuzmin3 years ago | 33 | matrix: |
e7bd0d97Stefan J. Wernli9 months ago | 34 | os: |
| 35 | [ubuntu-latest] # Fuzzing is not supported on Win. The macos is temporarily removed | |
| 36 | # because of low availability. | |
8d7ffb4fIan Davis1 years ago | 37 | target_name: [qsharp, qasm] |
589390eaRobin Kuzmin3 years ago | 38 | |
8d7ffb4fIan Davis1 years ago | 39 | runs-on: ${{ matrix.os }} |
| 40 | permissions: | |
| 41 | issues: write | |
589390eaRobin Kuzmin3 years ago | 42 | steps: |
| 43 | - name: Install and Configure Tools | |
| 44 | run: | | |
| 45 | rustup install nightly # Install nightly toolchain. | |
| 46 | rustup default nightly # Make nightly toolchain default. | |
| 47 | cargo install cargo-fuzz # Install cargo-fuzz (fuzzing tool). | |
| 48 | | |
| 49 | - name: Checkout the Repo | |
9831093dSalman Chishti8 months ago | 50 | uses: actions/checkout@v6 |
589390eaRobin Kuzmin3 years ago | 51 | with: |
| 52 | submodules: "true" | |
| 53 | | |
| 54 | - name: Gather the Seed Inputs | |
8d7ffb4fIan Davis1 years ago | 55 | if: matrix.target_name == 'qsharp' |
7b3bd426Scott Carda1 years ago | 56 | working-directory: ${{ env.OWNER_RDPATH }} |
589390eaRobin Kuzmin3 years ago | 57 | run: | |
| 58 | # Clone the submodules of QDK: | |
| 59 | REPOS="Quantum Quantum-NC QuantumKatas QuantumLibraries iqsharp qdk-python qsharp-compiler qsharp-runtime" | |
| 60 | for REPO in $REPOS ; do | |
| 61 | git clone --depth 1 --single-branch --no-tags --recurse-submodules --shallow-submodules --jobs 4 \ | |
8d7ffb4fIan Davis1 years ago | 62 | https://github.com/microsoft/$REPO.git $SEEDS_RDPATH/${{ matrix.target_name }}/$REPO |
589390eaRobin Kuzmin3 years ago | 63 | done |
| 64 | | |
| 65 | # Build a comma-separated list of all the .qs files in $SEEDS_FNAME file: | |
8d7ffb4fIan Davis1 years ago | 66 | find $SEEDS_RDPATH/${{ matrix.target_name }} -name "*.qs" | tr "\n" "," > \ |
| 67 | $SEEDS_RDPATH/${{ matrix.target_name }}/$SEEDS_FNAME | |
589390eaRobin Kuzmin3 years ago | 68 | |
8446d5c5Ian Davis1 years ago | 69 | - name: Gather the Seed Inputs (qasm) |
| 70 | if: matrix.target_name == 'qasm' | |
7b3bd426Scott Carda1 years ago | 71 | working-directory: ${{ env.OWNER_RDPATH }} |
8446d5c5Ian Davis1 years ago | 72 | run: | |
| 73 | # Clone openqasm repo for samples: | |
| 74 | git clone --depth 1 --single-branch --no-tags --recurse-submodules --shallow-submodules --jobs 4 \ | |
| 75 | https://github.com/openqasm/openqasm.git $SEEDS_RDPATH/${{ matrix.target_name }}/openqasm | |
| 76 | | |
| 77 | | |
| 78 | # Build a comma-separated list of all the .qasm and .inc files in $SEEDS_FNAME file: | |
| 79 | find $SEEDS_RDPATH/${{ matrix.target_name }} -name "*.qasm" | tr "\n" "," > \ | |
| 80 | $SEEDS_RDPATH/${{ matrix.target_name }}/$SEEDS_FNAME | |
| 81 | find $SEEDS_RDPATH/${{ matrix.target_name }} -name "*.inc" | tr "\n" "," > \ | |
| 82 | $SEEDS_RDPATH/${{ matrix.target_name }}/$SEEDS_FNAME | |
| 83 | | |
589390eaRobin Kuzmin3 years ago | 84 | - name: Build and Run the Fuzz Target |
7b3bd426Scott Carda1 years ago | 85 | working-directory: ${{ env.OWNER_RDPATH }} |
589390eaRobin Kuzmin3 years ago | 86 | run: | |
7b3bd426Scott Carda1 years ago | 87 | cargo fuzz build --fuzz-dir ./fuzz --release --sanitizer=none --features do_fuzz ${{ matrix.target_name }} # Build the fuzz target. |
589390eaRobin Kuzmin3 years ago | 88 | |
| 89 | # Run fuzzing for specified number of seconds and redirect the `stderr` to a file | |
| 90 | # whose name is specified by the STDERR_LOG_FNAME env var: | |
7b3bd426Scott Carda1 years ago | 91 | RUST_BACKTRACE=1 cargo fuzz run --fuzz-dir ./fuzz --release --sanitizer=none --features do_fuzz ${{ matrix.target_name }} -- \ |
8d7ffb4fIan Davis1 years ago | 92 | -seed_inputs=@$SEEDS_RDPATH/${{ matrix.target_name }}/$SEEDS_FNAME \ |
589390eaRobin Kuzmin3 years ago | 93 | -max_total_time=$DURATION_SEC \ |
| 94 | -rss_limit_mb=4096 \ | |
f0a682a2Stefan J. Wernli3 years ago | 95 | -max_len=20000 \ |
589390eaRobin Kuzmin3 years ago | 96 | 2>$STDERR_LOG_FNAME |
| 97 | # The `-rss_limit_mb` and `-max_len` work around running out of memory. | |
| 98 | | |
| 99 | - name: "If Fuzzing Failed: Collect Failure Info" | |
| 100 | if: failure() | |
7b3bd426Scott Carda1 years ago | 101 | working-directory: ${{ env.OWNER_RDPATH }} |
589390eaRobin Kuzmin3 years ago | 102 | run: | |
| 103 | # Extract from stderr log the panic message: | |
| 104 | PANIC_MESSAGE=`cat $STDERR_LOG_FNAME | | |
| 105 | grep "panicked at" | sed "s|thread '<unnamed>' panicked at '\([^']*\).*|\1|"` | |
| 106 | # Explanation: | |
| 107 | # `cat $STDERR_LOG_FNAME |`: Display the contents of the stderr log file and pass the contents | |
| 108 | # to the next command. | |
| 109 | # `grep "panicked at" |`: Filter out (drop) all the lines except the ones containing "panicked at", | |
| 110 | # the script expects that there is only one such line, pass that line to the next command. Line example: | |
| 111 | # thread '<unnamed>' panicked at 'global item should have type', . . ./compiler/qsc_frontend/src/typeck/rules.rs:300:26 | |
| 112 | # `sed "s|thread '<unnamed>' panicked at '\([^']*\).*|\1|"`: `sed` - stream editor. | |
| 113 | # `s` after quote: search command. After `s` there are two sections, each between a pair of '|'. | |
| 114 | # First section: | |
| 115 | # In the incoming stream search for a sequence starting with "thread '<unnamed>' panicked at '" | |
| 116 | # (sequence from the beginning of the line until after the apostrophe where the panic message starts), | |
| 117 | # followed by zero or more ('*' after ']') non-apostrophe chars (`[^']`) | |
| 118 | # and memorize ( `\(`, `\)` ) that sequence of non-apostrophe chars (between apostrophes - | |
| 119 | # "global item should have type") as a memory item 1; | |
| 120 | # followed by zero or more ('*' after '.') arbitrary chars ('.') till the end of the line. | |
| 121 | # Second section (`\1`): | |
| 122 | # If the sequence specified by the first section is found, then replace that sequence (the whole line) | |
| 123 | # with the memory item 1 (`\1`), ending up in a panic message between the apostrophes. | |
| 124 | # PANIC_MESSAGE=`. . .`: The output of the command(s) between the backticks ('`') is saved in the | |
| 125 | # env var PANIC_MESSAGE. | |
| 126 | # If the failure is not panic-based then extract any ERROR message(s): | |
| 127 | if [ "$PANIC_MESSAGE" == "" ]; then | |
| 128 | PANIC_MESSAGE=`cat $STDERR_LOG_FNAME | grep "ERROR"` | |
| 129 | fi | |
| 130 | echo "PANIC_MESSAGE: '$PANIC_MESSAGE'" # Output the PANIC_MESSAGE var value to the log | |
| 131 | # (optional, for workflow failure analysis and sanity check). | |
| 132 | echo "PANIC_MESSAGE=$PANIC_MESSAGE" >> "$GITHUB_ENV" # Save the PANIC_MESSAGE var in the env, will be used in | |
| 133 | # the subsequent `run:` and `uses:` steps. | |
| 134 | | |
| 135 | # Determine the name of a file containing the input of interest (that triggers the panic/crash): | |
8d7ffb4fIan Davis1 years ago | 136 | if [ -e $ARTIFACTS_RDPATH/${{ matrix.target_name }}/crash-* ]; then # Panic and Stack Overflow Cases. |
589390eaRobin Kuzmin3 years ago | 137 | TO_MINIMIZE_FNAME=crash-*; |
8d7ffb4fIan Davis1 years ago | 138 | elif [ -e $ARTIFACTS_RDPATH/${{ matrix.target_name }}/oom-* ]; then # Out-of-Memory Case. |
589390eaRobin Kuzmin3 years ago | 139 | TO_MINIMIZE_FNAME=oom-*; |
| 140 | else | |
8d7ffb4fIan Davis1 years ago | 141 | echo -e "File to minimize not found.\nContents of artifacts dir \"$ARTIFACTS_RDPATH/${{ matrix.target_name }}/\":" |
| 142 | ls $ARTIFACTS_RDPATH/${{ matrix.target_name }}/ | |
589390eaRobin Kuzmin3 years ago | 143 | fi |
| 144 | | |
| 145 | if [ "$TO_MINIMIZE_FNAME" != "" ]; then | |
| 146 | echo "TO_MINIMIZE_FNAME: $TO_MINIMIZE_FNAME" | |
| 147 | | |
| 148 | # Minimize the input: | |
7b3bd426Scott Carda1 years ago | 149 | ( cargo fuzz --fuzz-dir ./fuzz tmin --release --sanitizer=none --features do_fuzz -r 10000 ${{ matrix.target_name }} $ARTIFACTS_RDPATH/${{ matrix.target_name }}/$TO_MINIMIZE_FNAME 2>&1 ) > \ |
589390eaRobin Kuzmin3 years ago | 150 | $TMIN_LOG_FNAME || MINIMIZATION_FAILED=1 |
| 151 | | |
| 152 | # Get the minimized input relative faile path: | |
| 153 | if [ "$MINIMIZATION_FAILED" == "1" ]; then | |
| 154 | # Minimization failed, get the latest successful minimized input relative faile path: | |
| 155 | MINIMIZED_INPUT_RFPATH=` | |
| 156 | cat $TMIN_LOG_FNAME | grep "CRASH_MIN: minimizing crash input: " | tail -n 1 | | |
8d7ffb4fIan Davis1 years ago | 157 | sed "s|^.*\($ARTIFACTS_RDPATH/${{ matrix.target_name }}/[^\']*\).*|\1|"` |
589390eaRobin Kuzmin3 years ago | 158 | else |
| 159 | # Minimization Succeeded, get the reported minimized input relative faile path:: | |
| 160 | MINIMIZED_INPUT_RFPATH=` | |
| 161 | cat $TMIN_LOG_FNAME | grep "failed to minimize beyond" | | |
8d7ffb4fIan Davis1 years ago | 162 | sed "s|.*\($ARTIFACTS_RDPATH/${{ matrix.target_name }}/[^ ]*\).*|\1|" ` |
589390eaRobin Kuzmin3 years ago | 163 | fi |
| 164 | echo "MINIMIZED_INPUT_RFPATH: $MINIMIZED_INPUT_RFPATH" | |
| 165 | echo "MINIMIZED_INPUT_RFPATH=$MINIMIZED_INPUT_RFPATH" >> "$GITHUB_ENV" | |
| 166 | | |
| 167 | # Extract the minimized input: | |
| 168 | MINIMIZED_INPUT=`cat $MINIMIZED_INPUT_RFPATH | tr "\n" "\r"` | |
| 169 | # Display the contents of the minimized input file and replace all the occurrences of '\n' with '\r' | |
| 170 | # so that the potentially multiline sequence can be "serialized" into the env var, | |
| 171 | # while preserving the information about the line breaks. | |
| 172 | else | |
| 173 | MINIMIZED_INPUT="(Input minimization failed, see the workflow logs and artifacts)" | |
| 174 | fi | |
| 175 | echo "MINIMIZED_INPUT: '$MINIMIZED_INPUT'" | |
| 176 | echo "MINIMIZED_INPUT=$MINIMIZED_INPUT" >> "$GITHUB_ENV" | |
| 177 | | |
| 178 | # Get the workflow agent system info: | |
| 179 | WF_AGENT_SYS_INFO="`uname -a`" | |
| 180 | echo "WF_AGENT_SYS_INFO: $WF_AGENT_SYS_INFO" | |
| 181 | echo "WF_AGENT_SYS_INFO=$WF_AGENT_SYS_INFO" >> "$GITHUB_ENV" | |
| 182 | echo "WF_AGENT_OS=${{ matrix.os }}" >> "$GITHUB_ENV" | |
| 183 | | |
c4d0dc6cRobin Kuzmin3 years ago | 184 | # Get the branch info: |
| 185 | BRANCH_INFO=`git branch | grep '*'` | |
| 186 | echo "BRANCH_INFO: '$BRANCH_INFO'" | |
| 187 | echo "BRANCH_INFO=$BRANCH_INFO" >> "$GITHUB_ENV" | |
| 188 | | |
589390eaRobin Kuzmin3 years ago | 189 | # Get the commit info: |
| 190 | COMMIT_INFO=`git log -1 | tr "\n" "\r"` | |
| 191 | echo "COMMIT_INFO: '$COMMIT_INFO'" | |
| 192 | echo "COMMIT_INFO=$COMMIT_INFO" >> "$GITHUB_ENV" | |
| 193 | | |
| 194 | # Get the last N bytes of the fuzzing stderr log into the env var | |
| 195 | # (N is such that the subsequent GitHub issue reporting does not overflow): | |
| 196 | STDERR_LOG=`tail -c 63488 $STDERR_LOG_FNAME | tr "\n" "\r"` | |
| 197 | echo "STDERR_LOG: '$STDERR_LOG'" | |
| 198 | echo "STDERR_LOG=$STDERR_LOG" >> "$GITHUB_ENV" | |
| 199 | | |
| 200 | - name: "If Fuzzing Failed: Upload Failure Artifacts" | |
| 201 | if: failure() | |
9831093dSalman Chishti8 months ago | 202 | uses: actions/upload-artifact@v6 |
589390eaRobin Kuzmin3 years ago | 203 | with: |
8446d5c5Ian Davis1 years ago | 204 | name: ${{ matrix.target_name }}-fuzz-failure-artifacts |
589390eaRobin Kuzmin3 years ago | 205 | path: | |
| 206 | ${{ env.OWNER_RDPATH }}/${{ env.STDERR_LOG_FNAME }} | |
| 207 | ${{ env.OWNER_RDPATH }}/${{ env.TMIN_LOG_FNAME }} | |
8d7ffb4fIan Davis1 years ago | 208 | ${{ env.OWNER_RDPATH }}/${{ env.ARTIFACTS_RDPATH }}/${{ matrix.target_name }}/* |
| 209 | ${{ env.OWNER_RDPATH }}/${{ env.SEEDS_RDPATH }}/${{ matrix.target_name }}/${{ env.SEEDS_FNAME }} | |
589390eaRobin Kuzmin3 years ago | 210 | if-no-files-found: error |
| 211 | | |
4c4e9d5fRobin Kuzmin3 years ago | 212 | - name: "If Fuzzing Failed: Report GutHub Issue" |
| 213 | if: failure() | |
| 214 | uses: JasonEtco/create-an-issue@v2 | |
| 215 | env: | |
e7bd0d97Stefan J. Wernli9 months ago | 216 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |
4c4e9d5fRobin Kuzmin3 years ago | 217 | WORKFLOW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} |
| 218 | with: | |
| 219 | filename: ${{ env.GH_ISSUE_TEMPLATE_RFPATH }} | |
| 220 | # This issue template file uses a number of env vars collected above. | |
| 221 | id: create-issue | |
| 222 | | |
| 223 | - name: "If Fuzzing Failed: Log Issue Info" | |
| 224 | if: failure() | |
| 225 | run: | | |
| 226 | echo "Created issue #${{ steps.create-issue.outputs.number }} ${{ steps.create-issue.outputs.url }}" |