diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 0000000000..a1e849c754 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,110 @@ +# Merged with devcontainer from Microsoft VSCode +# https://github.com/microsoft/vscode-dev-containers/tree/v0.212.0/containers/alpine/.devcontainer + +ARG VARIANT="3.15" +FROM alpine:${VARIANT} + +ARG USERNAME=rocket +ARG USER_UID=10000 +ARG USER_GID=$USER_UID +ARG INSTALL_ZSH="false" + +ARG ROCKET_MSRV="1.54.0" +ARG ROCKET_REPO="https://github.com/rocket-org/Rocket.git" +ARG ROCKET_VOLUME="/home/$USERNAME/Rocket" + +COPY library-scripts/*.sh library-scripts/*.env /tmp/library-scripts/ + +RUN echo "Updating packages..." \ + && apk update --no-cache \ + && echo "Executing common-alpine.sh..." \ + && ash /tmp/library-scripts/common-alpine.sh "${INSTALL_ZSH}" "${USERNAME}" "${USER_UID}" "${USER_GID}" \ + && rm -rf /tmp/library-scripts \ + && usermod -p $(echo "$RANDOM-x-$RANDOM-x-$RANDOM-x-$RANDOM-x-$RANDOM" | hexdump -e '"%02x"') "${USERNAME}" \ + && echo "" > /etc/motd \ + && echo "Installing required packages..." \ + && apk add -U --no-cache bash build-base cmake curl-dev lldb mariadb-dev \ + musl-dev ncurses openssh-server openssh-keygen \ + openssl-dev perl pkgconf-dev postgresql-dev python3 \ + sqlite-dev \ + && sed -i -e 's/^[#]Port .*$/Port 2222/' \ + -e 's/^[#]PermitRootLogin .*$/PermitRootLogin no/' \ + -e 's/^[#]PasswordAuthentication .*$/PasswordAuthentication no/' \ + /etc/ssh/sshd_config \ + && mkdir "${ROCKET_VOLUME}" \ + && chown $USER_UID:$USER_GID "${ROCKET_VOLUME}" \ + && chmod 0755 "${ROCKET_VOLUME}" \ + && umask 077 && for ciph in rsa ecdsa ed25519 ; do \ + ssh-keygen -f /etc/ssh/ssh_host_${ciph}_key -t "$ciph" -N "" || exit 1 ; \ + done \ + && exit 0 + +EXPOSE 2222 +CMD ["sudo", "/usr/sbin/sshd", "-D"] + +USER rocket +WORKDIR /home/rocket + +RUN echo "Installing rustup..." \ + && curl -sSf -o /tmp/rustup-init https://sh.rustup.rs \ + && sh /tmp/rustup-init -y -c rust-docs,rust-src,rust-analysis,clippy \ + && rm /tmp/rustup-init \ + && source $HOME/.cargo/env \ + && echo "Installing rust beta..." \ + && rustup toolchain install beta \ + && echo "Installing rust nightly..." \ + && rustup toolchain install --force nightly \ + && echo "Installing rust MSRV ${ROCKET_MSRV}..." \ + && rustup toolchain install "${ROCKET_MSRV}" \ + && echo "Checking rust installation..." \ + && rustc --version && cargo --version \ + && rustc +beta --version && cargo +beta --version \ + && rustc +nightly --version && cargo +nightly --version \ + && rustc +${ROCKET_MSRV} --version && cargo +${ROCKET_MSRV} --version \ + && rustup default ${ROCKET_MSRV} \ + && echo "This needs now some time here... please be patient..." \ + && exit 0 + +RUN echo "Cloning Rocket Github repository..." ; \ + [ -d "${ROCKET_VOLUME}/.git" ] \ + && echo "Directory \"${ROCKET_VOLUME}\" exists... skipping clone." ; \ + [ ! -d "${ROCKET_VOLUME}/.git" ] \ + && { git clone "${ROCKET_REPO}" || exit 1 ; } ; \ + exit 0 + +VOLUME /home/rocket/Rocket + +RUN echo "Installing tools with cargo..." \ + && source $HOME/.cargo/env \ + || exit 1 ; \ + echo "Cargo config crt-static = false" \ + && echo -e "\n[target.x86_64-unknown-linux-musl]\nrustflags = [\"-C\", \"target-feature=-crt-static\"]\n" >> $HOME/.cargo/config.toml \ + || exit 1 ; \ + no_sccache_cleanup=0 ; \ + [ -d "$HOME/.cache/sccache" ] \ + && no_sccache_cleanup=1 ; \ + cargo +stable install sccache \ + && echo "Config sccache..." \ + && echo -e "\n[build]\nrustc-wrapper = \"$HOME/.cargo/bin/sccache\"\n" >> $HOME/.cargo/config.toml \ + && echo "Cargo installing..." \ + && cargo +stable install cargo-audit cargo-binutils cargo-cache cargo-crev cargo-duplicates \ + cargo-expand cargo-license cargo-msrv cargo-outdated cargo-trim cargo-update \ + drill fd-find flamegraph sd \ + && echo "Cleanup..." \ + && cargo +stable cache -a \ + || exit 1 ; \ + [ "$no_sccache_cleanup" -ne 1 ] \ + && rm -r $HOME/.cache/sccache/* \ + || exit 1 ; \ + exit 0 + +VOLUME /home/rocket/.cache/sccache + +RUN ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519 \ + && cat ~/.ssh/id_ed25519.pub >> ~/.ssh/authorized_keys \ + && echo "" \ + && echo "============================================================================" \ + && echo "============== Login with user $USERNAME and this openssh key ==============" \ + && cat ~/.ssh/id_ed25519 \ + && echo "============================================================================" \ + && exit 0 diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000000..d78cf28377 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,34 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at: +// https://github.com/microsoft/vscode-dev-containers/tree/v0.209.6/containers/alpine +{ + "name": "Rocket-Alpine-3_15", + "build": { + "dockerfile": "Dockerfile", + // Update 'VARIANT' to pick an Alpine version: 3.12, 3.13, 3.14, 3.15 + "args": { "VARIANT": "3.15" } + }, + + // Set *default* container specific settings.json values on container create. + "settings": {}, + + // Add the IDs of extensions you want installed when the container is created. + // Note that some extensions may not work in Alpine Linux. See https://aka.ms/vscode-remote/linux. + "extensions": [ + "ms-vscode-remote.remote-containers", + "matklad.rust-analyzer", + "serayuzgur.crates", + "vadimcn.vscode-lldb" + ], + + // Use 'forwardPorts' to make a list of ports inside the container available locally. + "forwardPorts": [2222, 8000], + + // Use 'postCreateCommand' to run commands after the container is created. + // "postCreateCommand": "uname -a", + + // Replace when using a ptrace-based debugger like C++, Go, and Rust + // "runArgs": [ "--init", "--cap-add=SYS_PTRACE", "--security-opt", "seccomp=unconfined" ], + + // Comment out connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. + "remoteUser": "rocket" +} diff --git a/.devcontainer/library-scripts/common-alpine.sh b/.devcontainer/library-scripts/common-alpine.sh new file mode 100644 index 0000000000..07c4c63438 --- /dev/null +++ b/.devcontainer/library-scripts/common-alpine.sh @@ -0,0 +1,364 @@ +#!/bin/ash +#------------------------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information. +#------------------------------------------------------------------------------------------------------------- +# +# Docs: https://github.com/microsoft/vscode-dev-containers/blob/main/script-library/docs/common.md +# Maintainer: The VS Code and Codespaces Teams +# +# Syntax: ./common-alpine.sh [install zsh flag] [username] [user UID] [user GID] [install Oh My Zsh! flag] + +set -e + +INSTALL_ZSH=${1:-"true"} +USERNAME=${2:-"automatic"} +USER_UID=${3:-"automatic"} +USER_GID=${4:-"automatic"} +INSTALL_OH_MYS=${5:-"true"} +SCRIPT_DIR="$(cd $(dirname "$0") && pwd)" +MARKER_FILE="/usr/local/etc/vscode-dev-containers/common" + +# Switch to bash right away +if [ "${SWITCHED_TO_BASH}" != "true" ]; then + apk add bash + export SWITCHED_TO_BASH=true + exec /bin/bash "$0" "$@" + exit $? +fi + +if [ "$(id -u)" -ne 0 ]; then + echo -e 'Script must be run as root. Use sudo, su, or add "USER root" to your Dockerfile before running this script.' + exit 1 +fi + +# Ensure that login shells get the correct path if the user updated the PATH using ENV. +rm -f /etc/profile.d/00-restore-env.sh +echo "export PATH=${PATH//$(sh -lc 'echo $PATH')/\$PATH}" > /etc/profile.d/00-restore-env.sh +chmod +x /etc/profile.d/00-restore-env.sh + +# If in automatic mode, determine if a user already exists, if not use vscode +if [ "${USERNAME}" = "auto" ] || [ "${USERNAME}" = "automatic" ]; then + USERNAME="" + POSSIBLE_USERS=("vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd)") + for CURRENT_USER in ${POSSIBLE_USERS[@]}; do + if id -u ${CURRENT_USER} > /dev/null 2>&1; then + USERNAME=${CURRENT_USER} + break + fi + done + if [ "${USERNAME}" = "" ]; then + USERNAME=vscode + fi +elif [ "${USERNAME}" = "none" ]; then + USERNAME=root + USER_UID=0 + USER_GID=0 +fi + +# Load markers to see which steps have already run +if [ -f "${MARKER_FILE}" ]; then + echo "Marker file found:" + cat "${MARKER_FILE}" + source "${MARKER_FILE}" +fi + +# Install git, bash, common dependencies +if [ "${PACKAGES_ALREADY_INSTALLED}" != "true" ]; then + apk update + apk add --no-cache \ + openssh-client \ + gnupg \ + procps \ + lsof \ + htop \ + net-tools \ + psmisc \ + curl \ + wget \ + rsync \ + ca-certificates \ + unzip \ + zip \ + nano \ + vim \ + less \ + jq \ + libgcc \ + libstdc++ \ + krb5-libs \ + libintl \ + libssl1.1 \ + lttng-ust \ + tzdata \ + userspace-rcu \ + zlib \ + sudo \ + coreutils \ + sed \ + grep \ + which \ + ncdu \ + shadow \ + strace + + # Install man pages - package name varies between 3.12 and earlier versions + if apk info man > /dev/null 2>&1; then + apk add --no-cache man man-pages + else + apk add --no-cache mandoc man-pages + fi + + # Install git if not already installed (may be more recent than distro version) + if ! type git > /dev/null 2>&1; then + apk add --no-cache git + fi + + PACKAGES_ALREADY_INSTALLED="true" +fi + +# Create or update a non-root user to match UID/GID. +group_name="${USERNAME}" +if id -u ${USERNAME} > /dev/null 2>&1; then + # User exists, update if needed + if [ "${USER_GID}" != "automatic" ] && [ "$USER_GID" != "$(id -g $USERNAME)" ]; then + group_name="$(id -gn $USERNAME)" + groupmod --gid $USER_GID ${group_name} + usermod --gid $USER_GID $USERNAME + fi + if [ "${USER_UID}" != "automatic" ] && [ "$USER_UID" != "$(id -u $USERNAME)" ]; then + usermod --uid $USER_UID $USERNAME + fi +else + # Create user + if [ "${USER_GID}" = "automatic" ]; then + groupadd $USERNAME + else + groupadd --gid $USER_GID $USERNAME + fi + if [ "${USER_UID}" = "automatic" ]; then + useradd -s /bin/bash --gid $USERNAME -m $USERNAME + else + useradd -s /bin/bash -K MAIL_DIR=/dev/null --uid $USER_UID --gid $USERNAME -m $USERNAME + fi +fi + +# Add add sudo support for non-root user +if [ "${USERNAME}" != "root" ] && [ "${EXISTING_NON_ROOT_USER}" != "${USERNAME}" ]; then + echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME + chmod 0440 /etc/sudoers.d/$USERNAME + EXISTING_NON_ROOT_USER="${USERNAME}" +fi + +# ** Shell customization section ** +if [ "${USERNAME}" = "root" ]; then + user_rc_path="/root" +else + user_rc_path="/home/${USERNAME}" +fi + +# .bashrc/.zshrc snippet +rc_snippet="$(cat << 'EOF' + +if [ -z "${USER}" ]; then export USER=$(whoami); fi +if [[ "${PATH}" != *"$HOME/.local/bin"* ]]; then export PATH="${PATH}:$HOME/.local/bin"; fi + +# Display optional first run image specific notice if configured and terminal is interactive +if [ -t 1 ] && [[ "${TERM_PROGRAM}" = "vscode" || "${TERM_PROGRAM}" = "codespaces" ]] && [ ! -f "$HOME/.config/vscode-dev-containers/first-run-notice-already-displayed" ]; then + if [ -f "/usr/local/etc/vscode-dev-containers/first-run-notice.txt" ]; then + cat "/usr/local/etc/vscode-dev-containers/first-run-notice.txt" + elif [ -f "/workspaces/.codespaces/shared/first-run-notice.txt" ]; then + cat "/workspaces/.codespaces/shared/first-run-notice.txt" + fi + mkdir -p "$HOME/.config/vscode-dev-containers" + # Mark first run notice as displayed after 10s to avoid problems with fast terminal refreshes hiding it + ((sleep 10s; touch "$HOME/.config/vscode-dev-containers/first-run-notice-already-displayed") &) +fi + +# Set the default git editor if not already set +if [ -z "$(git config --get core.editor)" ] && [ -z "${GIT_EDITOR}" ]; then + if [ "${TERM_PROGRAM}" = "vscode" ]; then + if [[ -n $(command -v code-insiders) && -z $(command -v code) ]]; then + export GIT_EDITOR="code-insiders --wait" + else + export GIT_EDITOR="code --wait" + fi + fi +fi + +EOF +)" + +# code shim, it fallbacks to code-insiders if code is not available +cat << 'EOF' > /usr/local/bin/code +#!/bin/sh + +get_in_path_except_current() { + which -a "$1" | grep -A1 "$0" | grep -v "$0" +} + +code="$(get_in_path_except_current code)" + +if [ -n "$code" ]; then + exec "$code" "$@" +elif [ "$(command -v code-insiders)" ]; then + exec code-insiders "$@" +else + echo "code or code-insiders is not installed" >&2 + exit 127 +fi +EOF +chmod +x /usr/local/bin/code + +# Codespaces bash and OMZ themes - partly inspired by https://github.com/ohmyzsh/ohmyzsh/blob/master/themes/robbyrussell.zsh-theme +codespaces_bash="$(cat \ +<<'EOF' + +# Codespaces bash prompt theme +__bash_prompt() { + local userpart='`export XIT=$? \ + && [ ! -z "${GITHUB_USER}" ] && echo -n "\[\033[0;32m\]@${GITHUB_USER} " || echo -n "\[\033[0;32m\]\u " \ + && [ "$XIT" -ne "0" ] && echo -n "\[\033[1;31m\]➜" || echo -n "\[\033[0m\]➜"`' + local gitbranch='`\ + if [ "$(git config --get codespaces-theme.hide-status 2>/dev/null)" != 1 ]; then \ + export BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null || git rev-parse --short HEAD 2>/dev/null); \ + if [ "${BRANCH}" != "" ]; then \ + echo -n "\[\033[0;36m\](\[\033[1;31m\]${BRANCH}" \ + && if git ls-files --error-unmatch -m --directory --no-empty-directory -o --exclude-standard ":/*" > /dev/null 2>&1; then \ + echo -n " \[\033[1;33m\]✗"; \ + fi \ + && echo -n "\[\033[0;36m\]) "; \ + fi; \ + fi`' + local lightblue='\[\033[1;34m\]' + local removecolor='\[\033[0m\]' + PS1="${userpart} ${lightblue}\w ${gitbranch}${removecolor}\$ " + unset -f __bash_prompt +} +__bash_prompt + +EOF +)" + +codespaces_zsh="$(cat \ +<<'EOF' +# Codespaces zsh prompt theme +__zsh_prompt() { + local prompt_username + if [ ! -z "${GITHUB_USER}" ]; then + prompt_username="@${GITHUB_USER}" + else + prompt_username="%n" + fi + PROMPT="%{$fg[green]%}${prompt_username} %(?:%{$reset_color%}➜ :%{$fg_bold[red]%}➜ )" # User/exit code arrow + PROMPT+='%{$fg_bold[blue]%}%(5~|%-1~/…/%3~|%4~)%{$reset_color%} ' # cwd + PROMPT+='$([ "$(git config --get codespaces-theme.hide-status 2>/dev/null)" != 1 ] && git_prompt_info)' # Git status + PROMPT+='%{$fg[white]%}$ %{$reset_color%}' + unset -f __zsh_prompt +} +ZSH_THEME_GIT_PROMPT_PREFIX="%{$fg_bold[cyan]%}(%{$fg_bold[red]%}" +ZSH_THEME_GIT_PROMPT_SUFFIX="%{$reset_color%} " +ZSH_THEME_GIT_PROMPT_DIRTY=" %{$fg_bold[yellow]%}✗%{$fg_bold[cyan]%})" +ZSH_THEME_GIT_PROMPT_CLEAN="%{$fg_bold[cyan]%})" +__zsh_prompt + +EOF +)" + +# Add RC snippet and custom bash prompt +if [ "${RC_SNIPPET_ALREADY_ADDED}" != "true" ]; then + echo -e "${rc_snippet}\n${codespaces_bash}" >> "${user_rc_path}/.bashrc" + if [ "${USERNAME}" != "root" ]; then + echo -e "${rc_snippet}\n${codespaces_bash}" >> "/root/.bashrc" + fi + chown ${USERNAME}:${group_name} "${user_rc_path}/.bashrc" + RC_SNIPPET_ALREADY_ADDED="true" +fi + +# Optionally install and configure zsh and Oh My Zsh! +if [ "${INSTALL_ZSH}" = "true" ]; then + if ! type zsh > /dev/null 2>&1; then + apk add zsh + fi + if [ "${ZSH_ALREADY_INSTALLED}" != "true" ]; then + echo "${rc_snippet}" >> /etc/zsh/zshrc + ZSH_ALREADY_INSTALLED="true" + fi + + # Adapted, simplified inline Oh My Zsh! install steps that adds, defaults to a codespaces theme. + # See https://github.com/ohmyzsh/ohmyzsh/blob/master/tools/install.sh for official script. + oh_my_install_dir="${user_rc_path}/.oh-my-zsh" + if [ ! -d "${oh_my_install_dir}" ] && [ "${INSTALL_OH_MYS}" = "true" ]; then + template_path="${oh_my_install_dir}/templates/zshrc.zsh-template" + user_rc_file="${user_rc_path}/.zshrc" + umask g-w,o-w + mkdir -p ${oh_my_install_dir} + git clone --depth=1 \ + -c core.eol=lf \ + -c core.autocrlf=false \ + -c fsck.zeroPaddedFilemode=ignore \ + -c fetch.fsck.zeroPaddedFilemode=ignore \ + -c receive.fsck.zeroPaddedFilemode=ignore \ + "https://github.com/ohmyzsh/ohmyzsh" "${oh_my_install_dir}" 2>&1 + echo -e "$(cat "${template_path}")\nDISABLE_AUTO_UPDATE=true\nDISABLE_UPDATE_PROMPT=true" > ${user_rc_file} + sed -i -e 's/ZSH_THEME=.*/ZSH_THEME="codespaces"/g' ${user_rc_file} + mkdir -p ${oh_my_install_dir}/custom/themes + echo "${codespaces_zsh}" > "${oh_my_install_dir}/custom/themes/codespaces.zsh-theme" + # Shrink git while still enabling updates + cd "${oh_my_install_dir}" + git repack -a -d -f --depth=1 --window=1 + # Copy to non-root user if one is specified + if [ "${USERNAME}" != "root" ]; then + cp -rf "${user_rc_file}" "${oh_my_install_dir}" /root + chown -R ${USERNAME}:${group_name} "${user_rc_path}" + fi + fi +fi + +# Persist image metadata info, script if meta.env found in same directory +meta_info_script="$(cat << 'EOF' +#!/bin/sh +. /usr/local/etc/vscode-dev-containers/meta.env + +# Minimal output +if [ "$1" = "version" ] || [ "$1" = "image-version" ]; then + echo "${VERSION}" + exit 0 +elif [ "$1" = "release" ]; then + echo "${GIT_REPOSITORY_RELEASE}" + exit 0 +elif [ "$1" = "content" ] || [ "$1" = "content-url" ] || [ "$1" = "contents" ] || [ "$1" = "contents-url" ]; then + echo "${CONTENTS_URL}" + exit 0 +fi + +#Full output +echo +echo "Development container image information" +echo +if [ ! -z "${VERSION}" ]; then echo "- Image version: ${VERSION}"; fi +if [ ! -z "${DEFINITION_ID}" ]; then echo "- Definition ID: ${DEFINITION_ID}"; fi +if [ ! -z "${VARIANT}" ]; then echo "- Variant: ${VARIANT}"; fi +if [ ! -z "${GIT_REPOSITORY}" ]; then echo "- Source code repository: ${GIT_REPOSITORY}"; fi +if [ ! -z "${GIT_REPOSITORY_RELEASE}" ]; then echo "- Source code release/branch: ${GIT_REPOSITORY_RELEASE}"; fi +if [ ! -z "${BUILD_TIMESTAMP}" ]; then echo "- Timestamp: ${BUILD_TIMESTAMP}"; fi +if [ ! -z "${CONTENTS_URL}" ]; then echo && echo "More info: ${CONTENTS_URL}"; fi +echo +EOF +)" +if [ -f "${SCRIPT_DIR}/meta.env" ]; then + mkdir -p /usr/local/etc/vscode-dev-containers/ + cp -f "${SCRIPT_DIR}/meta.env" /usr/local/etc/vscode-dev-containers/meta.env + echo "${meta_info_script}" > /usr/local/bin/devcontainer-info + chmod +x /usr/local/bin/devcontainer-info +fi + +# Write marker file +mkdir -p "$(dirname "${MARKER_FILE}")" +echo -e "\ + PACKAGES_ALREADY_INSTALLED=${PACKAGES_ALREADY_INSTALLED}\n\ + EXISTING_NON_ROOT_USER=${EXISTING_NON_ROOT_USER}\n\ + RC_SNIPPET_ALREADY_ADDED=${RC_SNIPPET_ALREADY_ADDED}\n\ + ZSH_ALREADY_INSTALLED=${ZSH_ALREADY_INSTALLED}" > "${MARKER_FILE}" + +echo "Done!" diff --git a/.devcontainer/library-scripts/meta.env b/.devcontainer/library-scripts/meta.env new file mode 100644 index 0000000000..9e5433682e --- /dev/null +++ b/.devcontainer/library-scripts/meta.env @@ -0,0 +1 @@ +VERSION='dev' diff --git a/.github/ISSUE_TEMPLATE/suggestion.md b/.github/ISSUE_TEMPLATE/suggestion.md index 46e0170608..8a07c2ae10 100644 --- a/.github/ISSUE_TEMPLATE/suggestion.md +++ b/.github/ISSUE_TEMPLATE/suggestion.md @@ -15,7 +15,7 @@ A clear and concise description of existing functionality and why it is insuffic **Suggested Changes** -If you have a conrete idea for an improvement, propose it with a clear and concise description. +If you have a concrete idea for an improvement, propose it with a clear and concise description. **Alternatives Considered** diff --git a/Cargo.toml b/Cargo.toml index 8ec081ef00..5ef9c77919 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,11 @@ members = [ "contrib/db_pools/codegen/", "contrib/db_pools/lib/", "contrib/sync_db_pools/codegen/", + "contrib/sync_db_pools/codegen-diesel-tests", "contrib/sync_db_pools/lib/", "contrib/dyn_templates/", "site/tests", ] + +[patch.crates-io] +chrono = { git = "https://github.com/timvisee/chrono.git", rev = "5292db8" } diff --git a/benchmarks/Cargo.toml b/benchmarks/Cargo.toml index 68bc82d255..0605b73a84 100644 --- a/benchmarks/Cargo.toml +++ b/benchmarks/Cargo.toml @@ -13,4 +13,4 @@ harness = false [dev-dependencies] rocket = { path = "../core/lib/" } -criterion = "0.3" +criterion = { version = "0.3", features = ["html_reports"] } diff --git a/contrib/db_pools/codegen/Cargo.toml b/contrib/db_pools/codegen/Cargo.toml index 770388c72f..4d07221697 100644 --- a/contrib/db_pools/codegen/Cargo.toml +++ b/contrib/db_pools/codegen/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rocket_db_pools_codegen" -version = "0.1.0-rc" +version = "0.1.0-rc.2" authors = ["Sergio Benitez ", "Jeb Rosen "] description = "Procedural macros for rocket_db_pools." repository = "https://github.com/SergioBenitez/Rocket/contrib/db_pools" @@ -14,7 +14,7 @@ proc-macro = true [dependencies] devise = "0.3" -quote = "1" +quote = "1.0" [dev-dependencies] rocket = { path = "../../../core/lib", default-features = false } diff --git a/contrib/sync_db_pools/codegen/tests/ui-fail-nightly/database-syntax.rs b/contrib/db_pools/codegen/tests/ui-fail-msrv/database-syntax.rs similarity index 100% rename from contrib/sync_db_pools/codegen/tests/ui-fail-nightly/database-syntax.rs rename to contrib/db_pools/codegen/tests/ui-fail-msrv/database-syntax.rs diff --git a/contrib/db_pools/codegen/tests/ui-fail-msrv/database-syntax.stderr b/contrib/db_pools/codegen/tests/ui-fail-msrv/database-syntax.stderr new file mode 100644 index 0000000000..2bd4d2ebf2 --- /dev/null +++ b/contrib/db_pools/codegen/tests/ui-fail-msrv/database-syntax.stderr @@ -0,0 +1,111 @@ +error: invalid value: expected string literal + --> tests/ui-fail-msrv/database-syntax.rs:4:12 + | +4 | #[database(123)] + | ^^^ + +error: [note] error occurred while deriving `Database` + --> tests/ui-fail-msrv/database-syntax.rs:3:10 + | +3 | #[derive(Database)] + | ^^^^^^^^ + | + = note: this error originates in the derive macro `Database` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: expected key/value `key = value` + --> tests/ui-fail-msrv/database-syntax.rs:8:25 + | +8 | #[database("some-name", "another")] + | ^^^^^^^^^ + +error: [note] error occurred while deriving `Database` + --> tests/ui-fail-msrv/database-syntax.rs:7:10 + | +7 | #[derive(Database)] + | ^^^^^^^^ + | + = note: this error originates in the derive macro `Database` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: unexpected attribute parameter: `name` + --> tests/ui-fail-msrv/database-syntax.rs:12:25 + | +12 | #[database("some-name", name = "another")] + | ^^^^ + +error: [note] error occurred while deriving `Database` + --> tests/ui-fail-msrv/database-syntax.rs:11:10 + | +11 | #[derive(Database)] + | ^^^^^^^^ + | + = note: this error originates in the derive macro `Database` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: enums are not supported + --> tests/ui-fail-msrv/database-syntax.rs:16:1 + | +16 | #[database("foo")] + | ^ + +error: [note] error occurred while deriving `Database` + --> tests/ui-fail-msrv/database-syntax.rs:15:10 + | +15 | #[derive(Database)] + | ^^^^^^^^ + | + = note: this error originates in the derive macro `Database` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: missing `#[database("name")]` attribute + --> tests/ui-fail-msrv/database-syntax.rs:20:1 + | +20 | struct E(deadpool_postgres::Pool); + | ^^^^^^ + +error: [note] error occurred while deriving `Database` + --> tests/ui-fail-msrv/database-syntax.rs:19:10 + | +19 | #[derive(Database)] + | ^^^^^^^^ + | + = note: this error originates in the derive macro `Database` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: struct must have exactly one unnamed field + --> tests/ui-fail-msrv/database-syntax.rs:23:1 + | +23 | #[database("foo")] + | ^ + +error: [note] error occurred while deriving `Database` + --> tests/ui-fail-msrv/database-syntax.rs:22:10 + | +22 | #[derive(Database)] + | ^^^^^^^^ + | + = note: this error originates in the derive macro `Database` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: struct must have exactly one unnamed field + --> tests/ui-fail-msrv/database-syntax.rs:27:1 + | +27 | #[database("foo")] + | ^ + +error: [note] error occurred while deriving `Database` + --> tests/ui-fail-msrv/database-syntax.rs:26:10 + | +26 | #[derive(Database)] + | ^^^^^^^^ + | + = note: this error originates in the derive macro `Database` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: named structs are not supported + --> tests/ui-fail-msrv/database-syntax.rs:31:1 + | +31 | #[database("foo")] + | ^ + +error: [note] error occurred while deriving `Database` + --> tests/ui-fail-msrv/database-syntax.rs:30:10 + | +30 | #[derive(Database)] + | ^^^^^^^^ + | + = note: this error originates in the derive macro `Database` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/contrib/sync_db_pools/codegen/tests/ui-fail-nightly/database-types.rs b/contrib/db_pools/codegen/tests/ui-fail-msrv/database-types.rs similarity index 100% rename from contrib/sync_db_pools/codegen/tests/ui-fail-nightly/database-types.rs rename to contrib/db_pools/codegen/tests/ui-fail-msrv/database-types.rs diff --git a/contrib/db_pools/codegen/tests/ui-fail-msrv/database-types.stderr b/contrib/db_pools/codegen/tests/ui-fail-msrv/database-types.stderr new file mode 100644 index 0000000000..5c560fa5fd --- /dev/null +++ b/contrib/db_pools/codegen/tests/ui-fail-msrv/database-types.stderr @@ -0,0 +1,21 @@ +error[E0277]: the trait bound `Unknown: Pool` is not satisfied + --> tests/ui-fail-msrv/database-types.rs:7:10 + | +7 | struct A(Unknown); + | ^^^^^^^ the trait `Pool` is not implemented for `Unknown` + | + ::: $WORKSPACE/contrib/db_pools/lib/src/database.rs + | + | type Pool: Pool; + | ---- required by this bound in `rocket_db_pools::Database::Pool` + +error[E0277]: the trait bound `Vec: Pool` is not satisfied + --> tests/ui-fail-msrv/database-types.rs:11:10 + | +11 | struct B(Vec); + | ^^^ the trait `Pool` is not implemented for `Vec` + | + ::: $WORKSPACE/contrib/db_pools/lib/src/database.rs + | + | type Pool: Pool; + | ---- required by this bound in `rocket_db_pools::Database::Pool` diff --git a/contrib/db_pools/codegen/tests/ui-fail-nightly/database-syntax.stderr b/contrib/db_pools/codegen/tests/ui-fail-nightly/database-syntax.stderr index 96b79e34c1..052998eab0 100644 --- a/contrib/db_pools/codegen/tests/ui-fail-nightly/database-syntax.stderr +++ b/contrib/db_pools/codegen/tests/ui-fail-nightly/database-syntax.stderr @@ -1,99 +1,99 @@ error: invalid value: expected string literal - --> $DIR/database-syntax.rs:4:12 + --> tests/ui-fail-nightly/database-syntax.rs:4:12 | 4 | #[database(123)] | ^^^ | note: error occurred while deriving `Database` - --> $DIR/database-syntax.rs:3:10 + --> tests/ui-fail-nightly/database-syntax.rs:3:10 | 3 | #[derive(Database)] | ^^^^^^^^ = note: this error originates in the derive macro `Database` (in Nightly builds, run with -Z macro-backtrace for more info) error: expected key/value `key = value` - --> $DIR/database-syntax.rs:8:25 + --> tests/ui-fail-nightly/database-syntax.rs:8:25 | 8 | #[database("some-name", "another")] | ^^^^^^^^^ | note: error occurred while deriving `Database` - --> $DIR/database-syntax.rs:7:10 + --> tests/ui-fail-nightly/database-syntax.rs:7:10 | 7 | #[derive(Database)] | ^^^^^^^^ = note: this error originates in the derive macro `Database` (in Nightly builds, run with -Z macro-backtrace for more info) error: unexpected attribute parameter: `name` - --> $DIR/database-syntax.rs:12:25 + --> tests/ui-fail-nightly/database-syntax.rs:12:25 | 12 | #[database("some-name", name = "another")] | ^^^^^^^^^^^^^^^^ | note: error occurred while deriving `Database` - --> $DIR/database-syntax.rs:11:10 + --> tests/ui-fail-nightly/database-syntax.rs:11:10 | 11 | #[derive(Database)] | ^^^^^^^^ = note: this error originates in the derive macro `Database` (in Nightly builds, run with -Z macro-backtrace for more info) error: enums are not supported - --> $DIR/database-syntax.rs:16:1 + --> tests/ui-fail-nightly/database-syntax.rs:16:1 | 16 | / #[database("foo")] 17 | | enum D { } | |___________^ | note: error occurred while deriving `Database` - --> $DIR/database-syntax.rs:15:10 + --> tests/ui-fail-nightly/database-syntax.rs:15:10 | 15 | #[derive(Database)] | ^^^^^^^^ = note: this error originates in the derive macro `Database` (in Nightly builds, run with -Z macro-backtrace for more info) error: missing `#[database("name")]` attribute - --> $DIR/database-syntax.rs:20:1 + --> tests/ui-fail-nightly/database-syntax.rs:20:1 | 20 | struct E(deadpool_postgres::Pool); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | note: error occurred while deriving `Database` - --> $DIR/database-syntax.rs:19:10 + --> tests/ui-fail-nightly/database-syntax.rs:19:10 | 19 | #[derive(Database)] | ^^^^^^^^ = note: this error originates in the derive macro `Database` (in Nightly builds, run with -Z macro-backtrace for more info) error: struct must have exactly one unnamed field - --> $DIR/database-syntax.rs:23:1 + --> tests/ui-fail-nightly/database-syntax.rs:23:1 | 23 | / #[database("foo")] 24 | | struct F; | |_________^ | note: error occurred while deriving `Database` - --> $DIR/database-syntax.rs:22:10 + --> tests/ui-fail-nightly/database-syntax.rs:22:10 | 22 | #[derive(Database)] | ^^^^^^^^ = note: this error originates in the derive macro `Database` (in Nightly builds, run with -Z macro-backtrace for more info) error: struct must have exactly one unnamed field - --> $DIR/database-syntax.rs:27:1 + --> tests/ui-fail-nightly/database-syntax.rs:27:1 | 27 | / #[database("foo")] 28 | | struct G(deadpool_postgres::Pool, deadpool_postgres::Pool); | |___________________________________________________________^ | note: error occurred while deriving `Database` - --> $DIR/database-syntax.rs:26:10 + --> tests/ui-fail-nightly/database-syntax.rs:26:10 | 26 | #[derive(Database)] | ^^^^^^^^ = note: this error originates in the derive macro `Database` (in Nightly builds, run with -Z macro-backtrace for more info) error: named structs are not supported - --> $DIR/database-syntax.rs:31:1 + --> tests/ui-fail-nightly/database-syntax.rs:31:1 | 31 | / #[database("foo")] 32 | | struct H { @@ -102,7 +102,7 @@ error: named structs are not supported | |_^ | note: error occurred while deriving `Database` - --> $DIR/database-syntax.rs:30:10 + --> tests/ui-fail-nightly/database-syntax.rs:30:10 | 30 | #[derive(Database)] | ^^^^^^^^ diff --git a/contrib/db_pools/codegen/tests/ui-fail-nightly/database-types.stderr b/contrib/db_pools/codegen/tests/ui-fail-nightly/database-types.stderr index 3574666bd6..2911ff2582 100644 --- a/contrib/db_pools/codegen/tests/ui-fail-nightly/database-types.stderr +++ b/contrib/db_pools/codegen/tests/ui-fail-nightly/database-types.stderr @@ -1,23 +1,23 @@ error[E0277]: the trait bound `Unknown: Pool` is not satisfied - --> $DIR/database-types.rs:7:10 + --> tests/ui-fail-nightly/database-types.rs:7:10 | 7 | struct A(Unknown); | ^^^^^^^ the trait `Pool` is not implemented for `Unknown` | note: required by a bound in `rocket_db_pools::Database::Pool` - --> $DIR/database.rs:41:16 + --> $WORKSPACE/contrib/db_pools/lib/src/database.rs | -41 | type Pool: Pool; + | type Pool: Pool; | ^^^^ required by this bound in `rocket_db_pools::Database::Pool` error[E0277]: the trait bound `Vec: Pool` is not satisfied - --> $DIR/database-types.rs:11:10 + --> tests/ui-fail-nightly/database-types.rs:11:10 | 11 | struct B(Vec); | ^^^^^^^^ the trait `Pool` is not implemented for `Vec` | note: required by a bound in `rocket_db_pools::Database::Pool` - --> $DIR/database.rs:41:16 + --> $WORKSPACE/contrib/db_pools/lib/src/database.rs | -41 | type Pool: Pool; + | type Pool: Pool; | ^^^^ required by this bound in `rocket_db_pools::Database::Pool` diff --git a/contrib/db_pools/codegen/tests/ui-fail-stable/database-syntax.stderr b/contrib/db_pools/codegen/tests/ui-fail-stable/database-syntax.stderr index 8f3a58160e..3a342acab4 100644 --- a/contrib/db_pools/codegen/tests/ui-fail-stable/database-syntax.stderr +++ b/contrib/db_pools/codegen/tests/ui-fail-stable/database-syntax.stderr @@ -1,11 +1,11 @@ error: invalid value: expected string literal - --> $DIR/database-syntax.rs:4:12 + --> tests/ui-fail-stable/database-syntax.rs:4:12 | 4 | #[database(123)] | ^^^ error: [note] error occurred while deriving `Database` - --> $DIR/database-syntax.rs:3:10 + --> tests/ui-fail-stable/database-syntax.rs:3:10 | 3 | #[derive(Database)] | ^^^^^^^^ @@ -13,13 +13,13 @@ error: [note] error occurred while deriving `Database` = note: this error originates in the derive macro `Database` (in Nightly builds, run with -Z macro-backtrace for more info) error: expected key/value `key = value` - --> $DIR/database-syntax.rs:8:25 + --> tests/ui-fail-stable/database-syntax.rs:8:25 | 8 | #[database("some-name", "another")] | ^^^^^^^^^ error: [note] error occurred while deriving `Database` - --> $DIR/database-syntax.rs:7:10 + --> tests/ui-fail-stable/database-syntax.rs:7:10 | 7 | #[derive(Database)] | ^^^^^^^^ @@ -27,13 +27,13 @@ error: [note] error occurred while deriving `Database` = note: this error originates in the derive macro `Database` (in Nightly builds, run with -Z macro-backtrace for more info) error: unexpected attribute parameter: `name` - --> $DIR/database-syntax.rs:12:25 + --> tests/ui-fail-stable/database-syntax.rs:12:25 | 12 | #[database("some-name", name = "another")] | ^^^^ error: [note] error occurred while deriving `Database` - --> $DIR/database-syntax.rs:11:10 + --> tests/ui-fail-stable/database-syntax.rs:11:10 | 11 | #[derive(Database)] | ^^^^^^^^ @@ -41,13 +41,13 @@ error: [note] error occurred while deriving `Database` = note: this error originates in the derive macro `Database` (in Nightly builds, run with -Z macro-backtrace for more info) error: enums are not supported - --> $DIR/database-syntax.rs:16:1 + --> tests/ui-fail-stable/database-syntax.rs:16:1 | 16 | #[database("foo")] | ^ error: [note] error occurred while deriving `Database` - --> $DIR/database-syntax.rs:15:10 + --> tests/ui-fail-stable/database-syntax.rs:15:10 | 15 | #[derive(Database)] | ^^^^^^^^ @@ -55,13 +55,13 @@ error: [note] error occurred while deriving `Database` = note: this error originates in the derive macro `Database` (in Nightly builds, run with -Z macro-backtrace for more info) error: missing `#[database("name")]` attribute - --> $DIR/database-syntax.rs:20:1 + --> tests/ui-fail-stable/database-syntax.rs:20:1 | 20 | struct E(deadpool_postgres::Pool); | ^^^^^^ error: [note] error occurred while deriving `Database` - --> $DIR/database-syntax.rs:19:10 + --> tests/ui-fail-stable/database-syntax.rs:19:10 | 19 | #[derive(Database)] | ^^^^^^^^ @@ -69,13 +69,13 @@ error: [note] error occurred while deriving `Database` = note: this error originates in the derive macro `Database` (in Nightly builds, run with -Z macro-backtrace for more info) error: struct must have exactly one unnamed field - --> $DIR/database-syntax.rs:23:1 + --> tests/ui-fail-stable/database-syntax.rs:23:1 | 23 | #[database("foo")] | ^ error: [note] error occurred while deriving `Database` - --> $DIR/database-syntax.rs:22:10 + --> tests/ui-fail-stable/database-syntax.rs:22:10 | 22 | #[derive(Database)] | ^^^^^^^^ @@ -83,13 +83,13 @@ error: [note] error occurred while deriving `Database` = note: this error originates in the derive macro `Database` (in Nightly builds, run with -Z macro-backtrace for more info) error: struct must have exactly one unnamed field - --> $DIR/database-syntax.rs:27:1 + --> tests/ui-fail-stable/database-syntax.rs:27:1 | 27 | #[database("foo")] | ^ error: [note] error occurred while deriving `Database` - --> $DIR/database-syntax.rs:26:10 + --> tests/ui-fail-stable/database-syntax.rs:26:10 | 26 | #[derive(Database)] | ^^^^^^^^ @@ -97,13 +97,13 @@ error: [note] error occurred while deriving `Database` = note: this error originates in the derive macro `Database` (in Nightly builds, run with -Z macro-backtrace for more info) error: named structs are not supported - --> $DIR/database-syntax.rs:31:1 + --> tests/ui-fail-stable/database-syntax.rs:31:1 | 31 | #[database("foo")] | ^ error: [note] error occurred while deriving `Database` - --> $DIR/database-syntax.rs:30:10 + --> tests/ui-fail-stable/database-syntax.rs:30:10 | 30 | #[derive(Database)] | ^^^^^^^^ diff --git a/contrib/db_pools/codegen/tests/ui-fail-stable/database-types.stderr b/contrib/db_pools/codegen/tests/ui-fail-stable/database-types.stderr index 30b52af17d..4e1bec5e45 100644 --- a/contrib/db_pools/codegen/tests/ui-fail-stable/database-types.stderr +++ b/contrib/db_pools/codegen/tests/ui-fail-stable/database-types.stderr @@ -1,21 +1,23 @@ error[E0277]: the trait bound `Unknown: Pool` is not satisfied - --> $DIR/database-types.rs:7:10 + --> tests/ui-fail-stable/database-types.rs:7:10 | 7 | struct A(Unknown); | ^^^^^^^ the trait `Pool` is not implemented for `Unknown` | - ::: $WORKSPACE/contrib/db_pools/lib/src/database.rs +note: required by a bound in `rocket_db_pools::Database::Pool` + --> $WORKSPACE/contrib/db_pools/lib/src/database.rs | | type Pool: Pool; - | ---- required by this bound in `rocket_db_pools::Database::Pool` + | ^^^^ required by this bound in `rocket_db_pools::Database::Pool` error[E0277]: the trait bound `Vec: Pool` is not satisfied - --> $DIR/database-types.rs:11:10 + --> tests/ui-fail-stable/database-types.rs:11:10 | 11 | struct B(Vec); | ^^^ the trait `Pool` is not implemented for `Vec` | - ::: $WORKSPACE/contrib/db_pools/lib/src/database.rs +note: required by a bound in `rocket_db_pools::Database::Pool` + --> $WORKSPACE/contrib/db_pools/lib/src/database.rs | | type Pool: Pool; - | ---- required by this bound in `rocket_db_pools::Database::Pool` + | ^^^^ required by this bound in `rocket_db_pools::Database::Pool` diff --git a/contrib/db_pools/codegen/tests/ui-fail.rs b/contrib/db_pools/codegen/tests/ui-fail.rs index 1cd5552a93..c5997ee0b7 100644 --- a/contrib/db_pools/codegen/tests/ui-fail.rs +++ b/contrib/db_pools/codegen/tests/ui-fail.rs @@ -2,7 +2,13 @@ fn ui() { let path = match version_check::is_feature_flaggable() { Some(true) => "ui-fail-nightly", - _ => "ui-fail-stable" + _ => { + if version_check::is_min_version("1.56.0").unwrap() { + "ui-fail-stable" + } else { + "ui-fail-msrv" + } + } }; let t = trybuild::TestCases::new(); diff --git a/contrib/db_pools/lib/Cargo.toml b/contrib/db_pools/lib/Cargo.toml index 80c5e40ee1..743e34be0c 100644 --- a/contrib/db_pools/lib/Cargo.toml +++ b/contrib/db_pools/lib/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rocket_db_pools" -version = "0.1.0-rc" +version = "0.1.0-rc.2" authors = ["Sergio Benitez ", "Jeb Rosen "] description = "Rocket async database pooling support" repository = "https://github.com/SergioBenitez/Rocket/contrib/db_pools" @@ -9,6 +9,13 @@ keywords = ["rocket", "framework", "database", "pools"] license = "MIT OR Apache-2.0" edition = "2018" +[package.metadata] +# minimum supported rust version 1.53.0 because of +# error[E0391]: cycle detected when computing the supertraits of `database::Database` +# minimum supported rust version 1.54.0 because of +# dependency sqlx uses `std::io::ErrorKind::OutOfMemory` +msrv = "1.54.0" + [package.metadata.docs.rs] all-features = true @@ -26,33 +33,36 @@ sqlx_macros = ["sqlx/macros"] [dependencies.rocket] path = "../../../core/lib" -version = "0.5.0-rc.1" +version = "0.5.0-rc.2" default-features = false [dependencies.rocket_db_pools_codegen] path = "../codegen" -version = "0.1.0-rc" +version = "0.1.0-rc.2" [dependencies.deadpool] -version = "0.8" +# to update deadpool to 0.9 would require MSRV 1.54.0 +version = "0.9" default-features = false features = ["rt_tokio_1", "managed"] optional = true [dependencies.deadpool-postgres] -version = "0.9" +# to update to 0.10 would require MSRV 1.54.0 +version = "0.10" default-features = false features = ["rt_tokio_1"] optional = true [dependencies.deadpool-redis] -version = "0.8.1" +# to update to 0.10 would require MSRV 1.54.0 +version = "0.10" default-features = false features = ["rt_tokio_1"] optional = true [dependencies.mongodb] -version = "1" +version = "2.1" default-features = false features = ["tokio-runtime"] optional = true diff --git a/contrib/db_pools/lib/src/pool.rs b/contrib/db_pools/lib/src/pool.rs index a1ecf0b9ac..03335bfe18 100644 --- a/contrib/db_pools/lib/src/pool.rs +++ b/contrib/db_pools/lib/src/pool.rs @@ -1,7 +1,10 @@ use rocket::figment::Figment; #[allow(unused_imports)] -use {std::time::Duration, crate::{Error, Config}}; +use { + crate::{Config, Error}, + std::time::Duration, +}; /// Generic [`Database`](crate::Database) driver connection pool trait. /// @@ -137,8 +140,8 @@ pub trait Pool: Sized + Send + Sync + 'static { #[cfg(feature = "deadpool")] mod deadpool_postgres { - use deadpool::managed::{Manager, Pool, PoolConfig, PoolError, Object}; - use super::{Duration, Error, Config, Figment}; + use super::{Config, Duration, Error, Figment}; + use deadpool::managed::{BuildError, Manager, Object, Pool, PoolError}; pub trait DeadManager: Manager + Sized + Send + Sync + 'static { fn new(config: &Config) -> Result; @@ -147,7 +150,10 @@ mod deadpool_postgres { #[cfg(feature = "deadpool_postgres")] impl DeadManager for deadpool_postgres::Manager { fn new(config: &Config) -> Result { - Ok(Self::new(config.url.parse()?, deadpool_postgres::tokio_postgres::NoTls)) + Ok(Self::new( + config.url.parse()?, + deadpool_postgres::tokio_postgres::NoTls, + )) } } @@ -160,7 +166,10 @@ mod deadpool_postgres { #[rocket::async_trait] impl>> crate::Pool for Pool - where M::Type: Send, C: Send + Sync + 'static, M::Error: std::error::Error + where + M::Type: Send, + C: Send + Sync + 'static, + M::Error: std::error::Error, { type Error = Error>; @@ -170,12 +179,21 @@ mod deadpool_postgres { let config: Config = figment.extract()?; let manager = M::new(&config).map_err(Error::Init)?; - let mut pool = PoolConfig::new(config.max_connections); - pool.timeouts.create = Some(Duration::from_secs(config.connect_timeout)); - pool.timeouts.wait = Some(Duration::from_secs(config.connect_timeout)); - pool.timeouts.recycle = config.idle_timeout.map(Duration::from_secs); - pool.runtime = deadpool::Runtime::Tokio1; - Ok(Pool::from_config(manager, pool)) + let pool = Pool::builder(manager) + .max_size(config.max_connections) + .create_timeout(Some(Duration::from_secs(config.connect_timeout))) + .wait_timeout(Some(Duration::from_secs(config.connect_timeout))) + .recycle_timeout(config.idle_timeout.map(Duration::from_secs)) + .runtime(deadpool::Runtime::Tokio1) + .build(); + + pool.map_err(|e| match e { + BuildError::Backend(e) => Error::Init(e), + BuildError::NoRuntimeSpecified(e_msg) => { + // it is set above in builder, so panic here + panic!("init failed to set runtime: {}", e_msg) + } + }) } async fn get(&self) -> Result { @@ -186,9 +204,9 @@ mod deadpool_postgres { #[cfg(feature = "sqlx")] mod sqlx { - use sqlx::ConnectOptions; - use super::{Duration, Error, Config, Figment}; + use super::{Config, Duration, Error, Figment}; use rocket::config::LogLevel; + use sqlx::ConnectOptions; type Options = <::Connection as sqlx::Connection>::Options; @@ -239,8 +257,8 @@ mod sqlx { #[cfg(feature = "mongodb")] mod mongodb { - use mongodb::{Client, options::ClientOptions}; - use super::{Duration, Error, Config, Figment}; + use super::{Config, Duration, Error, Figment}; + use mongodb::{options::ClientOptions, Client}; #[rocket::async_trait] impl crate::Pool for Client { @@ -250,11 +268,12 @@ mod mongodb { async fn init(figment: &Figment) -> Result { let config = figment.extract::()?; - let mut opts = ClientOptions::parse(&config.url).await.map_err(Error::Init)?; + let mut opts = ClientOptions::parse(&config.url) + .await + .map_err(Error::Init)?; opts.min_pool_size = config.min_connections; opts.max_pool_size = Some(config.max_connections as u32); opts.max_idle_time = config.idle_timeout.map(Duration::from_secs); - opts.wait_queue_timeout = Some(Duration::from_secs(config.connect_timeout)); opts.connect_timeout = Some(Duration::from_secs(config.connect_timeout)); Client::with_options(opts).map_err(Error::Init) } diff --git a/contrib/dyn_templates/Cargo.toml b/contrib/dyn_templates/Cargo.toml index d6dd21f855..3fb767000c 100644 --- a/contrib/dyn_templates/Cargo.toml +++ b/contrib/dyn_templates/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rocket_dyn_templates" -version = "0.1.0-rc.1" +version = "0.1.0-rc.2" authors = ["Sergio Benitez "] description = "Dynamic templating engine integration for Rocket." documentation = "https://api.rocket.rs/v0.5-rc/rocket_dyn_templates/" @@ -17,22 +17,22 @@ handlebars = ["handlebars_"] [dependencies] glob = "0.3" -notify = "4.0.6" +notify = "4.0" normpath = "0.3" [dependencies.rocket] -version = "0.5.0-rc.1" +version = "0.5.0-rc.2" path = "../../core/lib" default-features = false [dependencies.tera_] package = "tera" -version = "1.10.0" +version = "1.15" optional = true [dependencies.handlebars_] package = "handlebars" -version = "4.1" +version = "4.2" optional = true [package.metadata.docs.rs] diff --git a/contrib/dyn_templates/README.md b/contrib/dyn_templates/README.md index 57421d3c2e..76dbb4b6a6 100644 --- a/contrib/dyn_templates/README.md +++ b/contrib/dyn_templates/README.md @@ -35,6 +35,7 @@ supports [Handlebars] and [Tera]. last two extensions**: ```rust + use std::collections::HashMap; use rocket_dyn_templates::Template; #[launch] @@ -44,6 +45,7 @@ supports [Handlebars] and [Tera]. #[get("/")] fn index() -> Template { + let context: HashMap<&str, &str> = HashMap::new(); Template::render("template-name", &context) } ``` diff --git a/contrib/dyn_templates/src/engine.rs b/contrib/dyn_templates/src/engine.rs index a1669142c7..eba7230737 100644 --- a/contrib/dyn_templates/src/engine.rs +++ b/contrib/dyn_templates/src/engine.rs @@ -60,7 +60,7 @@ pub struct Engines { pub tera: Tera, /// The Handlebars templating engine. This field is only available when the /// `handlebars_templates` feature is enabled. When calling methods on the - /// `Tera` instance, ensure you use types imported from + /// `Handlebars` instance, ensure you use types imported from /// `rocket_dyn_templates::handlebars` to avoid version mismatches. #[cfg(feature = "handlebars")] pub handlebars: Handlebars<'static>, diff --git a/contrib/dyn_templates/src/lib.rs b/contrib/dyn_templates/src/lib.rs index 7b29b425b7..1740cc71ce 100644 --- a/contrib/dyn_templates/src/lib.rs +++ b/contrib/dyn_templates/src/lib.rs @@ -12,7 +12,7 @@ //! //! ```toml //! [dependencies.rocket_dyn_templates] -//! version = "0.1.0-rc.1" +//! version = "0.1.0-rc.2" //! features = ["handlebars", "tera"] //! ``` //! @@ -26,6 +26,7 @@ //! //! ```rust //! # #[macro_use] extern crate rocket; +//! use std::collections::HashMap; //! use rocket_dyn_templates::Template; //! //! #[launch] @@ -35,7 +36,7 @@ //! //! #[get("/")] //! fn index() -> Template { -//! # let context = (); +//! let context: HashMap<&str, &str> = HashMap::new(); //! Template::render("template-name", &context) //! } //! ``` @@ -76,11 +77,11 @@ //! //! | Engine | Version | Extension | //! |--------------|---------|-----------| -//! | [Tera] | 1 | `.tera` | -//! | [Handlebars] | 4 | `.hbs` | +//! | [Tera] | 1.15 | `.tera` | +//! | [Handlebars] | 4.2 | `.hbs` | //! -//! [Tera]: https://docs.rs/crate/tera/1 -//! [Handlebars]: https://docs.rs/crate/handlebars/4 +//! [Tera]: https://docs.rs/crate/tera/1.15 +//! [Handlebars]: https://docs.rs/crate/handlebars/4.2 //! //! Any file that ends with one of these extension will be discovered and //! rendered with the corresponding templating engine. The _name_ of the @@ -368,8 +369,7 @@ impl Template { /// let client = Client::untracked(rocket).expect("valid rocket"); /// /// // Create a `context`. Here, just an empty `HashMap`. - /// let mut context = HashMap::new(); - /// # context.insert("test", "test"); + /// let context: HashMap<&str, &str> = HashMap::new(); /// let template = Template::show(client.rocket(), "index", context); /// } /// ``` @@ -528,7 +528,7 @@ macro_rules! context { #[allow(non_camel_case_types)] impl<$($key: Serialize),*> Serialize for ContextMacroCtxObject<$($key),*> { - fn serialize(&self, serializer: S) -> Result + fn serialize(&self, serializer: S) -> ::std::result::Result where S: Serializer, { let mut map = serializer.serialize_map(None)?; diff --git a/contrib/sync_db_pools/codegen-diesel-tests/Cargo.toml b/contrib/sync_db_pools/codegen-diesel-tests/Cargo.toml new file mode 100644 index 0000000000..f5e845a55a --- /dev/null +++ b/contrib/sync_db_pools/codegen-diesel-tests/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "rocket_sync_db_pools_codegen_diesel_tests" +version = "0.0.0" +authors = ["Sergio Benitez "] +repository = "https://github.com/SergioBenitez/Rocket/contrib/sync_db_pools" +readme = "../../README.md" +keywords = ["rocket", "framework", "database", "pools"] +license = "MIT OR Apache-2.0" +edition = "2018" +publish = false + +[dev-dependencies] +version_check = "0.9" +trybuild = "1.0" +rocket_sync_db_pools = { path = "../lib", features = ["diesel_sqlite_pool"] } diff --git a/contrib/sync_db_pools/codegen-diesel-tests/src/lib.rs b/contrib/sync_db_pools/codegen-diesel-tests/src/lib.rs new file mode 100644 index 0000000000..903647db07 --- /dev/null +++ b/contrib/sync_db_pools/codegen-diesel-tests/src/lib.rs @@ -0,0 +1 @@ +//! empty lib to link with tests of package diff --git a/contrib/sync_db_pools/codegen/tests/ui-fail-stable/database-syntax.rs b/contrib/sync_db_pools/codegen-diesel-tests/tests/ui-fail-msrv/database-syntax.rs similarity index 100% rename from contrib/sync_db_pools/codegen/tests/ui-fail-stable/database-syntax.rs rename to contrib/sync_db_pools/codegen-diesel-tests/tests/ui-fail-msrv/database-syntax.rs diff --git a/contrib/sync_db_pools/codegen/tests/ui-fail-stable/database-syntax.stderr b/contrib/sync_db_pools/codegen-diesel-tests/tests/ui-fail-msrv/database-syntax.stderr similarity index 72% rename from contrib/sync_db_pools/codegen/tests/ui-fail-stable/database-syntax.stderr rename to contrib/sync_db_pools/codegen-diesel-tests/tests/ui-fail-msrv/database-syntax.stderr index 35c0ad99a7..e776412921 100644 --- a/contrib/sync_db_pools/codegen/tests/ui-fail-stable/database-syntax.stderr +++ b/contrib/sync_db_pools/codegen-diesel-tests/tests/ui-fail-msrv/database-syntax.stderr @@ -1,5 +1,5 @@ error: unexpected end of input, expected string literal - --> $DIR/database-syntax.rs:6:1 + --> tests/ui-fail-msrv/database-syntax.rs:6:1 | 6 | #[database] | ^^^^^^^^^^^ @@ -7,50 +7,50 @@ error: unexpected end of input, expected string literal = note: this error originates in the attribute macro `database` (in Nightly builds, run with -Z macro-backtrace for more info) error: expected string literal - --> $DIR/database-syntax.rs:9:12 + --> tests/ui-fail-msrv/database-syntax.rs:9:12 | 9 | #[database(1)] | ^ error: expected string literal - --> $DIR/database-syntax.rs:12:12 + --> tests/ui-fail-msrv/database-syntax.rs:12:12 | 12 | #[database(123)] | ^^^ error: unexpected token - --> $DIR/database-syntax.rs:15:20 + --> tests/ui-fail-msrv/database-syntax.rs:15:20 | 15 | #[database("hello" "hi")] | ^^^^ error: `database` attribute can only be used on structs - --> $DIR/database-syntax.rs:19:1 + --> tests/ui-fail-msrv/database-syntax.rs:19:1 | 19 | enum Foo { } | ^^^^ error: `database` attribute can only be applied to structs with exactly one unnamed field --- help: example: `struct MyDatabase(diesel::SqliteConnection);` - --> $DIR/database-syntax.rs:22:11 + --> tests/ui-fail-msrv/database-syntax.rs:22:11 | 22 | struct Bar(diesel::SqliteConnection, diesel::SqliteConnection); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: `database` attribute can only be used on structs - --> $DIR/database-syntax.rs:25:1 + --> tests/ui-fail-msrv/database-syntax.rs:25:1 | 25 | union Baz { } | ^^^^^ error: `database` attribute cannot be applied to structs with generics - --> $DIR/database-syntax.rs:28:9 + --> tests/ui-fail-msrv/database-syntax.rs:28:9 | 28 | struct E<'r>(&'r str); | ^ error: `database` attribute cannot be applied to structs with generics - --> $DIR/database-syntax.rs:31:9 + --> tests/ui-fail-msrv/database-syntax.rs:31:9 | 31 | struct F(T); | ^ diff --git a/contrib/sync_db_pools/codegen/tests/ui-fail-stable/database-types.rs b/contrib/sync_db_pools/codegen-diesel-tests/tests/ui-fail-msrv/database-types.rs similarity index 100% rename from contrib/sync_db_pools/codegen/tests/ui-fail-stable/database-types.rs rename to contrib/sync_db_pools/codegen-diesel-tests/tests/ui-fail-msrv/database-types.rs diff --git a/contrib/sync_db_pools/codegen/tests/ui-fail-stable/database-types.stderr b/contrib/sync_db_pools/codegen-diesel-tests/tests/ui-fail-msrv/database-types.stderr similarity index 89% rename from contrib/sync_db_pools/codegen/tests/ui-fail-stable/database-types.stderr rename to contrib/sync_db_pools/codegen-diesel-tests/tests/ui-fail-msrv/database-types.stderr index 65e3ed8f2a..55f6f48af5 100644 --- a/contrib/sync_db_pools/codegen/tests/ui-fail-stable/database-types.stderr +++ b/contrib/sync_db_pools/codegen-diesel-tests/tests/ui-fail-msrv/database-types.stderr @@ -1,5 +1,5 @@ error[E0277]: the trait bound `Unknown: Poolable` is not satisfied - --> $DIR/database-types.rs:6:10 + --> tests/ui-fail-msrv/database-types.rs:6:10 | 6 | struct A(Unknown); | ^^^^^^^ the trait `Poolable` is not implemented for `Unknown` @@ -10,7 +10,7 @@ error[E0277]: the trait bound `Unknown: Poolable` is not satisfied | -------- required by this bound in `rocket_sync_db_pools::Connection` error[E0277]: the trait bound `Vec: Poolable` is not satisfied - --> $DIR/database-types.rs:9:10 + --> tests/ui-fail-msrv/database-types.rs:9:10 | 9 | struct B(Vec); | ^^^ the trait `Poolable` is not implemented for `Vec` diff --git a/contrib/sync_db_pools/codegen-diesel-tests/tests/ui-fail-nightly/database-syntax.rs b/contrib/sync_db_pools/codegen-diesel-tests/tests/ui-fail-nightly/database-syntax.rs new file mode 120000 index 0000000000..717e99bd0e --- /dev/null +++ b/contrib/sync_db_pools/codegen-diesel-tests/tests/ui-fail-nightly/database-syntax.rs @@ -0,0 +1 @@ +../ui-fail/database-syntax.rs \ No newline at end of file diff --git a/contrib/sync_db_pools/codegen/tests/ui-fail-nightly/database-syntax.stderr b/contrib/sync_db_pools/codegen-diesel-tests/tests/ui-fail-nightly/database-syntax.stderr similarity index 72% rename from contrib/sync_db_pools/codegen/tests/ui-fail-nightly/database-syntax.stderr rename to contrib/sync_db_pools/codegen-diesel-tests/tests/ui-fail-nightly/database-syntax.stderr index 0f331e3131..e89220bded 100644 --- a/contrib/sync_db_pools/codegen/tests/ui-fail-nightly/database-syntax.stderr +++ b/contrib/sync_db_pools/codegen-diesel-tests/tests/ui-fail-nightly/database-syntax.stderr @@ -1,5 +1,5 @@ error: unexpected end of input, expected string literal - --> $DIR/database-syntax.rs:6:1 + --> tests/ui-fail-nightly/database-syntax.rs:6:1 | 6 | #[database] | ^^^^^^^^^^^ @@ -7,31 +7,31 @@ error: unexpected end of input, expected string literal = note: this error originates in the attribute macro `database` (in Nightly builds, run with -Z macro-backtrace for more info) error: expected string literal - --> $DIR/database-syntax.rs:9:12 + --> tests/ui-fail-nightly/database-syntax.rs:9:12 | 9 | #[database(1)] | ^ error: expected string literal - --> $DIR/database-syntax.rs:12:12 + --> tests/ui-fail-nightly/database-syntax.rs:12:12 | 12 | #[database(123)] | ^^^ error: unexpected token - --> $DIR/database-syntax.rs:15:20 + --> tests/ui-fail-nightly/database-syntax.rs:15:20 | 15 | #[database("hello" "hi")] | ^^^^ error: `database` attribute can only be used on structs - --> $DIR/database-syntax.rs:19:1 + --> tests/ui-fail-nightly/database-syntax.rs:19:1 | 19 | enum Foo { } | ^^^^^^^^^^^^^ error: `database` attribute can only be applied to structs with exactly one unnamed field - --> $DIR/database-syntax.rs:22:11 + --> tests/ui-fail-nightly/database-syntax.rs:22:11 | 22 | struct Bar(diesel::SqliteConnection, diesel::SqliteConnection); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -39,19 +39,19 @@ error: `database` attribute can only be applied to structs with exactly one unna = help: example: `struct MyDatabase(diesel::SqliteConnection);` error: `database` attribute can only be used on structs - --> $DIR/database-syntax.rs:25:1 + --> tests/ui-fail-nightly/database-syntax.rs:25:1 | 25 | union Baz { } | ^^^^^^^^^^^^^^ error: `database` attribute cannot be applied to structs with generics - --> $DIR/database-syntax.rs:28:9 + --> tests/ui-fail-nightly/database-syntax.rs:28:9 | 28 | struct E<'r>(&'r str); | ^^^^ error: `database` attribute cannot be applied to structs with generics - --> $DIR/database-syntax.rs:31:9 + --> tests/ui-fail-nightly/database-syntax.rs:31:9 | 31 | struct F(T); | ^^^ diff --git a/contrib/sync_db_pools/codegen-diesel-tests/tests/ui-fail-nightly/database-types.rs b/contrib/sync_db_pools/codegen-diesel-tests/tests/ui-fail-nightly/database-types.rs new file mode 120000 index 0000000000..98c58fba54 --- /dev/null +++ b/contrib/sync_db_pools/codegen-diesel-tests/tests/ui-fail-nightly/database-types.rs @@ -0,0 +1 @@ +../ui-fail/database-types.rs \ No newline at end of file diff --git a/contrib/sync_db_pools/codegen/tests/ui-fail-nightly/database-types.stderr b/contrib/sync_db_pools/codegen-diesel-tests/tests/ui-fail-nightly/database-types.stderr similarity index 69% rename from contrib/sync_db_pools/codegen/tests/ui-fail-nightly/database-types.stderr rename to contrib/sync_db_pools/codegen-diesel-tests/tests/ui-fail-nightly/database-types.stderr index 7143b4a882..2987650fad 100644 --- a/contrib/sync_db_pools/codegen/tests/ui-fail-nightly/database-types.stderr +++ b/contrib/sync_db_pools/codegen-diesel-tests/tests/ui-fail-nightly/database-types.stderr @@ -1,23 +1,23 @@ error[E0277]: the trait bound `Unknown: Poolable` is not satisfied - --> $DIR/database-types.rs:6:10 + --> tests/ui-fail-nightly/database-types.rs:6:10 | 6 | struct A(Unknown); | ^^^^^^^ the trait `Poolable` is not implemented for `Unknown` | note: required by a bound in `rocket_sync_db_pools::Connection` - --> $DIR/connection.rs:44:29 + --> $WORKSPACE/contrib/sync_db_pools/lib/src/connection.rs | -44 | pub struct Connection { + | pub struct Connection { | ^^^^^^^^ required by this bound in `rocket_sync_db_pools::Connection` error[E0277]: the trait bound `Vec: Poolable` is not satisfied - --> $DIR/database-types.rs:9:10 + --> tests/ui-fail-nightly/database-types.rs:9:10 | 9 | struct B(Vec); | ^^^^^^^^ the trait `Poolable` is not implemented for `Vec` | note: required by a bound in `rocket_sync_db_pools::Connection` - --> $DIR/connection.rs:44:29 + --> $WORKSPACE/contrib/sync_db_pools/lib/src/connection.rs | -44 | pub struct Connection { + | pub struct Connection { | ^^^^^^^^ required by this bound in `rocket_sync_db_pools::Connection` diff --git a/contrib/sync_db_pools/codegen-diesel-tests/tests/ui-fail-stable/database-syntax.rs b/contrib/sync_db_pools/codegen-diesel-tests/tests/ui-fail-stable/database-syntax.rs new file mode 120000 index 0000000000..717e99bd0e --- /dev/null +++ b/contrib/sync_db_pools/codegen-diesel-tests/tests/ui-fail-stable/database-syntax.rs @@ -0,0 +1 @@ +../ui-fail/database-syntax.rs \ No newline at end of file diff --git a/contrib/sync_db_pools/codegen-diesel-tests/tests/ui-fail-stable/database-syntax.stderr b/contrib/sync_db_pools/codegen-diesel-tests/tests/ui-fail-stable/database-syntax.stderr new file mode 100644 index 0000000000..6e5323157d --- /dev/null +++ b/contrib/sync_db_pools/codegen-diesel-tests/tests/ui-fail-stable/database-syntax.stderr @@ -0,0 +1,56 @@ +error: unexpected end of input, expected string literal + --> tests/ui-fail-stable/database-syntax.rs:6:1 + | +6 | #[database] + | ^^^^^^^^^^^ + | + = note: this error originates in the attribute macro `database` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: expected string literal + --> tests/ui-fail-stable/database-syntax.rs:9:12 + | +9 | #[database(1)] + | ^ + +error: expected string literal + --> tests/ui-fail-stable/database-syntax.rs:12:12 + | +12 | #[database(123)] + | ^^^ + +error: unexpected token + --> tests/ui-fail-stable/database-syntax.rs:15:20 + | +15 | #[database("hello" "hi")] + | ^^^^ + +error: `database` attribute can only be used on structs + --> tests/ui-fail-stable/database-syntax.rs:19:1 + | +19 | enum Foo { } + | ^^^^ + +error: `database` attribute can only be applied to structs with exactly one unnamed field + --- help: example: `struct MyDatabase(diesel::SqliteConnection);` + --> tests/ui-fail-stable/database-syntax.rs:22:11 + | +22 | struct Bar(diesel::SqliteConnection, diesel::SqliteConnection); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: `database` attribute can only be used on structs + --> tests/ui-fail-stable/database-syntax.rs:25:1 + | +25 | union Baz { } + | ^^^^^ + +error: `database` attribute cannot be applied to structs with generics + --> tests/ui-fail-stable/database-syntax.rs:28:9 + | +28 | struct E<'r>(&'r str); + | ^ + +error: `database` attribute cannot be applied to structs with generics + --> tests/ui-fail-stable/database-syntax.rs:31:9 + | +31 | struct F(T); + | ^ diff --git a/contrib/sync_db_pools/codegen-diesel-tests/tests/ui-fail-stable/database-types.rs b/contrib/sync_db_pools/codegen-diesel-tests/tests/ui-fail-stable/database-types.rs new file mode 120000 index 0000000000..98c58fba54 --- /dev/null +++ b/contrib/sync_db_pools/codegen-diesel-tests/tests/ui-fail-stable/database-types.rs @@ -0,0 +1 @@ +../ui-fail/database-types.rs \ No newline at end of file diff --git a/contrib/sync_db_pools/codegen-diesel-tests/tests/ui-fail-stable/database-types.stderr b/contrib/sync_db_pools/codegen-diesel-tests/tests/ui-fail-stable/database-types.stderr new file mode 100644 index 0000000000..ab23da6ae0 --- /dev/null +++ b/contrib/sync_db_pools/codegen-diesel-tests/tests/ui-fail-stable/database-types.stderr @@ -0,0 +1,23 @@ +error[E0277]: the trait bound `Unknown: Poolable` is not satisfied + --> tests/ui-fail-stable/database-types.rs:6:10 + | +6 | struct A(Unknown); + | ^^^^^^^ the trait `Poolable` is not implemented for `Unknown` + | +note: required by a bound in `rocket_sync_db_pools::Connection` + --> $WORKSPACE/contrib/sync_db_pools/lib/src/connection.rs + | + | pub struct Connection { + | ^^^^^^^^ required by this bound in `rocket_sync_db_pools::Connection` + +error[E0277]: the trait bound `Vec: Poolable` is not satisfied + --> tests/ui-fail-stable/database-types.rs:9:10 + | +9 | struct B(Vec); + | ^^^ the trait `Poolable` is not implemented for `Vec` + | +note: required by a bound in `rocket_sync_db_pools::Connection` + --> $WORKSPACE/contrib/sync_db_pools/lib/src/connection.rs + | + | pub struct Connection { + | ^^^^^^^^ required by this bound in `rocket_sync_db_pools::Connection` diff --git a/contrib/sync_db_pools/codegen/tests/ui-fail.rs b/contrib/sync_db_pools/codegen-diesel-tests/tests/ui-fail.rs similarity index 53% rename from contrib/sync_db_pools/codegen/tests/ui-fail.rs rename to contrib/sync_db_pools/codegen-diesel-tests/tests/ui-fail.rs index 1cd5552a93..c5997ee0b7 100644 --- a/contrib/sync_db_pools/codegen/tests/ui-fail.rs +++ b/contrib/sync_db_pools/codegen-diesel-tests/tests/ui-fail.rs @@ -2,7 +2,13 @@ fn ui() { let path = match version_check::is_feature_flaggable() { Some(true) => "ui-fail-nightly", - _ => "ui-fail-stable" + _ => { + if version_check::is_min_version("1.56.0").unwrap() { + "ui-fail-stable" + } else { + "ui-fail-msrv" + } + } }; let t = trybuild::TestCases::new(); diff --git a/contrib/sync_db_pools/codegen/tests/ui-fail/database-syntax.rs b/contrib/sync_db_pools/codegen-diesel-tests/tests/ui-fail/database-syntax.rs similarity index 100% rename from contrib/sync_db_pools/codegen/tests/ui-fail/database-syntax.rs rename to contrib/sync_db_pools/codegen-diesel-tests/tests/ui-fail/database-syntax.rs diff --git a/contrib/sync_db_pools/codegen/tests/ui-fail/database-types.rs b/contrib/sync_db_pools/codegen-diesel-tests/tests/ui-fail/database-types.rs similarity index 100% rename from contrib/sync_db_pools/codegen/tests/ui-fail/database-types.rs rename to contrib/sync_db_pools/codegen-diesel-tests/tests/ui-fail/database-types.rs diff --git a/contrib/sync_db_pools/codegen/Cargo.toml b/contrib/sync_db_pools/codegen/Cargo.toml index 86b794dd55..4a761ea9a3 100644 --- a/contrib/sync_db_pools/codegen/Cargo.toml +++ b/contrib/sync_db_pools/codegen/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rocket_sync_db_pools_codegen" -version = "0.1.0-rc.1" +version = "0.1.0-rc.2" authors = ["Sergio Benitez "] description = "Procedural macros for rocket_sync_db_pools." repository = "https://github.com/SergioBenitez/Rocket/contrib/sync_db_pools" @@ -15,8 +15,3 @@ proc-macro = true [dependencies] quote = "1.0" devise = "0.3" - -[dev-dependencies] -version_check = "0.9" -trybuild = "1.0" -rocket_sync_db_pools = { path = "../lib", features = ["diesel_sqlite_pool"] } diff --git a/contrib/sync_db_pools/codegen/src/database.rs b/contrib/sync_db_pools/codegen/src/database.rs index 989cc0a634..217c35844d 100644 --- a/contrib/sync_db_pools/codegen/src/database.rs +++ b/contrib/sync_db_pools/codegen/src/database.rs @@ -64,6 +64,7 @@ pub fn database_attr(attr: TokenStream, input: TokenStream) -> Result"] description = "Rocket async database pooling support for sync database drivers." repository = "https://github.com/SergioBenitez/Rocket/tree/v0.5-rc/contrib/sync_db_pools" @@ -19,26 +19,27 @@ memcache_pool = ["memcache", "r2d2-memcache"] [dependencies] r2d2 = "0.8" -tokio = { version = "1.6.1", features = ["rt", "rt-multi-thread"] } +tokio = { version = "1.15", features = ["rt", "rt-multi-thread"] } serde = { version = "1.0", features = ["derive"] } -diesel = { version = "1.0", default-features = false, optional = true } +diesel = { version = "1.4", default-features = false, optional = true } postgres = { version = "0.19", optional = true } r2d2_postgres = { version = "0.18", optional = true } -rusqlite = { version = "0.25", optional = true } -r2d2_sqlite = { version = "0.18", optional = true } +rusqlite = { version = "0.26", optional = true } +r2d2_sqlite = { version = "0.19", optional = true } +# r2d2-memcache 0.6 depends on memcache 0.15 memcache = { version = "0.15", optional = true } r2d2-memcache = { version = "0.6", optional = true } [dependencies.rocket_sync_db_pools_codegen] -version = "0.1.0-rc.1" +version = "0.1.0-rc.2" path = "../codegen" [dependencies.rocket] -version = "0.5.0-rc.1" +version = "0.5.0-rc.2" path = "../../../core/lib" default-features = false diff --git a/contrib/sync_db_pools/lib/src/lib.rs b/contrib/sync_db_pools/lib/src/lib.rs index b8d0165e10..c5304b5a8e 100644 --- a/contrib/sync_db_pools/lib/src/lib.rs +++ b/contrib/sync_db_pools/lib/src/lib.rs @@ -327,7 +327,7 @@ //! | Memcache | [`memcache`] | `0.15` | [`memcache::Client`] | `memcache_pool` | //! //! [Diesel]: https://diesel.rs -//! [`rusqlite::Connection`]: https://docs.rs/rusqlite/0.23.0/rusqlite/struct.Connection.html +//! [`rusqlite::Connection`]: https://docs.rs/rusqlite/0.26/rusqlite/struct.Connection.html //! [`diesel::SqliteConnection`]: http://docs.diesel.rs/diesel/prelude/struct.SqliteConnection.html //! [`postgres::Client`]: https://docs.rs/postgres/0.19/postgres/struct.Client.html //! [`diesel::PgConnection`]: http://docs.diesel.rs/diesel/pg/struct.PgConnection.html diff --git a/contrib/sync_db_pools/lib/src/poolable.rs b/contrib/sync_db_pools/lib/src/poolable.rs index 9ba895f0c3..28e3671c3c 100644 --- a/contrib/sync_db_pools/lib/src/poolable.rs +++ b/contrib/sync_db_pools/lib/src/poolable.rs @@ -1,5 +1,3 @@ -use std::time::Duration; - use r2d2::ManageConnection; use rocket::{Rocket, Build}; @@ -119,6 +117,7 @@ impl Poolable for diesel::SqliteConnection { type Error = std::convert::Infallible; fn pool(db_name: &str, rocket: &Rocket) -> PoolResult { + use std::time::Duration; use diesel::{SqliteConnection, connection::SimpleConnection}; use diesel::r2d2::{CustomizeConnection, ConnectionManager, Error, Pool}; @@ -155,6 +154,8 @@ impl Poolable for diesel::PgConnection { type Error = std::convert::Infallible; fn pool(db_name: &str, rocket: &Rocket) -> PoolResult { + use std::time::Duration; + let config = Config::from(db_name, rocket)?; let manager = diesel::r2d2::ConnectionManager::new(&config.url); let pool = r2d2::Pool::builder() @@ -172,6 +173,8 @@ impl Poolable for diesel::MysqlConnection { type Error = std::convert::Infallible; fn pool(db_name: &str, rocket: &Rocket) -> PoolResult { + use std::time::Duration; + let config = Config::from(db_name, rocket)?; let manager = diesel::r2d2::ConnectionManager::new(&config.url); let pool = r2d2::Pool::builder() @@ -190,6 +193,8 @@ impl Poolable for postgres::Client { type Error = postgres::Error; fn pool(db_name: &str, rocket: &Rocket) -> PoolResult { + use std::time::Duration; + let config = Config::from(db_name, rocket)?; let url = config.url.parse().map_err(Error::Custom)?; let manager = r2d2_postgres::PostgresConnectionManager::new(url, postgres::tls::NoTls); @@ -208,6 +213,7 @@ impl Poolable for rusqlite::Connection { type Error = std::convert::Infallible; fn pool(db_name: &str, rocket: &Rocket) -> PoolResult { + use std::time::Duration; use rocket::figment::providers::Serialized; #[derive(Debug, serde::Deserialize, serde::Serialize)] @@ -268,6 +274,8 @@ impl Poolable for memcache::Client { type Error = memcache::MemcacheError; fn pool(db_name: &str, rocket: &Rocket) -> PoolResult { + use std::time::Duration; + let config = Config::from(db_name, rocket)?; let manager = r2d2_memcache::MemcacheConnectionManager::new(&*config.url); let pool = r2d2::Pool::builder() diff --git a/core/codegen/Cargo.toml b/core/codegen/Cargo.toml index 1832784487..5b39f304bd 100644 --- a/core/codegen/Cargo.toml +++ b/core/codegen/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rocket_codegen" -version = "0.5.0-rc.1" +version = "0.5.0-rc.2" authors = ["Sergio Benitez "] description = "Procedural macros for the Rocket web framework." documentation = "https://api.rocket.rs/v0.5-rc/rocket_codegen/" @@ -11,6 +11,10 @@ keywords = ["rocket", "web", "framework", "code", "generation"] license = "MIT OR Apache-2.0" edition = "2018" +[package.metadata] +# minimum supported rust version 1.51.0 because of cargo resolver in dependency crate time +msrv = "1.51.0" + [lib] proc-macro = true @@ -20,13 +24,13 @@ quote = "1.0" syn = { version = "1.0.72", features = ["full", "visit", "visit-mut", "extra-traits"] } proc-macro2 = "1.0.27" devise = "0.3.1" -rocket_http = { version = "0.5.0-rc.1", path = "../http/" } +rocket_http = { version = "0.5.0-rc.2", path = "../http/" } unicode-xid = "0.2" glob = "0.3" [dev-dependencies] -rocket = { version = "0.5.0-rc.1", path = "../lib", features = ["json", "msgpack"] } +rocket = { version = "0.5.0-rc.2", path = "../lib", features = ["json", "msgpack"] } time = { version = "0.3", features = ["macros"] } -pretty_assertions = "0.7" +pretty_assertions = "1.0" version_check = "0.9" trybuild = "1.0" diff --git a/core/codegen/src/lib.rs b/core/codegen/src/lib.rs index c7cd15e775..a055f6e12d 100644 --- a/core/codegen/src/lib.rs +++ b/core/codegen/src/lib.rs @@ -21,7 +21,7 @@ //! //! ```toml //! [dependencies] -//! rocket = "0.5.0-rc.1" +//! rocket = "0.5.0-rc.2" //! ``` //! //! And to import all macros, attributes, and derives via `#[macro_use]` in the diff --git a/core/codegen/tests/ui-fail-nightly/async-entry.stderr b/core/codegen/tests/ui-fail-nightly/async-entry.stderr index 6044748341..3949b02f62 100644 --- a/core/codegen/tests/ui-fail-nightly/async-entry.stderr +++ b/core/codegen/tests/ui-fail-nightly/async-entry.stderr @@ -1,120 +1,120 @@ error: attribute can only be applied to `async` functions - --> $DIR/async-entry.rs:4:5 + --> tests/ui-fail-nightly/async-entry.rs:4:5 | 4 | #[rocket::main] | ^^^^^^^^^^^^^^^ | note: this function must be `async` - --> $DIR/async-entry.rs:5:5 + --> tests/ui-fail-nightly/async-entry.rs:5:5 | 5 | fn foo() { } | ^^^^^^^^ = note: this error originates in the attribute macro `rocket::main` (in Nightly builds, run with -Z macro-backtrace for more info) warning: attribute is typically applied to `main` function - --> $DIR/async-entry.rs:10:5 + --> tests/ui-fail-nightly/async-entry.rs:10:5 | 10 | #[rocket::main] | ^^^^^^^^^^^^^^^ | note: this function is not `main` - --> $DIR/async-entry.rs:11:14 + --> tests/ui-fail-nightly/async-entry.rs:11:14 | 11 | async fn foo() { } | ^^^ = note: this warning originates in the attribute macro `rocket::main` (in Nightly builds, run with -Z macro-backtrace for more info) error: attribute can only be applied to `async` functions - --> $DIR/async-entry.rs:16:5 + --> tests/ui-fail-nightly/async-entry.rs:16:5 | 16 | #[rocket::main] | ^^^^^^^^^^^^^^^ | note: this function must be `async` - --> $DIR/async-entry.rs:17:5 + --> tests/ui-fail-nightly/async-entry.rs:17:5 | 17 | fn main() { | ^^^^^^^^^ = note: this error originates in the attribute macro `rocket::main` (in Nightly builds, run with -Z macro-backtrace for more info) error: attribute cannot be applied to `main` function - --> $DIR/async-entry.rs:49:5 + --> tests/ui-fail-nightly/async-entry.rs:49:5 | 49 | #[rocket::launch] | ^^^^^^^^^^^^^^^^^ | = note: this attribute generates a `main` function note: this function cannot be `main` - --> $DIR/async-entry.rs:50:8 + --> tests/ui-fail-nightly/async-entry.rs:50:8 | 50 | fn main() -> rocekt::Rocket { | ^^^^ = note: this error originates in the attribute macro `rocket::launch` (in Nightly builds, run with -Z macro-backtrace for more info) error: attribute can only be applied to functions that return a value - --> $DIR/async-entry.rs:56:5 + --> tests/ui-fail-nightly/async-entry.rs:56:5 | 56 | #[rocket::launch] | ^^^^^^^^^^^^^^^^^ | note: this function must return a value - --> $DIR/async-entry.rs:57:5 + --> tests/ui-fail-nightly/async-entry.rs:57:5 | 57 | async fn rocket() { | ^^^^^^^^^^^^^^^^^ = note: this error originates in the attribute macro `rocket::launch` (in Nightly builds, run with -Z macro-backtrace for more info) error: attribute can only be applied to functions that return a value - --> $DIR/async-entry.rs:64:5 + --> tests/ui-fail-nightly/async-entry.rs:64:5 | 64 | #[rocket::launch] | ^^^^^^^^^^^^^^^^^ | note: this function must return a value - --> $DIR/async-entry.rs:65:5 + --> tests/ui-fail-nightly/async-entry.rs:65:5 | 65 | fn rocket() { | ^^^^^^^^^^^ = note: this error originates in the attribute macro `rocket::launch` (in Nightly builds, run with -Z macro-backtrace for more info) error: attribute cannot be applied to `main` function - --> $DIR/async-entry.rs:79:5 + --> tests/ui-fail-nightly/async-entry.rs:79:5 | 79 | #[rocket::launch] | ^^^^^^^^^^^^^^^^^ | = note: this attribute generates a `main` function note: this function cannot be `main` - --> $DIR/async-entry.rs:80:8 + --> tests/ui-fail-nightly/async-entry.rs:80:8 | 80 | fn main() -> &'static str { | ^^^^ = note: this error originates in the attribute macro `rocket::launch` (in Nightly builds, run with -Z macro-backtrace for more info) error: attribute cannot be applied to `main` function - --> $DIR/async-entry.rs:87:5 + --> tests/ui-fail-nightly/async-entry.rs:87:5 | 87 | #[rocket::launch] | ^^^^^^^^^^^^^^^^^ | = note: this attribute generates a `main` function note: this function cannot be `main` - --> $DIR/async-entry.rs:88:14 + --> tests/ui-fail-nightly/async-entry.rs:88:14 | 88 | async fn main() -> _ { | ^^^^ = note: this error originates in the attribute macro `rocket::launch` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0728]: `await` is only allowed inside `async` functions and blocks - --> $DIR/async-entry.rs:73:17 + --> tests/ui-fail-nightly/async-entry.rs:73:41 | 72 | fn rocket() -> _ { | ------ this is not `async` 73 | let _ = rocket::build().launch().await; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ only allowed inside `async` functions and blocks + | ^^^^^^ only allowed inside `async` functions and blocks error[E0308]: mismatched types - --> $DIR/async-entry.rs:35:9 + --> tests/ui-fail-nightly/async-entry.rs:35:9 | 35 | rocket::build() | ^^^^^^^^^^^^^^^ expected struct `std::string::String`, found struct `Rocket` @@ -123,7 +123,7 @@ error[E0308]: mismatched types found struct `Rocket` error[E0308]: mismatched types - --> $DIR/async-entry.rs:44:9 + --> tests/ui-fail-nightly/async-entry.rs:44:9 | 44 | "hi".to_string() | ^^^^^^^^^^^^^^^^ expected struct `Rocket`, found struct `std::string::String` @@ -132,7 +132,7 @@ error[E0308]: mismatched types found struct `std::string::String` error[E0308]: mismatched types - --> $DIR/async-entry.rs:24:21 + --> tests/ui-fail-nightly/async-entry.rs:24:21 | 24 | async fn main() { | ^ expected `()` because of default return type @@ -148,7 +148,7 @@ error[E0308]: mismatched types found struct `Rocket` error[E0308]: mismatched types - --> $DIR/async-entry.rs:33:26 + --> tests/ui-fail-nightly/async-entry.rs:33:26 | 33 | async fn rocket() -> String { | ^^^^^^ @@ -160,7 +160,7 @@ error[E0308]: mismatched types found struct `std::string::String` error[E0277]: `main` has invalid return type `Rocket` - --> $DIR/async-entry.rs:94:20 + --> tests/ui-fail-nightly/async-entry.rs:94:20 | 94 | async fn main() -> rocket::Rocket { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `main` can only return types that implement `Termination` diff --git a/core/codegen/tests/ui-fail-nightly/bad-ignored-segments.stderr b/core/codegen/tests/ui-fail-nightly/bad-ignored-segments.stderr index ce8b42f685..6a1d4acf3c 100644 --- a/core/codegen/tests/ui-fail-nightly/bad-ignored-segments.stderr +++ b/core/codegen/tests/ui-fail-nightly/bad-ignored-segments.stderr @@ -1,5 +1,5 @@ error: parameter must be named - --> $DIR/bad-ignored-segments.rs:6:12 + --> tests/ui-fail-nightly/bad-ignored-segments.rs:6:12 | 6 | #[get("/c?<_>")] | ^ @@ -7,7 +7,7 @@ error: parameter must be named = help: use a name such as `_guard` or `_param` error: parameter must be named - --> $DIR/bad-ignored-segments.rs:9:22 + --> tests/ui-fail-nightly/bad-ignored-segments.rs:9:22 | 9 | #[post("/d", data = "<_>")] | ^^^ diff --git a/core/codegen/tests/ui-fail-nightly/catch.stderr b/core/codegen/tests/ui-fail-nightly/catch.stderr index 14a830f5bf..2989a7bb70 100644 --- a/core/codegen/tests/ui-fail-nightly/catch.stderr +++ b/core/codegen/tests/ui-fail-nightly/catch.stderr @@ -1,5 +1,5 @@ error: expected `fn` - --> $DIR/catch.rs:6:1 + --> tests/ui-fail-nightly/catch.rs:6:1 | 6 | struct Catcher(String); | ^^^^^^ @@ -7,7 +7,7 @@ error: expected `fn` = help: `#[catch]` can only be used on functions error: expected `fn` - --> $DIR/catch.rs:9:7 + --> tests/ui-fail-nightly/catch.rs:9:7 | 9 | const CATCH: &str = "Catcher"; | ^^^^^ @@ -15,7 +15,7 @@ error: expected `fn` = help: `#[catch]` can only be used on functions error: expected integer or `default`, found string literal - --> $DIR/catch.rs:11:9 + --> tests/ui-fail-nightly/catch.rs:11:9 | 11 | #[catch("404")] | ^^^^^ @@ -23,7 +23,7 @@ error: expected integer or `default`, found string literal = help: `#[catch]` expects a status code int or `default`: `#[catch(404)]` or `#[catch(default)]` error: unexpected keyed parameter: expected literal or identifier - --> $DIR/catch.rs:14:9 + --> tests/ui-fail-nightly/catch.rs:14:9 | 14 | #[catch(code = "404")] | ^^^^^^^^^^^^ @@ -31,7 +31,7 @@ error: unexpected keyed parameter: expected literal or identifier = help: `#[catch]` expects a status code int or `default`: `#[catch(404)]` or `#[catch(default)]` error: unexpected keyed parameter: expected literal or identifier - --> $DIR/catch.rs:17:9 + --> tests/ui-fail-nightly/catch.rs:17:9 | 17 | #[catch(code = 404)] | ^^^^^^^^^^ @@ -39,7 +39,7 @@ error: unexpected keyed parameter: expected literal or identifier = help: `#[catch]` expects a status code int or `default`: `#[catch(404)]` or `#[catch(default)]` error: status must be in range [100, 599] - --> $DIR/catch.rs:20:9 + --> tests/ui-fail-nightly/catch.rs:20:9 | 20 | #[catch(99)] | ^^ @@ -47,7 +47,7 @@ error: status must be in range [100, 599] = help: `#[catch]` expects a status code int or `default`: `#[catch(404)]` or `#[catch(default)]` error: status must be in range [100, 599] - --> $DIR/catch.rs:23:9 + --> tests/ui-fail-nightly/catch.rs:23:9 | 23 | #[catch(600)] | ^^^ @@ -55,7 +55,7 @@ error: status must be in range [100, 599] = help: `#[catch]` expects a status code int or `default`: `#[catch(404)]` or `#[catch(default)]` error: unexpected attribute parameter: `message` - --> $DIR/catch.rs:26:14 + --> tests/ui-fail-nightly/catch.rs:26:14 | 26 | #[catch(400, message = "foo")] | ^^^^^^^^^^^^^^^ @@ -63,13 +63,13 @@ error: unexpected attribute parameter: `message` = help: `#[catch]` expects a status code int or `default`: `#[catch(404)]` or `#[catch(default)]` error[E0308]: mismatched types - --> $DIR/catch.rs:30:17 + --> tests/ui-fail-nightly/catch.rs:30:17 | 30 | fn f3(_request: &Request, other: bool) { } | ^^^^^^^^ expected `&rocket::Request<'_>`, found struct `Status` error[E0308]: mismatched types - --> $DIR/catch.rs:30:34 + --> tests/ui-fail-nightly/catch.rs:30:34 | 30 | fn f3(_request: &Request, other: bool) { } | ^^^^ expected `bool`, found `&rocket::Request<'_>` diff --git a/core/codegen/tests/ui-fail-nightly/catch_type_errors.stderr b/core/codegen/tests/ui-fail-nightly/catch_type_errors.stderr index d761eeae56..90f585a2d4 100644 --- a/core/codegen/tests/ui-fail-nightly/catch_type_errors.stderr +++ b/core/codegen/tests/ui-fail-nightly/catch_type_errors.stderr @@ -1,53 +1,37 @@ error[E0277]: the trait bound `usize: Responder<'_, '_>` is not satisfied - --> $DIR/catch_type_errors.rs:6:30 - | -6 | fn f1(_request: &Request) -> usize { - | ^^^^^ the trait `Responder<'_, '_>` is not implemented for `usize` - | -note: required by `respond_to` - --> $DIR/responder.rs:298:5 - | -298 | fn respond_to(self, request: &'r Request<'_>) -> response::Result<'o>; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + --> tests/ui-fail-nightly/catch_type_errors.rs:6:30 + | +5 | #[catch(404)] + | ------------- required by a bound introduced by this call +6 | fn f1(_request: &Request) -> usize { + | ^^^^^ the trait `Responder<'_, '_>` is not implemented for `usize` error[E0277]: the trait bound `bool: Responder<'_, '_>` is not satisfied - --> $DIR/catch_type_errors.rs:11:30 - | -11 | fn f2(_request: &Request) -> bool { - | ^^^^ the trait `Responder<'_, '_>` is not implemented for `bool` - | -note: required by `respond_to` - --> $DIR/responder.rs:298:5 - | -298 | fn respond_to(self, request: &'r Request<'_>) -> response::Result<'o>; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + --> tests/ui-fail-nightly/catch_type_errors.rs:11:30 + | +10 | #[catch(404)] + | ------------- required by a bound introduced by this call +11 | fn f2(_request: &Request) -> bool { + | ^^^^ the trait `Responder<'_, '_>` is not implemented for `bool` error[E0308]: mismatched types - --> $DIR/catch_type_errors.rs:16:17 + --> tests/ui-fail-nightly/catch_type_errors.rs:16:17 | 16 | fn f3(_request: bool) -> usize { | ^^^^ expected `bool`, found `&rocket::Request<'_>` error[E0277]: the trait bound `usize: Responder<'_, '_>` is not satisfied - --> $DIR/catch_type_errors.rs:16:26 - | -16 | fn f3(_request: bool) -> usize { - | ^^^^^ the trait `Responder<'_, '_>` is not implemented for `usize` - | -note: required by `respond_to` - --> $DIR/responder.rs:298:5 - | -298 | fn respond_to(self, request: &'r Request<'_>) -> response::Result<'o>; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + --> tests/ui-fail-nightly/catch_type_errors.rs:16:26 + | +15 | #[catch(404)] + | ------------- required by a bound introduced by this call +16 | fn f3(_request: bool) -> usize { + | ^^^^^ the trait `Responder<'_, '_>` is not implemented for `usize` error[E0277]: the trait bound `usize: Responder<'_, '_>` is not satisfied - --> $DIR/catch_type_errors.rs:21:12 - | -21 | fn f4() -> usize { - | ^^^^^ the trait `Responder<'_, '_>` is not implemented for `usize` - | -note: required by `respond_to` - --> $DIR/responder.rs:298:5 - | -298 | fn respond_to(self, request: &'r Request<'_>) -> response::Result<'o>; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + --> tests/ui-fail-nightly/catch_type_errors.rs:21:12 + | +20 | #[catch(404)] + | ------------- required by a bound introduced by this call +21 | fn f4() -> usize { + | ^^^^^ the trait `Responder<'_, '_>` is not implemented for `usize` diff --git a/core/codegen/tests/ui-fail-nightly/catchers.stderr b/core/codegen/tests/ui-fail-nightly/catchers.stderr index 59f164bb24..518a3f645f 100644 --- a/core/codegen/tests/ui-fail-nightly/catchers.stderr +++ b/core/codegen/tests/ui-fail-nightly/catchers.stderr @@ -1,17 +1,17 @@ error: expected `,` - --> $DIR/catchers.rs:4:25 + --> tests/ui-fail-nightly/catchers.rs:4:25 | 4 | let _ = catchers![a b]; | ^ error: expected identifier - --> $DIR/catchers.rs:6:26 + --> tests/ui-fail-nightly/catchers.rs:6:26 | 6 | let _ = catchers![a::, ]; | ^ error: unexpected end of input, expected identifier - --> $DIR/catchers.rs:7:13 + --> tests/ui-fail-nightly/catchers.rs:7:13 | 7 | let _ = catchers![a::]; | ^^^^^^^^^^^^^^ diff --git a/core/codegen/tests/ui-fail-nightly/from_form.stderr b/core/codegen/tests/ui-fail-nightly/from_form.stderr index 508ace0fed..a48feda3c9 100644 --- a/core/codegen/tests/ui-fail-nightly/from_form.stderr +++ b/core/codegen/tests/ui-fail-nightly/from_form.stderr @@ -1,222 +1,222 @@ error: enums are not supported - --> $DIR/from_form.rs:4:1 + --> tests/ui-fail-nightly/from_form.rs:4:1 | 4 | enum Thing { } | ^^^^^^^^^^^^^^ | note: error occurred while deriving `FromForm` - --> $DIR/from_form.rs:3:10 + --> tests/ui-fail-nightly/from_form.rs:3:10 | 3 | #[derive(FromForm)] | ^^^^^^^^ = note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info) error: at least one field is required - --> $DIR/from_form.rs:7:1 + --> tests/ui-fail-nightly/from_form.rs:7:1 | 7 | struct Foo1; | ^^^^^^^^^^^^ | note: error occurred while deriving `FromForm` - --> $DIR/from_form.rs:6:10 + --> tests/ui-fail-nightly/from_form.rs:6:10 | 6 | #[derive(FromForm)] | ^^^^^^^^ = note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info) error: at least one field is required - --> $DIR/from_form.rs:10:13 + --> tests/ui-fail-nightly/from_form.rs:10:13 | 10 | struct Foo2 { } | ^^^^ | note: error occurred while deriving `FromForm` - --> $DIR/from_form.rs:9:10 + --> tests/ui-fail-nightly/from_form.rs:9:10 | 9 | #[derive(FromForm)] | ^^^^^^^^ = note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info) error: tuple struct must have exactly one field - --> $DIR/from_form.rs:16:12 + --> tests/ui-fail-nightly/from_form.rs:16:12 | 16 | struct Foo4(usize, usize, usize); | ^^^^^^^^^^^^^^^^^^^^^ | note: error occurred while deriving `FromForm` - --> $DIR/from_form.rs:15:10 + --> tests/ui-fail-nightly/from_form.rs:15:10 | 15 | #[derive(FromForm)] | ^^^^^^^^ = note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info) error: only one lifetime is supported - --> $DIR/from_form.rs:19:25 + --> tests/ui-fail-nightly/from_form.rs:19:25 | 19 | struct NextTodoTask<'f, 'a> { | ^^ | note: error occurred while deriving `FromForm` - --> $DIR/from_form.rs:18:10 + --> tests/ui-fail-nightly/from_form.rs:18:10 | 18 | #[derive(FromForm)] | ^^^^^^^^ = note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info) error: invalid form field name - --> $DIR/from_form.rs:28:20 + --> tests/ui-fail-nightly/from_form.rs:28:20 | 28 | #[field(name = "isindex")] | ^^^^^^^^^ | = help: field name cannot be `isindex` or contain '&', '=', '?', '.', '[', ']' note: error occurred while deriving `FromForm` - --> $DIR/from_form.rs:26:10 + --> tests/ui-fail-nightly/from_form.rs:26:10 | 26 | #[derive(FromForm)] | ^^^^^^^^ = note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info) error: field name conflicts with previous name - --> $DIR/from_form.rs:36:5 + --> tests/ui-fail-nightly/from_form.rs:36:5 | 36 | foo: usize, | ^^^ | help: declared in this field - --> $DIR/from_form.rs:36:5 + --> tests/ui-fail-nightly/from_form.rs:36:5 | 36 | foo: usize, | ^^^^^^^^^^ note: previous field with conflicting name - --> $DIR/from_form.rs:34:5 + --> tests/ui-fail-nightly/from_form.rs:34:5 | 34 | / #[field(name = "foo")] 35 | | field: String, | |_________________^ note: error occurred while deriving `FromForm` - --> $DIR/from_form.rs:32:10 + --> tests/ui-fail-nightly/from_form.rs:32:10 | 32 | #[derive(FromForm)] | ^^^^^^^^ = note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info) error: field name conflicts with previous name - --> $DIR/from_form.rs:43:20 + --> tests/ui-fail-nightly/from_form.rs:43:20 | 43 | #[field(name = "hello")] | ^^^^^^^ | help: declared in this field - --> $DIR/from_form.rs:43:5 + --> tests/ui-fail-nightly/from_form.rs:43:5 | 43 | / #[field(name = "hello")] 44 | | other: String, | |_________________^ note: previous field with conflicting name - --> $DIR/from_form.rs:41:5 + --> tests/ui-fail-nightly/from_form.rs:41:5 | 41 | / #[field(name = "hello")] 42 | | first: String, | |_________________^ note: error occurred while deriving `FromForm` - --> $DIR/from_form.rs:39:10 + --> tests/ui-fail-nightly/from_form.rs:39:10 | 39 | #[derive(FromForm)] | ^^^^^^^^ = note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info) error: field name conflicts with previous name - --> $DIR/from_form.rs:50:20 + --> tests/ui-fail-nightly/from_form.rs:50:20 | 50 | #[field(name = "first")] | ^^^^^^^ | help: declared in this field - --> $DIR/from_form.rs:50:5 + --> tests/ui-fail-nightly/from_form.rs:50:5 | 50 | / #[field(name = "first")] 51 | | other: String, | |_________________^ note: previous field with conflicting name - --> $DIR/from_form.rs:49:5 + --> tests/ui-fail-nightly/from_form.rs:49:5 | 49 | first: String, | ^^^^^^^^^^^^^ note: error occurred while deriving `FromForm` - --> $DIR/from_form.rs:47:10 + --> tests/ui-fail-nightly/from_form.rs:47:10 | 47 | #[derive(FromForm)] | ^^^^^^^^ = note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info) error: unexpected attribute parameter: `field` - --> $DIR/from_form.rs:56:28 + --> tests/ui-fail-nightly/from_form.rs:56:28 | 56 | #[field(name = "blah", field = "bloo")] | ^^^^^^^^^^^^^^ | note: error occurred while deriving `FromForm` - --> $DIR/from_form.rs:54:10 + --> tests/ui-fail-nightly/from_form.rs:54:10 | 54 | #[derive(FromForm)] | ^^^^^^^^ = note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info) error: expected list `#[field(..)]`, found bare path "field" - --> $DIR/from_form.rs:62:7 + --> tests/ui-fail-nightly/from_form.rs:62:7 | 62 | #[field] | ^^^^^ | note: error occurred while deriving `FromForm` - --> $DIR/from_form.rs:60:10 + --> tests/ui-fail-nightly/from_form.rs:60:10 | 60 | #[derive(FromForm)] | ^^^^^^^^ = note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info) error: expected key/value `key = value` - --> $DIR/from_form.rs:68:13 + --> tests/ui-fail-nightly/from_form.rs:68:13 | 68 | #[field("blah")] | ^^^^^^ | note: error occurred while deriving `FromForm` - --> $DIR/from_form.rs:66:10 + --> tests/ui-fail-nightly/from_form.rs:66:10 | 66 | #[derive(FromForm)] | ^^^^^^^^ = note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info) error: expected key/value `key = value` - --> $DIR/from_form.rs:74:13 + --> tests/ui-fail-nightly/from_form.rs:74:13 | 74 | #[field(123)] | ^^^ | note: error occurred while deriving `FromForm` - --> $DIR/from_form.rs:72:10 + --> tests/ui-fail-nightly/from_form.rs:72:10 | 72 | #[derive(FromForm)] | ^^^^^^^^ = note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info) error: unexpected attribute parameter: `beep` - --> $DIR/from_form.rs:80:13 + --> tests/ui-fail-nightly/from_form.rs:80:13 | 80 | #[field(beep = "bop")] | ^^^^^^^^^^^^ | note: error occurred while deriving `FromForm` - --> $DIR/from_form.rs:78:10 + --> tests/ui-fail-nightly/from_form.rs:78:10 | 78 | #[derive(FromForm)] | ^^^^^^^^ = note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info) error: field has conflicting names - --> $DIR/from_form.rs:86:5 + --> tests/ui-fail-nightly/from_form.rs:86:5 | 86 | / #[field(name = "blah")] 87 | | #[field(name = "blah")] @@ -224,218 +224,218 @@ error: field has conflicting names | |____________________^ | note: this field name... - --> $DIR/from_form.rs:86:20 + --> tests/ui-fail-nightly/from_form.rs:86:20 | 86 | #[field(name = "blah")] | ^^^^^^ note: ...conflicts with this field name - --> $DIR/from_form.rs:87:20 + --> tests/ui-fail-nightly/from_form.rs:87:20 | 87 | #[field(name = "blah")] | ^^^^^^ note: error occurred while deriving `FromForm` - --> $DIR/from_form.rs:84:10 + --> tests/ui-fail-nightly/from_form.rs:84:10 | 84 | #[derive(FromForm)] | ^^^^^^^^ = note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info) error: expected list `#[attr(..)]`, found bare boolean literal - --> $DIR/from_form.rs:93:20 + --> tests/ui-fail-nightly/from_form.rs:93:20 | 93 | #[field(name = true)] | ^^^^ | note: error occurred while deriving `FromForm` - --> $DIR/from_form.rs:91:10 + --> tests/ui-fail-nightly/from_form.rs:91:10 | 91 | #[derive(FromForm)] | ^^^^^^^^ = note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info) error: expected expression, found bare path "name" - --> $DIR/from_form.rs:99:13 + --> tests/ui-fail-nightly/from_form.rs:99:13 | 99 | #[field(name)] | ^^^^ | note: error occurred while deriving `FromForm` - --> $DIR/from_form.rs:97:10 + --> tests/ui-fail-nightly/from_form.rs:97:10 | 97 | #[derive(FromForm)] | ^^^^^^^^ = note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info) error: expected list `#[attr(..)]`, found bare integer literal - --> $DIR/from_form.rs:105:20 + --> tests/ui-fail-nightly/from_form.rs:105:20 | 105 | #[field(name = 123)] | ^^^ | note: error occurred while deriving `FromForm` - --> $DIR/from_form.rs:103:10 + --> tests/ui-fail-nightly/from_form.rs:103:10 | 103 | #[derive(FromForm)] | ^^^^^^^^ = note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info) error: invalid form field name - --> $DIR/from_form.rs:111:20 + --> tests/ui-fail-nightly/from_form.rs:111:20 | 111 | #[field(name = "hello&world")] | ^^^^^^^^^^^^^ | = help: field name cannot be `isindex` or contain '&', '=', '?', '.', '[', ']' note: error occurred while deriving `FromForm` - --> $DIR/from_form.rs:109:10 + --> tests/ui-fail-nightly/from_form.rs:109:10 | 109 | #[derive(FromForm)] | ^^^^^^^^ = note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info) error: invalid form field name - --> $DIR/from_form.rs:117:20 + --> tests/ui-fail-nightly/from_form.rs:117:20 | 117 | #[field(name = "!@#$%^&*()_")] | ^^^^^^^^^^^^^ | = help: field name cannot be `isindex` or contain '&', '=', '?', '.', '[', ']' note: error occurred while deriving `FromForm` - --> $DIR/from_form.rs:115:10 + --> tests/ui-fail-nightly/from_form.rs:115:10 | 115 | #[derive(FromForm)] | ^^^^^^^^ = note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info) error: invalid form field name - --> $DIR/from_form.rs:123:20 + --> tests/ui-fail-nightly/from_form.rs:123:20 | 123 | #[field(name = "?")] | ^^^ | = help: field name cannot be `isindex` or contain '&', '=', '?', '.', '[', ']' note: error occurred while deriving `FromForm` - --> $DIR/from_form.rs:121:10 + --> tests/ui-fail-nightly/from_form.rs:121:10 | 121 | #[derive(FromForm)] | ^^^^^^^^ = note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info) error: invalid form field name - --> $DIR/from_form.rs:129:20 + --> tests/ui-fail-nightly/from_form.rs:129:20 | 129 | #[field(name = "")] | ^^ | = help: field name cannot be `isindex` or contain '&', '=', '?', '.', '[', ']' note: error occurred while deriving `FromForm` - --> $DIR/from_form.rs:127:10 + --> tests/ui-fail-nightly/from_form.rs:127:10 | 127 | #[derive(FromForm)] | ^^^^^^^^ = note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info) error: invalid form field name - --> $DIR/from_form.rs:135:20 + --> tests/ui-fail-nightly/from_form.rs:135:20 | 135 | #[field(name = "a&b")] | ^^^^^ | = help: field name cannot be `isindex` or contain '&', '=', '?', '.', '[', ']' note: error occurred while deriving `FromForm` - --> $DIR/from_form.rs:133:10 + --> tests/ui-fail-nightly/from_form.rs:133:10 | 133 | #[derive(FromForm)] | ^^^^^^^^ = note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info) error: invalid form field name - --> $DIR/from_form.rs:141:20 + --> tests/ui-fail-nightly/from_form.rs:141:20 | 141 | #[field(name = "a=")] | ^^^^ | = help: field name cannot be `isindex` or contain '&', '=', '?', '.', '[', ']' note: error occurred while deriving `FromForm` - --> $DIR/from_form.rs:139:10 + --> tests/ui-fail-nightly/from_form.rs:139:10 | 139 | #[derive(FromForm)] | ^^^^^^^^ = note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info) error: duplicate attribute parameter: default - --> $DIR/from_form.rs:177:26 + --> tests/ui-fail-nightly/from_form.rs:177:26 | 177 | #[field(default = 1, default = 2)] | ^^^^^^^^^^^ | note: error occurred while deriving `FromForm` - --> $DIR/from_form.rs:175:10 + --> tests/ui-fail-nightly/from_form.rs:175:10 | 175 | #[derive(FromForm)] | ^^^^^^^^ = note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info) error: duplicate default field expression - --> $DIR/from_form.rs:184:23 + --> tests/ui-fail-nightly/from_form.rs:184:23 | 184 | #[field(default = 2)] | ^ | = help: at most one `default` or `default_with` is allowed note: error occurred while deriving `FromForm` - --> $DIR/from_form.rs:181:10 + --> tests/ui-fail-nightly/from_form.rs:181:10 | 181 | #[derive(FromForm)] | ^^^^^^^^ = note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info) error: duplicate default expressions - --> $DIR/from_form.rs:190:23 + --> tests/ui-fail-nightly/from_form.rs:190:23 | 190 | #[field(default = 1, default_with = None)] | ^ | = help: only one of `default` or `default_with` must be used note: other default expression is here - --> $DIR/from_form.rs:190:41 + --> tests/ui-fail-nightly/from_form.rs:190:41 | 190 | #[field(default = 1, default_with = None)] | ^^^^ note: error occurred while deriving `FromForm` - --> $DIR/from_form.rs:188:10 + --> tests/ui-fail-nightly/from_form.rs:188:10 | 188 | #[derive(FromForm)] | ^^^^^^^^ = note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info) error: duplicate default expressions - --> $DIR/from_form.rs:197:23 + --> tests/ui-fail-nightly/from_form.rs:197:23 | 197 | #[field(default = 1)] | ^ | = help: only one of `default` or `default_with` must be used note: other default expression is here - --> $DIR/from_form.rs:196:28 + --> tests/ui-fail-nightly/from_form.rs:196:28 | 196 | #[field(default_with = None)] | ^^^^ note: error occurred while deriving `FromForm` - --> $DIR/from_form.rs:194:10 + --> tests/ui-fail-nightly/from_form.rs:194:10 | 194 | #[derive(FromForm)] | ^^^^^^^^ = note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0425]: cannot find function `unknown` in this scope - --> $DIR/from_form.rs:153:24 + --> tests/ui-fail-nightly/from_form.rs:153:24 | 153 | #[field(validate = unknown())] | ^^^^^^^ not found in this scope error[E0308]: mismatched types - --> $DIR/from_form.rs:147:24 + --> tests/ui-fail-nightly/from_form.rs:147:24 | 147 | #[field(validate = 123)] | -------- ^^^ expected enum `Result`, found integer @@ -446,7 +446,7 @@ error[E0308]: mismatched types found type `{integer}` error[E0308]: mismatched types - --> $DIR/from_form.rs:160:12 + --> tests/ui-fail-nightly/from_form.rs:160:12 | 160 | first: String, | ^^^^^^ expected enum `TempFile`, found struct `std::string::String` @@ -455,7 +455,7 @@ error[E0308]: mismatched types found reference `&std::string::String` error[E0308]: mismatched types - --> $DIR/from_form.rs:166:12 + --> tests/ui-fail-nightly/from_form.rs:166:12 | 166 | first: String, | ^^^^^^ expected enum `TempFile`, found struct `std::string::String` @@ -464,13 +464,13 @@ error[E0308]: mismatched types found reference `&std::string::String` error[E0308]: mismatched types - --> $DIR/from_form.rs:165:28 + --> tests/ui-fail-nightly/from_form.rs:165:28 | 165 | #[field(validate = ext("hello"))] | ^^^^^^^ expected struct `ContentType`, found `&str` error[E0277]: the trait bound `i32: From<&str>` is not satisfied - --> $DIR/from_form.rs:171:23 + --> tests/ui-fail-nightly/from_form.rs:171:23 | 171 | #[field(default = "no conversion")] | ^^^^^^^^^^^^^^^ the trait `From<&str>` is not implemented for `i32` @@ -484,7 +484,7 @@ error[E0277]: the trait bound `i32: From<&str>` is not satisfied = note: required because of the requirements on the impl of `Into` for `&str` error[E0308]: mismatched types - --> $DIR/from_form.rs:203:33 + --> tests/ui-fail-nightly/from_form.rs:203:33 | 203 | #[field(default_with = Some("hi"))] | ^^^^ expected struct `std::string::String`, found `&str` diff --git a/core/codegen/tests/ui-fail-nightly/from_form_field.stderr b/core/codegen/tests/ui-fail-nightly/from_form_field.stderr index 270f8e14aa..806e37b8c6 100644 --- a/core/codegen/tests/ui-fail-nightly/from_form_field.stderr +++ b/core/codegen/tests/ui-fail-nightly/from_form_field.stderr @@ -1,31 +1,31 @@ error: tuple structs are not supported - --> $DIR/from_form_field.rs:4:1 + --> tests/ui-fail-nightly/from_form_field.rs:4:1 | 4 | struct Foo1; | ^^^^^^^^^^^^ | note: error occurred while deriving `FromFormField` - --> $DIR/from_form_field.rs:3:10 + --> tests/ui-fail-nightly/from_form_field.rs:3:10 | 3 | #[derive(FromFormField)] | ^^^^^^^^^^^^^ = note: this error originates in the derive macro `FromFormField` (in Nightly builds, run with -Z macro-backtrace for more info) error: tuple structs are not supported - --> $DIR/from_form_field.rs:7:1 + --> tests/ui-fail-nightly/from_form_field.rs:7:1 | 7 | struct Foo2(usize); | ^^^^^^^^^^^^^^^^^^^ | note: error occurred while deriving `FromFormField` - --> $DIR/from_form_field.rs:6:10 + --> tests/ui-fail-nightly/from_form_field.rs:6:10 | 6 | #[derive(FromFormField)] | ^^^^^^^^^^^^^ = note: this error originates in the derive macro `FromFormField` (in Nightly builds, run with -Z macro-backtrace for more info) error: named structs are not supported - --> $DIR/from_form_field.rs:10:1 + --> tests/ui-fail-nightly/from_form_field.rs:10:1 | 10 | / struct Foo3 { 11 | | foo: usize, @@ -33,79 +33,79 @@ error: named structs are not supported | |_^ | note: error occurred while deriving `FromFormField` - --> $DIR/from_form_field.rs:9:10 + --> tests/ui-fail-nightly/from_form_field.rs:9:10 | 9 | #[derive(FromFormField)] | ^^^^^^^^^^^^^ = note: this error originates in the derive macro `FromFormField` (in Nightly builds, run with -Z macro-backtrace for more info) error: variants cannot have fields - --> $DIR/from_form_field.rs:16:6 + --> tests/ui-fail-nightly/from_form_field.rs:16:6 | 16 | A(usize), | ^^^^^^^ | note: error occurred while deriving `FromFormField` - --> $DIR/from_form_field.rs:14:10 + --> tests/ui-fail-nightly/from_form_field.rs:14:10 | 14 | #[derive(FromFormField)] | ^^^^^^^^^^^^^ = note: this error originates in the derive macro `FromFormField` (in Nightly builds, run with -Z macro-backtrace for more info) error: enum must have at least one variant - --> $DIR/from_form_field.rs:20:1 + --> tests/ui-fail-nightly/from_form_field.rs:20:1 | 20 | enum Foo5 { } | ^^^^^^^^^^^^^ | note: error occurred while deriving `FromFormField` - --> $DIR/from_form_field.rs:19:10 + --> tests/ui-fail-nightly/from_form_field.rs:19:10 | 19 | #[derive(FromFormField)] | ^^^^^^^^^^^^^ = note: this error originates in the derive macro `FromFormField` (in Nightly builds, run with -Z macro-backtrace for more info) error: type generics are not supported - --> $DIR/from_form_field.rs:23:11 + --> tests/ui-fail-nightly/from_form_field.rs:23:11 | 23 | enum Foo6 { | ^ | note: error occurred while deriving `FromFormField` - --> $DIR/from_form_field.rs:22:10 + --> tests/ui-fail-nightly/from_form_field.rs:22:10 | 22 | #[derive(FromFormField)] | ^^^^^^^^^^^^^ = note: this error originates in the derive macro `FromFormField` (in Nightly builds, run with -Z macro-backtrace for more info) error: invalid value: expected string literal - --> $DIR/from_form_field.rs:29:21 + --> tests/ui-fail-nightly/from_form_field.rs:29:21 | 29 | #[field(value = 123)] | ^^^ | note: error occurred while deriving `FromFormField` - --> $DIR/from_form_field.rs:27:10 + --> tests/ui-fail-nightly/from_form_field.rs:27:10 | 27 | #[derive(FromFormField)] | ^^^^^^^^^^^^^ = note: this error originates in the derive macro `FromFormField` (in Nightly builds, run with -Z macro-backtrace for more info) error: expected literal, found bare path "value" - --> $DIR/from_form_field.rs:35:13 + --> tests/ui-fail-nightly/from_form_field.rs:35:13 | 35 | #[field(value)] | ^^^^^ | note: error occurred while deriving `FromFormField` - --> $DIR/from_form_field.rs:33:10 + --> tests/ui-fail-nightly/from_form_field.rs:33:10 | 33 | #[derive(FromFormField)] | ^^^^^^^^^^^^^ = note: this error originates in the derive macro `FromFormField` (in Nightly builds, run with -Z macro-backtrace for more info) error: variant has conflicting values - --> $DIR/from_form_field.rs:41:5 + --> tests/ui-fail-nightly/from_form_field.rs:41:5 | 41 | / #[field(value = "bar")] 42 | | #[field(value = "bar")] @@ -113,73 +113,73 @@ error: variant has conflicting values | |_____^ | note: this value... - --> $DIR/from_form_field.rs:41:21 + --> tests/ui-fail-nightly/from_form_field.rs:41:21 | 41 | #[field(value = "bar")] | ^^^^^ note: ...conflicts with this value - --> $DIR/from_form_field.rs:42:21 + --> tests/ui-fail-nightly/from_form_field.rs:42:21 | 42 | #[field(value = "bar")] | ^^^^^ note: error occurred while deriving `FromFormField` - --> $DIR/from_form_field.rs:39:10 + --> tests/ui-fail-nightly/from_form_field.rs:39:10 | 39 | #[derive(FromFormField)] | ^^^^^^^^^^^^^ = note: this error originates in the derive macro `FromFormField` (in Nightly builds, run with -Z macro-backtrace for more info) error: field value conflicts with previous value - --> $DIR/from_form_field.rs:50:21 + --> tests/ui-fail-nightly/from_form_field.rs:50:21 | 50 | #[field(value = "BAr")] | ^^^^^ | help: ...declared in this variant - --> $DIR/from_form_field.rs:50:5 + --> tests/ui-fail-nightly/from_form_field.rs:50:5 | 50 | / #[field(value = "BAr")] 51 | | B, | |_____^ note: previous field with conflicting name - --> $DIR/from_form_field.rs:48:5 + --> tests/ui-fail-nightly/from_form_field.rs:48:5 | 48 | / #[field(value = "bar")] 49 | | A, | |_____^ note: error occurred while deriving `FromFormField` - --> $DIR/from_form_field.rs:46:10 + --> tests/ui-fail-nightly/from_form_field.rs:46:10 | 46 | #[derive(FromFormField)] | ^^^^^^^^^^^^^ = note: this error originates in the derive macro `FromFormField` (in Nightly builds, run with -Z macro-backtrace for more info) error: field value conflicts with previous value - --> $DIR/from_form_field.rs:57:21 + --> tests/ui-fail-nightly/from_form_field.rs:57:21 | 57 | #[field(value = "a")] | ^^^ | help: ...declared in this variant - --> $DIR/from_form_field.rs:57:5 + --> tests/ui-fail-nightly/from_form_field.rs:57:5 | 57 | / #[field(value = "a")] 58 | | B, | |_____^ note: previous field with conflicting name - --> $DIR/from_form_field.rs:56:5 + --> tests/ui-fail-nightly/from_form_field.rs:56:5 | 56 | A, | ^ note: error occurred while deriving `FromFormField` - --> $DIR/from_form_field.rs:54:10 + --> tests/ui-fail-nightly/from_form_field.rs:54:10 | 54 | #[derive(FromFormField)] | ^^^^^^^^^^^^^ = note: this error originates in the derive macro `FromFormField` (in Nightly builds, run with -Z macro-backtrace for more info) error: variant has conflicting values - --> $DIR/from_form_field.rs:80:5 + --> tests/ui-fail-nightly/from_form_field.rs:80:5 | 80 | / #[field(value = "FoO")] 81 | | #[field(value = "foo")] @@ -187,24 +187,24 @@ error: variant has conflicting values | |_____^ | note: this value... - --> $DIR/from_form_field.rs:80:21 + --> tests/ui-fail-nightly/from_form_field.rs:80:21 | 80 | #[field(value = "FoO")] | ^^^^^ note: ...conflicts with this value - --> $DIR/from_form_field.rs:81:21 + --> tests/ui-fail-nightly/from_form_field.rs:81:21 | 81 | #[field(value = "foo")] | ^^^^^ note: error occurred while deriving `FromFormField` - --> $DIR/from_form_field.rs:78:10 + --> tests/ui-fail-nightly/from_form_field.rs:78:10 | 78 | #[derive(FromFormField)] | ^^^^^^^^^^^^^ = note: this error originates in the derive macro `FromFormField` (in Nightly builds, run with -Z macro-backtrace for more info) error: field has conflicting names - --> $DIR/from_form_field.rs:87:5 + --> tests/ui-fail-nightly/from_form_field.rs:87:5 | 87 | / #[field(name = "foo")] 88 | | #[field(name = uncased("FOO"))] @@ -212,90 +212,90 @@ error: field has conflicting names | |_________________^ | note: this field name... - --> $DIR/from_form_field.rs:87:20 + --> tests/ui-fail-nightly/from_form_field.rs:87:20 | 87 | #[field(name = "foo")] | ^^^^^ note: ...conflicts with this field name - --> $DIR/from_form_field.rs:88:28 + --> tests/ui-fail-nightly/from_form_field.rs:88:28 | 88 | #[field(name = uncased("FOO"))] | ^^^^^ note: error occurred while deriving `FromForm` - --> $DIR/from_form_field.rs:85:10 + --> tests/ui-fail-nightly/from_form_field.rs:85:10 | 85 | #[derive(FromForm)] | ^^^^^^^^ = note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info) error: field name conflicts with previous name - --> $DIR/from_form_field.rs:96:20 + --> tests/ui-fail-nightly/from_form_field.rs:96:20 | 96 | #[field(name = "foo")] | ^^^^^ | help: declared in this field - --> $DIR/from_form_field.rs:96:5 + --> tests/ui-fail-nightly/from_form_field.rs:96:5 | 96 | / #[field(name = "foo")] 97 | | other: usize, | |________________^ note: previous field with conflicting name - --> $DIR/from_form_field.rs:94:5 + --> tests/ui-fail-nightly/from_form_field.rs:94:5 | 94 | / #[field(name = "foo")] 95 | | single: usize, | |_________________^ note: error occurred while deriving `FromForm` - --> $DIR/from_form_field.rs:92:10 + --> tests/ui-fail-nightly/from_form_field.rs:92:10 | 92 | #[derive(FromForm)] | ^^^^^^^^ = note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info) error: field name conflicts with previous name - --> $DIR/from_form_field.rs:104:5 + --> tests/ui-fail-nightly/from_form_field.rs:104:5 | 104 | hello_there: usize, | ^^^^^^^^^^^ | help: declared in this field - --> $DIR/from_form_field.rs:104:5 + --> tests/ui-fail-nightly/from_form_field.rs:104:5 | 104 | hello_there: usize, | ^^^^^^^^^^^^^^^^^^ note: previous field with conflicting name - --> $DIR/from_form_field.rs:102:5 + --> tests/ui-fail-nightly/from_form_field.rs:102:5 | 102 | / #[field(name = uncased("HELLO_THERE"))] 103 | | single: usize, | |_________________^ note: error occurred while deriving `FromForm` - --> $DIR/from_form_field.rs:100:10 + --> tests/ui-fail-nightly/from_form_field.rs:100:10 | 100 | #[derive(FromForm)] | ^^^^^^^^ = note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info) error: field name conflicts with previous name - --> $DIR/from_form_field.rs:111:5 + --> tests/ui-fail-nightly/from_form_field.rs:111:5 | 111 | hello_there: usize, | ^^^^^^^^^^^ | help: declared in this field - --> $DIR/from_form_field.rs:111:5 + --> tests/ui-fail-nightly/from_form_field.rs:111:5 | 111 | hello_there: usize, | ^^^^^^^^^^^^^^^^^^ note: previous field with conflicting name - --> $DIR/from_form_field.rs:109:5 + --> tests/ui-fail-nightly/from_form_field.rs:109:5 | 109 | / #[field(name = "hello_there")] 110 | | single: usize, | |_________________^ note: error occurred while deriving `FromForm` - --> $DIR/from_form_field.rs:107:10 + --> tests/ui-fail-nightly/from_form_field.rs:107:10 | 107 | #[derive(FromForm)] | ^^^^^^^^ diff --git a/core/codegen/tests/ui-fail-nightly/from_form_type_errors.stderr b/core/codegen/tests/ui-fail-nightly/from_form_type_errors.stderr index 4b5d39fb3f..37cdf3fa16 100644 --- a/core/codegen/tests/ui-fail-nightly/from_form_type_errors.stderr +++ b/core/codegen/tests/ui-fail-nightly/from_form_type_errors.stderr @@ -1,5 +1,5 @@ error[E0277]: the trait bound `Unknown: FromFormField<'_>` is not satisfied - --> $DIR/from_form_type_errors.rs:7:12 + --> tests/ui-fail-nightly/from_form_type_errors.rs:7:12 | 7 | field: Unknown, | ^^^^^^^ the trait `FromFormField<'_>` is not implemented for `Unknown` @@ -7,7 +7,7 @@ error[E0277]: the trait bound `Unknown: FromFormField<'_>` is not satisfied = note: required because of the requirements on the impl of `FromForm<'r>` for `Unknown` error[E0277]: the trait bound `Foo: FromFormField<'_>` is not satisfied - --> $DIR/from_form_type_errors.rs:14:12 + --> tests/ui-fail-nightly/from_form_type_errors.rs:14:12 | 14 | field: Foo, | ^^^^^^^^^^ the trait `FromFormField<'_>` is not implemented for `Foo` diff --git a/core/codegen/tests/ui-fail-nightly/responder-types.stderr b/core/codegen/tests/ui-fail-nightly/responder-types.stderr index 7fbba7e778..c37a38be73 100644 --- a/core/codegen/tests/ui-fail-nightly/responder-types.stderr +++ b/core/codegen/tests/ui-fail-nightly/responder-types.stderr @@ -1,77 +1,80 @@ error[E0277]: the trait bound `u8: Responder<'_, '_>` is not satisfied - --> $DIR/responder-types.rs:5:5 + --> tests/ui-fail-nightly/responder-types.rs:5:5 + | +5 | thing: u8, + | ^^^^^^^^^ the trait `Responder<'_, '_>` is not implemented for `u8` + +error[E0277]: the trait bound `Header<'_>: From` is not satisfied + --> tests/ui-fail-nightly/responder-types.rs:11:5 | -5 | thing: u8, - | ^^^^^^^^^ the trait `Responder<'_, '_>` is not implemented for `u8` +11 | other: u8, + | ^^^^^^^^^ the trait `From` is not implemented for `Header<'_>` | -note: required by `respond_to` - --> $DIR/responder.rs:298:5 + = help: the following implementations were found: + as From<&Cookie<'_>>> + as From<&ExpectCt>> + as From<&Frame>> + as From<&Hsts>> + and 8 others + = note: required because of the requirements on the impl of `Into>` for `u8` +note: required by a bound in `rocket::Response::<'r>::set_header` + --> $WORKSPACE/core/lib/src/response/response.rs | -298 | fn respond_to(self, request: &'r Request<'_>) -> response::Result<'o>; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | pub fn set_header<'h: 'r, H: Into>>(&mut self, header: H) -> bool { + | ^^^^^^^^^^^^^^^^ required by this bound in `rocket::Response::<'r>::set_header` -error[E0277]: the trait bound `Header<'_>: From` is not satisfied - --> $DIR/responder-types.rs:11:5 - | -11 | other: u8, - | ^^^^^^^^^ the trait `From` is not implemented for `Header<'_>` +error[E0277]: the trait bound `u8: Responder<'_, '_>` is not satisfied + --> tests/ui-fail-nightly/responder-types.rs:16:5 | - = help: the following implementations were found: - as From<&Cookie<'_>>> - as From<&ExpectCt>> - as From<&Frame>> - as From<&Hsts>> - and 8 others - = note: required because of the requirements on the impl of `Into>` for `u8` +16 | thing: u8, + | ^^^^^^^^^ the trait `Responder<'_, '_>` is not implemented for `u8` -error[E0277]: the trait bound `u8: Responder<'_, '_>` is not satisfied - --> $DIR/responder-types.rs:16:5 +error[E0277]: the trait bound `Header<'_>: From` is not satisfied + --> tests/ui-fail-nightly/responder-types.rs:17:5 | -16 | thing: u8, - | ^^^^^^^^^ the trait `Responder<'_, '_>` is not implemented for `u8` +17 | other: u8, + | ^^^^^^^^^ the trait `From` is not implemented for `Header<'_>` | -note: required by `respond_to` - --> $DIR/responder.rs:298:5 + = help: the following implementations were found: + as From<&Cookie<'_>>> + as From<&ExpectCt>> + as From<&Frame>> + as From<&Hsts>> + and 8 others + = note: required because of the requirements on the impl of `Into>` for `u8` +note: required by a bound in `rocket::Response::<'r>::set_header` + --> $WORKSPACE/core/lib/src/response/response.rs | -298 | fn respond_to(self, request: &'r Request<'_>) -> response::Result<'o>; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -error[E0277]: the trait bound `Header<'_>: From` is not satisfied - --> $DIR/responder-types.rs:17:5 - | -17 | other: u8, - | ^^^^^^^^^ the trait `From` is not implemented for `Header<'_>` - | - = help: the following implementations were found: - as From<&Cookie<'_>>> - as From<&ExpectCt>> - as From<&Frame>> - as From<&Hsts>> - and 8 others - = note: required because of the requirements on the impl of `Into>` for `u8` + | pub fn set_header<'h: 'r, H: Into>>(&mut self, header: H) -> bool { + | ^^^^^^^^^^^^^^^^ required by this bound in `rocket::Response::<'r>::set_header` error[E0277]: the trait bound `Header<'_>: From` is not satisfied - --> $DIR/responder-types.rs:24:5 - | -24 | then: String, - | ^^^^^^^^^^^^ the trait `From` is not implemented for `Header<'_>` - | - = help: the following implementations were found: - as From<&Cookie<'_>>> - as From<&ExpectCt>> - as From<&Frame>> - as From<&Hsts>> - and 8 others - = note: required because of the requirements on the impl of `Into>` for `std::string::String` + --> tests/ui-fail-nightly/responder-types.rs:24:5 + | +24 | then: String, + | ^^^^^^^^^^^^ the trait `From` is not implemented for `Header<'_>` + | + = help: the following implementations were found: + as From<&Cookie<'_>>> + as From<&ExpectCt>> + as From<&Frame>> + as From<&Hsts>> + and 8 others + = note: required because of the requirements on the impl of `Into>` for `std::string::String` +note: required by a bound in `rocket::Response::<'r>::set_header` + --> $WORKSPACE/core/lib/src/response/response.rs + | + | pub fn set_header<'h: 'r, H: Into>>(&mut self, header: H) -> bool { + | ^^^^^^^^^^^^^^^^ required by this bound in `rocket::Response::<'r>::set_header` error[E0277]: the trait bound `usize: Responder<'_, '_>` is not satisfied - --> $DIR/responder-types.rs:28:13 + --> tests/ui-fail-nightly/responder-types.rs:28:13 | 28 | fn foo() -> usize { 0 } | ^^^^^ the trait `Responder<'_, '_>` is not implemented for `usize` | -note: required by `route::handler::, Status, rocket::Data<'o>>>::from` - --> $DIR/handler.rs:188:5 +note: required by a bound in `route::handler::, Status, rocket::Data<'o>>>::from` + --> $WORKSPACE/core/lib/src/route/handler.rs | -188 | pub fn from>(req: &'r Request<'_>, responder: R) -> Outcome<'r> { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | pub fn from>(req: &'r Request<'_>, responder: R) -> Outcome<'r> { + | ^^^^^^^^^^^^^^^^^ required by this bound in `route::handler::, Status, rocket::Data<'o>>>::from` diff --git a/core/codegen/tests/ui-fail-nightly/responder.stderr b/core/codegen/tests/ui-fail-nightly/responder.stderr index 1a0ec92063..b211d1f2ac 100644 --- a/core/codegen/tests/ui-fail-nightly/responder.stderr +++ b/core/codegen/tests/ui-fail-nightly/responder.stderr @@ -1,141 +1,141 @@ error: need at least one field - --> $DIR/responder.rs:4:1 + --> tests/ui-fail-nightly/responder.rs:4:1 | 4 | struct Thing1; | ^^^^^^^^^^^^^^ | note: error occurred while deriving `Responder` - --> $DIR/responder.rs:3:10 + --> tests/ui-fail-nightly/responder.rs:3:10 | 3 | #[derive(Responder)] | ^^^^^^^^^ = note: this error originates in the derive macro `Responder` (in Nightly builds, run with -Z macro-backtrace for more info) error: need at least one field - --> $DIR/responder.rs:7:14 + --> tests/ui-fail-nightly/responder.rs:7:14 | 7 | struct Thing2(); | ^^ | note: error occurred while deriving `Responder` - --> $DIR/responder.rs:6:10 + --> tests/ui-fail-nightly/responder.rs:6:10 | 6 | #[derive(Responder)] | ^^^^^^^^^ = note: this error originates in the derive macro `Responder` (in Nightly builds, run with -Z macro-backtrace for more info) error: need at least one field - --> $DIR/responder.rs:13:12 + --> tests/ui-fail-nightly/responder.rs:13:12 | 13 | enum Foo { Bark, } | ^^^^ | note: error occurred while deriving `Responder` - --> $DIR/responder.rs:12:10 + --> tests/ui-fail-nightly/responder.rs:12:10 | 12 | #[derive(Responder)] | ^^^^^^^^^ = note: this error originates in the derive macro `Responder` (in Nightly builds, run with -Z macro-backtrace for more info) error: only one lifetime is supported - --> $DIR/responder.rs:16:14 + --> tests/ui-fail-nightly/responder.rs:16:14 | 16 | struct Thing4<'a, 'b>(&'a str, &'b str); | ^^^^^^^^ | note: error occurred while deriving `Responder` - --> $DIR/responder.rs:15:10 + --> tests/ui-fail-nightly/responder.rs:15:10 | 15 | #[derive(Responder)] | ^^^^^^^^^ = note: this error originates in the derive macro `Responder` (in Nightly builds, run with -Z macro-backtrace for more info) error: invalid or unknown content type - --> $DIR/responder.rs:25:27 + --> tests/ui-fail-nightly/responder.rs:25:27 | 25 | #[response(content_type = "")] | ^^ | note: error occurred while deriving `Responder` - --> $DIR/responder.rs:24:10 + --> tests/ui-fail-nightly/responder.rs:24:10 | 24 | #[derive(Responder)] | ^^^^^^^^^ = note: this error originates in the derive macro `Responder` (in Nightly builds, run with -Z macro-backtrace for more info) error: invalid or unknown content type - --> $DIR/responder.rs:29:27 + --> tests/ui-fail-nightly/responder.rs:29:27 | 29 | #[response(content_type = "idk")] | ^^^^^ | note: error occurred while deriving `Responder` - --> $DIR/responder.rs:28:10 + --> tests/ui-fail-nightly/responder.rs:28:10 | 28 | #[derive(Responder)] | ^^^^^^^^^ = note: this error originates in the derive macro `Responder` (in Nightly builds, run with -Z macro-backtrace for more info) error: invalid value: expected string literal - --> $DIR/responder.rs:33:27 + --> tests/ui-fail-nightly/responder.rs:33:27 | 33 | #[response(content_type = 100)] | ^^^ | note: error occurred while deriving `Responder` - --> $DIR/responder.rs:32:10 + --> tests/ui-fail-nightly/responder.rs:32:10 | 32 | #[derive(Responder)] | ^^^^^^^^^ = note: this error originates in the derive macro `Responder` (in Nightly builds, run with -Z macro-backtrace for more info) error: status must be in range [100, 599] - --> $DIR/responder.rs:37:21 + --> tests/ui-fail-nightly/responder.rs:37:21 | 37 | #[response(status = 8)] | ^ | note: error occurred while deriving `Responder` - --> $DIR/responder.rs:36:10 + --> tests/ui-fail-nightly/responder.rs:36:10 | 36 | #[derive(Responder)] | ^^^^^^^^^ = note: this error originates in the derive macro `Responder` (in Nightly builds, run with -Z macro-backtrace for more info) error: invalid value: expected unsigned integer literal - --> $DIR/responder.rs:41:21 + --> tests/ui-fail-nightly/responder.rs:41:21 | 41 | #[response(status = "404")] | ^^^^^ | note: error occurred while deriving `Responder` - --> $DIR/responder.rs:40:10 + --> tests/ui-fail-nightly/responder.rs:40:10 | 40 | #[derive(Responder)] | ^^^^^^^^^ = note: this error originates in the derive macro `Responder` (in Nightly builds, run with -Z macro-backtrace for more info) error: invalid value: expected unsigned integer literal - --> $DIR/responder.rs:45:21 + --> tests/ui-fail-nightly/responder.rs:45:21 | 45 | #[response(status = "404", content_type = "html")] | ^^^^^ | note: error occurred while deriving `Responder` - --> $DIR/responder.rs:44:10 + --> tests/ui-fail-nightly/responder.rs:44:10 | 44 | #[derive(Responder)] | ^^^^^^^^^ = note: this error originates in the derive macro `Responder` (in Nightly builds, run with -Z macro-backtrace for more info) error: invalid value: expected string literal - --> $DIR/responder.rs:49:41 + --> tests/ui-fail-nightly/responder.rs:49:41 | 49 | #[response(status = 404, content_type = 120)] | ^^^ | note: error occurred while deriving `Responder` - --> $DIR/responder.rs:48:10 + --> tests/ui-fail-nightly/responder.rs:48:10 | 48 | #[derive(Responder)] | ^^^^^^^^^ diff --git a/core/codegen/tests/ui-fail-nightly/route-attribute-general-syntax.stderr b/core/codegen/tests/ui-fail-nightly/route-attribute-general-syntax.stderr index b545a0cd61..1b0c39b1de 100644 --- a/core/codegen/tests/ui-fail-nightly/route-attribute-general-syntax.stderr +++ b/core/codegen/tests/ui-fail-nightly/route-attribute-general-syntax.stderr @@ -1,5 +1,5 @@ error: missing expected parameter: `uri` - --> $DIR/route-attribute-general-syntax.rs:4:1 + --> tests/ui-fail-nightly/route-attribute-general-syntax.rs:4:1 | 4 | #[get()] | ^^^^^^^^ @@ -7,7 +7,7 @@ error: missing expected parameter: `uri` = note: this error originates in the attribute macro `get` (in Nightly builds, run with -Z macro-backtrace for more info) error: expected `fn` - --> $DIR/route-attribute-general-syntax.rs:9:1 + --> tests/ui-fail-nightly/route-attribute-general-syntax.rs:9:1 | 9 | struct S; | ^^^^^^ @@ -15,7 +15,7 @@ error: expected `fn` = help: #[get] can only be used on functions error: expected `fn` - --> $DIR/route-attribute-general-syntax.rs:12:1 + --> tests/ui-fail-nightly/route-attribute-general-syntax.rs:12:1 | 12 | enum A { } | ^^^^ @@ -23,7 +23,7 @@ error: expected `fn` = help: #[get] can only be used on functions error: expected `fn` - --> $DIR/route-attribute-general-syntax.rs:15:1 + --> tests/ui-fail-nightly/route-attribute-general-syntax.rs:15:1 | 15 | trait Foo { } | ^^^^^ @@ -31,7 +31,7 @@ error: expected `fn` = help: #[get] can only be used on functions error: expected `fn` - --> $DIR/route-attribute-general-syntax.rs:18:1 + --> tests/ui-fail-nightly/route-attribute-general-syntax.rs:18:1 | 18 | impl S { } | ^^^^ @@ -39,37 +39,37 @@ error: expected `fn` = help: #[get] can only be used on functions error: expected key/value `key = value` - --> $DIR/route-attribute-general-syntax.rs:21:12 + --> tests/ui-fail-nightly/route-attribute-general-syntax.rs:21:12 | 21 | #[get("/", 123)] | ^^^ error: expected key/value `key = value` - --> $DIR/route-attribute-general-syntax.rs:24:12 + --> tests/ui-fail-nightly/route-attribute-general-syntax.rs:24:12 | 24 | #[get("/", "/")] | ^^^ error: unexpected keyed parameter: expected literal or identifier - --> $DIR/route-attribute-general-syntax.rs:27:7 + --> tests/ui-fail-nightly/route-attribute-general-syntax.rs:27:7 | 27 | #[get(data = "", "/")] | ^^^^^^^^^^^^^^ error: unexpected attribute parameter: `unknown` - --> $DIR/route-attribute-general-syntax.rs:30:12 + --> tests/ui-fail-nightly/route-attribute-general-syntax.rs:30:12 | 30 | #[get("/", unknown = "foo")] | ^^^^^^^^^^^^^^^ error: expected key/value `key = value` - --> $DIR/route-attribute-general-syntax.rs:33:12 + --> tests/ui-fail-nightly/route-attribute-general-syntax.rs:33:12 | 33 | #[get("/", ...)] | ^^^ error: handler arguments must be named - --> $DIR/route-attribute-general-syntax.rs:39:7 + --> tests/ui-fail-nightly/route-attribute-general-syntax.rs:39:7 | 39 | fn c1(_: usize) {} | ^^^^^^^^ @@ -77,103 +77,103 @@ error: handler arguments must be named = help: to name an ignored handler argument, use `_name` error: invalid value: expected string literal - --> $DIR/route-attribute-general-syntax.rs:43:7 + --> tests/ui-fail-nightly/route-attribute-general-syntax.rs:43:7 | 43 | #[get(100)] | ^^^ error: invalid value: expected string literal - --> $DIR/route-attribute-general-syntax.rs:46:7 + --> tests/ui-fail-nightly/route-attribute-general-syntax.rs:46:7 | 46 | #[get('/')] | ^^^ error: invalid value: expected integer literal - --> $DIR/route-attribute-general-syntax.rs:49:19 + --> tests/ui-fail-nightly/route-attribute-general-syntax.rs:49:19 | 49 | #[get("/", rank = "1")] | ^^^ error: invalid value: expected integer literal - --> $DIR/route-attribute-general-syntax.rs:52:19 + --> tests/ui-fail-nightly/route-attribute-general-syntax.rs:52:19 | 52 | #[get("/", rank = '1')] | ^^^ error: invalid or unknown media type - --> $DIR/route-attribute-general-syntax.rs:57:21 + --> tests/ui-fail-nightly/route-attribute-general-syntax.rs:57:21 | 57 | #[get("/", format = "applicationx-custom")] | ^^^^^^^^^^^^^^^^^^^^^ error: invalid or unknown media type - --> $DIR/route-attribute-general-syntax.rs:60:21 + --> tests/ui-fail-nightly/route-attribute-general-syntax.rs:60:21 | 60 | #[get("/", format = "")] | ^^ error: invalid or unknown media type - --> $DIR/route-attribute-general-syntax.rs:63:21 + --> tests/ui-fail-nightly/route-attribute-general-syntax.rs:63:21 | 63 | #[get("/", format = "//")] | ^^^^ error: invalid or unknown media type - --> $DIR/route-attribute-general-syntax.rs:66:21 + --> tests/ui-fail-nightly/route-attribute-general-syntax.rs:66:21 | 66 | #[get("/", format = "/")] | ^^^ error: invalid or unknown media type - --> $DIR/route-attribute-general-syntax.rs:69:21 + --> tests/ui-fail-nightly/route-attribute-general-syntax.rs:69:21 | 69 | #[get("/", format = "a/")] | ^^^^ error: invalid or unknown media type - --> $DIR/route-attribute-general-syntax.rs:72:21 + --> tests/ui-fail-nightly/route-attribute-general-syntax.rs:72:21 | 72 | #[get("/", format = "/a")] | ^^^^ error: invalid or unknown media type - --> $DIR/route-attribute-general-syntax.rs:75:21 + --> tests/ui-fail-nightly/route-attribute-general-syntax.rs:75:21 | 75 | #[get("/", format = "/a/")] | ^^^^^ error: invalid or unknown media type - --> $DIR/route-attribute-general-syntax.rs:78:21 + --> tests/ui-fail-nightly/route-attribute-general-syntax.rs:78:21 | 78 | #[get("/", format = "a/b/")] | ^^^^^^ error: invalid or unknown media type - --> $DIR/route-attribute-general-syntax.rs:81:21 + --> tests/ui-fail-nightly/route-attribute-general-syntax.rs:81:21 | 81 | #[get("/", format = "unknown")] | ^^^^^^^^^ error: invalid value: expected string literal - --> $DIR/route-attribute-general-syntax.rs:84:21 + --> tests/ui-fail-nightly/route-attribute-general-syntax.rs:84:21 | 84 | #[get("/", format = 12)] | ^^ error: invalid value: expected string literal - --> $DIR/route-attribute-general-syntax.rs:87:21 + --> tests/ui-fail-nightly/route-attribute-general-syntax.rs:87:21 | 87 | #[get("/", format = 'j')] | ^^^ error: invalid or unknown media type - --> $DIR/route-attribute-general-syntax.rs:90:21 + --> tests/ui-fail-nightly/route-attribute-general-syntax.rs:90:21 | 90 | #[get("/", format = "text//foo")] | ^^^^^^^^^^^ error: invalid HTTP method for route handlers - --> $DIR/route-attribute-general-syntax.rs:95:9 + --> tests/ui-fail-nightly/route-attribute-general-syntax.rs:95:9 | 95 | #[route(CONNECT, "/")] | ^^^^^^^ @@ -181,7 +181,7 @@ error: invalid HTTP method for route handlers = help: method must be one of: `GET`, `PUT`, `POST`, `DELETE`, `HEAD`, `PATCH`, `OPTIONS` error: invalid HTTP method - --> $DIR/route-attribute-general-syntax.rs:98:9 + --> tests/ui-fail-nightly/route-attribute-general-syntax.rs:98:9 | 98 | #[route(FIX, "/")] | ^^^ @@ -189,7 +189,7 @@ error: invalid HTTP method = help: method must be one of: `GET`, `PUT`, `POST`, `DELETE`, `HEAD`, `PATCH`, `OPTIONS` error: expected identifier, found string literal - --> $DIR/route-attribute-general-syntax.rs:101:9 + --> tests/ui-fail-nightly/route-attribute-general-syntax.rs:101:9 | 101 | #[route("hi", "/")] | ^^^^ @@ -197,7 +197,7 @@ error: expected identifier, found string literal = help: method must be one of: `GET`, `PUT`, `POST`, `DELETE`, `HEAD`, `PATCH`, `OPTIONS` error: expected identifier, found string literal - --> $DIR/route-attribute-general-syntax.rs:104:9 + --> tests/ui-fail-nightly/route-attribute-general-syntax.rs:104:9 | 104 | #[route("GET", "/")] | ^^^^^ @@ -205,7 +205,7 @@ error: expected identifier, found string literal = help: method must be one of: `GET`, `PUT`, `POST`, `DELETE`, `HEAD`, `PATCH`, `OPTIONS` error: expected identifier, found integer literal - --> $DIR/route-attribute-general-syntax.rs:107:9 + --> tests/ui-fail-nightly/route-attribute-general-syntax.rs:107:9 | 107 | #[route(120, "/")] | ^^^ diff --git a/core/codegen/tests/ui-fail-nightly/route-path-bad-syntax.stderr b/core/codegen/tests/ui-fail-nightly/route-path-bad-syntax.stderr index d5618e3e84..c59c4ebc5b 100644 --- a/core/codegen/tests/ui-fail-nightly/route-path-bad-syntax.stderr +++ b/core/codegen/tests/ui-fail-nightly/route-path-bad-syntax.stderr @@ -1,5 +1,5 @@ error: invalid route URI: expected token '/' but found 'a' at index 0 - --> $DIR/route-path-bad-syntax.rs:5:8 + --> tests/ui-fail-nightly/route-path-bad-syntax.rs:5:8 | 5 | #[get("a")] | ^ @@ -7,7 +7,7 @@ error: invalid route URI: expected token '/' but found 'a' at index 0 = help: expected URI in origin form: "/path/" error: invalid route URI: unexpected EOF: expected token '/' at index 0 - --> $DIR/route-path-bad-syntax.rs:8:8 + --> tests/ui-fail-nightly/route-path-bad-syntax.rs:8:8 | 8 | #[get("")] | ^ @@ -15,7 +15,7 @@ error: invalid route URI: unexpected EOF: expected token '/' at index 0 = help: expected URI in origin form: "/path/" error: invalid route URI: expected token '/' but found 'a' at index 0 - --> $DIR/route-path-bad-syntax.rs:11:8 + --> tests/ui-fail-nightly/route-path-bad-syntax.rs:11:8 | 11 | #[get("a/b/c")] | ^ @@ -23,7 +23,7 @@ error: invalid route URI: expected token '/' but found 'a' at index 0 = help: expected URI in origin form: "/path/" error: route URIs cannot contain empty segments - --> $DIR/route-path-bad-syntax.rs:14:10 + --> tests/ui-fail-nightly/route-path-bad-syntax.rs:14:10 | 14 | #[get("/a///b")] | ^^ @@ -31,7 +31,7 @@ error: route URIs cannot contain empty segments = note: expected "/a/b", found "/a///b" error: route URIs cannot contain empty segments - --> $DIR/route-path-bad-syntax.rs:17:13 + --> tests/ui-fail-nightly/route-path-bad-syntax.rs:17:13 | 17 | #[get("/?bat&&")] | ^^ @@ -39,7 +39,7 @@ error: route URIs cannot contain empty segments = note: expected "/?bat", found "/?bat&&" error: route URIs cannot contain empty segments - --> $DIR/route-path-bad-syntax.rs:20:13 + --> tests/ui-fail-nightly/route-path-bad-syntax.rs:20:13 | 20 | #[get("/?bat&&")] | ^^ @@ -47,7 +47,7 @@ error: route URIs cannot contain empty segments = note: expected "/?bat", found "/?bat&&" error: route URIs cannot contain empty segments - --> $DIR/route-path-bad-syntax.rs:23:12 + --> tests/ui-fail-nightly/route-path-bad-syntax.rs:23:12 | 23 | #[get("/a/b//")] | ^^ @@ -55,79 +55,79 @@ error: route URIs cannot contain empty segments = note: expected "/a/b", found "/a/b//" error: unused parameter - --> $DIR/route-path-bad-syntax.rs:42:10 + --> tests/ui-fail-nightly/route-path-bad-syntax.rs:42:10 | 42 | #[get("/")] | ^^^^ | note: expected argument named `name` here - --> $DIR/route-path-bad-syntax.rs:43:6 + --> tests/ui-fail-nightly/route-path-bad-syntax.rs:43:6 | 43 | fn h0(_name: usize) {} | ^^^^^^^^^^^^^^ error: unused parameter - --> $DIR/route-path-bad-syntax.rs:45:12 + --> tests/ui-fail-nightly/route-path-bad-syntax.rs:45:12 | 45 | #[get("/a?")] | ^ | note: expected argument named `r` here - --> $DIR/route-path-bad-syntax.rs:46:6 + --> tests/ui-fail-nightly/route-path-bad-syntax.rs:46:6 | 46 | fn h1() {} | ^^ error: unused parameter - --> $DIR/route-path-bad-syntax.rs:48:23 + --> tests/ui-fail-nightly/route-path-bad-syntax.rs:48:23 | 48 | #[post("/a", data = "")] | ^^^^ | note: expected argument named `test` here - --> $DIR/route-path-bad-syntax.rs:49:6 + --> tests/ui-fail-nightly/route-path-bad-syntax.rs:49:6 | 49 | fn h2() {} | ^^ error: unused parameter - --> $DIR/route-path-bad-syntax.rs:51:10 + --> tests/ui-fail-nightly/route-path-bad-syntax.rs:51:10 | 51 | #[get("/<_r>")] | ^^ | note: expected argument named `_r` here - --> $DIR/route-path-bad-syntax.rs:52:6 + --> tests/ui-fail-nightly/route-path-bad-syntax.rs:52:6 | 52 | fn h3() {} | ^^ error: unused parameter - --> $DIR/route-path-bad-syntax.rs:54:10 + --> tests/ui-fail-nightly/route-path-bad-syntax.rs:54:10 | 54 | #[get("/<_r>/")] | ^^ | note: expected argument named `_r` here - --> $DIR/route-path-bad-syntax.rs:55:6 + --> tests/ui-fail-nightly/route-path-bad-syntax.rs:55:6 | 55 | fn h4() {} | ^^ error: unused parameter - --> $DIR/route-path-bad-syntax.rs:54:15 + --> tests/ui-fail-nightly/route-path-bad-syntax.rs:54:15 | 54 | #[get("/<_r>/")] | ^ | note: expected argument named `b` here - --> $DIR/route-path-bad-syntax.rs:55:6 + --> tests/ui-fail-nightly/route-path-bad-syntax.rs:55:6 | 55 | fn h4() {} | ^^ error: invalid identifier: `foo_.` - --> $DIR/route-path-bad-syntax.rs:60:10 + --> tests/ui-fail-nightly/route-path-bad-syntax.rs:60:10 | 60 | #[get("/")] | ^^^^^ @@ -136,7 +136,7 @@ error: invalid identifier: `foo_.` = help: did you mean ``? error: invalid identifier: `foo*` - --> $DIR/route-path-bad-syntax.rs:63:10 + --> tests/ui-fail-nightly/route-path-bad-syntax.rs:63:10 | 63 | #[get("/")] | ^^^^ @@ -145,7 +145,7 @@ error: invalid identifier: `foo*` = help: did you mean ``? error: invalid identifier: `!` - --> $DIR/route-path-bad-syntax.rs:66:10 + --> tests/ui-fail-nightly/route-path-bad-syntax.rs:66:10 | 66 | #[get("/")] | ^ @@ -154,7 +154,7 @@ error: invalid identifier: `!` = help: did you mean ``? error: invalid identifier: `name>: $DIR/route-path-bad-syntax.rs:69:10 + --> tests/ui-fail-nightly/route-path-bad-syntax.rs:69:10 | 69 | #[get("/:")] | ^^^^^^^^^ @@ -163,7 +163,7 @@ error: invalid identifier: `name>:`? error: unexpected static parameter - --> $DIR/route-path-bad-syntax.rs:74:20 + --> tests/ui-fail-nightly/route-path-bad-syntax.rs:74:20 | 74 | #[get("/", data = "foo")] | ^^^ @@ -171,7 +171,7 @@ error: unexpected static parameter = help: parameter must be dynamic: `` error: parameter cannot be trailing - --> $DIR/route-path-bad-syntax.rs:77:20 + --> tests/ui-fail-nightly/route-path-bad-syntax.rs:77:20 | 77 | #[get("/", data = "")] | ^^^^^^^ @@ -179,7 +179,7 @@ error: parameter cannot be trailing = help: did you mean ``? warning: `segment` starts with `<` but does not end with `>` - --> $DIR/route-path-bad-syntax.rs:80:20 + --> tests/ui-fail-nightly/route-path-bad-syntax.rs:80:20 | 80 | #[get("/", data = "` = help: perhaps you meant the dynamic parameter ``? error: unexpected static parameter - --> $DIR/route-path-bad-syntax.rs:80:20 + --> tests/ui-fail-nightly/route-path-bad-syntax.rs:80:20 | 80 | #[get("/", data = "` error: invalid identifier: `test ` - --> $DIR/route-path-bad-syntax.rs:83:21 + --> tests/ui-fail-nightly/route-path-bad-syntax.rs:83:21 | 83 | #[get("/", data = "")] | ^^^^^ @@ -204,7 +204,7 @@ error: invalid identifier: `test ` = help: did you mean ``? error: handler arguments must be named - --> $DIR/route-path-bad-syntax.rs:89:7 + --> tests/ui-fail-nightly/route-path-bad-syntax.rs:89:7 | 89 | fn k0(_: usize) {} | ^^^^^^^^ @@ -212,13 +212,13 @@ error: handler arguments must be named = help: to name an ignored handler argument, use `_name` error: parameters cannot be empty - --> $DIR/route-path-bad-syntax.rs:93:9 + --> tests/ui-fail-nightly/route-path-bad-syntax.rs:93:9 | 93 | #[get("/<>")] | ^^ warning: `segment` starts with `<` but does not end with `>` - --> $DIR/route-path-bad-syntax.rs:96:9 + --> tests/ui-fail-nightly/route-path-bad-syntax.rs:96:9 | 96 | #[get("/<")] | ^^^^^ @@ -226,7 +226,7 @@ warning: `segment` starts with `<` but does not end with `>` = help: perhaps you meant the dynamic parameter ``? warning: `segment` starts with `<` but does not end with `>` - --> $DIR/route-path-bad-syntax.rs:99:9 + --> tests/ui-fail-nightly/route-path-bad-syntax.rs:99:9 | 99 | #[get("/<<<<")] | ^^^^^^^^ @@ -234,7 +234,7 @@ warning: `segment` starts with `<` but does not end with `>` = help: perhaps you meant the dynamic parameter ``? warning: `segment` starts with `<` but does not end with `>` - --> $DIR/route-path-bad-syntax.rs:102:9 + --> tests/ui-fail-nightly/route-path-bad-syntax.rs:102:9 | 102 | #[get("/<>name><")] | ^^^^^^^^ diff --git a/core/codegen/tests/ui-fail-nightly/route-type-errors.stderr b/core/codegen/tests/ui-fail-nightly/route-type-errors.stderr index 3bf72a2871..3d370408cc 100644 --- a/core/codegen/tests/ui-fail-nightly/route-type-errors.stderr +++ b/core/codegen/tests/ui-fail-nightly/route-type-errors.stderr @@ -1,29 +1,17 @@ error[E0277]: the trait bound `Q: FromParam<'_>` is not satisfied - --> $DIR/route-type-errors.rs:6:12 - | -6 | fn f0(foo: Q) {} - | ^ the trait `FromParam<'_>` is not implemented for `Q` - | -note: required by `from_param` - --> $DIR/from_param.rs:183:5 - | -183 | fn from_param(param: &'a str) -> Result; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + --> tests/ui-fail-nightly/route-type-errors.rs:6:12 + | +6 | fn f0(foo: Q) {} + | ^ the trait `FromParam<'_>` is not implemented for `Q` error[E0277]: the trait bound `Q: FromSegments<'_>` is not satisfied - --> $DIR/route-type-errors.rs:9:12 - | -9 | fn f1(foo: Q) {} - | ^ the trait `FromSegments<'_>` is not implemented for `Q` - | -note: required by `from_segments` - --> $DIR/from_param.rs:291:5 - | -291 | fn from_segments(segments: Segments<'r, Path>) -> Result; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + --> tests/ui-fail-nightly/route-type-errors.rs:9:12 + | +9 | fn f1(foo: Q) {} + | ^ the trait `FromSegments<'_>` is not implemented for `Q` error[E0277]: the trait bound `Q: FromFormField<'_>` is not satisfied - --> $DIR/route-type-errors.rs:12:12 + --> tests/ui-fail-nightly/route-type-errors.rs:12:12 | 12 | fn f2(foo: Q) {} | ^ the trait `FromFormField<'_>` is not implemented for `Q` @@ -31,7 +19,7 @@ error[E0277]: the trait bound `Q: FromFormField<'_>` is not satisfied = note: required because of the requirements on the impl of `FromForm<'_>` for `Q` error[E0277]: the trait bound `Q: FromFormField<'_>` is not satisfied - --> $DIR/route-type-errors.rs:15:12 + --> tests/ui-fail-nightly/route-type-errors.rs:15:12 | 15 | fn f3(foo: Q) {} | ^ the trait `FromFormField<'_>` is not implemented for `Q` @@ -39,73 +27,37 @@ error[E0277]: the trait bound `Q: FromFormField<'_>` is not satisfied = note: required because of the requirements on the impl of `FromForm<'_>` for `Q` error[E0277]: the trait bound `Q: FromData<'_>` is not satisfied - --> $DIR/route-type-errors.rs:18:12 - | -18 | fn f4(foo: Q) {} - | ^ the trait `FromData<'_>` is not implemented for `Q` - | -note: required by a bound in `rocket::data::FromData::from_data` - --> $DIR/from_data.rs:194:41 - | -194 | async fn from_data(req: &'r Request<'_>, data: Data<'r>) -> Outcome<'r, Self>; - | ^^ required by this bound in `rocket::data::FromData::from_data` + --> tests/ui-fail-nightly/route-type-errors.rs:18:12 + | +18 | fn f4(foo: Q) {} + | ^ the trait `FromData<'_>` is not implemented for `Q` error[E0277]: the trait bound `Q: FromRequest<'_>` is not satisfied - --> $DIR/route-type-errors.rs:21:10 - | -21 | fn f5(a: Q, foo: Q) {} - | ^ the trait `FromRequest<'_>` is not implemented for `Q` - | -note: required by a bound in `from_request` - --> $DIR/from_request.rs:388:48 - | -388 | async fn from_request(request: &'r Request<'_>) -> Outcome; - | ^^ required by this bound in `from_request` + --> tests/ui-fail-nightly/route-type-errors.rs:21:10 + | +21 | fn f5(a: Q, foo: Q) {} + | ^ the trait `FromRequest<'_>` is not implemented for `Q` error[E0277]: the trait bound `Q: FromParam<'_>` is not satisfied - --> $DIR/route-type-errors.rs:21:18 - | -21 | fn f5(a: Q, foo: Q) {} - | ^ the trait `FromParam<'_>` is not implemented for `Q` - | -note: required by `from_param` - --> $DIR/from_param.rs:183:5 - | -183 | fn from_param(param: &'a str) -> Result; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + --> tests/ui-fail-nightly/route-type-errors.rs:21:18 + | +21 | fn f5(a: Q, foo: Q) {} + | ^ the trait `FromParam<'_>` is not implemented for `Q` error[E0277]: the trait bound `Q: FromRequest<'_>` is not satisfied - --> $DIR/route-type-errors.rs:24:10 - | -24 | fn f6(a: Q, foo: Q, good: usize, bar: Q) {} - | ^ the trait `FromRequest<'_>` is not implemented for `Q` - | -note: required by a bound in `from_request` - --> $DIR/from_request.rs:388:48 - | -388 | async fn from_request(request: &'r Request<'_>) -> Outcome; - | ^^ required by this bound in `from_request` + --> tests/ui-fail-nightly/route-type-errors.rs:24:10 + | +24 | fn f6(a: Q, foo: Q, good: usize, bar: Q) {} + | ^ the trait `FromRequest<'_>` is not implemented for `Q` error[E0277]: the trait bound `Q: FromParam<'_>` is not satisfied - --> $DIR/route-type-errors.rs:24:18 - | -24 | fn f6(a: Q, foo: Q, good: usize, bar: Q) {} - | ^ the trait `FromParam<'_>` is not implemented for `Q` - | -note: required by `from_param` - --> $DIR/from_param.rs:183:5 - | -183 | fn from_param(param: &'a str) -> Result; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + --> tests/ui-fail-nightly/route-type-errors.rs:24:18 + | +24 | fn f6(a: Q, foo: Q, good: usize, bar: Q) {} + | ^ the trait `FromParam<'_>` is not implemented for `Q` error[E0277]: the trait bound `Q: FromParam<'_>` is not satisfied - --> $DIR/route-type-errors.rs:24:39 - | -24 | fn f6(a: Q, foo: Q, good: usize, bar: Q) {} - | ^ the trait `FromParam<'_>` is not implemented for `Q` - | -note: required by `from_param` - --> $DIR/from_param.rs:183:5 - | -183 | fn from_param(param: &'a str) -> Result; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + --> tests/ui-fail-nightly/route-type-errors.rs:24:39 + | +24 | fn f6(a: Q, foo: Q, good: usize, bar: Q) {} + | ^ the trait `FromParam<'_>` is not implemented for `Q` diff --git a/core/codegen/tests/ui-fail-nightly/route-warnings.stderr b/core/codegen/tests/ui-fail-nightly/route-warnings.stderr index cb36048fb2..ba8e465724 100644 --- a/core/codegen/tests/ui-fail-nightly/route-warnings.stderr +++ b/core/codegen/tests/ui-fail-nightly/route-warnings.stderr @@ -1,49 +1,49 @@ warning: 'application/x-custom' is not a known media type - --> $DIR/route-warnings.rs:7:21 + --> tests/ui-fail-nightly/route-warnings.rs:7:21 | 7 | #[get("/", format = "application/x-custom")] | ^^^^^^^^^^^^^^^^^^^^^^ warning: 'x-custom/plain' is not a known media type - --> $DIR/route-warnings.rs:10:21 + --> tests/ui-fail-nightly/route-warnings.rs:10:21 | 10 | #[get("/", format = "x-custom/plain")] | ^^^^^^^^^^^^^^^^ warning: 'x-custom/x-custom' is not a known media type - --> $DIR/route-warnings.rs:13:21 + --> tests/ui-fail-nightly/route-warnings.rs:13:21 | 13 | #[get("/", format = "x-custom/x-custom")] | ^^^^^^^^^^^^^^^^^^^ warning: `data` used with non-payload-supporting method - --> $DIR/route-warnings.rs:18:12 + --> tests/ui-fail-nightly/route-warnings.rs:18:12 | 18 | #[get("/", data = "<_foo>")] | ^^^^^^^^^^^^^^^ | note: 'GET' does not typically support payloads - --> $DIR/route-warnings.rs:18:3 + --> tests/ui-fail-nightly/route-warnings.rs:18:3 | 18 | #[get("/", data = "<_foo>")] | ^^^ = note: this warning originates in the attribute macro `get` (in Nightly builds, run with -Z macro-backtrace for more info) warning: `data` used with non-payload-supporting method - --> $DIR/route-warnings.rs:21:13 + --> tests/ui-fail-nightly/route-warnings.rs:21:13 | 21 | #[head("/", data = "<_foo>")] | ^^^^^^^^^^^^^^^ | note: 'HEAD' does not typically support payloads - --> $DIR/route-warnings.rs:21:3 + --> tests/ui-fail-nightly/route-warnings.rs:21:3 | 21 | #[head("/", data = "<_foo>")] | ^^^^ = note: this warning originates in the attribute macro `head` (in Nightly builds, run with -Z macro-backtrace for more info) error: checking for warnings! - --> $DIR/route-warnings.rs:25:5 + --> tests/ui-fail-nightly/route-warnings.rs:25:5 | 25 | compile_error!("checking for warnings!") | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/core/codegen/tests/ui-fail-nightly/routes.stderr b/core/codegen/tests/ui-fail-nightly/routes.stderr index 1ec99110b2..d034681d27 100644 --- a/core/codegen/tests/ui-fail-nightly/routes.stderr +++ b/core/codegen/tests/ui-fail-nightly/routes.stderr @@ -1,17 +1,17 @@ error: expected `,` - --> $DIR/routes.rs:4:23 + --> tests/ui-fail-nightly/routes.rs:4:23 | 4 | let _ = routes![a b]; | ^ error: expected identifier - --> $DIR/routes.rs:6:24 + --> tests/ui-fail-nightly/routes.rs:6:24 | 6 | let _ = routes![a::, ]; | ^ error: unexpected end of input, expected identifier - --> $DIR/routes.rs:7:13 + --> tests/ui-fail-nightly/routes.rs:7:13 | 7 | let _ = routes![a::]; | ^^^^^^^^^^^^ diff --git a/core/codegen/tests/ui-fail-nightly/typed-uri-bad-type.stderr b/core/codegen/tests/ui-fail-nightly/typed-uri-bad-type.stderr index d0f3135a09..7b7031fca8 100644 --- a/core/codegen/tests/ui-fail-nightly/typed-uri-bad-type.stderr +++ b/core/codegen/tests/ui-fail-nightly/typed-uri-bad-type.stderr @@ -1,281 +1,238 @@ -error[E0271]: type mismatch resolving `>::Error == &str` - --> $DIR/typed-uri-bad-type.rs:22:37 - | -22 | fn optionals(id: Option, name: Result) { } - | ^^^^^^^^^^^^^^^^^^^^ expected enum `Infallible`, found `&str` - error[E0277]: the trait bound `usize: FromUriParam` is not satisfied - --> $DIR/typed-uri-bad-type.rs:45:22 - | -45 | uri!(simple(id = "hi")); - | ^^^^ the trait `FromUriParam` is not implemented for `usize` - | - = help: the following implementations were found: - > - > - > -note: required by `from_uri_param` - --> $DIR/from_uri_param.rs:192:5 - | -192 | fn from_uri_param(param: T) -> Self::Target; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + --> tests/ui-fail-nightly/typed-uri-bad-type.rs:45:22 + | +45 | uri!(simple(id = "hi")); + | ^^^^ the trait `FromUriParam` is not implemented for `usize` + | + = help: the following implementations were found: + > + > + > error[E0277]: the trait bound `usize: FromUriParam` is not satisfied - --> $DIR/typed-uri-bad-type.rs:47:17 - | -47 | uri!(simple("hello")); - | ^^^^^^^ the trait `FromUriParam` is not implemented for `usize` - | - = help: the following implementations were found: - > - > - > -note: required by `from_uri_param` - --> $DIR/from_uri_param.rs:192:5 - | -192 | fn from_uri_param(param: T) -> Self::Target; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + --> tests/ui-fail-nightly/typed-uri-bad-type.rs:47:17 + | +47 | uri!(simple("hello")); + | ^^^^^^^ the trait `FromUriParam` is not implemented for `usize` + | + = help: the following implementations were found: + > + > + > error[E0277]: the trait bound `usize: FromUriParam` is not satisfied - --> $DIR/typed-uri-bad-type.rs:49:22 - | -49 | uri!(simple(id = 239239i64)); - | ^^^^^^^^^ the trait `FromUriParam` is not implemented for `usize` - | - = help: the following implementations were found: - > - > - > -note: required by `from_uri_param` - --> $DIR/from_uri_param.rs:192:5 - | -192 | fn from_uri_param(param: T) -> Self::Target; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + --> tests/ui-fail-nightly/typed-uri-bad-type.rs:49:22 + | +49 | uri!(simple(id = 239239i64)); + | ^^^^^^^^^ the trait `FromUriParam` is not implemented for `usize` + | + = help: the following implementations were found: + > + > + > error[E0277]: the trait bound `S: FromUriParam` is not satisfied - --> $DIR/typed-uri-bad-type.rs:51:30 - | -51 | uri!(not_uri_display(10, S)); - | ^ the trait `FromUriParam` is not implemented for `S` - | -note: required by `from_uri_param` - --> $DIR/from_uri_param.rs:192:5 - | -192 | fn from_uri_param(param: T) -> Self::Target; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + --> tests/ui-fail-nightly/typed-uri-bad-type.rs:51:30 + | +51 | uri!(not_uri_display(10, S)); + | ^ the trait `FromUriParam` is not implemented for `S` error[E0277]: the trait bound `i32: FromUriParam>` is not satisfied - --> $DIR/typed-uri-bad-type.rs:56:25 - | -56 | uri!(optionals(id = Some(10), name = Ok("bob".into()))); - | ^^^^^^^^ the trait `FromUriParam>` is not implemented for `i32` - | - = help: the following implementations were found: - > - > - > - = note: required because of the requirements on the impl of `FromUriParam>` for `std::option::Option` -note: required by `from_uri_param` - --> $DIR/from_uri_param.rs:192:5 - | -192 | fn from_uri_param(param: T) -> Self::Target; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + --> tests/ui-fail-nightly/typed-uri-bad-type.rs:56:25 + | +56 | uri!(optionals(id = Some(10), name = Ok("bob".into()))); + | ^^^^^^^^ the trait `FromUriParam>` is not implemented for `i32` + | + = help: the following implementations were found: + > + > + > + = note: required because of the requirements on the impl of `FromUriParam>` for `std::option::Option` error[E0277]: the trait bound `std::string::String: FromUriParam>` is not satisfied - --> $DIR/typed-uri-bad-type.rs:56:42 - | -56 | uri!(optionals(id = Some(10), name = Ok("bob".into()))); - | ^^^^^^^^^^^^^^^^ the trait `FromUriParam>` is not implemented for `std::string::String` - | - = help: the following implementations were found: - > - > - > - > - and 2 others - = note: required because of the requirements on the impl of `FromUriParam>` for `Result` -note: required by `from_uri_param` - --> $DIR/from_uri_param.rs:192:5 - | -192 | fn from_uri_param(param: T) -> Self::Target; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + --> tests/ui-fail-nightly/typed-uri-bad-type.rs:56:42 + | +56 | uri!(optionals(id = Some(10), name = Ok("bob".into()))); + | ^^^^^^^^^^^^^^^^ the trait `FromUriParam>` is not implemented for `std::string::String` + | + = help: the following implementations were found: + > + > + > + > + and 2 others + = note: required because of the requirements on the impl of `FromUriParam>` for `Result` error[E0277]: the trait bound `isize: FromUriParam` is not satisfied - --> $DIR/typed-uri-bad-type.rs:58:19 - | -58 | uri!(simple_q("hi")); - | ^^^^ the trait `FromUriParam` is not implemented for `isize` - | - = help: the following implementations were found: - > - > - > -note: required by `from_uri_param` - --> $DIR/from_uri_param.rs:192:5 - | -192 | fn from_uri_param(param: T) -> Self::Target; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + --> tests/ui-fail-nightly/typed-uri-bad-type.rs:58:19 + | +58 | uri!(simple_q("hi")); + | ^^^^ the trait `FromUriParam` is not implemented for `isize` + | + = help: the following implementations were found: + > + > + > error[E0277]: the trait bound `isize: FromUriParam` is not satisfied - --> $DIR/typed-uri-bad-type.rs:60:24 - | -60 | uri!(simple_q(id = "hi")); - | ^^^^ the trait `FromUriParam` is not implemented for `isize` - | - = help: the following implementations were found: - > - > - > -note: required by `from_uri_param` - --> $DIR/from_uri_param.rs:192:5 - | -192 | fn from_uri_param(param: T) -> Self::Target; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + --> tests/ui-fail-nightly/typed-uri-bad-type.rs:60:24 + | +60 | uri!(simple_q(id = "hi")); + | ^^^^ the trait `FromUriParam` is not implemented for `isize` + | + = help: the following implementations were found: + > + > + > error[E0277]: the trait bound `S: FromUriParam` is not satisfied - --> $DIR/typed-uri-bad-type.rs:62:23 - | -62 | uri!(other_q(100, S)); - | ^ the trait `FromUriParam` is not implemented for `S` - | -note: required by `from_uri_param` - --> $DIR/from_uri_param.rs:192:5 - | -192 | fn from_uri_param(param: T) -> Self::Target; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + --> tests/ui-fail-nightly/typed-uri-bad-type.rs:62:23 + | +62 | uri!(other_q(100, S)); + | ^ the trait `FromUriParam` is not implemented for `S` error[E0277]: the trait bound `S: FromUriParam` is not satisfied - --> $DIR/typed-uri-bad-type.rs:64:25 - | -64 | uri!(other_q(rest = S, id = 100)); - | ^ the trait `FromUriParam` is not implemented for `S` - | -note: required by `from_uri_param` - --> $DIR/from_uri_param.rs:192:5 - | -192 | fn from_uri_param(param: T) -> Self::Target; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + --> tests/ui-fail-nightly/typed-uri-bad-type.rs:64:25 + | +64 | uri!(other_q(rest = S, id = 100)); + | ^ the trait `FromUriParam` is not implemented for `S` error[E0277]: the trait bound `S: Ignorable` is not satisfied - --> $DIR/typed-uri-bad-type.rs:66:25 + --> tests/ui-fail-nightly/typed-uri-bad-type.rs:66:25 | 66 | uri!(other_q(rest = _, id = 100)); | ^ the trait `Ignorable` is not implemented for `S` | note: required by a bound in `assert_ignorable` - --> $DIR/uri_display.rs:544:37 + --> $WORKSPACE/core/http/src/uri/fmt/uri_display.rs | -544 | pub fn assert_ignorable>() { } + | pub fn assert_ignorable>() { } | ^^^^^^^^^^^^ required by this bound in `assert_ignorable` error[E0277]: the trait bound `usize: Ignorable` is not satisfied - --> $DIR/typed-uri-bad-type.rs:68:33 + --> tests/ui-fail-nightly/typed-uri-bad-type.rs:68:33 | 68 | uri!(other_q(rest = S, id = _)); | ^ the trait `Ignorable` is not implemented for `usize` | note: required by a bound in `assert_ignorable` - --> $DIR/uri_display.rs:544:37 + --> $WORKSPACE/core/http/src/uri/fmt/uri_display.rs | -544 | pub fn assert_ignorable>() { } + | pub fn assert_ignorable>() { } | ^^^^^^^^^^^^ required by this bound in `assert_ignorable` error[E0277]: the trait bound `S: FromUriParam` is not satisfied - --> $DIR/typed-uri-bad-type.rs:68:25 - | -68 | uri!(other_q(rest = S, id = _)); - | ^ the trait `FromUriParam` is not implemented for `S` - | -note: required by `from_uri_param` - --> $DIR/from_uri_param.rs:192:5 - | -192 | fn from_uri_param(param: T) -> Self::Target; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + --> tests/ui-fail-nightly/typed-uri-bad-type.rs:68:25 + | +68 | uri!(other_q(rest = S, id = _)); + | ^ the trait `FromUriParam` is not implemented for `S` error[E0277]: the trait bound `usize: FromUriParam` is not satisfied - --> $DIR/typed-uri-bad-type.rs:77:40 + --> tests/ui-fail-nightly/typed-uri-bad-type.rs:77:40 + | +77 | uri!(uri!("?foo#bar"), simple(id = "hi")); + | ^^^^ the trait `FromUriParam` is not implemented for `usize` + | + = help: the following implementations were found: + > + > + > + +error[E0277]: the trait bound `rocket::http::uri::Reference<'_>: ValidRoutePrefix` is not satisfied + --> tests/ui-fail-nightly/typed-uri-bad-type.rs:77:15 | 77 | uri!(uri!("?foo#bar"), simple(id = "hi")); - | ^^^^ the trait `FromUriParam` is not implemented for `usize` + | -----^^^^^^^^^^- + | | | + | | the trait `ValidRoutePrefix` is not implemented for `rocket::http::uri::Reference<'_>` + | required by a bound introduced by this call | - = help: the following implementations were found: - > - > - > -note: required by `from_uri_param` - --> $DIR/from_uri_param.rs:192:5 +note: required by a bound in `RouteUriBuilder::with_prefix` + --> $WORKSPACE/core/http/src/uri/fmt/formatter.rs | -192 | fn from_uri_param(param: T) -> Self::Target; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | pub fn with_prefix(self, p: P) -> PrefixedRouteUri { + | ^^^^^^^^^^^^^^^^ required by this bound in `RouteUriBuilder::with_prefix` -error[E0277]: the trait bound `rocket::http::uri::Reference<'_>: ValidRoutePrefix` is not satisfied - --> $DIR/typed-uri-bad-type.rs:77:15 +error[E0277]: the trait bound `usize: FromUriParam` is not satisfied + --> tests/ui-fail-nightly/typed-uri-bad-type.rs:78:33 | -77 | uri!(uri!("?foo#bar"), simple(id = "hi")); - | ^^^^^^^^^^ the trait `ValidRoutePrefix` is not implemented for `rocket::http::uri::Reference<'_>` +78 | uri!(uri!("*"), simple(id = "hi")); + | ^^^^ the trait `FromUriParam` is not implemented for `usize` + | + = help: the following implementations were found: + > + > + > -error[E0277]: the trait bound `usize: FromUriParam` is not satisfied - --> $DIR/typed-uri-bad-type.rs:78:33 +error[E0277]: the trait bound `rocket::http::uri::Asterisk: ValidRoutePrefix` is not satisfied + --> tests/ui-fail-nightly/typed-uri-bad-type.rs:78:15 | 78 | uri!(uri!("*"), simple(id = "hi")); - | ^^^^ the trait `FromUriParam` is not implemented for `usize` + | -----^^^- + | | | + | | the trait `ValidRoutePrefix` is not implemented for `rocket::http::uri::Asterisk` + | required by a bound introduced by this call | - = help: the following implementations were found: - > - > - > -note: required by `from_uri_param` - --> $DIR/from_uri_param.rs:192:5 +note: required by a bound in `RouteUriBuilder::with_prefix` + --> $WORKSPACE/core/http/src/uri/fmt/formatter.rs | -192 | fn from_uri_param(param: T) -> Self::Target; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | pub fn with_prefix(self, p: P) -> PrefixedRouteUri { + | ^^^^^^^^^^^^^^^^ required by this bound in `RouteUriBuilder::with_prefix` -error[E0277]: the trait bound `rocket::http::uri::Asterisk: ValidRoutePrefix` is not satisfied - --> $DIR/typed-uri-bad-type.rs:78:15 +error[E0277]: the trait bound `usize: FromUriParam` is not satisfied + --> tests/ui-fail-nightly/typed-uri-bad-type.rs:81:25 | -78 | uri!(uri!("*"), simple(id = "hi")); - | ^^^ the trait `ValidRoutePrefix` is not implemented for `rocket::http::uri::Asterisk` +81 | uri!(_, simple(id = "hi"), uri!("*")); + | ^^^^ the trait `FromUriParam` is not implemented for `usize` + | + = help: the following implementations were found: + > + > + > -error[E0277]: the trait bound `usize: FromUriParam` is not satisfied - --> $DIR/typed-uri-bad-type.rs:81:25 +error[E0277]: the trait bound `rocket::http::uri::Asterisk: ValidRouteSuffix>` is not satisfied + --> tests/ui-fail-nightly/typed-uri-bad-type.rs:81:37 | 81 | uri!(_, simple(id = "hi"), uri!("*")); - | ^^^^ the trait `FromUriParam` is not implemented for `usize` + | -----^^^- + | | | + | | the trait `ValidRouteSuffix>` is not implemented for `rocket::http::uri::Asterisk` + | required by a bound introduced by this call | - = help: the following implementations were found: - > - > - > -note: required by `from_uri_param` - --> $DIR/from_uri_param.rs:192:5 +note: required by a bound in `RouteUriBuilder::with_suffix` + --> $WORKSPACE/core/http/src/uri/fmt/formatter.rs | -192 | fn from_uri_param(param: T) -> Self::Target; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | where S: ValidRouteSuffix> + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `RouteUriBuilder::with_suffix` -error[E0277]: the trait bound `rocket::http::uri::Asterisk: ValidRouteSuffix>` is not satisfied - --> $DIR/typed-uri-bad-type.rs:81:37 +error[E0277]: the trait bound `usize: FromUriParam` is not satisfied + --> tests/ui-fail-nightly/typed-uri-bad-type.rs:82:25 | -81 | uri!(_, simple(id = "hi"), uri!("*")); - | ^^^ the trait `ValidRouteSuffix>` is not implemented for `rocket::http::uri::Asterisk` +82 | uri!(_, simple(id = "hi"), uri!("/foo/bar")); + | ^^^^ the trait `FromUriParam` is not implemented for `usize` + | + = help: the following implementations were found: + > + > + > -error[E0277]: the trait bound `usize: FromUriParam` is not satisfied - --> $DIR/typed-uri-bad-type.rs:82:25 +error[E0277]: the trait bound `rocket::http::uri::Origin<'_>: ValidRouteSuffix>` is not satisfied + --> tests/ui-fail-nightly/typed-uri-bad-type.rs:82:37 | 82 | uri!(_, simple(id = "hi"), uri!("/foo/bar")); - | ^^^^ the trait `FromUriParam` is not implemented for `usize` + | -----^^^^^^^^^^- + | | | + | | the trait `ValidRouteSuffix>` is not implemented for `rocket::http::uri::Origin<'_>` + | required by a bound introduced by this call | - = help: the following implementations were found: - > - > - > -note: required by `from_uri_param` - --> $DIR/from_uri_param.rs:192:5 +note: required by a bound in `RouteUriBuilder::with_suffix` + --> $WORKSPACE/core/http/src/uri/fmt/formatter.rs | -192 | fn from_uri_param(param: T) -> Self::Target; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | where S: ValidRouteSuffix> + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `RouteUriBuilder::with_suffix` -error[E0277]: the trait bound `rocket::http::uri::Origin<'_>: ValidRouteSuffix>` is not satisfied - --> $DIR/typed-uri-bad-type.rs:82:37 +error[E0271]: type mismatch resolving `>::Error == &str` + --> tests/ui-fail-nightly/typed-uri-bad-type.rs:22:37 | -82 | uri!(_, simple(id = "hi"), uri!("/foo/bar")); - | ^^^^^^^^^^ the trait `ValidRouteSuffix>` is not implemented for `rocket::http::uri::Origin<'_>` +22 | fn optionals(id: Option, name: Result) { } + | ^^^^^^^^^^^^^^^^^^^^ expected enum `Infallible`, found `&str` diff --git a/core/codegen/tests/ui-fail-nightly/typed-uris-bad-params.stderr b/core/codegen/tests/ui-fail-nightly/typed-uris-bad-params.stderr index bb655f68b9..9adb9936f0 100644 --- a/core/codegen/tests/ui-fail-nightly/typed-uris-bad-params.stderr +++ b/core/codegen/tests/ui-fail-nightly/typed-uris-bad-params.stderr @@ -1,11 +1,11 @@ error: expected identifier - --> $DIR/typed-uris-bad-params.rs:63:18 + --> tests/ui-fail-nightly/typed-uris-bad-params.rs:63:18 | 63 | uri!(ignored(_ = 10)); | ^ error: route expects 1 parameter but 2 were supplied - --> $DIR/typed-uris-bad-params.rs:69:18 + --> tests/ui-fail-nightly/typed-uris-bad-params.rs:69:18 | 69 | uri!(ignored(10, "10")); | ^^^^^^^^ @@ -13,7 +13,7 @@ error: route expects 1 parameter but 2 were supplied = note: route `ignored` has uri "/<_>" error: expected unnamed arguments due to ignored parameters - --> $DIR/typed-uris-bad-params.rs:67:18 + --> tests/ui-fail-nightly/typed-uris-bad-params.rs:67:18 | 67 | uri!(ignored(num = 10)); | ^^^^^^^^ @@ -21,7 +21,7 @@ error: expected unnamed arguments due to ignored parameters = note: uri for route `ignored` ignores path parameters: "/<_>" error: route expects 1 parameter but 2 were supplied - --> $DIR/typed-uris-bad-params.rs:65:18 + --> tests/ui-fail-nightly/typed-uris-bad-params.rs:65:18 | 65 | uri!(ignored(10, 20)); | ^^^^^^ @@ -29,25 +29,25 @@ error: route expects 1 parameter but 2 were supplied = note: route `ignored` has uri "/<_>" error: path parameters cannot be ignored - --> $DIR/typed-uris-bad-params.rs:61:18 + --> tests/ui-fail-nightly/typed-uris-bad-params.rs:61:18 | 61 | uri!(ignored(_)); | ^ error: path parameters cannot be ignored - --> $DIR/typed-uris-bad-params.rs:59:36 + --> tests/ui-fail-nightly/typed-uris-bad-params.rs:59:36 | 59 | uri!(optionals(id = 10, name = _)); | ^ error: path parameters cannot be ignored - --> $DIR/typed-uris-bad-params.rs:57:25 + --> tests/ui-fail-nightly/typed-uris-bad-params.rs:57:25 | 57 | uri!(optionals(id = _, name = "bob".into())); | ^ error: invalid parameters for `has_two` route uri - --> $DIR/typed-uris-bad-params.rs:55:18 + --> tests/ui-fail-nightly/typed-uris-bad-params.rs:55:18 | 55 | uri!(has_two(id = 100, cookies = "hi")); | ^^^^^^^^^^^^^^^^^^^^^^^^ @@ -55,13 +55,13 @@ error: invalid parameters for `has_two` route uri = note: uri parameters are: id: i32, name: String = help: missing parameter: `name` help: unknown parameter: `cookies` - --> $DIR/typed-uris-bad-params.rs:55:28 + --> tests/ui-fail-nightly/typed-uris-bad-params.rs:55:28 | 55 | uri!(has_two(id = 100, cookies = "hi")); | ^^^^^^^ error: invalid parameters for `has_two` route uri - --> $DIR/typed-uris-bad-params.rs:53:18 + --> tests/ui-fail-nightly/typed-uris-bad-params.rs:53:18 | 53 | uri!(has_two(cookies = "hi", id = 100, id = 10, id = 10)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -69,18 +69,18 @@ error: invalid parameters for `has_two` route uri = note: uri parameters are: id: i32, name: String = help: missing parameter: `name` help: unknown parameter: `cookies` - --> $DIR/typed-uris-bad-params.rs:53:18 + --> tests/ui-fail-nightly/typed-uris-bad-params.rs:53:18 | 53 | uri!(has_two(cookies = "hi", id = 100, id = 10, id = 10)); | ^^^^^^^ help: duplicate parameter: `id` - --> $DIR/typed-uris-bad-params.rs:53:44 + --> tests/ui-fail-nightly/typed-uris-bad-params.rs:53:44 | 53 | uri!(has_two(cookies = "hi", id = 100, id = 10, id = 10)); | ^^ ^^ error: invalid parameters for `has_two` route uri - --> $DIR/typed-uris-bad-params.rs:51:18 + --> tests/ui-fail-nightly/typed-uris-bad-params.rs:51:18 | 51 | uri!(has_two(name = "hi")); | ^^^^^^^^^^^ @@ -89,7 +89,7 @@ error: invalid parameters for `has_two` route uri = help: missing parameter: `id` error: invalid parameters for `has_two` route uri - --> $DIR/typed-uris-bad-params.rs:49:18 + --> tests/ui-fail-nightly/typed-uris-bad-params.rs:49:18 | 49 | uri!(has_two(id = 100, id = 100, )); | ^^^^^^^^^^^^^^^^^^^ @@ -97,39 +97,39 @@ error: invalid parameters for `has_two` route uri = note: uri parameters are: id: i32, name: String = help: missing parameter: `name` help: duplicate parameter: `id` - --> $DIR/typed-uris-bad-params.rs:49:28 + --> tests/ui-fail-nightly/typed-uris-bad-params.rs:49:28 | 49 | uri!(has_two(id = 100, id = 100, )); | ^^ error: invalid parameters for `has_one_guarded` route uri - --> $DIR/typed-uris-bad-params.rs:47:26 + --> tests/ui-fail-nightly/typed-uris-bad-params.rs:47:26 | 47 | uri!(has_one_guarded(id = 100, cookies = "hi")); | ^^^^^^^^^^^^^^^^^^^^^^^^ | = note: uri parameters are: id: i32 help: unknown parameter: `cookies` - --> $DIR/typed-uris-bad-params.rs:47:36 + --> tests/ui-fail-nightly/typed-uris-bad-params.rs:47:36 | 47 | uri!(has_one_guarded(id = 100, cookies = "hi")); | ^^^^^^^ error: invalid parameters for `has_one_guarded` route uri - --> $DIR/typed-uris-bad-params.rs:45:26 + --> tests/ui-fail-nightly/typed-uris-bad-params.rs:45:26 | 45 | uri!(has_one_guarded(cookies = "hi", id = 100)); | ^^^^^^^^^^^^^^^^^^^^^^^^ | = note: uri parameters are: id: i32 help: unknown parameter: `cookies` - --> $DIR/typed-uris-bad-params.rs:45:26 + --> tests/ui-fail-nightly/typed-uris-bad-params.rs:45:26 | 45 | uri!(has_one_guarded(cookies = "hi", id = 100)); | ^^^^^^^ error: invalid parameters for `has_one` route uri - --> $DIR/typed-uris-bad-params.rs:43:18 + --> tests/ui-fail-nightly/typed-uris-bad-params.rs:43:18 | 43 | uri!(has_one(name = "hi")); | ^^^^^^^^^^^ @@ -137,96 +137,96 @@ error: invalid parameters for `has_one` route uri = note: uri parameters are: id: i32 = help: missing parameter: `id` help: unknown parameter: `name` - --> $DIR/typed-uris-bad-params.rs:43:18 + --> tests/ui-fail-nightly/typed-uris-bad-params.rs:43:18 | 43 | uri!(has_one(name = "hi")); | ^^^^ error: invalid parameters for `has_one` route uri - --> $DIR/typed-uris-bad-params.rs:41:18 + --> tests/ui-fail-nightly/typed-uris-bad-params.rs:41:18 | 41 | uri!(has_one(id = 100, id = 100, )); | ^^^^^^^^^^^^^^^^^^^ | = note: uri parameters are: id: i32 help: duplicate parameter: `id` - --> $DIR/typed-uris-bad-params.rs:41:28 + --> tests/ui-fail-nightly/typed-uris-bad-params.rs:41:28 | 41 | uri!(has_one(id = 100, id = 100, )); | ^^ error: invalid parameters for `has_one` route uri - --> $DIR/typed-uris-bad-params.rs:39:18 + --> tests/ui-fail-nightly/typed-uris-bad-params.rs:39:18 | 39 | uri!(has_one(id = 100, id = 100)); | ^^^^^^^^^^^^^^^^^^ | = note: uri parameters are: id: i32 help: duplicate parameter: `id` - --> $DIR/typed-uris-bad-params.rs:39:28 + --> tests/ui-fail-nightly/typed-uris-bad-params.rs:39:28 | 39 | uri!(has_one(id = 100, id = 100)); | ^^ error: invalid parameters for `has_one` route uri - --> $DIR/typed-uris-bad-params.rs:37:18 + --> tests/ui-fail-nightly/typed-uris-bad-params.rs:37:18 | 37 | uri!(has_one(name = 100, age = 50, id = 100, id = 50)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: uri parameters are: id: i32 help: unknown parameters: `name`, `age` - --> $DIR/typed-uris-bad-params.rs:37:18 + --> tests/ui-fail-nightly/typed-uris-bad-params.rs:37:18 | 37 | uri!(has_one(name = 100, age = 50, id = 100, id = 50)); | ^^^^ ^^^ help: duplicate parameter: `id` - --> $DIR/typed-uris-bad-params.rs:37:50 + --> tests/ui-fail-nightly/typed-uris-bad-params.rs:37:50 | 37 | uri!(has_one(name = 100, age = 50, id = 100, id = 50)); | ^^ error: invalid parameters for `has_one` route uri - --> $DIR/typed-uris-bad-params.rs:35:18 + --> tests/ui-fail-nightly/typed-uris-bad-params.rs:35:18 | 35 | uri!(has_one(name = 100, age = 50, id = 100)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: uri parameters are: id: i32 help: unknown parameters: `name`, `age` - --> $DIR/typed-uris-bad-params.rs:35:18 + --> tests/ui-fail-nightly/typed-uris-bad-params.rs:35:18 | 35 | uri!(has_one(name = 100, age = 50, id = 100)); | ^^^^ ^^^ error: invalid parameters for `has_one` route uri - --> $DIR/typed-uris-bad-params.rs:33:18 + --> tests/ui-fail-nightly/typed-uris-bad-params.rs:33:18 | 33 | uri!(has_one(name = 100, id = 100)); | ^^^^^^^^^^^^^^^^^^^^ | = note: uri parameters are: id: i32 help: unknown parameter: `name` - --> $DIR/typed-uris-bad-params.rs:33:18 + --> tests/ui-fail-nightly/typed-uris-bad-params.rs:33:18 | 33 | uri!(has_one(name = 100, id = 100)); | ^^^^ error: invalid parameters for `has_one` route uri - --> $DIR/typed-uris-bad-params.rs:31:18 + --> tests/ui-fail-nightly/typed-uris-bad-params.rs:31:18 | 31 | uri!(has_one(id = 100, name = "hi")); | ^^^^^^^^^^^^^^^^^^^^^ | = note: uri parameters are: id: i32 help: unknown parameter: `name` - --> $DIR/typed-uris-bad-params.rs:31:28 + --> tests/ui-fail-nightly/typed-uris-bad-params.rs:31:28 | 31 | uri!(has_one(id = 100, name = "hi")); | ^^^^ error: route expects 2 parameters but 1 was supplied - --> $DIR/typed-uris-bad-params.rs:29:18 + --> tests/ui-fail-nightly/typed-uris-bad-params.rs:29:18 | 29 | uri!(has_two(10)); | ^^ @@ -234,7 +234,7 @@ error: route expects 2 parameters but 1 was supplied = note: route `has_two` has uri "/?" error: route expects 2 parameters but 3 were supplied - --> $DIR/typed-uris-bad-params.rs:28:18 + --> tests/ui-fail-nightly/typed-uris-bad-params.rs:28:18 | 28 | uri!(has_two(10, "hi", "there")); | ^^^^^^^^^^^^^^^^^ @@ -242,7 +242,7 @@ error: route expects 2 parameters but 3 were supplied = note: route `has_two` has uri "/?" error: route expects 1 parameter but 2 were supplied - --> $DIR/typed-uris-bad-params.rs:26:26 + --> tests/ui-fail-nightly/typed-uris-bad-params.rs:26:26 | 26 | uri!(has_one_guarded("hi", 100)); | ^^^^^^^^^ @@ -250,7 +250,7 @@ error: route expects 1 parameter but 2 were supplied = note: route `has_one_guarded` has uri "/" error: route expects 1 parameter but 2 were supplied - --> $DIR/typed-uris-bad-params.rs:25:18 + --> tests/ui-fail-nightly/typed-uris-bad-params.rs:25:18 | 25 | uri!(has_one("Hello", 23, )); | ^^^^^^^^^^^^ @@ -258,7 +258,7 @@ error: route expects 1 parameter but 2 were supplied = note: route `has_one` has uri "/" error: route expects 1 parameter but 2 were supplied - --> $DIR/typed-uris-bad-params.rs:24:18 + --> tests/ui-fail-nightly/typed-uris-bad-params.rs:24:18 | 24 | uri!(has_one(1, 23)); | ^^^^^ @@ -266,7 +266,7 @@ error: route expects 1 parameter but 2 were supplied = note: route `has_one` has uri "/" error: route expects 1 parameter but 0 were supplied - --> $DIR/typed-uris-bad-params.rs:22:10 + --> tests/ui-fail-nightly/typed-uris-bad-params.rs:22:10 | 22 | uri!(has_one()); | ^^^^^^^ @@ -274,7 +274,7 @@ error: route expects 1 parameter but 0 were supplied = note: route `has_one` has uri "/" error: route expects 1 parameter but 0 were supplied - --> $DIR/typed-uris-bad-params.rs:21:10 + --> tests/ui-fail-nightly/typed-uris-bad-params.rs:21:10 | 21 | uri!(has_one); | ^^^^^^^ @@ -282,7 +282,7 @@ error: route expects 1 parameter but 0 were supplied = note: route `has_one` has uri "/" error[E0271]: type mismatch resolving `>::Error == &str` - --> $DIR/typed-uris-bad-params.rs:15:37 + --> tests/ui-fail-nightly/typed-uris-bad-params.rs:15:37 | 15 | fn optionals(id: Option, name: Result) { } | ^^^^^^^^^^^^^^^^^^^^ expected enum `Infallible`, found `&str` diff --git a/core/codegen/tests/ui-fail-nightly/typed-uris-invalid-syntax.stderr b/core/codegen/tests/ui-fail-nightly/typed-uris-invalid-syntax.stderr index c0cd9eccf6..c0b5247713 100644 --- a/core/codegen/tests/ui-fail-nightly/typed-uris-invalid-syntax.stderr +++ b/core/codegen/tests/ui-fail-nightly/typed-uris-invalid-syntax.stderr @@ -1,151 +1,151 @@ error: expected identifier - --> $DIR/typed-uris-invalid-syntax.rs:10:28 + --> tests/ui-fail-nightly/typed-uris-invalid-syntax.rs:10:28 | 10 | uri!(simple: id = 100, "Hello"); | ^^^^^^^ error: named and unnamed parameters cannot be mixed - --> $DIR/typed-uris-invalid-syntax.rs:11:17 + --> tests/ui-fail-nightly/typed-uris-invalid-syntax.rs:11:17 | 11 | uri!(simple(id = 100, "Hello")); | ^^^^^^^^^^^^^^^^^ error: named and unnamed parameters cannot be mixed - --> $DIR/typed-uris-invalid-syntax.rs:12:17 + --> tests/ui-fail-nightly/typed-uris-invalid-syntax.rs:12:17 | 12 | uri!(simple("Hello", id = 100)); | ^^^^^^^^^^^^^^^^^ error: unexpected token - --> $DIR/typed-uris-invalid-syntax.rs:14:16 + --> tests/ui-fail-nightly/typed-uris-invalid-syntax.rs:14:16 | 14 | uri!(simple:); | ^ error: invalid URI: unexpected EOF: expected token ':' at index 5 - --> $DIR/typed-uris-invalid-syntax.rs:16:16 + --> tests/ui-fail-nightly/typed-uris-invalid-syntax.rs:16:16 | 16 | uri!("mount", simple); | ^ error: invalid URI: unexpected EOF: expected token ':' at index 5 - --> $DIR/typed-uris-invalid-syntax.rs:17:16 + --> tests/ui-fail-nightly/typed-uris-invalid-syntax.rs:17:16 | 17 | uri!("mount", simple, "http://"); | ^ error: URI suffix must contain only query and/or fragment - --> $DIR/typed-uris-invalid-syntax.rs:18:28 + --> tests/ui-fail-nightly/typed-uris-invalid-syntax.rs:18:28 | 18 | uri!("/mount", simple, "http://"); | ^^^^^^^^^ error: expected 1, 2, or 3 arguments, found 4 - --> $DIR/typed-uris-invalid-syntax.rs:19:36 + --> tests/ui-fail-nightly/typed-uris-invalid-syntax.rs:19:36 | 19 | uri!("/mount", simple, "#foo", "?foo"); | ^^^^^^ error: invalid URI: unexpected EOF: expected token ':' at index 5 - --> $DIR/typed-uris-invalid-syntax.rs:20:16 + --> tests/ui-fail-nightly/typed-uris-invalid-syntax.rs:20:16 | 20 | uri!("mount", simple(10, "hi"), "http://"); | ^ error: URI suffix must contain only query and/or fragment - --> $DIR/typed-uris-invalid-syntax.rs:21:38 + --> tests/ui-fail-nightly/typed-uris-invalid-syntax.rs:21:38 | 21 | uri!("/mount", simple(10, "hi"), "http://"); | ^^^^^^^^^ error: URI prefix cannot contain query part - --> $DIR/typed-uris-invalid-syntax.rs:22:10 + --> tests/ui-fail-nightly/typed-uris-invalid-syntax.rs:22:10 | 22 | uri!("/mount?foo", simple(10, "hi"), "foo/bar?foo#bar"); | ^^^^^^^^^^^^ error: URI suffix must contain only query and/or fragment - --> $DIR/typed-uris-invalid-syntax.rs:23:38 + --> tests/ui-fail-nightly/typed-uris-invalid-syntax.rs:23:38 | 23 | uri!("/mount", simple(10, "hi"), "a/b"); | ^^^^^ error: expected 1, 2, or 3 arguments, found 4 - --> $DIR/typed-uris-invalid-syntax.rs:24:46 + --> tests/ui-fail-nightly/typed-uris-invalid-syntax.rs:24:46 | 24 | uri!("/mount", simple(10, "hi"), "#foo", "?foo"); | ^^^^^^ error: invalid URI: unexpected token '<' at index 7 - --> $DIR/typed-uris-invalid-syntax.rs:25:18 + --> tests/ui-fail-nightly/typed-uris-invalid-syntax.rs:25:18 | 25 | uri!("/mount/", simple); | ^ error: expected at least 1 argument, found none - --> $DIR/typed-uris-invalid-syntax.rs:26:5 + --> tests/ui-fail-nightly/typed-uris-invalid-syntax.rs:26:5 | 26 | uri!(); - | ^^^^^^^ + | ^^^^^^ | = note: this error originates in the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info) error: unexpected token - --> $DIR/typed-uris-invalid-syntax.rs:27:16 + --> tests/ui-fail-nightly/typed-uris-invalid-syntax.rs:27:16 | 27 | uri!(simple: id = ); | ^ error: unexpected end of input, expected expression - --> $DIR/typed-uris-invalid-syntax.rs:28:22 + --> tests/ui-fail-nightly/typed-uris-invalid-syntax.rs:28:22 | 28 | uri!(simple(id = )); | ^ error: invalid URI: unexpected EOF: expected some token at index 0 - --> $DIR/typed-uris-invalid-syntax.rs:29:11 + --> tests/ui-fail-nightly/typed-uris-invalid-syntax.rs:29:11 | 29 | uri!("*", simple(10), "hi"); | ^ error: URI suffix must contain only query and/or fragment - --> $DIR/typed-uris-invalid-syntax.rs:30:40 + --> tests/ui-fail-nightly/typed-uris-invalid-syntax.rs:30:40 | 30 | uri!("some.host:8088", simple(10), "hi"); | ^^^^ error: expected identifier - --> $DIR/typed-uris-invalid-syntax.rs:33:18 + --> tests/ui-fail-nightly/typed-uris-invalid-syntax.rs:33:18 | 33 | uri!("/foo", "bar"); | ^^^^^ error: unexpected token - --> $DIR/typed-uris-invalid-syntax.rs:34:17 + --> tests/ui-fail-nightly/typed-uris-invalid-syntax.rs:34:17 | 34 | uri!("/foo" ("bar")); | ^^^^^^^ error: URI prefix cannot contain query part - --> $DIR/typed-uris-invalid-syntax.rs:35:10 + --> tests/ui-fail-nightly/typed-uris-invalid-syntax.rs:35:10 | 35 | uri!("ftp:?", index); | ^^^^^^^ error: URI suffix must contain only query and/or fragment - --> $DIR/typed-uris-invalid-syntax.rs:36:25 + --> tests/ui-fail-nightly/typed-uris-invalid-syntax.rs:36:25 | 36 | uri!("ftp:", index, "foo#bar"); | ^^^^^^^^^ error: URI suffix must contain only query and/or fragment - --> $DIR/typed-uris-invalid-syntax.rs:37:25 + --> tests/ui-fail-nightly/typed-uris-invalid-syntax.rs:37:25 | 37 | uri!("ftp:", index, "foo?bar"); | ^^^^^^^^^ error: route expects 2 parameters but 0 were supplied - --> $DIR/typed-uris-invalid-syntax.rs:13:10 + --> tests/ui-fail-nightly/typed-uris-invalid-syntax.rs:13:10 | 13 | uri!(simple,); | ^^^^^^ diff --git a/core/codegen/tests/ui-fail-nightly/uri_display.stderr b/core/codegen/tests/ui-fail-nightly/uri_display.stderr index 6caf4379be..96ada3fa95 100644 --- a/core/codegen/tests/ui-fail-nightly/uri_display.stderr +++ b/core/codegen/tests/ui-fail-nightly/uri_display.stderr @@ -1,109 +1,109 @@ error: fieldless structs are not supported - --> $DIR/uri_display.rs:4:1 + --> tests/ui-fail-nightly/uri_display.rs:4:1 | 4 | struct Foo1; | ^^^^^^^^^^^^ | note: error occurred while deriving `UriDisplay` - --> $DIR/uri_display.rs:3:10 + --> tests/ui-fail-nightly/uri_display.rs:3:10 | 3 | #[derive(UriDisplayQuery)] | ^^^^^^^^^^^^^^^ = note: this error originates in the derive macro `UriDisplayQuery` (in Nightly builds, run with -Z macro-backtrace for more info) error: fieldless structs are not supported - --> $DIR/uri_display.rs:7:1 + --> tests/ui-fail-nightly/uri_display.rs:7:1 | 7 | struct Foo2(); | ^^^^^^^^^^^^^^ | note: error occurred while deriving `UriDisplay` - --> $DIR/uri_display.rs:6:10 + --> tests/ui-fail-nightly/uri_display.rs:6:10 | 6 | #[derive(UriDisplayQuery)] | ^^^^^^^^^^^^^^^ = note: this error originates in the derive macro `UriDisplayQuery` (in Nightly builds, run with -Z macro-backtrace for more info) error: empty enums are not supported - --> $DIR/uri_display.rs:10:11 + --> tests/ui-fail-nightly/uri_display.rs:10:11 | 10 | enum Foo3 { } | ^^^ | note: error occurred while deriving `UriDisplay` - --> $DIR/uri_display.rs:9:10 + --> tests/ui-fail-nightly/uri_display.rs:9:10 | 9 | #[derive(UriDisplayQuery)] | ^^^^^^^^^^^^^^^ = note: this error originates in the derive macro `UriDisplayQuery` (in Nightly builds, run with -Z macro-backtrace for more info) error: tuple structs or variants must have exactly one field - --> $DIR/uri_display.rs:18:12 + --> tests/ui-fail-nightly/uri_display.rs:18:12 | 18 | struct Foo5(String, String); | ^^^^^^^^^^^^^^^^ | note: error occurred while deriving `UriDisplay` - --> $DIR/uri_display.rs:17:10 + --> tests/ui-fail-nightly/uri_display.rs:17:10 | 17 | #[derive(UriDisplayQuery)] | ^^^^^^^^^^^^^^^ = note: this error originates in the derive macro `UriDisplayQuery` (in Nightly builds, run with -Z macro-backtrace for more info) error: expected list `#[attr(..)]`, found bare integer literal - --> $DIR/uri_display.rs:22:20 + --> tests/ui-fail-nightly/uri_display.rs:22:20 | 22 | #[field(name = 123)] | ^^^ | note: error occurred while deriving `UriDisplay` - --> $DIR/uri_display.rs:20:10 + --> tests/ui-fail-nightly/uri_display.rs:20:10 | 20 | #[derive(UriDisplayQuery)] | ^^^^^^^^^^^^^^^ = note: this error originates in the derive macro `UriDisplayQuery` (in Nightly builds, run with -Z macro-backtrace for more info) error: struct must have exactly one field - --> $DIR/uri_display.rs:27:12 + --> tests/ui-fail-nightly/uri_display.rs:27:12 | 27 | struct Foo7(String, usize); | ^^^^^^^^^^^^^^^ | note: error occurred while deriving `UriDisplay` - --> $DIR/uri_display.rs:26:10 + --> tests/ui-fail-nightly/uri_display.rs:26:10 | 26 | #[derive(UriDisplayPath)] | ^^^^^^^^^^^^^^ = note: this error originates in the derive macro `UriDisplayPath` (in Nightly builds, run with -Z macro-backtrace for more info) error: struct must have exactly one field - --> $DIR/uri_display.rs:30:1 + --> tests/ui-fail-nightly/uri_display.rs:30:1 | 30 | struct Foo8; | ^^^^^^^^^^^^ | note: error occurred while deriving `UriDisplay` - --> $DIR/uri_display.rs:29:10 + --> tests/ui-fail-nightly/uri_display.rs:29:10 | 29 | #[derive(UriDisplayPath)] | ^^^^^^^^^^^^^^ = note: this error originates in the derive macro `UriDisplayPath` (in Nightly builds, run with -Z macro-backtrace for more info) error: enums are not supported - --> $DIR/uri_display.rs:33:1 + --> tests/ui-fail-nightly/uri_display.rs:33:1 | 33 | enum Foo9 { } | ^^^^^^^^^^^^^^ | note: error occurred while deriving `UriDisplay` - --> $DIR/uri_display.rs:32:10 + --> tests/ui-fail-nightly/uri_display.rs:32:10 | 32 | #[derive(UriDisplayPath)] | ^^^^^^^^^^^^^^ = note: this error originates in the derive macro `UriDisplayPath` (in Nightly builds, run with -Z macro-backtrace for more info) error: named structs are not supported - --> $DIR/uri_display.rs:36:1 + --> tests/ui-fail-nightly/uri_display.rs:36:1 | 36 | / struct Foo10 { 37 | | named: usize @@ -111,7 +111,7 @@ error: named structs are not supported | |_^ | note: error occurred while deriving `UriDisplay` - --> $DIR/uri_display.rs:35:10 + --> tests/ui-fail-nightly/uri_display.rs:35:10 | 35 | #[derive(UriDisplayPath)] | ^^^^^^^^^^^^^^ diff --git a/core/codegen/tests/ui-fail-nightly/uri_display_type_errors.stderr b/core/codegen/tests/ui-fail-nightly/uri_display_type_errors.stderr index 13d31426ff..99cf3b7329 100644 --- a/core/codegen/tests/ui-fail-nightly/uri_display_type_errors.stderr +++ b/core/codegen/tests/ui-fail-nightly/uri_display_type_errors.stderr @@ -1,61 +1,96 @@ error[E0277]: the trait bound `BadType: UriDisplay` is not satisfied - --> $DIR/uri_display_type_errors.rs:6:13 - | -6 | struct Bar1(BadType); - | ^^^^^^^ the trait `UriDisplay` is not implemented for `BadType` - | - = note: required because of the requirements on the impl of `UriDisplay` for `&BadType` + --> tests/ui-fail-nightly/uri_display_type_errors.rs:6:13 + | +6 | struct Bar1(BadType); + | ^^^^^^^ the trait `UriDisplay` is not implemented for `BadType` + | + = note: required because of the requirements on the impl of `UriDisplay` for `&BadType` +note: required by a bound in `rocket::http::uri::fmt::Formatter::<'i, P>::write_value` + --> $WORKSPACE/core/http/src/uri/fmt/formatter.rs + | + | pub fn write_value>(&mut self, value: T) -> fmt::Result { + | ^^^^^^^^^^^^^ required by this bound in `rocket::http::uri::fmt::Formatter::<'i, P>::write_value` error[E0277]: the trait bound `BadType: UriDisplay` is not satisfied - --> $DIR/uri_display_type_errors.rs:10:5 - | -10 | field: BadType, - | ^^^^^^^^^^^^^^ the trait `UriDisplay` is not implemented for `BadType` - | - = note: required because of the requirements on the impl of `UriDisplay` for `&BadType` + --> tests/ui-fail-nightly/uri_display_type_errors.rs:10:5 + | +10 | field: BadType, + | ^^^^^^^^^^^^^^ the trait `UriDisplay` is not implemented for `BadType` + | + = note: required because of the requirements on the impl of `UriDisplay` for `&BadType` +note: required by a bound in `rocket::http::uri::fmt::Formatter::<'_, rocket::http::uri::fmt::Query>::write_named_value` + --> $WORKSPACE/core/http/src/uri/fmt/formatter.rs + | + | pub fn write_named_value>(&mut self, name: &str, value: T) -> fmt::Result { + | ^^^^^^^^^^^^^^^^^ required by this bound in `rocket::http::uri::fmt::Formatter::<'_, rocket::http::uri::fmt::Query>::write_named_value` error[E0277]: the trait bound `BadType: UriDisplay` is not satisfied - --> $DIR/uri_display_type_errors.rs:16:5 - | -16 | bad: BadType, - | ^^^^^^^^^^^^ the trait `UriDisplay` is not implemented for `BadType` - | - = note: required because of the requirements on the impl of `UriDisplay` for `&BadType` + --> tests/ui-fail-nightly/uri_display_type_errors.rs:16:5 + | +16 | bad: BadType, + | ^^^^^^^^^^^^ the trait `UriDisplay` is not implemented for `BadType` + | + = note: required because of the requirements on the impl of `UriDisplay` for `&BadType` +note: required by a bound in `rocket::http::uri::fmt::Formatter::<'_, rocket::http::uri::fmt::Query>::write_named_value` + --> $WORKSPACE/core/http/src/uri/fmt/formatter.rs + | + | pub fn write_named_value>(&mut self, name: &str, value: T) -> fmt::Result { + | ^^^^^^^^^^^^^^^^^ required by this bound in `rocket::http::uri::fmt::Formatter::<'_, rocket::http::uri::fmt::Query>::write_named_value` error[E0277]: the trait bound `BadType: UriDisplay` is not satisfied - --> $DIR/uri_display_type_errors.rs:21:11 - | -21 | Inner(BadType), - | ^^^^^^^ the trait `UriDisplay` is not implemented for `BadType` - | - = note: required because of the requirements on the impl of `UriDisplay` for `&BadType` - = note: 1 redundant requirements hidden - = note: required because of the requirements on the impl of `UriDisplay` for `&&BadType` + --> tests/ui-fail-nightly/uri_display_type_errors.rs:21:11 + | +21 | Inner(BadType), + | ^^^^^^^ the trait `UriDisplay` is not implemented for `BadType` + | + = note: required because of the requirements on the impl of `UriDisplay` for `&BadType` + = note: 1 redundant requirement hidden + = note: required because of the requirements on the impl of `UriDisplay` for `&&BadType` +note: required by a bound in `rocket::http::uri::fmt::Formatter::<'i, P>::write_value` + --> $WORKSPACE/core/http/src/uri/fmt/formatter.rs + | + | pub fn write_value>(&mut self, value: T) -> fmt::Result { + | ^^^^^^^^^^^^^ required by this bound in `rocket::http::uri::fmt::Formatter::<'i, P>::write_value` error[E0277]: the trait bound `BadType: UriDisplay` is not satisfied - --> $DIR/uri_display_type_errors.rs:27:9 - | -27 | field: BadType, - | ^^^^^^^^^^^^^^ the trait `UriDisplay` is not implemented for `BadType` - | - = note: required because of the requirements on the impl of `UriDisplay` for `&BadType` - = note: 1 redundant requirements hidden - = note: required because of the requirements on the impl of `UriDisplay` for `&&BadType` + --> tests/ui-fail-nightly/uri_display_type_errors.rs:27:9 + | +27 | field: BadType, + | ^^^^^^^^^^^^^^ the trait `UriDisplay` is not implemented for `BadType` + | + = note: required because of the requirements on the impl of `UriDisplay` for `&BadType` + = note: 1 redundant requirement hidden + = note: required because of the requirements on the impl of `UriDisplay` for `&&BadType` +note: required by a bound in `rocket::http::uri::fmt::Formatter::<'_, rocket::http::uri::fmt::Query>::write_named_value` + --> $WORKSPACE/core/http/src/uri/fmt/formatter.rs + | + | pub fn write_named_value>(&mut self, name: &str, value: T) -> fmt::Result { + | ^^^^^^^^^^^^^^^^^ required by this bound in `rocket::http::uri::fmt::Formatter::<'_, rocket::http::uri::fmt::Query>::write_named_value` error[E0277]: the trait bound `BadType: UriDisplay` is not satisfied - --> $DIR/uri_display_type_errors.rs:35:9 - | -35 | other: BadType, - | ^^^^^^^^^^^^^^ the trait `UriDisplay` is not implemented for `BadType` - | - = note: required because of the requirements on the impl of `UriDisplay` for `&BadType` - = note: 1 redundant requirements hidden - = note: required because of the requirements on the impl of `UriDisplay` for `&&BadType` + --> tests/ui-fail-nightly/uri_display_type_errors.rs:35:9 + | +35 | other: BadType, + | ^^^^^^^^^^^^^^ the trait `UriDisplay` is not implemented for `BadType` + | + = note: required because of the requirements on the impl of `UriDisplay` for `&BadType` + = note: 1 redundant requirement hidden + = note: required because of the requirements on the impl of `UriDisplay` for `&&BadType` +note: required by a bound in `rocket::http::uri::fmt::Formatter::<'_, rocket::http::uri::fmt::Query>::write_named_value` + --> $WORKSPACE/core/http/src/uri/fmt/formatter.rs + | + | pub fn write_named_value>(&mut self, name: &str, value: T) -> fmt::Result { + | ^^^^^^^^^^^^^^^^^ required by this bound in `rocket::http::uri::fmt::Formatter::<'_, rocket::http::uri::fmt::Query>::write_named_value` error[E0277]: the trait bound `BadType: UriDisplay` is not satisfied - --> $DIR/uri_display_type_errors.rs:40:12 - | -40 | struct Baz(BadType); - | ^^^^^^^ the trait `UriDisplay` is not implemented for `BadType` - | - = note: required because of the requirements on the impl of `UriDisplay` for `&BadType` + --> tests/ui-fail-nightly/uri_display_type_errors.rs:40:12 + | +40 | struct Baz(BadType); + | ^^^^^^^ the trait `UriDisplay` is not implemented for `BadType` + | + = note: required because of the requirements on the impl of `UriDisplay` for `&BadType` +note: required by a bound in `rocket::http::uri::fmt::Formatter::<'i, P>::write_value` + --> $WORKSPACE/core/http/src/uri/fmt/formatter.rs + | + | pub fn write_value>(&mut self, value: T) -> fmt::Result { + | ^^^^^^^^^^^^^ required by this bound in `rocket::http::uri::fmt::Formatter::<'i, P>::write_value` diff --git a/core/codegen/tests/ui-fail-stable/async-entry.stderr b/core/codegen/tests/ui-fail-stable/async-entry.stderr index d5eb48effd..ae9c0593bb 100644 --- a/core/codegen/tests/ui-fail-stable/async-entry.stderr +++ b/core/codegen/tests/ui-fail-stable/async-entry.stderr @@ -1,5 +1,5 @@ error: attribute can only be applied to `async` functions - --> $DIR/async-entry.rs:4:5 + --> tests/ui-fail-stable/async-entry.rs:4:5 | 4 | #[rocket::main] | ^^^^^^^^^^^^^^^ @@ -7,13 +7,13 @@ error: attribute can only be applied to `async` functions = note: this error originates in the attribute macro `rocket::main` (in Nightly builds, run with -Z macro-backtrace for more info) error: [note] this function must be `async` - --> $DIR/async-entry.rs:5:5 + --> tests/ui-fail-stable/async-entry.rs:5:5 | 5 | fn foo() { } | ^^ error: attribute can only be applied to `async` functions - --> $DIR/async-entry.rs:16:5 + --> tests/ui-fail-stable/async-entry.rs:16:5 | 16 | #[rocket::main] | ^^^^^^^^^^^^^^^ @@ -21,14 +21,14 @@ error: attribute can only be applied to `async` functions = note: this error originates in the attribute macro `rocket::main` (in Nightly builds, run with -Z macro-backtrace for more info) error: [note] this function must be `async` - --> $DIR/async-entry.rs:17:5 + --> tests/ui-fail-stable/async-entry.rs:17:5 | 17 | fn main() { | ^^ error: attribute cannot be applied to `main` function - --- note: this attribute generates a `main` function - --> $DIR/async-entry.rs:49:5 + --- note: this attribute generates a `main` function + --> tests/ui-fail-stable/async-entry.rs:49:5 | 49 | #[rocket::launch] | ^^^^^^^^^^^^^^^^^ @@ -36,13 +36,13 @@ error: attribute cannot be applied to `main` function = note: this error originates in the attribute macro `rocket::launch` (in Nightly builds, run with -Z macro-backtrace for more info) error: [note] this function cannot be `main` - --> $DIR/async-entry.rs:50:8 + --> tests/ui-fail-stable/async-entry.rs:50:8 | 50 | fn main() -> rocekt::Rocket { | ^^^^ error: attribute can only be applied to functions that return a value - --> $DIR/async-entry.rs:56:5 + --> tests/ui-fail-stable/async-entry.rs:56:5 | 56 | #[rocket::launch] | ^^^^^^^^^^^^^^^^^ @@ -50,13 +50,13 @@ error: attribute can only be applied to functions that return a value = note: this error originates in the attribute macro `rocket::launch` (in Nightly builds, run with -Z macro-backtrace for more info) error: [note] this function must return a value - --> $DIR/async-entry.rs:57:5 + --> tests/ui-fail-stable/async-entry.rs:57:5 | 57 | async fn rocket() { | ^^^^^ error: attribute can only be applied to functions that return a value - --> $DIR/async-entry.rs:64:5 + --> tests/ui-fail-stable/async-entry.rs:64:5 | 64 | #[rocket::launch] | ^^^^^^^^^^^^^^^^^ @@ -64,14 +64,14 @@ error: attribute can only be applied to functions that return a value = note: this error originates in the attribute macro `rocket::launch` (in Nightly builds, run with -Z macro-backtrace for more info) error: [note] this function must return a value - --> $DIR/async-entry.rs:65:5 + --> tests/ui-fail-stable/async-entry.rs:65:5 | 65 | fn rocket() { | ^^ error: attribute cannot be applied to `main` function - --- note: this attribute generates a `main` function - --> $DIR/async-entry.rs:79:5 + --- note: this attribute generates a `main` function + --> tests/ui-fail-stable/async-entry.rs:79:5 | 79 | #[rocket::launch] | ^^^^^^^^^^^^^^^^^ @@ -79,14 +79,14 @@ error: attribute cannot be applied to `main` function = note: this error originates in the attribute macro `rocket::launch` (in Nightly builds, run with -Z macro-backtrace for more info) error: [note] this function cannot be `main` - --> $DIR/async-entry.rs:80:8 + --> tests/ui-fail-stable/async-entry.rs:80:8 | 80 | fn main() -> &'static str { | ^^^^ error: attribute cannot be applied to `main` function - --- note: this attribute generates a `main` function - --> $DIR/async-entry.rs:87:5 + --- note: this attribute generates a `main` function + --> tests/ui-fail-stable/async-entry.rs:87:5 | 87 | #[rocket::launch] | ^^^^^^^^^^^^^^^^^ @@ -94,13 +94,13 @@ error: attribute cannot be applied to `main` function = note: this error originates in the attribute macro `rocket::launch` (in Nightly builds, run with -Z macro-backtrace for more info) error: [note] this function cannot be `main` - --> $DIR/async-entry.rs:88:14 + --> tests/ui-fail-stable/async-entry.rs:88:14 | 88 | async fn main() -> _ { | ^^^^ error[E0728]: `await` is only allowed inside `async` functions and blocks - --> $DIR/async-entry.rs:73:17 + --> tests/ui-fail-stable/async-entry.rs:73:17 | 72 | fn rocket() -> _ { | ------ this is not `async` @@ -108,7 +108,7 @@ error[E0728]: `await` is only allowed inside `async` functions and blocks | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ only allowed inside `async` functions and blocks error[E0308]: mismatched types - --> $DIR/async-entry.rs:35:9 + --> tests/ui-fail-stable/async-entry.rs:35:9 | 35 | rocket::build() | ^^^^^^^^^^^^^^^ expected struct `std::string::String`, found struct `Rocket` @@ -117,7 +117,7 @@ error[E0308]: mismatched types found struct `Rocket` error[E0308]: mismatched types - --> $DIR/async-entry.rs:44:9 + --> tests/ui-fail-stable/async-entry.rs:44:9 | 44 | "hi".to_string() | ^^^^^^^^^^^^^^^^ expected struct `Rocket`, found struct `std::string::String` @@ -126,7 +126,7 @@ error[E0308]: mismatched types found struct `std::string::String` error[E0308]: mismatched types - --> $DIR/async-entry.rs:24:21 + --> tests/ui-fail-stable/async-entry.rs:24:21 | 24 | async fn main() { | ^ expected `()` because of default return type @@ -142,7 +142,7 @@ error[E0308]: mismatched types found struct `Rocket` error[E0308]: mismatched types - --> $DIR/async-entry.rs:33:26 + --> tests/ui-fail-stable/async-entry.rs:33:26 | 33 | async fn rocket() -> String { | ^^^^^^ @@ -154,7 +154,7 @@ error[E0308]: mismatched types found struct `std::string::String` error[E0277]: `main` has invalid return type `Rocket` - --> $DIR/async-entry.rs:94:20 + --> tests/ui-fail-stable/async-entry.rs:94:20 | 94 | async fn main() -> rocket::Rocket { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `main` can only return types that implement `Termination` diff --git a/core/codegen/tests/ui-fail-stable/bad-ignored-segments.stderr b/core/codegen/tests/ui-fail-stable/bad-ignored-segments.stderr index 423c7105c5..9a79954b83 100644 --- a/core/codegen/tests/ui-fail-stable/bad-ignored-segments.stderr +++ b/core/codegen/tests/ui-fail-stable/bad-ignored-segments.stderr @@ -1,13 +1,13 @@ error: parameter must be named - --- help: use a name such as `_guard` or `_param` - --> $DIR/bad-ignored-segments.rs:6:7 + --- help: use a name such as `_guard` or `_param` + --> tests/ui-fail-stable/bad-ignored-segments.rs:6:7 | 6 | #[get("/c?<_>")] | ^^^^^^^^ error: parameter must be named - --- help: use a name such as `_guard` or `_param` - --> $DIR/bad-ignored-segments.rs:9:21 + --- help: use a name such as `_guard` or `_param` + --> tests/ui-fail-stable/bad-ignored-segments.rs:9:21 | 9 | #[post("/d", data = "<_>")] | ^^^^^ diff --git a/core/codegen/tests/ui-fail-stable/catch.stderr b/core/codegen/tests/ui-fail-stable/catch.stderr index b720f953bc..9653e49490 100644 --- a/core/codegen/tests/ui-fail-stable/catch.stderr +++ b/core/codegen/tests/ui-fail-stable/catch.stderr @@ -1,67 +1,67 @@ error: expected `fn` - --- help: `#[catch]` can only be used on functions - --> $DIR/catch.rs:6:1 + --- help: `#[catch]` can only be used on functions + --> tests/ui-fail-stable/catch.rs:6:1 | 6 | struct Catcher(String); | ^^^^^^ error: expected `fn` - --- help: `#[catch]` can only be used on functions - --> $DIR/catch.rs:9:7 + --- help: `#[catch]` can only be used on functions + --> tests/ui-fail-stable/catch.rs:9:7 | 9 | const CATCH: &str = "Catcher"; | ^^^^^ error: expected integer or `default`, found string literal - --- help: `#[catch]` expects a status code int or `default`: `#[catch(404)]` or `#[catch(default)]` - --> $DIR/catch.rs:11:9 + --- help: `#[catch]` expects a status code int or `default`: `#[catch(404)]` or `#[catch(default)]` + --> tests/ui-fail-stable/catch.rs:11:9 | 11 | #[catch("404")] | ^^^^^ error: unexpected keyed parameter: expected literal or identifier - --- help: `#[catch]` expects a status code int or `default`: `#[catch(404)]` or `#[catch(default)]` - --> $DIR/catch.rs:14:9 + --- help: `#[catch]` expects a status code int or `default`: `#[catch(404)]` or `#[catch(default)]` + --> tests/ui-fail-stable/catch.rs:14:9 | 14 | #[catch(code = "404")] | ^^^^ error: unexpected keyed parameter: expected literal or identifier - --- help: `#[catch]` expects a status code int or `default`: `#[catch(404)]` or `#[catch(default)]` - --> $DIR/catch.rs:17:9 + --- help: `#[catch]` expects a status code int or `default`: `#[catch(404)]` or `#[catch(default)]` + --> tests/ui-fail-stable/catch.rs:17:9 | 17 | #[catch(code = 404)] | ^^^^ error: status must be in range [100, 599] - --- help: `#[catch]` expects a status code int or `default`: `#[catch(404)]` or `#[catch(default)]` - --> $DIR/catch.rs:20:9 + --- help: `#[catch]` expects a status code int or `default`: `#[catch(404)]` or `#[catch(default)]` + --> tests/ui-fail-stable/catch.rs:20:9 | 20 | #[catch(99)] | ^^ error: status must be in range [100, 599] - --- help: `#[catch]` expects a status code int or `default`: `#[catch(404)]` or `#[catch(default)]` - --> $DIR/catch.rs:23:9 + --- help: `#[catch]` expects a status code int or `default`: `#[catch(404)]` or `#[catch(default)]` + --> tests/ui-fail-stable/catch.rs:23:9 | 23 | #[catch(600)] | ^^^ error: unexpected attribute parameter: `message` - --- help: `#[catch]` expects a status code int or `default`: `#[catch(404)]` or `#[catch(default)]` - --> $DIR/catch.rs:26:14 + --- help: `#[catch]` expects a status code int or `default`: `#[catch(404)]` or `#[catch(default)]` + --> tests/ui-fail-stable/catch.rs:26:14 | 26 | #[catch(400, message = "foo")] | ^^^^^^^ error[E0308]: mismatched types - --> $DIR/catch.rs:30:17 + --> tests/ui-fail-stable/catch.rs:30:17 | 30 | fn f3(_request: &Request, other: bool) { } | ^ expected `&rocket::Request<'_>`, found struct `Status` error[E0308]: mismatched types - --> $DIR/catch.rs:30:34 + --> tests/ui-fail-stable/catch.rs:30:34 | 30 | fn f3(_request: &Request, other: bool) { } | ^^^^ expected `bool`, found `&rocket::Request<'_>` diff --git a/core/codegen/tests/ui-fail-stable/catch_type_errors.stderr b/core/codegen/tests/ui-fail-stable/catch_type_errors.stderr index 7d3a56a51f..b3ab0ba2fb 100644 --- a/core/codegen/tests/ui-fail-stable/catch_type_errors.stderr +++ b/core/codegen/tests/ui-fail-stable/catch_type_errors.stderr @@ -1,37 +1,37 @@ error[E0277]: the trait bound `usize: Responder<'_, '_>` is not satisfied - --> $DIR/catch_type_errors.rs:6:30 + --> tests/ui-fail-stable/catch_type_errors.rs:6:30 | +5 | #[catch(404)] + | ------------- required by a bound introduced by this call 6 | fn f1(_request: &Request) -> usize { | ^^^^^ the trait `Responder<'_, '_>` is not implemented for `usize` - | - = note: required by `respond_to` error[E0277]: the trait bound `bool: Responder<'_, '_>` is not satisfied - --> $DIR/catch_type_errors.rs:11:30 + --> tests/ui-fail-stable/catch_type_errors.rs:11:30 | +10 | #[catch(404)] + | ------------- required by a bound introduced by this call 11 | fn f2(_request: &Request) -> bool { | ^^^^ the trait `Responder<'_, '_>` is not implemented for `bool` - | - = note: required by `respond_to` error[E0308]: mismatched types - --> $DIR/catch_type_errors.rs:16:17 + --> tests/ui-fail-stable/catch_type_errors.rs:16:17 | 16 | fn f3(_request: bool) -> usize { | ^^^^ expected `bool`, found `&rocket::Request<'_>` error[E0277]: the trait bound `usize: Responder<'_, '_>` is not satisfied - --> $DIR/catch_type_errors.rs:16:26 + --> tests/ui-fail-stable/catch_type_errors.rs:16:26 | +15 | #[catch(404)] + | ------------- required by a bound introduced by this call 16 | fn f3(_request: bool) -> usize { | ^^^^^ the trait `Responder<'_, '_>` is not implemented for `usize` - | - = note: required by `respond_to` error[E0277]: the trait bound `usize: Responder<'_, '_>` is not satisfied - --> $DIR/catch_type_errors.rs:21:12 + --> tests/ui-fail-stable/catch_type_errors.rs:21:12 | +20 | #[catch(404)] + | ------------- required by a bound introduced by this call 21 | fn f4() -> usize { | ^^^^^ the trait `Responder<'_, '_>` is not implemented for `usize` - | - = note: required by `respond_to` diff --git a/core/codegen/tests/ui-fail-stable/catchers.stderr b/core/codegen/tests/ui-fail-stable/catchers.stderr index 59f164bb24..f7a5c641d3 100644 --- a/core/codegen/tests/ui-fail-stable/catchers.stderr +++ b/core/codegen/tests/ui-fail-stable/catchers.stderr @@ -1,17 +1,17 @@ error: expected `,` - --> $DIR/catchers.rs:4:25 + --> tests/ui-fail-stable/catchers.rs:4:25 | 4 | let _ = catchers![a b]; | ^ error: expected identifier - --> $DIR/catchers.rs:6:26 + --> tests/ui-fail-stable/catchers.rs:6:26 | 6 | let _ = catchers![a::, ]; | ^ error: unexpected end of input, expected identifier - --> $DIR/catchers.rs:7:13 + --> tests/ui-fail-stable/catchers.rs:7:13 | 7 | let _ = catchers![a::]; | ^^^^^^^^^^^^^^ diff --git a/core/codegen/tests/ui-fail-stable/from_form.stderr b/core/codegen/tests/ui-fail-stable/from_form.stderr index a464db2926..0805645779 100644 --- a/core/codegen/tests/ui-fail-stable/from_form.stderr +++ b/core/codegen/tests/ui-fail-stable/from_form.stderr @@ -1,11 +1,11 @@ error: enums are not supported - --> $DIR/from_form.rs:4:1 + --> tests/ui-fail-stable/from_form.rs:4:1 | 4 | enum Thing { } | ^^^^ error: [note] error occurred while deriving `FromForm` - --> $DIR/from_form.rs:3:10 + --> tests/ui-fail-stable/from_form.rs:3:10 | 3 | #[derive(FromForm)] | ^^^^^^^^ @@ -13,13 +13,13 @@ error: [note] error occurred while deriving `FromForm` = note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info) error: at least one field is required - --> $DIR/from_form.rs:7:1 + --> tests/ui-fail-stable/from_form.rs:7:1 | 7 | struct Foo1; | ^^^^^^ error: [note] error occurred while deriving `FromForm` - --> $DIR/from_form.rs:6:10 + --> tests/ui-fail-stable/from_form.rs:6:10 | 6 | #[derive(FromForm)] | ^^^^^^^^ @@ -27,13 +27,13 @@ error: [note] error occurred while deriving `FromForm` = note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info) error: at least one field is required - --> $DIR/from_form.rs:10:13 + --> tests/ui-fail-stable/from_form.rs:10:13 | 10 | struct Foo2 { } | ^^^^ error: [note] error occurred while deriving `FromForm` - --> $DIR/from_form.rs:9:10 + --> tests/ui-fail-stable/from_form.rs:9:10 | 9 | #[derive(FromForm)] | ^^^^^^^^ @@ -41,13 +41,13 @@ error: [note] error occurred while deriving `FromForm` = note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info) error: tuple struct must have exactly one field - --> $DIR/from_form.rs:16:12 + --> tests/ui-fail-stable/from_form.rs:16:12 | 16 | struct Foo4(usize, usize, usize); | ^^^^^^^^^^^^^^^^^^^^^ error: [note] error occurred while deriving `FromForm` - --> $DIR/from_form.rs:15:10 + --> tests/ui-fail-stable/from_form.rs:15:10 | 15 | #[derive(FromForm)] | ^^^^^^^^ @@ -55,13 +55,13 @@ error: [note] error occurred while deriving `FromForm` = note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info) error: only one lifetime is supported - --> $DIR/from_form.rs:19:25 + --> tests/ui-fail-stable/from_form.rs:19:25 | 19 | struct NextTodoTask<'f, 'a> { | ^^ error: [note] error occurred while deriving `FromForm` - --> $DIR/from_form.rs:18:10 + --> tests/ui-fail-stable/from_form.rs:18:10 | 18 | #[derive(FromForm)] | ^^^^^^^^ @@ -69,14 +69,14 @@ error: [note] error occurred while deriving `FromForm` = note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info) error: invalid form field name - --- help: field name cannot be `isindex` or contain '&', '=', '?', '.', '[', ']' - --> $DIR/from_form.rs:28:20 + --- help: field name cannot be `isindex` or contain '&', '=', '?', '.', '[', ']' + --> tests/ui-fail-stable/from_form.rs:28:20 | 28 | #[field(name = "isindex")] | ^^^^^^^^^ error: [note] error occurred while deriving `FromForm` - --> $DIR/from_form.rs:26:10 + --> tests/ui-fail-stable/from_form.rs:26:10 | 26 | #[derive(FromForm)] | ^^^^^^^^ @@ -84,25 +84,25 @@ error: [note] error occurred while deriving `FromForm` = note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info) error: field name conflicts with previous name - --> $DIR/from_form.rs:36:5 + --> tests/ui-fail-stable/from_form.rs:36:5 | 36 | foo: usize, | ^^^ error: [help] declared in this field - --> $DIR/from_form.rs:36:5 + --> tests/ui-fail-stable/from_form.rs:36:5 | 36 | foo: usize, | ^^^ error: [note] previous field with conflicting name - --> $DIR/from_form.rs:34:5 + --> tests/ui-fail-stable/from_form.rs:34:5 | 34 | #[field(name = "foo")] | ^ error: [note] error occurred while deriving `FromForm` - --> $DIR/from_form.rs:32:10 + --> tests/ui-fail-stable/from_form.rs:32:10 | 32 | #[derive(FromForm)] | ^^^^^^^^ @@ -110,25 +110,25 @@ error: [note] error occurred while deriving `FromForm` = note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info) error: field name conflicts with previous name - --> $DIR/from_form.rs:43:20 + --> tests/ui-fail-stable/from_form.rs:43:20 | 43 | #[field(name = "hello")] | ^^^^^^^ error: [help] declared in this field - --> $DIR/from_form.rs:43:5 + --> tests/ui-fail-stable/from_form.rs:43:5 | 43 | #[field(name = "hello")] | ^ error: [note] previous field with conflicting name - --> $DIR/from_form.rs:41:5 + --> tests/ui-fail-stable/from_form.rs:41:5 | 41 | #[field(name = "hello")] | ^ error: [note] error occurred while deriving `FromForm` - --> $DIR/from_form.rs:39:10 + --> tests/ui-fail-stable/from_form.rs:39:10 | 39 | #[derive(FromForm)] | ^^^^^^^^ @@ -136,25 +136,25 @@ error: [note] error occurred while deriving `FromForm` = note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info) error: field name conflicts with previous name - --> $DIR/from_form.rs:50:20 + --> tests/ui-fail-stable/from_form.rs:50:20 | 50 | #[field(name = "first")] | ^^^^^^^ error: [help] declared in this field - --> $DIR/from_form.rs:50:5 + --> tests/ui-fail-stable/from_form.rs:50:5 | 50 | #[field(name = "first")] | ^ error: [note] previous field with conflicting name - --> $DIR/from_form.rs:49:5 + --> tests/ui-fail-stable/from_form.rs:49:5 | 49 | first: String, | ^^^^^ error: [note] error occurred while deriving `FromForm` - --> $DIR/from_form.rs:47:10 + --> tests/ui-fail-stable/from_form.rs:47:10 | 47 | #[derive(FromForm)] | ^^^^^^^^ @@ -162,13 +162,13 @@ error: [note] error occurred while deriving `FromForm` = note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info) error: unexpected attribute parameter: `field` - --> $DIR/from_form.rs:56:28 + --> tests/ui-fail-stable/from_form.rs:56:28 | 56 | #[field(name = "blah", field = "bloo")] | ^^^^^ error: [note] error occurred while deriving `FromForm` - --> $DIR/from_form.rs:54:10 + --> tests/ui-fail-stable/from_form.rs:54:10 | 54 | #[derive(FromForm)] | ^^^^^^^^ @@ -176,13 +176,13 @@ error: [note] error occurred while deriving `FromForm` = note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info) error: expected list `#[field(..)]`, found bare path "field" - --> $DIR/from_form.rs:62:7 + --> tests/ui-fail-stable/from_form.rs:62:7 | 62 | #[field] | ^^^^^ error: [note] error occurred while deriving `FromForm` - --> $DIR/from_form.rs:60:10 + --> tests/ui-fail-stable/from_form.rs:60:10 | 60 | #[derive(FromForm)] | ^^^^^^^^ @@ -190,13 +190,13 @@ error: [note] error occurred while deriving `FromForm` = note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info) error: expected key/value `key = value` - --> $DIR/from_form.rs:68:13 + --> tests/ui-fail-stable/from_form.rs:68:13 | 68 | #[field("blah")] | ^^^^^^ error: [note] error occurred while deriving `FromForm` - --> $DIR/from_form.rs:66:10 + --> tests/ui-fail-stable/from_form.rs:66:10 | 66 | #[derive(FromForm)] | ^^^^^^^^ @@ -204,13 +204,13 @@ error: [note] error occurred while deriving `FromForm` = note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info) error: expected key/value `key = value` - --> $DIR/from_form.rs:74:13 + --> tests/ui-fail-stable/from_form.rs:74:13 | 74 | #[field(123)] | ^^^ error: [note] error occurred while deriving `FromForm` - --> $DIR/from_form.rs:72:10 + --> tests/ui-fail-stable/from_form.rs:72:10 | 72 | #[derive(FromForm)] | ^^^^^^^^ @@ -218,13 +218,13 @@ error: [note] error occurred while deriving `FromForm` = note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info) error: unexpected attribute parameter: `beep` - --> $DIR/from_form.rs:80:13 + --> tests/ui-fail-stable/from_form.rs:80:13 | 80 | #[field(beep = "bop")] | ^^^^ error: [note] error occurred while deriving `FromForm` - --> $DIR/from_form.rs:78:10 + --> tests/ui-fail-stable/from_form.rs:78:10 | 78 | #[derive(FromForm)] | ^^^^^^^^ @@ -232,25 +232,25 @@ error: [note] error occurred while deriving `FromForm` = note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info) error: field has conflicting names - --> $DIR/from_form.rs:86:5 + --> tests/ui-fail-stable/from_form.rs:86:5 | 86 | #[field(name = "blah")] | ^ error: [note] this field name... - --> $DIR/from_form.rs:86:20 + --> tests/ui-fail-stable/from_form.rs:86:20 | 86 | #[field(name = "blah")] | ^^^^^^ error: [note] ...conflicts with this field name - --> $DIR/from_form.rs:87:20 + --> tests/ui-fail-stable/from_form.rs:87:20 | 87 | #[field(name = "blah")] | ^^^^^^ error: [note] error occurred while deriving `FromForm` - --> $DIR/from_form.rs:84:10 + --> tests/ui-fail-stable/from_form.rs:84:10 | 84 | #[derive(FromForm)] | ^^^^^^^^ @@ -258,13 +258,13 @@ error: [note] error occurred while deriving `FromForm` = note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info) error: expected list `#[attr(..)]`, found bare boolean literal - --> $DIR/from_form.rs:93:20 + --> tests/ui-fail-stable/from_form.rs:93:20 | 93 | #[field(name = true)] | ^^^^ error: [note] error occurred while deriving `FromForm` - --> $DIR/from_form.rs:91:10 + --> tests/ui-fail-stable/from_form.rs:91:10 | 91 | #[derive(FromForm)] | ^^^^^^^^ @@ -272,13 +272,13 @@ error: [note] error occurred while deriving `FromForm` = note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info) error: expected expression, found bare path "name" - --> $DIR/from_form.rs:99:13 + --> tests/ui-fail-stable/from_form.rs:99:13 | 99 | #[field(name)] | ^^^^ error: [note] error occurred while deriving `FromForm` - --> $DIR/from_form.rs:97:10 + --> tests/ui-fail-stable/from_form.rs:97:10 | 97 | #[derive(FromForm)] | ^^^^^^^^ @@ -286,13 +286,13 @@ error: [note] error occurred while deriving `FromForm` = note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info) error: expected list `#[attr(..)]`, found bare integer literal - --> $DIR/from_form.rs:105:20 + --> tests/ui-fail-stable/from_form.rs:105:20 | 105 | #[field(name = 123)] | ^^^ error: [note] error occurred while deriving `FromForm` - --> $DIR/from_form.rs:103:10 + --> tests/ui-fail-stable/from_form.rs:103:10 | 103 | #[derive(FromForm)] | ^^^^^^^^ @@ -300,14 +300,14 @@ error: [note] error occurred while deriving `FromForm` = note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info) error: invalid form field name - --- help: field name cannot be `isindex` or contain '&', '=', '?', '.', '[', ']' - --> $DIR/from_form.rs:111:20 + --- help: field name cannot be `isindex` or contain '&', '=', '?', '.', '[', ']' + --> tests/ui-fail-stable/from_form.rs:111:20 | 111 | #[field(name = "hello&world")] | ^^^^^^^^^^^^^ error: [note] error occurred while deriving `FromForm` - --> $DIR/from_form.rs:109:10 + --> tests/ui-fail-stable/from_form.rs:109:10 | 109 | #[derive(FromForm)] | ^^^^^^^^ @@ -315,14 +315,14 @@ error: [note] error occurred while deriving `FromForm` = note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info) error: invalid form field name - --- help: field name cannot be `isindex` or contain '&', '=', '?', '.', '[', ']' - --> $DIR/from_form.rs:117:20 + --- help: field name cannot be `isindex` or contain '&', '=', '?', '.', '[', ']' + --> tests/ui-fail-stable/from_form.rs:117:20 | 117 | #[field(name = "!@#$%^&*()_")] | ^^^^^^^^^^^^^ error: [note] error occurred while deriving `FromForm` - --> $DIR/from_form.rs:115:10 + --> tests/ui-fail-stable/from_form.rs:115:10 | 115 | #[derive(FromForm)] | ^^^^^^^^ @@ -330,14 +330,14 @@ error: [note] error occurred while deriving `FromForm` = note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info) error: invalid form field name - --- help: field name cannot be `isindex` or contain '&', '=', '?', '.', '[', ']' - --> $DIR/from_form.rs:123:20 + --- help: field name cannot be `isindex` or contain '&', '=', '?', '.', '[', ']' + --> tests/ui-fail-stable/from_form.rs:123:20 | 123 | #[field(name = "?")] | ^^^ error: [note] error occurred while deriving `FromForm` - --> $DIR/from_form.rs:121:10 + --> tests/ui-fail-stable/from_form.rs:121:10 | 121 | #[derive(FromForm)] | ^^^^^^^^ @@ -345,14 +345,14 @@ error: [note] error occurred while deriving `FromForm` = note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info) error: invalid form field name - --- help: field name cannot be `isindex` or contain '&', '=', '?', '.', '[', ']' - --> $DIR/from_form.rs:129:20 + --- help: field name cannot be `isindex` or contain '&', '=', '?', '.', '[', ']' + --> tests/ui-fail-stable/from_form.rs:129:20 | 129 | #[field(name = "")] | ^^ error: [note] error occurred while deriving `FromForm` - --> $DIR/from_form.rs:127:10 + --> tests/ui-fail-stable/from_form.rs:127:10 | 127 | #[derive(FromForm)] | ^^^^^^^^ @@ -360,14 +360,14 @@ error: [note] error occurred while deriving `FromForm` = note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info) error: invalid form field name - --- help: field name cannot be `isindex` or contain '&', '=', '?', '.', '[', ']' - --> $DIR/from_form.rs:135:20 + --- help: field name cannot be `isindex` or contain '&', '=', '?', '.', '[', ']' + --> tests/ui-fail-stable/from_form.rs:135:20 | 135 | #[field(name = "a&b")] | ^^^^^ error: [note] error occurred while deriving `FromForm` - --> $DIR/from_form.rs:133:10 + --> tests/ui-fail-stable/from_form.rs:133:10 | 133 | #[derive(FromForm)] | ^^^^^^^^ @@ -375,14 +375,14 @@ error: [note] error occurred while deriving `FromForm` = note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info) error: invalid form field name - --- help: field name cannot be `isindex` or contain '&', '=', '?', '.', '[', ']' - --> $DIR/from_form.rs:141:20 + --- help: field name cannot be `isindex` or contain '&', '=', '?', '.', '[', ']' + --> tests/ui-fail-stable/from_form.rs:141:20 | 141 | #[field(name = "a=")] | ^^^^ error: [note] error occurred while deriving `FromForm` - --> $DIR/from_form.rs:139:10 + --> tests/ui-fail-stable/from_form.rs:139:10 | 139 | #[derive(FromForm)] | ^^^^^^^^ @@ -390,13 +390,13 @@ error: [note] error occurred while deriving `FromForm` = note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info) error: duplicate attribute parameter: default - --> $DIR/from_form.rs:177:26 + --> tests/ui-fail-stable/from_form.rs:177:26 | 177 | #[field(default = 1, default = 2)] | ^^^^^^^ error: [note] error occurred while deriving `FromForm` - --> $DIR/from_form.rs:175:10 + --> tests/ui-fail-stable/from_form.rs:175:10 | 175 | #[derive(FromForm)] | ^^^^^^^^ @@ -404,14 +404,14 @@ error: [note] error occurred while deriving `FromForm` = note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info) error: duplicate default field expression - --- help: at most one `default` or `default_with` is allowed - --> $DIR/from_form.rs:184:23 + --- help: at most one `default` or `default_with` is allowed + --> tests/ui-fail-stable/from_form.rs:184:23 | 184 | #[field(default = 2)] | ^ error: [note] error occurred while deriving `FromForm` - --> $DIR/from_form.rs:181:10 + --> tests/ui-fail-stable/from_form.rs:181:10 | 181 | #[derive(FromForm)] | ^^^^^^^^ @@ -419,20 +419,20 @@ error: [note] error occurred while deriving `FromForm` = note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info) error: duplicate default expressions - --- help: only one of `default` or `default_with` must be used - --> $DIR/from_form.rs:190:23 + --- help: only one of `default` or `default_with` must be used + --> tests/ui-fail-stable/from_form.rs:190:23 | 190 | #[field(default = 1, default_with = None)] | ^ error: [note] other default expression is here - --> $DIR/from_form.rs:190:41 + --> tests/ui-fail-stable/from_form.rs:190:41 | 190 | #[field(default = 1, default_with = None)] | ^^^^ error: [note] error occurred while deriving `FromForm` - --> $DIR/from_form.rs:188:10 + --> tests/ui-fail-stable/from_form.rs:188:10 | 188 | #[derive(FromForm)] | ^^^^^^^^ @@ -440,20 +440,20 @@ error: [note] error occurred while deriving `FromForm` = note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info) error: duplicate default expressions - --- help: only one of `default` or `default_with` must be used - --> $DIR/from_form.rs:197:23 + --- help: only one of `default` or `default_with` must be used + --> tests/ui-fail-stable/from_form.rs:197:23 | 197 | #[field(default = 1)] | ^ error: [note] other default expression is here - --> $DIR/from_form.rs:196:28 + --> tests/ui-fail-stable/from_form.rs:196:28 | 196 | #[field(default_with = None)] | ^^^^ error: [note] error occurred while deriving `FromForm` - --> $DIR/from_form.rs:194:10 + --> tests/ui-fail-stable/from_form.rs:194:10 | 194 | #[derive(FromForm)] | ^^^^^^^^ @@ -461,13 +461,13 @@ error: [note] error occurred while deriving `FromForm` = note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0425]: cannot find function `unknown` in this scope - --> $DIR/from_form.rs:153:24 + --> tests/ui-fail-stable/from_form.rs:153:24 | 153 | #[field(validate = unknown())] | ^^^^^^^ not found in this scope error[E0308]: mismatched types - --> $DIR/from_form.rs:147:24 + --> tests/ui-fail-stable/from_form.rs:147:24 | 147 | #[field(validate = 123)] | -------- ^^^ expected enum `Result`, found integer @@ -478,7 +478,7 @@ error[E0308]: mismatched types found type `{integer}` error[E0308]: mismatched types - --> $DIR/from_form.rs:160:12 + --> tests/ui-fail-stable/from_form.rs:160:12 | 160 | first: String, | ^^^^^^ expected enum `TempFile`, found struct `std::string::String` @@ -487,7 +487,7 @@ error[E0308]: mismatched types found reference `&std::string::String` error[E0308]: mismatched types - --> $DIR/from_form.rs:166:12 + --> tests/ui-fail-stable/from_form.rs:166:12 | 166 | first: String, | ^^^^^^ expected enum `TempFile`, found struct `std::string::String` @@ -496,13 +496,13 @@ error[E0308]: mismatched types found reference `&std::string::String` error[E0308]: mismatched types - --> $DIR/from_form.rs:165:28 + --> tests/ui-fail-stable/from_form.rs:165:28 | 165 | #[field(validate = ext("hello"))] | ^^^^^^^ expected struct `ContentType`, found `&str` error[E0277]: the trait bound `i32: From<&str>` is not satisfied - --> $DIR/from_form.rs:171:23 + --> tests/ui-fail-stable/from_form.rs:171:23 | 171 | #[field(default = "no conversion")] | ^^^^^^^^^^^^^^^ the trait `From<&str>` is not implemented for `i32` @@ -516,7 +516,7 @@ error[E0277]: the trait bound `i32: From<&str>` is not satisfied = note: required because of the requirements on the impl of `Into` for `&str` error[E0308]: mismatched types - --> $DIR/from_form.rs:203:33 + --> tests/ui-fail-stable/from_form.rs:203:33 | 203 | #[field(default_with = Some("hi"))] | ^^^^ expected struct `std::string::String`, found `&str` @@ -524,6 +524,6 @@ error[E0308]: mismatched types help: try using a conversion method | 203 | #[field(default_with = Some("hi".to_string()))] - | ^^^^^^^^^^^^^^^^ + | ++++++++++++ 203 | #[field(default_with = Some("hi".to_string()))] - | ^^^^^^^^^^^^^^^^ + | ++++++++++++ diff --git a/core/codegen/tests/ui-fail-stable/from_form_field.stderr b/core/codegen/tests/ui-fail-stable/from_form_field.stderr index 4e4dcfdb51..5891090441 100644 --- a/core/codegen/tests/ui-fail-stable/from_form_field.stderr +++ b/core/codegen/tests/ui-fail-stable/from_form_field.stderr @@ -1,11 +1,11 @@ error: tuple structs are not supported - --> $DIR/from_form_field.rs:4:1 + --> tests/ui-fail-stable/from_form_field.rs:4:1 | 4 | struct Foo1; | ^^^^^^ error: [note] error occurred while deriving `FromFormField` - --> $DIR/from_form_field.rs:3:10 + --> tests/ui-fail-stable/from_form_field.rs:3:10 | 3 | #[derive(FromFormField)] | ^^^^^^^^^^^^^ @@ -13,13 +13,13 @@ error: [note] error occurred while deriving `FromFormField` = note: this error originates in the derive macro `FromFormField` (in Nightly builds, run with -Z macro-backtrace for more info) error: tuple structs are not supported - --> $DIR/from_form_field.rs:7:1 + --> tests/ui-fail-stable/from_form_field.rs:7:1 | 7 | struct Foo2(usize); | ^^^^^^ error: [note] error occurred while deriving `FromFormField` - --> $DIR/from_form_field.rs:6:10 + --> tests/ui-fail-stable/from_form_field.rs:6:10 | 6 | #[derive(FromFormField)] | ^^^^^^^^^^^^^ @@ -27,13 +27,13 @@ error: [note] error occurred while deriving `FromFormField` = note: this error originates in the derive macro `FromFormField` (in Nightly builds, run with -Z macro-backtrace for more info) error: named structs are not supported - --> $DIR/from_form_field.rs:10:1 + --> tests/ui-fail-stable/from_form_field.rs:10:1 | 10 | struct Foo3 { | ^^^^^^ error: [note] error occurred while deriving `FromFormField` - --> $DIR/from_form_field.rs:9:10 + --> tests/ui-fail-stable/from_form_field.rs:9:10 | 9 | #[derive(FromFormField)] | ^^^^^^^^^^^^^ @@ -41,13 +41,13 @@ error: [note] error occurred while deriving `FromFormField` = note: this error originates in the derive macro `FromFormField` (in Nightly builds, run with -Z macro-backtrace for more info) error: variants cannot have fields - --> $DIR/from_form_field.rs:16:6 + --> tests/ui-fail-stable/from_form_field.rs:16:6 | 16 | A(usize), | ^^^^^^^ error: [note] error occurred while deriving `FromFormField` - --> $DIR/from_form_field.rs:14:10 + --> tests/ui-fail-stable/from_form_field.rs:14:10 | 14 | #[derive(FromFormField)] | ^^^^^^^^^^^^^ @@ -55,13 +55,13 @@ error: [note] error occurred while deriving `FromFormField` = note: this error originates in the derive macro `FromFormField` (in Nightly builds, run with -Z macro-backtrace for more info) error: enum must have at least one variant - --> $DIR/from_form_field.rs:20:1 + --> tests/ui-fail-stable/from_form_field.rs:20:1 | 20 | enum Foo5 { } | ^^^^ error: [note] error occurred while deriving `FromFormField` - --> $DIR/from_form_field.rs:19:10 + --> tests/ui-fail-stable/from_form_field.rs:19:10 | 19 | #[derive(FromFormField)] | ^^^^^^^^^^^^^ @@ -69,13 +69,13 @@ error: [note] error occurred while deriving `FromFormField` = note: this error originates in the derive macro `FromFormField` (in Nightly builds, run with -Z macro-backtrace for more info) error: type generics are not supported - --> $DIR/from_form_field.rs:23:11 + --> tests/ui-fail-stable/from_form_field.rs:23:11 | 23 | enum Foo6 { | ^ error: [note] error occurred while deriving `FromFormField` - --> $DIR/from_form_field.rs:22:10 + --> tests/ui-fail-stable/from_form_field.rs:22:10 | 22 | #[derive(FromFormField)] | ^^^^^^^^^^^^^ @@ -83,13 +83,13 @@ error: [note] error occurred while deriving `FromFormField` = note: this error originates in the derive macro `FromFormField` (in Nightly builds, run with -Z macro-backtrace for more info) error: invalid value: expected string literal - --> $DIR/from_form_field.rs:29:21 + --> tests/ui-fail-stable/from_form_field.rs:29:21 | 29 | #[field(value = 123)] | ^^^ error: [note] error occurred while deriving `FromFormField` - --> $DIR/from_form_field.rs:27:10 + --> tests/ui-fail-stable/from_form_field.rs:27:10 | 27 | #[derive(FromFormField)] | ^^^^^^^^^^^^^ @@ -97,13 +97,13 @@ error: [note] error occurred while deriving `FromFormField` = note: this error originates in the derive macro `FromFormField` (in Nightly builds, run with -Z macro-backtrace for more info) error: expected literal, found bare path "value" - --> $DIR/from_form_field.rs:35:13 + --> tests/ui-fail-stable/from_form_field.rs:35:13 | 35 | #[field(value)] | ^^^^^ error: [note] error occurred while deriving `FromFormField` - --> $DIR/from_form_field.rs:33:10 + --> tests/ui-fail-stable/from_form_field.rs:33:10 | 33 | #[derive(FromFormField)] | ^^^^^^^^^^^^^ @@ -111,25 +111,25 @@ error: [note] error occurred while deriving `FromFormField` = note: this error originates in the derive macro `FromFormField` (in Nightly builds, run with -Z macro-backtrace for more info) error: variant has conflicting values - --> $DIR/from_form_field.rs:41:5 + --> tests/ui-fail-stable/from_form_field.rs:41:5 | 41 | #[field(value = "bar")] | ^ error: [note] this value... - --> $DIR/from_form_field.rs:41:21 + --> tests/ui-fail-stable/from_form_field.rs:41:21 | 41 | #[field(value = "bar")] | ^^^^^ error: [note] ...conflicts with this value - --> $DIR/from_form_field.rs:42:21 + --> tests/ui-fail-stable/from_form_field.rs:42:21 | 42 | #[field(value = "bar")] | ^^^^^ error: [note] error occurred while deriving `FromFormField` - --> $DIR/from_form_field.rs:39:10 + --> tests/ui-fail-stable/from_form_field.rs:39:10 | 39 | #[derive(FromFormField)] | ^^^^^^^^^^^^^ @@ -137,25 +137,25 @@ error: [note] error occurred while deriving `FromFormField` = note: this error originates in the derive macro `FromFormField` (in Nightly builds, run with -Z macro-backtrace for more info) error: field value conflicts with previous value - --> $DIR/from_form_field.rs:50:21 + --> tests/ui-fail-stable/from_form_field.rs:50:21 | 50 | #[field(value = "BAr")] | ^^^^^ error: [help] ...declared in this variant - --> $DIR/from_form_field.rs:50:5 + --> tests/ui-fail-stable/from_form_field.rs:50:5 | 50 | #[field(value = "BAr")] | ^ error: [note] previous field with conflicting name - --> $DIR/from_form_field.rs:48:5 + --> tests/ui-fail-stable/from_form_field.rs:48:5 | 48 | #[field(value = "bar")] | ^ error: [note] error occurred while deriving `FromFormField` - --> $DIR/from_form_field.rs:46:10 + --> tests/ui-fail-stable/from_form_field.rs:46:10 | 46 | #[derive(FromFormField)] | ^^^^^^^^^^^^^ @@ -163,25 +163,25 @@ error: [note] error occurred while deriving `FromFormField` = note: this error originates in the derive macro `FromFormField` (in Nightly builds, run with -Z macro-backtrace for more info) error: field value conflicts with previous value - --> $DIR/from_form_field.rs:57:21 + --> tests/ui-fail-stable/from_form_field.rs:57:21 | 57 | #[field(value = "a")] | ^^^ error: [help] ...declared in this variant - --> $DIR/from_form_field.rs:57:5 + --> tests/ui-fail-stable/from_form_field.rs:57:5 | 57 | #[field(value = "a")] | ^ error: [note] previous field with conflicting name - --> $DIR/from_form_field.rs:56:5 + --> tests/ui-fail-stable/from_form_field.rs:56:5 | 56 | A, | ^ error: [note] error occurred while deriving `FromFormField` - --> $DIR/from_form_field.rs:54:10 + --> tests/ui-fail-stable/from_form_field.rs:54:10 | 54 | #[derive(FromFormField)] | ^^^^^^^^^^^^^ @@ -189,25 +189,25 @@ error: [note] error occurred while deriving `FromFormField` = note: this error originates in the derive macro `FromFormField` (in Nightly builds, run with -Z macro-backtrace for more info) error: variant has conflicting values - --> $DIR/from_form_field.rs:80:5 + --> tests/ui-fail-stable/from_form_field.rs:80:5 | 80 | #[field(value = "FoO")] | ^ error: [note] this value... - --> $DIR/from_form_field.rs:80:21 + --> tests/ui-fail-stable/from_form_field.rs:80:21 | 80 | #[field(value = "FoO")] | ^^^^^ error: [note] ...conflicts with this value - --> $DIR/from_form_field.rs:81:21 + --> tests/ui-fail-stable/from_form_field.rs:81:21 | 81 | #[field(value = "foo")] | ^^^^^ error: [note] error occurred while deriving `FromFormField` - --> $DIR/from_form_field.rs:78:10 + --> tests/ui-fail-stable/from_form_field.rs:78:10 | 78 | #[derive(FromFormField)] | ^^^^^^^^^^^^^ @@ -215,25 +215,25 @@ error: [note] error occurred while deriving `FromFormField` = note: this error originates in the derive macro `FromFormField` (in Nightly builds, run with -Z macro-backtrace for more info) error: field has conflicting names - --> $DIR/from_form_field.rs:87:5 + --> tests/ui-fail-stable/from_form_field.rs:87:5 | 87 | #[field(name = "foo")] | ^ error: [note] this field name... - --> $DIR/from_form_field.rs:87:20 + --> tests/ui-fail-stable/from_form_field.rs:87:20 | 87 | #[field(name = "foo")] | ^^^^^ error: [note] ...conflicts with this field name - --> $DIR/from_form_field.rs:88:28 + --> tests/ui-fail-stable/from_form_field.rs:88:28 | 88 | #[field(name = uncased("FOO"))] | ^^^^^ error: [note] error occurred while deriving `FromForm` - --> $DIR/from_form_field.rs:85:10 + --> tests/ui-fail-stable/from_form_field.rs:85:10 | 85 | #[derive(FromForm)] | ^^^^^^^^ @@ -241,25 +241,25 @@ error: [note] error occurred while deriving `FromForm` = note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info) error: field name conflicts with previous name - --> $DIR/from_form_field.rs:96:20 + --> tests/ui-fail-stable/from_form_field.rs:96:20 | 96 | #[field(name = "foo")] | ^^^^^ error: [help] declared in this field - --> $DIR/from_form_field.rs:96:5 + --> tests/ui-fail-stable/from_form_field.rs:96:5 | 96 | #[field(name = "foo")] | ^ error: [note] previous field with conflicting name - --> $DIR/from_form_field.rs:94:5 + --> tests/ui-fail-stable/from_form_field.rs:94:5 | 94 | #[field(name = "foo")] | ^ error: [note] error occurred while deriving `FromForm` - --> $DIR/from_form_field.rs:92:10 + --> tests/ui-fail-stable/from_form_field.rs:92:10 | 92 | #[derive(FromForm)] | ^^^^^^^^ @@ -267,25 +267,25 @@ error: [note] error occurred while deriving `FromForm` = note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info) error: field name conflicts with previous name - --> $DIR/from_form_field.rs:104:5 + --> tests/ui-fail-stable/from_form_field.rs:104:5 | 104 | hello_there: usize, | ^^^^^^^^^^^ error: [help] declared in this field - --> $DIR/from_form_field.rs:104:5 + --> tests/ui-fail-stable/from_form_field.rs:104:5 | 104 | hello_there: usize, | ^^^^^^^^^^^ error: [note] previous field with conflicting name - --> $DIR/from_form_field.rs:102:5 + --> tests/ui-fail-stable/from_form_field.rs:102:5 | 102 | #[field(name = uncased("HELLO_THERE"))] | ^ error: [note] error occurred while deriving `FromForm` - --> $DIR/from_form_field.rs:100:10 + --> tests/ui-fail-stable/from_form_field.rs:100:10 | 100 | #[derive(FromForm)] | ^^^^^^^^ @@ -293,25 +293,25 @@ error: [note] error occurred while deriving `FromForm` = note: this error originates in the derive macro `FromForm` (in Nightly builds, run with -Z macro-backtrace for more info) error: field name conflicts with previous name - --> $DIR/from_form_field.rs:111:5 + --> tests/ui-fail-stable/from_form_field.rs:111:5 | 111 | hello_there: usize, | ^^^^^^^^^^^ error: [help] declared in this field - --> $DIR/from_form_field.rs:111:5 + --> tests/ui-fail-stable/from_form_field.rs:111:5 | 111 | hello_there: usize, | ^^^^^^^^^^^ error: [note] previous field with conflicting name - --> $DIR/from_form_field.rs:109:5 + --> tests/ui-fail-stable/from_form_field.rs:109:5 | 109 | #[field(name = "hello_there")] | ^ error: [note] error occurred while deriving `FromForm` - --> $DIR/from_form_field.rs:107:10 + --> tests/ui-fail-stable/from_form_field.rs:107:10 | 107 | #[derive(FromForm)] | ^^^^^^^^ diff --git a/core/codegen/tests/ui-fail-stable/from_form_type_errors.stderr b/core/codegen/tests/ui-fail-stable/from_form_type_errors.stderr index 27a490b91d..15c20b812c 100644 --- a/core/codegen/tests/ui-fail-stable/from_form_type_errors.stderr +++ b/core/codegen/tests/ui-fail-stable/from_form_type_errors.stderr @@ -1,5 +1,5 @@ error[E0277]: the trait bound `Unknown: FromFormField<'_>` is not satisfied - --> $DIR/from_form_type_errors.rs:7:12 + --> tests/ui-fail-stable/from_form_type_errors.rs:7:12 | 7 | field: Unknown, | ^^^^^^^ the trait `FromFormField<'_>` is not implemented for `Unknown` @@ -7,7 +7,7 @@ error[E0277]: the trait bound `Unknown: FromFormField<'_>` is not satisfied = note: required because of the requirements on the impl of `FromForm<'r>` for `Unknown` error[E0277]: the trait bound `Foo: FromFormField<'_>` is not satisfied - --> $DIR/from_form_type_errors.rs:14:12 + --> tests/ui-fail-stable/from_form_type_errors.rs:14:12 | 14 | field: Foo, | ^^^ the trait `FromFormField<'_>` is not implemented for `Foo` diff --git a/core/codegen/tests/ui-fail-stable/responder-types.stderr b/core/codegen/tests/ui-fail-stable/responder-types.stderr index 61801f4f7d..152e5302d0 100644 --- a/core/codegen/tests/ui-fail-stable/responder-types.stderr +++ b/core/codegen/tests/ui-fail-stable/responder-types.stderr @@ -1,65 +1,80 @@ error[E0277]: the trait bound `u8: Responder<'_, '_>` is not satisfied - --> $DIR/responder-types.rs:5:5 + --> tests/ui-fail-stable/responder-types.rs:5:5 | 5 | thing: u8, | ^^^^^ the trait `Responder<'_, '_>` is not implemented for `u8` - | - = note: required by `respond_to` error[E0277]: the trait bound `Header<'_>: From` is not satisfied - --> $DIR/responder-types.rs:11:5 - | -11 | other: u8, - | ^^^^^ the trait `From` is not implemented for `Header<'_>` - | - = help: the following implementations were found: - as From<&Cookie<'_>>> - as From<&ExpectCt>> - as From<&Frame>> - as From<&Hsts>> - and 8 others - = note: required because of the requirements on the impl of `Into>` for `u8` + --> tests/ui-fail-stable/responder-types.rs:11:5 + | +11 | other: u8, + | ^^^^^ the trait `From` is not implemented for `Header<'_>` + | + = help: the following implementations were found: + as From<&Cookie<'_>>> + as From<&ExpectCt>> + as From<&Frame>> + as From<&Hsts>> + and 8 others + = note: required because of the requirements on the impl of `Into>` for `u8` +note: required by a bound in `rocket::Response::<'r>::set_header` + --> $WORKSPACE/core/lib/src/response/response.rs + | + | pub fn set_header<'h: 'r, H: Into>>(&mut self, header: H) -> bool { + | ^^^^^^^^^^^^^^^^ required by this bound in `rocket::Response::<'r>::set_header` error[E0277]: the trait bound `u8: Responder<'_, '_>` is not satisfied - --> $DIR/responder-types.rs:16:5 + --> tests/ui-fail-stable/responder-types.rs:16:5 | 16 | thing: u8, | ^^^^^ the trait `Responder<'_, '_>` is not implemented for `u8` - | - = note: required by `respond_to` error[E0277]: the trait bound `Header<'_>: From` is not satisfied - --> $DIR/responder-types.rs:17:5 - | -17 | other: u8, - | ^^^^^ the trait `From` is not implemented for `Header<'_>` - | - = help: the following implementations were found: - as From<&Cookie<'_>>> - as From<&ExpectCt>> - as From<&Frame>> - as From<&Hsts>> - and 8 others - = note: required because of the requirements on the impl of `Into>` for `u8` + --> tests/ui-fail-stable/responder-types.rs:17:5 + | +17 | other: u8, + | ^^^^^ the trait `From` is not implemented for `Header<'_>` + | + = help: the following implementations were found: + as From<&Cookie<'_>>> + as From<&ExpectCt>> + as From<&Frame>> + as From<&Hsts>> + and 8 others + = note: required because of the requirements on the impl of `Into>` for `u8` +note: required by a bound in `rocket::Response::<'r>::set_header` + --> $WORKSPACE/core/lib/src/response/response.rs + | + | pub fn set_header<'h: 'r, H: Into>>(&mut self, header: H) -> bool { + | ^^^^^^^^^^^^^^^^ required by this bound in `rocket::Response::<'r>::set_header` error[E0277]: the trait bound `Header<'_>: From` is not satisfied - --> $DIR/responder-types.rs:24:5 - | -24 | then: String, - | ^^^^ the trait `From` is not implemented for `Header<'_>` - | - = help: the following implementations were found: - as From<&Cookie<'_>>> - as From<&ExpectCt>> - as From<&Frame>> - as From<&Hsts>> - and 8 others - = note: required because of the requirements on the impl of `Into>` for `std::string::String` + --> tests/ui-fail-stable/responder-types.rs:24:5 + | +24 | then: String, + | ^^^^ the trait `From` is not implemented for `Header<'_>` + | + = help: the following implementations were found: + as From<&Cookie<'_>>> + as From<&ExpectCt>> + as From<&Frame>> + as From<&Hsts>> + and 8 others + = note: required because of the requirements on the impl of `Into>` for `std::string::String` +note: required by a bound in `rocket::Response::<'r>::set_header` + --> $WORKSPACE/core/lib/src/response/response.rs + | + | pub fn set_header<'h: 'r, H: Into>>(&mut self, header: H) -> bool { + | ^^^^^^^^^^^^^^^^ required by this bound in `rocket::Response::<'r>::set_header` error[E0277]: the trait bound `usize: Responder<'_, '_>` is not satisfied - --> $DIR/responder-types.rs:28:13 - | -28 | fn foo() -> usize { 0 } - | ^^^^^ the trait `Responder<'_, '_>` is not implemented for `usize` - | - = note: required by `route::handler::, Status, rocket::Data<'o>>>::from` + --> tests/ui-fail-stable/responder-types.rs:28:13 + | +28 | fn foo() -> usize { 0 } + | ^^^^^ the trait `Responder<'_, '_>` is not implemented for `usize` + | +note: required by a bound in `route::handler::, Status, rocket::Data<'o>>>::from` + --> $WORKSPACE/core/lib/src/route/handler.rs + | + | pub fn from>(req: &'r Request<'_>, responder: R) -> Outcome<'r> { + | ^^^^^^^^^^^^^^^^^ required by this bound in `route::handler::, Status, rocket::Data<'o>>>::from` diff --git a/core/codegen/tests/ui-fail-stable/responder.stderr b/core/codegen/tests/ui-fail-stable/responder.stderr index 2f03d1701e..9b57a18409 100644 --- a/core/codegen/tests/ui-fail-stable/responder.stderr +++ b/core/codegen/tests/ui-fail-stable/responder.stderr @@ -1,11 +1,11 @@ error: need at least one field - --> $DIR/responder.rs:4:1 + --> tests/ui-fail-stable/responder.rs:4:1 | 4 | struct Thing1; | ^^^^^^ error: [note] error occurred while deriving `Responder` - --> $DIR/responder.rs:3:10 + --> tests/ui-fail-stable/responder.rs:3:10 | 3 | #[derive(Responder)] | ^^^^^^^^^ @@ -13,13 +13,13 @@ error: [note] error occurred while deriving `Responder` = note: this error originates in the derive macro `Responder` (in Nightly builds, run with -Z macro-backtrace for more info) error: need at least one field - --> $DIR/responder.rs:7:14 + --> tests/ui-fail-stable/responder.rs:7:14 | 7 | struct Thing2(); | ^^ error: [note] error occurred while deriving `Responder` - --> $DIR/responder.rs:6:10 + --> tests/ui-fail-stable/responder.rs:6:10 | 6 | #[derive(Responder)] | ^^^^^^^^^ @@ -27,13 +27,13 @@ error: [note] error occurred while deriving `Responder` = note: this error originates in the derive macro `Responder` (in Nightly builds, run with -Z macro-backtrace for more info) error: need at least one field - --> $DIR/responder.rs:13:12 + --> tests/ui-fail-stable/responder.rs:13:12 | 13 | enum Foo { Bark, } | ^^^^ error: [note] error occurred while deriving `Responder` - --> $DIR/responder.rs:12:10 + --> tests/ui-fail-stable/responder.rs:12:10 | 12 | #[derive(Responder)] | ^^^^^^^^^ @@ -41,13 +41,13 @@ error: [note] error occurred while deriving `Responder` = note: this error originates in the derive macro `Responder` (in Nightly builds, run with -Z macro-backtrace for more info) error: only one lifetime is supported - --> $DIR/responder.rs:16:14 + --> tests/ui-fail-stable/responder.rs:16:14 | 16 | struct Thing4<'a, 'b>(&'a str, &'b str); | ^ error: [note] error occurred while deriving `Responder` - --> $DIR/responder.rs:15:10 + --> tests/ui-fail-stable/responder.rs:15:10 | 15 | #[derive(Responder)] | ^^^^^^^^^ @@ -55,13 +55,13 @@ error: [note] error occurred while deriving `Responder` = note: this error originates in the derive macro `Responder` (in Nightly builds, run with -Z macro-backtrace for more info) error: invalid or unknown content type - --> $DIR/responder.rs:25:27 + --> tests/ui-fail-stable/responder.rs:25:27 | 25 | #[response(content_type = "")] | ^^ error: [note] error occurred while deriving `Responder` - --> $DIR/responder.rs:24:10 + --> tests/ui-fail-stable/responder.rs:24:10 | 24 | #[derive(Responder)] | ^^^^^^^^^ @@ -69,13 +69,13 @@ error: [note] error occurred while deriving `Responder` = note: this error originates in the derive macro `Responder` (in Nightly builds, run with -Z macro-backtrace for more info) error: invalid or unknown content type - --> $DIR/responder.rs:29:27 + --> tests/ui-fail-stable/responder.rs:29:27 | 29 | #[response(content_type = "idk")] | ^^^^^ error: [note] error occurred while deriving `Responder` - --> $DIR/responder.rs:28:10 + --> tests/ui-fail-stable/responder.rs:28:10 | 28 | #[derive(Responder)] | ^^^^^^^^^ @@ -83,13 +83,13 @@ error: [note] error occurred while deriving `Responder` = note: this error originates in the derive macro `Responder` (in Nightly builds, run with -Z macro-backtrace for more info) error: invalid value: expected string literal - --> $DIR/responder.rs:33:27 + --> tests/ui-fail-stable/responder.rs:33:27 | 33 | #[response(content_type = 100)] | ^^^ error: [note] error occurred while deriving `Responder` - --> $DIR/responder.rs:32:10 + --> tests/ui-fail-stable/responder.rs:32:10 | 32 | #[derive(Responder)] | ^^^^^^^^^ @@ -97,13 +97,13 @@ error: [note] error occurred while deriving `Responder` = note: this error originates in the derive macro `Responder` (in Nightly builds, run with -Z macro-backtrace for more info) error: status must be in range [100, 599] - --> $DIR/responder.rs:37:21 + --> tests/ui-fail-stable/responder.rs:37:21 | 37 | #[response(status = 8)] | ^ error: [note] error occurred while deriving `Responder` - --> $DIR/responder.rs:36:10 + --> tests/ui-fail-stable/responder.rs:36:10 | 36 | #[derive(Responder)] | ^^^^^^^^^ @@ -111,13 +111,13 @@ error: [note] error occurred while deriving `Responder` = note: this error originates in the derive macro `Responder` (in Nightly builds, run with -Z macro-backtrace for more info) error: invalid value: expected unsigned integer literal - --> $DIR/responder.rs:41:21 + --> tests/ui-fail-stable/responder.rs:41:21 | 41 | #[response(status = "404")] | ^^^^^ error: [note] error occurred while deriving `Responder` - --> $DIR/responder.rs:40:10 + --> tests/ui-fail-stable/responder.rs:40:10 | 40 | #[derive(Responder)] | ^^^^^^^^^ @@ -125,13 +125,13 @@ error: [note] error occurred while deriving `Responder` = note: this error originates in the derive macro `Responder` (in Nightly builds, run with -Z macro-backtrace for more info) error: invalid value: expected unsigned integer literal - --> $DIR/responder.rs:45:21 + --> tests/ui-fail-stable/responder.rs:45:21 | 45 | #[response(status = "404", content_type = "html")] | ^^^^^ error: [note] error occurred while deriving `Responder` - --> $DIR/responder.rs:44:10 + --> tests/ui-fail-stable/responder.rs:44:10 | 44 | #[derive(Responder)] | ^^^^^^^^^ @@ -139,13 +139,13 @@ error: [note] error occurred while deriving `Responder` = note: this error originates in the derive macro `Responder` (in Nightly builds, run with -Z macro-backtrace for more info) error: invalid value: expected string literal - --> $DIR/responder.rs:49:41 + --> tests/ui-fail-stable/responder.rs:49:41 | 49 | #[response(status = 404, content_type = 120)] | ^^^ error: [note] error occurred while deriving `Responder` - --> $DIR/responder.rs:48:10 + --> tests/ui-fail-stable/responder.rs:48:10 | 48 | #[derive(Responder)] | ^^^^^^^^^ diff --git a/core/codegen/tests/ui-fail-stable/route-attribute-general-syntax.stderr b/core/codegen/tests/ui-fail-stable/route-attribute-general-syntax.stderr index 9a8d3cd056..8f6d4bd2bc 100644 --- a/core/codegen/tests/ui-fail-stable/route-attribute-general-syntax.stderr +++ b/core/codegen/tests/ui-fail-stable/route-attribute-general-syntax.stderr @@ -1,5 +1,5 @@ error: missing expected parameter: `uri` - --> $DIR/route-attribute-general-syntax.rs:4:1 + --> tests/ui-fail-stable/route-attribute-general-syntax.rs:4:1 | 4 | #[get()] | ^^^^^^^^ @@ -7,197 +7,197 @@ error: missing expected parameter: `uri` = note: this error originates in the attribute macro `get` (in Nightly builds, run with -Z macro-backtrace for more info) error: expected `fn` - --- help: #[get] can only be used on functions - --> $DIR/route-attribute-general-syntax.rs:9:1 + --- help: #[get] can only be used on functions + --> tests/ui-fail-stable/route-attribute-general-syntax.rs:9:1 | 9 | struct S; | ^^^^^^ error: expected `fn` - --- help: #[get] can only be used on functions - --> $DIR/route-attribute-general-syntax.rs:12:1 + --- help: #[get] can only be used on functions + --> tests/ui-fail-stable/route-attribute-general-syntax.rs:12:1 | 12 | enum A { } | ^^^^ error: expected `fn` - --- help: #[get] can only be used on functions - --> $DIR/route-attribute-general-syntax.rs:15:1 + --- help: #[get] can only be used on functions + --> tests/ui-fail-stable/route-attribute-general-syntax.rs:15:1 | 15 | trait Foo { } | ^^^^^ error: expected `fn` - --- help: #[get] can only be used on functions - --> $DIR/route-attribute-general-syntax.rs:18:1 + --- help: #[get] can only be used on functions + --> tests/ui-fail-stable/route-attribute-general-syntax.rs:18:1 | 18 | impl S { } | ^^^^ error: expected key/value `key = value` - --> $DIR/route-attribute-general-syntax.rs:21:12 + --> tests/ui-fail-stable/route-attribute-general-syntax.rs:21:12 | 21 | #[get("/", 123)] | ^^^ error: expected key/value `key = value` - --> $DIR/route-attribute-general-syntax.rs:24:12 + --> tests/ui-fail-stable/route-attribute-general-syntax.rs:24:12 | 24 | #[get("/", "/")] | ^^^ error: unexpected keyed parameter: expected literal or identifier - --> $DIR/route-attribute-general-syntax.rs:27:7 + --> tests/ui-fail-stable/route-attribute-general-syntax.rs:27:7 | 27 | #[get(data = "", "/")] | ^^^^ error: unexpected attribute parameter: `unknown` - --> $DIR/route-attribute-general-syntax.rs:30:12 + --> tests/ui-fail-stable/route-attribute-general-syntax.rs:30:12 | 30 | #[get("/", unknown = "foo")] | ^^^^^^^ error: expected key/value `key = value` - --> $DIR/route-attribute-general-syntax.rs:33:12 + --> tests/ui-fail-stable/route-attribute-general-syntax.rs:33:12 | 33 | #[get("/", ...)] | ^^^ error: handler arguments must be named - --- help: to name an ignored handler argument, use `_name` - --> $DIR/route-attribute-general-syntax.rs:39:7 + --- help: to name an ignored handler argument, use `_name` + --> tests/ui-fail-stable/route-attribute-general-syntax.rs:39:7 | 39 | fn c1(_: usize) {} | ^ error: invalid value: expected string literal - --> $DIR/route-attribute-general-syntax.rs:43:7 + --> tests/ui-fail-stable/route-attribute-general-syntax.rs:43:7 | 43 | #[get(100)] | ^^^ error: invalid value: expected string literal - --> $DIR/route-attribute-general-syntax.rs:46:7 + --> tests/ui-fail-stable/route-attribute-general-syntax.rs:46:7 | 46 | #[get('/')] | ^^^ error: invalid value: expected integer literal - --> $DIR/route-attribute-general-syntax.rs:49:19 + --> tests/ui-fail-stable/route-attribute-general-syntax.rs:49:19 | 49 | #[get("/", rank = "1")] | ^^^ error: invalid value: expected integer literal - --> $DIR/route-attribute-general-syntax.rs:52:19 + --> tests/ui-fail-stable/route-attribute-general-syntax.rs:52:19 | 52 | #[get("/", rank = '1')] | ^^^ error: invalid or unknown media type - --> $DIR/route-attribute-general-syntax.rs:57:21 + --> tests/ui-fail-stable/route-attribute-general-syntax.rs:57:21 | 57 | #[get("/", format = "applicationx-custom")] | ^^^^^^^^^^^^^^^^^^^^^ error: invalid or unknown media type - --> $DIR/route-attribute-general-syntax.rs:60:21 + --> tests/ui-fail-stable/route-attribute-general-syntax.rs:60:21 | 60 | #[get("/", format = "")] | ^^ error: invalid or unknown media type - --> $DIR/route-attribute-general-syntax.rs:63:21 + --> tests/ui-fail-stable/route-attribute-general-syntax.rs:63:21 | 63 | #[get("/", format = "//")] | ^^^^ error: invalid or unknown media type - --> $DIR/route-attribute-general-syntax.rs:66:21 + --> tests/ui-fail-stable/route-attribute-general-syntax.rs:66:21 | 66 | #[get("/", format = "/")] | ^^^ error: invalid or unknown media type - --> $DIR/route-attribute-general-syntax.rs:69:21 + --> tests/ui-fail-stable/route-attribute-general-syntax.rs:69:21 | 69 | #[get("/", format = "a/")] | ^^^^ error: invalid or unknown media type - --> $DIR/route-attribute-general-syntax.rs:72:21 + --> tests/ui-fail-stable/route-attribute-general-syntax.rs:72:21 | 72 | #[get("/", format = "/a")] | ^^^^ error: invalid or unknown media type - --> $DIR/route-attribute-general-syntax.rs:75:21 + --> tests/ui-fail-stable/route-attribute-general-syntax.rs:75:21 | 75 | #[get("/", format = "/a/")] | ^^^^^ error: invalid or unknown media type - --> $DIR/route-attribute-general-syntax.rs:78:21 + --> tests/ui-fail-stable/route-attribute-general-syntax.rs:78:21 | 78 | #[get("/", format = "a/b/")] | ^^^^^^ error: invalid or unknown media type - --> $DIR/route-attribute-general-syntax.rs:81:21 + --> tests/ui-fail-stable/route-attribute-general-syntax.rs:81:21 | 81 | #[get("/", format = "unknown")] | ^^^^^^^^^ error: invalid value: expected string literal - --> $DIR/route-attribute-general-syntax.rs:84:21 + --> tests/ui-fail-stable/route-attribute-general-syntax.rs:84:21 | 84 | #[get("/", format = 12)] | ^^ error: invalid value: expected string literal - --> $DIR/route-attribute-general-syntax.rs:87:21 + --> tests/ui-fail-stable/route-attribute-general-syntax.rs:87:21 | 87 | #[get("/", format = 'j')] | ^^^ error: invalid or unknown media type - --> $DIR/route-attribute-general-syntax.rs:90:21 + --> tests/ui-fail-stable/route-attribute-general-syntax.rs:90:21 | 90 | #[get("/", format = "text//foo")] | ^^^^^^^^^^^ error: invalid HTTP method for route handlers - --- help: method must be one of: `GET`, `PUT`, `POST`, `DELETE`, `HEAD`, `PATCH`, `OPTIONS` - --> $DIR/route-attribute-general-syntax.rs:95:9 + --- help: method must be one of: `GET`, `PUT`, `POST`, `DELETE`, `HEAD`, `PATCH`, `OPTIONS` + --> tests/ui-fail-stable/route-attribute-general-syntax.rs:95:9 | 95 | #[route(CONNECT, "/")] | ^^^^^^^ error: invalid HTTP method - --- help: method must be one of: `GET`, `PUT`, `POST`, `DELETE`, `HEAD`, `PATCH`, `OPTIONS` - --> $DIR/route-attribute-general-syntax.rs:98:9 + --- help: method must be one of: `GET`, `PUT`, `POST`, `DELETE`, `HEAD`, `PATCH`, `OPTIONS` + --> tests/ui-fail-stable/route-attribute-general-syntax.rs:98:9 | 98 | #[route(FIX, "/")] | ^^^ error: expected identifier, found string literal - --- help: method must be one of: `GET`, `PUT`, `POST`, `DELETE`, `HEAD`, `PATCH`, `OPTIONS` - --> $DIR/route-attribute-general-syntax.rs:101:9 + --- help: method must be one of: `GET`, `PUT`, `POST`, `DELETE`, `HEAD`, `PATCH`, `OPTIONS` + --> tests/ui-fail-stable/route-attribute-general-syntax.rs:101:9 | 101 | #[route("hi", "/")] | ^^^^ error: expected identifier, found string literal - --- help: method must be one of: `GET`, `PUT`, `POST`, `DELETE`, `HEAD`, `PATCH`, `OPTIONS` - --> $DIR/route-attribute-general-syntax.rs:104:9 + --- help: method must be one of: `GET`, `PUT`, `POST`, `DELETE`, `HEAD`, `PATCH`, `OPTIONS` + --> tests/ui-fail-stable/route-attribute-general-syntax.rs:104:9 | 104 | #[route("GET", "/")] | ^^^^^ error: expected identifier, found integer literal - --- help: method must be one of: `GET`, `PUT`, `POST`, `DELETE`, `HEAD`, `PATCH`, `OPTIONS` - --> $DIR/route-attribute-general-syntax.rs:107:9 + --- help: method must be one of: `GET`, `PUT`, `POST`, `DELETE`, `HEAD`, `PATCH`, `OPTIONS` + --> tests/ui-fail-stable/route-attribute-general-syntax.rs:107:9 | 107 | #[route(120, "/")] | ^^^ diff --git a/core/codegen/tests/ui-fail-stable/route-path-bad-syntax.stderr b/core/codegen/tests/ui-fail-stable/route-path-bad-syntax.stderr index 463166aeca..c48ccadc01 100644 --- a/core/codegen/tests/ui-fail-stable/route-path-bad-syntax.stderr +++ b/core/codegen/tests/ui-fail-stable/route-path-bad-syntax.stderr @@ -1,182 +1,182 @@ error: invalid route URI: expected token '/' but found 'a' at index 0 - --- help: expected URI in origin form: "/path/" - --> $DIR/route-path-bad-syntax.rs:5:7 + --- help: expected URI in origin form: "/path/" + --> tests/ui-fail-stable/route-path-bad-syntax.rs:5:7 | 5 | #[get("a")] | ^^^ error: invalid route URI: unexpected EOF: expected token '/' at index 0 - --- help: expected URI in origin form: "/path/" - --> $DIR/route-path-bad-syntax.rs:8:7 + --- help: expected URI in origin form: "/path/" + --> tests/ui-fail-stable/route-path-bad-syntax.rs:8:7 | 8 | #[get("")] | ^^ error: invalid route URI: expected token '/' but found 'a' at index 0 - --- help: expected URI in origin form: "/path/" - --> $DIR/route-path-bad-syntax.rs:11:7 + --- help: expected URI in origin form: "/path/" + --> tests/ui-fail-stable/route-path-bad-syntax.rs:11:7 | 11 | #[get("a/b/c")] | ^^^^^^^ error: route URIs cannot contain empty segments - --- note: expected "/a/b", found "/a///b" - --> $DIR/route-path-bad-syntax.rs:14:7 + --- note: expected "/a/b", found "/a///b" + --> tests/ui-fail-stable/route-path-bad-syntax.rs:14:7 | 14 | #[get("/a///b")] | ^^^^^^^^ error: route URIs cannot contain empty segments - --- note: expected "/?bat", found "/?bat&&" - --> $DIR/route-path-bad-syntax.rs:17:7 + --- note: expected "/?bat", found "/?bat&&" + --> tests/ui-fail-stable/route-path-bad-syntax.rs:17:7 | 17 | #[get("/?bat&&")] | ^^^^^^^^^ error: route URIs cannot contain empty segments - --- note: expected "/?bat", found "/?bat&&" - --> $DIR/route-path-bad-syntax.rs:20:7 + --- note: expected "/?bat", found "/?bat&&" + --> tests/ui-fail-stable/route-path-bad-syntax.rs:20:7 | 20 | #[get("/?bat&&")] | ^^^^^^^^^ error: route URIs cannot contain empty segments - --- note: expected "/a/b", found "/a/b//" - --> $DIR/route-path-bad-syntax.rs:23:7 + --- note: expected "/a/b", found "/a/b//" + --> tests/ui-fail-stable/route-path-bad-syntax.rs:23:7 | 23 | #[get("/a/b//")] | ^^^^^^^^ error: unused parameter - --> $DIR/route-path-bad-syntax.rs:42:7 + --> tests/ui-fail-stable/route-path-bad-syntax.rs:42:7 | 42 | #[get("/")] | ^^^^^^^^^ error: [note] expected argument named `name` here - --> $DIR/route-path-bad-syntax.rs:43:6 + --> tests/ui-fail-stable/route-path-bad-syntax.rs:43:6 | 43 | fn h0(_name: usize) {} | ^^^^^^^^^^^^^^ error: unused parameter - --> $DIR/route-path-bad-syntax.rs:45:7 + --> tests/ui-fail-stable/route-path-bad-syntax.rs:45:7 | 45 | #[get("/a?")] | ^^^^^^^^ error: [note] expected argument named `r` here - --> $DIR/route-path-bad-syntax.rs:46:6 + --> tests/ui-fail-stable/route-path-bad-syntax.rs:46:6 | 46 | fn h1() {} | ^^ error: unused parameter - --> $DIR/route-path-bad-syntax.rs:48:21 + --> tests/ui-fail-stable/route-path-bad-syntax.rs:48:21 | 48 | #[post("/a", data = "")] | ^^^^^^^^ error: [note] expected argument named `test` here - --> $DIR/route-path-bad-syntax.rs:49:6 + --> tests/ui-fail-stable/route-path-bad-syntax.rs:49:6 | 49 | fn h2() {} | ^^ error: unused parameter - --> $DIR/route-path-bad-syntax.rs:51:7 + --> tests/ui-fail-stable/route-path-bad-syntax.rs:51:7 | 51 | #[get("/<_r>")] | ^^^^^^^ error: [note] expected argument named `_r` here - --> $DIR/route-path-bad-syntax.rs:52:6 + --> tests/ui-fail-stable/route-path-bad-syntax.rs:52:6 | 52 | fn h3() {} | ^^ error: unused parameter - --> $DIR/route-path-bad-syntax.rs:54:7 + --> tests/ui-fail-stable/route-path-bad-syntax.rs:54:7 | 54 | #[get("/<_r>/")] | ^^^^^^^^^^^ error: [note] expected argument named `b` here - --> $DIR/route-path-bad-syntax.rs:55:6 + --> tests/ui-fail-stable/route-path-bad-syntax.rs:55:6 | 55 | fn h4() {} | ^^ error: invalid identifier: `foo_.` - --- help: dynamic parameters must be valid identifiers - --- help: did you mean ``? - --> $DIR/route-path-bad-syntax.rs:60:7 + --- help: dynamic parameters must be valid identifiers + --- help: did you mean ``? + --> tests/ui-fail-stable/route-path-bad-syntax.rs:60:7 | 60 | #[get("/")] | ^^^^^^^^^^ error: invalid identifier: `foo*` - --- help: dynamic parameters must be valid identifiers - --- help: did you mean ``? - --> $DIR/route-path-bad-syntax.rs:63:7 + --- help: dynamic parameters must be valid identifiers + --- help: did you mean ``? + --> tests/ui-fail-stable/route-path-bad-syntax.rs:63:7 | 63 | #[get("/")] | ^^^^^^^^^ error: invalid identifier: `!` - --- help: dynamic parameters must be valid identifiers - --- help: did you mean ``? - --> $DIR/route-path-bad-syntax.rs:66:7 + --- help: dynamic parameters must be valid identifiers + --- help: did you mean ``? + --> tests/ui-fail-stable/route-path-bad-syntax.rs:66:7 | 66 | #[get("/")] | ^^^^^^ error: invalid identifier: `name>:`? - --> $DIR/route-path-bad-syntax.rs:69:7 + --- help: dynamic parameters must be valid identifiers + --- help: did you mean ``? + --> tests/ui-fail-stable/route-path-bad-syntax.rs:69:7 | 69 | #[get("/:")] | ^^^^^^^^^^^^^^ error: unexpected static parameter - --- help: parameter must be dynamic: `` - --> $DIR/route-path-bad-syntax.rs:74:19 + --- help: parameter must be dynamic: `` + --> tests/ui-fail-stable/route-path-bad-syntax.rs:74:19 | 74 | #[get("/", data = "foo")] | ^^^^^ error: parameter cannot be trailing - --- help: did you mean ``? - --> $DIR/route-path-bad-syntax.rs:77:19 + --- help: did you mean ``? + --> tests/ui-fail-stable/route-path-bad-syntax.rs:77:19 | 77 | #[get("/", data = "")] | ^^^^^^^^^ error: unexpected static parameter - --- help: parameter must be dynamic: `` - --> $DIR/route-path-bad-syntax.rs:80:19 + --- help: parameter must be dynamic: `` + --> tests/ui-fail-stable/route-path-bad-syntax.rs:80:19 | 80 | #[get("/", data = "`? - --> $DIR/route-path-bad-syntax.rs:83:19 + --- help: dynamic parameters must be valid identifiers + --- help: did you mean ``? + --> tests/ui-fail-stable/route-path-bad-syntax.rs:83:19 | 83 | #[get("/", data = "")] | ^^^^^^^^^ error: handler arguments must be named - --- help: to name an ignored handler argument, use `_name` - --> $DIR/route-path-bad-syntax.rs:89:7 + --- help: to name an ignored handler argument, use `_name` + --> tests/ui-fail-stable/route-path-bad-syntax.rs:89:7 | 89 | fn k0(_: usize) {} | ^ error: parameters cannot be empty - --> $DIR/route-path-bad-syntax.rs:93:7 + --> tests/ui-fail-stable/route-path-bad-syntax.rs:93:7 | 93 | #[get("/<>")] | ^^^^^ diff --git a/core/codegen/tests/ui-fail-stable/route-type-errors.stderr b/core/codegen/tests/ui-fail-stable/route-type-errors.stderr index e9f53ff919..d2abc24c47 100644 --- a/core/codegen/tests/ui-fail-stable/route-type-errors.stderr +++ b/core/codegen/tests/ui-fail-stable/route-type-errors.stderr @@ -1,21 +1,17 @@ error[E0277]: the trait bound `Q: FromParam<'_>` is not satisfied - --> $DIR/route-type-errors.rs:6:12 + --> tests/ui-fail-stable/route-type-errors.rs:6:12 | 6 | fn f0(foo: Q) {} | ^ the trait `FromParam<'_>` is not implemented for `Q` - | - = note: required by `from_param` error[E0277]: the trait bound `Q: FromSegments<'_>` is not satisfied - --> $DIR/route-type-errors.rs:9:12 + --> tests/ui-fail-stable/route-type-errors.rs:9:12 | 9 | fn f1(foo: Q) {} | ^ the trait `FromSegments<'_>` is not implemented for `Q` - | - = note: required by `from_segments` error[E0277]: the trait bound `Q: FromFormField<'_>` is not satisfied - --> $DIR/route-type-errors.rs:12:12 + --> tests/ui-fail-stable/route-type-errors.rs:12:12 | 12 | fn f2(foo: Q) {} | ^ the trait `FromFormField<'_>` is not implemented for `Q` @@ -23,7 +19,7 @@ error[E0277]: the trait bound `Q: FromFormField<'_>` is not satisfied = note: required because of the requirements on the impl of `FromForm<'_>` for `Q` error[E0277]: the trait bound `Q: FromFormField<'_>` is not satisfied - --> $DIR/route-type-errors.rs:15:12 + --> tests/ui-fail-stable/route-type-errors.rs:15:12 | 15 | fn f3(foo: Q) {} | ^ the trait `FromFormField<'_>` is not implemented for `Q` @@ -31,58 +27,37 @@ error[E0277]: the trait bound `Q: FromFormField<'_>` is not satisfied = note: required because of the requirements on the impl of `FromForm<'_>` for `Q` error[E0277]: the trait bound `Q: FromData<'_>` is not satisfied - --> $DIR/route-type-errors.rs:18:12 - | -18 | fn f4(foo: Q) {} - | ^ the trait `FromData<'_>` is not implemented for `Q` - | - ::: $WORKSPACE/core/lib/src/data/from_data.rs - | - | async fn from_data(req: &'r Request<'_>, data: Data<'r>) -> Outcome<'r, Self>; - | -- required by this bound in `rocket::data::FromData::from_data` + --> tests/ui-fail-stable/route-type-errors.rs:18:12 + | +18 | fn f4(foo: Q) {} + | ^ the trait `FromData<'_>` is not implemented for `Q` error[E0277]: the trait bound `Q: FromRequest<'_>` is not satisfied - --> $DIR/route-type-errors.rs:21:10 - | -21 | fn f5(a: Q, foo: Q) {} - | ^ the trait `FromRequest<'_>` is not implemented for `Q` - | - ::: $WORKSPACE/core/lib/src/request/from_request.rs - | - | async fn from_request(request: &'r Request<'_>) -> Outcome; - | -- required by this bound in `from_request` + --> tests/ui-fail-stable/route-type-errors.rs:21:10 + | +21 | fn f5(a: Q, foo: Q) {} + | ^ the trait `FromRequest<'_>` is not implemented for `Q` error[E0277]: the trait bound `Q: FromParam<'_>` is not satisfied - --> $DIR/route-type-errors.rs:21:18 + --> tests/ui-fail-stable/route-type-errors.rs:21:18 | 21 | fn f5(a: Q, foo: Q) {} | ^ the trait `FromParam<'_>` is not implemented for `Q` - | - = note: required by `from_param` error[E0277]: the trait bound `Q: FromRequest<'_>` is not satisfied - --> $DIR/route-type-errors.rs:24:10 - | -24 | fn f6(a: Q, foo: Q, good: usize, bar: Q) {} - | ^ the trait `FromRequest<'_>` is not implemented for `Q` - | - ::: $WORKSPACE/core/lib/src/request/from_request.rs - | - | async fn from_request(request: &'r Request<'_>) -> Outcome; - | -- required by this bound in `from_request` + --> tests/ui-fail-stable/route-type-errors.rs:24:10 + | +24 | fn f6(a: Q, foo: Q, good: usize, bar: Q) {} + | ^ the trait `FromRequest<'_>` is not implemented for `Q` error[E0277]: the trait bound `Q: FromParam<'_>` is not satisfied - --> $DIR/route-type-errors.rs:24:18 + --> tests/ui-fail-stable/route-type-errors.rs:24:18 | 24 | fn f6(a: Q, foo: Q, good: usize, bar: Q) {} | ^ the trait `FromParam<'_>` is not implemented for `Q` - | - = note: required by `from_param` error[E0277]: the trait bound `Q: FromParam<'_>` is not satisfied - --> $DIR/route-type-errors.rs:24:39 + --> tests/ui-fail-stable/route-type-errors.rs:24:39 | 24 | fn f6(a: Q, foo: Q, good: usize, bar: Q) {} | ^ the trait `FromParam<'_>` is not implemented for `Q` - | - = note: required by `from_param` diff --git a/core/codegen/tests/ui-fail-stable/route-warnings.stderr b/core/codegen/tests/ui-fail-stable/route-warnings.stderr index 4b3e10fd2e..df27ca5770 100644 --- a/core/codegen/tests/ui-fail-stable/route-warnings.stderr +++ b/core/codegen/tests/ui-fail-stable/route-warnings.stderr @@ -1,5 +1,5 @@ error: checking for warnings! - --> $DIR/route-warnings.rs:25:5 + --> tests/ui-fail-stable/route-warnings.rs:25:5 | 25 | compile_error!("checking for warnings!") | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/core/codegen/tests/ui-fail-stable/routes.stderr b/core/codegen/tests/ui-fail-stable/routes.stderr index 1ec99110b2..71aab18b63 100644 --- a/core/codegen/tests/ui-fail-stable/routes.stderr +++ b/core/codegen/tests/ui-fail-stable/routes.stderr @@ -1,17 +1,17 @@ error: expected `,` - --> $DIR/routes.rs:4:23 + --> tests/ui-fail-stable/routes.rs:4:23 | 4 | let _ = routes![a b]; | ^ error: expected identifier - --> $DIR/routes.rs:6:24 + --> tests/ui-fail-stable/routes.rs:6:24 | 6 | let _ = routes![a::, ]; | ^ error: unexpected end of input, expected identifier - --> $DIR/routes.rs:7:13 + --> tests/ui-fail-stable/routes.rs:7:13 | 7 | let _ = routes![a::]; | ^^^^^^^^^^^^ diff --git a/core/codegen/tests/ui-fail-stable/typed-uri-bad-type.stderr b/core/codegen/tests/ui-fail-stable/typed-uri-bad-type.stderr index 6d6348ac8f..6b7c64cc40 100644 --- a/core/codegen/tests/ui-fail-stable/typed-uri-bad-type.stderr +++ b/core/codegen/tests/ui-fail-stable/typed-uri-bad-type.stderr @@ -1,11 +1,5 @@ -error[E0271]: type mismatch resolving `>::Error == &str` - --> $DIR/typed-uri-bad-type.rs:22:37 - | -22 | fn optionals(id: Option, name: Result) { } - | ^^^^^^ expected enum `Infallible`, found `&str` - error[E0277]: the trait bound `usize: FromUriParam` is not satisfied - --> $DIR/typed-uri-bad-type.rs:45:22 + --> tests/ui-fail-stable/typed-uri-bad-type.rs:45:22 | 45 | uri!(simple(id = "hi")); | ^^^^ the trait `FromUriParam` is not implemented for `usize` @@ -14,10 +8,9 @@ error[E0277]: the trait bound `usize: FromUriParam> > > - = note: required by `from_uri_param` error[E0277]: the trait bound `usize: FromUriParam` is not satisfied - --> $DIR/typed-uri-bad-type.rs:47:17 + --> tests/ui-fail-stable/typed-uri-bad-type.rs:47:17 | 47 | uri!(simple("hello")); | ^^^^^^^ the trait `FromUriParam` is not implemented for `usize` @@ -26,10 +19,9 @@ error[E0277]: the trait bound `usize: FromUriParam> > > - = note: required by `from_uri_param` error[E0277]: the trait bound `usize: FromUriParam` is not satisfied - --> $DIR/typed-uri-bad-type.rs:49:22 + --> tests/ui-fail-stable/typed-uri-bad-type.rs:49:22 | 49 | uri!(simple(id = 239239i64)); | ^^^^^^^^^ the trait `FromUriParam` is not implemented for `usize` @@ -38,18 +30,15 @@ error[E0277]: the trait bound `usize: FromUriParam> > > - = note: required by `from_uri_param` error[E0277]: the trait bound `S: FromUriParam` is not satisfied - --> $DIR/typed-uri-bad-type.rs:51:30 + --> tests/ui-fail-stable/typed-uri-bad-type.rs:51:30 | 51 | uri!(not_uri_display(10, S)); | ^ the trait `FromUriParam` is not implemented for `S` - | - = note: required by `from_uri_param` error[E0277]: the trait bound `i32: FromUriParam>` is not satisfied - --> $DIR/typed-uri-bad-type.rs:56:25 + --> tests/ui-fail-stable/typed-uri-bad-type.rs:56:25 | 56 | uri!(optionals(id = Some(10), name = Ok("bob".into()))); | ^^^^ the trait `FromUriParam>` is not implemented for `i32` @@ -59,10 +48,9 @@ error[E0277]: the trait bound `i32: FromUriParam> > = note: required because of the requirements on the impl of `FromUriParam>` for `std::option::Option` - = note: required by `from_uri_param` error[E0277]: the trait bound `std::string::String: FromUriParam>` is not satisfied - --> $DIR/typed-uri-bad-type.rs:56:42 + --> tests/ui-fail-stable/typed-uri-bad-type.rs:56:42 | 56 | uri!(optionals(id = Some(10), name = Ok("bob".into()))); | ^^ the trait `FromUriParam>` is not implemented for `std::string::String` @@ -74,10 +62,9 @@ error[E0277]: the trait bound `std::string::String: FromUriParam> and 2 others = note: required because of the requirements on the impl of `FromUriParam>` for `Result` - = note: required by `from_uri_param` error[E0277]: the trait bound `isize: FromUriParam` is not satisfied - --> $DIR/typed-uri-bad-type.rs:58:19 + --> tests/ui-fail-stable/typed-uri-bad-type.rs:58:19 | 58 | uri!(simple_q("hi")); | ^^^^ the trait `FromUriParam` is not implemented for `isize` @@ -86,10 +73,9 @@ error[E0277]: the trait bound `isize: FromUriParam> > > - = note: required by `from_uri_param` error[E0277]: the trait bound `isize: FromUriParam` is not satisfied - --> $DIR/typed-uri-bad-type.rs:60:24 + --> tests/ui-fail-stable/typed-uri-bad-type.rs:60:24 | 60 | uri!(simple_q(id = "hi")); | ^^^^ the trait `FromUriParam` is not implemented for `isize` @@ -98,56 +84,51 @@ error[E0277]: the trait bound `isize: FromUriParam> > > - = note: required by `from_uri_param` error[E0277]: the trait bound `S: FromUriParam` is not satisfied - --> $DIR/typed-uri-bad-type.rs:62:23 + --> tests/ui-fail-stable/typed-uri-bad-type.rs:62:23 | 62 | uri!(other_q(100, S)); | ^ the trait `FromUriParam` is not implemented for `S` - | - = note: required by `from_uri_param` error[E0277]: the trait bound `S: FromUriParam` is not satisfied - --> $DIR/typed-uri-bad-type.rs:64:25 + --> tests/ui-fail-stable/typed-uri-bad-type.rs:64:25 | 64 | uri!(other_q(rest = S, id = 100)); | ^ the trait `FromUriParam` is not implemented for `S` - | - = note: required by `from_uri_param` error[E0277]: the trait bound `S: Ignorable` is not satisfied - --> $DIR/typed-uri-bad-type.rs:66:25 + --> tests/ui-fail-stable/typed-uri-bad-type.rs:66:25 | 66 | uri!(other_q(rest = _, id = 100)); | ^ the trait `Ignorable` is not implemented for `S` | - ::: $WORKSPACE/core/http/src/uri/fmt/uri_display.rs +note: required by a bound in `assert_ignorable` + --> $WORKSPACE/core/http/src/uri/fmt/uri_display.rs | | pub fn assert_ignorable>() { } - | ------------ required by this bound in `assert_ignorable` + | ^^^^^^^^^^^^ required by this bound in `assert_ignorable` error[E0277]: the trait bound `usize: Ignorable` is not satisfied - --> $DIR/typed-uri-bad-type.rs:68:33 + --> tests/ui-fail-stable/typed-uri-bad-type.rs:68:33 | 68 | uri!(other_q(rest = S, id = _)); | ^ the trait `Ignorable` is not implemented for `usize` | - ::: $WORKSPACE/core/http/src/uri/fmt/uri_display.rs +note: required by a bound in `assert_ignorable` + --> $WORKSPACE/core/http/src/uri/fmt/uri_display.rs | | pub fn assert_ignorable>() { } - | ------------ required by this bound in `assert_ignorable` + | ^^^^^^^^^^^^ required by this bound in `assert_ignorable` error[E0277]: the trait bound `S: FromUriParam` is not satisfied - --> $DIR/typed-uri-bad-type.rs:68:25 + --> tests/ui-fail-stable/typed-uri-bad-type.rs:68:25 | 68 | uri!(other_q(rest = S, id = _)); | ^ the trait `FromUriParam` is not implemented for `S` - | - = note: required by `from_uri_param` error[E0277]: the trait bound `usize: FromUriParam` is not satisfied - --> $DIR/typed-uri-bad-type.rs:77:40 + --> tests/ui-fail-stable/typed-uri-bad-type.rs:77:40 | 77 | uri!(uri!("?foo#bar"), simple(id = "hi")); | ^^^^ the trait `FromUriParam` is not implemented for `usize` @@ -156,16 +137,23 @@ error[E0277]: the trait bound `usize: FromUriParam> > > - = note: required by `from_uri_param` error[E0277]: the trait bound `rocket::http::uri::Reference<'_>: ValidRoutePrefix` is not satisfied - --> $DIR/typed-uri-bad-type.rs:77:15 - | -77 | uri!(uri!("?foo#bar"), simple(id = "hi")); - | ^^^^^^^^^^ the trait `ValidRoutePrefix` is not implemented for `rocket::http::uri::Reference<'_>` + --> tests/ui-fail-stable/typed-uri-bad-type.rs:77:15 + | +77 | uri!(uri!("?foo#bar"), simple(id = "hi")); + | --- ^^^^^^^^^^ the trait `ValidRoutePrefix` is not implemented for `rocket::http::uri::Reference<'_>` + | | + | required by a bound introduced by this call + | +note: required by a bound in `RouteUriBuilder::with_prefix` + --> $WORKSPACE/core/http/src/uri/fmt/formatter.rs + | + | pub fn with_prefix(self, p: P) -> PrefixedRouteUri { + | ^^^^^^^^^^^^^^^^ required by this bound in `RouteUriBuilder::with_prefix` error[E0277]: the trait bound `usize: FromUriParam` is not satisfied - --> $DIR/typed-uri-bad-type.rs:78:33 + --> tests/ui-fail-stable/typed-uri-bad-type.rs:78:33 | 78 | uri!(uri!("*"), simple(id = "hi")); | ^^^^ the trait `FromUriParam` is not implemented for `usize` @@ -174,16 +162,23 @@ error[E0277]: the trait bound `usize: FromUriParam> > > - = note: required by `from_uri_param` error[E0277]: the trait bound `rocket::http::uri::Asterisk: ValidRoutePrefix` is not satisfied - --> $DIR/typed-uri-bad-type.rs:78:15 - | -78 | uri!(uri!("*"), simple(id = "hi")); - | ^^^ the trait `ValidRoutePrefix` is not implemented for `rocket::http::uri::Asterisk` + --> tests/ui-fail-stable/typed-uri-bad-type.rs:78:15 + | +78 | uri!(uri!("*"), simple(id = "hi")); + | --- ^^^ the trait `ValidRoutePrefix` is not implemented for `rocket::http::uri::Asterisk` + | | + | required by a bound introduced by this call + | +note: required by a bound in `RouteUriBuilder::with_prefix` + --> $WORKSPACE/core/http/src/uri/fmt/formatter.rs + | + | pub fn with_prefix(self, p: P) -> PrefixedRouteUri { + | ^^^^^^^^^^^^^^^^ required by this bound in `RouteUriBuilder::with_prefix` error[E0277]: the trait bound `usize: FromUriParam` is not satisfied - --> $DIR/typed-uri-bad-type.rs:81:25 + --> tests/ui-fail-stable/typed-uri-bad-type.rs:81:25 | 81 | uri!(_, simple(id = "hi"), uri!("*")); | ^^^^ the trait `FromUriParam` is not implemented for `usize` @@ -192,16 +187,23 @@ error[E0277]: the trait bound `usize: FromUriParam> > > - = note: required by `from_uri_param` error[E0277]: the trait bound `rocket::http::uri::Asterisk: ValidRouteSuffix>` is not satisfied - --> $DIR/typed-uri-bad-type.rs:81:37 - | -81 | uri!(_, simple(id = "hi"), uri!("*")); - | ^^^ the trait `ValidRouteSuffix>` is not implemented for `rocket::http::uri::Asterisk` + --> tests/ui-fail-stable/typed-uri-bad-type.rs:81:37 + | +81 | uri!(_, simple(id = "hi"), uri!("*")); + | --- ^^^ the trait `ValidRouteSuffix>` is not implemented for `rocket::http::uri::Asterisk` + | | + | required by a bound introduced by this call + | +note: required by a bound in `RouteUriBuilder::with_suffix` + --> $WORKSPACE/core/http/src/uri/fmt/formatter.rs + | + | where S: ValidRouteSuffix> + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `RouteUriBuilder::with_suffix` error[E0277]: the trait bound `usize: FromUriParam` is not satisfied - --> $DIR/typed-uri-bad-type.rs:82:25 + --> tests/ui-fail-stable/typed-uri-bad-type.rs:82:25 | 82 | uri!(_, simple(id = "hi"), uri!("/foo/bar")); | ^^^^ the trait `FromUriParam` is not implemented for `usize` @@ -210,10 +212,23 @@ error[E0277]: the trait bound `usize: FromUriParam> > > - = note: required by `from_uri_param` error[E0277]: the trait bound `rocket::http::uri::Origin<'_>: ValidRouteSuffix>` is not satisfied - --> $DIR/typed-uri-bad-type.rs:82:37 + --> tests/ui-fail-stable/typed-uri-bad-type.rs:82:37 + | +82 | uri!(_, simple(id = "hi"), uri!("/foo/bar")); + | --- ^^^^^^^^^^ the trait `ValidRouteSuffix>` is not implemented for `rocket::http::uri::Origin<'_>` + | | + | required by a bound introduced by this call + | +note: required by a bound in `RouteUriBuilder::with_suffix` + --> $WORKSPACE/core/http/src/uri/fmt/formatter.rs + | + | where S: ValidRouteSuffix> + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `RouteUriBuilder::with_suffix` + +error[E0271]: type mismatch resolving `>::Error == &str` + --> tests/ui-fail-stable/typed-uri-bad-type.rs:22:37 | -82 | uri!(_, simple(id = "hi"), uri!("/foo/bar")); - | ^^^^^^^^^^ the trait `ValidRouteSuffix>` is not implemented for `rocket::http::uri::Origin<'_>` +22 | fn optionals(id: Option, name: Result) { } + | ^^^^^^ expected enum `Infallible`, found `&str` diff --git a/core/codegen/tests/ui-fail-stable/typed-uris-bad-params.stderr b/core/codegen/tests/ui-fail-stable/typed-uris-bad-params.stderr index aad543f3b5..d5757f6177 100644 --- a/core/codegen/tests/ui-fail-stable/typed-uris-bad-params.stderr +++ b/core/codegen/tests/ui-fail-stable/typed-uris-bad-params.stderr @@ -1,279 +1,279 @@ error: expected identifier - --> $DIR/typed-uris-bad-params.rs:63:18 + --> tests/ui-fail-stable/typed-uris-bad-params.rs:63:18 | 63 | uri!(ignored(_ = 10)); | ^ error: route expects 1 parameter but 2 were supplied - --- note: route `ignored` has uri "/<_>" - --> $DIR/typed-uris-bad-params.rs:69:18 + --- note: route `ignored` has uri "/<_>" + --> tests/ui-fail-stable/typed-uris-bad-params.rs:69:18 | 69 | uri!(ignored(10, "10")); | ^^ error: expected unnamed arguments due to ignored parameters - --- note: uri for route `ignored` ignores path parameters: "/<_>" - --> $DIR/typed-uris-bad-params.rs:67:18 + --- note: uri for route `ignored` ignores path parameters: "/<_>" + --> tests/ui-fail-stable/typed-uris-bad-params.rs:67:18 | 67 | uri!(ignored(num = 10)); | ^^^ error: route expects 1 parameter but 2 were supplied - --- note: route `ignored` has uri "/<_>" - --> $DIR/typed-uris-bad-params.rs:65:18 + --- note: route `ignored` has uri "/<_>" + --> tests/ui-fail-stable/typed-uris-bad-params.rs:65:18 | 65 | uri!(ignored(10, 20)); | ^^ error: path parameters cannot be ignored - --> $DIR/typed-uris-bad-params.rs:61:18 + --> tests/ui-fail-stable/typed-uris-bad-params.rs:61:18 | 61 | uri!(ignored(_)); | ^ error: path parameters cannot be ignored - --> $DIR/typed-uris-bad-params.rs:59:36 + --> tests/ui-fail-stable/typed-uris-bad-params.rs:59:36 | 59 | uri!(optionals(id = 10, name = _)); | ^ error: path parameters cannot be ignored - --> $DIR/typed-uris-bad-params.rs:57:25 + --> tests/ui-fail-stable/typed-uris-bad-params.rs:57:25 | 57 | uri!(optionals(id = _, name = "bob".into())); | ^ error: invalid parameters for `has_two` route uri - --- note: uri parameters are: id: i32, name: String - --- help: missing parameter: `name` - --> $DIR/typed-uris-bad-params.rs:55:18 + --- note: uri parameters are: id: i32, name: String + --- help: missing parameter: `name` + --> tests/ui-fail-stable/typed-uris-bad-params.rs:55:18 | 55 | uri!(has_two(id = 100, cookies = "hi")); | ^^ error: [help] unknown parameter: `cookies` - --> $DIR/typed-uris-bad-params.rs:55:28 + --> tests/ui-fail-stable/typed-uris-bad-params.rs:55:28 | 55 | uri!(has_two(id = 100, cookies = "hi")); | ^^^^^^^ error: invalid parameters for `has_two` route uri - --- note: uri parameters are: id: i32, name: String - --- help: missing parameter: `name` - --> $DIR/typed-uris-bad-params.rs:53:18 + --- note: uri parameters are: id: i32, name: String + --- help: missing parameter: `name` + --> tests/ui-fail-stable/typed-uris-bad-params.rs:53:18 | 53 | uri!(has_two(cookies = "hi", id = 100, id = 10, id = 10)); | ^^^^^^^ error: [help] unknown parameter: `cookies` - --> $DIR/typed-uris-bad-params.rs:53:18 + --> tests/ui-fail-stable/typed-uris-bad-params.rs:53:18 | 53 | uri!(has_two(cookies = "hi", id = 100, id = 10, id = 10)); | ^^^^^^^ error: [help] duplicate parameter: `id` - --> $DIR/typed-uris-bad-params.rs:53:44 + --> tests/ui-fail-stable/typed-uris-bad-params.rs:53:44 | 53 | uri!(has_two(cookies = "hi", id = 100, id = 10, id = 10)); | ^^ error: invalid parameters for `has_two` route uri - --- note: uri parameters are: id: i32, name: String - --- help: missing parameter: `id` - --> $DIR/typed-uris-bad-params.rs:51:18 + --- note: uri parameters are: id: i32, name: String + --- help: missing parameter: `id` + --> tests/ui-fail-stable/typed-uris-bad-params.rs:51:18 | 51 | uri!(has_two(name = "hi")); | ^^^^ error: invalid parameters for `has_two` route uri - --- note: uri parameters are: id: i32, name: String - --- help: missing parameter: `name` - --> $DIR/typed-uris-bad-params.rs:49:18 + --- note: uri parameters are: id: i32, name: String + --- help: missing parameter: `name` + --> tests/ui-fail-stable/typed-uris-bad-params.rs:49:18 | 49 | uri!(has_two(id = 100, id = 100, )); | ^^ error: [help] duplicate parameter: `id` - --> $DIR/typed-uris-bad-params.rs:49:28 + --> tests/ui-fail-stable/typed-uris-bad-params.rs:49:28 | 49 | uri!(has_two(id = 100, id = 100, )); | ^^ error: invalid parameters for `has_one_guarded` route uri - --- note: uri parameters are: id: i32 - --> $DIR/typed-uris-bad-params.rs:47:26 + --- note: uri parameters are: id: i32 + --> tests/ui-fail-stable/typed-uris-bad-params.rs:47:26 | 47 | uri!(has_one_guarded(id = 100, cookies = "hi")); | ^^ error: [help] unknown parameter: `cookies` - --> $DIR/typed-uris-bad-params.rs:47:36 + --> tests/ui-fail-stable/typed-uris-bad-params.rs:47:36 | 47 | uri!(has_one_guarded(id = 100, cookies = "hi")); | ^^^^^^^ error: invalid parameters for `has_one_guarded` route uri - --- note: uri parameters are: id: i32 - --> $DIR/typed-uris-bad-params.rs:45:26 + --- note: uri parameters are: id: i32 + --> tests/ui-fail-stable/typed-uris-bad-params.rs:45:26 | 45 | uri!(has_one_guarded(cookies = "hi", id = 100)); | ^^^^^^^ error: [help] unknown parameter: `cookies` - --> $DIR/typed-uris-bad-params.rs:45:26 + --> tests/ui-fail-stable/typed-uris-bad-params.rs:45:26 | 45 | uri!(has_one_guarded(cookies = "hi", id = 100)); | ^^^^^^^ error: invalid parameters for `has_one` route uri - --- note: uri parameters are: id: i32 - --- help: missing parameter: `id` - --> $DIR/typed-uris-bad-params.rs:43:18 + --- note: uri parameters are: id: i32 + --- help: missing parameter: `id` + --> tests/ui-fail-stable/typed-uris-bad-params.rs:43:18 | 43 | uri!(has_one(name = "hi")); | ^^^^ error: [help] unknown parameter: `name` - --> $DIR/typed-uris-bad-params.rs:43:18 + --> tests/ui-fail-stable/typed-uris-bad-params.rs:43:18 | 43 | uri!(has_one(name = "hi")); | ^^^^ error: invalid parameters for `has_one` route uri - --- note: uri parameters are: id: i32 - --> $DIR/typed-uris-bad-params.rs:41:18 + --- note: uri parameters are: id: i32 + --> tests/ui-fail-stable/typed-uris-bad-params.rs:41:18 | 41 | uri!(has_one(id = 100, id = 100, )); | ^^ error: [help] duplicate parameter: `id` - --> $DIR/typed-uris-bad-params.rs:41:28 + --> tests/ui-fail-stable/typed-uris-bad-params.rs:41:28 | 41 | uri!(has_one(id = 100, id = 100, )); | ^^ error: invalid parameters for `has_one` route uri - --- note: uri parameters are: id: i32 - --> $DIR/typed-uris-bad-params.rs:39:18 + --- note: uri parameters are: id: i32 + --> tests/ui-fail-stable/typed-uris-bad-params.rs:39:18 | 39 | uri!(has_one(id = 100, id = 100)); | ^^ error: [help] duplicate parameter: `id` - --> $DIR/typed-uris-bad-params.rs:39:28 + --> tests/ui-fail-stable/typed-uris-bad-params.rs:39:28 | 39 | uri!(has_one(id = 100, id = 100)); | ^^ error: invalid parameters for `has_one` route uri - --- note: uri parameters are: id: i32 - --> $DIR/typed-uris-bad-params.rs:37:18 + --- note: uri parameters are: id: i32 + --> tests/ui-fail-stable/typed-uris-bad-params.rs:37:18 | 37 | uri!(has_one(name = 100, age = 50, id = 100, id = 50)); | ^^^^ error: [help] unknown parameters: `name`, `age` - --> $DIR/typed-uris-bad-params.rs:37:18 + --> tests/ui-fail-stable/typed-uris-bad-params.rs:37:18 | 37 | uri!(has_one(name = 100, age = 50, id = 100, id = 50)); | ^^^^ error: [help] duplicate parameter: `id` - --> $DIR/typed-uris-bad-params.rs:37:50 + --> tests/ui-fail-stable/typed-uris-bad-params.rs:37:50 | 37 | uri!(has_one(name = 100, age = 50, id = 100, id = 50)); | ^^ error: invalid parameters for `has_one` route uri - --- note: uri parameters are: id: i32 - --> $DIR/typed-uris-bad-params.rs:35:18 + --- note: uri parameters are: id: i32 + --> tests/ui-fail-stable/typed-uris-bad-params.rs:35:18 | 35 | uri!(has_one(name = 100, age = 50, id = 100)); | ^^^^ error: [help] unknown parameters: `name`, `age` - --> $DIR/typed-uris-bad-params.rs:35:18 + --> tests/ui-fail-stable/typed-uris-bad-params.rs:35:18 | 35 | uri!(has_one(name = 100, age = 50, id = 100)); | ^^^^ error: invalid parameters for `has_one` route uri - --- note: uri parameters are: id: i32 - --> $DIR/typed-uris-bad-params.rs:33:18 + --- note: uri parameters are: id: i32 + --> tests/ui-fail-stable/typed-uris-bad-params.rs:33:18 | 33 | uri!(has_one(name = 100, id = 100)); | ^^^^ error: [help] unknown parameter: `name` - --> $DIR/typed-uris-bad-params.rs:33:18 + --> tests/ui-fail-stable/typed-uris-bad-params.rs:33:18 | 33 | uri!(has_one(name = 100, id = 100)); | ^^^^ error: invalid parameters for `has_one` route uri - --- note: uri parameters are: id: i32 - --> $DIR/typed-uris-bad-params.rs:31:18 + --- note: uri parameters are: id: i32 + --> tests/ui-fail-stable/typed-uris-bad-params.rs:31:18 | 31 | uri!(has_one(id = 100, name = "hi")); | ^^ error: [help] unknown parameter: `name` - --> $DIR/typed-uris-bad-params.rs:31:28 + --> tests/ui-fail-stable/typed-uris-bad-params.rs:31:28 | 31 | uri!(has_one(id = 100, name = "hi")); | ^^^^ error: route expects 2 parameters but 1 was supplied - --- note: route `has_two` has uri "/?" - --> $DIR/typed-uris-bad-params.rs:29:18 + --- note: route `has_two` has uri "/?" + --> tests/ui-fail-stable/typed-uris-bad-params.rs:29:18 | 29 | uri!(has_two(10)); | ^^ error: route expects 2 parameters but 3 were supplied - --- note: route `has_two` has uri "/?" - --> $DIR/typed-uris-bad-params.rs:28:18 + --- note: route `has_two` has uri "/?" + --> tests/ui-fail-stable/typed-uris-bad-params.rs:28:18 | 28 | uri!(has_two(10, "hi", "there")); | ^^ error: route expects 1 parameter but 2 were supplied - --- note: route `has_one_guarded` has uri "/" - --> $DIR/typed-uris-bad-params.rs:26:26 + --- note: route `has_one_guarded` has uri "/" + --> tests/ui-fail-stable/typed-uris-bad-params.rs:26:26 | 26 | uri!(has_one_guarded("hi", 100)); | ^^^^ error: route expects 1 parameter but 2 were supplied - --- note: route `has_one` has uri "/" - --> $DIR/typed-uris-bad-params.rs:25:18 + --- note: route `has_one` has uri "/" + --> tests/ui-fail-stable/typed-uris-bad-params.rs:25:18 | 25 | uri!(has_one("Hello", 23, )); | ^^^^^^^ error: route expects 1 parameter but 2 were supplied - --- note: route `has_one` has uri "/" - --> $DIR/typed-uris-bad-params.rs:24:18 + --- note: route `has_one` has uri "/" + --> tests/ui-fail-stable/typed-uris-bad-params.rs:24:18 | 24 | uri!(has_one(1, 23)); | ^ error: route expects 1 parameter but 0 were supplied - --- note: route `has_one` has uri "/" - --> $DIR/typed-uris-bad-params.rs:22:10 + --- note: route `has_one` has uri "/" + --> tests/ui-fail-stable/typed-uris-bad-params.rs:22:10 | 22 | uri!(has_one()); | ^^^^^^^ error: route expects 1 parameter but 0 were supplied - --- note: route `has_one` has uri "/" - --> $DIR/typed-uris-bad-params.rs:21:10 + --- note: route `has_one` has uri "/" + --> tests/ui-fail-stable/typed-uris-bad-params.rs:21:10 | 21 | uri!(has_one); | ^^^^^^^ error[E0271]: type mismatch resolving `>::Error == &str` - --> $DIR/typed-uris-bad-params.rs:15:37 + --> tests/ui-fail-stable/typed-uris-bad-params.rs:15:37 | 15 | fn optionals(id: Option, name: Result) { } | ^^^^^^ expected enum `Infallible`, found `&str` diff --git a/core/codegen/tests/ui-fail-stable/typed-uris-invalid-syntax.stderr b/core/codegen/tests/ui-fail-stable/typed-uris-invalid-syntax.stderr index 0fa009664b..968cef7952 100644 --- a/core/codegen/tests/ui-fail-stable/typed-uris-invalid-syntax.stderr +++ b/core/codegen/tests/ui-fail-stable/typed-uris-invalid-syntax.stderr @@ -1,152 +1,152 @@ error: expected identifier - --> $DIR/typed-uris-invalid-syntax.rs:10:28 + --> tests/ui-fail-stable/typed-uris-invalid-syntax.rs:10:28 | 10 | uri!(simple: id = 100, "Hello"); | ^^^^^^^ error: named and unnamed parameters cannot be mixed - --> $DIR/typed-uris-invalid-syntax.rs:11:17 + --> tests/ui-fail-stable/typed-uris-invalid-syntax.rs:11:17 | 11 | uri!(simple(id = 100, "Hello")); | ^^ error: named and unnamed parameters cannot be mixed - --> $DIR/typed-uris-invalid-syntax.rs:12:17 + --> tests/ui-fail-stable/typed-uris-invalid-syntax.rs:12:17 | 12 | uri!(simple("Hello", id = 100)); | ^^^^^^^ error: unexpected token - --> $DIR/typed-uris-invalid-syntax.rs:14:16 + --> tests/ui-fail-stable/typed-uris-invalid-syntax.rs:14:16 | 14 | uri!(simple:); | ^ error: invalid URI: unexpected EOF: expected token ':' at index 5 - --> $DIR/typed-uris-invalid-syntax.rs:16:10 + --> tests/ui-fail-stable/typed-uris-invalid-syntax.rs:16:10 | 16 | uri!("mount", simple); | ^^^^^^^ error: invalid URI: unexpected EOF: expected token ':' at index 5 - --> $DIR/typed-uris-invalid-syntax.rs:17:10 + --> tests/ui-fail-stable/typed-uris-invalid-syntax.rs:17:10 | 17 | uri!("mount", simple, "http://"); | ^^^^^^^ error: URI suffix must contain only query and/or fragment - --> $DIR/typed-uris-invalid-syntax.rs:18:28 + --> tests/ui-fail-stable/typed-uris-invalid-syntax.rs:18:28 | 18 | uri!("/mount", simple, "http://"); | ^^^^^^^^^ error: expected 1, 2, or 3 arguments, found 4 - --> $DIR/typed-uris-invalid-syntax.rs:19:36 + --> tests/ui-fail-stable/typed-uris-invalid-syntax.rs:19:36 | 19 | uri!("/mount", simple, "#foo", "?foo"); | ^^^^^^ error: invalid URI: unexpected EOF: expected token ':' at index 5 - --> $DIR/typed-uris-invalid-syntax.rs:20:10 + --> tests/ui-fail-stable/typed-uris-invalid-syntax.rs:20:10 | 20 | uri!("mount", simple(10, "hi"), "http://"); | ^^^^^^^ error: URI suffix must contain only query and/or fragment - --> $DIR/typed-uris-invalid-syntax.rs:21:38 + --> tests/ui-fail-stable/typed-uris-invalid-syntax.rs:21:38 | 21 | uri!("/mount", simple(10, "hi"), "http://"); | ^^^^^^^^^ error: URI prefix cannot contain query part - --> $DIR/typed-uris-invalid-syntax.rs:22:10 + --> tests/ui-fail-stable/typed-uris-invalid-syntax.rs:22:10 | 22 | uri!("/mount?foo", simple(10, "hi"), "foo/bar?foo#bar"); | ^^^^^^^^^^^^ error: URI suffix must contain only query and/or fragment - --> $DIR/typed-uris-invalid-syntax.rs:23:38 + --> tests/ui-fail-stable/typed-uris-invalid-syntax.rs:23:38 | 23 | uri!("/mount", simple(10, "hi"), "a/b"); | ^^^^^ error: expected 1, 2, or 3 arguments, found 4 - --> $DIR/typed-uris-invalid-syntax.rs:24:46 + --> tests/ui-fail-stable/typed-uris-invalid-syntax.rs:24:46 | 24 | uri!("/mount", simple(10, "hi"), "#foo", "?foo"); | ^^^^^^ error: invalid URI: unexpected token '<' at index 7 - --> $DIR/typed-uris-invalid-syntax.rs:25:10 + --> tests/ui-fail-stable/typed-uris-invalid-syntax.rs:25:10 | 25 | uri!("/mount/", simple); | ^^^^^^^^^^^^^ error: expected at least 1 argument, found none - --> $DIR/typed-uris-invalid-syntax.rs:26:5 + --> tests/ui-fail-stable/typed-uris-invalid-syntax.rs:26:5 | 26 | uri!(); - | ^^^^^^^ + | ^^^^^^ | = note: this error originates in the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info) error: unexpected token - --> $DIR/typed-uris-invalid-syntax.rs:27:16 + --> tests/ui-fail-stable/typed-uris-invalid-syntax.rs:27:16 | 27 | uri!(simple: id = ); | ^ error: unexpected end of input, expected expression - --> $DIR/typed-uris-invalid-syntax.rs:28:16 + --> tests/ui-fail-stable/typed-uris-invalid-syntax.rs:28:22 | 28 | uri!(simple(id = )); - | ^^^^^^^ + | ^ error: invalid URI: unexpected EOF: expected some token at index 0 - --> $DIR/typed-uris-invalid-syntax.rs:29:10 + --> tests/ui-fail-stable/typed-uris-invalid-syntax.rs:29:10 | 29 | uri!("*", simple(10), "hi"); | ^^^ error: URI suffix must contain only query and/or fragment - --> $DIR/typed-uris-invalid-syntax.rs:30:40 + --> tests/ui-fail-stable/typed-uris-invalid-syntax.rs:30:40 | 30 | uri!("some.host:8088", simple(10), "hi"); | ^^^^ error: expected identifier - --> $DIR/typed-uris-invalid-syntax.rs:33:18 + --> tests/ui-fail-stable/typed-uris-invalid-syntax.rs:33:18 | 33 | uri!("/foo", "bar"); | ^^^^^ error: unexpected token - --> $DIR/typed-uris-invalid-syntax.rs:34:17 + --> tests/ui-fail-stable/typed-uris-invalid-syntax.rs:34:17 | 34 | uri!("/foo" ("bar")); | ^^^^^^^ error: URI prefix cannot contain query part - --> $DIR/typed-uris-invalid-syntax.rs:35:10 + --> tests/ui-fail-stable/typed-uris-invalid-syntax.rs:35:10 | 35 | uri!("ftp:?", index); | ^^^^^^^ error: URI suffix must contain only query and/or fragment - --> $DIR/typed-uris-invalid-syntax.rs:36:25 + --> tests/ui-fail-stable/typed-uris-invalid-syntax.rs:36:25 | 36 | uri!("ftp:", index, "foo#bar"); | ^^^^^^^^^ error: URI suffix must contain only query and/or fragment - --> $DIR/typed-uris-invalid-syntax.rs:37:25 + --> tests/ui-fail-stable/typed-uris-invalid-syntax.rs:37:25 | 37 | uri!("ftp:", index, "foo?bar"); | ^^^^^^^^^ error: route expects 2 parameters but 0 were supplied - --- note: route `simple` has uri "//" - --> $DIR/typed-uris-invalid-syntax.rs:13:10 + --- note: route `simple` has uri "//" + --> tests/ui-fail-stable/typed-uris-invalid-syntax.rs:13:10 | 13 | uri!(simple,); | ^^^^^^ diff --git a/core/codegen/tests/ui-fail-stable/uri_display.stderr b/core/codegen/tests/ui-fail-stable/uri_display.stderr index deacb670f4..10a2c3ddc5 100644 --- a/core/codegen/tests/ui-fail-stable/uri_display.stderr +++ b/core/codegen/tests/ui-fail-stable/uri_display.stderr @@ -1,11 +1,11 @@ error: fieldless structs are not supported - --> $DIR/uri_display.rs:4:1 + --> tests/ui-fail-stable/uri_display.rs:4:1 | 4 | struct Foo1; | ^^^^^^ error: [note] error occurred while deriving `UriDisplay` - --> $DIR/uri_display.rs:3:10 + --> tests/ui-fail-stable/uri_display.rs:3:10 | 3 | #[derive(UriDisplayQuery)] | ^^^^^^^^^^^^^^^ @@ -13,13 +13,13 @@ error: [note] error occurred while deriving `UriDisplay` = note: this error originates in the derive macro `UriDisplayQuery` (in Nightly builds, run with -Z macro-backtrace for more info) error: fieldless structs are not supported - --> $DIR/uri_display.rs:7:1 + --> tests/ui-fail-stable/uri_display.rs:7:1 | 7 | struct Foo2(); | ^^^^^^ error: [note] error occurred while deriving `UriDisplay` - --> $DIR/uri_display.rs:6:10 + --> tests/ui-fail-stable/uri_display.rs:6:10 | 6 | #[derive(UriDisplayQuery)] | ^^^^^^^^^^^^^^^ @@ -27,13 +27,13 @@ error: [note] error occurred while deriving `UriDisplay` = note: this error originates in the derive macro `UriDisplayQuery` (in Nightly builds, run with -Z macro-backtrace for more info) error: empty enums are not supported - --> $DIR/uri_display.rs:10:11 + --> tests/ui-fail-stable/uri_display.rs:10:11 | 10 | enum Foo3 { } | ^^^ error: [note] error occurred while deriving `UriDisplay` - --> $DIR/uri_display.rs:9:10 + --> tests/ui-fail-stable/uri_display.rs:9:10 | 9 | #[derive(UriDisplayQuery)] | ^^^^^^^^^^^^^^^ @@ -41,13 +41,13 @@ error: [note] error occurred while deriving `UriDisplay` = note: this error originates in the derive macro `UriDisplayQuery` (in Nightly builds, run with -Z macro-backtrace for more info) error: tuple structs or variants must have exactly one field - --> $DIR/uri_display.rs:18:12 + --> tests/ui-fail-stable/uri_display.rs:18:12 | 18 | struct Foo5(String, String); | ^^^^^^^^^^^^^^^^ error: [note] error occurred while deriving `UriDisplay` - --> $DIR/uri_display.rs:17:10 + --> tests/ui-fail-stable/uri_display.rs:17:10 | 17 | #[derive(UriDisplayQuery)] | ^^^^^^^^^^^^^^^ @@ -55,13 +55,13 @@ error: [note] error occurred while deriving `UriDisplay` = note: this error originates in the derive macro `UriDisplayQuery` (in Nightly builds, run with -Z macro-backtrace for more info) error: expected list `#[attr(..)]`, found bare integer literal - --> $DIR/uri_display.rs:22:20 + --> tests/ui-fail-stable/uri_display.rs:22:20 | 22 | #[field(name = 123)] | ^^^ error: [note] error occurred while deriving `UriDisplay` - --> $DIR/uri_display.rs:20:10 + --> tests/ui-fail-stable/uri_display.rs:20:10 | 20 | #[derive(UriDisplayQuery)] | ^^^^^^^^^^^^^^^ @@ -69,13 +69,13 @@ error: [note] error occurred while deriving `UriDisplay` = note: this error originates in the derive macro `UriDisplayQuery` (in Nightly builds, run with -Z macro-backtrace for more info) error: struct must have exactly one field - --> $DIR/uri_display.rs:27:12 + --> tests/ui-fail-stable/uri_display.rs:27:12 | 27 | struct Foo7(String, usize); | ^^^^^^^^^^^^^^^ error: [note] error occurred while deriving `UriDisplay` - --> $DIR/uri_display.rs:26:10 + --> tests/ui-fail-stable/uri_display.rs:26:10 | 26 | #[derive(UriDisplayPath)] | ^^^^^^^^^^^^^^ @@ -83,13 +83,13 @@ error: [note] error occurred while deriving `UriDisplay` = note: this error originates in the derive macro `UriDisplayPath` (in Nightly builds, run with -Z macro-backtrace for more info) error: struct must have exactly one field - --> $DIR/uri_display.rs:30:1 + --> tests/ui-fail-stable/uri_display.rs:30:1 | 30 | struct Foo8; | ^^^^^^ error: [note] error occurred while deriving `UriDisplay` - --> $DIR/uri_display.rs:29:10 + --> tests/ui-fail-stable/uri_display.rs:29:10 | 29 | #[derive(UriDisplayPath)] | ^^^^^^^^^^^^^^ @@ -97,13 +97,13 @@ error: [note] error occurred while deriving `UriDisplay` = note: this error originates in the derive macro `UriDisplayPath` (in Nightly builds, run with -Z macro-backtrace for more info) error: enums are not supported - --> $DIR/uri_display.rs:33:1 + --> tests/ui-fail-stable/uri_display.rs:33:1 | 33 | enum Foo9 { } | ^^^^ error: [note] error occurred while deriving `UriDisplay` - --> $DIR/uri_display.rs:32:10 + --> tests/ui-fail-stable/uri_display.rs:32:10 | 32 | #[derive(UriDisplayPath)] | ^^^^^^^^^^^^^^ @@ -111,13 +111,13 @@ error: [note] error occurred while deriving `UriDisplay` = note: this error originates in the derive macro `UriDisplayPath` (in Nightly builds, run with -Z macro-backtrace for more info) error: named structs are not supported - --> $DIR/uri_display.rs:36:1 + --> tests/ui-fail-stable/uri_display.rs:36:1 | 36 | struct Foo10 { | ^^^^^^ error: [note] error occurred while deriving `UriDisplay` - --> $DIR/uri_display.rs:35:10 + --> tests/ui-fail-stable/uri_display.rs:35:10 | 35 | #[derive(UriDisplayPath)] | ^^^^^^^^^^^^^^ diff --git a/core/codegen/tests/ui-fail-stable/uri_display_type_errors.stderr b/core/codegen/tests/ui-fail-stable/uri_display_type_errors.stderr index 234b85956c..d8d0ff2584 100644 --- a/core/codegen/tests/ui-fail-stable/uri_display_type_errors.stderr +++ b/core/codegen/tests/ui-fail-stable/uri_display_type_errors.stderr @@ -1,61 +1,96 @@ error[E0277]: the trait bound `BadType: UriDisplay` is not satisfied - --> $DIR/uri_display_type_errors.rs:6:13 - | -6 | struct Bar1(BadType); - | ^^^^^^^ the trait `UriDisplay` is not implemented for `BadType` - | - = note: required because of the requirements on the impl of `UriDisplay` for `&BadType` + --> tests/ui-fail-stable/uri_display_type_errors.rs:6:13 + | +6 | struct Bar1(BadType); + | ^^^^^^^ the trait `UriDisplay` is not implemented for `BadType` + | + = note: required because of the requirements on the impl of `UriDisplay` for `&BadType` +note: required by a bound in `rocket::http::uri::fmt::Formatter::<'i, P>::write_value` + --> $WORKSPACE/core/http/src/uri/fmt/formatter.rs + | + | pub fn write_value>(&mut self, value: T) -> fmt::Result { + | ^^^^^^^^^^^^^ required by this bound in `rocket::http::uri::fmt::Formatter::<'i, P>::write_value` error[E0277]: the trait bound `BadType: UriDisplay` is not satisfied - --> $DIR/uri_display_type_errors.rs:10:5 - | -10 | field: BadType, - | ^^^^^ the trait `UriDisplay` is not implemented for `BadType` - | - = note: required because of the requirements on the impl of `UriDisplay` for `&BadType` + --> tests/ui-fail-stable/uri_display_type_errors.rs:10:5 + | +10 | field: BadType, + | ^^^^^ the trait `UriDisplay` is not implemented for `BadType` + | + = note: required because of the requirements on the impl of `UriDisplay` for `&BadType` +note: required by a bound in `rocket::http::uri::fmt::Formatter::<'_, rocket::http::uri::fmt::Query>::write_named_value` + --> $WORKSPACE/core/http/src/uri/fmt/formatter.rs + | + | pub fn write_named_value>(&mut self, name: &str, value: T) -> fmt::Result { + | ^^^^^^^^^^^^^^^^^ required by this bound in `rocket::http::uri::fmt::Formatter::<'_, rocket::http::uri::fmt::Query>::write_named_value` error[E0277]: the trait bound `BadType: UriDisplay` is not satisfied - --> $DIR/uri_display_type_errors.rs:16:5 - | -16 | bad: BadType, - | ^^^ the trait `UriDisplay` is not implemented for `BadType` - | - = note: required because of the requirements on the impl of `UriDisplay` for `&BadType` + --> tests/ui-fail-stable/uri_display_type_errors.rs:16:5 + | +16 | bad: BadType, + | ^^^ the trait `UriDisplay` is not implemented for `BadType` + | + = note: required because of the requirements on the impl of `UriDisplay` for `&BadType` +note: required by a bound in `rocket::http::uri::fmt::Formatter::<'_, rocket::http::uri::fmt::Query>::write_named_value` + --> $WORKSPACE/core/http/src/uri/fmt/formatter.rs + | + | pub fn write_named_value>(&mut self, name: &str, value: T) -> fmt::Result { + | ^^^^^^^^^^^^^^^^^ required by this bound in `rocket::http::uri::fmt::Formatter::<'_, rocket::http::uri::fmt::Query>::write_named_value` error[E0277]: the trait bound `BadType: UriDisplay` is not satisfied - --> $DIR/uri_display_type_errors.rs:21:11 - | -21 | Inner(BadType), - | ^^^^^^^ the trait `UriDisplay` is not implemented for `BadType` - | - = note: required because of the requirements on the impl of `UriDisplay` for `&BadType` - = note: 1 redundant requirements hidden - = note: required because of the requirements on the impl of `UriDisplay` for `&&BadType` + --> tests/ui-fail-stable/uri_display_type_errors.rs:21:11 + | +21 | Inner(BadType), + | ^^^^^^^ the trait `UriDisplay` is not implemented for `BadType` + | + = note: required because of the requirements on the impl of `UriDisplay` for `&BadType` + = note: 1 redundant requirement hidden + = note: required because of the requirements on the impl of `UriDisplay` for `&&BadType` +note: required by a bound in `rocket::http::uri::fmt::Formatter::<'i, P>::write_value` + --> $WORKSPACE/core/http/src/uri/fmt/formatter.rs + | + | pub fn write_value>(&mut self, value: T) -> fmt::Result { + | ^^^^^^^^^^^^^ required by this bound in `rocket::http::uri::fmt::Formatter::<'i, P>::write_value` error[E0277]: the trait bound `BadType: UriDisplay` is not satisfied - --> $DIR/uri_display_type_errors.rs:27:9 - | -27 | field: BadType, - | ^^^^^ the trait `UriDisplay` is not implemented for `BadType` - | - = note: required because of the requirements on the impl of `UriDisplay` for `&BadType` - = note: 1 redundant requirements hidden - = note: required because of the requirements on the impl of `UriDisplay` for `&&BadType` + --> tests/ui-fail-stable/uri_display_type_errors.rs:27:9 + | +27 | field: BadType, + | ^^^^^ the trait `UriDisplay` is not implemented for `BadType` + | + = note: required because of the requirements on the impl of `UriDisplay` for `&BadType` + = note: 1 redundant requirement hidden + = note: required because of the requirements on the impl of `UriDisplay` for `&&BadType` +note: required by a bound in `rocket::http::uri::fmt::Formatter::<'_, rocket::http::uri::fmt::Query>::write_named_value` + --> $WORKSPACE/core/http/src/uri/fmt/formatter.rs + | + | pub fn write_named_value>(&mut self, name: &str, value: T) -> fmt::Result { + | ^^^^^^^^^^^^^^^^^ required by this bound in `rocket::http::uri::fmt::Formatter::<'_, rocket::http::uri::fmt::Query>::write_named_value` error[E0277]: the trait bound `BadType: UriDisplay` is not satisfied - --> $DIR/uri_display_type_errors.rs:35:9 - | -35 | other: BadType, - | ^^^^^ the trait `UriDisplay` is not implemented for `BadType` - | - = note: required because of the requirements on the impl of `UriDisplay` for `&BadType` - = note: 1 redundant requirements hidden - = note: required because of the requirements on the impl of `UriDisplay` for `&&BadType` + --> tests/ui-fail-stable/uri_display_type_errors.rs:35:9 + | +35 | other: BadType, + | ^^^^^ the trait `UriDisplay` is not implemented for `BadType` + | + = note: required because of the requirements on the impl of `UriDisplay` for `&BadType` + = note: 1 redundant requirement hidden + = note: required because of the requirements on the impl of `UriDisplay` for `&&BadType` +note: required by a bound in `rocket::http::uri::fmt::Formatter::<'_, rocket::http::uri::fmt::Query>::write_named_value` + --> $WORKSPACE/core/http/src/uri/fmt/formatter.rs + | + | pub fn write_named_value>(&mut self, name: &str, value: T) -> fmt::Result { + | ^^^^^^^^^^^^^^^^^ required by this bound in `rocket::http::uri::fmt::Formatter::<'_, rocket::http::uri::fmt::Query>::write_named_value` error[E0277]: the trait bound `BadType: UriDisplay` is not satisfied - --> $DIR/uri_display_type_errors.rs:40:12 - | -40 | struct Baz(BadType); - | ^^^^^^^ the trait `UriDisplay` is not implemented for `BadType` - | - = note: required because of the requirements on the impl of `UriDisplay` for `&BadType` + --> tests/ui-fail-stable/uri_display_type_errors.rs:40:12 + | +40 | struct Baz(BadType); + | ^^^^^^^ the trait `UriDisplay` is not implemented for `BadType` + | + = note: required because of the requirements on the impl of `UriDisplay` for `&BadType` +note: required by a bound in `rocket::http::uri::fmt::Formatter::<'i, P>::write_value` + --> $WORKSPACE/core/http/src/uri/fmt/formatter.rs + | + | pub fn write_value>(&mut self, value: T) -> fmt::Result { + | ^^^^^^^^^^^^^ required by this bound in `rocket::http::uri::fmt::Formatter::<'i, P>::write_value` diff --git a/core/http/Cargo.toml b/core/http/Cargo.toml index f8bdc16523..851ded5f8a 100644 --- a/core/http/Cargo.toml +++ b/core/http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rocket_http" -version = "0.5.0-rc.1" +version = "0.5.0-rc.2" authors = ["Sergio Benitez "] description = """ Types, traits, and parsers for HTTP requests, responses, and headers. @@ -14,9 +14,13 @@ license = "MIT OR Apache-2.0" categories = ["web-programming"] edition = "2018" +[package.metadata] +# minimum supported rust version 1.51.0 because of cargo resolver in dependency crate time +msrv = "1.51.0" + [features] default = [] -tls = ["rustls", "tokio-rustls"] +tls = ["rustls-pemfile", "tokio-rustls"] mtls = ["tls", "x509-parser"] private-cookies = ["cookie/private", "cookie/key-expansion"] serde = ["uncased/with-serde-alloc", "serde_"] @@ -24,30 +28,30 @@ uuid = ["uuid_"] [dependencies] smallvec = "1.0" -percent-encoding = "2" +percent-encoding = "2.1" http = "0.2" time = { version = "0.3", features = ["formatting", "macros"] } -indexmap = { version = "1.5.2", features = ["std"] } -rustls = { version = "0.19", optional = true } -tokio-rustls = { version = "0.22.0", optional = true } -tokio = { version = "1.6.1", features = ["net", "sync", "time"] } +indexmap = { version = "1.8", features = ["std"] } +rustls-pemfile = { version = "0.2", optional = true } +tokio-rustls = { version = "0.23", optional = true } +tokio = { version = "1.15", features = ["net", "sync", "time"] } log = "0.4" ref-cast = "1.0" -uncased = "0.9.6" -either = "1" -pear = "0.2.3" +uncased = "0.9" +either = "1.6" +pear = "0.2" pin-project-lite = "0.2" -memchr = "2" +memchr = "2.4" stable-pattern = "0.1" -cookie = { version = "0.16.0-rc.1", features = ["percent-encode"] } -state = "0.5.1" +cookie = { version = "0.16", features = ["percent-encode", "secure"] } +state = "0.5" [dependencies.x509-parser] -version = "0.9.2" +version = "0.12" optional = true [dependencies.hyper] -version = "0.14.9" +version = "0.14" default-features = false features = ["http1", "http2", "runtime", "server", "stream"] @@ -65,4 +69,4 @@ optional = true default-features = false [dev-dependencies] -rocket = { version = "0.5.0-rc.1", path = "../lib", features = ["mtls"] } +rocket = { version = "0.5.0-rc.2", path = "../lib", features = ["mtls"] } diff --git a/core/http/src/ext.rs b/core/http/src/ext.rs index 1a0ab4e9c0..8e273f0e36 100644 --- a/core/http/src/ext.rs +++ b/core/http/src/ext.rs @@ -63,14 +63,14 @@ impl IntoCollection for &[T] { impl IntoCollection for [T; N] { #[inline(always)] fn into_collection>(self) -> SmallVec { - std::array::IntoIter::new(self).collect() + IntoIterator::into_iter(self).collect() } #[inline] fn mapped>(self, f: F) -> SmallVec where F: FnMut(T) -> U { - std::array::IntoIter::new(self).map(f).collect() + IntoIterator::into_iter(self).map(f).collect() } } diff --git a/core/http/src/listener.rs b/core/http/src/listener.rs index 2c60811986..cd3d4208fd 100644 --- a/core/http/src/listener.rs +++ b/core/http/src/listener.rs @@ -39,7 +39,7 @@ pub struct RawCertificate(pub Vec); // NOTE: `rustls::Certificate` is exactly isomorphic to `RawCertificate`. #[doc(inline)] #[cfg(feature = "tls")] -pub use rustls::Certificate as RawCertificate; +pub use tokio_rustls::rustls::Certificate as RawCertificate; /// A 'Connection' represents an open connection to a client pub trait Connection: AsyncRead + AsyncWrite { @@ -54,6 +54,9 @@ pub trait Connection: AsyncRead + AsyncWrite { /// /// Defaults to an empty vector to indicate that no certificates were /// presented. + /// + /// Supported only with feature `tls`. + #[cfg(feature = "tls")] fn peer_certificates(&self) -> Option> { None } } @@ -167,12 +170,9 @@ impl Accept for Incoming { /// The delay is useful to handle resource exhaustion errors like ENFILE /// and EMFILE. Otherwise, could enter into tight loop. fn is_connection_error(e: &io::Error) -> bool { - match e.kind() { - io::ErrorKind::ConnectionRefused | + matches!(e.kind(), io::ErrorKind::ConnectionRefused | io::ErrorKind::ConnectionAborted | - io::ErrorKind::ConnectionReset => true, - _ => false, - } + io::ErrorKind::ConnectionReset) } impl fmt::Debug for Incoming { diff --git a/core/http/src/parse/checkers.rs b/core/http/src/parse/checkers.rs index a56a9bce9b..5e351f25a2 100644 --- a/core/http/src/parse/checkers.rs +++ b/core/http/src/parse/checkers.rs @@ -5,9 +5,6 @@ pub fn is_whitespace(&byte: &char) -> bool { #[inline] pub fn is_valid_token(&c: &char) -> bool { - match c { - '0'..='9' | 'A'..='Z' | '^'..='~' | '#'..='\'' - | '!' | '*' | '+' | '-' | '.' => true, - _ => false - } + matches!(c, '0'..='9' | 'A'..='Z' | '^'..='~' | '#'..='\'' + | '!' | '*' | '+' | '-' | '.') } diff --git a/core/http/src/tls/listener.rs b/core/http/src/tls/listener.rs index 90d14f220b..4837f0fcee 100644 --- a/core/http/src/tls/listener.rs +++ b/core/http/src/tls/listener.rs @@ -1,16 +1,26 @@ +use std::future::Future; use std::io; +use std::net::SocketAddr; use std::pin::Pin; use std::sync::Arc; use std::task::{Context, Poll}; -use std::net::SocketAddr; -use std::future::Future; -use rustls::{ServerConfig, SupportedCipherSuite}; -use tokio_rustls::{TlsAcceptor, Accept, server::TlsStream}; use tokio::net::{TcpListener, TcpStream}; +use tokio_rustls::{ + rustls::{ + self, + server::{ + AllowAnyAnonymousOrAuthenticatedClient, AllowAnyAuthenticatedClient, NoClientAuth, + ServerConfig, ServerSessionMemoryCache, + }, + SupportedCipherSuite, + }, + server::TlsStream, + Accept, TlsAcceptor, +}; -use crate::tls::util::{load_certs, load_private_key, load_ca_certs}; use crate::listener::{Connection, Listener, RawCertificate}; +use crate::tls::util::{load_ca_certs, load_certs, load_private_key}; /// A TLS listener over TCP. pub struct TlsListener { @@ -35,7 +45,8 @@ pub struct Config { impl TlsListener { pub async fn bind(addr: SocketAddr, mut c: Config) -> io::Result - where R: io::BufRead + where + R: io::BufRead, { let cert_chain = load_certs(&mut c.cert_chain).map_err(|e| { let msg = format!("malformed TLS certificate chain: {}", e); @@ -55,26 +66,51 @@ impl TlsListener { })?; if c.mandatory_mtls { - rustls::AllowAnyAuthenticatedClient::new(roots) + AllowAnyAuthenticatedClient::new(roots) } else { - rustls::AllowAnyAnonymousOrAuthenticatedClient::new(roots) + AllowAnyAnonymousOrAuthenticatedClient::new(roots) } } - None => rustls::NoClientAuth::new(), + None => NoClientAuth::new(), }; - let mut tls_config = ServerConfig::new(client_auth); - let cache = rustls::ServerSessionMemoryCache::new(1024); - tls_config.set_persistence(cache); - tls_config.ticketer = rustls::Ticketer::new(); - tls_config.ciphersuites = c.ciphersuites; - tls_config.ignore_client_order = c.prefer_server_order; - tls_config.set_single_cert(cert_chain, key).expect("invalid key"); - tls_config.set_protocols(&[b"h2".to_vec(), b"http/1.1".to_vec()]); + // TODO: deprecate the reference in listener::Config, we need now owned ciphers + let ciphersuites = c + .ciphersuites + .into_iter() + .cloned() + .collect::>(); + + // build tls_config + let tls_config_res = if let Ok(tls_config_bld) = ServerConfig::builder() + .with_cipher_suites(ciphersuites.as_slice()) + .with_safe_default_kx_groups() + .with_safe_default_protocol_versions() + { + if let Ok(mut tls_config) = tls_config_bld + .with_client_cert_verifier(client_auth) + .with_single_cert(cert_chain, key) + { + tls_config.session_storage = ServerSessionMemoryCache::new(1024); + tls_config.ticketer = rustls::Ticketer::new() + .map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))?; + tls_config.ignore_client_order = c.prefer_server_order; + tls_config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()]; + Ok(tls_config) + } else { + Err(io::Error::new(io::ErrorKind::Other, "invalid key")) + } + } else { + Err(io::Error::new(io::ErrorKind::Other, "tls config failed")) + }; let listener = TcpListener::bind(addr).await?; - let acceptor = TlsAcceptor::from(Arc::new(tls_config)); - Ok(TlsListener { listener, acceptor, state: State::Listening }) + let acceptor = TlsAcceptor::from(Arc::new(tls_config_res?)); + Ok(TlsListener { + listener, + acceptor, + state: State::Listening, + }) } } @@ -87,29 +123,25 @@ impl Listener for TlsListener { fn poll_accept( mut self: Pin<&mut Self>, - cx: &mut Context<'_> + cx: &mut Context<'_>, ) -> Poll> { loop { match self.state { - State::Listening => { - match self.listener.poll_accept(cx) { - Poll::Pending => return Poll::Pending, - Poll::Ready(Err(e)) => return Poll::Ready(Err(e)), - Poll::Ready(Ok((stream, _addr))) => { - let fut = self.acceptor.accept(stream); - self.state = State::Accepting(fut); - } + State::Listening => match self.listener.poll_accept(cx) { + Poll::Pending => return Poll::Pending, + Poll::Ready(Err(e)) => return Poll::Ready(Err(e)), + Poll::Ready(Ok((stream, _addr))) => { + let fut = self.acceptor.accept(stream); + self.state = State::Accepting(fut); } - } - State::Accepting(ref mut fut) => { - match Pin::new(fut).poll(cx) { - Poll::Pending => return Poll::Pending, - Poll::Ready(result) => { - self.state = State::Listening; - return Poll::Ready(result); - } + }, + State::Accepting(ref mut fut) => match Pin::new(fut).poll(cx) { + Poll::Pending => return Poll::Pending, + Poll::Ready(result) => { + self.state = State::Listening; + return Poll::Ready(result); } - } + }, } } } @@ -121,8 +153,6 @@ impl Connection for TlsStream { } fn peer_certificates(&self) -> Option> { - use rustls::Session; - - self.get_ref().1.get_peer_certificates() + Some(self.get_ref().1.peer_certificates()?.to_vec()) } } diff --git a/core/http/src/tls/mod.rs b/core/http/src/tls/mod.rs index b529ee4082..12e5955800 100644 --- a/core/http/src/tls/mod.rs +++ b/core/http/src/tls/mod.rs @@ -4,5 +4,5 @@ mod util; #[cfg(feature = "mtls")] pub mod mtls; -pub use rustls; +pub use tokio_rustls::rustls; pub use listener::{TlsListener, Config}; diff --git a/core/http/src/tls/mtls.rs b/core/http/src/tls/mtls.rs index d588ef3a9d..5817c046b4 100644 --- a/core/http/src/tls/mtls.rs +++ b/core/http/src/tls/mtls.rs @@ -1,11 +1,11 @@ pub mod oid { //! Lower-level OID types re-exported from - //! [`oid_registry`](https://docs.rs/oid-registry/0.1) and - //! [`der-parser`](https://docs.rs/der-parser/5). + //! [`oid_registry`](https://docs.rs/oid-registry/0.2) and + //! [`der-parser`](https://docs.rs/der-parser/6.0). - pub use x509_parser::oid_registry::*; pub use x509_parser::der_parser::oid::*; pub use x509_parser::objects::*; + pub use x509_parser::oid_registry::*; } pub mod bigint { @@ -16,31 +16,34 @@ pub mod bigint { pub mod x509 { //! Lower-level X.509 types re-exported from - //! [`x509_parser`](https://docs.rs/x509-parser/0.9). + //! [`x509_parser`](https://docs.rs/x509-parser/0.12). //! //! Lack of documentation is directly inherited from the source crate. //! Prefer to use Rocket's wrappers when possible. pub use x509_parser::certificate::*; pub use x509_parser::cri_attributes::*; + pub use x509_parser::der_parser::ber; + pub use x509_parser::der_parser::der; pub use x509_parser::error::*; pub use x509_parser::extensions::*; pub use x509_parser::revocation_list::*; pub use x509_parser::time::*; pub use x509_parser::x509::*; - pub use x509_parser::der_parser::der; - pub use x509_parser::der_parser::ber; } -use std::fmt; -use std::ops::Deref; use std::collections::HashMap; +use std::fmt; use std::num::NonZeroUsize; +use std::ops::Deref; use ref_cast::RefCast; -use x509_parser::nom; -use x509::{ParsedExtension, X509Name, X509Certificate, TbsCertificate, X509Error}; -use oid::OID_X509_EXT_SUBJECT_ALT_NAME as SUBJECT_ALT_NAME; +use x509::{ParsedExtension, TbsCertificate, X509Certificate, X509Error, X509Name}; +use x509_parser::{ + extensions::X509Extension, + nom::{self, Parser}, + prelude::{FromDer, X509CertificateParser}, +}; use crate::listener::RawCertificate; @@ -75,11 +78,8 @@ pub enum Error { NoSubject, /// There is no subject and the subjectAlt is not marked as critical. NonCriticalSubjectAlt, - // FIXME: Waiting on https://github.com/rusticata/x509-parser/pull/92. - // Parse(X509Error), /// An error occurred while parsing the certificate. - #[doc(hidden)] - Parse(String), + Parse(X509Error), /// The certificate parsed partially but is incomplete. /// /// If `Some(n)`, then `n` more bytes were expected. Otherwise, the number @@ -196,14 +196,18 @@ pub struct Name<'a>(X509Name<'a>); impl<'a> Certificate<'a> { fn parse_one(raw: &[u8]) -> Result> { - let (left, x509) = X509Certificate::from_der(raw)?; - if !left.is_empty() { - return Err(Error::Trailing(left.len())); + let mut parser = X509CertificateParser::new().with_deep_parse_extensions(false); + let (rem, x509) = parser.parse(raw)?; + if !rem.is_empty() { + return Err(Error::Trailing(rem.len())); } if x509.subject().as_raw().is_empty() { - if let Some(ext) = x509.extensions().get(&SUBJECT_ALT_NAME) { - if !matches!(ext.parsed_extension(), ParsedExtension::SubjectAlternativeName(..)) { + if let Ok((_rem, ext)) = X509Extension::from_der(raw) { + if !matches!( + ext.parsed_extension(), + ParsedExtension::SubjectAlternativeName(..) + ) { return Err(Error::NoSubject); } else if !ext.critical { return Err(Error::NonCriticalSubjectAlt); @@ -226,7 +230,7 @@ impl<'a> Certificate<'a> { pub fn parse(chain: &[RawCertificate]) -> Result> { match chain.first() { Some(cert) => Certificate::parse_one(&cert.0).map(Certificate), - None => Err(Error::Empty) + None => Err(Error::Empty), } } @@ -314,7 +318,7 @@ impl<'a> Certificate<'a> { /// /// ```rust /// # extern crate rocket; - /// # use rocket::get; + /// use rocket::get; /// use rocket::mtls::{oid, x509, Certificate}; /// /// #[get("/auth")] @@ -322,6 +326,48 @@ impl<'a> Certificate<'a> { /// let subject_alt = cert.extensions() /// .get(&oid::OID_X509_EXT_SUBJECT_ALT_NAME) /// .and_then(|e| match e.parsed_extension() { + /// x509::ParsedExtension::SubjectAlternativeName(s) => { + /// Some(s.clone()) + /// } + /// _ => None + /// }); + /// + /// if let Some(subject_alt) = subject_alt { + /// for name in &subject_alt.general_names { + /// if let x509::GeneralName::RFC822Name(name) = name { + /// println!("An email, perhaps? {}", name); + /// } + /// } + /// } + /// } + /// ``` + #[deprecated(since = "0.5.0-rc.2", note = "use find_extension(&oid::Oid)")] + pub fn extensions(&self) -> HashMap, x509::X509Extension<'a>> { + let extensions: HashMap, x509::X509Extension<'a>> = self + .inner() + .extensions() + .iter() + .map(|ext| (ext.oid.clone(), ext.clone())) + .collect(); + extensions + } + + /// Searches for an extension with the given [`oid::Oid`]. + /// + /// Note: if there are several extensions with the same Oid, the first one is returned. + /// + /// # Example + /// + /// ```rust + /// # extern crate rocket; + /// use rocket::get; + /// use rocket::mtls::{oid, x509, Certificate}; + /// + /// #[get("/auth")] + /// fn auth(cert: Certificate<'_>) { + /// let subject_alt = cert + /// .find_extension(&oid::OID_X509_EXT_SUBJECT_ALT_NAME) + /// .and_then(|e| match e.parsed_extension() { /// x509::ParsedExtension::SubjectAlternativeName(s) => Some(s), /// _ => None /// }); @@ -335,8 +381,8 @@ impl<'a> Certificate<'a> { /// } /// } /// ``` - pub fn extensions(&self) -> &HashMap, x509::X509Extension<'a>> { - &self.inner().extensions + pub fn find_extension(&self, oid: &oid::Oid<'a>) -> Option<&x509::X509Extension<'a>> { + self.inner().find_extension(oid) } /// Checks if the certificate has the serial number `number`. @@ -419,7 +465,8 @@ impl<'a> Name<'a> { /// } /// ``` pub fn common_names(&self) -> impl Iterator + '_ { - self.iter_by_oid(&oid::OID_X509_COMMON_NAME).filter_map(|n| n.as_str().ok()) + self.iter_by_oid(&oid::OID_X509_COMMON_NAME) + .filter_map(|n| n.as_str().ok()) } /// Returns the _first_ UTF-8 _string_ email address, if any. @@ -466,7 +513,8 @@ impl<'a> Name<'a> { /// } /// ``` pub fn emails(&self) -> impl Iterator + '_ { - self.iter_by_oid(&oid::OID_PKCS9_EMAIL_ADDRESS).filter_map(|n| n.as_str().ok()) + self.iter_by_oid(&oid::OID_PKCS9_EMAIL_ADDRESS) + .filter_map(|n| n.as_str().ok()) } /// Returns `true` if `self` has no data. @@ -522,12 +570,13 @@ impl From> for Error { match e { nom::Err::Incomplete(nom::Needed::Unknown) => Error::Incomplete(None), nom::Err::Incomplete(nom::Needed::Size(n)) => Error::Incomplete(Some(n)), - nom::Err::Error(e) | nom::Err::Failure(e) => Error::Parse(e.to_string()), + nom::Err::Error(e) | nom::Err::Failure(e) => Error::Parse(e), } } } impl std::error::Error for Error { + // TODO: do we need this impl and need to provide here anything or remove it?! // fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { // match self { // Error::Parse(e) => Some(e), diff --git a/core/http/src/tls/util.rs b/core/http/src/tls/util.rs index fa598f2956..96807e20ac 100644 --- a/core/http/src/tls/util.rs +++ b/core/http/src/tls/util.rs @@ -1,6 +1,6 @@ use std::io::{self, Cursor, Read}; -use rustls::{internal::pemfile, Certificate, PrivateKey, RootCertStore}; +use tokio_rustls::rustls::{self, Certificate, PrivateKey, RootCertStore}; fn err(message: impl Into>) -> io::Error { io::Error::new(io::ErrorKind::Other, message.into()) @@ -8,10 +8,11 @@ fn err(message: impl Into>) -> io::Error { /// Loads certificates from `reader`. pub fn load_certs(reader: &mut dyn io::BufRead) -> io::Result> { - pemfile::certs(reader).map_err(|_| err("invalid certificate")) + let der_cert_data = rustls_pemfile::certs(reader).map_err(|_| err("invalid certificate"))?; + Ok(der_cert_data.into_iter().map(Certificate).collect()) } -/// Load and decode the private key from `reader`. +/// Load and decode the private key from `reader`. pub fn load_private_key(reader: &mut dyn io::BufRead) -> io::Result { // "rsa" (PKCS1) PEM files have a different first-line header than PKCS8 // PEM files, use that to determine the parse function to use. @@ -19,16 +20,16 @@ pub fn load_private_key(reader: &mut dyn io::BufRead) -> io::Result reader.read_line(&mut first_line)?; let private_keys_fn = match first_line.trim_end() { - "-----BEGIN RSA PRIVATE KEY-----" => pemfile::rsa_private_keys, - "-----BEGIN PRIVATE KEY-----" => pemfile::pkcs8_private_keys, - _ => return Err(err("invalid key header")) + "-----BEGIN RSA PRIVATE KEY-----" => rustls_pemfile::rsa_private_keys, + "-----BEGIN PRIVATE KEY-----" => rustls_pemfile::pkcs8_private_keys, + _ => return Err(err("invalid key header")), }; let key = private_keys_fn(&mut Cursor::new(first_line).chain(reader)) .map_err(|_| err("invalid key file")) .and_then(|mut keys| match keys.len() { 0 => Err(err("no valid keys found; is the file malformed?")), - 1 => Ok(keys.remove(0)), + 1 => Ok(PrivateKey(keys.remove(0))), n => Err(err(format!("expected 1 key, found {}", n))), })?; @@ -41,12 +42,14 @@ pub fn load_private_key(reader: &mut dyn io::BufRead) -> io::Result /// Load and decode CA certificates from `reader`. pub fn load_ca_certs(reader: &mut dyn io::BufRead) -> io::Result { let mut roots = rustls::RootCertStore::empty(); - let (_, e) = roots.add_pem_file(reader).map_err(|_| err("PEM format error"))?; - if e != 0 { - return Err(err("validity checks failed")); - } + let certs = rustls_pemfile::certs(reader).map_err(|_| err("invalid ca certificate"))?; - Ok(roots) + let (_, e_count) = roots.add_parsable_certificates(certs.as_slice()); + if e_count != 0 { + Err(err("validity checks failed")) + } else { + Ok(roots) + } } #[cfg(test)] @@ -55,8 +58,12 @@ mod test { macro_rules! tls_example_key { ($k:expr) => { - include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/../../examples/tls/private/", $k)) - } + include_bytes!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/../../examples/tls/private/", + $k + )) + }; } #[test] diff --git a/core/lib/Cargo.toml b/core/lib/Cargo.toml index 758fb4ea6e..706ec3c65d 100644 --- a/core/lib/Cargo.toml +++ b/core/lib/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rocket" -version = "0.5.0-rc.1" +version = "0.5.0-rc.2" authors = ["Sergio Benitez "] description = """ Web framework with a focus on usability, security, extensibility, and speed. @@ -15,6 +15,12 @@ build = "build.rs" categories = ["web-programming::http-server"] edition = "2018" +[package.metadata] +# minimum supported rust version 1.53.0 because of +# TlsConfig::with_ciphers() based on crate indexmap needs IntoIterator impls +# on arrays of any length from 1.53.0 +msrv = "1.53.0" + [package.metadata.docs.rs] all-features = true @@ -25,21 +31,22 @@ mtls = ["rocket_http/mtls", "tls"] secrets = ["rocket_http/private-cookies"] json = ["serde_json", "tokio/io-util"] msgpack = ["rmp-serde", "tokio/io-util"] +msgpack-compact = ["msgpack"] uuid = ["uuid_", "rocket_http/uuid"] [dependencies] # Serialization dependencies. -serde_json = { version = "1.0.26", optional = true } -rmp-serde = { version = "0.15.0", optional = true } +serde_json = { version = "1.0", optional = true } +rmp-serde = { version = "1.0", optional = true } uuid_ = { package = "uuid", version = "0.8", optional = true, features = ["serde"] } # Non-optional, core dependencies from here on out. -futures = { version = "0.3.0", default-features = false, features = ["std"] } +futures = { version = "0.3", default-features = false, features = ["std"] } yansi = "0.5" log = { version = "0.4", features = ["std"] } -num_cpus = "1.0" +num_cpus = "1.13" time = { version = "0.3", features = ["macros", "parsing"] } -memchr = "2" # TODO: Use pear instead. +memchr = "2.4" # TODO: Use pear instead. binascii = "0.1" atty = "0.2" ref-cast = "1.0" @@ -47,29 +54,29 @@ atomic = "0.5" parking_lot = "0.11" ubyte = {version = "0.10", features = ["serde"] } serde = { version = "1.0", features = ["derive"] } -figment = { version = "0.10.6", features = ["toml", "env"] } +figment = { version = "0.10", features = ["toml", "env"] } rand = "0.8" -either = "1" +either = "1.6" pin-project-lite = "0.2" -indexmap = { version = "1.0", features = ["serde-1", "std"] } -tempfile = "3" -async-trait = "0.1.43" -async-stream = "0.3.2" -multer = { version = "2", features = ["tokio-io"] } -tokio-stream = { version = "0.1.6", features = ["signal", "time"] } -state = "0.5.1" +indexmap = { version = "1.8", features = ["serde-1", "std"] } +tempfile = "3.3" +async-trait = "0.1" +async-stream = "0.3" +multer = { version = "2.0", features = ["tokio-io"] } +tokio-stream = { version = "0.1", features = ["signal", "time"] } +state = "0.5" [dependencies.rocket_codegen] -version = "0.5.0-rc.1" +version = "0.5.0-rc.2" path = "../codegen" [dependencies.rocket_http] -version = "0.5.0-rc.1" +version = "0.5.0-rc.2" path = "../http" features = ["serde"] [dependencies.tokio] -version = "1.6.1" +version = "1.15" features = ["fs", "io-std", "io-util", "rt-multi-thread", "sync", "signal", "macros"] [dependencies.tokio-util] @@ -82,8 +89,8 @@ version = "1.0" [build-dependencies] yansi = "0.5" -version_check = "0.9.1" +version_check = "0.9" [dev-dependencies] figment = { version = "0.10", features = ["test"] } -pretty_assertions = "0.7" +pretty_assertions = "1.0" diff --git a/core/lib/build.rs b/core/lib/build.rs index dd11943ba9..1f78674b73 100644 --- a/core/lib/build.rs +++ b/core/lib/build.rs @@ -3,7 +3,7 @@ use yansi::{Paint, Color::{Red, Yellow}}; fn main() { - const MIN_VERSION: &str = "1.46.0"; + const MIN_VERSION: &str = "1.54.0"; if let Some(version) = version_check::Version::read() { if !version.at_least(MIN_VERSION) { diff --git a/core/lib/fuzz/Cargo.toml b/core/lib/fuzz/Cargo.toml index a8334caa88..f1a0be047a 100644 --- a/core/lib/fuzz/Cargo.toml +++ b/core/lib/fuzz/Cargo.toml @@ -10,7 +10,7 @@ edition = "2018" cargo-fuzz = true [dependencies] -libfuzzer-sys = "0.4" +libfuzzer-sys = { version = "0.4", path = "../libfuzzer-sys" } [dependencies.rocket] path = ".." diff --git a/core/lib/libfuzzer-sys/.gitignore b/core/lib/libfuzzer-sys/.gitignore new file mode 100644 index 0000000000..a9d37c560c --- /dev/null +++ b/core/lib/libfuzzer-sys/.gitignore @@ -0,0 +1,2 @@ +target +Cargo.lock diff --git a/core/lib/libfuzzer-sys/CHANGELOG.md b/core/lib/libfuzzer-sys/CHANGELOG.md new file mode 100644 index 0000000000..77be1fde42 --- /dev/null +++ b/core/lib/libfuzzer-sys/CHANGELOG.md @@ -0,0 +1,170 @@ +## Unreleased + +Released YYYY-MM-DD. + +### Added + +* TODO (or remove section if none) + +### Changed + +* TODO (or remove section if none) + +### Deprecated + +* TODO (or remove section if none) + +### Removed + +* TODO (or remove section if none) + +### Fixed + +* TODO (or remove section if none) + +### Security + +* TODO (or remove section if none) + +-------------------------------------------------------------------------------- + +## 0.4.2 + +Released 2020-05-26. + +### Changed + +* Improved performance of checking for whether `cargo fuzz` is requesting the + `std::fmt::Debug` output of an input or not. This is always false during + regular fuzzing, so making this check faster should give slightly better + fuzzing throughput. + +-------------------------------------------------------------------------------- + +## 0.4.1 + +Released 2020-05-13. + +### Added + +* Added support for defining custom mutators. See [the documentation for the + `fuzz_mutator!` + macro](https://docs.rs/libfuzzer-sys/0.4.1/libfuzzer_sys/macro.fuzz_mutator.html) + for details. + +### Changed + +* Upgraded libfuzzer to llvm/llvm-project's 70cbc6d. + +-------------------------------------------------------------------------------- + +## 0.4.0 + +Released 2021-02-24. + +### Changed + +* The public `arbitrary` dependency was updated to version 1.0. + +-------------------------------------------------------------------------------- + +## 0.3.5 + +Released 2020-11-18. + +### Changed + +* [Upgrade libfuzzer to 7bf89c2](https://github.com/rust-fuzz/libfuzzer/pull/68) + +-------------------------------------------------------------------------------- + +## 0.3.4 + +Released 2020-08-22. + +### Changed + +* Updated `arbitrary` dependency to 0.4.6 + +-------------------------------------------------------------------------------- + +## 0.3.3 + +Released 2020-07-27. + +### Changed + +* Upgraded libfuzzer to commit + [4a4cafa](https://github.com/llvm/llvm-project/commit/4a4cafabc9067fced5890a245b03ef5897ad988b). + + Notably, this pulls in [the new Entropic engine for + libFuzzer](https://mboehme.github.io/paper/FSE20.Entropy.pdf), which should + boost fuzzing efficiency when enabled. You can enable Entropic by passing + `-entropic=1` to your built fuzz targets (although, note that it is still + labeled "experimental"). + +-------------------------------------------------------------------------------- + +## 0.3.2 + +Released 2020-03-18. + +### Changed + +* Upgraded the `arbitrary` dependency re-export to version 0.4.1. + +-------------------------------------------------------------------------------- + +## 0.3.1 + +Released 2020-02-27. + +### Changed + +* Fixed a fuzzing performance issue where libfuzzer could unnecessarily spend + time exploring all the ways that an `Arbitrary` implementation could fail to + construct an instance of itself because the fuzzer provided too few bytes. See + https://github.com/rust-fuzz/libfuzzer/issues/59 for details. + +-------------------------------------------------------------------------------- + +## 0.3.0 + +Released 2019-01-22. + +### Changed + +* Now works with and re-exports `arbitrary` versions 0.4.x. + +-------------------------------------------------------------------------------- + +## 0.2.1 + +Released 2019-01-16. + +### Added + +* Added support for the `CUSTOM_LIBFUZZER_STD_CXX=` environment variable + during builds that already use a custom libFuzzer checkout with + `CUSTOM_LIBFUZZER_PATH`. This allows you to explicitly choose to link LLVM or + GNU C++ standard libraries. + +-------------------------------------------------------------------------------- + +## 0.2.0 + +Released 2020-01-14. + +### Changed + +* Using `arbitrary` 0.3.x now. It is re-exported as `libfuzzer_sys::arbitrary`. + +### Added + +* You can enable support for `#[derive(Arbitrary)]` with the + `"arbitrary-derive"` cargo feature. This is a synonym for the `arbitrary` + crate's `"derive"` cargo feature. + +-------------------------------------------------------------------------------- + +## 0.1.0 diff --git a/core/lib/libfuzzer-sys/Cargo.toml b/core/lib/libfuzzer-sys/Cargo.toml new file mode 100644 index 0000000000..72285b926c --- /dev/null +++ b/core/lib/libfuzzer-sys/Cargo.toml @@ -0,0 +1,30 @@ +[package] +authors = ["The rust-fuzz Project Developers"] +description = "A wrapper around LLVM's libFuzzer runtime." +edition = "2018" +license = "MIT/Apache-2.0/NCSA" +name = "libfuzzer-sys" +readme = "./README.md" +repository = "https://github.com/rust-fuzz/libfuzzer" +version = "0.4.2" + +[dependencies] +arbitrary = "1" +once_cell = "1" + +[build-dependencies] +cc = "1.0" + +[features] +arbitrary-derive = ["arbitrary/derive"] + +[workspace] +members = [ + "./example", + "./example_arbitrary", + "./example_mutator", +] + +[dev-dependencies] +flate2 = "1.0.20" +rand = "0.8.3" diff --git a/core/lib/libfuzzer-sys/LICENSE-APACHE b/core/lib/libfuzzer-sys/LICENSE-APACHE new file mode 100644 index 0000000000..16fe87b06e --- /dev/null +++ b/core/lib/libfuzzer-sys/LICENSE-APACHE @@ -0,0 +1,201 @@ + 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 + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/core/lib/libfuzzer-sys/LICENSE-MIT b/core/lib/libfuzzer-sys/LICENSE-MIT new file mode 100644 index 0000000000..25597d5838 --- /dev/null +++ b/core/lib/libfuzzer-sys/LICENSE-MIT @@ -0,0 +1,25 @@ +Copyright (c) 2010 The Rust Project Developers + +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/core/lib/libfuzzer-sys/README.md b/core/lib/libfuzzer-sys/README.md new file mode 100644 index 0000000000..8bdba6eb70 --- /dev/null +++ b/core/lib/libfuzzer-sys/README.md @@ -0,0 +1,74 @@ +# The `libfuzzer-sys` Crate + +Barebones wrapper around LLVM's libFuzzer runtime library. + +The CPP parts are extracted from compiler-rt git repository with `git filter-branch`. + +libFuzzer relies on LLVM sanitizer support. The Rust compiler has built-in support for LLVM sanitizer support, for now, it's limited to Linux. As a result, `libfuzzer-sys` only works on Linux. + +## Usage + +### Use `cargo fuzz`! + +[The recommended way to use this crate with `cargo fuzz`!][cargo-fuzz]. + +[cargo-fuzz]: https://github.com/rust-fuzz/cargo-fuzz + +### Manual Usage + +This crate can also be used manually as following: + +First create a new cargo project: + +``` +$ cargo new --bin fuzzed +$ cd fuzzed +``` + +Then add a dependency on the `fuzzer-sys` crate and your own crate: + +```toml +[dependencies] +libfuzzer-sys = "0.4.0" +your_crate = { path = "../path/to/your/crate" } +``` + +Change the `fuzzed/src/main.rs` to fuzz your code: + +```rust +#![no_main] + +use libfuzzer_sys::fuzz_target; + +fuzz_target!(|data: &[u8]| { + // code to fuzz goes here +}); +``` + +Build by running the following command: + +```sh +$ cargo rustc -- \ + -C passes='sancov' \ + -C llvm-args='-sanitizer-coverage-level=3' \ + -C llvm-args='-sanitizer-coverage-inline-8bit-counters' \ + -Z sanitizer=address +``` + +And finally, run the fuzzer: + +```sh +$ ./target/debug/fuzzed +``` + +## Updating libfuzzer from upstream + +``` +./update-libfuzzer.sh +``` + +## License + +All files in `libfuzzer` directory are licensed NCSA. + +Everything else is dual-licensed Apache 2.0 and MIT. diff --git a/core/lib/libfuzzer-sys/build.rs b/core/lib/libfuzzer-sys/build.rs new file mode 100644 index 0000000000..21c57c15d9 --- /dev/null +++ b/core/lib/libfuzzer-sys/build.rs @@ -0,0 +1,35 @@ +fn main() { + if let Ok(custom) = ::std::env::var("CUSTOM_LIBFUZZER_PATH") { + let custom_lib_path = ::std::path::PathBuf::from(&custom); + let custom_lib_dir = custom_lib_path.parent().unwrap().to_string_lossy(); + + let custom_lib_name = custom_lib_path.file_stem().unwrap().to_string_lossy(); + let custom_lib_name = custom_lib_name.trim_start_matches("lib"); + + println!("cargo:rustc-link-search=native={}", custom_lib_dir); + println!("cargo:rustc-link-lib=static={}", custom_lib_name); + + match std::env::var("CUSTOM_LIBFUZZER_STD_CXX") { + // Default behavior for backwards compat. + Err(_) => println!("cargo:rustc-link-lib=stdc++"), + Ok(s) if s == "none" => (), + Ok(s) => println!("cargo:rustc-link-lib={}", s), + } + } else { + let mut build = cc::Build::new(); + let sources = ::std::fs::read_dir("libfuzzer") + .expect("listable source directory") + .map(|de| de.expect("file in directory").path()) + .filter(|p| p.extension().map(|ext| ext == "cpp") == Some(true)) + .collect::>(); + for source in sources.iter() { + println!("cargo:rerun-if-changed={}", source.display()); + build.file(source.to_str().unwrap()); + } + build.flag("-std=c++11"); + build.flag("-fno-omit-frame-pointer"); + build.flag("-w"); + build.cpp(true); + build.compile("libfuzzer.a"); + } +} diff --git a/core/lib/libfuzzer-sys/ci/script.sh b/core/lib/libfuzzer-sys/ci/script.sh new file mode 100755 index 0000000000..91fad80eb5 --- /dev/null +++ b/core/lib/libfuzzer-sys/ci/script.sh @@ -0,0 +1,60 @@ +#!/usr/bin/env bash + +set -eux +cd $(dirname $0)/.. + +export CARGO_TARGET_DIR=$(pwd)/target + +cargo test --doc + +pushd ./example +cargo rustc \ + --release \ + -- \ + -Cpasses='sancov' \ + -Cllvm-args=-sanitizer-coverage-level=3 \ + -Cllvm-args=-sanitizer-coverage-trace-compares \ + -Cllvm-args=-sanitizer-coverage-inline-8bit-counters \ + -Cllvm-args=-sanitizer-coverage-stack-depth \ + -Cllvm-args=-sanitizer-coverage-trace-geps \ + -Cllvm-args=-sanitizer-coverage-prune-blocks=0 \ + -Zsanitizer=address +(! $CARGO_TARGET_DIR/release/example -runs=100000) +popd + +pushd ./example_arbitrary +cargo rustc \ + --release \ + -- \ + -Cpasses='sancov' \ + -Cllvm-args=-sanitizer-coverage-level=3 \ + -Cllvm-args=-sanitizer-coverage-trace-compares \ + -Cllvm-args=-sanitizer-coverage-inline-8bit-counters \ + -Cllvm-args=-sanitizer-coverage-stack-depth \ + -Cllvm-args=-sanitizer-coverage-trace-geps \ + -Cllvm-args=-sanitizer-coverage-prune-blocks=0 \ + -Zsanitizer=address +(! $CARGO_TARGET_DIR/release/example_arbitrary -runs=10000000) +RUST_LIBFUZZER_DEBUG_PATH=$(pwd)/debug_output \ + $CARGO_TARGET_DIR/release/example_arbitrary \ + $(ls ./crash-* | head -n 1) +cat $(pwd)/debug_output +grep -q Rgb $(pwd)/debug_output +popd + +pushd ./example_mutator +cargo rustc \ + --release \ + -- \ + -Cpasses='sancov' \ + -Cllvm-args=-sanitizer-coverage-level=3 \ + -Cllvm-args=-sanitizer-coverage-trace-compares \ + -Cllvm-args=-sanitizer-coverage-inline-8bit-counters \ + -Cllvm-args=-sanitizer-coverage-stack-depth \ + -Cllvm-args=-sanitizer-coverage-trace-geps \ + -Cllvm-args=-sanitizer-coverage-prune-blocks=0 \ + -Zsanitizer=address +(! $CARGO_TARGET_DIR/release/example_mutator -runs=10000000) +popd + +echo "All good!" diff --git a/core/lib/libfuzzer-sys/example/.gitignore b/core/lib/libfuzzer-sys/example/.gitignore new file mode 100644 index 0000000000..d8d1df60ce --- /dev/null +++ b/core/lib/libfuzzer-sys/example/.gitignore @@ -0,0 +1 @@ +crash-* diff --git a/core/lib/libfuzzer-sys/example/Cargo.toml b/core/lib/libfuzzer-sys/example/Cargo.toml new file mode 100644 index 0000000000..b338d950ee --- /dev/null +++ b/core/lib/libfuzzer-sys/example/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "example" +version = "0.1.0" +authors = ["Simonas Kazlauskas "] +edition = "2018" + +[dependencies] +libfuzzer-sys = { path = ".." } diff --git a/core/lib/libfuzzer-sys/example/src/main.rs b/core/lib/libfuzzer-sys/example/src/main.rs new file mode 100755 index 0000000000..be7ebe2f1b --- /dev/null +++ b/core/lib/libfuzzer-sys/example/src/main.rs @@ -0,0 +1,9 @@ +#![no_main] + +use libfuzzer_sys::fuzz_target; + +fuzz_target!(|data: &[u8]| { + if data == b"banana!" { + panic!("success!"); + } +}); diff --git a/core/lib/libfuzzer-sys/example_arbitrary/.gitignore b/core/lib/libfuzzer-sys/example_arbitrary/.gitignore new file mode 100644 index 0000000000..24aac753c4 --- /dev/null +++ b/core/lib/libfuzzer-sys/example_arbitrary/.gitignore @@ -0,0 +1,2 @@ +crash-* +debug_output diff --git a/core/lib/libfuzzer-sys/example_arbitrary/Cargo.toml b/core/lib/libfuzzer-sys/example_arbitrary/Cargo.toml new file mode 100644 index 0000000000..4593fd02eb --- /dev/null +++ b/core/lib/libfuzzer-sys/example_arbitrary/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "example_arbitrary" +version = "0.1.0" +authors = ["Simonas Kazlauskas "] +edition = "2018" + +[dependencies] +libfuzzer-sys = { path = "..", features = ["arbitrary-derive"] } diff --git a/core/lib/libfuzzer-sys/example_arbitrary/src/main.rs b/core/lib/libfuzzer-sys/example_arbitrary/src/main.rs new file mode 100755 index 0000000000..df4eecf224 --- /dev/null +++ b/core/lib/libfuzzer-sys/example_arbitrary/src/main.rs @@ -0,0 +1,18 @@ +#![no_main] + +use libfuzzer_sys::{arbitrary, fuzz_target}; + +#[derive(arbitrary::Arbitrary, Debug)] +struct Rgb { + r: u8, + g: u8, + b: u8, +} + +fuzz_target!(|rgb: Rgb| { + if rgb.r < rgb.g { + if rgb.g < rgb.b { + panic!("success: r < g < b!"); + } + } +}); diff --git a/core/lib/libfuzzer-sys/example_mutator/.gitignore b/core/lib/libfuzzer-sys/example_mutator/.gitignore new file mode 100644 index 0000000000..d8d1df60ce --- /dev/null +++ b/core/lib/libfuzzer-sys/example_mutator/.gitignore @@ -0,0 +1 @@ +crash-* diff --git a/core/lib/libfuzzer-sys/example_mutator/Cargo.toml b/core/lib/libfuzzer-sys/example_mutator/Cargo.toml new file mode 100644 index 0000000000..a74f650a86 --- /dev/null +++ b/core/lib/libfuzzer-sys/example_mutator/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "example_mutator" +version = "0.1.0" +authors = ["Nick Fitzgerald "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +flate2 = "1.0.20" +libfuzzer-sys = { path = ".." } diff --git a/core/lib/libfuzzer-sys/example_mutator/src/main.rs b/core/lib/libfuzzer-sys/example_mutator/src/main.rs new file mode 100755 index 0000000000..c0ca0b4b86 --- /dev/null +++ b/core/lib/libfuzzer-sys/example_mutator/src/main.rs @@ -0,0 +1,55 @@ +#![no_main] + +use flate2::{read::GzDecoder, write::GzEncoder, Compression}; +use libfuzzer_sys::{fuzz_mutator, fuzz_target}; +use std::io::{Read, Write}; + +fuzz_target!(|data: &[u8]| { + // Decompress the input data and crash if it starts with "boom". + if let Some(data) = decompress(data) { + if data.starts_with(b"boom") { + panic!(); + } + } +}); + +fuzz_mutator!( + |data: &mut [u8], size: usize, max_size: usize, _seed: u32| { + // Decompress the input data. If that fails, use a dummy value. + let mut decompressed = decompress(&data[..size]).unwrap_or_else(|| b"hi".to_vec()); + + // Mutate the decompressed data with `libFuzzer`'s default mutator. Make + // the `decompressed` vec's extra capacity available for insertion + // mutations via `resize`. + let len = decompressed.len(); + let cap = decompressed.capacity(); + decompressed.resize(cap, 0); + let new_decompressed_size = libfuzzer_sys::fuzzer_mutate(&mut decompressed, len, cap); + + // Recompress the mutated data. + let compressed = compress(&decompressed[..new_decompressed_size]); + + // Copy the recompressed mutated data into `data` and return the new size. + let new_size = std::cmp::min(max_size, compressed.len()); + data[..new_size].copy_from_slice(&compressed[..new_size]); + new_size + } +); + +fn decompress(data: &[u8]) -> Option> { + let mut decoder = GzDecoder::new(data); + let mut decompressed = Vec::new(); + if decoder.read_to_end(&mut decompressed).is_ok() { + Some(decompressed) + } else { + None + } +} + +fn compress(data: &[u8]) -> Vec { + let mut encoder = GzEncoder::new(Vec::new(), Compression::default()); + encoder + .write_all(data) + .expect("writing into a vec is infallible"); + encoder.finish().expect("writing into a vec is infallible") +} diff --git a/core/lib/libfuzzer-sys/libfuzzer/CMakeLists.txt b/core/lib/libfuzzer-sys/libfuzzer/CMakeLists.txt new file mode 100644 index 0000000000..3201ed279a --- /dev/null +++ b/core/lib/libfuzzer-sys/libfuzzer/CMakeLists.txt @@ -0,0 +1,178 @@ +set(LIBFUZZER_SOURCES + FuzzerCrossOver.cpp + FuzzerDataFlowTrace.cpp + FuzzerDriver.cpp + FuzzerExtFunctionsDlsym.cpp + FuzzerExtFunctionsWeak.cpp + FuzzerExtFunctionsWindows.cpp + FuzzerExtraCounters.cpp + FuzzerFork.cpp + FuzzerIO.cpp + FuzzerIOPosix.cpp + FuzzerIOWindows.cpp + FuzzerLoop.cpp + FuzzerMerge.cpp + FuzzerMutate.cpp + FuzzerSHA1.cpp + FuzzerTracePC.cpp + FuzzerUtil.cpp + FuzzerUtilDarwin.cpp + FuzzerUtilFuchsia.cpp + FuzzerUtilLinux.cpp + FuzzerUtilPosix.cpp + FuzzerUtilWindows.cpp) + +set(LIBFUZZER_HEADERS + FuzzerBuiltins.h + FuzzerBuiltinsMsvc.h + FuzzerCommand.h + FuzzerCorpus.h + FuzzerDataFlowTrace.h + FuzzerDefs.h + FuzzerDictionary.h + FuzzerExtFunctions.def + FuzzerExtFunctions.h + FuzzerFlags.def + FuzzerFork.h + FuzzerIO.h + FuzzerInterface.h + FuzzerInternal.h + FuzzerMerge.h + FuzzerMutate.h + FuzzerOptions.h + FuzzerRandom.h + FuzzerSHA1.h + FuzzerTracePC.h + FuzzerUtil.h + FuzzerValueBitMap.h) + +include_directories(../../include) + +CHECK_CXX_SOURCE_COMPILES(" + static thread_local int blah; + int main() { + return 0; + } + " HAS_THREAD_LOCAL) + +set(LIBFUZZER_CFLAGS ${COMPILER_RT_COMMON_CFLAGS}) + +if(OS_NAME MATCHES "Linux|Fuchsia" AND + COMPILER_RT_LIBCXX_PATH AND + COMPILER_RT_LIBCXXABI_PATH) + list(APPEND LIBFUZZER_CFLAGS -D_LIBCPP_ABI_VERSION=Fuzzer) + append_list_if(COMPILER_RT_HAS_NOSTDINCXX_FLAG -nostdinc++ LIBFUZZER_CFLAGS) +elseif(TARGET cxx-headers OR HAVE_LIBCXX) + # libFuzzer uses C++ standard library headers. + set(LIBFUZZER_DEPS cxx-headers) +endif() + +append_list_if(COMPILER_RT_HAS_OMIT_FRAME_POINTER_FLAG -fno-omit-frame-pointer LIBFUZZER_CFLAGS) + +if (CMAKE_CXX_FLAGS MATCHES "fsanitize-coverage") + list(APPEND LIBFUZZER_CFLAGS -fno-sanitize-coverage=trace-pc-guard,edge,trace-cmp,indirect-calls,8bit-counters) +endif() + +if(MSVC) + # Silence warnings by turning off exceptions in MSVC headers and avoid an + # error by unecessarily defining thread_local when it isn't even used on + # Windows. + list(APPEND LIBFUZZER_CFLAGS -D_HAS_EXCEPTIONS=0) +else() + if(NOT HAS_THREAD_LOCAL) + list(APPEND LIBFUZZER_CFLAGS -Dthread_local=__thread) + endif() +endif() + +add_compiler_rt_component(fuzzer) + +add_compiler_rt_object_libraries(RTfuzzer + OS ${FUZZER_SUPPORTED_OS} + ARCHS ${FUZZER_SUPPORTED_ARCH} + SOURCES ${LIBFUZZER_SOURCES} + ADDITIONAL_HEADERS ${LIBFUZZER_HEADERS} + CFLAGS ${LIBFUZZER_CFLAGS} + DEPS ${LIBFUZZER_DEPS}) + +add_compiler_rt_object_libraries(RTfuzzer_main + OS ${FUZZER_SUPPORTED_OS} + ARCHS ${FUZZER_SUPPORTED_ARCH} + SOURCES FuzzerMain.cpp + CFLAGS ${LIBFUZZER_CFLAGS} + DEPS ${LIBFUZZER_DEPS}) + +add_compiler_rt_object_libraries(RTfuzzer_interceptors + OS ${FUZZER_SUPPORTED_OS} + ARCHS ${FUZZER_SUPPORTED_ARCH} + SOURCES FuzzerInterceptors.cpp + CFLAGS ${LIBFUZZER_CFLAGS} + DEPS ${LIBFUZZER_DEPS}) + +add_compiler_rt_runtime(clang_rt.fuzzer + STATIC + OS ${FUZZER_SUPPORTED_OS} + ARCHS ${FUZZER_SUPPORTED_ARCH} + OBJECT_LIBS RTfuzzer RTfuzzer_main + CFLAGS ${LIBFUZZER_CFLAGS} + PARENT_TARGET fuzzer) + +add_compiler_rt_runtime(clang_rt.fuzzer_no_main + STATIC + OS ${FUZZER_SUPPORTED_OS} + ARCHS ${FUZZER_SUPPORTED_ARCH} + OBJECT_LIBS RTfuzzer + CFLAGS ${LIBFUZZER_CFLAGS} + PARENT_TARGET fuzzer) + +add_compiler_rt_runtime(clang_rt.fuzzer_interceptors + STATIC + OS ${FUZZER_SUPPORTED_OS} + ARCHS ${FUZZER_SUPPORTED_ARCH} + OBJECT_LIBS RTfuzzer_interceptors + CFLAGS ${LIBFUZZER_CFLAGS} + PARENT_TARGET fuzzer) + +if(OS_NAME MATCHES "Linux|Fuchsia" AND + COMPILER_RT_LIBCXX_PATH AND + COMPILER_RT_LIBCXXABI_PATH) + macro(partially_link_libcxx name dir arch) + if(${arch} MATCHES "i386") + set(EMULATION_ARGUMENT "-m" "elf_i386") + else() + set(EMULATION_ARGUMENT "") + endif() + set(cxx_${arch}_merge_dir "${CMAKE_CURRENT_BINARY_DIR}/cxx_${arch}_merge.dir") + file(MAKE_DIRECTORY ${cxx_${arch}_merge_dir}) + add_custom_command(TARGET clang_rt.${name}-${arch} POST_BUILD + COMMAND ${CMAKE_LINKER} ${EMULATION_ARGUMENT} --whole-archive "$" --no-whole-archive ${dir}/lib/libc++.a -r -o ${name}.o + COMMAND ${CMAKE_OBJCOPY} --localize-hidden ${name}.o + COMMAND ${CMAKE_COMMAND} -E remove "$" + COMMAND ${CMAKE_AR} qcs "$" ${name}.o + WORKING_DIRECTORY ${cxx_${arch}_merge_dir} + ) + endmacro() + + foreach(arch ${FUZZER_SUPPORTED_ARCH}) + get_target_flags_for_arch(${arch} TARGET_CFLAGS) + set(LIBCXX_${arch}_PREFIX ${CMAKE_CURRENT_BINARY_DIR}/libcxx_fuzzer_${arch}) + add_custom_libcxx(libcxx_fuzzer_${arch} ${LIBCXX_${arch}_PREFIX} + CFLAGS ${TARGET_CFLAGS} + CMAKE_ARGS -DCMAKE_CXX_COMPILER_WORKS=ON + -DCMAKE_POSITION_INDEPENDENT_CODE=ON + -DLIBCXXABI_ENABLE_EXCEPTIONS=OFF + -DLIBCXX_ABI_NAMESPACE=__Fuzzer) + target_compile_options(RTfuzzer.${arch} PRIVATE -isystem ${LIBCXX_${arch}_PREFIX}/include/c++/v1) + add_dependencies(RTfuzzer.${arch} libcxx_fuzzer_${arch}-build) + target_compile_options(RTfuzzer_main.${arch} PRIVATE -isystem ${LIBCXX_${arch}_PREFIX}/include/c++/v1) + add_dependencies(RTfuzzer_main.${arch} libcxx_fuzzer_${arch}-build) + target_compile_options(RTfuzzer_interceptors.${arch} PRIVATE -isystem ${LIBCXX_${arch}_PREFIX}/include/c++/v1) + add_dependencies(RTfuzzer_interceptors.${arch} libcxx_fuzzer_${arch}-build) + partially_link_libcxx(fuzzer_no_main ${LIBCXX_${arch}_PREFIX} ${arch}) + partially_link_libcxx(fuzzer_interceptors ${LIBCXX_${arch}_PREFIX} ${arch}) + partially_link_libcxx(fuzzer ${LIBCXX_${arch}_PREFIX} ${arch}) + endforeach() +endif() + +if(COMPILER_RT_INCLUDE_TESTS) + add_subdirectory(tests) +endif() diff --git a/core/lib/libfuzzer-sys/libfuzzer/FuzzerBuiltins.h b/core/lib/libfuzzer-sys/libfuzzer/FuzzerBuiltins.h new file mode 100644 index 0000000000..ce0bd5cb47 --- /dev/null +++ b/core/lib/libfuzzer-sys/libfuzzer/FuzzerBuiltins.h @@ -0,0 +1,34 @@ +//===- FuzzerBuiltins.h - Internal header for builtins ----------*- C++ -* ===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// Wrapper functions and marcos around builtin functions. +//===----------------------------------------------------------------------===// + +#ifndef LLVM_FUZZER_BUILTINS_H +#define LLVM_FUZZER_BUILTINS_H + +#include "FuzzerPlatform.h" + +#if !LIBFUZZER_MSVC +#include + +#define GET_CALLER_PC() __builtin_return_address(0) + +namespace fuzzer { + +inline uint8_t Bswap(uint8_t x) { return x; } +inline uint16_t Bswap(uint16_t x) { return __builtin_bswap16(x); } +inline uint32_t Bswap(uint32_t x) { return __builtin_bswap32(x); } +inline uint64_t Bswap(uint64_t x) { return __builtin_bswap64(x); } + +inline uint32_t Clzll(unsigned long long X) { return __builtin_clzll(X); } +inline int Popcountll(unsigned long long X) { return __builtin_popcountll(X); } + +} // namespace fuzzer + +#endif // !LIBFUZZER_MSVC +#endif // LLVM_FUZZER_BUILTINS_H diff --git a/core/lib/libfuzzer-sys/libfuzzer/FuzzerBuiltinsMsvc.h b/core/lib/libfuzzer-sys/libfuzzer/FuzzerBuiltinsMsvc.h new file mode 100644 index 0000000000..ab191b60ef --- /dev/null +++ b/core/lib/libfuzzer-sys/libfuzzer/FuzzerBuiltinsMsvc.h @@ -0,0 +1,66 @@ +//===- FuzzerBuiltinsMSVC.h - Internal header for builtins ------*- C++ -* ===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// Wrapper functions and marcos that use intrinsics instead of builtin functions +// which cannot be compiled by MSVC. +//===----------------------------------------------------------------------===// + +#ifndef LLVM_FUZZER_BUILTINS_MSVC_H +#define LLVM_FUZZER_BUILTINS_MSVC_H + +#include "FuzzerPlatform.h" + +#if LIBFUZZER_MSVC +#include +#include +#include + +// __builtin_return_address() cannot be compiled with MSVC. Use the equivalent +// from +#define GET_CALLER_PC() _ReturnAddress() + +namespace fuzzer { + +inline uint8_t Bswap(uint8_t x) { return x; } +// Use alternatives to __builtin functions from and on +// Windows since the builtins are not supported by MSVC. +inline uint16_t Bswap(uint16_t x) { return _byteswap_ushort(x); } +inline uint32_t Bswap(uint32_t x) { return _byteswap_ulong(x); } +inline uint64_t Bswap(uint64_t x) { return _byteswap_uint64(x); } + +// The functions below were mostly copied from +// compiler-rt/lib/builtins/int_lib.h which defines the __builtin functions used +// outside of Windows. +inline uint32_t Clzll(uint64_t X) { + unsigned long LeadZeroIdx = 0; + +#if !defined(_M_ARM) && !defined(_M_X64) + // Scan the high 32 bits. + if (_BitScanReverse(&LeadZeroIdx, static_cast(X >> 32))) + return static_cast(63 - (LeadZeroIdx + 32)); // Create a bit offset from the MSB. + // Scan the low 32 bits. + if (_BitScanReverse(&LeadZeroIdx, static_cast(X))) + return static_cast(63 - LeadZeroIdx); + +#else + if (_BitScanReverse64(&LeadZeroIdx, X)) return 63 - LeadZeroIdx; +#endif + return 64; +} + +inline int Popcountll(unsigned long long X) { +#if !defined(_M_ARM) && !defined(_M_X64) + return __popcnt(X) + __popcnt(X >> 32); +#else + return __popcnt64(X); +#endif +} + +} // namespace fuzzer + +#endif // LIBFUZER_MSVC +#endif // LLVM_FUZZER_BUILTINS_MSVC_H diff --git a/core/lib/libfuzzer-sys/libfuzzer/FuzzerCommand.h b/core/lib/libfuzzer-sys/libfuzzer/FuzzerCommand.h new file mode 100644 index 0000000000..87308864af --- /dev/null +++ b/core/lib/libfuzzer-sys/libfuzzer/FuzzerCommand.h @@ -0,0 +1,178 @@ +//===- FuzzerCommand.h - Interface representing a process -------*- C++ -* ===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// FuzzerCommand represents a command to run in a subprocess. It allows callers +// to manage command line arguments and output and error streams. +//===----------------------------------------------------------------------===// + +#ifndef LLVM_FUZZER_COMMAND_H +#define LLVM_FUZZER_COMMAND_H + +#include "FuzzerDefs.h" +#include "FuzzerIO.h" + +#include +#include +#include +#include + +namespace fuzzer { + +class Command final { +public: + // This command line flag is used to indicate that the remaining command line + // is immutable, meaning this flag effectively marks the end of the mutable + // argument list. + static inline const char *ignoreRemainingArgs() { + return "-ignore_remaining_args=1"; + } + + Command() : CombinedOutAndErr(false) {} + + explicit Command(const Vector &ArgsToAdd) + : Args(ArgsToAdd), CombinedOutAndErr(false) {} + + explicit Command(const Command &Other) + : Args(Other.Args), CombinedOutAndErr(Other.CombinedOutAndErr), + OutputFile(Other.OutputFile) {} + + Command &operator=(const Command &Other) { + Args = Other.Args; + CombinedOutAndErr = Other.CombinedOutAndErr; + OutputFile = Other.OutputFile; + return *this; + } + + ~Command() {} + + // Returns true if the given Arg is present in Args. Only checks up to + // "-ignore_remaining_args=1". + bool hasArgument(const std::string &Arg) const { + auto i = endMutableArgs(); + return std::find(Args.begin(), i, Arg) != i; + } + + // Gets all of the current command line arguments, **including** those after + // "-ignore-remaining-args=1". + const Vector &getArguments() const { return Args; } + + // Adds the given argument before "-ignore_remaining_args=1", or at the end + // if that flag isn't present. + void addArgument(const std::string &Arg) { + Args.insert(endMutableArgs(), Arg); + } + + // Adds all given arguments before "-ignore_remaining_args=1", or at the end + // if that flag isn't present. + void addArguments(const Vector &ArgsToAdd) { + Args.insert(endMutableArgs(), ArgsToAdd.begin(), ArgsToAdd.end()); + } + + // Removes the given argument from the command argument list. Ignores any + // occurrences after "-ignore_remaining_args=1", if present. + void removeArgument(const std::string &Arg) { + auto i = endMutableArgs(); + Args.erase(std::remove(Args.begin(), i, Arg), i); + } + + // Like hasArgument, but checks for "-[Flag]=...". + bool hasFlag(const std::string &Flag) const { + std::string Arg("-" + Flag + "="); + auto IsMatch = [&](const std::string &Other) { + return Arg.compare(0, std::string::npos, Other, 0, Arg.length()) == 0; + }; + return std::any_of(Args.begin(), endMutableArgs(), IsMatch); + } + + // Returns the value of the first instance of a given flag, or an empty string + // if the flag isn't present. Ignores any occurrences after + // "-ignore_remaining_args=1", if present. + std::string getFlagValue(const std::string &Flag) const { + std::string Arg("-" + Flag + "="); + auto IsMatch = [&](const std::string &Other) { + return Arg.compare(0, std::string::npos, Other, 0, Arg.length()) == 0; + }; + auto i = endMutableArgs(); + auto j = std::find_if(Args.begin(), i, IsMatch); + std::string result; + if (j != i) { + result = j->substr(Arg.length()); + } + return result; + } + + // Like AddArgument, but adds "-[Flag]=[Value]". + void addFlag(const std::string &Flag, const std::string &Value) { + addArgument("-" + Flag + "=" + Value); + } + + // Like RemoveArgument, but removes "-[Flag]=...". + void removeFlag(const std::string &Flag) { + std::string Arg("-" + Flag + "="); + auto IsMatch = [&](const std::string &Other) { + return Arg.compare(0, std::string::npos, Other, 0, Arg.length()) == 0; + }; + auto i = endMutableArgs(); + Args.erase(std::remove_if(Args.begin(), i, IsMatch), i); + } + + // Returns whether the command's stdout is being written to an output file. + bool hasOutputFile() const { return !OutputFile.empty(); } + + // Returns the currently set output file. + const std::string &getOutputFile() const { return OutputFile; } + + // Configures the command to redirect its output to the name file. + void setOutputFile(const std::string &FileName) { OutputFile = FileName; } + + // Returns whether the command's stderr is redirected to stdout. + bool isOutAndErrCombined() const { return CombinedOutAndErr; } + + // Sets whether to redirect the command's stderr to its stdout. + void combineOutAndErr(bool combine = true) { CombinedOutAndErr = combine; } + + // Returns a string representation of the command. On many systems this will + // be the equivalent command line. + std::string toString() const { + std::stringstream SS; + for (auto arg : getArguments()) + SS << arg << " "; + if (hasOutputFile()) + SS << ">" << getOutputFile() << " "; + if (isOutAndErrCombined()) + SS << "2>&1 "; + std::string result = SS.str(); + if (!result.empty()) + result = result.substr(0, result.length() - 1); + return result; + } + +private: + Command(Command &&Other) = delete; + Command &operator=(Command &&Other) = delete; + + Vector::iterator endMutableArgs() { + return std::find(Args.begin(), Args.end(), ignoreRemainingArgs()); + } + + Vector::const_iterator endMutableArgs() const { + return std::find(Args.begin(), Args.end(), ignoreRemainingArgs()); + } + + // The command arguments. Args[0] is the command name. + Vector Args; + + // True indicates stderr is redirected to stdout. + bool CombinedOutAndErr; + + // If not empty, stdout is redirected to the named file. + std::string OutputFile; +}; + +} // namespace fuzzer + +#endif // LLVM_FUZZER_COMMAND_H diff --git a/core/lib/libfuzzer-sys/libfuzzer/FuzzerCorpus.h b/core/lib/libfuzzer-sys/libfuzzer/FuzzerCorpus.h new file mode 100644 index 0000000000..f8c126072c --- /dev/null +++ b/core/lib/libfuzzer-sys/libfuzzer/FuzzerCorpus.h @@ -0,0 +1,587 @@ +//===- FuzzerCorpus.h - Internal header for the Fuzzer ----------*- C++ -* ===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// fuzzer::InputCorpus +//===----------------------------------------------------------------------===// + +#ifndef LLVM_FUZZER_CORPUS +#define LLVM_FUZZER_CORPUS + +#include "FuzzerDataFlowTrace.h" +#include "FuzzerDefs.h" +#include "FuzzerIO.h" +#include "FuzzerRandom.h" +#include "FuzzerSHA1.h" +#include "FuzzerTracePC.h" +#include +#include +#include +#include +#include + +namespace fuzzer { + +struct InputInfo { + Unit U; // The actual input data. + std::chrono::microseconds TimeOfUnit; + uint8_t Sha1[kSHA1NumBytes]; // Checksum. + // Number of features that this input has and no smaller input has. + size_t NumFeatures = 0; + size_t Tmp = 0; // Used by ValidateFeatureSet. + // Stats. + size_t NumExecutedMutations = 0; + size_t NumSuccessfullMutations = 0; + bool NeverReduce = false; + bool MayDeleteFile = false; + bool Reduced = false; + bool HasFocusFunction = false; + Vector UniqFeatureSet; + Vector DataFlowTraceForFocusFunction; + // Power schedule. + bool NeedsEnergyUpdate = false; + double Energy = 0.0; + double SumIncidence = 0.0; + Vector> FeatureFreqs; + + // Delete feature Idx and its frequency from FeatureFreqs. + bool DeleteFeatureFreq(uint32_t Idx) { + if (FeatureFreqs.empty()) + return false; + + // Binary search over local feature frequencies sorted by index. + auto Lower = std::lower_bound(FeatureFreqs.begin(), FeatureFreqs.end(), + std::pair(Idx, 0)); + + if (Lower != FeatureFreqs.end() && Lower->first == Idx) { + FeatureFreqs.erase(Lower); + return true; + } + return false; + } + + // Assign more energy to a high-entropy seed, i.e., that reveals more + // information about the globally rare features in the neighborhood of the + // seed. Since we do not know the entropy of a seed that has never been + // executed we assign fresh seeds maximum entropy and let II->Energy approach + // the true entropy from above. If ScalePerExecTime is true, the computed + // entropy is scaled based on how fast this input executes compared to the + // average execution time of inputs. The faster an input executes, the more + // energy gets assigned to the input. + void UpdateEnergy(size_t GlobalNumberOfFeatures, bool ScalePerExecTime, + std::chrono::microseconds AverageUnitExecutionTime) { + Energy = 0.0; + SumIncidence = 0.0; + + // Apply add-one smoothing to locally discovered features. + for (auto F : FeatureFreqs) { + double LocalIncidence = F.second + 1; + Energy -= LocalIncidence * log(LocalIncidence); + SumIncidence += LocalIncidence; + } + + // Apply add-one smoothing to locally undiscovered features. + // PreciseEnergy -= 0; // since log(1.0) == 0) + SumIncidence += + static_cast(GlobalNumberOfFeatures - FeatureFreqs.size()); + + // Add a single locally abundant feature apply add-one smoothing. + double AbdIncidence = static_cast(NumExecutedMutations + 1); + Energy -= AbdIncidence * log(AbdIncidence); + SumIncidence += AbdIncidence; + + // Normalize. + if (SumIncidence != 0) + Energy = Energy / SumIncidence + log(SumIncidence); + + if (ScalePerExecTime) { + // Scaling to favor inputs with lower execution time. + uint32_t PerfScore = 100; + if (TimeOfUnit.count() > AverageUnitExecutionTime.count() * 10) + PerfScore = 10; + else if (TimeOfUnit.count() > AverageUnitExecutionTime.count() * 4) + PerfScore = 25; + else if (TimeOfUnit.count() > AverageUnitExecutionTime.count() * 2) + PerfScore = 50; + else if (TimeOfUnit.count() * 3 > AverageUnitExecutionTime.count() * 4) + PerfScore = 75; + else if (TimeOfUnit.count() * 4 < AverageUnitExecutionTime.count()) + PerfScore = 300; + else if (TimeOfUnit.count() * 3 < AverageUnitExecutionTime.count()) + PerfScore = 200; + else if (TimeOfUnit.count() * 2 < AverageUnitExecutionTime.count()) + PerfScore = 150; + + Energy *= PerfScore; + } + } + + // Increment the frequency of the feature Idx. + void UpdateFeatureFrequency(uint32_t Idx) { + NeedsEnergyUpdate = true; + + // The local feature frequencies is an ordered vector of pairs. + // If there are no local feature frequencies, push_back preserves order. + // Set the feature frequency for feature Idx32 to 1. + if (FeatureFreqs.empty()) { + FeatureFreqs.push_back(std::pair(Idx, 1)); + return; + } + + // Binary search over local feature frequencies sorted by index. + auto Lower = std::lower_bound(FeatureFreqs.begin(), FeatureFreqs.end(), + std::pair(Idx, 0)); + + // If feature Idx32 already exists, increment its frequency. + // Otherwise, insert a new pair right after the next lower index. + if (Lower != FeatureFreqs.end() && Lower->first == Idx) { + Lower->second++; + } else { + FeatureFreqs.insert(Lower, std::pair(Idx, 1)); + } + } +}; + +struct EntropicOptions { + bool Enabled; + size_t NumberOfRarestFeatures; + size_t FeatureFrequencyThreshold; + bool ScalePerExecTime; +}; + +class InputCorpus { + static const uint32_t kFeatureSetSize = 1 << 21; + static const uint8_t kMaxMutationFactor = 20; + static const size_t kSparseEnergyUpdates = 100; + + size_t NumExecutedMutations = 0; + + EntropicOptions Entropic; + +public: + InputCorpus(const std::string &OutputCorpus, EntropicOptions Entropic) + : Entropic(Entropic), OutputCorpus(OutputCorpus) { + memset(InputSizesPerFeature, 0, sizeof(InputSizesPerFeature)); + memset(SmallestElementPerFeature, 0, sizeof(SmallestElementPerFeature)); + } + ~InputCorpus() { + for (auto II : Inputs) + delete II; + } + size_t size() const { return Inputs.size(); } + size_t SizeInBytes() const { + size_t Res = 0; + for (auto II : Inputs) + Res += II->U.size(); + return Res; + } + size_t NumActiveUnits() const { + size_t Res = 0; + for (auto II : Inputs) + Res += !II->U.empty(); + return Res; + } + size_t MaxInputSize() const { + size_t Res = 0; + for (auto II : Inputs) + Res = std::max(Res, II->U.size()); + return Res; + } + void IncrementNumExecutedMutations() { NumExecutedMutations++; } + + size_t NumInputsThatTouchFocusFunction() { + return std::count_if(Inputs.begin(), Inputs.end(), [](const InputInfo *II) { + return II->HasFocusFunction; + }); + } + + size_t NumInputsWithDataFlowTrace() { + return std::count_if(Inputs.begin(), Inputs.end(), [](const InputInfo *II) { + return !II->DataFlowTraceForFocusFunction.empty(); + }); + } + + bool empty() const { return Inputs.empty(); } + const Unit &operator[] (size_t Idx) const { return Inputs[Idx]->U; } + InputInfo *AddToCorpus(const Unit &U, size_t NumFeatures, bool MayDeleteFile, + bool HasFocusFunction, bool NeverReduce, + std::chrono::microseconds TimeOfUnit, + const Vector &FeatureSet, + const DataFlowTrace &DFT, const InputInfo *BaseII) { + assert(!U.empty()); + if (FeatureDebug) + Printf("ADD_TO_CORPUS %zd NF %zd\n", Inputs.size(), NumFeatures); + // Inputs.size() is cast to uint32_t below. + assert(Inputs.size() < std::numeric_limits::max()); + Inputs.push_back(new InputInfo()); + InputInfo &II = *Inputs.back(); + II.U = U; + II.NumFeatures = NumFeatures; + II.NeverReduce = NeverReduce; + II.TimeOfUnit = TimeOfUnit; + II.MayDeleteFile = MayDeleteFile; + II.UniqFeatureSet = FeatureSet; + II.HasFocusFunction = HasFocusFunction; + // Assign maximal energy to the new seed. + II.Energy = RareFeatures.empty() ? 1.0 : log(RareFeatures.size()); + II.SumIncidence = static_cast(RareFeatures.size()); + II.NeedsEnergyUpdate = false; + std::sort(II.UniqFeatureSet.begin(), II.UniqFeatureSet.end()); + ComputeSHA1(U.data(), U.size(), II.Sha1); + auto Sha1Str = Sha1ToString(II.Sha1); + Hashes.insert(Sha1Str); + if (HasFocusFunction) + if (auto V = DFT.Get(Sha1Str)) + II.DataFlowTraceForFocusFunction = *V; + // This is a gross heuristic. + // Ideally, when we add an element to a corpus we need to know its DFT. + // But if we don't, we'll use the DFT of its base input. + if (II.DataFlowTraceForFocusFunction.empty() && BaseII) + II.DataFlowTraceForFocusFunction = BaseII->DataFlowTraceForFocusFunction; + DistributionNeedsUpdate = true; + PrintCorpus(); + // ValidateFeatureSet(); + return &II; + } + + // Debug-only + void PrintUnit(const Unit &U) { + if (!FeatureDebug) return; + for (uint8_t C : U) { + if (C != 'F' && C != 'U' && C != 'Z') + C = '.'; + Printf("%c", C); + } + } + + // Debug-only + void PrintFeatureSet(const Vector &FeatureSet) { + if (!FeatureDebug) return; + Printf("{"); + for (uint32_t Feature: FeatureSet) + Printf("%u,", Feature); + Printf("}"); + } + + // Debug-only + void PrintCorpus() { + if (!FeatureDebug) return; + Printf("======= CORPUS:\n"); + int i = 0; + for (auto II : Inputs) { + if (std::find(II->U.begin(), II->U.end(), 'F') != II->U.end()) { + Printf("[%2d] ", i); + Printf("%s sz=%zd ", Sha1ToString(II->Sha1).c_str(), II->U.size()); + PrintUnit(II->U); + Printf(" "); + PrintFeatureSet(II->UniqFeatureSet); + Printf("\n"); + } + i++; + } + } + + void Replace(InputInfo *II, const Unit &U) { + assert(II->U.size() > U.size()); + Hashes.erase(Sha1ToString(II->Sha1)); + DeleteFile(*II); + ComputeSHA1(U.data(), U.size(), II->Sha1); + Hashes.insert(Sha1ToString(II->Sha1)); + II->U = U; + II->Reduced = true; + DistributionNeedsUpdate = true; + } + + bool HasUnit(const Unit &U) { return Hashes.count(Hash(U)); } + bool HasUnit(const std::string &H) { return Hashes.count(H); } + InputInfo &ChooseUnitToMutate(Random &Rand) { + InputInfo &II = *Inputs[ChooseUnitIdxToMutate(Rand)]; + assert(!II.U.empty()); + return II; + } + + InputInfo &ChooseUnitToCrossOverWith(Random &Rand, bool UniformDist) { + if (!UniformDist) { + return ChooseUnitToMutate(Rand); + } + InputInfo &II = *Inputs[Rand(Inputs.size())]; + assert(!II.U.empty()); + return II; + } + + // Returns an index of random unit from the corpus to mutate. + size_t ChooseUnitIdxToMutate(Random &Rand) { + UpdateCorpusDistribution(Rand); + size_t Idx = static_cast(CorpusDistribution(Rand)); + assert(Idx < Inputs.size()); + return Idx; + } + + void PrintStats() { + for (size_t i = 0; i < Inputs.size(); i++) { + const auto &II = *Inputs[i]; + Printf(" [% 3zd %s] sz: % 5zd runs: % 5zd succ: % 5zd focus: %d\n", i, + Sha1ToString(II.Sha1).c_str(), II.U.size(), + II.NumExecutedMutations, II.NumSuccessfullMutations, II.HasFocusFunction); + } + } + + void PrintFeatureSet() { + for (size_t i = 0; i < kFeatureSetSize; i++) { + if(size_t Sz = GetFeature(i)) + Printf("[%zd: id %zd sz%zd] ", i, SmallestElementPerFeature[i], Sz); + } + Printf("\n\t"); + for (size_t i = 0; i < Inputs.size(); i++) + if (size_t N = Inputs[i]->NumFeatures) + Printf(" %zd=>%zd ", i, N); + Printf("\n"); + } + + void DeleteFile(const InputInfo &II) { + if (!OutputCorpus.empty() && II.MayDeleteFile) + RemoveFile(DirPlusFile(OutputCorpus, Sha1ToString(II.Sha1))); + } + + void DeleteInput(size_t Idx) { + InputInfo &II = *Inputs[Idx]; + DeleteFile(II); + Unit().swap(II.U); + II.Energy = 0.0; + II.NeedsEnergyUpdate = false; + DistributionNeedsUpdate = true; + if (FeatureDebug) + Printf("EVICTED %zd\n", Idx); + } + + void AddRareFeature(uint32_t Idx) { + // Maintain *at least* TopXRarestFeatures many rare features + // and all features with a frequency below ConsideredRare. + // Remove all other features. + while (RareFeatures.size() > Entropic.NumberOfRarestFeatures && + FreqOfMostAbundantRareFeature > Entropic.FeatureFrequencyThreshold) { + + // Find most and second most abbundant feature. + uint32_t MostAbundantRareFeatureIndices[2] = {RareFeatures[0], + RareFeatures[0]}; + size_t Delete = 0; + for (size_t i = 0; i < RareFeatures.size(); i++) { + uint32_t Idx2 = RareFeatures[i]; + if (GlobalFeatureFreqs[Idx2] >= + GlobalFeatureFreqs[MostAbundantRareFeatureIndices[0]]) { + MostAbundantRareFeatureIndices[1] = MostAbundantRareFeatureIndices[0]; + MostAbundantRareFeatureIndices[0] = Idx2; + Delete = i; + } + } + + // Remove most abundant rare feature. + RareFeatures[Delete] = RareFeatures.back(); + RareFeatures.pop_back(); + + for (auto II : Inputs) { + if (II->DeleteFeatureFreq(MostAbundantRareFeatureIndices[0])) + II->NeedsEnergyUpdate = true; + } + + // Set 2nd most abundant as the new most abundant feature count. + FreqOfMostAbundantRareFeature = + GlobalFeatureFreqs[MostAbundantRareFeatureIndices[1]]; + } + + // Add rare feature, handle collisions, and update energy. + RareFeatures.push_back(Idx); + GlobalFeatureFreqs[Idx] = 0; + for (auto II : Inputs) { + II->DeleteFeatureFreq(Idx); + + // Apply add-one smoothing to this locally undiscovered feature. + // Zero energy seeds will never be fuzzed and remain zero energy. + if (II->Energy > 0.0) { + II->SumIncidence += 1; + II->Energy += log(II->SumIncidence) / II->SumIncidence; + } + } + + DistributionNeedsUpdate = true; + } + + bool AddFeature(size_t Idx, uint32_t NewSize, bool Shrink) { + assert(NewSize); + Idx = Idx % kFeatureSetSize; + uint32_t OldSize = GetFeature(Idx); + if (OldSize == 0 || (Shrink && OldSize > NewSize)) { + if (OldSize > 0) { + size_t OldIdx = SmallestElementPerFeature[Idx]; + InputInfo &II = *Inputs[OldIdx]; + assert(II.NumFeatures > 0); + II.NumFeatures--; + if (II.NumFeatures == 0) + DeleteInput(OldIdx); + } else { + NumAddedFeatures++; + if (Entropic.Enabled) + AddRareFeature((uint32_t)Idx); + } + NumUpdatedFeatures++; + if (FeatureDebug) + Printf("ADD FEATURE %zd sz %d\n", Idx, NewSize); + // Inputs.size() is guaranteed to be less than UINT32_MAX by AddToCorpus. + SmallestElementPerFeature[Idx] = static_cast(Inputs.size()); + InputSizesPerFeature[Idx] = NewSize; + return true; + } + return false; + } + + // Increment frequency of feature Idx globally and locally. + void UpdateFeatureFrequency(InputInfo *II, size_t Idx) { + uint32_t Idx32 = Idx % kFeatureSetSize; + + // Saturated increment. + if (GlobalFeatureFreqs[Idx32] == 0xFFFF) + return; + uint16_t Freq = GlobalFeatureFreqs[Idx32]++; + + // Skip if abundant. + if (Freq > FreqOfMostAbundantRareFeature || + std::find(RareFeatures.begin(), RareFeatures.end(), Idx32) == + RareFeatures.end()) + return; + + // Update global frequencies. + if (Freq == FreqOfMostAbundantRareFeature) + FreqOfMostAbundantRareFeature++; + + // Update local frequencies. + if (II) + II->UpdateFeatureFrequency(Idx32); + } + + size_t NumFeatures() const { return NumAddedFeatures; } + size_t NumFeatureUpdates() const { return NumUpdatedFeatures; } + +private: + + static const bool FeatureDebug = false; + + uint32_t GetFeature(size_t Idx) const { return InputSizesPerFeature[Idx]; } + + void ValidateFeatureSet() { + if (FeatureDebug) + PrintFeatureSet(); + for (size_t Idx = 0; Idx < kFeatureSetSize; Idx++) + if (GetFeature(Idx)) + Inputs[SmallestElementPerFeature[Idx]]->Tmp++; + for (auto II: Inputs) { + if (II->Tmp != II->NumFeatures) + Printf("ZZZ %zd %zd\n", II->Tmp, II->NumFeatures); + assert(II->Tmp == II->NumFeatures); + II->Tmp = 0; + } + } + + // Updates the probability distribution for the units in the corpus. + // Must be called whenever the corpus or unit weights are changed. + // + // Hypothesis: inputs that maximize information about globally rare features + // are interesting. + void UpdateCorpusDistribution(Random &Rand) { + // Skip update if no seeds or rare features were added/deleted. + // Sparse updates for local change of feature frequencies, + // i.e., randomly do not skip. + if (!DistributionNeedsUpdate && + (!Entropic.Enabled || Rand(kSparseEnergyUpdates))) + return; + + DistributionNeedsUpdate = false; + + size_t N = Inputs.size(); + assert(N); + Intervals.resize(N + 1); + Weights.resize(N); + std::iota(Intervals.begin(), Intervals.end(), 0); + + std::chrono::microseconds AverageUnitExecutionTime(0); + for (auto II : Inputs) { + AverageUnitExecutionTime += II->TimeOfUnit; + } + AverageUnitExecutionTime /= N; + + bool VanillaSchedule = true; + if (Entropic.Enabled) { + for (auto II : Inputs) { + if (II->NeedsEnergyUpdate && II->Energy != 0.0) { + II->NeedsEnergyUpdate = false; + II->UpdateEnergy(RareFeatures.size(), Entropic.ScalePerExecTime, + AverageUnitExecutionTime); + } + } + + for (size_t i = 0; i < N; i++) { + + if (Inputs[i]->NumFeatures == 0) { + // If the seed doesn't represent any features, assign zero energy. + Weights[i] = 0.; + } else if (Inputs[i]->NumExecutedMutations / kMaxMutationFactor > + NumExecutedMutations / Inputs.size()) { + // If the seed was fuzzed a lot more than average, assign zero energy. + Weights[i] = 0.; + } else { + // Otherwise, simply assign the computed energy. + Weights[i] = Inputs[i]->Energy; + } + + // If energy for all seeds is zero, fall back to vanilla schedule. + if (Weights[i] > 0.0) + VanillaSchedule = false; + } + } + + if (VanillaSchedule) { + for (size_t i = 0; i < N; i++) + Weights[i] = + Inputs[i]->NumFeatures + ? static_cast((i + 1) * + (Inputs[i]->HasFocusFunction ? 1000 : 1)) + : 0.; + } + + if (FeatureDebug) { + for (size_t i = 0; i < N; i++) + Printf("%zd ", Inputs[i]->NumFeatures); + Printf("SCORE\n"); + for (size_t i = 0; i < N; i++) + Printf("%f ", Weights[i]); + Printf("Weights\n"); + } + CorpusDistribution = std::piecewise_constant_distribution( + Intervals.begin(), Intervals.end(), Weights.begin()); + } + std::piecewise_constant_distribution CorpusDistribution; + + Vector Intervals; + Vector Weights; + + std::unordered_set Hashes; + Vector Inputs; + + size_t NumAddedFeatures = 0; + size_t NumUpdatedFeatures = 0; + uint32_t InputSizesPerFeature[kFeatureSetSize]; + uint32_t SmallestElementPerFeature[kFeatureSetSize]; + + bool DistributionNeedsUpdate = true; + uint16_t FreqOfMostAbundantRareFeature = 0; + uint16_t GlobalFeatureFreqs[kFeatureSetSize] = {}; + Vector RareFeatures; + + std::string OutputCorpus; +}; + +} // namespace fuzzer + +#endif // LLVM_FUZZER_CORPUS diff --git a/core/lib/libfuzzer-sys/libfuzzer/FuzzerCrossOver.cpp b/core/lib/libfuzzer-sys/libfuzzer/FuzzerCrossOver.cpp new file mode 100644 index 0000000000..83d9f8d47c --- /dev/null +++ b/core/lib/libfuzzer-sys/libfuzzer/FuzzerCrossOver.cpp @@ -0,0 +1,51 @@ +//===- FuzzerCrossOver.cpp - Cross over two test inputs -------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// Cross over test inputs. +//===----------------------------------------------------------------------===// + +#include "FuzzerDefs.h" +#include "FuzzerMutate.h" +#include "FuzzerRandom.h" +#include + +namespace fuzzer { + +// Cross Data1 and Data2, store the result (up to MaxOutSize bytes) in Out. +size_t MutationDispatcher::CrossOver(const uint8_t *Data1, size_t Size1, + const uint8_t *Data2, size_t Size2, + uint8_t *Out, size_t MaxOutSize) { + assert(Size1 || Size2); + MaxOutSize = Rand(MaxOutSize) + 1; + size_t OutPos = 0; + size_t Pos1 = 0; + size_t Pos2 = 0; + size_t *InPos = &Pos1; + size_t InSize = Size1; + const uint8_t *Data = Data1; + bool CurrentlyUsingFirstData = true; + while (OutPos < MaxOutSize && (Pos1 < Size1 || Pos2 < Size2)) { + // Merge a part of Data into Out. + size_t OutSizeLeft = MaxOutSize - OutPos; + if (*InPos < InSize) { + size_t InSizeLeft = InSize - *InPos; + size_t MaxExtraSize = std::min(OutSizeLeft, InSizeLeft); + size_t ExtraSize = Rand(MaxExtraSize) + 1; + memcpy(Out + OutPos, Data + *InPos, ExtraSize); + OutPos += ExtraSize; + (*InPos) += ExtraSize; + } + // Use the other input data on the next iteration. + InPos = CurrentlyUsingFirstData ? &Pos2 : &Pos1; + InSize = CurrentlyUsingFirstData ? Size2 : Size1; + Data = CurrentlyUsingFirstData ? Data2 : Data1; + CurrentlyUsingFirstData = !CurrentlyUsingFirstData; + } + return OutPos; +} + +} // namespace fuzzer diff --git a/core/lib/libfuzzer-sys/libfuzzer/FuzzerDataFlowTrace.cpp b/core/lib/libfuzzer-sys/libfuzzer/FuzzerDataFlowTrace.cpp new file mode 100644 index 0000000000..23d422590d --- /dev/null +++ b/core/lib/libfuzzer-sys/libfuzzer/FuzzerDataFlowTrace.cpp @@ -0,0 +1,288 @@ +//===- FuzzerDataFlowTrace.cpp - DataFlowTrace ---*- C++ -* ===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// fuzzer::DataFlowTrace +//===----------------------------------------------------------------------===// + +#include "FuzzerDataFlowTrace.h" + +#include "FuzzerCommand.h" +#include "FuzzerIO.h" +#include "FuzzerRandom.h" +#include "FuzzerSHA1.h" +#include "FuzzerUtil.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace fuzzer { +static const char *kFunctionsTxt = "functions.txt"; + +bool BlockCoverage::AppendCoverage(const std::string &S) { + std::stringstream SS(S); + return AppendCoverage(SS); +} + +// Coverage lines have this form: +// CN X Y Z T +// where N is the number of the function, T is the total number of instrumented +// BBs, and X,Y,Z, if present, are the indecies of covered BB. +// BB #0, which is the entry block, is not explicitly listed. +bool BlockCoverage::AppendCoverage(std::istream &IN) { + std::string L; + while (std::getline(IN, L, '\n')) { + if (L.empty()) + continue; + std::stringstream SS(L.c_str() + 1); + size_t FunctionId = 0; + SS >> FunctionId; + if (L[0] == 'F') { + FunctionsWithDFT.insert(FunctionId); + continue; + } + if (L[0] != 'C') continue; + Vector CoveredBlocks; + while (true) { + uint32_t BB = 0; + SS >> BB; + if (!SS) break; + CoveredBlocks.push_back(BB); + } + if (CoveredBlocks.empty()) return false; + // Ensures no CoverageVector is longer than UINT32_MAX. + uint32_t NumBlocks = CoveredBlocks.back(); + CoveredBlocks.pop_back(); + for (auto BB : CoveredBlocks) + if (BB >= NumBlocks) return false; + auto It = Functions.find(FunctionId); + auto &Counters = + It == Functions.end() + ? Functions.insert({FunctionId, Vector(NumBlocks)}) + .first->second + : It->second; + + if (Counters.size() != NumBlocks) return false; // wrong number of blocks. + + Counters[0]++; + for (auto BB : CoveredBlocks) + Counters[BB]++; + } + return true; +} + +// Assign weights to each function. +// General principles: +// * any uncovered function gets weight 0. +// * a function with lots of uncovered blocks gets bigger weight. +// * a function with a less frequently executed code gets bigger weight. +Vector BlockCoverage::FunctionWeights(size_t NumFunctions) const { + Vector Res(NumFunctions); + for (auto It : Functions) { + auto FunctionID = It.first; + auto Counters = It.second; + assert(FunctionID < NumFunctions); + auto &Weight = Res[FunctionID]; + // Give higher weight if the function has a DFT. + Weight = FunctionsWithDFT.count(FunctionID) ? 1000. : 1; + // Give higher weight to functions with less frequently seen basic blocks. + Weight /= SmallestNonZeroCounter(Counters); + // Give higher weight to functions with the most uncovered basic blocks. + Weight *= NumberOfUncoveredBlocks(Counters) + 1; + } + return Res; +} + +void DataFlowTrace::ReadCoverage(const std::string &DirPath) { + Vector Files; + GetSizedFilesFromDir(DirPath, &Files); + for (auto &SF : Files) { + auto Name = Basename(SF.File); + if (Name == kFunctionsTxt) continue; + if (!CorporaHashes.count(Name)) continue; + std::ifstream IF(SF.File); + Coverage.AppendCoverage(IF); + } +} + +static void DFTStringAppendToVector(Vector *DFT, + const std::string &DFTString) { + assert(DFT->size() == DFTString.size()); + for (size_t I = 0, Len = DFT->size(); I < Len; I++) + (*DFT)[I] = DFTString[I] == '1'; +} + +// converts a string of '0' and '1' into a Vector +static Vector DFTStringToVector(const std::string &DFTString) { + Vector DFT(DFTString.size()); + DFTStringAppendToVector(&DFT, DFTString); + return DFT; +} + +static bool ParseError(const char *Err, const std::string &Line) { + Printf("DataFlowTrace: parse error: %s: Line: %s\n", Err, Line.c_str()); + return false; +} + +// TODO(metzman): replace std::string with std::string_view for +// better performance. Need to figure our how to use string_view on Windows. +static bool ParseDFTLine(const std::string &Line, size_t *FunctionNum, + std::string *DFTString) { + if (!Line.empty() && Line[0] != 'F') + return false; // Ignore coverage. + size_t SpacePos = Line.find(' '); + if (SpacePos == std::string::npos) + return ParseError("no space in the trace line", Line); + if (Line.empty() || Line[0] != 'F') + return ParseError("the trace line doesn't start with 'F'", Line); + *FunctionNum = std::atol(Line.c_str() + 1); + const char *Beg = Line.c_str() + SpacePos + 1; + const char *End = Line.c_str() + Line.size(); + assert(Beg < End); + size_t Len = End - Beg; + for (size_t I = 0; I < Len; I++) { + if (Beg[I] != '0' && Beg[I] != '1') + return ParseError("the trace should contain only 0 or 1", Line); + } + *DFTString = Beg; + return true; +} + +bool DataFlowTrace::Init(const std::string &DirPath, std::string *FocusFunction, + Vector &CorporaFiles, Random &Rand) { + if (DirPath.empty()) return false; + Printf("INFO: DataFlowTrace: reading from '%s'\n", DirPath.c_str()); + Vector Files; + GetSizedFilesFromDir(DirPath, &Files); + std::string L; + size_t FocusFuncIdx = SIZE_MAX; + Vector FunctionNames; + + // Collect the hashes of the corpus files. + for (auto &SF : CorporaFiles) + CorporaHashes.insert(Hash(FileToVector(SF.File))); + + // Read functions.txt + std::ifstream IF(DirPlusFile(DirPath, kFunctionsTxt)); + size_t NumFunctions = 0; + while (std::getline(IF, L, '\n')) { + FunctionNames.push_back(L); + NumFunctions++; + if (*FocusFunction == L) + FocusFuncIdx = NumFunctions - 1; + } + if (!NumFunctions) + return false; + + if (*FocusFunction == "auto") { + // AUTOFOCUS works like this: + // * reads the coverage data from the DFT files. + // * assigns weights to functions based on coverage. + // * chooses a random function according to the weights. + ReadCoverage(DirPath); + auto Weights = Coverage.FunctionWeights(NumFunctions); + Vector Intervals(NumFunctions + 1); + std::iota(Intervals.begin(), Intervals.end(), 0); + auto Distribution = std::piecewise_constant_distribution( + Intervals.begin(), Intervals.end(), Weights.begin()); + FocusFuncIdx = static_cast(Distribution(Rand)); + *FocusFunction = FunctionNames[FocusFuncIdx]; + assert(FocusFuncIdx < NumFunctions); + Printf("INFO: AUTOFOCUS: %zd %s\n", FocusFuncIdx, + FunctionNames[FocusFuncIdx].c_str()); + for (size_t i = 0; i < NumFunctions; i++) { + if (Weights[i] == 0.0) + continue; + Printf(" [%zd] W %g\tBB-tot %u\tBB-cov %u\tEntryFreq %u:\t%s\n", i, + Weights[i], Coverage.GetNumberOfBlocks(i), + Coverage.GetNumberOfCoveredBlocks(i), Coverage.GetCounter(i, 0), + FunctionNames[i].c_str()); + } + } + + if (!NumFunctions || FocusFuncIdx == SIZE_MAX || Files.size() <= 1) + return false; + + // Read traces. + size_t NumTraceFiles = 0; + size_t NumTracesWithFocusFunction = 0; + for (auto &SF : Files) { + auto Name = Basename(SF.File); + if (Name == kFunctionsTxt) continue; + if (!CorporaHashes.count(Name)) continue; // not in the corpus. + NumTraceFiles++; + // Printf("=== %s\n", Name.c_str()); + std::ifstream IF(SF.File); + while (std::getline(IF, L, '\n')) { + size_t FunctionNum = 0; + std::string DFTString; + if (ParseDFTLine(L, &FunctionNum, &DFTString) && + FunctionNum == FocusFuncIdx) { + NumTracesWithFocusFunction++; + + if (FunctionNum >= NumFunctions) + return ParseError("N is greater than the number of functions", L); + Traces[Name] = DFTStringToVector(DFTString); + // Print just a few small traces. + if (NumTracesWithFocusFunction <= 3 && DFTString.size() <= 16) + Printf("%s => |%s|\n", Name.c_str(), std::string(DFTString).c_str()); + break; // No need to parse the following lines. + } + } + } + Printf("INFO: DataFlowTrace: %zd trace files, %zd functions, " + "%zd traces with focus function\n", + NumTraceFiles, NumFunctions, NumTracesWithFocusFunction); + return NumTraceFiles > 0; +} + +int CollectDataFlow(const std::string &DFTBinary, const std::string &DirPath, + const Vector &CorporaFiles) { + Printf("INFO: collecting data flow: bin: %s dir: %s files: %zd\n", + DFTBinary.c_str(), DirPath.c_str(), CorporaFiles.size()); + if (CorporaFiles.empty()) { + Printf("ERROR: can't collect data flow without corpus provided."); + return 1; + } + + static char DFSanEnv[] = "DFSAN_OPTIONS=warn_unimplemented=0"; + putenv(DFSanEnv); + MkDir(DirPath); + for (auto &F : CorporaFiles) { + // For every input F we need to collect the data flow and the coverage. + // Data flow collection may fail if we request too many DFSan tags at once. + // So, we start from requesting all tags in range [0,Size) and if that fails + // we then request tags in [0,Size/2) and [Size/2, Size), and so on. + // Function number => DFT. + auto OutPath = DirPlusFile(DirPath, Hash(FileToVector(F.File))); + std::unordered_map> DFTMap; + std::unordered_set Cov; + Command Cmd; + Cmd.addArgument(DFTBinary); + Cmd.addArgument(F.File); + Cmd.addArgument(OutPath); + Printf("CMD: %s\n", Cmd.toString().c_str()); + ExecuteCommand(Cmd); + } + // Write functions.txt if it's currently empty or doesn't exist. + auto FunctionsTxtPath = DirPlusFile(DirPath, kFunctionsTxt); + if (FileToString(FunctionsTxtPath).empty()) { + Command Cmd; + Cmd.addArgument(DFTBinary); + Cmd.setOutputFile(FunctionsTxtPath); + ExecuteCommand(Cmd); + } + return 0; +} + +} // namespace fuzzer diff --git a/core/lib/libfuzzer-sys/libfuzzer/FuzzerDataFlowTrace.h b/core/lib/libfuzzer-sys/libfuzzer/FuzzerDataFlowTrace.h new file mode 100644 index 0000000000..07c03bb256 --- /dev/null +++ b/core/lib/libfuzzer-sys/libfuzzer/FuzzerDataFlowTrace.h @@ -0,0 +1,137 @@ +//===- FuzzerDataFlowTrace.h - Internal header for the Fuzzer ---*- C++ -* ===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// fuzzer::DataFlowTrace; reads and handles a data-flow trace. +// +// A data flow trace is generated by e.g. dataflow/DataFlow.cpp +// and is stored on disk in a separate directory. +// +// The trace dir contains a file 'functions.txt' which lists function names, +// oner per line, e.g. +// ==> functions.txt <== +// Func2 +// LLVMFuzzerTestOneInput +// Func1 +// +// All other files in the dir are the traces, see dataflow/DataFlow.cpp. +// The name of the file is sha1 of the input used to generate the trace. +// +// Current status: +// the data is parsed and the summary is printed, but the data is not yet +// used in any other way. +//===----------------------------------------------------------------------===// + +#ifndef LLVM_FUZZER_DATA_FLOW_TRACE +#define LLVM_FUZZER_DATA_FLOW_TRACE + +#include "FuzzerDefs.h" +#include "FuzzerIO.h" + +#include +#include +#include +#include + +namespace fuzzer { + +int CollectDataFlow(const std::string &DFTBinary, const std::string &DirPath, + const Vector &CorporaFiles); + +class BlockCoverage { +public: + // These functions guarantee no CoverageVector is longer than UINT32_MAX. + bool AppendCoverage(std::istream &IN); + bool AppendCoverage(const std::string &S); + + size_t NumCoveredFunctions() const { return Functions.size(); } + + uint32_t GetCounter(size_t FunctionId, size_t BasicBlockId) { + auto It = Functions.find(FunctionId); + if (It == Functions.end()) + return 0; + const auto &Counters = It->second; + if (BasicBlockId < Counters.size()) + return Counters[BasicBlockId]; + return 0; + } + + uint32_t GetNumberOfBlocks(size_t FunctionId) { + auto It = Functions.find(FunctionId); + if (It == Functions.end()) return 0; + const auto &Counters = It->second; + return static_cast(Counters.size()); + } + + uint32_t GetNumberOfCoveredBlocks(size_t FunctionId) { + auto It = Functions.find(FunctionId); + if (It == Functions.end()) return 0; + const auto &Counters = It->second; + uint32_t Result = 0; + for (auto Cnt: Counters) + if (Cnt) + Result++; + return Result; + } + + Vector FunctionWeights(size_t NumFunctions) const; + void clear() { Functions.clear(); } + +private: + typedef Vector CoverageVector; + + uint32_t NumberOfCoveredBlocks(const CoverageVector &Counters) const { + uint32_t Res = 0; + for (auto Cnt : Counters) + if (Cnt) + Res++; + return Res; + } + + uint32_t NumberOfUncoveredBlocks(const CoverageVector &Counters) const { + return static_cast(Counters.size()) - + NumberOfCoveredBlocks(Counters); + } + + uint32_t SmallestNonZeroCounter(const CoverageVector &Counters) const { + assert(!Counters.empty()); + uint32_t Res = Counters[0]; + for (auto Cnt : Counters) + if (Cnt) + Res = Min(Res, Cnt); + assert(Res); + return Res; + } + + // Function ID => vector of counters. + // Each counter represents how many input files trigger the given basic block. + std::unordered_map Functions; + // Functions that have DFT entry. + std::unordered_set FunctionsWithDFT; +}; + +class DataFlowTrace { + public: + void ReadCoverage(const std::string &DirPath); + bool Init(const std::string &DirPath, std::string *FocusFunction, + Vector &CorporaFiles, Random &Rand); + void Clear() { Traces.clear(); } + const Vector *Get(const std::string &InputSha1) const { + auto It = Traces.find(InputSha1); + if (It != Traces.end()) + return &It->second; + return nullptr; + } + + private: + // Input's sha1 => DFT for the FocusFunction. + std::unordered_map > Traces; + BlockCoverage Coverage; + std::unordered_set CorporaHashes; +}; +} // namespace fuzzer + +#endif // LLVM_FUZZER_DATA_FLOW_TRACE diff --git a/core/lib/libfuzzer-sys/libfuzzer/FuzzerDefs.h b/core/lib/libfuzzer-sys/libfuzzer/FuzzerDefs.h new file mode 100644 index 0000000000..1a2752af2f --- /dev/null +++ b/core/lib/libfuzzer-sys/libfuzzer/FuzzerDefs.h @@ -0,0 +1,75 @@ +//===- FuzzerDefs.h - Internal header for the Fuzzer ------------*- C++ -* ===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// Basic definitions. +//===----------------------------------------------------------------------===// + +#ifndef LLVM_FUZZER_DEFS_H +#define LLVM_FUZZER_DEFS_H + +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace fuzzer { + +template T Min(T a, T b) { return a < b ? a : b; } +template T Max(T a, T b) { return a > b ? a : b; } + +class Random; +class Dictionary; +class DictionaryEntry; +class MutationDispatcher; +struct FuzzingOptions; +class InputCorpus; +struct InputInfo; +struct ExternalFunctions; + +// Global interface to functions that may or may not be available. +extern ExternalFunctions *EF; + +// We are using a custom allocator to give a different symbol name to STL +// containers in order to avoid ODR violations. +template + class fuzzer_allocator: public std::allocator { + public: + fuzzer_allocator() = default; + + template + fuzzer_allocator(const fuzzer_allocator&) {} + + template + struct rebind { typedef fuzzer_allocator other; }; + }; + +template +using Vector = std::vector>; + +template +using Set = std::set, fuzzer_allocator>; + +typedef Vector Unit; +typedef Vector UnitVector; +typedef int (*UserCallback)(const uint8_t *Data, size_t Size); + +int FuzzerDriver(int *argc, char ***argv, UserCallback Callback); + +uint8_t *ExtraCountersBegin(); +uint8_t *ExtraCountersEnd(); +void ClearExtraCounters(); + +extern bool RunningUserCallback; + +} // namespace fuzzer + +#endif // LLVM_FUZZER_DEFS_H diff --git a/core/lib/libfuzzer-sys/libfuzzer/FuzzerDictionary.h b/core/lib/libfuzzer-sys/libfuzzer/FuzzerDictionary.h new file mode 100644 index 0000000000..db55907d93 --- /dev/null +++ b/core/lib/libfuzzer-sys/libfuzzer/FuzzerDictionary.h @@ -0,0 +1,120 @@ +//===- FuzzerDictionary.h - Internal header for the Fuzzer ------*- C++ -* ===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// fuzzer::Dictionary +//===----------------------------------------------------------------------===// + +#ifndef LLVM_FUZZER_DICTIONARY_H +#define LLVM_FUZZER_DICTIONARY_H + +#include "FuzzerDefs.h" +#include "FuzzerIO.h" +#include "FuzzerUtil.h" +#include +#include + +namespace fuzzer { +// A simple POD sized array of bytes. +template class FixedWord { +public: + static const size_t kMaxSize = kMaxSizeT; + FixedWord() {} + FixedWord(const uint8_t *B, size_t S) { Set(B, S); } + + void Set(const uint8_t *B, size_t S) { + static_assert(kMaxSizeT <= std::numeric_limits::max(), + "FixedWord::kMaxSizeT cannot fit in a uint8_t."); + assert(S <= kMaxSize); + memcpy(Data, B, S); + Size = static_cast(S); + } + + bool operator==(const FixedWord &w) const { + return Size == w.Size && 0 == memcmp(Data, w.Data, Size); + } + + static size_t GetMaxSize() { return kMaxSize; } + const uint8_t *data() const { return Data; } + uint8_t size() const { return Size; } + +private: + uint8_t Size = 0; + uint8_t Data[kMaxSize]; +}; + +typedef FixedWord<64> Word; + +class DictionaryEntry { + public: + DictionaryEntry() {} + DictionaryEntry(Word W) : W(W) {} + DictionaryEntry(Word W, size_t PositionHint) : W(W), PositionHint(PositionHint) {} + const Word &GetW() const { return W; } + + bool HasPositionHint() const { return PositionHint != std::numeric_limits::max(); } + size_t GetPositionHint() const { + assert(HasPositionHint()); + return PositionHint; + } + void IncUseCount() { UseCount++; } + void IncSuccessCount() { SuccessCount++; } + size_t GetUseCount() const { return UseCount; } + size_t GetSuccessCount() const {return SuccessCount; } + + void Print(const char *PrintAfter = "\n") { + PrintASCII(W.data(), W.size()); + if (HasPositionHint()) + Printf("@%zd", GetPositionHint()); + Printf("%s", PrintAfter); + } + +private: + Word W; + size_t PositionHint = std::numeric_limits::max(); + size_t UseCount = 0; + size_t SuccessCount = 0; +}; + +class Dictionary { + public: + static const size_t kMaxDictSize = 1 << 14; + + bool ContainsWord(const Word &W) const { + return std::any_of(begin(), end(), [&](const DictionaryEntry &DE) { + return DE.GetW() == W; + }); + } + const DictionaryEntry *begin() const { return &DE[0]; } + const DictionaryEntry *end() const { return begin() + Size; } + DictionaryEntry & operator[] (size_t Idx) { + assert(Idx < Size); + return DE[Idx]; + } + void push_back(DictionaryEntry DE) { + if (Size < kMaxDictSize) + this->DE[Size++] = DE; + } + void clear() { Size = 0; } + bool empty() const { return Size == 0; } + size_t size() const { return Size; } + +private: + DictionaryEntry DE[kMaxDictSize]; + size_t Size = 0; +}; + +// Parses one dictionary entry. +// If successful, write the enty to Unit and returns true, +// otherwise returns false. +bool ParseOneDictionaryEntry(const std::string &Str, Unit *U); +// Parses the dictionary file, fills Units, returns true iff all lines +// were parsed successfully. +bool ParseDictionaryFile(const std::string &Text, Vector *Units); + +} // namespace fuzzer + +#endif // LLVM_FUZZER_DICTIONARY_H diff --git a/core/lib/libfuzzer-sys/libfuzzer/FuzzerDriver.cpp b/core/lib/libfuzzer-sys/libfuzzer/FuzzerDriver.cpp new file mode 100644 index 0000000000..ceaa907051 --- /dev/null +++ b/core/lib/libfuzzer-sys/libfuzzer/FuzzerDriver.cpp @@ -0,0 +1,925 @@ +//===- FuzzerDriver.cpp - FuzzerDriver function and flags -----------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// FuzzerDriver and flag parsing. +//===----------------------------------------------------------------------===// + +#include "FuzzerCommand.h" +#include "FuzzerCorpus.h" +#include "FuzzerFork.h" +#include "FuzzerIO.h" +#include "FuzzerInterface.h" +#include "FuzzerInternal.h" +#include "FuzzerMerge.h" +#include "FuzzerMutate.h" +#include "FuzzerPlatform.h" +#include "FuzzerRandom.h" +#include "FuzzerTracePC.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// This function should be present in the libFuzzer so that the client +// binary can test for its existence. +#if LIBFUZZER_MSVC +extern "C" void __libfuzzer_is_present() {} +#if defined(_M_IX86) || defined(__i386__) +#pragma comment(linker, "/include:___libfuzzer_is_present") +#else +#pragma comment(linker, "/include:__libfuzzer_is_present") +#endif +#else +extern "C" __attribute__((used)) void __libfuzzer_is_present() {} +#endif // LIBFUZZER_MSVC + +namespace fuzzer { + +// Program arguments. +struct FlagDescription { + const char *Name; + const char *Description; + int Default; + int *IntFlag; + const char **StrFlag; + unsigned int *UIntFlag; +}; + +struct { +#define FUZZER_DEPRECATED_FLAG(Name) +#define FUZZER_FLAG_INT(Name, Default, Description) int Name; +#define FUZZER_FLAG_UNSIGNED(Name, Default, Description) unsigned int Name; +#define FUZZER_FLAG_STRING(Name, Description) const char *Name; +#include "FuzzerFlags.def" +#undef FUZZER_DEPRECATED_FLAG +#undef FUZZER_FLAG_INT +#undef FUZZER_FLAG_UNSIGNED +#undef FUZZER_FLAG_STRING +} Flags; + +static const FlagDescription FlagDescriptions [] { +#define FUZZER_DEPRECATED_FLAG(Name) \ + {#Name, "Deprecated; don't use", 0, nullptr, nullptr, nullptr}, +#define FUZZER_FLAG_INT(Name, Default, Description) \ + {#Name, Description, Default, &Flags.Name, nullptr, nullptr}, +#define FUZZER_FLAG_UNSIGNED(Name, Default, Description) \ + {#Name, Description, static_cast(Default), \ + nullptr, nullptr, &Flags.Name}, +#define FUZZER_FLAG_STRING(Name, Description) \ + {#Name, Description, 0, nullptr, &Flags.Name, nullptr}, +#include "FuzzerFlags.def" +#undef FUZZER_DEPRECATED_FLAG +#undef FUZZER_FLAG_INT +#undef FUZZER_FLAG_UNSIGNED +#undef FUZZER_FLAG_STRING +}; + +static const size_t kNumFlags = + sizeof(FlagDescriptions) / sizeof(FlagDescriptions[0]); + +static Vector *Inputs; +static std::string *ProgName; + +static void PrintHelp() { + Printf("Usage:\n"); + auto Prog = ProgName->c_str(); + Printf("\nTo run fuzzing pass 0 or more directories.\n"); + Printf("%s [-flag1=val1 [-flag2=val2 ...] ] [dir1 [dir2 ...] ]\n", Prog); + + Printf("\nTo run individual tests without fuzzing pass 1 or more files:\n"); + Printf("%s [-flag1=val1 [-flag2=val2 ...] ] file1 [file2 ...]\n", Prog); + + Printf("\nFlags: (strictly in form -flag=value)\n"); + size_t MaxFlagLen = 0; + for (size_t F = 0; F < kNumFlags; F++) + MaxFlagLen = std::max(strlen(FlagDescriptions[F].Name), MaxFlagLen); + + for (size_t F = 0; F < kNumFlags; F++) { + const auto &D = FlagDescriptions[F]; + if (strstr(D.Description, "internal flag") == D.Description) continue; + Printf(" %s", D.Name); + for (size_t i = 0, n = MaxFlagLen - strlen(D.Name); i < n; i++) + Printf(" "); + Printf("\t"); + Printf("%d\t%s\n", D.Default, D.Description); + } + Printf("\nFlags starting with '--' will be ignored and " + "will be passed verbatim to subprocesses.\n"); +} + +static const char *FlagValue(const char *Param, const char *Name) { + size_t Len = strlen(Name); + if (Param[0] == '-' && strstr(Param + 1, Name) == Param + 1 && + Param[Len + 1] == '=') + return &Param[Len + 2]; + return nullptr; +} + +// Avoid calling stol as it triggers a bug in clang/glibc build. +static long MyStol(const char *Str) { + long Res = 0; + long Sign = 1; + if (*Str == '-') { + Str++; + Sign = -1; + } + for (size_t i = 0; Str[i]; i++) { + char Ch = Str[i]; + if (Ch < '0' || Ch > '9') + return Res; + Res = Res * 10 + (Ch - '0'); + } + return Res * Sign; +} + +static bool ParseOneFlag(const char *Param) { + if (Param[0] != '-') return false; + if (Param[1] == '-') { + static bool PrintedWarning = false; + if (!PrintedWarning) { + PrintedWarning = true; + Printf("INFO: libFuzzer ignores flags that start with '--'\n"); + } + for (size_t F = 0; F < kNumFlags; F++) + if (FlagValue(Param + 1, FlagDescriptions[F].Name)) + Printf("WARNING: did you mean '%s' (single dash)?\n", Param + 1); + return true; + } + for (size_t F = 0; F < kNumFlags; F++) { + const char *Name = FlagDescriptions[F].Name; + const char *Str = FlagValue(Param, Name); + if (Str) { + if (FlagDescriptions[F].IntFlag) { + auto Val = MyStol(Str); + *FlagDescriptions[F].IntFlag = static_cast(Val); + if (Flags.verbosity >= 2) + Printf("Flag: %s %d\n", Name, Val); + return true; + } else if (FlagDescriptions[F].UIntFlag) { + auto Val = std::stoul(Str); + *FlagDescriptions[F].UIntFlag = static_cast(Val); + if (Flags.verbosity >= 2) + Printf("Flag: %s %u\n", Name, Val); + return true; + } else if (FlagDescriptions[F].StrFlag) { + *FlagDescriptions[F].StrFlag = Str; + if (Flags.verbosity >= 2) + Printf("Flag: %s %s\n", Name, Str); + return true; + } else { // Deprecated flag. + Printf("Flag: %s: deprecated, don't use\n", Name); + return true; + } + } + } + Printf("\n\nWARNING: unrecognized flag '%s'; " + "use -help=1 to list all flags\n\n", Param); + return true; +} + +// We don't use any library to minimize dependencies. +static void ParseFlags(const Vector &Args, + const ExternalFunctions *EF) { + for (size_t F = 0; F < kNumFlags; F++) { + if (FlagDescriptions[F].IntFlag) + *FlagDescriptions[F].IntFlag = FlagDescriptions[F].Default; + if (FlagDescriptions[F].UIntFlag) + *FlagDescriptions[F].UIntFlag = + static_cast(FlagDescriptions[F].Default); + if (FlagDescriptions[F].StrFlag) + *FlagDescriptions[F].StrFlag = nullptr; + } + + // Disable len_control by default, if LLVMFuzzerCustomMutator is used. + if (EF->LLVMFuzzerCustomMutator) { + Flags.len_control = 0; + Printf("INFO: found LLVMFuzzerCustomMutator (%p). " + "Disabling -len_control by default.\n", EF->LLVMFuzzerCustomMutator); + } + + Inputs = new Vector; + for (size_t A = 1; A < Args.size(); A++) { + if (ParseOneFlag(Args[A].c_str())) { + if (Flags.ignore_remaining_args) + break; + continue; + } + Inputs->push_back(Args[A]); + } +} + +static std::mutex Mu; + +static void PulseThread() { + while (true) { + SleepSeconds(600); + std::lock_guard Lock(Mu); + Printf("pulse...\n"); + } +} + +static void WorkerThread(const Command &BaseCmd, std::atomic *Counter, + unsigned NumJobs, std::atomic *HasErrors) { + while (true) { + unsigned C = (*Counter)++; + if (C >= NumJobs) break; + std::string Log = "fuzz-" + std::to_string(C) + ".log"; + Command Cmd(BaseCmd); + Cmd.setOutputFile(Log); + Cmd.combineOutAndErr(); + if (Flags.verbosity) { + std::string CommandLine = Cmd.toString(); + Printf("%s\n", CommandLine.c_str()); + } + int ExitCode = ExecuteCommand(Cmd); + if (ExitCode != 0) + *HasErrors = true; + std::lock_guard Lock(Mu); + Printf("================== Job %u exited with exit code %d ============\n", + C, ExitCode); + fuzzer::CopyFileToErr(Log); + } +} + +static void ValidateDirectoryExists(const std::string &Path, + bool CreateDirectory) { + if (Path.empty()) { + Printf("ERROR: Provided directory path is an empty string\n"); + exit(1); + } + + if (IsDirectory(Path)) + return; + + if (CreateDirectory) { + if (!MkDirRecursive(Path)) { + Printf("ERROR: Failed to create directory \"%s\"\n", Path.c_str()); + exit(1); + } + return; + } + + Printf("ERROR: The required directory \"%s\" does not exist\n", Path.c_str()); + exit(1); +} + +std::string CloneArgsWithoutX(const Vector &Args, + const char *X1, const char *X2) { + std::string Cmd; + for (auto &S : Args) { + if (FlagValue(S.c_str(), X1) || FlagValue(S.c_str(), X2)) + continue; + Cmd += S + " "; + } + return Cmd; +} + +static int RunInMultipleProcesses(const Vector &Args, + unsigned NumWorkers, unsigned NumJobs) { + std::atomic Counter(0); + std::atomic HasErrors(false); + Command Cmd(Args); + Cmd.removeFlag("jobs"); + Cmd.removeFlag("workers"); + Vector V; + std::thread Pulse(PulseThread); + Pulse.detach(); + for (unsigned i = 0; i < NumWorkers; i++) + V.push_back(std::thread(WorkerThread, std::ref(Cmd), &Counter, NumJobs, &HasErrors)); + for (auto &T : V) + T.join(); + return HasErrors ? 1 : 0; +} + +static void RssThread(Fuzzer *F, size_t RssLimitMb) { + while (true) { + SleepSeconds(1); + size_t Peak = GetPeakRSSMb(); + if (Peak > RssLimitMb) + F->RssLimitCallback(); + } +} + +static void StartRssThread(Fuzzer *F, size_t RssLimitMb) { + if (!RssLimitMb) + return; + std::thread T(RssThread, F, RssLimitMb); + T.detach(); +} + +int RunOneTest(Fuzzer *F, const char *InputFilePath, size_t MaxLen) { + Unit U = FileToVector(InputFilePath); + if (MaxLen && MaxLen < U.size()) + U.resize(MaxLen); + F->ExecuteCallback(U.data(), U.size()); + if (Flags.print_full_coverage) { + // Leak detection is not needed when collecting full coverage data. + F->TPCUpdateObservedPCs(); + } else { + F->TryDetectingAMemoryLeak(U.data(), U.size(), true); + } + return 0; +} + +static bool AllInputsAreFiles() { + if (Inputs->empty()) return false; + for (auto &Path : *Inputs) + if (!IsFile(Path)) + return false; + return true; +} + +static std::string GetDedupTokenFromCmdOutput(const std::string &S) { + auto Beg = S.find("DEDUP_TOKEN:"); + if (Beg == std::string::npos) + return ""; + auto End = S.find('\n', Beg); + if (End == std::string::npos) + return ""; + return S.substr(Beg, End - Beg); +} + +int CleanseCrashInput(const Vector &Args, + const FuzzingOptions &Options) { + if (Inputs->size() != 1 || !Flags.exact_artifact_path) { + Printf("ERROR: -cleanse_crash should be given one input file and" + " -exact_artifact_path\n"); + exit(1); + } + std::string InputFilePath = Inputs->at(0); + std::string OutputFilePath = Flags.exact_artifact_path; + Command Cmd(Args); + Cmd.removeFlag("cleanse_crash"); + + assert(Cmd.hasArgument(InputFilePath)); + Cmd.removeArgument(InputFilePath); + + auto TmpFilePath = TempPath("CleanseCrashInput", ".repro"); + Cmd.addArgument(TmpFilePath); + Cmd.setOutputFile(getDevNull()); + Cmd.combineOutAndErr(); + + std::string CurrentFilePath = InputFilePath; + auto U = FileToVector(CurrentFilePath); + size_t Size = U.size(); + + const Vector ReplacementBytes = {' ', 0xff}; + for (int NumAttempts = 0; NumAttempts < 5; NumAttempts++) { + bool Changed = false; + for (size_t Idx = 0; Idx < Size; Idx++) { + Printf("CLEANSE[%d]: Trying to replace byte %zd of %zd\n", NumAttempts, + Idx, Size); + uint8_t OriginalByte = U[Idx]; + if (ReplacementBytes.end() != std::find(ReplacementBytes.begin(), + ReplacementBytes.end(), + OriginalByte)) + continue; + for (auto NewByte : ReplacementBytes) { + U[Idx] = NewByte; + WriteToFile(U, TmpFilePath); + auto ExitCode = ExecuteCommand(Cmd); + RemoveFile(TmpFilePath); + if (!ExitCode) { + U[Idx] = OriginalByte; + } else { + Changed = true; + Printf("CLEANSE: Replaced byte %zd with 0x%x\n", Idx, NewByte); + WriteToFile(U, OutputFilePath); + break; + } + } + } + if (!Changed) break; + } + return 0; +} + +int MinimizeCrashInput(const Vector &Args, + const FuzzingOptions &Options) { + if (Inputs->size() != 1) { + Printf("ERROR: -minimize_crash should be given one input file\n"); + exit(1); + } + std::string InputFilePath = Inputs->at(0); + Command BaseCmd(Args); + BaseCmd.removeFlag("minimize_crash"); + BaseCmd.removeFlag("exact_artifact_path"); + assert(BaseCmd.hasArgument(InputFilePath)); + BaseCmd.removeArgument(InputFilePath); + if (Flags.runs <= 0 && Flags.max_total_time == 0) { + Printf("INFO: you need to specify -runs=N or " + "-max_total_time=N with -minimize_crash=1\n" + "INFO: defaulting to -max_total_time=600\n"); + BaseCmd.addFlag("max_total_time", "600"); + } + + BaseCmd.combineOutAndErr(); + + std::string CurrentFilePath = InputFilePath; + while (true) { + Unit U = FileToVector(CurrentFilePath); + Printf("CRASH_MIN: minimizing crash input: '%s' (%zd bytes)\n", + CurrentFilePath.c_str(), U.size()); + + Command Cmd(BaseCmd); + Cmd.addArgument(CurrentFilePath); + + Printf("CRASH_MIN: executing: %s\n", Cmd.toString().c_str()); + std::string CmdOutput; + bool Success = ExecuteCommand(Cmd, &CmdOutput); + if (Success) { + Printf("ERROR: the input %s did not crash\n", CurrentFilePath.c_str()); + exit(1); + } + Printf("CRASH_MIN: '%s' (%zd bytes) caused a crash. Will try to minimize " + "it further\n", + CurrentFilePath.c_str(), U.size()); + auto DedupToken1 = GetDedupTokenFromCmdOutput(CmdOutput); + if (!DedupToken1.empty()) + Printf("CRASH_MIN: DedupToken1: %s\n", DedupToken1.c_str()); + + std::string ArtifactPath = + Flags.exact_artifact_path + ? Flags.exact_artifact_path + : Options.ArtifactPrefix + "minimized-from-" + Hash(U); + Cmd.addFlag("minimize_crash_internal_step", "1"); + Cmd.addFlag("exact_artifact_path", ArtifactPath); + Printf("CRASH_MIN: executing: %s\n", Cmd.toString().c_str()); + CmdOutput.clear(); + Success = ExecuteCommand(Cmd, &CmdOutput); + Printf("%s", CmdOutput.c_str()); + if (Success) { + if (Flags.exact_artifact_path) { + CurrentFilePath = Flags.exact_artifact_path; + WriteToFile(U, CurrentFilePath); + } + Printf("CRASH_MIN: failed to minimize beyond %s (%d bytes), exiting\n", + CurrentFilePath.c_str(), U.size()); + break; + } + auto DedupToken2 = GetDedupTokenFromCmdOutput(CmdOutput); + if (!DedupToken2.empty()) + Printf("CRASH_MIN: DedupToken2: %s\n", DedupToken2.c_str()); + + if (DedupToken1 != DedupToken2) { + if (Flags.exact_artifact_path) { + CurrentFilePath = Flags.exact_artifact_path; + WriteToFile(U, CurrentFilePath); + } + Printf("CRASH_MIN: mismatch in dedup tokens" + " (looks like a different bug). Won't minimize further\n"); + break; + } + + CurrentFilePath = ArtifactPath; + Printf("*********************************\n"); + } + return 0; +} + +int MinimizeCrashInputInternalStep(Fuzzer *F, InputCorpus *Corpus) { + assert(Inputs->size() == 1); + std::string InputFilePath = Inputs->at(0); + Unit U = FileToVector(InputFilePath); + Printf("INFO: Starting MinimizeCrashInputInternalStep: %zd\n", U.size()); + if (U.size() < 2) { + Printf("INFO: The input is small enough, exiting\n"); + exit(0); + } + F->SetMaxInputLen(U.size()); + F->SetMaxMutationLen(U.size() - 1); + F->MinimizeCrashLoop(U); + Printf("INFO: Done MinimizeCrashInputInternalStep, no crashes found\n"); + exit(0); + return 0; +} + +void Merge(Fuzzer *F, FuzzingOptions &Options, const Vector &Args, + const Vector &Corpora, const char *CFPathOrNull) { + if (Corpora.size() < 2) { + Printf("INFO: Merge requires two or more corpus dirs\n"); + exit(0); + } + + Vector OldCorpus, NewCorpus; + GetSizedFilesFromDir(Corpora[0], &OldCorpus); + for (size_t i = 1; i < Corpora.size(); i++) + GetSizedFilesFromDir(Corpora[i], &NewCorpus); + std::sort(OldCorpus.begin(), OldCorpus.end()); + std::sort(NewCorpus.begin(), NewCorpus.end()); + + std::string CFPath = CFPathOrNull ? CFPathOrNull : TempPath("Merge", ".txt"); + Vector NewFiles; + Set NewFeatures, NewCov; + CrashResistantMerge(Args, OldCorpus, NewCorpus, &NewFiles, {}, &NewFeatures, + {}, &NewCov, CFPath, true); + for (auto &Path : NewFiles) + F->WriteToOutputCorpus(FileToVector(Path, Options.MaxLen)); + // We are done, delete the control file if it was a temporary one. + if (!Flags.merge_control_file) + RemoveFile(CFPath); + + exit(0); +} + +int AnalyzeDictionary(Fuzzer *F, const Vector& Dict, + UnitVector& Corpus) { + Printf("Started dictionary minimization (up to %d tests)\n", + Dict.size() * Corpus.size() * 2); + + // Scores and usage count for each dictionary unit. + Vector Scores(Dict.size()); + Vector Usages(Dict.size()); + + Vector InitialFeatures; + Vector ModifiedFeatures; + for (auto &C : Corpus) { + // Get coverage for the testcase without modifications. + F->ExecuteCallback(C.data(), C.size()); + InitialFeatures.clear(); + TPC.CollectFeatures([&](size_t Feature) { + InitialFeatures.push_back(Feature); + }); + + for (size_t i = 0; i < Dict.size(); ++i) { + Vector Data = C; + auto StartPos = std::search(Data.begin(), Data.end(), + Dict[i].begin(), Dict[i].end()); + // Skip dictionary unit, if the testcase does not contain it. + if (StartPos == Data.end()) + continue; + + ++Usages[i]; + while (StartPos != Data.end()) { + // Replace all occurrences of dictionary unit in the testcase. + auto EndPos = StartPos + Dict[i].size(); + for (auto It = StartPos; It != EndPos; ++It) + *It ^= 0xFF; + + StartPos = std::search(EndPos, Data.end(), + Dict[i].begin(), Dict[i].end()); + } + + // Get coverage for testcase with masked occurrences of dictionary unit. + F->ExecuteCallback(Data.data(), Data.size()); + ModifiedFeatures.clear(); + TPC.CollectFeatures([&](size_t Feature) { + ModifiedFeatures.push_back(Feature); + }); + + if (InitialFeatures == ModifiedFeatures) + --Scores[i]; + else + Scores[i] += 2; + } + } + + Printf("###### Useless dictionary elements. ######\n"); + for (size_t i = 0; i < Dict.size(); ++i) { + // Dictionary units with positive score are treated as useful ones. + if (Scores[i] > 0) + continue; + + Printf("\""); + PrintASCII(Dict[i].data(), Dict[i].size(), "\""); + Printf(" # Score: %d, Used: %d\n", Scores[i], Usages[i]); + } + Printf("###### End of useless dictionary elements. ######\n"); + return 0; +} + +Vector ParseSeedInuts(const char *seed_inputs) { + // Parse -seed_inputs=file1,file2,... or -seed_inputs=@seed_inputs_file + Vector Files; + if (!seed_inputs) return Files; + std::string SeedInputs; + if (Flags.seed_inputs[0] == '@') + SeedInputs = FileToString(Flags.seed_inputs + 1); // File contains list. + else + SeedInputs = Flags.seed_inputs; // seed_inputs contains the list. + if (SeedInputs.empty()) { + Printf("seed_inputs is empty or @file does not exist.\n"); + exit(1); + } + // Parse SeedInputs. + size_t comma_pos = 0; + while ((comma_pos = SeedInputs.find_last_of(',')) != std::string::npos) { + Files.push_back(SeedInputs.substr(comma_pos + 1)); + SeedInputs = SeedInputs.substr(0, comma_pos); + } + Files.push_back(SeedInputs); + return Files; +} + +static Vector ReadCorpora(const Vector &CorpusDirs, + const Vector &ExtraSeedFiles) { + Vector SizedFiles; + size_t LastNumFiles = 0; + for (auto &Dir : CorpusDirs) { + GetSizedFilesFromDir(Dir, &SizedFiles); + Printf("INFO: % 8zd files found in %s\n", SizedFiles.size() - LastNumFiles, + Dir.c_str()); + LastNumFiles = SizedFiles.size(); + } + for (auto &File : ExtraSeedFiles) + if (auto Size = FileSize(File)) + SizedFiles.push_back({File, Size}); + return SizedFiles; +} + +int FuzzerDriver(int *argc, char ***argv, UserCallback Callback) { + using namespace fuzzer; + assert(argc && argv && "Argument pointers cannot be nullptr"); + std::string Argv0((*argv)[0]); + EF = new ExternalFunctions(); + if (EF->LLVMFuzzerInitialize) + EF->LLVMFuzzerInitialize(argc, argv); + if (EF->__msan_scoped_disable_interceptor_checks) + EF->__msan_scoped_disable_interceptor_checks(); + const Vector Args(*argv, *argv + *argc); + assert(!Args.empty()); + ProgName = new std::string(Args[0]); + if (Argv0 != *ProgName) { + Printf("ERROR: argv[0] has been modified in LLVMFuzzerInitialize\n"); + exit(1); + } + ParseFlags(Args, EF); + if (Flags.help) { + PrintHelp(); + return 0; + } + + if (Flags.close_fd_mask & 2) + DupAndCloseStderr(); + if (Flags.close_fd_mask & 1) + CloseStdout(); + + if (Flags.jobs > 0 && Flags.workers == 0) { + Flags.workers = std::min(NumberOfCpuCores() / 2, Flags.jobs); + if (Flags.workers > 1) + Printf("Running %u workers\n", Flags.workers); + } + + if (Flags.workers > 0 && Flags.jobs > 0) + return RunInMultipleProcesses(Args, Flags.workers, Flags.jobs); + + FuzzingOptions Options; + Options.Verbosity = Flags.verbosity; + Options.MaxLen = Flags.max_len; + Options.LenControl = Flags.len_control; + Options.KeepSeed = Flags.keep_seed; + Options.UnitTimeoutSec = Flags.timeout; + Options.ErrorExitCode = Flags.error_exitcode; + Options.TimeoutExitCode = Flags.timeout_exitcode; + Options.IgnoreTimeouts = Flags.ignore_timeouts; + Options.IgnoreOOMs = Flags.ignore_ooms; + Options.IgnoreCrashes = Flags.ignore_crashes; + Options.MaxTotalTimeSec = Flags.max_total_time; + Options.DoCrossOver = Flags.cross_over; + Options.CrossOverUniformDist = Flags.cross_over_uniform_dist; + Options.MutateDepth = Flags.mutate_depth; + Options.ReduceDepth = Flags.reduce_depth; + Options.UseCounters = Flags.use_counters; + Options.UseMemmem = Flags.use_memmem; + Options.UseCmp = Flags.use_cmp; + Options.UseValueProfile = Flags.use_value_profile; + Options.Shrink = Flags.shrink; + Options.ReduceInputs = Flags.reduce_inputs; + Options.ShuffleAtStartUp = Flags.shuffle; + Options.PreferSmall = Flags.prefer_small; + Options.ReloadIntervalSec = Flags.reload; + Options.OnlyASCII = Flags.only_ascii; + Options.DetectLeaks = Flags.detect_leaks; + Options.PurgeAllocatorIntervalSec = Flags.purge_allocator_interval; + Options.TraceMalloc = Flags.trace_malloc; + Options.RssLimitMb = Flags.rss_limit_mb; + Options.MallocLimitMb = Flags.malloc_limit_mb; + if (!Options.MallocLimitMb) + Options.MallocLimitMb = Options.RssLimitMb; + if (Flags.runs >= 0) + Options.MaxNumberOfRuns = Flags.runs; + if (!Inputs->empty() && !Flags.minimize_crash_internal_step) { + // Ensure output corpus assumed to be the first arbitrary argument input + // is not a path to an existing file. + std::string OutputCorpusDir = (*Inputs)[0]; + if (!IsFile(OutputCorpusDir)) { + Options.OutputCorpus = OutputCorpusDir; + ValidateDirectoryExists(Options.OutputCorpus, Flags.create_missing_dirs); + } + } + Options.ReportSlowUnits = Flags.report_slow_units; + if (Flags.artifact_prefix) { + Options.ArtifactPrefix = Flags.artifact_prefix; + + // Since the prefix could be a full path to a file name prefix, assume + // that if the path ends with the platform's separator that a directory + // is desired + std::string ArtifactPathDir = Options.ArtifactPrefix; + if (!IsSeparator(ArtifactPathDir[ArtifactPathDir.length() - 1])) { + ArtifactPathDir = DirName(ArtifactPathDir); + } + ValidateDirectoryExists(ArtifactPathDir, Flags.create_missing_dirs); + } + if (Flags.exact_artifact_path) { + Options.ExactArtifactPath = Flags.exact_artifact_path; + ValidateDirectoryExists(DirName(Options.ExactArtifactPath), + Flags.create_missing_dirs); + } + Vector Dictionary; + if (Flags.dict) + if (!ParseDictionaryFile(FileToString(Flags.dict), &Dictionary)) + return 1; + if (Flags.verbosity > 0 && !Dictionary.empty()) + Printf("Dictionary: %zd entries\n", Dictionary.size()); + bool RunIndividualFiles = AllInputsAreFiles(); + Options.SaveArtifacts = + !RunIndividualFiles || Flags.minimize_crash_internal_step; + Options.PrintNewCovPcs = Flags.print_pcs; + Options.PrintNewCovFuncs = Flags.print_funcs; + Options.PrintFinalStats = Flags.print_final_stats; + Options.PrintCorpusStats = Flags.print_corpus_stats; + Options.PrintCoverage = Flags.print_coverage; + Options.PrintFullCoverage = Flags.print_full_coverage; + if (Flags.exit_on_src_pos) + Options.ExitOnSrcPos = Flags.exit_on_src_pos; + if (Flags.exit_on_item) + Options.ExitOnItem = Flags.exit_on_item; + if (Flags.focus_function) + Options.FocusFunction = Flags.focus_function; + if (Flags.data_flow_trace) + Options.DataFlowTrace = Flags.data_flow_trace; + if (Flags.features_dir) { + Options.FeaturesDir = Flags.features_dir; + ValidateDirectoryExists(Options.FeaturesDir, Flags.create_missing_dirs); + } + if (Flags.mutation_graph_file) + Options.MutationGraphFile = Flags.mutation_graph_file; + if (Flags.collect_data_flow) + Options.CollectDataFlow = Flags.collect_data_flow; + if (Flags.stop_file) + Options.StopFile = Flags.stop_file; + Options.Entropic = Flags.entropic; + Options.EntropicFeatureFrequencyThreshold = + (size_t)Flags.entropic_feature_frequency_threshold; + Options.EntropicNumberOfRarestFeatures = + (size_t)Flags.entropic_number_of_rarest_features; + Options.EntropicScalePerExecTime = Flags.entropic_scale_per_exec_time; + if (!Options.FocusFunction.empty()) + Options.Entropic = false; // FocusFunction overrides entropic scheduling. + if (Options.Entropic) + Printf("INFO: Running with entropic power schedule (0x%X, %d).\n", + Options.EntropicFeatureFrequencyThreshold, + Options.EntropicNumberOfRarestFeatures); + struct EntropicOptions Entropic; + Entropic.Enabled = Options.Entropic; + Entropic.FeatureFrequencyThreshold = + Options.EntropicFeatureFrequencyThreshold; + Entropic.NumberOfRarestFeatures = Options.EntropicNumberOfRarestFeatures; + Entropic.ScalePerExecTime = Options.EntropicScalePerExecTime; + + unsigned Seed = Flags.seed; + // Initialize Seed. + if (Seed == 0) + Seed = static_cast( + std::chrono::system_clock::now().time_since_epoch().count() + GetPid()); + if (Flags.verbosity) + Printf("INFO: Seed: %u\n", Seed); + + if (Flags.collect_data_flow && !Flags.fork && !Flags.merge) { + if (RunIndividualFiles) + return CollectDataFlow(Flags.collect_data_flow, Flags.data_flow_trace, + ReadCorpora({}, *Inputs)); + else + return CollectDataFlow(Flags.collect_data_flow, Flags.data_flow_trace, + ReadCorpora(*Inputs, {})); + } + + Random Rand(Seed); + auto *MD = new MutationDispatcher(Rand, Options); + auto *Corpus = new InputCorpus(Options.OutputCorpus, Entropic); + auto *F = new Fuzzer(Callback, *Corpus, *MD, Options); + + for (auto &U: Dictionary) + if (U.size() <= Word::GetMaxSize()) + MD->AddWordToManualDictionary(Word(U.data(), U.size())); + + // Threads are only supported by Chrome. Don't use them with emscripten + // for now. +#if !LIBFUZZER_EMSCRIPTEN + StartRssThread(F, Flags.rss_limit_mb); +#endif // LIBFUZZER_EMSCRIPTEN + + Options.HandleAbrt = Flags.handle_abrt; + Options.HandleAlrm = !Flags.minimize_crash; + Options.HandleBus = Flags.handle_bus; + Options.HandleFpe = Flags.handle_fpe; + Options.HandleIll = Flags.handle_ill; + Options.HandleInt = Flags.handle_int; + Options.HandleSegv = Flags.handle_segv; + Options.HandleTerm = Flags.handle_term; + Options.HandleXfsz = Flags.handle_xfsz; + Options.HandleUsr1 = Flags.handle_usr1; + Options.HandleUsr2 = Flags.handle_usr2; + Options.HandleWinExcept = Flags.handle_winexcept; + + SetSignalHandler(Options); + + std::atexit(Fuzzer::StaticExitCallback); + + if (Flags.minimize_crash) + return MinimizeCrashInput(Args, Options); + + if (Flags.minimize_crash_internal_step) + return MinimizeCrashInputInternalStep(F, Corpus); + + if (Flags.cleanse_crash) + return CleanseCrashInput(Args, Options); + + if (RunIndividualFiles) { + Options.SaveArtifacts = false; + int Runs = std::max(1, Flags.runs); + Printf("%s: Running %zd inputs %d time(s) each.\n", ProgName->c_str(), + Inputs->size(), Runs); + for (auto &Path : *Inputs) { + auto StartTime = system_clock::now(); + Printf("Running: %s\n", Path.c_str()); + for (int Iter = 0; Iter < Runs; Iter++) + RunOneTest(F, Path.c_str(), Options.MaxLen); + auto StopTime = system_clock::now(); + auto MS = duration_cast(StopTime - StartTime).count(); + Printf("Executed %s in %zd ms\n", Path.c_str(), (long)MS); + } + Printf("***\n" + "*** NOTE: fuzzing was not performed, you have only\n" + "*** executed the target code on a fixed set of inputs.\n" + "***\n"); + F->PrintFinalStats(); + exit(0); + } + + if (Flags.fork) + FuzzWithFork(F->GetMD().GetRand(), Options, Args, *Inputs, Flags.fork); + + if (Flags.merge) + Merge(F, Options, Args, *Inputs, Flags.merge_control_file); + + if (Flags.merge_inner) { + const size_t kDefaultMaxMergeLen = 1 << 20; + if (Options.MaxLen == 0) + F->SetMaxInputLen(kDefaultMaxMergeLen); + assert(Flags.merge_control_file); + F->CrashResistantMergeInternalStep(Flags.merge_control_file); + exit(0); + } + + if (Flags.analyze_dict) { + size_t MaxLen = INT_MAX; // Large max length. + UnitVector InitialCorpus; + for (auto &Inp : *Inputs) { + Printf("Loading corpus dir: %s\n", Inp.c_str()); + ReadDirToVectorOfUnits(Inp.c_str(), &InitialCorpus, nullptr, + MaxLen, /*ExitOnError=*/false); + } + + if (Dictionary.empty() || Inputs->empty()) { + Printf("ERROR: can't analyze dict without dict and corpus provided\n"); + return 1; + } + if (AnalyzeDictionary(F, Dictionary, InitialCorpus)) { + Printf("Dictionary analysis failed\n"); + exit(1); + } + Printf("Dictionary analysis succeeded\n"); + exit(0); + } + + auto CorporaFiles = ReadCorpora(*Inputs, ParseSeedInuts(Flags.seed_inputs)); + F->Loop(CorporaFiles); + + if (Flags.verbosity) + Printf("Done %zd runs in %zd second(s)\n", F->getTotalNumberOfRuns(), + F->secondsSinceProcessStartUp()); + F->PrintFinalStats(); + + exit(0); // Don't let F destroy itself. +} + +extern "C" ATTRIBUTE_INTERFACE int +LLVMFuzzerRunDriver(int *argc, char ***argv, + int (*UserCb)(const uint8_t *Data, size_t Size)) { + return FuzzerDriver(argc, argv, UserCb); +} + +// Storage for global ExternalFunctions object. +ExternalFunctions *EF = nullptr; + +} // namespace fuzzer diff --git a/core/lib/libfuzzer-sys/libfuzzer/FuzzerExtFunctions.def b/core/lib/libfuzzer-sys/libfuzzer/FuzzerExtFunctions.def new file mode 100644 index 0000000000..51edf8444e --- /dev/null +++ b/core/lib/libfuzzer-sys/libfuzzer/FuzzerExtFunctions.def @@ -0,0 +1,50 @@ +//===- FuzzerExtFunctions.def - External functions --------------*- C++ -* ===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// This defines the external function pointers that +// ``fuzzer::ExternalFunctions`` should contain and try to initialize. The +// EXT_FUNC macro must be defined at the point of inclusion. The signature of +// the macro is: +// +// EXT_FUNC(, , , ) +//===----------------------------------------------------------------------===// + +// Optional user functions +EXT_FUNC(LLVMFuzzerInitialize, int, (int *argc, char ***argv), false); +EXT_FUNC(LLVMFuzzerCustomMutator, size_t, + (uint8_t *Data, size_t Size, size_t MaxSize, unsigned int Seed), + false); +EXT_FUNC(LLVMFuzzerCustomCrossOver, size_t, + (const uint8_t *Data1, size_t Size1, + const uint8_t *Data2, size_t Size2, + uint8_t *Out, size_t MaxOutSize, unsigned int Seed), + false); + +// Sanitizer functions +EXT_FUNC(__lsan_enable, void, (), false); +EXT_FUNC(__lsan_disable, void, (), false); +EXT_FUNC(__lsan_do_recoverable_leak_check, int, (), false); +EXT_FUNC(__sanitizer_acquire_crash_state, int, (), true); +EXT_FUNC(__sanitizer_install_malloc_and_free_hooks, int, + (void (*malloc_hook)(const volatile void *, size_t), + void (*free_hook)(const volatile void *)), + false); +EXT_FUNC(__sanitizer_log_write, void, (const char *buf, size_t len), false); +EXT_FUNC(__sanitizer_purge_allocator, void, (), false); +EXT_FUNC(__sanitizer_print_memory_profile, void, (size_t, size_t), false); +EXT_FUNC(__sanitizer_print_stack_trace, void, (), true); +EXT_FUNC(__sanitizer_symbolize_pc, void, + (void *, const char *fmt, char *out_buf, size_t out_buf_size), false); +EXT_FUNC(__sanitizer_get_module_and_offset_for_pc, int, + (void *pc, char *module_path, + size_t module_path_len,void **pc_offset), false); +EXT_FUNC(__sanitizer_set_death_callback, void, (void (*)(void)), true); +EXT_FUNC(__sanitizer_set_report_fd, void, (void*), false); +EXT_FUNC(__msan_scoped_disable_interceptor_checks, void, (), false); +EXT_FUNC(__msan_scoped_enable_interceptor_checks, void, (), false); +EXT_FUNC(__msan_unpoison, void, (const volatile void *, size_t size), false); +EXT_FUNC(__msan_unpoison_param, void, (size_t n), false); diff --git a/core/lib/libfuzzer-sys/libfuzzer/FuzzerExtFunctions.h b/core/lib/libfuzzer-sys/libfuzzer/FuzzerExtFunctions.h new file mode 100644 index 0000000000..c88aac4e67 --- /dev/null +++ b/core/lib/libfuzzer-sys/libfuzzer/FuzzerExtFunctions.h @@ -0,0 +1,34 @@ +//===- FuzzerExtFunctions.h - Interface to external functions ---*- C++ -* ===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// Defines an interface to (possibly optional) functions. +//===----------------------------------------------------------------------===// + +#ifndef LLVM_FUZZER_EXT_FUNCTIONS_H +#define LLVM_FUZZER_EXT_FUNCTIONS_H + +#include +#include + +namespace fuzzer { + +struct ExternalFunctions { + // Initialize function pointers. Functions that are not available will be set + // to nullptr. Do not call this constructor before ``main()`` has been + // entered. + ExternalFunctions(); + +#define EXT_FUNC(NAME, RETURN_TYPE, FUNC_SIG, WARN) \ + RETURN_TYPE(*NAME) FUNC_SIG = nullptr + +#include "FuzzerExtFunctions.def" + +#undef EXT_FUNC +}; +} // namespace fuzzer + +#endif diff --git a/core/lib/libfuzzer-sys/libfuzzer/FuzzerExtFunctionsDlsym.cpp b/core/lib/libfuzzer-sys/libfuzzer/FuzzerExtFunctionsDlsym.cpp new file mode 100644 index 0000000000..95233d2a10 --- /dev/null +++ b/core/lib/libfuzzer-sys/libfuzzer/FuzzerExtFunctionsDlsym.cpp @@ -0,0 +1,51 @@ +//===- FuzzerExtFunctionsDlsym.cpp - Interface to external functions ------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// Implementation for operating systems that support dlsym(). We only use it on +// Apple platforms for now. We don't use this approach on Linux because it +// requires that clients of LibFuzzer pass ``--export-dynamic`` to the linker. +// That is a complication we don't wish to expose to clients right now. +//===----------------------------------------------------------------------===// +#include "FuzzerPlatform.h" +#if LIBFUZZER_APPLE + +#include "FuzzerExtFunctions.h" +#include "FuzzerIO.h" +#include + +using namespace fuzzer; + +template +static T GetFnPtr(const char *FnName, bool WarnIfMissing) { + dlerror(); // Clear any previous errors. + void *Fn = dlsym(RTLD_DEFAULT, FnName); + if (Fn == nullptr) { + if (WarnIfMissing) { + const char *ErrorMsg = dlerror(); + Printf("WARNING: Failed to find function \"%s\".", FnName); + if (ErrorMsg) + Printf(" Reason %s.", ErrorMsg); + Printf("\n"); + } + } + return reinterpret_cast(Fn); +} + +namespace fuzzer { + +ExternalFunctions::ExternalFunctions() { +#define EXT_FUNC(NAME, RETURN_TYPE, FUNC_SIG, WARN) \ + this->NAME = GetFnPtr(#NAME, WARN) + +#include "FuzzerExtFunctions.def" + +#undef EXT_FUNC +} + +} // namespace fuzzer + +#endif // LIBFUZZER_APPLE diff --git a/core/lib/libfuzzer-sys/libfuzzer/FuzzerExtFunctionsWeak.cpp b/core/lib/libfuzzer-sys/libfuzzer/FuzzerExtFunctionsWeak.cpp new file mode 100644 index 0000000000..3ef758daa7 --- /dev/null +++ b/core/lib/libfuzzer-sys/libfuzzer/FuzzerExtFunctionsWeak.cpp @@ -0,0 +1,54 @@ +//===- FuzzerExtFunctionsWeak.cpp - Interface to external functions -------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// Implementation for Linux. This relies on the linker's support for weak +// symbols. We don't use this approach on Apple platforms because it requires +// clients of LibFuzzer to pass ``-U _`` to the linker to allow +// weak symbols to be undefined. That is a complication we don't want to expose +// to clients right now. +//===----------------------------------------------------------------------===// +#include "FuzzerPlatform.h" +#if LIBFUZZER_LINUX || LIBFUZZER_NETBSD || LIBFUZZER_FUCHSIA || \ + LIBFUZZER_FREEBSD || LIBFUZZER_EMSCRIPTEN + +#include "FuzzerExtFunctions.h" +#include "FuzzerIO.h" + +extern "C" { +// Declare these symbols as weak to allow them to be optionally defined. +#define EXT_FUNC(NAME, RETURN_TYPE, FUNC_SIG, WARN) \ + __attribute__((weak, visibility("default"))) RETURN_TYPE NAME FUNC_SIG + +#include "FuzzerExtFunctions.def" + +#undef EXT_FUNC +} + +using namespace fuzzer; + +static void CheckFnPtr(void *FnPtr, const char *FnName, bool WarnIfMissing) { + if (FnPtr == nullptr && WarnIfMissing) { + Printf("WARNING: Failed to find function \"%s\".\n", FnName); + } +} + +namespace fuzzer { + +ExternalFunctions::ExternalFunctions() { +#define EXT_FUNC(NAME, RETURN_TYPE, FUNC_SIG, WARN) \ + this->NAME = ::NAME; \ + CheckFnPtr(reinterpret_cast(reinterpret_cast(::NAME)), \ + #NAME, WARN); + +#include "FuzzerExtFunctions.def" + +#undef EXT_FUNC +} + +} // namespace fuzzer + +#endif diff --git a/core/lib/libfuzzer-sys/libfuzzer/FuzzerExtFunctionsWindows.cpp b/core/lib/libfuzzer-sys/libfuzzer/FuzzerExtFunctionsWindows.cpp new file mode 100644 index 0000000000..688bad1d51 --- /dev/null +++ b/core/lib/libfuzzer-sys/libfuzzer/FuzzerExtFunctionsWindows.cpp @@ -0,0 +1,82 @@ +//=== FuzzerExtWindows.cpp - Interface to external functions --------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// Implementation of FuzzerExtFunctions for Windows. Uses alternatename when +// compiled with MSVC. Uses weak aliases when compiled with clang. Unfortunately +// the method each compiler supports is not supported by the other. +//===----------------------------------------------------------------------===// +#include "FuzzerPlatform.h" +#if LIBFUZZER_WINDOWS + +#include "FuzzerExtFunctions.h" +#include "FuzzerIO.h" + +using namespace fuzzer; + +// Intermediate macro to ensure the parameter is expanded before stringified. +#define STRINGIFY_(A) #A +#define STRINGIFY(A) STRINGIFY_(A) + +#if LIBFUZZER_MSVC +// Copied from compiler-rt/lib/sanitizer_common/sanitizer_win_defs.h +#if defined(_M_IX86) || defined(__i386__) +#define WIN_SYM_PREFIX "_" +#else +#define WIN_SYM_PREFIX +#endif + +// Declare external functions as having alternativenames, so that we can +// determine if they are not defined. +#define EXTERNAL_FUNC(Name, Default) \ + __pragma(comment(linker, "/alternatename:" WIN_SYM_PREFIX STRINGIFY( \ + Name) "=" WIN_SYM_PREFIX STRINGIFY(Default))) +#else +// Declare external functions as weak to allow them to default to a specified +// function if not defined explicitly. We must use weak symbols because clang's +// support for alternatename is not 100%, see +// https://bugs.llvm.org/show_bug.cgi?id=40218 for more details. +#define EXTERNAL_FUNC(Name, Default) \ + __attribute__((weak, alias(STRINGIFY(Default)))) +#endif // LIBFUZZER_MSVC + +extern "C" { +#define EXT_FUNC(NAME, RETURN_TYPE, FUNC_SIG, WARN) \ + RETURN_TYPE NAME##Def FUNC_SIG { \ + Printf("ERROR: Function \"%s\" not defined.\n", #NAME); \ + exit(1); \ + } \ + EXTERNAL_FUNC(NAME, NAME##Def) RETURN_TYPE NAME FUNC_SIG + +#include "FuzzerExtFunctions.def" + +#undef EXT_FUNC +} + +template +static T *GetFnPtr(T *Fun, T *FunDef, const char *FnName, bool WarnIfMissing) { + if (Fun == FunDef) { + if (WarnIfMissing) + Printf("WARNING: Failed to find function \"%s\".\n", FnName); + return nullptr; + } + return Fun; +} + +namespace fuzzer { + +ExternalFunctions::ExternalFunctions() { +#define EXT_FUNC(NAME, RETURN_TYPE, FUNC_SIG, WARN) \ + this->NAME = GetFnPtr(::NAME, ::NAME##Def, #NAME, WARN); + +#include "FuzzerExtFunctions.def" + +#undef EXT_FUNC +} + +} // namespace fuzzer + +#endif // LIBFUZZER_WINDOWS diff --git a/core/lib/libfuzzer-sys/libfuzzer/FuzzerExtraCounters.cpp b/core/lib/libfuzzer-sys/libfuzzer/FuzzerExtraCounters.cpp new file mode 100644 index 0000000000..04f569a1a8 --- /dev/null +++ b/core/lib/libfuzzer-sys/libfuzzer/FuzzerExtraCounters.cpp @@ -0,0 +1,42 @@ +//===- FuzzerExtraCounters.cpp - Extra coverage counters ------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// Extra coverage counters defined by user code. +//===----------------------------------------------------------------------===// + +#include "FuzzerPlatform.h" +#include + +#if LIBFUZZER_LINUX || LIBFUZZER_NETBSD || LIBFUZZER_FREEBSD || \ + LIBFUZZER_FUCHSIA || LIBFUZZER_EMSCRIPTEN +__attribute__((weak)) extern uint8_t __start___libfuzzer_extra_counters; +__attribute__((weak)) extern uint8_t __stop___libfuzzer_extra_counters; + +namespace fuzzer { +uint8_t *ExtraCountersBegin() { return &__start___libfuzzer_extra_counters; } +uint8_t *ExtraCountersEnd() { return &__stop___libfuzzer_extra_counters; } +ATTRIBUTE_NO_SANITIZE_ALL +void ClearExtraCounters() { // hand-written memset, don't asan-ify. + uintptr_t *Beg = reinterpret_cast(ExtraCountersBegin()); + uintptr_t *End = reinterpret_cast(ExtraCountersEnd()); + for (; Beg < End; Beg++) { + *Beg = 0; + __asm__ __volatile__("" : : : "memory"); + } +} + +} // namespace fuzzer + +#else +// TODO: implement for other platforms. +namespace fuzzer { +uint8_t *ExtraCountersBegin() { return nullptr; } +uint8_t *ExtraCountersEnd() { return nullptr; } +void ClearExtraCounters() {} +} // namespace fuzzer + +#endif diff --git a/core/lib/libfuzzer-sys/libfuzzer/FuzzerFlags.def b/core/lib/libfuzzer-sys/libfuzzer/FuzzerFlags.def new file mode 100644 index 0000000000..ab31da0ae5 --- /dev/null +++ b/core/lib/libfuzzer-sys/libfuzzer/FuzzerFlags.def @@ -0,0 +1,202 @@ +//===- FuzzerFlags.def - Run-time flags -------------------------*- C++ -* ===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// Flags. FUZZER_FLAG_INT/FUZZER_FLAG_STRING macros should be defined at the +// point of inclusion. We are not using any flag parsing library for better +// portability and independence. +//===----------------------------------------------------------------------===// +FUZZER_FLAG_INT(verbosity, 1, "Verbosity level.") +FUZZER_FLAG_UNSIGNED(seed, 0, "Random seed. If 0, seed is generated.") +FUZZER_FLAG_INT(runs, -1, + "Number of individual test runs (-1 for infinite runs).") +FUZZER_FLAG_INT(max_len, 0, "Maximum length of the test input. " + "If 0, libFuzzer tries to guess a good value based on the corpus " + "and reports it. ") +FUZZER_FLAG_INT(len_control, 100, "Try generating small inputs first, " + "then try larger inputs over time. Specifies the rate at which the length " + "limit is increased (smaller == faster). If 0, immediately try inputs with " + "size up to max_len. Default value is 0, if LLVMFuzzerCustomMutator is used.") +FUZZER_FLAG_STRING(seed_inputs, "A comma-separated list of input files " + "to use as an additional seed corpus. Alternatively, an \"@\" followed by " + "the name of a file containing the comma-separated list.") +FUZZER_FLAG_INT(keep_seed, 0, "If 1, keep seed inputs in the corpus even if " + "they do not produce new coverage. When used with |reduce_inputs==1|, the " + "seed inputs will never be reduced. This option can be useful when seeds are" + "not properly formed for the fuzz target but still have useful snippets.") +FUZZER_FLAG_INT(cross_over, 1, "If 1, cross over inputs.") +FUZZER_FLAG_INT(cross_over_uniform_dist, 0, "Experimental. If 1, use a " + "uniform probability distribution when choosing inputs to cross over with. " + "Some of the inputs in the corpus may never get chosen for mutation " + "depending on the input mutation scheduling policy. With this flag, all " + "inputs, regardless of the input mutation scheduling policy, can be chosen " + "as an input to cross over with. This can be particularly useful with " + "|keep_seed==1|; all the initial seed inputs, even though they do not " + "increase coverage because they are not properly formed, will still be " + "chosen as an input to cross over with.") + +FUZZER_FLAG_INT(mutate_depth, 5, + "Apply this number of consecutive mutations to each input.") +FUZZER_FLAG_INT(reduce_depth, 0, "Experimental/internal. " + "Reduce depth if mutations lose unique features") +FUZZER_FLAG_INT(shuffle, 1, "Shuffle inputs at startup") +FUZZER_FLAG_INT(prefer_small, 1, + "If 1, always prefer smaller inputs during the corpus shuffle.") +FUZZER_FLAG_INT( + timeout, 1200, + "Timeout in seconds (if positive). " + "If one unit runs more than this number of seconds the process will abort.") +FUZZER_FLAG_INT(error_exitcode, 77, "When libFuzzer itself reports a bug " + "this exit code will be used.") +FUZZER_FLAG_INT(timeout_exitcode, 70, "When libFuzzer reports a timeout " + "this exit code will be used.") +FUZZER_FLAG_INT(max_total_time, 0, "If positive, indicates the maximal total " + "time in seconds to run the fuzzer.") +FUZZER_FLAG_INT(help, 0, "Print help.") +FUZZER_FLAG_INT(fork, 0, "Experimental mode where fuzzing happens " + "in a subprocess") +FUZZER_FLAG_INT(ignore_timeouts, 1, "Ignore timeouts in fork mode") +FUZZER_FLAG_INT(ignore_ooms, 1, "Ignore OOMs in fork mode") +FUZZER_FLAG_INT(ignore_crashes, 0, "Ignore crashes in fork mode") +FUZZER_FLAG_INT(merge, 0, "If 1, the 2-nd, 3-rd, etc corpora will be " + "merged into the 1-st corpus. Only interesting units will be taken. " + "This flag can be used to minimize a corpus.") +FUZZER_FLAG_STRING(stop_file, "Stop fuzzing ASAP if this file exists") +FUZZER_FLAG_STRING(merge_inner, "internal flag") +FUZZER_FLAG_STRING(merge_control_file, + "Specify a control file used for the merge process. " + "If a merge process gets killed it tries to leave this file " + "in a state suitable for resuming the merge. " + "By default a temporary file will be used." + "The same file can be used for multistep merge process.") +FUZZER_FLAG_INT(minimize_crash, 0, "If 1, minimizes the provided" + " crash input. Use with -runs=N or -max_total_time=N to limit " + "the number attempts." + " Use with -exact_artifact_path to specify the output." + " Combine with ASAN_OPTIONS=dedup_token_length=3 (or similar) to ensure that" + " the minimized input triggers the same crash." + ) +FUZZER_FLAG_INT(cleanse_crash, 0, "If 1, tries to cleanse the provided" + " crash input to make it contain fewer original bytes." + " Use with -exact_artifact_path to specify the output." + ) +FUZZER_FLAG_INT(minimize_crash_internal_step, 0, "internal flag") +FUZZER_FLAG_STRING(features_dir, "internal flag. Used to dump feature sets on disk." + "Every time a new input is added to the corpus, a corresponding file in the features_dir" + " is created containing the unique features of that input." + " Features are stored in binary format.") +FUZZER_FLAG_STRING(mutation_graph_file, "Saves a graph (in DOT format) to" + " mutation_graph_file. The graph contains a vertex for each input that has" + " unique coverage; directed edges are provided between parents and children" + " where the child has unique coverage, and are recorded with the type of" + " mutation that caused the child.") +FUZZER_FLAG_INT(use_counters, 1, "Use coverage counters") +FUZZER_FLAG_INT(use_memmem, 1, + "Use hints from intercepting memmem, strstr, etc") +FUZZER_FLAG_INT(use_value_profile, 0, + "Experimental. Use value profile to guide fuzzing.") +FUZZER_FLAG_INT(use_cmp, 1, "Use CMP traces to guide mutations") +FUZZER_FLAG_INT(shrink, 0, "Experimental. Try to shrink corpus inputs.") +FUZZER_FLAG_INT(reduce_inputs, 1, + "Try to reduce the size of inputs while preserving their full feature sets") +FUZZER_FLAG_UNSIGNED(jobs, 0, "Number of jobs to run. If jobs >= 1 we spawn" + " this number of jobs in separate worker processes" + " with stdout/stderr redirected to fuzz-JOB.log.") +FUZZER_FLAG_UNSIGNED(workers, 0, + "Number of simultaneous worker processes to run the jobs." + " If zero, \"min(jobs,NumberOfCpuCores()/2)\" is used.") +FUZZER_FLAG_INT(reload, 1, + "Reload the main corpus every seconds to get new units" + " discovered by other processes. If 0, disabled") +FUZZER_FLAG_INT(report_slow_units, 10, + "Report slowest units if they run for more than this number of seconds.") +FUZZER_FLAG_INT(only_ascii, 0, + "If 1, generate only ASCII (isprint+isspace) inputs.") +FUZZER_FLAG_STRING(dict, "Experimental. Use the dictionary file.") +FUZZER_FLAG_STRING(artifact_prefix, "Write fuzzing artifacts (crash, " + "timeout, or slow inputs) as " + "$(artifact_prefix)file") +FUZZER_FLAG_STRING(exact_artifact_path, + "Write the single artifact on failure (crash, timeout) " + "as $(exact_artifact_path). This overrides -artifact_prefix " + "and will not use checksum in the file name. Do not " + "use the same path for several parallel processes.") +FUZZER_FLAG_INT(print_pcs, 0, "If 1, print out newly covered PCs.") +FUZZER_FLAG_INT(print_funcs, 2, "If >=1, print out at most this number of " + "newly covered functions.") +FUZZER_FLAG_INT(print_final_stats, 0, "If 1, print statistics at exit.") +FUZZER_FLAG_INT(print_corpus_stats, 0, + "If 1, print statistics on corpus elements at exit.") +FUZZER_FLAG_INT(print_coverage, 0, "If 1, print coverage information as text" + " at exit.") +FUZZER_FLAG_INT(print_full_coverage, 0, "If 1, print full coverage information " + "(all branches) as text at exit.") +FUZZER_FLAG_INT(dump_coverage, 0, "Deprecated.") +FUZZER_FLAG_INT(handle_segv, 1, "If 1, try to intercept SIGSEGV.") +FUZZER_FLAG_INT(handle_bus, 1, "If 1, try to intercept SIGBUS.") +FUZZER_FLAG_INT(handle_abrt, 1, "If 1, try to intercept SIGABRT.") +FUZZER_FLAG_INT(handle_ill, 1, "If 1, try to intercept SIGILL.") +FUZZER_FLAG_INT(handle_fpe, 1, "If 1, try to intercept SIGFPE.") +FUZZER_FLAG_INT(handle_int, 1, "If 1, try to intercept SIGINT.") +FUZZER_FLAG_INT(handle_term, 1, "If 1, try to intercept SIGTERM.") +FUZZER_FLAG_INT(handle_xfsz, 1, "If 1, try to intercept SIGXFSZ.") +FUZZER_FLAG_INT(handle_usr1, 1, "If 1, try to intercept SIGUSR1.") +FUZZER_FLAG_INT(handle_usr2, 1, "If 1, try to intercept SIGUSR2.") +FUZZER_FLAG_INT(handle_winexcept, 1, "If 1, try to intercept uncaught Windows " + "Visual C++ Exceptions.") +FUZZER_FLAG_INT(close_fd_mask, 0, "If 1, close stdout at startup; " + "if 2, close stderr; if 3, close both. " + "Be careful, this will also close e.g. stderr of asan.") +FUZZER_FLAG_INT(detect_leaks, 1, "If 1, and if LeakSanitizer is enabled " + "try to detect memory leaks during fuzzing (i.e. not only at shut down).") +FUZZER_FLAG_INT(purge_allocator_interval, 1, "Purge allocator caches and " + "quarantines every seconds. When rss_limit_mb is specified (>0), " + "purging starts when RSS exceeds 50% of rss_limit_mb. Pass " + "purge_allocator_interval=-1 to disable this functionality.") +FUZZER_FLAG_INT(trace_malloc, 0, "If >= 1 will print all mallocs/frees. " + "If >= 2 will also print stack traces.") +FUZZER_FLAG_INT(rss_limit_mb, 2048, "If non-zero, the fuzzer will exit upon" + "reaching this limit of RSS memory usage.") +FUZZER_FLAG_INT(malloc_limit_mb, 0, "If non-zero, the fuzzer will exit " + "if the target tries to allocate this number of Mb with one malloc call. " + "If zero (default) same limit as rss_limit_mb is applied.") +FUZZER_FLAG_STRING(exit_on_src_pos, "Exit if a newly found PC originates" + " from the given source location. Example: -exit_on_src_pos=foo.cc:123. " + "Used primarily for testing libFuzzer itself.") +FUZZER_FLAG_STRING(exit_on_item, "Exit if an item with a given sha1 sum" + " was added to the corpus. " + "Used primarily for testing libFuzzer itself.") +FUZZER_FLAG_INT(ignore_remaining_args, 0, "If 1, ignore all arguments passed " + "after this one. Useful for fuzzers that need to do their own " + "argument parsing.") +FUZZER_FLAG_STRING(focus_function, "Experimental. " + "Fuzzing will focus on inputs that trigger calls to this function. " + "If -focus_function=auto and -data_flow_trace is used, libFuzzer " + "will choose the focus functions automatically. Disables -entropic when " + "specified.") +FUZZER_FLAG_INT(entropic, 1, "Enables entropic power schedule.") +FUZZER_FLAG_INT(entropic_feature_frequency_threshold, 0xFF, "Experimental. If " + "entropic is enabled, all features which are observed less often than " + "the specified value are considered as rare.") +FUZZER_FLAG_INT(entropic_number_of_rarest_features, 100, "Experimental. If " + "entropic is enabled, we keep track of the frequencies only for the " + "Top-X least abundant features (union features that are considered as " + "rare).") +FUZZER_FLAG_INT(entropic_scale_per_exec_time, 0, "Experimental. If 1, " + "the Entropic power schedule gets scaled based on the input execution " + "time. Inputs with lower execution time get scheduled more (up to 30x). " + "Note that, if 1, fuzzer stops from being deterministic even if a " + "non-zero random seed is given.") + +FUZZER_FLAG_INT(analyze_dict, 0, "Experimental") +FUZZER_DEPRECATED_FLAG(use_clang_coverage) +FUZZER_FLAG_STRING(data_flow_trace, "Experimental: use the data flow trace") +FUZZER_FLAG_STRING(collect_data_flow, + "Experimental: collect the data flow trace") + +FUZZER_FLAG_INT(create_missing_dirs, 0, "Automatically attempt to create " + "directories for arguments that would normally expect them to already " + "exist (i.e. artifact_prefix, exact_artifact_path, features_dir, corpus)") diff --git a/core/lib/libfuzzer-sys/libfuzzer/FuzzerFork.cpp b/core/lib/libfuzzer-sys/libfuzzer/FuzzerFork.cpp new file mode 100644 index 0000000000..5134a5d979 --- /dev/null +++ b/core/lib/libfuzzer-sys/libfuzzer/FuzzerFork.cpp @@ -0,0 +1,418 @@ +//===- FuzzerFork.cpp - run fuzzing in separate subprocesses --------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// Spawn and orchestrate separate fuzzing processes. +//===----------------------------------------------------------------------===// + +#include "FuzzerCommand.h" +#include "FuzzerFork.h" +#include "FuzzerIO.h" +#include "FuzzerInternal.h" +#include "FuzzerMerge.h" +#include "FuzzerSHA1.h" +#include "FuzzerTracePC.h" +#include "FuzzerUtil.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace fuzzer { + +struct Stats { + size_t number_of_executed_units = 0; + size_t peak_rss_mb = 0; + size_t average_exec_per_sec = 0; +}; + +static Stats ParseFinalStatsFromLog(const std::string &LogPath) { + std::ifstream In(LogPath); + std::string Line; + Stats Res; + struct { + const char *Name; + size_t *Var; + } NameVarPairs[] = { + {"stat::number_of_executed_units:", &Res.number_of_executed_units}, + {"stat::peak_rss_mb:", &Res.peak_rss_mb}, + {"stat::average_exec_per_sec:", &Res.average_exec_per_sec}, + {nullptr, nullptr}, + }; + while (std::getline(In, Line, '\n')) { + if (Line.find("stat::") != 0) continue; + std::istringstream ISS(Line); + std::string Name; + size_t Val; + ISS >> Name >> Val; + for (size_t i = 0; NameVarPairs[i].Name; i++) + if (Name == NameVarPairs[i].Name) + *NameVarPairs[i].Var = Val; + } + return Res; +} + +struct FuzzJob { + // Inputs. + Command Cmd; + std::string CorpusDir; + std::string FeaturesDir; + std::string LogPath; + std::string SeedListPath; + std::string CFPath; + size_t JobId; + + int DftTimeInSeconds = 0; + + // Fuzzing Outputs. + int ExitCode; + + ~FuzzJob() { + RemoveFile(CFPath); + RemoveFile(LogPath); + RemoveFile(SeedListPath); + RmDirRecursive(CorpusDir); + RmDirRecursive(FeaturesDir); + } +}; + +struct GlobalEnv { + Vector Args; + Vector CorpusDirs; + std::string MainCorpusDir; + std::string TempDir; + std::string DFTDir; + std::string DataFlowBinary; + Set Features, Cov; + Set FilesWithDFT; + Vector Files; + Random *Rand; + std::chrono::system_clock::time_point ProcessStartTime; + int Verbosity = 0; + + size_t NumTimeouts = 0; + size_t NumOOMs = 0; + size_t NumCrashes = 0; + + + size_t NumRuns = 0; + + std::string StopFile() { return DirPlusFile(TempDir, "STOP"); } + + size_t secondsSinceProcessStartUp() const { + return std::chrono::duration_cast( + std::chrono::system_clock::now() - ProcessStartTime) + .count(); + } + + FuzzJob *CreateNewJob(size_t JobId) { + Command Cmd(Args); + Cmd.removeFlag("fork"); + Cmd.removeFlag("runs"); + Cmd.removeFlag("collect_data_flow"); + for (auto &C : CorpusDirs) // Remove all corpora from the args. + Cmd.removeArgument(C); + Cmd.addFlag("reload", "0"); // working in an isolated dir, no reload. + Cmd.addFlag("print_final_stats", "1"); + Cmd.addFlag("print_funcs", "0"); // no need to spend time symbolizing. + Cmd.addFlag("max_total_time", std::to_string(std::min((size_t)300, JobId))); + Cmd.addFlag("stop_file", StopFile()); + if (!DataFlowBinary.empty()) { + Cmd.addFlag("data_flow_trace", DFTDir); + if (!Cmd.hasFlag("focus_function")) + Cmd.addFlag("focus_function", "auto"); + } + auto Job = new FuzzJob; + std::string Seeds; + if (size_t CorpusSubsetSize = + std::min(Files.size(), (size_t)sqrt(Files.size() + 2))) { + auto Time1 = std::chrono::system_clock::now(); + for (size_t i = 0; i < CorpusSubsetSize; i++) { + auto &SF = Files[Rand->SkewTowardsLast(Files.size())]; + Seeds += (Seeds.empty() ? "" : ",") + SF; + CollectDFT(SF); + } + auto Time2 = std::chrono::system_clock::now(); + auto DftTimeInSeconds = duration_cast(Time2 - Time1).count(); + assert(DftTimeInSeconds < std::numeric_limits::max()); + Job->DftTimeInSeconds = static_cast(DftTimeInSeconds); + } + if (!Seeds.empty()) { + Job->SeedListPath = + DirPlusFile(TempDir, std::to_string(JobId) + ".seeds"); + WriteToFile(Seeds, Job->SeedListPath); + Cmd.addFlag("seed_inputs", "@" + Job->SeedListPath); + } + Job->LogPath = DirPlusFile(TempDir, std::to_string(JobId) + ".log"); + Job->CorpusDir = DirPlusFile(TempDir, "C" + std::to_string(JobId)); + Job->FeaturesDir = DirPlusFile(TempDir, "F" + std::to_string(JobId)); + Job->CFPath = DirPlusFile(TempDir, std::to_string(JobId) + ".merge"); + Job->JobId = JobId; + + + Cmd.addArgument(Job->CorpusDir); + Cmd.addFlag("features_dir", Job->FeaturesDir); + + for (auto &D : {Job->CorpusDir, Job->FeaturesDir}) { + RmDirRecursive(D); + MkDir(D); + } + + Cmd.setOutputFile(Job->LogPath); + Cmd.combineOutAndErr(); + + Job->Cmd = Cmd; + + if (Verbosity >= 2) + Printf("Job %zd/%p Created: %s\n", JobId, Job, + Job->Cmd.toString().c_str()); + // Start from very short runs and gradually increase them. + return Job; + } + + void RunOneMergeJob(FuzzJob *Job) { + auto Stats = ParseFinalStatsFromLog(Job->LogPath); + NumRuns += Stats.number_of_executed_units; + + Vector TempFiles, MergeCandidates; + // Read all newly created inputs and their feature sets. + // Choose only those inputs that have new features. + GetSizedFilesFromDir(Job->CorpusDir, &TempFiles); + std::sort(TempFiles.begin(), TempFiles.end()); + for (auto &F : TempFiles) { + auto FeatureFile = F.File; + FeatureFile.replace(0, Job->CorpusDir.size(), Job->FeaturesDir); + auto FeatureBytes = FileToVector(FeatureFile, 0, false); + assert((FeatureBytes.size() % sizeof(uint32_t)) == 0); + Vector NewFeatures(FeatureBytes.size() / sizeof(uint32_t)); + memcpy(NewFeatures.data(), FeatureBytes.data(), FeatureBytes.size()); + for (auto Ft : NewFeatures) { + if (!Features.count(Ft)) { + MergeCandidates.push_back(F); + break; + } + } + } + // if (!FilesToAdd.empty() || Job->ExitCode != 0) + Printf("#%zd: cov: %zd ft: %zd corp: %zd exec/s %zd " + "oom/timeout/crash: %zd/%zd/%zd time: %zds job: %zd dft_time: %d\n", + NumRuns, Cov.size(), Features.size(), Files.size(), + Stats.average_exec_per_sec, NumOOMs, NumTimeouts, NumCrashes, + secondsSinceProcessStartUp(), Job->JobId, Job->DftTimeInSeconds); + + if (MergeCandidates.empty()) return; + + Vector FilesToAdd; + Set NewFeatures, NewCov; + CrashResistantMerge(Args, {}, MergeCandidates, &FilesToAdd, Features, + &NewFeatures, Cov, &NewCov, Job->CFPath, false); + for (auto &Path : FilesToAdd) { + auto U = FileToVector(Path); + auto NewPath = DirPlusFile(MainCorpusDir, Hash(U)); + WriteToFile(U, NewPath); + Files.push_back(NewPath); + } + Features.insert(NewFeatures.begin(), NewFeatures.end()); + Cov.insert(NewCov.begin(), NewCov.end()); + for (auto Idx : NewCov) + if (auto *TE = TPC.PCTableEntryByIdx(Idx)) + if (TPC.PcIsFuncEntry(TE)) + PrintPC(" NEW_FUNC: %p %F %L\n", "", + TPC.GetNextInstructionPc(TE->PC)); + + } + + + void CollectDFT(const std::string &InputPath) { + if (DataFlowBinary.empty()) return; + if (!FilesWithDFT.insert(InputPath).second) return; + Command Cmd(Args); + Cmd.removeFlag("fork"); + Cmd.removeFlag("runs"); + Cmd.addFlag("data_flow_trace", DFTDir); + Cmd.addArgument(InputPath); + for (auto &C : CorpusDirs) // Remove all corpora from the args. + Cmd.removeArgument(C); + Cmd.setOutputFile(DirPlusFile(TempDir, "dft.log")); + Cmd.combineOutAndErr(); + // Printf("CollectDFT: %s\n", Cmd.toString().c_str()); + ExecuteCommand(Cmd); + } + +}; + +struct JobQueue { + std::queue Qu; + std::mutex Mu; + std::condition_variable Cv; + + void Push(FuzzJob *Job) { + { + std::lock_guard Lock(Mu); + Qu.push(Job); + } + Cv.notify_one(); + } + FuzzJob *Pop() { + std::unique_lock Lk(Mu); + // std::lock_guard Lock(Mu); + Cv.wait(Lk, [&]{return !Qu.empty();}); + assert(!Qu.empty()); + auto Job = Qu.front(); + Qu.pop(); + return Job; + } +}; + +void WorkerThread(JobQueue *FuzzQ, JobQueue *MergeQ) { + while (auto Job = FuzzQ->Pop()) { + // Printf("WorkerThread: job %p\n", Job); + Job->ExitCode = ExecuteCommand(Job->Cmd); + MergeQ->Push(Job); + } +} + +// This is just a skeleton of an experimental -fork=1 feature. +void FuzzWithFork(Random &Rand, const FuzzingOptions &Options, + const Vector &Args, + const Vector &CorpusDirs, int NumJobs) { + Printf("INFO: -fork=%d: fuzzing in separate process(s)\n", NumJobs); + + GlobalEnv Env; + Env.Args = Args; + Env.CorpusDirs = CorpusDirs; + Env.Rand = &Rand; + Env.Verbosity = Options.Verbosity; + Env.ProcessStartTime = std::chrono::system_clock::now(); + Env.DataFlowBinary = Options.CollectDataFlow; + + Vector SeedFiles; + for (auto &Dir : CorpusDirs) + GetSizedFilesFromDir(Dir, &SeedFiles); + std::sort(SeedFiles.begin(), SeedFiles.end()); + Env.TempDir = TempPath("FuzzWithFork", ".dir"); + Env.DFTDir = DirPlusFile(Env.TempDir, "DFT"); + RmDirRecursive(Env.TempDir); // in case there is a leftover from old runs. + MkDir(Env.TempDir); + MkDir(Env.DFTDir); + + + if (CorpusDirs.empty()) + MkDir(Env.MainCorpusDir = DirPlusFile(Env.TempDir, "C")); + else + Env.MainCorpusDir = CorpusDirs[0]; + + if (Options.KeepSeed) { + for (auto &File : SeedFiles) + Env.Files.push_back(File.File); + } else { + auto CFPath = DirPlusFile(Env.TempDir, "merge.txt"); + Set NewFeatures, NewCov; + CrashResistantMerge(Env.Args, {}, SeedFiles, &Env.Files, Env.Features, + &NewFeatures, Env.Cov, &NewCov, CFPath, false); + Env.Features.insert(NewFeatures.begin(), NewFeatures.end()); + Env.Cov.insert(NewFeatures.begin(), NewFeatures.end()); + RemoveFile(CFPath); + } + Printf("INFO: -fork=%d: %zd seed inputs, starting to fuzz in %s\n", NumJobs, + Env.Files.size(), Env.TempDir.c_str()); + + int ExitCode = 0; + + JobQueue FuzzQ, MergeQ; + + auto StopJobs = [&]() { + for (int i = 0; i < NumJobs; i++) + FuzzQ.Push(nullptr); + MergeQ.Push(nullptr); + WriteToFile(Unit({1}), Env.StopFile()); + }; + + size_t JobId = 1; + Vector Threads; + for (int t = 0; t < NumJobs; t++) { + Threads.push_back(std::thread(WorkerThread, &FuzzQ, &MergeQ)); + FuzzQ.Push(Env.CreateNewJob(JobId++)); + } + + while (true) { + std::unique_ptr Job(MergeQ.Pop()); + if (!Job) + break; + ExitCode = Job->ExitCode; + if (ExitCode == Options.InterruptExitCode) { + Printf("==%lu== libFuzzer: a child was interrupted; exiting\n", GetPid()); + StopJobs(); + break; + } + Fuzzer::MaybeExitGracefully(); + + Env.RunOneMergeJob(Job.get()); + + // Continue if our crash is one of the ignorred ones. + if (Options.IgnoreTimeouts && ExitCode == Options.TimeoutExitCode) + Env.NumTimeouts++; + else if (Options.IgnoreOOMs && ExitCode == Options.OOMExitCode) + Env.NumOOMs++; + else if (ExitCode != 0) { + Env.NumCrashes++; + if (Options.IgnoreCrashes) { + std::ifstream In(Job->LogPath); + std::string Line; + while (std::getline(In, Line, '\n')) + if (Line.find("ERROR:") != Line.npos || + Line.find("runtime error:") != Line.npos) + Printf("%s\n", Line.c_str()); + } else { + // And exit if we don't ignore this crash. + Printf("INFO: log from the inner process:\n%s", + FileToString(Job->LogPath).c_str()); + StopJobs(); + break; + } + } + + // Stop if we are over the time budget. + // This is not precise, since other threads are still running + // and we will wait while joining them. + // We also don't stop instantly: other jobs need to finish. + if (Options.MaxTotalTimeSec > 0 && + Env.secondsSinceProcessStartUp() >= (size_t)Options.MaxTotalTimeSec) { + Printf("INFO: fuzzed for %zd seconds, wrapping up soon\n", + Env.secondsSinceProcessStartUp()); + StopJobs(); + break; + } + if (Env.NumRuns >= Options.MaxNumberOfRuns) { + Printf("INFO: fuzzed for %zd iterations, wrapping up soon\n", + Env.NumRuns); + StopJobs(); + break; + } + + FuzzQ.Push(Env.CreateNewJob(JobId++)); + } + + for (auto &T : Threads) + T.join(); + + // The workers have terminated. Don't try to remove the directory before they + // terminate to avoid a race condition preventing cleanup on Windows. + RmDirRecursive(Env.TempDir); + + // Use the exit code from the last child process. + Printf("INFO: exiting: %d time: %zds\n", ExitCode, + Env.secondsSinceProcessStartUp()); + exit(ExitCode); +} + +} // namespace fuzzer diff --git a/core/lib/libfuzzer-sys/libfuzzer/FuzzerFork.h b/core/lib/libfuzzer-sys/libfuzzer/FuzzerFork.h new file mode 100644 index 0000000000..b29a43e13f --- /dev/null +++ b/core/lib/libfuzzer-sys/libfuzzer/FuzzerFork.h @@ -0,0 +1,24 @@ +//===- FuzzerFork.h - run fuzzing in sub-processes --------------*- C++ -* ===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_FUZZER_FORK_H +#define LLVM_FUZZER_FORK_H + +#include "FuzzerDefs.h" +#include "FuzzerOptions.h" +#include "FuzzerRandom.h" + +#include + +namespace fuzzer { +void FuzzWithFork(Random &Rand, const FuzzingOptions &Options, + const Vector &Args, + const Vector &CorpusDirs, int NumJobs); +} // namespace fuzzer + +#endif // LLVM_FUZZER_FORK_H diff --git a/core/lib/libfuzzer-sys/libfuzzer/FuzzerIO.cpp b/core/lib/libfuzzer-sys/libfuzzer/FuzzerIO.cpp new file mode 100644 index 0000000000..7f149ac6c4 --- /dev/null +++ b/core/lib/libfuzzer-sys/libfuzzer/FuzzerIO.cpp @@ -0,0 +1,207 @@ +//===- FuzzerIO.cpp - IO utils. -------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// IO functions. +//===----------------------------------------------------------------------===// + +#include "FuzzerDefs.h" +#include "FuzzerExtFunctions.h" +#include "FuzzerIO.h" +#include "FuzzerUtil.h" +#include +#include +#include +#include +#include +#include + +namespace fuzzer { + +static FILE *OutputFile = stderr; + +long GetEpoch(const std::string &Path) { + struct stat St; + if (stat(Path.c_str(), &St)) + return 0; // Can't stat, be conservative. + return St.st_mtime; +} + +Unit FileToVector(const std::string &Path, size_t MaxSize, bool ExitOnError) { + std::ifstream T(Path, std::ios::binary); + if (ExitOnError && !T) { + Printf("No such directory: %s; exiting\n", Path.c_str()); + exit(1); + } + + T.seekg(0, T.end); + auto EndPos = T.tellg(); + if (EndPos < 0) return {}; + size_t FileLen = EndPos; + if (MaxSize) + FileLen = std::min(FileLen, MaxSize); + + T.seekg(0, T.beg); + Unit Res(FileLen); + T.read(reinterpret_cast(Res.data()), FileLen); + return Res; +} + +std::string FileToString(const std::string &Path) { + std::ifstream T(Path, std::ios::binary); + return std::string((std::istreambuf_iterator(T)), + std::istreambuf_iterator()); +} + +void CopyFileToErr(const std::string &Path) { + Printf("%s", FileToString(Path).c_str()); +} + +void WriteToFile(const Unit &U, const std::string &Path) { + WriteToFile(U.data(), U.size(), Path); +} + +void WriteToFile(const std::string &Data, const std::string &Path) { + WriteToFile(reinterpret_cast(Data.c_str()), Data.size(), + Path); +} + +void WriteToFile(const uint8_t *Data, size_t Size, const std::string &Path) { + // Use raw C interface because this function may be called from a sig handler. + FILE *Out = fopen(Path.c_str(), "wb"); + if (!Out) return; + fwrite(Data, sizeof(Data[0]), Size, Out); + fclose(Out); +} + +void AppendToFile(const std::string &Data, const std::string &Path) { + AppendToFile(reinterpret_cast(Data.data()), Data.size(), + Path); +} + +void AppendToFile(const uint8_t *Data, size_t Size, const std::string &Path) { + FILE *Out = fopen(Path.c_str(), "a"); + if (!Out) + return; + fwrite(Data, sizeof(Data[0]), Size, Out); + fclose(Out); +} + +void ReadDirToVectorOfUnits(const char *Path, Vector *V, long *Epoch, + size_t MaxSize, bool ExitOnError, + Vector *VPaths) { + long E = Epoch ? *Epoch : 0; + Vector Files; + ListFilesInDirRecursive(Path, Epoch, &Files, /*TopDir*/true); + size_t NumLoaded = 0; + for (size_t i = 0; i < Files.size(); i++) { + auto &X = Files[i]; + if (Epoch && GetEpoch(X) < E) continue; + NumLoaded++; + if ((NumLoaded & (NumLoaded - 1)) == 0 && NumLoaded >= 1024) + Printf("Loaded %zd/%zd files from %s\n", NumLoaded, Files.size(), Path); + auto S = FileToVector(X, MaxSize, ExitOnError); + if (!S.empty()) { + V->push_back(S); + if (VPaths) + VPaths->push_back(X); + } + } +} + +void GetSizedFilesFromDir(const std::string &Dir, Vector *V) { + Vector Files; + ListFilesInDirRecursive(Dir, 0, &Files, /*TopDir*/true); + for (auto &File : Files) + if (size_t Size = FileSize(File)) + V->push_back({File, Size}); +} + +std::string DirPlusFile(const std::string &DirPath, + const std::string &FileName) { + return DirPath + GetSeparator() + FileName; +} + +void DupAndCloseStderr() { + int OutputFd = DuplicateFile(2); + if (OutputFd >= 0) { + FILE *NewOutputFile = OpenFile(OutputFd, "w"); + if (NewOutputFile) { + OutputFile = NewOutputFile; + if (EF->__sanitizer_set_report_fd) + EF->__sanitizer_set_report_fd( + reinterpret_cast(GetHandleFromFd(OutputFd))); + DiscardOutput(2); + } + } +} + +void CloseStdout() { + DiscardOutput(1); +} + +void Printf(const char *Fmt, ...) { + va_list ap; + va_start(ap, Fmt); + vfprintf(OutputFile, Fmt, ap); + va_end(ap); + fflush(OutputFile); +} + +void VPrintf(bool Verbose, const char *Fmt, ...) { + if (!Verbose) return; + va_list ap; + va_start(ap, Fmt); + vfprintf(OutputFile, Fmt, ap); + va_end(ap); + fflush(OutputFile); +} + +static bool MkDirRecursiveInner(const std::string &Leaf) { + // Prevent chance of potential infinite recursion + if (Leaf == ".") + return true; + + const std::string &Dir = DirName(Leaf); + + if (IsDirectory(Dir)) { + MkDir(Leaf); + return IsDirectory(Leaf); + } + + bool ret = MkDirRecursiveInner(Dir); + if (!ret) { + // Give up early if a previous MkDir failed + return ret; + } + + MkDir(Leaf); + return IsDirectory(Leaf); +} + +bool MkDirRecursive(const std::string &Dir) { + if (Dir.empty()) + return false; + + if (IsDirectory(Dir)) + return true; + + return MkDirRecursiveInner(Dir); +} + +void RmDirRecursive(const std::string &Dir) { + IterateDirRecursive( + Dir, [](const std::string &Path) {}, + [](const std::string &Path) { RmDir(Path); }, + [](const std::string &Path) { RemoveFile(Path); }); +} + +std::string TempPath(const char *Prefix, const char *Extension) { + return DirPlusFile(TmpDir(), std::string("libFuzzerTemp.") + Prefix + + std::to_string(GetPid()) + Extension); +} + +} // namespace fuzzer diff --git a/core/lib/libfuzzer-sys/libfuzzer/FuzzerIO.h b/core/lib/libfuzzer-sys/libfuzzer/FuzzerIO.h new file mode 100644 index 0000000000..bde18267ea --- /dev/null +++ b/core/lib/libfuzzer-sys/libfuzzer/FuzzerIO.h @@ -0,0 +1,113 @@ +//===- FuzzerIO.h - Internal header for IO utils ----------------*- C++ -* ===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// IO interface. +//===----------------------------------------------------------------------===// + +#ifndef LLVM_FUZZER_IO_H +#define LLVM_FUZZER_IO_H + +#include "FuzzerDefs.h" + +namespace fuzzer { + +long GetEpoch(const std::string &Path); + +Unit FileToVector(const std::string &Path, size_t MaxSize = 0, + bool ExitOnError = true); + +std::string FileToString(const std::string &Path); + +void CopyFileToErr(const std::string &Path); + +void WriteToFile(const uint8_t *Data, size_t Size, const std::string &Path); +// Write Data.c_str() to the file without terminating null character. +void WriteToFile(const std::string &Data, const std::string &Path); +void WriteToFile(const Unit &U, const std::string &Path); + +void AppendToFile(const uint8_t *Data, size_t Size, const std::string &Path); +void AppendToFile(const std::string &Data, const std::string &Path); + +void ReadDirToVectorOfUnits(const char *Path, Vector *V, long *Epoch, + size_t MaxSize, bool ExitOnError, + Vector *VPaths = 0); + +// Returns "Dir/FileName" or equivalent for the current OS. +std::string DirPlusFile(const std::string &DirPath, + const std::string &FileName); + +// Returns the name of the dir, similar to the 'dirname' utility. +std::string DirName(const std::string &FileName); + +// Returns path to a TmpDir. +std::string TmpDir(); + +std::string TempPath(const char *Prefix, const char *Extension); + +bool IsInterestingCoverageFile(const std::string &FileName); + +void DupAndCloseStderr(); + +void CloseStdout(); + +void Printf(const char *Fmt, ...); +void VPrintf(bool Verbose, const char *Fmt, ...); + +// Print using raw syscalls, useful when printing at early init stages. +void RawPrint(const char *Str); + +// Platform specific functions: +bool IsFile(const std::string &Path); +bool IsDirectory(const std::string &Path); +size_t FileSize(const std::string &Path); + +void ListFilesInDirRecursive(const std::string &Dir, long *Epoch, + Vector *V, bool TopDir); + +bool MkDirRecursive(const std::string &Dir); +void RmDirRecursive(const std::string &Dir); + +// Iterate files and dirs inside Dir, recursively. +// Call DirPreCallback/DirPostCallback on dirs before/after +// calling FileCallback on files. +void IterateDirRecursive(const std::string &Dir, + void (*DirPreCallback)(const std::string &Dir), + void (*DirPostCallback)(const std::string &Dir), + void (*FileCallback)(const std::string &Dir)); + +struct SizedFile { + std::string File; + size_t Size; + bool operator<(const SizedFile &B) const { return Size < B.Size; } +}; + +void GetSizedFilesFromDir(const std::string &Dir, Vector *V); + +char GetSeparator(); +bool IsSeparator(char C); +// Similar to the basename utility: returns the file name w/o the dir prefix. +std::string Basename(const std::string &Path); + +FILE* OpenFile(int Fd, const char *Mode); + +int CloseFile(int Fd); + +int DuplicateFile(int Fd); + +void RemoveFile(const std::string &Path); +void RenameFile(const std::string &OldPath, const std::string &NewPath); + +intptr_t GetHandleFromFd(int fd); + +void MkDir(const std::string &Path); +void RmDir(const std::string &Path); + +const std::string &getDevNull(); + +} // namespace fuzzer + +#endif // LLVM_FUZZER_IO_H diff --git a/core/lib/libfuzzer-sys/libfuzzer/FuzzerIOPosix.cpp b/core/lib/libfuzzer-sys/libfuzzer/FuzzerIOPosix.cpp new file mode 100644 index 0000000000..4706a40959 --- /dev/null +++ b/core/lib/libfuzzer-sys/libfuzzer/FuzzerIOPosix.cpp @@ -0,0 +1,180 @@ +//===- FuzzerIOPosix.cpp - IO utils for Posix. ----------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// IO functions implementation using Posix API. +//===----------------------------------------------------------------------===// +#include "FuzzerPlatform.h" +#if LIBFUZZER_POSIX || LIBFUZZER_FUCHSIA + +#include "FuzzerExtFunctions.h" +#include "FuzzerIO.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace fuzzer { + +bool IsFile(const std::string &Path) { + struct stat St; + if (stat(Path.c_str(), &St)) + return false; + return S_ISREG(St.st_mode); +} + +bool IsDirectory(const std::string &Path) { + struct stat St; + if (stat(Path.c_str(), &St)) + return false; + return S_ISDIR(St.st_mode); +} + +size_t FileSize(const std::string &Path) { + struct stat St; + if (stat(Path.c_str(), &St)) + return 0; + return St.st_size; +} + +std::string Basename(const std::string &Path) { + size_t Pos = Path.rfind(GetSeparator()); + if (Pos == std::string::npos) return Path; + assert(Pos < Path.size()); + return Path.substr(Pos + 1); +} + +void ListFilesInDirRecursive(const std::string &Dir, long *Epoch, + Vector *V, bool TopDir) { + auto E = GetEpoch(Dir); + if (Epoch) + if (E && *Epoch >= E) return; + + DIR *D = opendir(Dir.c_str()); + if (!D) { + Printf("%s: %s; exiting\n", strerror(errno), Dir.c_str()); + exit(1); + } + while (auto E = readdir(D)) { + std::string Path = DirPlusFile(Dir, E->d_name); + if (E->d_type == DT_REG || E->d_type == DT_LNK || + (E->d_type == DT_UNKNOWN && IsFile(Path))) + V->push_back(Path); + else if ((E->d_type == DT_DIR || + (E->d_type == DT_UNKNOWN && IsDirectory(Path))) && + *E->d_name != '.') + ListFilesInDirRecursive(Path, Epoch, V, false); + } + closedir(D); + if (Epoch && TopDir) + *Epoch = E; +} + + +void IterateDirRecursive(const std::string &Dir, + void (*DirPreCallback)(const std::string &Dir), + void (*DirPostCallback)(const std::string &Dir), + void (*FileCallback)(const std::string &Dir)) { + DirPreCallback(Dir); + DIR *D = opendir(Dir.c_str()); + if (!D) return; + while (auto E = readdir(D)) { + std::string Path = DirPlusFile(Dir, E->d_name); + if (E->d_type == DT_REG || E->d_type == DT_LNK || + (E->d_type == DT_UNKNOWN && IsFile(Path))) + FileCallback(Path); + else if ((E->d_type == DT_DIR || + (E->d_type == DT_UNKNOWN && IsDirectory(Path))) && + *E->d_name != '.') + IterateDirRecursive(Path, DirPreCallback, DirPostCallback, FileCallback); + } + closedir(D); + DirPostCallback(Dir); +} + +char GetSeparator() { + return '/'; +} + +bool IsSeparator(char C) { + return C == '/'; +} + +FILE* OpenFile(int Fd, const char* Mode) { + return fdopen(Fd, Mode); +} + +int CloseFile(int fd) { + return close(fd); +} + +int DuplicateFile(int Fd) { + return dup(Fd); +} + +void RemoveFile(const std::string &Path) { + unlink(Path.c_str()); +} + +void RenameFile(const std::string &OldPath, const std::string &NewPath) { + rename(OldPath.c_str(), NewPath.c_str()); +} + +intptr_t GetHandleFromFd(int fd) { + return static_cast(fd); +} + +std::string DirName(const std::string &FileName) { + char *Tmp = new char[FileName.size() + 1]; + memcpy(Tmp, FileName.c_str(), FileName.size() + 1); + std::string Res = dirname(Tmp); + delete [] Tmp; + return Res; +} + +std::string TmpDir() { + if (auto Env = getenv("TMPDIR")) + return Env; + return "/tmp"; +} + +bool IsInterestingCoverageFile(const std::string &FileName) { + if (FileName.find("compiler-rt/lib/") != std::string::npos) + return false; // sanitizer internal. + if (FileName.find("/usr/lib/") != std::string::npos) + return false; + if (FileName.find("/usr/include/") != std::string::npos) + return false; + if (FileName == "") + return false; + return true; +} + +void RawPrint(const char *Str) { + (void)write(2, Str, strlen(Str)); +} + +void MkDir(const std::string &Path) { + mkdir(Path.c_str(), 0700); +} + +void RmDir(const std::string &Path) { + rmdir(Path.c_str()); +} + +const std::string &getDevNull() { + static const std::string devNull = "/dev/null"; + return devNull; +} + +} // namespace fuzzer + +#endif // LIBFUZZER_POSIX diff --git a/core/lib/libfuzzer-sys/libfuzzer/FuzzerIOWindows.cpp b/core/lib/libfuzzer-sys/libfuzzer/FuzzerIOWindows.cpp new file mode 100644 index 0000000000..61ad35e281 --- /dev/null +++ b/core/lib/libfuzzer-sys/libfuzzer/FuzzerIOWindows.cpp @@ -0,0 +1,425 @@ +//===- FuzzerIOWindows.cpp - IO utils for Windows. ------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// IO functions implementation for Windows. +//===----------------------------------------------------------------------===// +#include "FuzzerPlatform.h" +#if LIBFUZZER_WINDOWS + +#include "FuzzerExtFunctions.h" +#include "FuzzerIO.h" +#include +#include +#include +#include +#include +#include +#include +#include + +namespace fuzzer { + +static bool IsFile(const std::string &Path, const DWORD &FileAttributes) { + + if (FileAttributes & FILE_ATTRIBUTE_NORMAL) + return true; + + if (FileAttributes & FILE_ATTRIBUTE_DIRECTORY) + return false; + + HANDLE FileHandle( + CreateFileA(Path.c_str(), 0, FILE_SHARE_READ, NULL, OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, 0)); + + if (FileHandle == INVALID_HANDLE_VALUE) { + Printf("CreateFileA() failed for \"%s\" (Error code: %lu).\n", Path.c_str(), + GetLastError()); + return false; + } + + DWORD FileType = GetFileType(FileHandle); + + if (FileType == FILE_TYPE_UNKNOWN) { + Printf("GetFileType() failed for \"%s\" (Error code: %lu).\n", Path.c_str(), + GetLastError()); + CloseHandle(FileHandle); + return false; + } + + if (FileType != FILE_TYPE_DISK) { + CloseHandle(FileHandle); + return false; + } + + CloseHandle(FileHandle); + return true; +} + +bool IsFile(const std::string &Path) { + DWORD Att = GetFileAttributesA(Path.c_str()); + + if (Att == INVALID_FILE_ATTRIBUTES) { + Printf("GetFileAttributesA() failed for \"%s\" (Error code: %lu).\n", + Path.c_str(), GetLastError()); + return false; + } + + return IsFile(Path, Att); +} + +static bool IsDir(DWORD FileAttrs) { + if (FileAttrs == INVALID_FILE_ATTRIBUTES) return false; + return FileAttrs & FILE_ATTRIBUTE_DIRECTORY; +} + +bool IsDirectory(const std::string &Path) { + DWORD Att = GetFileAttributesA(Path.c_str()); + + if (Att == INVALID_FILE_ATTRIBUTES) { + Printf("GetFileAttributesA() failed for \"%s\" (Error code: %lu).\n", + Path.c_str(), GetLastError()); + return false; + } + + return IsDir(Att); +} + +std::string Basename(const std::string &Path) { + size_t Pos = Path.find_last_of("/\\"); + if (Pos == std::string::npos) return Path; + assert(Pos < Path.size()); + return Path.substr(Pos + 1); +} + +size_t FileSize(const std::string &Path) { + WIN32_FILE_ATTRIBUTE_DATA attr; + if (!GetFileAttributesExA(Path.c_str(), GetFileExInfoStandard, &attr)) { + DWORD LastError = GetLastError(); + if (LastError != ERROR_FILE_NOT_FOUND) + Printf("GetFileAttributesExA() failed for \"%s\" (Error code: %lu).\n", + Path.c_str(), LastError); + return 0; + } + ULARGE_INTEGER size; + size.HighPart = attr.nFileSizeHigh; + size.LowPart = attr.nFileSizeLow; + return size.QuadPart; +} + +void ListFilesInDirRecursive(const std::string &Dir, long *Epoch, + Vector *V, bool TopDir) { + auto E = GetEpoch(Dir); + if (Epoch) + if (E && *Epoch >= E) return; + + std::string Path(Dir); + assert(!Path.empty()); + if (Path.back() != '\\') + Path.push_back('\\'); + Path.push_back('*'); + + // Get the first directory entry. + WIN32_FIND_DATAA FindInfo; + HANDLE FindHandle(FindFirstFileA(Path.c_str(), &FindInfo)); + if (FindHandle == INVALID_HANDLE_VALUE) + { + if (GetLastError() == ERROR_FILE_NOT_FOUND) + return; + Printf("No such file or directory: %s; exiting\n", Dir.c_str()); + exit(1); + } + + do { + std::string FileName = DirPlusFile(Dir, FindInfo.cFileName); + + if (FindInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + size_t FilenameLen = strlen(FindInfo.cFileName); + if ((FilenameLen == 1 && FindInfo.cFileName[0] == '.') || + (FilenameLen == 2 && FindInfo.cFileName[0] == '.' && + FindInfo.cFileName[1] == '.')) + continue; + + ListFilesInDirRecursive(FileName, Epoch, V, false); + } + else if (IsFile(FileName, FindInfo.dwFileAttributes)) + V->push_back(FileName); + } while (FindNextFileA(FindHandle, &FindInfo)); + + DWORD LastError = GetLastError(); + if (LastError != ERROR_NO_MORE_FILES) + Printf("FindNextFileA failed (Error code: %lu).\n", LastError); + + FindClose(FindHandle); + + if (Epoch && TopDir) + *Epoch = E; +} + + +void IterateDirRecursive(const std::string &Dir, + void (*DirPreCallback)(const std::string &Dir), + void (*DirPostCallback)(const std::string &Dir), + void (*FileCallback)(const std::string &Dir)) { + // TODO(metzman): Implement ListFilesInDirRecursive via this function. + DirPreCallback(Dir); + + DWORD DirAttrs = GetFileAttributesA(Dir.c_str()); + if (!IsDir(DirAttrs)) return; + + std::string TargetDir(Dir); + assert(!TargetDir.empty()); + if (TargetDir.back() != '\\') TargetDir.push_back('\\'); + TargetDir.push_back('*'); + + WIN32_FIND_DATAA FindInfo; + // Find the directory's first file. + HANDLE FindHandle = FindFirstFileA(TargetDir.c_str(), &FindInfo); + if (FindHandle == INVALID_HANDLE_VALUE) { + DWORD LastError = GetLastError(); + if (LastError != ERROR_FILE_NOT_FOUND) { + // If the directory isn't empty, then something abnormal is going on. + Printf("FindFirstFileA failed for %s (Error code: %lu).\n", Dir.c_str(), + LastError); + } + return; + } + + do { + std::string Path = DirPlusFile(Dir, FindInfo.cFileName); + DWORD PathAttrs = FindInfo.dwFileAttributes; + if (IsDir(PathAttrs)) { + // Is Path the current directory (".") or the parent ("..")? + if (strcmp(FindInfo.cFileName, ".") == 0 || + strcmp(FindInfo.cFileName, "..") == 0) + continue; + IterateDirRecursive(Path, DirPreCallback, DirPostCallback, FileCallback); + } else if (PathAttrs != INVALID_FILE_ATTRIBUTES) { + FileCallback(Path); + } + } while (FindNextFileA(FindHandle, &FindInfo)); + + DWORD LastError = GetLastError(); + if (LastError != ERROR_NO_MORE_FILES) + Printf("FindNextFileA failed for %s (Error code: %lu).\n", Dir.c_str(), + LastError); + + FindClose(FindHandle); + DirPostCallback(Dir); +} + +char GetSeparator() { + return '\\'; +} + +FILE* OpenFile(int Fd, const char* Mode) { + return _fdopen(Fd, Mode); +} + +int CloseFile(int Fd) { + return _close(Fd); +} + +int DuplicateFile(int Fd) { + return _dup(Fd); +} + +void RemoveFile(const std::string &Path) { + _unlink(Path.c_str()); +} + +void RenameFile(const std::string &OldPath, const std::string &NewPath) { + rename(OldPath.c_str(), NewPath.c_str()); +} + +intptr_t GetHandleFromFd(int fd) { + return _get_osfhandle(fd); +} + +bool IsSeparator(char C) { + return C == '\\' || C == '/'; +} + +// Parse disk designators, like "C:\". If Relative == true, also accepts: "C:". +// Returns number of characters considered if successful. +static size_t ParseDrive(const std::string &FileName, const size_t Offset, + bool Relative = true) { + if (Offset + 1 >= FileName.size() || FileName[Offset + 1] != ':') + return 0; + if (Offset + 2 >= FileName.size() || !IsSeparator(FileName[Offset + 2])) { + if (!Relative) // Accept relative path? + return 0; + else + return 2; + } + return 3; +} + +// Parse a file name, like: SomeFile.txt +// Returns number of characters considered if successful. +static size_t ParseFileName(const std::string &FileName, const size_t Offset) { + size_t Pos = Offset; + const size_t End = FileName.size(); + for(; Pos < End && !IsSeparator(FileName[Pos]); ++Pos) + ; + return Pos - Offset; +} + +// Parse a directory ending in separator, like: `SomeDir\` +// Returns number of characters considered if successful. +static size_t ParseDir(const std::string &FileName, const size_t Offset) { + size_t Pos = Offset; + const size_t End = FileName.size(); + if (Pos >= End || IsSeparator(FileName[Pos])) + return 0; + for(; Pos < End && !IsSeparator(FileName[Pos]); ++Pos) + ; + if (Pos >= End) + return 0; + ++Pos; // Include separator. + return Pos - Offset; +} + +// Parse a servername and share, like: `SomeServer\SomeShare\` +// Returns number of characters considered if successful. +static size_t ParseServerAndShare(const std::string &FileName, + const size_t Offset) { + size_t Pos = Offset, Res; + if (!(Res = ParseDir(FileName, Pos))) + return 0; + Pos += Res; + if (!(Res = ParseDir(FileName, Pos))) + return 0; + Pos += Res; + return Pos - Offset; +} + +// Parse the given Ref string from the position Offset, to exactly match the given +// string Patt. +// Returns number of characters considered if successful. +static size_t ParseCustomString(const std::string &Ref, size_t Offset, + const char *Patt) { + size_t Len = strlen(Patt); + if (Offset + Len > Ref.size()) + return 0; + return Ref.compare(Offset, Len, Patt) == 0 ? Len : 0; +} + +// Parse a location, like: +// \\?\UNC\Server\Share\ \\?\C:\ \\Server\Share\ \ C:\ C: +// Returns number of characters considered if successful. +static size_t ParseLocation(const std::string &FileName) { + size_t Pos = 0, Res; + + if ((Res = ParseCustomString(FileName, Pos, R"(\\?\)"))) { + Pos += Res; + if ((Res = ParseCustomString(FileName, Pos, R"(UNC\)"))) { + Pos += Res; + if ((Res = ParseServerAndShare(FileName, Pos))) + return Pos + Res; + return 0; + } + if ((Res = ParseDrive(FileName, Pos, false))) + return Pos + Res; + return 0; + } + + if (Pos < FileName.size() && IsSeparator(FileName[Pos])) { + ++Pos; + if (Pos < FileName.size() && IsSeparator(FileName[Pos])) { + ++Pos; + if ((Res = ParseServerAndShare(FileName, Pos))) + return Pos + Res; + return 0; + } + return Pos; + } + + if ((Res = ParseDrive(FileName, Pos))) + return Pos + Res; + + return Pos; +} + +std::string DirName(const std::string &FileName) { + size_t LocationLen = ParseLocation(FileName); + size_t DirLen = 0, Res; + while ((Res = ParseDir(FileName, LocationLen + DirLen))) + DirLen += Res; + size_t FileLen = ParseFileName(FileName, LocationLen + DirLen); + + if (LocationLen + DirLen + FileLen != FileName.size()) { + Printf("DirName() failed for \"%s\", invalid path.\n", FileName.c_str()); + exit(1); + } + + if (DirLen) { + --DirLen; // Remove trailing separator. + if (!FileLen) { // Path ended in separator. + assert(DirLen); + // Remove file name from Dir. + while (DirLen && !IsSeparator(FileName[LocationLen + DirLen - 1])) + --DirLen; + if (DirLen) // Remove trailing separator. + --DirLen; + } + } + + if (!LocationLen) { // Relative path. + if (!DirLen) + return "."; + return std::string(".\\").append(FileName, 0, DirLen); + } + + return FileName.substr(0, LocationLen + DirLen); +} + +std::string TmpDir() { + std::string Tmp; + Tmp.resize(MAX_PATH + 1); + DWORD Size = GetTempPathA(Tmp.size(), &Tmp[0]); + if (Size == 0) { + Printf("Couldn't get Tmp path.\n"); + exit(1); + } + Tmp.resize(Size); + return Tmp; +} + +bool IsInterestingCoverageFile(const std::string &FileName) { + if (FileName.find("Program Files") != std::string::npos) + return false; + if (FileName.find("compiler-rt\\lib\\") != std::string::npos) + return false; // sanitizer internal. + if (FileName == "") + return false; + return true; +} + +void RawPrint(const char *Str) { + _write(2, Str, strlen(Str)); +} + +void MkDir(const std::string &Path) { + if (CreateDirectoryA(Path.c_str(), nullptr)) return; + Printf("CreateDirectoryA failed for %s (Error code: %lu).\n", Path.c_str(), + GetLastError()); +} + +void RmDir(const std::string &Path) { + if (RemoveDirectoryA(Path.c_str())) return; + Printf("RemoveDirectoryA failed for %s (Error code: %lu).\n", Path.c_str(), + GetLastError()); +} + +const std::string &getDevNull() { + static const std::string devNull = "NUL"; + return devNull; +} + +} // namespace fuzzer + +#endif // LIBFUZZER_WINDOWS diff --git a/core/lib/libfuzzer-sys/libfuzzer/FuzzerInterceptors.cpp b/core/lib/libfuzzer-sys/libfuzzer/FuzzerInterceptors.cpp new file mode 100644 index 0000000000..d5b0a42fd3 --- /dev/null +++ b/core/lib/libfuzzer-sys/libfuzzer/FuzzerInterceptors.cpp @@ -0,0 +1,254 @@ +//===-- FuzzerInterceptors.cpp --------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// Intercept certain libc functions to aid fuzzing. +// Linked only when other RTs that define their own interceptors are not linked. +//===----------------------------------------------------------------------===// + +#include "FuzzerPlatform.h" + +#if LIBFUZZER_LINUX + +#define GET_CALLER_PC() __builtin_return_address(0) + +#define PTR_TO_REAL(x) real_##x +#define REAL(x) __interception::PTR_TO_REAL(x) +#define FUNC_TYPE(x) x##_type +#define DEFINE_REAL(ret_type, func, ...) \ + typedef ret_type (*FUNC_TYPE(func))(__VA_ARGS__); \ + namespace __interception { \ + FUNC_TYPE(func) PTR_TO_REAL(func); \ + } + +#include +#include // for size_t +#include +#include // for dlsym() + +static void *getFuncAddr(const char *name, uintptr_t wrapper_addr) { + void *addr = dlsym(RTLD_NEXT, name); + if (!addr) { + // If the lookup using RTLD_NEXT failed, the sanitizer runtime library is + // later in the library search order than the DSO that we are trying to + // intercept, which means that we cannot intercept this function. We still + // want the address of the real definition, though, so look it up using + // RTLD_DEFAULT. + addr = dlsym(RTLD_DEFAULT, name); + + // In case `name' is not loaded, dlsym ends up finding the actual wrapper. + // We don't want to intercept the wrapper and have it point to itself. + if (reinterpret_cast(addr) == wrapper_addr) + addr = nullptr; + } + return addr; +} + +static int FuzzerInited = 0; +static bool FuzzerInitIsRunning; + +static void fuzzerInit(); + +static void ensureFuzzerInited() { + assert(!FuzzerInitIsRunning); + if (!FuzzerInited) { + fuzzerInit(); + } +} + +static int internal_strcmp_strncmp(const char *s1, const char *s2, bool strncmp, + size_t n) { + size_t i = 0; + while (true) { + if (strncmp) { + if (i == n) + break; + i++; + } + unsigned c1 = *s1; + unsigned c2 = *s2; + if (c1 != c2) + return (c1 < c2) ? -1 : 1; + if (c1 == 0) + break; + s1++; + s2++; + } + return 0; +} + +static int internal_strncmp(const char *s1, const char *s2, size_t n) { + return internal_strcmp_strncmp(s1, s2, true, n); +} + +static int internal_strcmp(const char *s1, const char *s2) { + return internal_strcmp_strncmp(s1, s2, false, 0); +} + +static int internal_memcmp(const void *s1, const void *s2, size_t n) { + const uint8_t *t1 = static_cast(s1); + const uint8_t *t2 = static_cast(s2); + for (size_t i = 0; i < n; ++i, ++t1, ++t2) + if (*t1 != *t2) + return *t1 < *t2 ? -1 : 1; + return 0; +} + +static size_t internal_strlen(const char *s) { + size_t i = 0; + while (s[i]) + i++; + return i; +} + +static char *internal_strstr(const char *haystack, const char *needle) { + // This is O(N^2), but we are not using it in hot places. + size_t len1 = internal_strlen(haystack); + size_t len2 = internal_strlen(needle); + if (len1 < len2) + return nullptr; + for (size_t pos = 0; pos <= len1 - len2; pos++) { + if (internal_memcmp(haystack + pos, needle, len2) == 0) + return const_cast(haystack) + pos; + } + return nullptr; +} + +extern "C" { + +// Weak hooks forward-declared to avoid dependency on +// . +void __sanitizer_weak_hook_memcmp(void *called_pc, const void *s1, + const void *s2, size_t n, int result); +void __sanitizer_weak_hook_strncmp(void *called_pc, const char *s1, + const char *s2, size_t n, int result); +void __sanitizer_weak_hook_strncasecmp(void *called_pc, const char *s1, + const char *s2, size_t n, int result); +void __sanitizer_weak_hook_strcmp(void *called_pc, const char *s1, + const char *s2, int result); +void __sanitizer_weak_hook_strcasecmp(void *called_pc, const char *s1, + const char *s2, int result); +void __sanitizer_weak_hook_strstr(void *called_pc, const char *s1, + const char *s2, char *result); +void __sanitizer_weak_hook_strcasestr(void *called_pc, const char *s1, + const char *s2, char *result); +void __sanitizer_weak_hook_memmem(void *called_pc, const void *s1, size_t len1, + const void *s2, size_t len2, void *result); + +DEFINE_REAL(int, bcmp, const void *, const void *, size_t) +DEFINE_REAL(int, memcmp, const void *, const void *, size_t) +DEFINE_REAL(int, strncmp, const char *, const char *, size_t) +DEFINE_REAL(int, strcmp, const char *, const char *) +DEFINE_REAL(int, strncasecmp, const char *, const char *, size_t) +DEFINE_REAL(int, strcasecmp, const char *, const char *) +DEFINE_REAL(char *, strstr, const char *, const char *) +DEFINE_REAL(char *, strcasestr, const char *, const char *) +DEFINE_REAL(void *, memmem, const void *, size_t, const void *, size_t) + +ATTRIBUTE_INTERFACE int bcmp(const char *s1, const char *s2, size_t n) { + if (!FuzzerInited) + return internal_memcmp(s1, s2, n); + int result = REAL(bcmp)(s1, s2, n); + __sanitizer_weak_hook_memcmp(GET_CALLER_PC(), s1, s2, n, result); + return result; +} + +ATTRIBUTE_INTERFACE int memcmp(const void *s1, const void *s2, size_t n) { + if (!FuzzerInited) + return internal_memcmp(s1, s2, n); + int result = REAL(memcmp)(s1, s2, n); + __sanitizer_weak_hook_memcmp(GET_CALLER_PC(), s1, s2, n, result); + return result; +} + +ATTRIBUTE_INTERFACE int strncmp(const char *s1, const char *s2, size_t n) { + if (!FuzzerInited) + return internal_strncmp(s1, s2, n); + int result = REAL(strncmp)(s1, s2, n); + __sanitizer_weak_hook_strncmp(GET_CALLER_PC(), s1, s2, n, result); + return result; +} + +ATTRIBUTE_INTERFACE int strcmp(const char *s1, const char *s2) { + if (!FuzzerInited) + return internal_strcmp(s1, s2); + int result = REAL(strcmp)(s1, s2); + __sanitizer_weak_hook_strcmp(GET_CALLER_PC(), s1, s2, result); + return result; +} + +ATTRIBUTE_INTERFACE int strncasecmp(const char *s1, const char *s2, size_t n) { + ensureFuzzerInited(); + int result = REAL(strncasecmp)(s1, s2, n); + __sanitizer_weak_hook_strncasecmp(GET_CALLER_PC(), s1, s2, n, result); + return result; +} + +ATTRIBUTE_INTERFACE int strcasecmp(const char *s1, const char *s2) { + ensureFuzzerInited(); + int result = REAL(strcasecmp)(s1, s2); + __sanitizer_weak_hook_strcasecmp(GET_CALLER_PC(), s1, s2, result); + return result; +} + +ATTRIBUTE_INTERFACE char *strstr(const char *s1, const char *s2) { + if (!FuzzerInited) + return internal_strstr(s1, s2); + char *result = REAL(strstr)(s1, s2); + __sanitizer_weak_hook_strstr(GET_CALLER_PC(), s1, s2, result); + return result; +} + +ATTRIBUTE_INTERFACE char *strcasestr(const char *s1, const char *s2) { + ensureFuzzerInited(); + char *result = REAL(strcasestr)(s1, s2); + __sanitizer_weak_hook_strcasestr(GET_CALLER_PC(), s1, s2, result); + return result; +} + +ATTRIBUTE_INTERFACE +void *memmem(const void *s1, size_t len1, const void *s2, size_t len2) { + ensureFuzzerInited(); + void *result = REAL(memmem)(s1, len1, s2, len2); + __sanitizer_weak_hook_memmem(GET_CALLER_PC(), s1, len1, s2, len2, result); + return result; +} + +__attribute__((section(".preinit_array"), + used)) static void (*__local_fuzzer_preinit)(void) = fuzzerInit; + +} // extern "C" + +static void fuzzerInit() { + assert(!FuzzerInitIsRunning); + if (FuzzerInited) + return; + FuzzerInitIsRunning = true; + + REAL(bcmp) = reinterpret_cast( + getFuncAddr("bcmp", reinterpret_cast(&bcmp))); + REAL(memcmp) = reinterpret_cast( + getFuncAddr("memcmp", reinterpret_cast(&memcmp))); + REAL(strncmp) = reinterpret_cast( + getFuncAddr("strncmp", reinterpret_cast(&strncmp))); + REAL(strcmp) = reinterpret_cast( + getFuncAddr("strcmp", reinterpret_cast(&strcmp))); + REAL(strncasecmp) = reinterpret_cast( + getFuncAddr("strncasecmp", reinterpret_cast(&strncasecmp))); + REAL(strcasecmp) = reinterpret_cast( + getFuncAddr("strcasecmp", reinterpret_cast(&strcasecmp))); + REAL(strstr) = reinterpret_cast( + getFuncAddr("strstr", reinterpret_cast(&strstr))); + REAL(strcasestr) = reinterpret_cast( + getFuncAddr("strcasestr", reinterpret_cast(&strcasestr))); + REAL(memmem) = reinterpret_cast( + getFuncAddr("memmem", reinterpret_cast(&memmem))); + + FuzzerInitIsRunning = false; + FuzzerInited = 1; +} + +#endif diff --git a/core/lib/libfuzzer-sys/libfuzzer/FuzzerInterface.h b/core/lib/libfuzzer-sys/libfuzzer/FuzzerInterface.h new file mode 100644 index 0000000000..4f62822eac --- /dev/null +++ b/core/lib/libfuzzer-sys/libfuzzer/FuzzerInterface.h @@ -0,0 +1,79 @@ +//===- FuzzerInterface.h - Interface header for the Fuzzer ------*- C++ -* ===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// Define the interface between libFuzzer and the library being tested. +//===----------------------------------------------------------------------===// + +// NOTE: the libFuzzer interface is thin and in the majority of cases +// you should not include this file into your target. In 95% of cases +// all you need is to define the following function in your file: +// extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size); + +// WARNING: keep the interface in C. + +#ifndef LLVM_FUZZER_INTERFACE_H +#define LLVM_FUZZER_INTERFACE_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +// Define FUZZER_INTERFACE_VISIBILITY to set default visibility in a way that +// doesn't break MSVC. +#if defined(_WIN32) +#define FUZZER_INTERFACE_VISIBILITY __declspec(dllexport) +#else +#define FUZZER_INTERFACE_VISIBILITY __attribute__((visibility("default"))) +#endif + +// Mandatory user-provided target function. +// Executes the code under test with [Data, Data+Size) as the input. +// libFuzzer will invoke this function *many* times with different inputs. +// Must return 0. +FUZZER_INTERFACE_VISIBILITY int +LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size); + +// Optional user-provided initialization function. +// If provided, this function will be called by libFuzzer once at startup. +// It may read and modify argc/argv. +// Must return 0. +FUZZER_INTERFACE_VISIBILITY int LLVMFuzzerInitialize(int *argc, char ***argv); + +// Optional user-provided custom mutator. +// Mutates raw data in [Data, Data+Size) inplace. +// Returns the new size, which is not greater than MaxSize. +// Given the same Seed produces the same mutation. +FUZZER_INTERFACE_VISIBILITY size_t +LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size, size_t MaxSize, + unsigned int Seed); + +// Optional user-provided custom cross-over function. +// Combines pieces of Data1 & Data2 together into Out. +// Returns the new size, which is not greater than MaxOutSize. +// Should produce the same mutation given the same Seed. +FUZZER_INTERFACE_VISIBILITY size_t +LLVMFuzzerCustomCrossOver(const uint8_t *Data1, size_t Size1, + const uint8_t *Data2, size_t Size2, uint8_t *Out, + size_t MaxOutSize, unsigned int Seed); + +// Experimental, may go away in future. +// libFuzzer-provided function to be used inside LLVMFuzzerCustomMutator. +// Mutates raw data in [Data, Data+Size) inplace. +// Returns the new size, which is not greater than MaxSize. +FUZZER_INTERFACE_VISIBILITY size_t +LLVMFuzzerMutate(uint8_t *Data, size_t Size, size_t MaxSize); + +#undef FUZZER_INTERFACE_VISIBILITY + +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus + +#endif // LLVM_FUZZER_INTERFACE_H diff --git a/core/lib/libfuzzer-sys/libfuzzer/FuzzerInternal.h b/core/lib/libfuzzer-sys/libfuzzer/FuzzerInternal.h new file mode 100644 index 0000000000..37c8a01dc3 --- /dev/null +++ b/core/lib/libfuzzer-sys/libfuzzer/FuzzerInternal.h @@ -0,0 +1,174 @@ +//===- FuzzerInternal.h - Internal header for the Fuzzer --------*- C++ -* ===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// Define the main class fuzzer::Fuzzer and most functions. +//===----------------------------------------------------------------------===// + +#ifndef LLVM_FUZZER_INTERNAL_H +#define LLVM_FUZZER_INTERNAL_H + +#include "FuzzerDataFlowTrace.h" +#include "FuzzerDefs.h" +#include "FuzzerExtFunctions.h" +#include "FuzzerInterface.h" +#include "FuzzerOptions.h" +#include "FuzzerSHA1.h" +#include "FuzzerValueBitMap.h" +#include +#include +#include +#include +#include +#include + +namespace fuzzer { + +using namespace std::chrono; + +class Fuzzer { +public: + + Fuzzer(UserCallback CB, InputCorpus &Corpus, MutationDispatcher &MD, + FuzzingOptions Options); + ~Fuzzer(); + void Loop(Vector &CorporaFiles); + void ReadAndExecuteSeedCorpora(Vector &CorporaFiles); + void MinimizeCrashLoop(const Unit &U); + void RereadOutputCorpus(size_t MaxSize); + + size_t secondsSinceProcessStartUp() { + return duration_cast(system_clock::now() - ProcessStartTime) + .count(); + } + + bool TimedOut() { + return Options.MaxTotalTimeSec > 0 && + secondsSinceProcessStartUp() > + static_cast(Options.MaxTotalTimeSec); + } + + size_t execPerSec() { + size_t Seconds = secondsSinceProcessStartUp(); + return Seconds ? TotalNumberOfRuns / Seconds : 0; + } + + size_t getTotalNumberOfRuns() { return TotalNumberOfRuns; } + + static void StaticAlarmCallback(); + static void StaticCrashSignalCallback(); + static void StaticExitCallback(); + static void StaticInterruptCallback(); + static void StaticFileSizeExceedCallback(); + static void StaticGracefulExitCallback(); + + void ExecuteCallback(const uint8_t *Data, size_t Size); + bool RunOne(const uint8_t *Data, size_t Size, bool MayDeleteFile = false, + InputInfo *II = nullptr, bool ForceAddToCorpus = false, + bool *FoundUniqFeatures = nullptr); + void TPCUpdateObservedPCs(); + + // Merge Corpora[1:] into Corpora[0]. + void Merge(const Vector &Corpora); + void CrashResistantMergeInternalStep(const std::string &ControlFilePath); + MutationDispatcher &GetMD() { return MD; } + void PrintFinalStats(); + void SetMaxInputLen(size_t MaxInputLen); + void SetMaxMutationLen(size_t MaxMutationLen); + void RssLimitCallback(); + + bool InFuzzingThread() const { return IsMyThread; } + size_t GetCurrentUnitInFuzzingThead(const uint8_t **Data) const; + void TryDetectingAMemoryLeak(const uint8_t *Data, size_t Size, + bool DuringInitialCorpusExecution); + + void HandleMalloc(size_t Size); + static void MaybeExitGracefully(); + std::string WriteToOutputCorpus(const Unit &U); + +private: + void AlarmCallback(); + void CrashCallback(); + void ExitCallback(); + void CrashOnOverwrittenData(); + void InterruptCallback(); + void MutateAndTestOne(); + void PurgeAllocator(); + void ReportNewCoverage(InputInfo *II, const Unit &U); + void PrintPulseAndReportSlowInput(const uint8_t *Data, size_t Size); + void WriteUnitToFileWithPrefix(const Unit &U, const char *Prefix); + void PrintStats(const char *Where, const char *End = "\n", size_t Units = 0, + size_t Features = 0); + void PrintStatusForNewUnit(const Unit &U, const char *Text); + void CheckExitOnSrcPosOrItem(); + + static void StaticDeathCallback(); + void DumpCurrentUnit(const char *Prefix); + void DeathCallback(); + + void AllocateCurrentUnitData(); + uint8_t *CurrentUnitData = nullptr; + std::atomic CurrentUnitSize; + uint8_t BaseSha1[kSHA1NumBytes]; // Checksum of the base unit. + + bool GracefulExitRequested = false; + + size_t TotalNumberOfRuns = 0; + size_t NumberOfNewUnitsAdded = 0; + + size_t LastCorpusUpdateRun = 0; + + bool HasMoreMallocsThanFrees = false; + size_t NumberOfLeakDetectionAttempts = 0; + + system_clock::time_point LastAllocatorPurgeAttemptTime = system_clock::now(); + + UserCallback CB; + InputCorpus &Corpus; + MutationDispatcher &MD; + FuzzingOptions Options; + DataFlowTrace DFT; + + system_clock::time_point ProcessStartTime = system_clock::now(); + system_clock::time_point UnitStartTime, UnitStopTime; + long TimeOfLongestUnitInSeconds = 0; + long EpochOfLastReadOfOutputCorpus = 0; + + size_t MaxInputLen = 0; + size_t MaxMutationLen = 0; + size_t TmpMaxMutationLen = 0; + + Vector UniqFeatureSetTmp; + + // Need to know our own thread. + static thread_local bool IsMyThread; +}; + +struct ScopedEnableMsanInterceptorChecks { + ScopedEnableMsanInterceptorChecks() { + if (EF->__msan_scoped_enable_interceptor_checks) + EF->__msan_scoped_enable_interceptor_checks(); + } + ~ScopedEnableMsanInterceptorChecks() { + if (EF->__msan_scoped_disable_interceptor_checks) + EF->__msan_scoped_disable_interceptor_checks(); + } +}; + +struct ScopedDisableMsanInterceptorChecks { + ScopedDisableMsanInterceptorChecks() { + if (EF->__msan_scoped_disable_interceptor_checks) + EF->__msan_scoped_disable_interceptor_checks(); + } + ~ScopedDisableMsanInterceptorChecks() { + if (EF->__msan_scoped_enable_interceptor_checks) + EF->__msan_scoped_enable_interceptor_checks(); + } +}; + +} // namespace fuzzer + +#endif // LLVM_FUZZER_INTERNAL_H diff --git a/core/lib/libfuzzer-sys/libfuzzer/FuzzerLoop.cpp b/core/lib/libfuzzer-sys/libfuzzer/FuzzerLoop.cpp new file mode 100644 index 0000000000..86a78ab751 --- /dev/null +++ b/core/lib/libfuzzer-sys/libfuzzer/FuzzerLoop.cpp @@ -0,0 +1,931 @@ +//===- FuzzerLoop.cpp - Fuzzer's main loop --------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// Fuzzer's main loop. +//===----------------------------------------------------------------------===// + +#include "FuzzerCorpus.h" +#include "FuzzerIO.h" +#include "FuzzerInternal.h" +#include "FuzzerMutate.h" +#include "FuzzerPlatform.h" +#include "FuzzerRandom.h" +#include "FuzzerTracePC.h" +#include +#include +#include +#include +#include + +#if defined(__has_include) +#if __has_include() +#include +#endif +#endif + +#define NO_SANITIZE_MEMORY +#if defined(__has_feature) +#if __has_feature(memory_sanitizer) +#undef NO_SANITIZE_MEMORY +#define NO_SANITIZE_MEMORY __attribute__((no_sanitize_memory)) +#endif +#endif + +namespace fuzzer { +static const size_t kMaxUnitSizeToPrint = 256; + +thread_local bool Fuzzer::IsMyThread; + +bool RunningUserCallback = false; + +// Only one Fuzzer per process. +static Fuzzer *F; + +// Leak detection is expensive, so we first check if there were more mallocs +// than frees (using the sanitizer malloc hooks) and only then try to call lsan. +struct MallocFreeTracer { + void Start(int TraceLevel) { + this->TraceLevel = TraceLevel; + if (TraceLevel) + Printf("MallocFreeTracer: START\n"); + Mallocs = 0; + Frees = 0; + } + // Returns true if there were more mallocs than frees. + bool Stop() { + if (TraceLevel) + Printf("MallocFreeTracer: STOP %zd %zd (%s)\n", Mallocs.load(), + Frees.load(), Mallocs == Frees ? "same" : "DIFFERENT"); + bool Result = Mallocs > Frees; + Mallocs = 0; + Frees = 0; + TraceLevel = 0; + return Result; + } + std::atomic Mallocs; + std::atomic Frees; + int TraceLevel = 0; + + std::recursive_mutex TraceMutex; + bool TraceDisabled = false; +}; + +static MallocFreeTracer AllocTracer; + +// Locks printing and avoids nested hooks triggered from mallocs/frees in +// sanitizer. +class TraceLock { +public: + TraceLock() : Lock(AllocTracer.TraceMutex) { + AllocTracer.TraceDisabled = !AllocTracer.TraceDisabled; + } + ~TraceLock() { AllocTracer.TraceDisabled = !AllocTracer.TraceDisabled; } + + bool IsDisabled() const { + // This is already inverted value. + return !AllocTracer.TraceDisabled; + } + +private: + std::lock_guard Lock; +}; + +ATTRIBUTE_NO_SANITIZE_MEMORY +void MallocHook(const volatile void *ptr, size_t size) { + size_t N = AllocTracer.Mallocs++; + F->HandleMalloc(size); + if (int TraceLevel = AllocTracer.TraceLevel) { + TraceLock Lock; + if (Lock.IsDisabled()) + return; + Printf("MALLOC[%zd] %p %zd\n", N, ptr, size); + if (TraceLevel >= 2 && EF) + PrintStackTrace(); + } +} + +ATTRIBUTE_NO_SANITIZE_MEMORY +void FreeHook(const volatile void *ptr) { + size_t N = AllocTracer.Frees++; + if (int TraceLevel = AllocTracer.TraceLevel) { + TraceLock Lock; + if (Lock.IsDisabled()) + return; + Printf("FREE[%zd] %p\n", N, ptr); + if (TraceLevel >= 2 && EF) + PrintStackTrace(); + } +} + +// Crash on a single malloc that exceeds the rss limit. +void Fuzzer::HandleMalloc(size_t Size) { + if (!Options.MallocLimitMb || (Size >> 20) < (size_t)Options.MallocLimitMb) + return; + Printf("==%d== ERROR: libFuzzer: out-of-memory (malloc(%zd))\n", GetPid(), + Size); + Printf(" To change the out-of-memory limit use -rss_limit_mb=\n\n"); + PrintStackTrace(); + DumpCurrentUnit("oom-"); + Printf("SUMMARY: libFuzzer: out-of-memory\n"); + PrintFinalStats(); + _Exit(Options.OOMExitCode); // Stop right now. +} + +Fuzzer::Fuzzer(UserCallback CB, InputCorpus &Corpus, MutationDispatcher &MD, + FuzzingOptions Options) + : CB(CB), Corpus(Corpus), MD(MD), Options(Options) { + if (EF->__sanitizer_set_death_callback) + EF->__sanitizer_set_death_callback(StaticDeathCallback); + assert(!F); + F = this; + TPC.ResetMaps(); + IsMyThread = true; + if (Options.DetectLeaks && EF->__sanitizer_install_malloc_and_free_hooks) + EF->__sanitizer_install_malloc_and_free_hooks(MallocHook, FreeHook); + TPC.SetUseCounters(Options.UseCounters); + TPC.SetUseValueProfileMask(Options.UseValueProfile); + + if (Options.Verbosity) + TPC.PrintModuleInfo(); + if (!Options.OutputCorpus.empty() && Options.ReloadIntervalSec) + EpochOfLastReadOfOutputCorpus = GetEpoch(Options.OutputCorpus); + MaxInputLen = MaxMutationLen = Options.MaxLen; + TmpMaxMutationLen = 0; // Will be set once we load the corpus. + AllocateCurrentUnitData(); + CurrentUnitSize = 0; + memset(BaseSha1, 0, sizeof(BaseSha1)); +} + +Fuzzer::~Fuzzer() {} + +void Fuzzer::AllocateCurrentUnitData() { + if (CurrentUnitData || MaxInputLen == 0) + return; + CurrentUnitData = new uint8_t[MaxInputLen]; +} + +void Fuzzer::StaticDeathCallback() { + assert(F); + F->DeathCallback(); +} + +void Fuzzer::DumpCurrentUnit(const char *Prefix) { + if (!CurrentUnitData) + return; // Happens when running individual inputs. + ScopedDisableMsanInterceptorChecks S; + MD.PrintMutationSequence(); + Printf("; base unit: %s\n", Sha1ToString(BaseSha1).c_str()); + size_t UnitSize = CurrentUnitSize; + if (UnitSize <= kMaxUnitSizeToPrint) { + PrintHexArray(CurrentUnitData, UnitSize, "\n"); + PrintASCII(CurrentUnitData, UnitSize, "\n"); + } + WriteUnitToFileWithPrefix({CurrentUnitData, CurrentUnitData + UnitSize}, + Prefix); +} + +NO_SANITIZE_MEMORY +void Fuzzer::DeathCallback() { + DumpCurrentUnit("crash-"); + PrintFinalStats(); +} + +void Fuzzer::StaticAlarmCallback() { + assert(F); + F->AlarmCallback(); +} + +void Fuzzer::StaticCrashSignalCallback() { + assert(F); + F->CrashCallback(); +} + +void Fuzzer::StaticExitCallback() { + assert(F); + F->ExitCallback(); +} + +void Fuzzer::StaticInterruptCallback() { + assert(F); + F->InterruptCallback(); +} + +void Fuzzer::StaticGracefulExitCallback() { + assert(F); + F->GracefulExitRequested = true; + Printf("INFO: signal received, trying to exit gracefully\n"); +} + +void Fuzzer::StaticFileSizeExceedCallback() { + Printf("==%lu== ERROR: libFuzzer: file size exceeded\n", GetPid()); + exit(1); +} + +void Fuzzer::CrashCallback() { + if (EF->__sanitizer_acquire_crash_state && + !EF->__sanitizer_acquire_crash_state()) + return; + Printf("==%lu== ERROR: libFuzzer: deadly signal\n", GetPid()); + PrintStackTrace(); + Printf("NOTE: libFuzzer has rudimentary signal handlers.\n" + " Combine libFuzzer with AddressSanitizer or similar for better " + "crash reports.\n"); + Printf("SUMMARY: libFuzzer: deadly signal\n"); + DumpCurrentUnit("crash-"); + PrintFinalStats(); + _Exit(Options.ErrorExitCode); // Stop right now. +} + +void Fuzzer::ExitCallback() { + if (!RunningUserCallback) + return; // This exit did not come from the user callback + if (EF->__sanitizer_acquire_crash_state && + !EF->__sanitizer_acquire_crash_state()) + return; + Printf("==%lu== ERROR: libFuzzer: fuzz target exited\n", GetPid()); + PrintStackTrace(); + Printf("SUMMARY: libFuzzer: fuzz target exited\n"); + DumpCurrentUnit("crash-"); + PrintFinalStats(); + _Exit(Options.ErrorExitCode); +} + +void Fuzzer::MaybeExitGracefully() { + if (!F->GracefulExitRequested) return; + Printf("==%lu== INFO: libFuzzer: exiting as requested\n", GetPid()); + RmDirRecursive(TempPath("FuzzWithFork", ".dir")); + F->PrintFinalStats(); + _Exit(0); +} + +void Fuzzer::InterruptCallback() { + Printf("==%lu== libFuzzer: run interrupted; exiting\n", GetPid()); + PrintFinalStats(); + ScopedDisableMsanInterceptorChecks S; // RmDirRecursive may call opendir(). + RmDirRecursive(TempPath("FuzzWithFork", ".dir")); + // Stop right now, don't perform any at-exit actions. + _Exit(Options.InterruptExitCode); +} + +NO_SANITIZE_MEMORY +void Fuzzer::AlarmCallback() { + assert(Options.UnitTimeoutSec > 0); + // In Windows and Fuchsia, Alarm callback is executed by a different thread. + // NetBSD's current behavior needs this change too. +#if !LIBFUZZER_WINDOWS && !LIBFUZZER_NETBSD && !LIBFUZZER_FUCHSIA + if (!InFuzzingThread()) + return; +#endif + if (!RunningUserCallback) + return; // We have not started running units yet. + size_t Seconds = + duration_cast(system_clock::now() - UnitStartTime).count(); + if (Seconds == 0) + return; + if (Options.Verbosity >= 2) + Printf("AlarmCallback %zd\n", Seconds); + if (Seconds >= (size_t)Options.UnitTimeoutSec) { + if (EF->__sanitizer_acquire_crash_state && + !EF->__sanitizer_acquire_crash_state()) + return; + Printf("ALARM: working on the last Unit for %zd seconds\n", Seconds); + Printf(" and the timeout value is %d (use -timeout=N to change)\n", + Options.UnitTimeoutSec); + DumpCurrentUnit("timeout-"); + Printf("==%lu== ERROR: libFuzzer: timeout after %d seconds\n", GetPid(), + Seconds); + PrintStackTrace(); + Printf("SUMMARY: libFuzzer: timeout\n"); + PrintFinalStats(); + _Exit(Options.TimeoutExitCode); // Stop right now. + } +} + +void Fuzzer::RssLimitCallback() { + if (EF->__sanitizer_acquire_crash_state && + !EF->__sanitizer_acquire_crash_state()) + return; + Printf( + "==%lu== ERROR: libFuzzer: out-of-memory (used: %zdMb; limit: %zdMb)\n", + GetPid(), GetPeakRSSMb(), Options.RssLimitMb); + Printf(" To change the out-of-memory limit use -rss_limit_mb=\n\n"); + PrintMemoryProfile(); + DumpCurrentUnit("oom-"); + Printf("SUMMARY: libFuzzer: out-of-memory\n"); + PrintFinalStats(); + _Exit(Options.OOMExitCode); // Stop right now. +} + +void Fuzzer::PrintStats(const char *Where, const char *End, size_t Units, + size_t Features) { + size_t ExecPerSec = execPerSec(); + if (!Options.Verbosity) + return; + Printf("#%zd\t%s", TotalNumberOfRuns, Where); + if (size_t N = TPC.GetTotalPCCoverage()) + Printf(" cov: %zd", N); + if (size_t N = Features ? Features : Corpus.NumFeatures()) + Printf(" ft: %zd", N); + if (!Corpus.empty()) { + Printf(" corp: %zd", Corpus.NumActiveUnits()); + if (size_t N = Corpus.SizeInBytes()) { + if (N < (1 << 14)) + Printf("/%zdb", N); + else if (N < (1 << 24)) + Printf("/%zdKb", N >> 10); + else + Printf("/%zdMb", N >> 20); + } + if (size_t FF = Corpus.NumInputsThatTouchFocusFunction()) + Printf(" focus: %zd", FF); + } + if (TmpMaxMutationLen) + Printf(" lim: %zd", TmpMaxMutationLen); + if (Units) + Printf(" units: %zd", Units); + + Printf(" exec/s: %zd", ExecPerSec); + Printf(" rss: %zdMb", GetPeakRSSMb()); + Printf("%s", End); +} + +void Fuzzer::PrintFinalStats() { + if (Options.PrintFullCoverage) + TPC.PrintCoverage(/*PrintAllCounters=*/true); + if (Options.PrintCoverage) + TPC.PrintCoverage(/*PrintAllCounters=*/false); + if (Options.PrintCorpusStats) + Corpus.PrintStats(); + if (!Options.PrintFinalStats) + return; + size_t ExecPerSec = execPerSec(); + Printf("stat::number_of_executed_units: %zd\n", TotalNumberOfRuns); + Printf("stat::average_exec_per_sec: %zd\n", ExecPerSec); + Printf("stat::new_units_added: %zd\n", NumberOfNewUnitsAdded); + Printf("stat::slowest_unit_time_sec: %zd\n", TimeOfLongestUnitInSeconds); + Printf("stat::peak_rss_mb: %zd\n", GetPeakRSSMb()); +} + +void Fuzzer::SetMaxInputLen(size_t MaxInputLen) { + assert(this->MaxInputLen == 0); // Can only reset MaxInputLen from 0 to non-0. + assert(MaxInputLen); + this->MaxInputLen = MaxInputLen; + this->MaxMutationLen = MaxInputLen; + AllocateCurrentUnitData(); + Printf("INFO: -max_len is not provided; " + "libFuzzer will not generate inputs larger than %zd bytes\n", + MaxInputLen); +} + +void Fuzzer::SetMaxMutationLen(size_t MaxMutationLen) { + assert(MaxMutationLen && MaxMutationLen <= MaxInputLen); + this->MaxMutationLen = MaxMutationLen; +} + +void Fuzzer::CheckExitOnSrcPosOrItem() { + if (!Options.ExitOnSrcPos.empty()) { + static auto *PCsSet = new Set; + auto HandlePC = [&](const TracePC::PCTableEntry *TE) { + if (!PCsSet->insert(TE->PC).second) + return; + std::string Descr = DescribePC("%F %L", TE->PC + 1); + if (Descr.find(Options.ExitOnSrcPos) != std::string::npos) { + Printf("INFO: found line matching '%s', exiting.\n", + Options.ExitOnSrcPos.c_str()); + _Exit(0); + } + }; + TPC.ForEachObservedPC(HandlePC); + } + if (!Options.ExitOnItem.empty()) { + if (Corpus.HasUnit(Options.ExitOnItem)) { + Printf("INFO: found item with checksum '%s', exiting.\n", + Options.ExitOnItem.c_str()); + _Exit(0); + } + } +} + +void Fuzzer::RereadOutputCorpus(size_t MaxSize) { + if (Options.OutputCorpus.empty() || !Options.ReloadIntervalSec) + return; + Vector AdditionalCorpus; + Vector AdditionalCorpusPaths; + ReadDirToVectorOfUnits( + Options.OutputCorpus.c_str(), &AdditionalCorpus, + &EpochOfLastReadOfOutputCorpus, MaxSize, + /*ExitOnError*/ false, + (Options.Verbosity >= 2 ? &AdditionalCorpusPaths : nullptr)); + if (Options.Verbosity >= 2) + Printf("Reload: read %zd new units.\n", AdditionalCorpus.size()); + bool Reloaded = false; + for (size_t i = 0; i != AdditionalCorpus.size(); ++i) { + auto &U = AdditionalCorpus[i]; + if (U.size() > MaxSize) + U.resize(MaxSize); + if (!Corpus.HasUnit(U)) { + if (RunOne(U.data(), U.size())) { + CheckExitOnSrcPosOrItem(); + Reloaded = true; + if (Options.Verbosity >= 2) + Printf("Reloaded %s\n", AdditionalCorpusPaths[i].c_str()); + } + } + } + if (Reloaded) + PrintStats("RELOAD"); +} + +void Fuzzer::PrintPulseAndReportSlowInput(const uint8_t *Data, size_t Size) { + auto TimeOfUnit = + duration_cast(UnitStopTime - UnitStartTime).count(); + if (!(TotalNumberOfRuns & (TotalNumberOfRuns - 1)) && + secondsSinceProcessStartUp() >= 2) + PrintStats("pulse "); + auto Threshhold = + static_cast(static_cast(TimeOfLongestUnitInSeconds) * 1.1); + if (TimeOfUnit > Threshhold && TimeOfUnit >= Options.ReportSlowUnits) { + TimeOfLongestUnitInSeconds = TimeOfUnit; + Printf("Slowest unit: %zd s:\n", TimeOfLongestUnitInSeconds); + WriteUnitToFileWithPrefix({Data, Data + Size}, "slow-unit-"); + } +} + +static void WriteFeatureSetToFile(const std::string &FeaturesDir, + const std::string &FileName, + const Vector &FeatureSet) { + if (FeaturesDir.empty() || FeatureSet.empty()) return; + WriteToFile(reinterpret_cast(FeatureSet.data()), + FeatureSet.size() * sizeof(FeatureSet[0]), + DirPlusFile(FeaturesDir, FileName)); +} + +static void RenameFeatureSetFile(const std::string &FeaturesDir, + const std::string &OldFile, + const std::string &NewFile) { + if (FeaturesDir.empty()) return; + RenameFile(DirPlusFile(FeaturesDir, OldFile), + DirPlusFile(FeaturesDir, NewFile)); +} + +static void WriteEdgeToMutationGraphFile(const std::string &MutationGraphFile, + const InputInfo *II, + const InputInfo *BaseII, + const std::string &MS) { + if (MutationGraphFile.empty()) + return; + + std::string Sha1 = Sha1ToString(II->Sha1); + + std::string OutputString; + + // Add a new vertex. + OutputString.append("\""); + OutputString.append(Sha1); + OutputString.append("\"\n"); + + // Add a new edge if there is base input. + if (BaseII) { + std::string BaseSha1 = Sha1ToString(BaseII->Sha1); + OutputString.append("\""); + OutputString.append(BaseSha1); + OutputString.append("\" -> \""); + OutputString.append(Sha1); + OutputString.append("\" [label=\""); + OutputString.append(MS); + OutputString.append("\"];\n"); + } + + AppendToFile(OutputString, MutationGraphFile); +} + +bool Fuzzer::RunOne(const uint8_t *Data, size_t Size, bool MayDeleteFile, + InputInfo *II, bool ForceAddToCorpus, + bool *FoundUniqFeatures) { + if (!Size) + return false; + // Largest input length should be INT_MAX. + assert(Size < std::numeric_limits::max()); + + ExecuteCallback(Data, Size); + auto TimeOfUnit = duration_cast(UnitStopTime - UnitStartTime); + + UniqFeatureSetTmp.clear(); + size_t FoundUniqFeaturesOfII = 0; + size_t NumUpdatesBefore = Corpus.NumFeatureUpdates(); + TPC.CollectFeatures([&](uint32_t Feature) { + if (Corpus.AddFeature(Feature, static_cast(Size), Options.Shrink)) + UniqFeatureSetTmp.push_back(Feature); + if (Options.Entropic) + Corpus.UpdateFeatureFrequency(II, Feature); + if (Options.ReduceInputs && II && !II->NeverReduce) + if (std::binary_search(II->UniqFeatureSet.begin(), + II->UniqFeatureSet.end(), Feature)) + FoundUniqFeaturesOfII++; + }); + if (FoundUniqFeatures) + *FoundUniqFeatures = FoundUniqFeaturesOfII; + PrintPulseAndReportSlowInput(Data, Size); + size_t NumNewFeatures = Corpus.NumFeatureUpdates() - NumUpdatesBefore; + if (NumNewFeatures || ForceAddToCorpus) { + TPC.UpdateObservedPCs(); + auto NewII = + Corpus.AddToCorpus({Data, Data + Size}, NumNewFeatures, MayDeleteFile, + TPC.ObservedFocusFunction(), ForceAddToCorpus, + TimeOfUnit, UniqFeatureSetTmp, DFT, II); + WriteFeatureSetToFile(Options.FeaturesDir, Sha1ToString(NewII->Sha1), + NewII->UniqFeatureSet); + WriteEdgeToMutationGraphFile(Options.MutationGraphFile, NewII, II, + MD.MutationSequence()); + return true; + } + if (II && FoundUniqFeaturesOfII && + II->DataFlowTraceForFocusFunction.empty() && + FoundUniqFeaturesOfII == II->UniqFeatureSet.size() && + II->U.size() > Size) { + auto OldFeaturesFile = Sha1ToString(II->Sha1); + Corpus.Replace(II, {Data, Data + Size}); + RenameFeatureSetFile(Options.FeaturesDir, OldFeaturesFile, + Sha1ToString(II->Sha1)); + return true; + } + return false; +} + +void Fuzzer::TPCUpdateObservedPCs() { TPC.UpdateObservedPCs(); } + +size_t Fuzzer::GetCurrentUnitInFuzzingThead(const uint8_t **Data) const { + assert(InFuzzingThread()); + *Data = CurrentUnitData; + return CurrentUnitSize; +} + +void Fuzzer::CrashOnOverwrittenData() { + Printf("==%d== ERROR: libFuzzer: fuzz target overwrites its const input\n", + GetPid()); + PrintStackTrace(); + Printf("SUMMARY: libFuzzer: overwrites-const-input\n"); + DumpCurrentUnit("crash-"); + PrintFinalStats(); + _Exit(Options.ErrorExitCode); // Stop right now. +} + +// Compare two arrays, but not all bytes if the arrays are large. +static bool LooseMemeq(const uint8_t *A, const uint8_t *B, size_t Size) { + const size_t Limit = 64; + if (Size <= 64) + return !memcmp(A, B, Size); + // Compare first and last Limit/2 bytes. + return !memcmp(A, B, Limit / 2) && + !memcmp(A + Size - Limit / 2, B + Size - Limit / 2, Limit / 2); +} + +// This method is not inlined because it would cause a test to fail where it +// is part of the stack unwinding. See D97975 for details. +ATTRIBUTE_NOINLINE void Fuzzer::ExecuteCallback(const uint8_t *Data, + size_t Size) { + TPC.RecordInitialStack(); + TotalNumberOfRuns++; + assert(InFuzzingThread()); + // We copy the contents of Unit into a separate heap buffer + // so that we reliably find buffer overflows in it. + uint8_t *DataCopy = new uint8_t[Size]; + memcpy(DataCopy, Data, Size); + if (EF->__msan_unpoison) + EF->__msan_unpoison(DataCopy, Size); + if (EF->__msan_unpoison_param) + EF->__msan_unpoison_param(2); + if (CurrentUnitData && CurrentUnitData != Data) + memcpy(CurrentUnitData, Data, Size); + CurrentUnitSize = Size; + { + ScopedEnableMsanInterceptorChecks S; + AllocTracer.Start(Options.TraceMalloc); + UnitStartTime = system_clock::now(); + TPC.ResetMaps(); + RunningUserCallback = true; + int Res = CB(DataCopy, Size); + RunningUserCallback = false; + UnitStopTime = system_clock::now(); + (void)Res; + assert(Res == 0); + HasMoreMallocsThanFrees = AllocTracer.Stop(); + } + if (!LooseMemeq(DataCopy, Data, Size)) + CrashOnOverwrittenData(); + CurrentUnitSize = 0; + delete[] DataCopy; +} + +std::string Fuzzer::WriteToOutputCorpus(const Unit &U) { + if (Options.OnlyASCII) + assert(IsASCII(U)); + if (Options.OutputCorpus.empty()) + return ""; + std::string Path = DirPlusFile(Options.OutputCorpus, Hash(U)); + WriteToFile(U, Path); + if (Options.Verbosity >= 2) + Printf("Written %zd bytes to %s\n", U.size(), Path.c_str()); + return Path; +} + +void Fuzzer::WriteUnitToFileWithPrefix(const Unit &U, const char *Prefix) { + if (!Options.SaveArtifacts) + return; + std::string Path = Options.ArtifactPrefix + Prefix + Hash(U); + if (!Options.ExactArtifactPath.empty()) + Path = Options.ExactArtifactPath; // Overrides ArtifactPrefix. + WriteToFile(U, Path); + Printf("artifact_prefix='%s'; Test unit written to %s\n", + Options.ArtifactPrefix.c_str(), Path.c_str()); + if (U.size() <= kMaxUnitSizeToPrint) + Printf("Base64: %s\n", Base64(U).c_str()); +} + +void Fuzzer::PrintStatusForNewUnit(const Unit &U, const char *Text) { + if (!Options.PrintNEW) + return; + PrintStats(Text, ""); + if (Options.Verbosity) { + Printf(" L: %zd/%zd ", U.size(), Corpus.MaxInputSize()); + MD.PrintMutationSequence(Options.Verbosity >= 2); + Printf("\n"); + } +} + +void Fuzzer::ReportNewCoverage(InputInfo *II, const Unit &U) { + II->NumSuccessfullMutations++; + MD.RecordSuccessfulMutationSequence(); + PrintStatusForNewUnit(U, II->Reduced ? "REDUCE" : "NEW "); + WriteToOutputCorpus(U); + NumberOfNewUnitsAdded++; + CheckExitOnSrcPosOrItem(); // Check only after the unit is saved to corpus. + LastCorpusUpdateRun = TotalNumberOfRuns; +} + +// Tries detecting a memory leak on the particular input that we have just +// executed before calling this function. +void Fuzzer::TryDetectingAMemoryLeak(const uint8_t *Data, size_t Size, + bool DuringInitialCorpusExecution) { + if (!HasMoreMallocsThanFrees) + return; // mallocs==frees, a leak is unlikely. + if (!Options.DetectLeaks) + return; + if (!DuringInitialCorpusExecution && + TotalNumberOfRuns >= Options.MaxNumberOfRuns) + return; + if (!&(EF->__lsan_enable) || !&(EF->__lsan_disable) || + !(EF->__lsan_do_recoverable_leak_check)) + return; // No lsan. + // Run the target once again, but with lsan disabled so that if there is + // a real leak we do not report it twice. + EF->__lsan_disable(); + ExecuteCallback(Data, Size); + EF->__lsan_enable(); + if (!HasMoreMallocsThanFrees) + return; // a leak is unlikely. + if (NumberOfLeakDetectionAttempts++ > 1000) { + Options.DetectLeaks = false; + Printf("INFO: libFuzzer disabled leak detection after every mutation.\n" + " Most likely the target function accumulates allocated\n" + " memory in a global state w/o actually leaking it.\n" + " You may try running this binary with -trace_malloc=[12]" + " to get a trace of mallocs and frees.\n" + " If LeakSanitizer is enabled in this process it will still\n" + " run on the process shutdown.\n"); + return; + } + // Now perform the actual lsan pass. This is expensive and we must ensure + // we don't call it too often. + if (EF->__lsan_do_recoverable_leak_check()) { // Leak is found, report it. + if (DuringInitialCorpusExecution) + Printf("\nINFO: a leak has been found in the initial corpus.\n\n"); + Printf("INFO: to ignore leaks on libFuzzer side use -detect_leaks=0.\n\n"); + CurrentUnitSize = Size; + DumpCurrentUnit("leak-"); + PrintFinalStats(); + _Exit(Options.ErrorExitCode); // not exit() to disable lsan further on. + } +} + +void Fuzzer::MutateAndTestOne() { + MD.StartMutationSequence(); + + auto &II = Corpus.ChooseUnitToMutate(MD.GetRand()); + if (Options.DoCrossOver) { + auto &CrossOverII = Corpus.ChooseUnitToCrossOverWith( + MD.GetRand(), Options.CrossOverUniformDist); + MD.SetCrossOverWith(&CrossOverII.U); + } + const auto &U = II.U; + memcpy(BaseSha1, II.Sha1, sizeof(BaseSha1)); + assert(CurrentUnitData); + size_t Size = U.size(); + assert(Size <= MaxInputLen && "Oversized Unit"); + memcpy(CurrentUnitData, U.data(), Size); + + assert(MaxMutationLen > 0); + + size_t CurrentMaxMutationLen = + Min(MaxMutationLen, Max(U.size(), TmpMaxMutationLen)); + assert(CurrentMaxMutationLen > 0); + + for (int i = 0; i < Options.MutateDepth; i++) { + if (TotalNumberOfRuns >= Options.MaxNumberOfRuns) + break; + MaybeExitGracefully(); + size_t NewSize = 0; + if (II.HasFocusFunction && !II.DataFlowTraceForFocusFunction.empty() && + Size <= CurrentMaxMutationLen) + NewSize = MD.MutateWithMask(CurrentUnitData, Size, Size, + II.DataFlowTraceForFocusFunction); + + // If MutateWithMask either failed or wasn't called, call default Mutate. + if (!NewSize) + NewSize = MD.Mutate(CurrentUnitData, Size, CurrentMaxMutationLen); + assert(NewSize > 0 && "Mutator returned empty unit"); + assert(NewSize <= CurrentMaxMutationLen && "Mutator return oversized unit"); + Size = NewSize; + II.NumExecutedMutations++; + Corpus.IncrementNumExecutedMutations(); + + bool FoundUniqFeatures = false; + bool NewCov = RunOne(CurrentUnitData, Size, /*MayDeleteFile=*/true, &II, + /*ForceAddToCorpus*/ false, &FoundUniqFeatures); + TryDetectingAMemoryLeak(CurrentUnitData, Size, + /*DuringInitialCorpusExecution*/ false); + if (NewCov) { + ReportNewCoverage(&II, {CurrentUnitData, CurrentUnitData + Size}); + break; // We will mutate this input more in the next rounds. + } + if (Options.ReduceDepth && !FoundUniqFeatures) + break; + } + + II.NeedsEnergyUpdate = true; +} + +void Fuzzer::PurgeAllocator() { + if (Options.PurgeAllocatorIntervalSec < 0 || !EF->__sanitizer_purge_allocator) + return; + if (duration_cast(system_clock::now() - + LastAllocatorPurgeAttemptTime) + .count() < Options.PurgeAllocatorIntervalSec) + return; + + if (Options.RssLimitMb <= 0 || + GetPeakRSSMb() > static_cast(Options.RssLimitMb) / 2) + EF->__sanitizer_purge_allocator(); + + LastAllocatorPurgeAttemptTime = system_clock::now(); +} + +void Fuzzer::ReadAndExecuteSeedCorpora(Vector &CorporaFiles) { + const size_t kMaxSaneLen = 1 << 20; + const size_t kMinDefaultLen = 4096; + size_t MaxSize = 0; + size_t MinSize = -1; + size_t TotalSize = 0; + for (auto &File : CorporaFiles) { + MaxSize = Max(File.Size, MaxSize); + MinSize = Min(File.Size, MinSize); + TotalSize += File.Size; + } + if (Options.MaxLen == 0) + SetMaxInputLen(std::min(std::max(kMinDefaultLen, MaxSize), kMaxSaneLen)); + assert(MaxInputLen > 0); + + // Test the callback with empty input and never try it again. + uint8_t dummy = 0; + ExecuteCallback(&dummy, 0); + + if (CorporaFiles.empty()) { + Printf("INFO: A corpus is not provided, starting from an empty corpus\n"); + Unit U({'\n'}); // Valid ASCII input. + RunOne(U.data(), U.size()); + } else { + Printf("INFO: seed corpus: files: %zd min: %zdb max: %zdb total: %zdb" + " rss: %zdMb\n", + CorporaFiles.size(), MinSize, MaxSize, TotalSize, GetPeakRSSMb()); + if (Options.ShuffleAtStartUp) + std::shuffle(CorporaFiles.begin(), CorporaFiles.end(), MD.GetRand()); + + if (Options.PreferSmall) { + std::stable_sort(CorporaFiles.begin(), CorporaFiles.end()); + assert(CorporaFiles.front().Size <= CorporaFiles.back().Size); + } + + // Load and execute inputs one by one. + for (auto &SF : CorporaFiles) { + auto U = FileToVector(SF.File, MaxInputLen, /*ExitOnError=*/false); + assert(U.size() <= MaxInputLen); + RunOne(U.data(), U.size(), /*MayDeleteFile*/ false, /*II*/ nullptr, + /*ForceAddToCorpus*/ Options.KeepSeed, + /*FoundUniqFeatures*/ nullptr); + CheckExitOnSrcPosOrItem(); + TryDetectingAMemoryLeak(U.data(), U.size(), + /*DuringInitialCorpusExecution*/ true); + } + } + + PrintStats("INITED"); + if (!Options.FocusFunction.empty()) { + Printf("INFO: %zd/%zd inputs touch the focus function\n", + Corpus.NumInputsThatTouchFocusFunction(), Corpus.size()); + if (!Options.DataFlowTrace.empty()) + Printf("INFO: %zd/%zd inputs have the Data Flow Trace\n", + Corpus.NumInputsWithDataFlowTrace(), + Corpus.NumInputsThatTouchFocusFunction()); + } + + if (Corpus.empty() && Options.MaxNumberOfRuns) { + Printf("ERROR: no interesting inputs were found. " + "Is the code instrumented for coverage? Exiting.\n"); + exit(1); + } +} + +void Fuzzer::Loop(Vector &CorporaFiles) { + auto FocusFunctionOrAuto = Options.FocusFunction; + DFT.Init(Options.DataFlowTrace, &FocusFunctionOrAuto, CorporaFiles, + MD.GetRand()); + TPC.SetFocusFunction(FocusFunctionOrAuto); + ReadAndExecuteSeedCorpora(CorporaFiles); + DFT.Clear(); // No need for DFT any more. + TPC.SetPrintNewPCs(Options.PrintNewCovPcs); + TPC.SetPrintNewFuncs(Options.PrintNewCovFuncs); + system_clock::time_point LastCorpusReload = system_clock::now(); + + TmpMaxMutationLen = + Min(MaxMutationLen, Max(size_t(4), Corpus.MaxInputSize())); + + while (true) { + auto Now = system_clock::now(); + if (!Options.StopFile.empty() && + !FileToVector(Options.StopFile, 1, false).empty()) + break; + if (duration_cast(Now - LastCorpusReload).count() >= + Options.ReloadIntervalSec) { + RereadOutputCorpus(MaxInputLen); + LastCorpusReload = system_clock::now(); + } + if (TotalNumberOfRuns >= Options.MaxNumberOfRuns) + break; + if (TimedOut()) + break; + + // Update TmpMaxMutationLen + if (Options.LenControl) { + if (TmpMaxMutationLen < MaxMutationLen && + TotalNumberOfRuns - LastCorpusUpdateRun > + Options.LenControl * Log(TmpMaxMutationLen)) { + TmpMaxMutationLen = + Min(MaxMutationLen, TmpMaxMutationLen + Log(TmpMaxMutationLen)); + LastCorpusUpdateRun = TotalNumberOfRuns; + } + } else { + TmpMaxMutationLen = MaxMutationLen; + } + + // Perform several mutations and runs. + MutateAndTestOne(); + + PurgeAllocator(); + } + + PrintStats("DONE ", "\n"); + MD.PrintRecommendedDictionary(); +} + +void Fuzzer::MinimizeCrashLoop(const Unit &U) { + if (U.size() <= 1) + return; + while (!TimedOut() && TotalNumberOfRuns < Options.MaxNumberOfRuns) { + MD.StartMutationSequence(); + memcpy(CurrentUnitData, U.data(), U.size()); + for (int i = 0; i < Options.MutateDepth; i++) { + size_t NewSize = MD.Mutate(CurrentUnitData, U.size(), MaxMutationLen); + assert(NewSize > 0 && NewSize <= MaxMutationLen); + ExecuteCallback(CurrentUnitData, NewSize); + PrintPulseAndReportSlowInput(CurrentUnitData, NewSize); + TryDetectingAMemoryLeak(CurrentUnitData, NewSize, + /*DuringInitialCorpusExecution*/ false); + } + } +} + +} // namespace fuzzer + +extern "C" { + +ATTRIBUTE_INTERFACE size_t +LLVMFuzzerMutate(uint8_t *Data, size_t Size, size_t MaxSize) { + assert(fuzzer::F); + return fuzzer::F->GetMD().DefaultMutate(Data, Size, MaxSize); +} + +} // extern "C" diff --git a/core/lib/libfuzzer-sys/libfuzzer/FuzzerMain.cpp b/core/lib/libfuzzer-sys/libfuzzer/FuzzerMain.cpp new file mode 100644 index 0000000000..75f2f8e75c --- /dev/null +++ b/core/lib/libfuzzer-sys/libfuzzer/FuzzerMain.cpp @@ -0,0 +1,21 @@ +//===- FuzzerMain.cpp - main() function and flags -------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// main() and flags. +//===----------------------------------------------------------------------===// + +#include "FuzzerDefs.h" +#include "FuzzerPlatform.h" + +extern "C" { +// This function should be defined by the user. +int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size); +} // extern "C" + +ATTRIBUTE_INTERFACE int main(int argc, char **argv) { + return fuzzer::FuzzerDriver(&argc, &argv, LLVMFuzzerTestOneInput); +} diff --git a/core/lib/libfuzzer-sys/libfuzzer/FuzzerMerge.cpp b/core/lib/libfuzzer-sys/libfuzzer/FuzzerMerge.cpp new file mode 100644 index 0000000000..162453ceae --- /dev/null +++ b/core/lib/libfuzzer-sys/libfuzzer/FuzzerMerge.cpp @@ -0,0 +1,404 @@ +//===- FuzzerMerge.cpp - merging corpora ----------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// Merging corpora. +//===----------------------------------------------------------------------===// + +#include "FuzzerCommand.h" +#include "FuzzerMerge.h" +#include "FuzzerIO.h" +#include "FuzzerInternal.h" +#include "FuzzerTracePC.h" +#include "FuzzerUtil.h" + +#include +#include +#include +#include +#include + +namespace fuzzer { + +bool Merger::Parse(const std::string &Str, bool ParseCoverage) { + std::istringstream SS(Str); + return Parse(SS, ParseCoverage); +} + +void Merger::ParseOrExit(std::istream &IS, bool ParseCoverage) { + if (!Parse(IS, ParseCoverage)) { + Printf("MERGE: failed to parse the control file (unexpected error)\n"); + exit(1); + } +} + +// The control file example: +// +// 3 # The number of inputs +// 1 # The number of inputs in the first corpus, <= the previous number +// file0 +// file1 +// file2 # One file name per line. +// STARTED 0 123 # FileID, file size +// FT 0 1 4 6 8 # FileID COV1 COV2 ... +// COV 0 7 8 9 # FileID COV1 COV1 +// STARTED 1 456 # If FT is missing, the input crashed while processing. +// STARTED 2 567 +// FT 2 8 9 +// COV 2 11 12 +bool Merger::Parse(std::istream &IS, bool ParseCoverage) { + LastFailure.clear(); + std::string Line; + + // Parse NumFiles. + if (!std::getline(IS, Line, '\n')) return false; + std::istringstream L1(Line); + size_t NumFiles = 0; + L1 >> NumFiles; + if (NumFiles == 0 || NumFiles > 10000000) return false; + + // Parse NumFilesInFirstCorpus. + if (!std::getline(IS, Line, '\n')) return false; + std::istringstream L2(Line); + NumFilesInFirstCorpus = NumFiles + 1; + L2 >> NumFilesInFirstCorpus; + if (NumFilesInFirstCorpus > NumFiles) return false; + + // Parse file names. + Files.resize(NumFiles); + for (size_t i = 0; i < NumFiles; i++) + if (!std::getline(IS, Files[i].Name, '\n')) + return false; + + // Parse STARTED, FT, and COV lines. + size_t ExpectedStartMarker = 0; + const size_t kInvalidStartMarker = -1; + size_t LastSeenStartMarker = kInvalidStartMarker; + Vector TmpFeatures; + Set PCs; + while (std::getline(IS, Line, '\n')) { + std::istringstream ISS1(Line); + std::string Marker; + uint32_t N; + if (!(ISS1 >> Marker) || !(ISS1 >> N)) + return false; + if (Marker == "STARTED") { + // STARTED FILE_ID FILE_SIZE + if (ExpectedStartMarker != N) + return false; + ISS1 >> Files[ExpectedStartMarker].Size; + LastSeenStartMarker = ExpectedStartMarker; + assert(ExpectedStartMarker < Files.size()); + ExpectedStartMarker++; + } else if (Marker == "FT") { + // FT FILE_ID COV1 COV2 COV3 ... + size_t CurrentFileIdx = N; + if (CurrentFileIdx != LastSeenStartMarker) + return false; + LastSeenStartMarker = kInvalidStartMarker; + if (ParseCoverage) { + TmpFeatures.clear(); // use a vector from outer scope to avoid resizes. + while (ISS1 >> N) + TmpFeatures.push_back(N); + std::sort(TmpFeatures.begin(), TmpFeatures.end()); + Files[CurrentFileIdx].Features = TmpFeatures; + } + } else if (Marker == "COV") { + size_t CurrentFileIdx = N; + if (ParseCoverage) + while (ISS1 >> N) + if (PCs.insert(N).second) + Files[CurrentFileIdx].Cov.push_back(N); + } else { + return false; + } + } + if (LastSeenStartMarker != kInvalidStartMarker) + LastFailure = Files[LastSeenStartMarker].Name; + + FirstNotProcessedFile = ExpectedStartMarker; + return true; +} + +size_t Merger::ApproximateMemoryConsumption() const { + size_t Res = 0; + for (const auto &F: Files) + Res += sizeof(F) + F.Features.size() * sizeof(F.Features[0]); + return Res; +} + +// Decides which files need to be merged (add those to NewFiles). +// Returns the number of new features added. +size_t Merger::Merge(const Set &InitialFeatures, + Set *NewFeatures, + const Set &InitialCov, Set *NewCov, + Vector *NewFiles) { + NewFiles->clear(); + NewFeatures->clear(); + NewCov->clear(); + assert(NumFilesInFirstCorpus <= Files.size()); + Set AllFeatures = InitialFeatures; + + // What features are in the initial corpus? + for (size_t i = 0; i < NumFilesInFirstCorpus; i++) { + auto &Cur = Files[i].Features; + AllFeatures.insert(Cur.begin(), Cur.end()); + } + // Remove all features that we already know from all other inputs. + for (size_t i = NumFilesInFirstCorpus; i < Files.size(); i++) { + auto &Cur = Files[i].Features; + Vector Tmp; + std::set_difference(Cur.begin(), Cur.end(), AllFeatures.begin(), + AllFeatures.end(), std::inserter(Tmp, Tmp.begin())); + Cur.swap(Tmp); + } + + // Sort. Give preference to + // * smaller files + // * files with more features. + std::sort(Files.begin() + NumFilesInFirstCorpus, Files.end(), + [&](const MergeFileInfo &a, const MergeFileInfo &b) -> bool { + if (a.Size != b.Size) + return a.Size < b.Size; + return a.Features.size() > b.Features.size(); + }); + + // One greedy pass: add the file's features to AllFeatures. + // If new features were added, add this file to NewFiles. + for (size_t i = NumFilesInFirstCorpus; i < Files.size(); i++) { + auto &Cur = Files[i].Features; + // Printf("%s -> sz %zd ft %zd\n", Files[i].Name.c_str(), + // Files[i].Size, Cur.size()); + bool FoundNewFeatures = false; + for (auto Fe: Cur) { + if (AllFeatures.insert(Fe).second) { + FoundNewFeatures = true; + NewFeatures->insert(Fe); + } + } + if (FoundNewFeatures) + NewFiles->push_back(Files[i].Name); + for (auto Cov : Files[i].Cov) + if (InitialCov.find(Cov) == InitialCov.end()) + NewCov->insert(Cov); + } + return NewFeatures->size(); +} + +Set Merger::AllFeatures() const { + Set S; + for (auto &File : Files) + S.insert(File.Features.begin(), File.Features.end()); + return S; +} + +// Inner process. May crash if the target crashes. +void Fuzzer::CrashResistantMergeInternalStep(const std::string &CFPath) { + Printf("MERGE-INNER: using the control file '%s'\n", CFPath.c_str()); + Merger M; + std::ifstream IF(CFPath); + M.ParseOrExit(IF, false); + IF.close(); + if (!M.LastFailure.empty()) + Printf("MERGE-INNER: '%s' caused a failure at the previous merge step\n", + M.LastFailure.c_str()); + + Printf("MERGE-INNER: %zd total files;" + " %zd processed earlier; will process %zd files now\n", + M.Files.size(), M.FirstNotProcessedFile, + M.Files.size() - M.FirstNotProcessedFile); + + std::ofstream OF(CFPath, std::ofstream::out | std::ofstream::app); + Set AllFeatures; + auto PrintStatsWrapper = [this, &AllFeatures](const char* Where) { + this->PrintStats(Where, "\n", 0, AllFeatures.size()); + }; + Set AllPCs; + for (size_t i = M.FirstNotProcessedFile; i < M.Files.size(); i++) { + Fuzzer::MaybeExitGracefully(); + auto U = FileToVector(M.Files[i].Name); + if (U.size() > MaxInputLen) { + U.resize(MaxInputLen); + U.shrink_to_fit(); + } + + // Write the pre-run marker. + OF << "STARTED " << i << " " << U.size() << "\n"; + OF.flush(); // Flush is important since Command::Execute may crash. + // Run. + TPC.ResetMaps(); + ExecuteCallback(U.data(), U.size()); + // Collect coverage. We are iterating over the files in this order: + // * First, files in the initial corpus ordered by size, smallest first. + // * Then, all other files, smallest first. + // So it makes no sense to record all features for all files, instead we + // only record features that were not seen before. + Set UniqFeatures; + TPC.CollectFeatures([&](size_t Feature) { + if (AllFeatures.insert(Feature).second) + UniqFeatures.insert(Feature); + }); + TPC.UpdateObservedPCs(); + // Show stats. + if (!(TotalNumberOfRuns & (TotalNumberOfRuns - 1))) + PrintStatsWrapper("pulse "); + if (TotalNumberOfRuns == M.NumFilesInFirstCorpus) + PrintStatsWrapper("LOADED"); + // Write the post-run marker and the coverage. + OF << "FT " << i; + for (size_t F : UniqFeatures) + OF << " " << F; + OF << "\n"; + OF << "COV " << i; + TPC.ForEachObservedPC([&](const TracePC::PCTableEntry *TE) { + if (AllPCs.insert(TE).second) + OF << " " << TPC.PCTableEntryIdx(TE); + }); + OF << "\n"; + OF.flush(); + } + PrintStatsWrapper("DONE "); +} + +static size_t WriteNewControlFile(const std::string &CFPath, + const Vector &OldCorpus, + const Vector &NewCorpus, + const Vector &KnownFiles) { + std::unordered_set FilesToSkip; + for (auto &SF: KnownFiles) + FilesToSkip.insert(SF.Name); + + Vector FilesToUse; + auto MaybeUseFile = [=, &FilesToUse](std::string Name) { + if (FilesToSkip.find(Name) == FilesToSkip.end()) + FilesToUse.push_back(Name); + }; + for (auto &SF: OldCorpus) + MaybeUseFile(SF.File); + auto FilesToUseFromOldCorpus = FilesToUse.size(); + for (auto &SF: NewCorpus) + MaybeUseFile(SF.File); + + RemoveFile(CFPath); + std::ofstream ControlFile(CFPath); + ControlFile << FilesToUse.size() << "\n"; + ControlFile << FilesToUseFromOldCorpus << "\n"; + for (auto &FN: FilesToUse) + ControlFile << FN << "\n"; + + if (!ControlFile) { + Printf("MERGE-OUTER: failed to write to the control file: %s\n", + CFPath.c_str()); + exit(1); + } + + return FilesToUse.size(); +} + +// Outer process. Does not call the target code and thus should not fail. +void CrashResistantMerge(const Vector &Args, + const Vector &OldCorpus, + const Vector &NewCorpus, + Vector *NewFiles, + const Set &InitialFeatures, + Set *NewFeatures, + const Set &InitialCov, + Set *NewCov, + const std::string &CFPath, + bool V /*Verbose*/) { + if (NewCorpus.empty() && OldCorpus.empty()) return; // Nothing to merge. + size_t NumAttempts = 0; + Vector KnownFiles; + if (FileSize(CFPath)) { + VPrintf(V, "MERGE-OUTER: non-empty control file provided: '%s'\n", + CFPath.c_str()); + Merger M; + std::ifstream IF(CFPath); + if (M.Parse(IF, /*ParseCoverage=*/true)) { + VPrintf(V, "MERGE-OUTER: control file ok, %zd files total," + " first not processed file %zd\n", + M.Files.size(), M.FirstNotProcessedFile); + if (!M.LastFailure.empty()) + VPrintf(V, "MERGE-OUTER: '%s' will be skipped as unlucky " + "(merge has stumbled on it the last time)\n", + M.LastFailure.c_str()); + if (M.FirstNotProcessedFile >= M.Files.size()) { + // Merge has already been completed with the given merge control file. + if (M.Files.size() == OldCorpus.size() + NewCorpus.size()) { + VPrintf( + V, + "MERGE-OUTER: nothing to do, merge has been completed before\n"); + exit(0); + } + + // Number of input files likely changed, start merge from scratch, but + // reuse coverage information from the given merge control file. + VPrintf( + V, + "MERGE-OUTER: starting merge from scratch, but reusing coverage " + "information from the given control file\n"); + KnownFiles = M.Files; + } else { + // There is a merge in progress, continue. + NumAttempts = M.Files.size() - M.FirstNotProcessedFile; + } + } else { + VPrintf(V, "MERGE-OUTER: bad control file, will overwrite it\n"); + } + } + + if (!NumAttempts) { + // The supplied control file is empty or bad, create a fresh one. + VPrintf(V, "MERGE-OUTER: " + "%zd files, %zd in the initial corpus, %zd processed earlier\n", + OldCorpus.size() + NewCorpus.size(), OldCorpus.size(), + KnownFiles.size()); + NumAttempts = WriteNewControlFile(CFPath, OldCorpus, NewCorpus, KnownFiles); + } + + // Execute the inner process until it passes. + // Every inner process should execute at least one input. + Command BaseCmd(Args); + BaseCmd.removeFlag("merge"); + BaseCmd.removeFlag("fork"); + BaseCmd.removeFlag("collect_data_flow"); + for (size_t Attempt = 1; Attempt <= NumAttempts; Attempt++) { + Fuzzer::MaybeExitGracefully(); + VPrintf(V, "MERGE-OUTER: attempt %zd\n", Attempt); + Command Cmd(BaseCmd); + Cmd.addFlag("merge_control_file", CFPath); + Cmd.addFlag("merge_inner", "1"); + if (!V) { + Cmd.setOutputFile(getDevNull()); + Cmd.combineOutAndErr(); + } + auto ExitCode = ExecuteCommand(Cmd); + if (!ExitCode) { + VPrintf(V, "MERGE-OUTER: succesfull in %zd attempt(s)\n", Attempt); + break; + } + } + // Read the control file and do the merge. + Merger M; + std::ifstream IF(CFPath); + IF.seekg(0, IF.end); + VPrintf(V, "MERGE-OUTER: the control file has %zd bytes\n", + (size_t)IF.tellg()); + IF.seekg(0, IF.beg); + M.ParseOrExit(IF, true); + IF.close(); + VPrintf(V, + "MERGE-OUTER: consumed %zdMb (%zdMb rss) to parse the control file\n", + M.ApproximateMemoryConsumption() >> 20, GetPeakRSSMb()); + + M.Files.insert(M.Files.end(), KnownFiles.begin(), KnownFiles.end()); + M.Merge(InitialFeatures, NewFeatures, InitialCov, NewCov, NewFiles); + VPrintf(V, "MERGE-OUTER: %zd new files with %zd new features added; " + "%zd new coverage edges\n", + NewFiles->size(), NewFeatures->size(), NewCov->size()); +} + +} // namespace fuzzer diff --git a/core/lib/libfuzzer-sys/libfuzzer/FuzzerMerge.h b/core/lib/libfuzzer-sys/libfuzzer/FuzzerMerge.h new file mode 100644 index 0000000000..e0c6bc539b --- /dev/null +++ b/core/lib/libfuzzer-sys/libfuzzer/FuzzerMerge.h @@ -0,0 +1,87 @@ +//===- FuzzerMerge.h - merging corpa ----------------------------*- C++ -* ===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// Merging Corpora. +// +// The task: +// Take the existing corpus (possibly empty) and merge new inputs into +// it so that only inputs with new coverage ('features') are added. +// The process should tolerate the crashes, OOMs, leaks, etc. +// +// Algorithm: +// The outer process collects the set of files and writes their names +// into a temporary "control" file, then repeatedly launches the inner +// process until all inputs are processed. +// The outer process does not actually execute the target code. +// +// The inner process reads the control file and sees a) list of all the inputs +// and b) the last processed input. Then it starts processing the inputs one +// by one. Before processing every input it writes one line to control file: +// STARTED INPUT_ID INPUT_SIZE +// After processing an input it writes the following lines: +// FT INPUT_ID Feature1 Feature2 Feature3 ... +// COV INPUT_ID Coverage1 Coverage2 Coverage3 ... +// If a crash happens while processing an input the last line in the control +// file will be "STARTED INPUT_ID" and so the next process will know +// where to resume. +// +// Once all inputs are processed by the inner process(es) the outer process +// reads the control files and does the merge based entirely on the contents +// of control file. +// It uses a single pass greedy algorithm choosing first the smallest inputs +// within the same size the inputs that have more new features. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_FUZZER_MERGE_H +#define LLVM_FUZZER_MERGE_H + +#include "FuzzerDefs.h" + +#include +#include +#include +#include + +namespace fuzzer { + +struct MergeFileInfo { + std::string Name; + size_t Size = 0; + Vector Features, Cov; +}; + +struct Merger { + Vector Files; + size_t NumFilesInFirstCorpus = 0; + size_t FirstNotProcessedFile = 0; + std::string LastFailure; + + bool Parse(std::istream &IS, bool ParseCoverage); + bool Parse(const std::string &Str, bool ParseCoverage); + void ParseOrExit(std::istream &IS, bool ParseCoverage); + size_t Merge(const Set &InitialFeatures, Set *NewFeatures, + const Set &InitialCov, Set *NewCov, + Vector *NewFiles); + size_t ApproximateMemoryConsumption() const; + Set AllFeatures() const; +}; + +void CrashResistantMerge(const Vector &Args, + const Vector &OldCorpus, + const Vector &NewCorpus, + Vector *NewFiles, + const Set &InitialFeatures, + Set *NewFeatures, + const Set &InitialCov, + Set *NewCov, + const std::string &CFPath, + bool Verbose); + +} // namespace fuzzer + +#endif // LLVM_FUZZER_MERGE_H diff --git a/core/lib/libfuzzer-sys/libfuzzer/FuzzerMutate.cpp b/core/lib/libfuzzer-sys/libfuzzer/FuzzerMutate.cpp new file mode 100644 index 0000000000..4650f1bece --- /dev/null +++ b/core/lib/libfuzzer-sys/libfuzzer/FuzzerMutate.cpp @@ -0,0 +1,596 @@ +//===- FuzzerMutate.cpp - Mutate a test input -----------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// Mutate a test input. +//===----------------------------------------------------------------------===// + +#include "FuzzerDefs.h" +#include "FuzzerExtFunctions.h" +#include "FuzzerIO.h" +#include "FuzzerMutate.h" +#include "FuzzerOptions.h" +#include "FuzzerTracePC.h" + +namespace fuzzer { + +const size_t Dictionary::kMaxDictSize; +static const size_t kMaxMutationsToPrint = 10; + +static void PrintASCII(const Word &W, const char *PrintAfter) { + PrintASCII(W.data(), W.size(), PrintAfter); +} + +MutationDispatcher::MutationDispatcher(Random &Rand, + const FuzzingOptions &Options) + : Rand(Rand), Options(Options) { + DefaultMutators.insert( + DefaultMutators.begin(), + { + {&MutationDispatcher::Mutate_EraseBytes, "EraseBytes"}, + {&MutationDispatcher::Mutate_InsertByte, "InsertByte"}, + {&MutationDispatcher::Mutate_InsertRepeatedBytes, + "InsertRepeatedBytes"}, + {&MutationDispatcher::Mutate_ChangeByte, "ChangeByte"}, + {&MutationDispatcher::Mutate_ChangeBit, "ChangeBit"}, + {&MutationDispatcher::Mutate_ShuffleBytes, "ShuffleBytes"}, + {&MutationDispatcher::Mutate_ChangeASCIIInteger, "ChangeASCIIInt"}, + {&MutationDispatcher::Mutate_ChangeBinaryInteger, "ChangeBinInt"}, + {&MutationDispatcher::Mutate_CopyPart, "CopyPart"}, + {&MutationDispatcher::Mutate_CrossOver, "CrossOver"}, + {&MutationDispatcher::Mutate_AddWordFromManualDictionary, + "ManualDict"}, + {&MutationDispatcher::Mutate_AddWordFromPersistentAutoDictionary, + "PersAutoDict"}, + }); + if(Options.UseCmp) + DefaultMutators.push_back( + {&MutationDispatcher::Mutate_AddWordFromTORC, "CMP"}); + + if (EF->LLVMFuzzerCustomMutator) + Mutators.push_back({&MutationDispatcher::Mutate_Custom, "Custom"}); + else + Mutators = DefaultMutators; + + if (EF->LLVMFuzzerCustomCrossOver) + Mutators.push_back( + {&MutationDispatcher::Mutate_CustomCrossOver, "CustomCrossOver"}); +} + +static char RandCh(Random &Rand) { + if (Rand.RandBool()) + return static_cast(Rand(256)); + const char Special[] = "!*'();:@&=+$,/?%#[]012Az-`~.\xff\x00"; + return Special[Rand(sizeof(Special) - 1)]; +} + +size_t MutationDispatcher::Mutate_Custom(uint8_t *Data, size_t Size, + size_t MaxSize) { + if (EF->__msan_unpoison) + EF->__msan_unpoison(Data, Size); + if (EF->__msan_unpoison_param) + EF->__msan_unpoison_param(4); + return EF->LLVMFuzzerCustomMutator(Data, Size, MaxSize, + Rand.Rand()); +} + +size_t MutationDispatcher::Mutate_CustomCrossOver(uint8_t *Data, size_t Size, + size_t MaxSize) { + if (Size == 0) + return 0; + if (!CrossOverWith) return 0; + const Unit &Other = *CrossOverWith; + if (Other.empty()) + return 0; + CustomCrossOverInPlaceHere.resize(MaxSize); + auto &U = CustomCrossOverInPlaceHere; + + if (EF->__msan_unpoison) { + EF->__msan_unpoison(Data, Size); + EF->__msan_unpoison(Other.data(), Other.size()); + EF->__msan_unpoison(U.data(), U.size()); + } + if (EF->__msan_unpoison_param) + EF->__msan_unpoison_param(7); + size_t NewSize = EF->LLVMFuzzerCustomCrossOver( + Data, Size, Other.data(), Other.size(), U.data(), U.size(), + Rand.Rand()); + + if (!NewSize) + return 0; + assert(NewSize <= MaxSize && "CustomCrossOver returned overisized unit"); + memcpy(Data, U.data(), NewSize); + return NewSize; +} + +size_t MutationDispatcher::Mutate_ShuffleBytes(uint8_t *Data, size_t Size, + size_t MaxSize) { + if (Size > MaxSize || Size == 0) return 0; + size_t ShuffleAmount = + Rand(std::min(Size, (size_t)8)) + 1; // [1,8] and <= Size. + size_t ShuffleStart = Rand(Size - ShuffleAmount); + assert(ShuffleStart + ShuffleAmount <= Size); + std::shuffle(Data + ShuffleStart, Data + ShuffleStart + ShuffleAmount, Rand); + return Size; +} + +size_t MutationDispatcher::Mutate_EraseBytes(uint8_t *Data, size_t Size, + size_t MaxSize) { + if (Size <= 1) return 0; + size_t N = Rand(Size / 2) + 1; + assert(N < Size); + size_t Idx = Rand(Size - N + 1); + // Erase Data[Idx:Idx+N]. + memmove(Data + Idx, Data + Idx + N, Size - Idx - N); + // Printf("Erase: %zd %zd => %zd; Idx %zd\n", N, Size, Size - N, Idx); + return Size - N; +} + +size_t MutationDispatcher::Mutate_InsertByte(uint8_t *Data, size_t Size, + size_t MaxSize) { + if (Size >= MaxSize) return 0; + size_t Idx = Rand(Size + 1); + // Insert new value at Data[Idx]. + memmove(Data + Idx + 1, Data + Idx, Size - Idx); + Data[Idx] = RandCh(Rand); + return Size + 1; +} + +size_t MutationDispatcher::Mutate_InsertRepeatedBytes(uint8_t *Data, + size_t Size, + size_t MaxSize) { + const size_t kMinBytesToInsert = 3; + if (Size + kMinBytesToInsert >= MaxSize) return 0; + size_t MaxBytesToInsert = std::min(MaxSize - Size, (size_t)128); + size_t N = Rand(MaxBytesToInsert - kMinBytesToInsert + 1) + kMinBytesToInsert; + assert(Size + N <= MaxSize && N); + size_t Idx = Rand(Size + 1); + // Insert new values at Data[Idx]. + memmove(Data + Idx + N, Data + Idx, Size - Idx); + // Give preference to 0x00 and 0xff. + uint8_t Byte = static_cast( + Rand.RandBool() ? Rand(256) : (Rand.RandBool() ? 0 : 255)); + for (size_t i = 0; i < N; i++) + Data[Idx + i] = Byte; + return Size + N; +} + +size_t MutationDispatcher::Mutate_ChangeByte(uint8_t *Data, size_t Size, + size_t MaxSize) { + if (Size > MaxSize) return 0; + size_t Idx = Rand(Size); + Data[Idx] = RandCh(Rand); + return Size; +} + +size_t MutationDispatcher::Mutate_ChangeBit(uint8_t *Data, size_t Size, + size_t MaxSize) { + if (Size > MaxSize) return 0; + size_t Idx = Rand(Size); + Data[Idx] ^= 1 << Rand(8); + return Size; +} + +size_t MutationDispatcher::Mutate_AddWordFromManualDictionary(uint8_t *Data, + size_t Size, + size_t MaxSize) { + return AddWordFromDictionary(ManualDictionary, Data, Size, MaxSize); +} + +size_t MutationDispatcher::ApplyDictionaryEntry(uint8_t *Data, size_t Size, + size_t MaxSize, + DictionaryEntry &DE) { + const Word &W = DE.GetW(); + bool UsePositionHint = DE.HasPositionHint() && + DE.GetPositionHint() + W.size() < Size && + Rand.RandBool(); + if (Rand.RandBool()) { // Insert W. + if (Size + W.size() > MaxSize) return 0; + size_t Idx = UsePositionHint ? DE.GetPositionHint() : Rand(Size + 1); + memmove(Data + Idx + W.size(), Data + Idx, Size - Idx); + memcpy(Data + Idx, W.data(), W.size()); + Size += W.size(); + } else { // Overwrite some bytes with W. + if (W.size() > Size) return 0; + size_t Idx = + UsePositionHint ? DE.GetPositionHint() : Rand(Size + 1 - W.size()); + memcpy(Data + Idx, W.data(), W.size()); + } + return Size; +} + +// Somewhere in the past we have observed a comparison instructions +// with arguments Arg1 Arg2. This function tries to guess a dictionary +// entry that will satisfy that comparison. +// It first tries to find one of the arguments (possibly swapped) in the +// input and if it succeeds it creates a DE with a position hint. +// Otherwise it creates a DE with one of the arguments w/o a position hint. +DictionaryEntry MutationDispatcher::MakeDictionaryEntryFromCMP( + const void *Arg1, const void *Arg2, + const void *Arg1Mutation, const void *Arg2Mutation, + size_t ArgSize, const uint8_t *Data, + size_t Size) { + bool HandleFirst = Rand.RandBool(); + const void *ExistingBytes, *DesiredBytes; + Word W; + const uint8_t *End = Data + Size; + for (int Arg = 0; Arg < 2; Arg++) { + ExistingBytes = HandleFirst ? Arg1 : Arg2; + DesiredBytes = HandleFirst ? Arg2Mutation : Arg1Mutation; + HandleFirst = !HandleFirst; + W.Set(reinterpret_cast(DesiredBytes), ArgSize); + const size_t kMaxNumPositions = 8; + size_t Positions[kMaxNumPositions]; + size_t NumPositions = 0; + for (const uint8_t *Cur = Data; + Cur < End && NumPositions < kMaxNumPositions; Cur++) { + Cur = + (const uint8_t *)SearchMemory(Cur, End - Cur, ExistingBytes, ArgSize); + if (!Cur) break; + Positions[NumPositions++] = Cur - Data; + } + if (!NumPositions) continue; + return DictionaryEntry(W, Positions[Rand(NumPositions)]); + } + DictionaryEntry DE(W); + return DE; +} + + +template +DictionaryEntry MutationDispatcher::MakeDictionaryEntryFromCMP( + T Arg1, T Arg2, const uint8_t *Data, size_t Size) { + if (Rand.RandBool()) Arg1 = Bswap(Arg1); + if (Rand.RandBool()) Arg2 = Bswap(Arg2); + T Arg1Mutation = static_cast(Arg1 + Rand(-1, 1)); + T Arg2Mutation = static_cast(Arg2 + Rand(-1, 1)); + return MakeDictionaryEntryFromCMP(&Arg1, &Arg2, &Arg1Mutation, &Arg2Mutation, + sizeof(Arg1), Data, Size); +} + +DictionaryEntry MutationDispatcher::MakeDictionaryEntryFromCMP( + const Word &Arg1, const Word &Arg2, const uint8_t *Data, size_t Size) { + return MakeDictionaryEntryFromCMP(Arg1.data(), Arg2.data(), Arg1.data(), + Arg2.data(), Arg1.size(), Data, Size); +} + +size_t MutationDispatcher::Mutate_AddWordFromTORC( + uint8_t *Data, size_t Size, size_t MaxSize) { + Word W; + DictionaryEntry DE; + switch (Rand(4)) { + case 0: { + auto X = TPC.TORC8.Get(Rand.Rand()); + DE = MakeDictionaryEntryFromCMP(X.A, X.B, Data, Size); + } break; + case 1: { + auto X = TPC.TORC4.Get(Rand.Rand()); + if ((X.A >> 16) == 0 && (X.B >> 16) == 0 && Rand.RandBool()) + DE = MakeDictionaryEntryFromCMP((uint16_t)X.A, (uint16_t)X.B, Data, Size); + else + DE = MakeDictionaryEntryFromCMP(X.A, X.B, Data, Size); + } break; + case 2: { + auto X = TPC.TORCW.Get(Rand.Rand()); + DE = MakeDictionaryEntryFromCMP(X.A, X.B, Data, Size); + } break; + case 3: if (Options.UseMemmem) { + auto X = TPC.MMT.Get(Rand.Rand()); + DE = DictionaryEntry(X); + } break; + default: + assert(0); + } + if (!DE.GetW().size()) return 0; + Size = ApplyDictionaryEntry(Data, Size, MaxSize, DE); + if (!Size) return 0; + DictionaryEntry &DERef = + CmpDictionaryEntriesDeque[CmpDictionaryEntriesDequeIdx++ % + kCmpDictionaryEntriesDequeSize]; + DERef = DE; + CurrentDictionaryEntrySequence.push_back(&DERef); + return Size; +} + +size_t MutationDispatcher::Mutate_AddWordFromPersistentAutoDictionary( + uint8_t *Data, size_t Size, size_t MaxSize) { + return AddWordFromDictionary(PersistentAutoDictionary, Data, Size, MaxSize); +} + +size_t MutationDispatcher::AddWordFromDictionary(Dictionary &D, uint8_t *Data, + size_t Size, size_t MaxSize) { + if (Size > MaxSize) return 0; + if (D.empty()) return 0; + DictionaryEntry &DE = D[Rand(D.size())]; + Size = ApplyDictionaryEntry(Data, Size, MaxSize, DE); + if (!Size) return 0; + DE.IncUseCount(); + CurrentDictionaryEntrySequence.push_back(&DE); + return Size; +} + +// Overwrites part of To[0,ToSize) with a part of From[0,FromSize). +// Returns ToSize. +size_t MutationDispatcher::CopyPartOf(const uint8_t *From, size_t FromSize, + uint8_t *To, size_t ToSize) { + // Copy From[FromBeg, FromBeg + CopySize) into To[ToBeg, ToBeg + CopySize). + size_t ToBeg = Rand(ToSize); + size_t CopySize = Rand(ToSize - ToBeg) + 1; + assert(ToBeg + CopySize <= ToSize); + CopySize = std::min(CopySize, FromSize); + size_t FromBeg = Rand(FromSize - CopySize + 1); + assert(FromBeg + CopySize <= FromSize); + memmove(To + ToBeg, From + FromBeg, CopySize); + return ToSize; +} + +// Inserts part of From[0,ToSize) into To. +// Returns new size of To on success or 0 on failure. +size_t MutationDispatcher::InsertPartOf(const uint8_t *From, size_t FromSize, + uint8_t *To, size_t ToSize, + size_t MaxToSize) { + if (ToSize >= MaxToSize) return 0; + size_t AvailableSpace = MaxToSize - ToSize; + size_t MaxCopySize = std::min(AvailableSpace, FromSize); + size_t CopySize = Rand(MaxCopySize) + 1; + size_t FromBeg = Rand(FromSize - CopySize + 1); + assert(FromBeg + CopySize <= FromSize); + size_t ToInsertPos = Rand(ToSize + 1); + assert(ToInsertPos + CopySize <= MaxToSize); + size_t TailSize = ToSize - ToInsertPos; + if (To == From) { + MutateInPlaceHere.resize(MaxToSize); + memcpy(MutateInPlaceHere.data(), From + FromBeg, CopySize); + memmove(To + ToInsertPos + CopySize, To + ToInsertPos, TailSize); + memmove(To + ToInsertPos, MutateInPlaceHere.data(), CopySize); + } else { + memmove(To + ToInsertPos + CopySize, To + ToInsertPos, TailSize); + memmove(To + ToInsertPos, From + FromBeg, CopySize); + } + return ToSize + CopySize; +} + +size_t MutationDispatcher::Mutate_CopyPart(uint8_t *Data, size_t Size, + size_t MaxSize) { + if (Size > MaxSize || Size == 0) return 0; + // If Size == MaxSize, `InsertPartOf(...)` will + // fail so there's no point using it in this case. + if (Size == MaxSize || Rand.RandBool()) + return CopyPartOf(Data, Size, Data, Size); + else + return InsertPartOf(Data, Size, Data, Size, MaxSize); +} + +size_t MutationDispatcher::Mutate_ChangeASCIIInteger(uint8_t *Data, size_t Size, + size_t MaxSize) { + if (Size > MaxSize) return 0; + size_t B = Rand(Size); + while (B < Size && !isdigit(Data[B])) B++; + if (B == Size) return 0; + size_t E = B; + while (E < Size && isdigit(Data[E])) E++; + assert(B < E); + // now we have digits in [B, E). + // strtol and friends don't accept non-zero-teminated data, parse it manually. + uint64_t Val = Data[B] - '0'; + for (size_t i = B + 1; i < E; i++) + Val = Val * 10 + Data[i] - '0'; + + // Mutate the integer value. + switch(Rand(5)) { + case 0: Val++; break; + case 1: Val--; break; + case 2: Val /= 2; break; + case 3: Val *= 2; break; + case 4: Val = Rand(Val * Val); break; + default: assert(0); + } + // Just replace the bytes with the new ones, don't bother moving bytes. + for (size_t i = B; i < E; i++) { + size_t Idx = E + B - i - 1; + assert(Idx >= B && Idx < E); + Data[Idx] = (Val % 10) + '0'; + Val /= 10; + } + return Size; +} + +template +size_t ChangeBinaryInteger(uint8_t *Data, size_t Size, Random &Rand) { + if (Size < sizeof(T)) return 0; + size_t Off = Rand(Size - sizeof(T) + 1); + assert(Off + sizeof(T) <= Size); + T Val; + if (Off < 64 && !Rand(4)) { + Val = static_cast(Size); + if (Rand.RandBool()) + Val = Bswap(Val); + } else { + memcpy(&Val, Data + Off, sizeof(Val)); + T Add = static_cast(Rand(21)); + Add -= 10; + if (Rand.RandBool()) + Val = Bswap(T(Bswap(Val) + Add)); // Add assuming different endiannes. + else + Val = Val + Add; // Add assuming current endiannes. + if (Add == 0 || Rand.RandBool()) // Maybe negate. + Val = -Val; + } + memcpy(Data + Off, &Val, sizeof(Val)); + return Size; +} + +size_t MutationDispatcher::Mutate_ChangeBinaryInteger(uint8_t *Data, + size_t Size, + size_t MaxSize) { + if (Size > MaxSize) return 0; + switch (Rand(4)) { + case 3: return ChangeBinaryInteger(Data, Size, Rand); + case 2: return ChangeBinaryInteger(Data, Size, Rand); + case 1: return ChangeBinaryInteger(Data, Size, Rand); + case 0: return ChangeBinaryInteger(Data, Size, Rand); + default: assert(0); + } + return 0; +} + +size_t MutationDispatcher::Mutate_CrossOver(uint8_t *Data, size_t Size, + size_t MaxSize) { + if (Size > MaxSize) return 0; + if (Size == 0) return 0; + if (!CrossOverWith) return 0; + const Unit &O = *CrossOverWith; + if (O.empty()) return 0; + size_t NewSize = 0; + switch(Rand(3)) { + case 0: + MutateInPlaceHere.resize(MaxSize); + NewSize = CrossOver(Data, Size, O.data(), O.size(), + MutateInPlaceHere.data(), MaxSize); + memcpy(Data, MutateInPlaceHere.data(), NewSize); + break; + case 1: + NewSize = InsertPartOf(O.data(), O.size(), Data, Size, MaxSize); + if (!NewSize) + NewSize = CopyPartOf(O.data(), O.size(), Data, Size); + break; + case 2: + NewSize = CopyPartOf(O.data(), O.size(), Data, Size); + break; + default: assert(0); + } + assert(NewSize > 0 && "CrossOver returned empty unit"); + assert(NewSize <= MaxSize && "CrossOver returned overisized unit"); + return NewSize; +} + +void MutationDispatcher::StartMutationSequence() { + CurrentMutatorSequence.clear(); + CurrentDictionaryEntrySequence.clear(); +} + +// Copy successful dictionary entries to PersistentAutoDictionary. +void MutationDispatcher::RecordSuccessfulMutationSequence() { + for (auto DE : CurrentDictionaryEntrySequence) { + // PersistentAutoDictionary.AddWithSuccessCountOne(DE); + DE->IncSuccessCount(); + assert(DE->GetW().size()); + // Linear search is fine here as this happens seldom. + if (!PersistentAutoDictionary.ContainsWord(DE->GetW())) + PersistentAutoDictionary.push_back(*DE); + } +} + +void MutationDispatcher::PrintRecommendedDictionary() { + Vector V; + for (auto &DE : PersistentAutoDictionary) + if (!ManualDictionary.ContainsWord(DE.GetW())) + V.push_back(DE); + if (V.empty()) return; + Printf("###### Recommended dictionary. ######\n"); + for (auto &DE: V) { + assert(DE.GetW().size()); + Printf("\""); + PrintASCII(DE.GetW(), "\""); + Printf(" # Uses: %zd\n", DE.GetUseCount()); + } + Printf("###### End of recommended dictionary. ######\n"); +} + +void MutationDispatcher::PrintMutationSequence(bool Verbose) { + Printf("MS: %zd ", CurrentMutatorSequence.size()); + size_t EntriesToPrint = + Verbose ? CurrentMutatorSequence.size() + : std::min(kMaxMutationsToPrint, CurrentMutatorSequence.size()); + for (size_t i = 0; i < EntriesToPrint; i++) + Printf("%s-", CurrentMutatorSequence[i].Name); + if (!CurrentDictionaryEntrySequence.empty()) { + Printf(" DE: "); + EntriesToPrint = Verbose ? CurrentDictionaryEntrySequence.size() + : std::min(kMaxMutationsToPrint, + CurrentDictionaryEntrySequence.size()); + for (size_t i = 0; i < EntriesToPrint; i++) { + Printf("\""); + PrintASCII(CurrentDictionaryEntrySequence[i]->GetW(), "\"-"); + } + } +} + +std::string MutationDispatcher::MutationSequence() { + std::string MS; + for (auto M : CurrentMutatorSequence) { + MS += M.Name; + MS += "-"; + } + return MS; +} + +size_t MutationDispatcher::Mutate(uint8_t *Data, size_t Size, size_t MaxSize) { + return MutateImpl(Data, Size, MaxSize, Mutators); +} + +size_t MutationDispatcher::DefaultMutate(uint8_t *Data, size_t Size, + size_t MaxSize) { + return MutateImpl(Data, Size, MaxSize, DefaultMutators); +} + +// Mutates Data in place, returns new size. +size_t MutationDispatcher::MutateImpl(uint8_t *Data, size_t Size, + size_t MaxSize, + Vector &Mutators) { + assert(MaxSize > 0); + // Some mutations may fail (e.g. can't insert more bytes if Size == MaxSize), + // in which case they will return 0. + // Try several times before returning un-mutated data. + for (int Iter = 0; Iter < 100; Iter++) { + auto M = Mutators[Rand(Mutators.size())]; + size_t NewSize = (this->*(M.Fn))(Data, Size, MaxSize); + if (NewSize && NewSize <= MaxSize) { + if (Options.OnlyASCII) + ToASCII(Data, NewSize); + CurrentMutatorSequence.push_back(M); + return NewSize; + } + } + *Data = ' '; + return 1; // Fallback, should not happen frequently. +} + +// Mask represents the set of Data bytes that are worth mutating. +size_t MutationDispatcher::MutateWithMask(uint8_t *Data, size_t Size, + size_t MaxSize, + const Vector &Mask) { + size_t MaskedSize = std::min(Size, Mask.size()); + // * Copy the worthy bytes into a temporary array T + // * Mutate T + // * Copy T back. + // This is totally unoptimized. + auto &T = MutateWithMaskTemp; + if (T.size() < Size) + T.resize(Size); + size_t OneBits = 0; + for (size_t I = 0; I < MaskedSize; I++) + if (Mask[I]) + T[OneBits++] = Data[I]; + + if (!OneBits) return 0; + assert(!T.empty()); + size_t NewSize = Mutate(T.data(), OneBits, OneBits); + assert(NewSize <= OneBits); + (void)NewSize; + // Even if NewSize < OneBits we still use all OneBits bytes. + for (size_t I = 0, J = 0; I < MaskedSize; I++) + if (Mask[I]) + Data[I] = T[J++]; + return Size; +} + +void MutationDispatcher::AddWordToManualDictionary(const Word &W) { + ManualDictionary.push_back( + {W, std::numeric_limits::max()}); +} + +} // namespace fuzzer diff --git a/core/lib/libfuzzer-sys/libfuzzer/FuzzerMutate.h b/core/lib/libfuzzer-sys/libfuzzer/FuzzerMutate.h new file mode 100644 index 0000000000..fd37191156 --- /dev/null +++ b/core/lib/libfuzzer-sys/libfuzzer/FuzzerMutate.h @@ -0,0 +1,156 @@ +//===- FuzzerMutate.h - Internal header for the Fuzzer ----------*- C++ -* ===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// fuzzer::MutationDispatcher +//===----------------------------------------------------------------------===// + +#ifndef LLVM_FUZZER_MUTATE_H +#define LLVM_FUZZER_MUTATE_H + +#include "FuzzerDefs.h" +#include "FuzzerDictionary.h" +#include "FuzzerOptions.h" +#include "FuzzerRandom.h" + +namespace fuzzer { + +class MutationDispatcher { +public: + MutationDispatcher(Random &Rand, const FuzzingOptions &Options); + ~MutationDispatcher() {} + /// Indicate that we are about to start a new sequence of mutations. + void StartMutationSequence(); + /// Print the current sequence of mutations. Only prints the full sequence + /// when Verbose is true. + void PrintMutationSequence(bool Verbose = true); + /// Return the current sequence of mutations. + std::string MutationSequence(); + /// Indicate that the current sequence of mutations was successful. + void RecordSuccessfulMutationSequence(); + /// Mutates data by invoking user-provided mutator. + size_t Mutate_Custom(uint8_t *Data, size_t Size, size_t MaxSize); + /// Mutates data by invoking user-provided crossover. + size_t Mutate_CustomCrossOver(uint8_t *Data, size_t Size, size_t MaxSize); + /// Mutates data by shuffling bytes. + size_t Mutate_ShuffleBytes(uint8_t *Data, size_t Size, size_t MaxSize); + /// Mutates data by erasing bytes. + size_t Mutate_EraseBytes(uint8_t *Data, size_t Size, size_t MaxSize); + /// Mutates data by inserting a byte. + size_t Mutate_InsertByte(uint8_t *Data, size_t Size, size_t MaxSize); + /// Mutates data by inserting several repeated bytes. + size_t Mutate_InsertRepeatedBytes(uint8_t *Data, size_t Size, size_t MaxSize); + /// Mutates data by changing one byte. + size_t Mutate_ChangeByte(uint8_t *Data, size_t Size, size_t MaxSize); + /// Mutates data by changing one bit. + size_t Mutate_ChangeBit(uint8_t *Data, size_t Size, size_t MaxSize); + /// Mutates data by copying/inserting a part of data into a different place. + size_t Mutate_CopyPart(uint8_t *Data, size_t Size, size_t MaxSize); + + /// Mutates data by adding a word from the manual dictionary. + size_t Mutate_AddWordFromManualDictionary(uint8_t *Data, size_t Size, + size_t MaxSize); + + /// Mutates data by adding a word from the TORC. + size_t Mutate_AddWordFromTORC(uint8_t *Data, size_t Size, size_t MaxSize); + + /// Mutates data by adding a word from the persistent automatic dictionary. + size_t Mutate_AddWordFromPersistentAutoDictionary(uint8_t *Data, size_t Size, + size_t MaxSize); + + /// Tries to find an ASCII integer in Data, changes it to another ASCII int. + size_t Mutate_ChangeASCIIInteger(uint8_t *Data, size_t Size, size_t MaxSize); + /// Change a 1-, 2-, 4-, or 8-byte integer in interesting ways. + size_t Mutate_ChangeBinaryInteger(uint8_t *Data, size_t Size, size_t MaxSize); + + /// CrossOver Data with CrossOverWith. + size_t Mutate_CrossOver(uint8_t *Data, size_t Size, size_t MaxSize); + + /// Applies one of the configured mutations. + /// Returns the new size of data which could be up to MaxSize. + size_t Mutate(uint8_t *Data, size_t Size, size_t MaxSize); + + /// Applies one of the configured mutations to the bytes of Data + /// that have '1' in Mask. + /// Mask.size() should be >= Size. + size_t MutateWithMask(uint8_t *Data, size_t Size, size_t MaxSize, + const Vector &Mask); + + /// Applies one of the default mutations. Provided as a service + /// to mutation authors. + size_t DefaultMutate(uint8_t *Data, size_t Size, size_t MaxSize); + + /// Creates a cross-over of two pieces of Data, returns its size. + size_t CrossOver(const uint8_t *Data1, size_t Size1, const uint8_t *Data2, + size_t Size2, uint8_t *Out, size_t MaxOutSize); + + void AddWordToManualDictionary(const Word &W); + + void PrintRecommendedDictionary(); + + void SetCrossOverWith(const Unit *U) { CrossOverWith = U; } + + Random &GetRand() { return Rand; } + + private: + struct Mutator { + size_t (MutationDispatcher::*Fn)(uint8_t *Data, size_t Size, size_t Max); + const char *Name; + }; + + size_t AddWordFromDictionary(Dictionary &D, uint8_t *Data, size_t Size, + size_t MaxSize); + size_t MutateImpl(uint8_t *Data, size_t Size, size_t MaxSize, + Vector &Mutators); + + size_t InsertPartOf(const uint8_t *From, size_t FromSize, uint8_t *To, + size_t ToSize, size_t MaxToSize); + size_t CopyPartOf(const uint8_t *From, size_t FromSize, uint8_t *To, + size_t ToSize); + size_t ApplyDictionaryEntry(uint8_t *Data, size_t Size, size_t MaxSize, + DictionaryEntry &DE); + + template + DictionaryEntry MakeDictionaryEntryFromCMP(T Arg1, T Arg2, + const uint8_t *Data, size_t Size); + DictionaryEntry MakeDictionaryEntryFromCMP(const Word &Arg1, const Word &Arg2, + const uint8_t *Data, size_t Size); + DictionaryEntry MakeDictionaryEntryFromCMP(const void *Arg1, const void *Arg2, + const void *Arg1Mutation, + const void *Arg2Mutation, + size_t ArgSize, + const uint8_t *Data, size_t Size); + + Random &Rand; + const FuzzingOptions Options; + + // Dictionary provided by the user via -dict=DICT_FILE. + Dictionary ManualDictionary; + // Persistent dictionary modified by the fuzzer, consists of + // entries that led to successful discoveries in the past mutations. + Dictionary PersistentAutoDictionary; + + Vector CurrentDictionaryEntrySequence; + + static const size_t kCmpDictionaryEntriesDequeSize = 16; + DictionaryEntry CmpDictionaryEntriesDeque[kCmpDictionaryEntriesDequeSize]; + size_t CmpDictionaryEntriesDequeIdx = 0; + + const Unit *CrossOverWith = nullptr; + Vector MutateInPlaceHere; + Vector MutateWithMaskTemp; + // CustomCrossOver needs its own buffer as a custom implementation may call + // LLVMFuzzerMutate, which in turn may resize MutateInPlaceHere. + Vector CustomCrossOverInPlaceHere; + + Vector Mutators; + Vector DefaultMutators; + Vector CurrentMutatorSequence; +}; + +} // namespace fuzzer + +#endif // LLVM_FUZZER_MUTATE_H diff --git a/core/lib/libfuzzer-sys/libfuzzer/FuzzerOptions.h b/core/lib/libfuzzer-sys/libfuzzer/FuzzerOptions.h new file mode 100644 index 0000000000..d0c285a682 --- /dev/null +++ b/core/lib/libfuzzer-sys/libfuzzer/FuzzerOptions.h @@ -0,0 +1,92 @@ +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// fuzzer::FuzzingOptions +//===----------------------------------------------------------------------===// + +#ifndef LLVM_FUZZER_OPTIONS_H +#define LLVM_FUZZER_OPTIONS_H + +#include "FuzzerDefs.h" + +namespace fuzzer { + +struct FuzzingOptions { + int Verbosity = 1; + size_t MaxLen = 0; + size_t LenControl = 1000; + bool KeepSeed = false; + int UnitTimeoutSec = 300; + int TimeoutExitCode = 70; + int OOMExitCode = 71; + int InterruptExitCode = 72; + int ErrorExitCode = 77; + bool IgnoreTimeouts = true; + bool IgnoreOOMs = true; + bool IgnoreCrashes = false; + int MaxTotalTimeSec = 0; + int RssLimitMb = 0; + int MallocLimitMb = 0; + bool DoCrossOver = true; + bool CrossOverUniformDist = false; + int MutateDepth = 5; + bool ReduceDepth = false; + bool UseCounters = false; + bool UseMemmem = true; + bool UseCmp = false; + int UseValueProfile = false; + bool Shrink = false; + bool ReduceInputs = false; + int ReloadIntervalSec = 1; + bool ShuffleAtStartUp = true; + bool PreferSmall = true; + size_t MaxNumberOfRuns = -1L; + int ReportSlowUnits = 10; + bool OnlyASCII = false; + bool Entropic = true; + size_t EntropicFeatureFrequencyThreshold = 0xFF; + size_t EntropicNumberOfRarestFeatures = 100; + bool EntropicScalePerExecTime = false; + std::string OutputCorpus; + std::string ArtifactPrefix = "./"; + std::string ExactArtifactPath; + std::string ExitOnSrcPos; + std::string ExitOnItem; + std::string FocusFunction; + std::string DataFlowTrace; + std::string CollectDataFlow; + std::string FeaturesDir; + std::string MutationGraphFile; + std::string StopFile; + bool SaveArtifacts = true; + bool PrintNEW = true; // Print a status line when new units are found; + bool PrintNewCovPcs = false; + int PrintNewCovFuncs = 0; + bool PrintFinalStats = false; + bool PrintCorpusStats = false; + bool PrintCoverage = false; + bool PrintFullCoverage = false; + bool DumpCoverage = false; + bool DetectLeaks = true; + int PurgeAllocatorIntervalSec = 1; + int TraceMalloc = 0; + bool HandleAbrt = false; + bool HandleAlrm = false; + bool HandleBus = false; + bool HandleFpe = false; + bool HandleIll = false; + bool HandleInt = false; + bool HandleSegv = false; + bool HandleTerm = false; + bool HandleXfsz = false; + bool HandleUsr1 = false; + bool HandleUsr2 = false; + bool HandleWinExcept = false; +}; + +} // namespace fuzzer + +#endif // LLVM_FUZZER_OPTIONS_H diff --git a/core/lib/libfuzzer-sys/libfuzzer/FuzzerPlatform.h b/core/lib/libfuzzer-sys/libfuzzer/FuzzerPlatform.h new file mode 100644 index 0000000000..1602e67895 --- /dev/null +++ b/core/lib/libfuzzer-sys/libfuzzer/FuzzerPlatform.h @@ -0,0 +1,147 @@ +//===-- FuzzerPlatform.h --------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// Common platform macros. +//===----------------------------------------------------------------------===// + +#ifndef LLVM_FUZZER_PLATFORM_H +#define LLVM_FUZZER_PLATFORM_H + +// Platform detection. +#ifdef __linux__ +#define LIBFUZZER_APPLE 0 +#define LIBFUZZER_FUCHSIA 0 +#define LIBFUZZER_LINUX 1 +#define LIBFUZZER_NETBSD 0 +#define LIBFUZZER_FREEBSD 0 +#define LIBFUZZER_WINDOWS 0 +#define LIBFUZZER_EMSCRIPTEN 0 +#elif __APPLE__ +#define LIBFUZZER_APPLE 1 +#define LIBFUZZER_FUCHSIA 0 +#define LIBFUZZER_LINUX 0 +#define LIBFUZZER_NETBSD 0 +#define LIBFUZZER_FREEBSD 0 +#define LIBFUZZER_WINDOWS 0 +#define LIBFUZZER_EMSCRIPTEN 0 +#elif __NetBSD__ +#define LIBFUZZER_APPLE 0 +#define LIBFUZZER_FUCHSIA 0 +#define LIBFUZZER_LINUX 0 +#define LIBFUZZER_NETBSD 1 +#define LIBFUZZER_FREEBSD 0 +#define LIBFUZZER_WINDOWS 0 +#define LIBFUZZER_EMSCRIPTEN 0 +#elif __FreeBSD__ +#define LIBFUZZER_APPLE 0 +#define LIBFUZZER_FUCHSIA 0 +#define LIBFUZZER_LINUX 0 +#define LIBFUZZER_NETBSD 0 +#define LIBFUZZER_FREEBSD 1 +#define LIBFUZZER_WINDOWS 0 +#define LIBFUZZER_EMSCRIPTEN 0 +#elif _WIN32 +#define LIBFUZZER_APPLE 0 +#define LIBFUZZER_FUCHSIA 0 +#define LIBFUZZER_LINUX 0 +#define LIBFUZZER_NETBSD 0 +#define LIBFUZZER_FREEBSD 0 +#define LIBFUZZER_WINDOWS 1 +#define LIBFUZZER_EMSCRIPTEN 0 +#elif __Fuchsia__ +#define LIBFUZZER_APPLE 0 +#define LIBFUZZER_FUCHSIA 1 +#define LIBFUZZER_LINUX 0 +#define LIBFUZZER_NETBSD 0 +#define LIBFUZZER_FREEBSD 0 +#define LIBFUZZER_WINDOWS 0 +#define LIBFUZZER_EMSCRIPTEN 0 +#elif __EMSCRIPTEN__ +#define LIBFUZZER_APPLE 0 +#define LIBFUZZER_FUCHSIA 0 +#define LIBFUZZER_LINUX 0 +#define LIBFUZZER_NETBSD 0 +#define LIBFUZZER_FREEBSD 0 +#define LIBFUZZER_WINDOWS 0 +#define LIBFUZZER_EMSCRIPTEN 1 +#else +#error "Support for your platform has not been implemented" +#endif + +#if defined(_MSC_VER) && !defined(__clang__) +// MSVC compiler is being used. +#define LIBFUZZER_MSVC 1 +#else +#define LIBFUZZER_MSVC 0 +#endif + +#ifndef __has_attribute +#define __has_attribute(x) 0 +#endif + +#define LIBFUZZER_POSIX \ + (LIBFUZZER_APPLE || LIBFUZZER_LINUX || LIBFUZZER_NETBSD || \ + LIBFUZZER_FREEBSD || LIBFUZZER_EMSCRIPTEN) + +#ifdef __x86_64 +#if __has_attribute(target) +#define ATTRIBUTE_TARGET_POPCNT __attribute__((target("popcnt"))) +#else +#define ATTRIBUTE_TARGET_POPCNT +#endif +#else +#define ATTRIBUTE_TARGET_POPCNT +#endif + +#ifdef __clang__ // avoid gcc warning. +#if __has_attribute(no_sanitize) +#define ATTRIBUTE_NO_SANITIZE_MEMORY __attribute__((no_sanitize("memory"))) +#else +#define ATTRIBUTE_NO_SANITIZE_MEMORY +#endif +#define ALWAYS_INLINE __attribute__((always_inline)) +#else +#define ATTRIBUTE_NO_SANITIZE_MEMORY +#define ALWAYS_INLINE +#endif // __clang__ + +#if LIBFUZZER_WINDOWS +#define ATTRIBUTE_NO_SANITIZE_ADDRESS +#else +#define ATTRIBUTE_NO_SANITIZE_ADDRESS __attribute__((no_sanitize_address)) +#endif + +#if LIBFUZZER_WINDOWS +#define ATTRIBUTE_ALIGNED(X) __declspec(align(X)) +#define ATTRIBUTE_INTERFACE __declspec(dllexport) +// This is used for __sancov_lowest_stack which is needed for +// -fsanitize-coverage=stack-depth. That feature is not yet available on +// Windows, so make the symbol static to avoid linking errors. +#define ATTRIBUTES_INTERFACE_TLS_INITIAL_EXEC static +#define ATTRIBUTE_NOINLINE __declspec(noinline) +#else +#define ATTRIBUTE_ALIGNED(X) __attribute__((aligned(X))) +#define ATTRIBUTE_INTERFACE __attribute__((visibility("default"))) +#define ATTRIBUTES_INTERFACE_TLS_INITIAL_EXEC \ + ATTRIBUTE_INTERFACE __attribute__((tls_model("initial-exec"))) thread_local + +#define ATTRIBUTE_NOINLINE __attribute__((noinline)) +#endif + +#if defined(__has_feature) +#if __has_feature(address_sanitizer) +#define ATTRIBUTE_NO_SANITIZE_ALL ATTRIBUTE_NO_SANITIZE_ADDRESS +#elif __has_feature(memory_sanitizer) +#define ATTRIBUTE_NO_SANITIZE_ALL ATTRIBUTE_NO_SANITIZE_MEMORY +#else +#define ATTRIBUTE_NO_SANITIZE_ALL +#endif +#else +#define ATTRIBUTE_NO_SANITIZE_ALL +#endif + +#endif // LLVM_FUZZER_PLATFORM_H diff --git a/core/lib/libfuzzer-sys/libfuzzer/FuzzerRandom.h b/core/lib/libfuzzer-sys/libfuzzer/FuzzerRandom.h new file mode 100644 index 0000000000..ad6c07eb5e --- /dev/null +++ b/core/lib/libfuzzer-sys/libfuzzer/FuzzerRandom.h @@ -0,0 +1,47 @@ +//===- FuzzerRandom.h - Internal header for the Fuzzer ----------*- C++ -* ===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// fuzzer::Random +//===----------------------------------------------------------------------===// + +#ifndef LLVM_FUZZER_RANDOM_H +#define LLVM_FUZZER_RANDOM_H + +#include + +namespace fuzzer { +class Random : public std::minstd_rand { + public: + Random(unsigned int seed) : std::minstd_rand(seed) {} + result_type operator()() { return this->std::minstd_rand::operator()(); } + template + typename std::enable_if::value, T>::type Rand() { + return static_cast(this->operator()()); + } + size_t RandBool() { return this->operator()() % 2; } + size_t SkewTowardsLast(size_t n) { + size_t T = this->operator()(n * n); + size_t Res = static_cast(sqrt(T)); + return Res; + } + template + typename std::enable_if::value, T>::type operator()(T n) { + return n ? Rand() % n : 0; + } + template + typename std::enable_if::value, T>::type + operator()(T From, T To) { + assert(From < To); + auto RangeSize = static_cast(To) - + static_cast(From) + 1; + return static_cast(this->operator()(RangeSize) + From); + } +}; + +} // namespace fuzzer + +#endif // LLVM_FUZZER_RANDOM_H diff --git a/core/lib/libfuzzer-sys/libfuzzer/FuzzerSHA1.cpp b/core/lib/libfuzzer-sys/libfuzzer/FuzzerSHA1.cpp new file mode 100644 index 0000000000..f2315ceb63 --- /dev/null +++ b/core/lib/libfuzzer-sys/libfuzzer/FuzzerSHA1.cpp @@ -0,0 +1,224 @@ +//===- FuzzerSHA1.h - Private copy of the SHA1 implementation ---*- C++ -* ===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// This code is taken from public domain +// (http://oauth.googlecode.com/svn/code/c/liboauth/src/sha1.c) +// and modified by adding anonymous namespace, adding an interface +// function fuzzer::ComputeSHA1() and removing unnecessary code. +// +// lib/Fuzzer can not use SHA1 implementation from openssl because +// openssl may not be available and because we may be fuzzing openssl itself. +// For the same reason we do not want to depend on SHA1 from LLVM tree. +//===----------------------------------------------------------------------===// + +#include "FuzzerSHA1.h" +#include "FuzzerDefs.h" +#include "FuzzerPlatform.h" + +/* This code is public-domain - it is based on libcrypt + * placed in the public domain by Wei Dai and other contributors. + */ + +#include +#include +#include +#include + +namespace { // Added for LibFuzzer + +#ifdef __BIG_ENDIAN__ +# define SHA_BIG_ENDIAN +// Windows is always little endian and MSVC doesn't have +#elif defined __LITTLE_ENDIAN__ || LIBFUZZER_WINDOWS +/* override */ +#elif defined __BYTE_ORDER +# if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ +# define SHA_BIG_ENDIAN +# endif +#else // ! defined __LITTLE_ENDIAN__ +# include // machine/endian.h +# if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ +# define SHA_BIG_ENDIAN +# endif +#endif + + +/* header */ + +#define HASH_LENGTH 20 +#define BLOCK_LENGTH 64 + +typedef struct sha1nfo { + uint32_t buffer[BLOCK_LENGTH/4]; + uint32_t state[HASH_LENGTH/4]; + uint32_t byteCount; + uint8_t bufferOffset; + uint8_t keyBuffer[BLOCK_LENGTH]; + uint8_t innerHash[HASH_LENGTH]; +} sha1nfo; + +/* public API - prototypes - TODO: doxygen*/ + +/** + */ +void sha1_init(sha1nfo *s); +/** + */ +void sha1_writebyte(sha1nfo *s, uint8_t data); +/** + */ +void sha1_write(sha1nfo *s, const char *data, size_t len); +/** + */ +uint8_t* sha1_result(sha1nfo *s); + + +/* code */ +#define SHA1_K0 0x5a827999 +#define SHA1_K20 0x6ed9eba1 +#define SHA1_K40 0x8f1bbcdc +#define SHA1_K60 0xca62c1d6 + +void sha1_init(sha1nfo *s) { + s->state[0] = 0x67452301; + s->state[1] = 0xefcdab89; + s->state[2] = 0x98badcfe; + s->state[3] = 0x10325476; + s->state[4] = 0xc3d2e1f0; + s->byteCount = 0; + s->bufferOffset = 0; +} + +uint32_t sha1_rol32(uint32_t number, uint8_t bits) { + return ((number << bits) | (number >> (32-bits))); +} + +void sha1_hashBlock(sha1nfo *s) { + uint8_t i; + uint32_t a,b,c,d,e,t; + + a=s->state[0]; + b=s->state[1]; + c=s->state[2]; + d=s->state[3]; + e=s->state[4]; + for (i=0; i<80; i++) { + if (i>=16) { + t = s->buffer[(i+13)&15] ^ s->buffer[(i+8)&15] ^ s->buffer[(i+2)&15] ^ s->buffer[i&15]; + s->buffer[i&15] = sha1_rol32(t,1); + } + if (i<20) { + t = (d ^ (b & (c ^ d))) + SHA1_K0; + } else if (i<40) { + t = (b ^ c ^ d) + SHA1_K20; + } else if (i<60) { + t = ((b & c) | (d & (b | c))) + SHA1_K40; + } else { + t = (b ^ c ^ d) + SHA1_K60; + } + t+=sha1_rol32(a,5) + e + s->buffer[i&15]; + e=d; + d=c; + c=sha1_rol32(b,30); + b=a; + a=t; + } + s->state[0] += a; + s->state[1] += b; + s->state[2] += c; + s->state[3] += d; + s->state[4] += e; +} + +// Adds the least significant byte of |data|. +void sha1_addUncounted(sha1nfo *s, uint32_t data) { + uint8_t *const b = (uint8_t *)s->buffer; +#ifdef SHA_BIG_ENDIAN + b[s->bufferOffset] = static_cast(data); +#else + b[s->bufferOffset ^ 3] = static_cast(data); +#endif + s->bufferOffset++; + if (s->bufferOffset == BLOCK_LENGTH) { + sha1_hashBlock(s); + s->bufferOffset = 0; + } +} + +void sha1_writebyte(sha1nfo *s, uint8_t data) { + ++s->byteCount; + sha1_addUncounted(s, data); +} + +void sha1_write(sha1nfo *s, const char *data, size_t len) { + for (;len--;) sha1_writebyte(s, (uint8_t) *data++); +} + +void sha1_pad(sha1nfo *s) { + // Implement SHA-1 padding (fips180-2 §5.1.1) + + // Pad with 0x80 followed by 0x00 until the end of the block + sha1_addUncounted(s, 0x80); + while (s->bufferOffset != 56) sha1_addUncounted(s, 0x00); + + // Append length in the last 8 bytes + sha1_addUncounted(s, 0); // We're only using 32 bit lengths + sha1_addUncounted(s, 0); // But SHA-1 supports 64 bit lengths + sha1_addUncounted(s, 0); // So zero pad the top bits + sha1_addUncounted(s, s->byteCount >> 29); // Shifting to multiply by 8 + sha1_addUncounted(s, s->byteCount >> 21); // as SHA-1 supports bitstreams as well as + sha1_addUncounted(s, s->byteCount >> 13); // byte. + sha1_addUncounted(s, s->byteCount >> 5); + sha1_addUncounted(s, s->byteCount << 3); +} + +uint8_t* sha1_result(sha1nfo *s) { + // Pad to complete the last block + sha1_pad(s); + +#ifndef SHA_BIG_ENDIAN + // Swap byte order back + int i; + for (i=0; i<5; i++) { + s->state[i]= + (((s->state[i])<<24)& 0xff000000) + | (((s->state[i])<<8) & 0x00ff0000) + | (((s->state[i])>>8) & 0x0000ff00) + | (((s->state[i])>>24)& 0x000000ff); + } +#endif + + // Return pointer to hash (20 characters) + return (uint8_t*) s->state; +} + +} // namespace; Added for LibFuzzer + +namespace fuzzer { + +// The rest is added for LibFuzzer +void ComputeSHA1(const uint8_t *Data, size_t Len, uint8_t *Out) { + sha1nfo s; + sha1_init(&s); + sha1_write(&s, (const char*)Data, Len); + memcpy(Out, sha1_result(&s), HASH_LENGTH); +} + +std::string Sha1ToString(const uint8_t Sha1[kSHA1NumBytes]) { + std::stringstream SS; + for (int i = 0; i < kSHA1NumBytes; i++) + SS << std::hex << std::setfill('0') << std::setw(2) << (unsigned)Sha1[i]; + return SS.str(); +} + +std::string Hash(const Unit &U) { + uint8_t Hash[kSHA1NumBytes]; + ComputeSHA1(U.data(), U.size(), Hash); + return Sha1ToString(Hash); +} + +} diff --git a/core/lib/libfuzzer-sys/libfuzzer/FuzzerSHA1.h b/core/lib/libfuzzer-sys/libfuzzer/FuzzerSHA1.h new file mode 100644 index 0000000000..05cbacda87 --- /dev/null +++ b/core/lib/libfuzzer-sys/libfuzzer/FuzzerSHA1.h @@ -0,0 +1,32 @@ +//===- FuzzerSHA1.h - Internal header for the SHA1 utils --------*- C++ -* ===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// SHA1 utils. +//===----------------------------------------------------------------------===// + +#ifndef LLVM_FUZZER_SHA1_H +#define LLVM_FUZZER_SHA1_H + +#include "FuzzerDefs.h" +#include +#include + +namespace fuzzer { + +// Private copy of SHA1 implementation. +static const int kSHA1NumBytes = 20; + +// Computes SHA1 hash of 'Len' bytes in 'Data', writes kSHA1NumBytes to 'Out'. +void ComputeSHA1(const uint8_t *Data, size_t Len, uint8_t *Out); + +std::string Sha1ToString(const uint8_t Sha1[kSHA1NumBytes]); + +std::string Hash(const Unit &U); + +} // namespace fuzzer + +#endif // LLVM_FUZZER_SHA1_H diff --git a/core/lib/libfuzzer-sys/libfuzzer/FuzzerTracePC.cpp b/core/lib/libfuzzer-sys/libfuzzer/FuzzerTracePC.cpp new file mode 100644 index 0000000000..d808b9b00f --- /dev/null +++ b/core/lib/libfuzzer-sys/libfuzzer/FuzzerTracePC.cpp @@ -0,0 +1,682 @@ +//===- FuzzerTracePC.cpp - PC tracing--------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// Trace PCs. +// This module implements __sanitizer_cov_trace_pc_guard[_init], +// the callback required for -fsanitize-coverage=trace-pc-guard instrumentation. +// +//===----------------------------------------------------------------------===// + +#include "FuzzerTracePC.h" +#include "FuzzerBuiltins.h" +#include "FuzzerBuiltinsMsvc.h" +#include "FuzzerCorpus.h" +#include "FuzzerDefs.h" +#include "FuzzerDictionary.h" +#include "FuzzerExtFunctions.h" +#include "FuzzerIO.h" +#include "FuzzerPlatform.h" +#include "FuzzerUtil.h" +#include "FuzzerValueBitMap.h" +#include + +// Used by -fsanitize-coverage=stack-depth to track stack depth +ATTRIBUTES_INTERFACE_TLS_INITIAL_EXEC uintptr_t __sancov_lowest_stack; + +namespace fuzzer { + +TracePC TPC; + +size_t TracePC::GetTotalPCCoverage() { + return ObservedPCs.size(); +} + + +void TracePC::HandleInline8bitCountersInit(uint8_t *Start, uint8_t *Stop) { + if (Start == Stop) return; + if (NumModules && + Modules[NumModules - 1].Start() == Start) + return; + assert(NumModules < + sizeof(Modules) / sizeof(Modules[0])); + auto &M = Modules[NumModules++]; + uint8_t *AlignedStart = RoundUpByPage(Start); + uint8_t *AlignedStop = RoundDownByPage(Stop); + size_t NumFullPages = AlignedStop > AlignedStart ? + (AlignedStop - AlignedStart) / PageSize() : 0; + bool NeedFirst = Start < AlignedStart || !NumFullPages; + bool NeedLast = Stop > AlignedStop && AlignedStop >= AlignedStart; + M.NumRegions = NumFullPages + NeedFirst + NeedLast;; + assert(M.NumRegions > 0); + M.Regions = new Module::Region[M.NumRegions]; + assert(M.Regions); + size_t R = 0; + if (NeedFirst) + M.Regions[R++] = {Start, std::min(Stop, AlignedStart), true, false}; + for (uint8_t *P = AlignedStart; P < AlignedStop; P += PageSize()) + M.Regions[R++] = {P, P + PageSize(), true, true}; + if (NeedLast) + M.Regions[R++] = {AlignedStop, Stop, true, false}; + assert(R == M.NumRegions); + assert(M.Size() == (size_t)(Stop - Start)); + assert(M.Stop() == Stop); + assert(M.Start() == Start); + NumInline8bitCounters += M.Size(); +} + +void TracePC::HandlePCsInit(const uintptr_t *Start, const uintptr_t *Stop) { + const PCTableEntry *B = reinterpret_cast(Start); + const PCTableEntry *E = reinterpret_cast(Stop); + if (NumPCTables && ModulePCTable[NumPCTables - 1].Start == B) return; + assert(NumPCTables < sizeof(ModulePCTable) / sizeof(ModulePCTable[0])); + ModulePCTable[NumPCTables++] = {B, E}; + NumPCsInPCTables += E - B; +} + +void TracePC::PrintModuleInfo() { + if (NumModules) { + Printf("INFO: Loaded %zd modules (%zd inline 8-bit counters): ", + NumModules, NumInline8bitCounters); + for (size_t i = 0; i < NumModules; i++) + Printf("%zd [%p, %p), ", Modules[i].Size(), Modules[i].Start(), + Modules[i].Stop()); + Printf("\n"); + } + if (NumPCTables) { + Printf("INFO: Loaded %zd PC tables (%zd PCs): ", NumPCTables, + NumPCsInPCTables); + for (size_t i = 0; i < NumPCTables; i++) { + Printf("%zd [%p,%p), ", ModulePCTable[i].Stop - ModulePCTable[i].Start, + ModulePCTable[i].Start, ModulePCTable[i].Stop); + } + Printf("\n"); + + if (NumInline8bitCounters && NumInline8bitCounters != NumPCsInPCTables) { + Printf("ERROR: The size of coverage PC tables does not match the\n" + "number of instrumented PCs. This might be a compiler bug,\n" + "please contact the libFuzzer developers.\n" + "Also check https://bugs.llvm.org/show_bug.cgi?id=34636\n" + "for possible workarounds (tl;dr: don't use the old GNU ld)\n"); + _Exit(1); + } + } + if (size_t NumExtraCounters = ExtraCountersEnd() - ExtraCountersBegin()) + Printf("INFO: %zd Extra Counters\n", NumExtraCounters); + + size_t MaxFeatures = CollectFeatures([](uint32_t) {}); + if (MaxFeatures > std::numeric_limits::max()) + Printf("WARNING: The coverage PC tables may produce up to %zu features.\n" + "This exceeds the maximum 32-bit value. Some features may be\n" + "ignored, and fuzzing may become less precise. If possible,\n" + "consider refactoring the fuzzer into several smaller fuzzers\n" + "linked against only a portion of the current target.\n", + MaxFeatures); +} + +ATTRIBUTE_NO_SANITIZE_ALL +void TracePC::HandleCallerCallee(uintptr_t Caller, uintptr_t Callee) { + const uintptr_t kBits = 12; + const uintptr_t kMask = (1 << kBits) - 1; + uintptr_t Idx = (Caller & kMask) | ((Callee & kMask) << kBits); + ValueProfileMap.AddValueModPrime(Idx); +} + +/// \return the address of the previous instruction. +/// Note: the logic is copied from `sanitizer_common/sanitizer_stacktrace.h` +inline ALWAYS_INLINE uintptr_t GetPreviousInstructionPc(uintptr_t PC) { +#if defined(__arm__) + // T32 (Thumb) branch instructions might be 16 or 32 bit long, + // so we return (pc-2) in that case in order to be safe. + // For A32 mode we return (pc-4) because all instructions are 32 bit long. + return (PC - 3) & (~1); +#elif defined(__powerpc__) || defined(__powerpc64__) || defined(__aarch64__) + // PCs are always 4 byte aligned. + return PC - 4; +#elif defined(__sparc__) || defined(__mips__) + return PC - 8; +#else + return PC - 1; +#endif +} + +/// \return the address of the next instruction. +/// Note: the logic is copied from `sanitizer_common/sanitizer_stacktrace.cpp` +ALWAYS_INLINE uintptr_t TracePC::GetNextInstructionPc(uintptr_t PC) { +#if defined(__mips__) + return PC + 8; +#elif defined(__powerpc__) || defined(__sparc__) || defined(__arm__) || \ + defined(__aarch64__) + return PC + 4; +#else + return PC + 1; +#endif +} + +void TracePC::UpdateObservedPCs() { + Vector CoveredFuncs; + auto ObservePC = [&](const PCTableEntry *TE) { + if (ObservedPCs.insert(TE).second && DoPrintNewPCs) { + PrintPC("\tNEW_PC: %p %F %L", "\tNEW_PC: %p", + GetNextInstructionPc(TE->PC)); + Printf("\n"); + } + }; + + auto Observe = [&](const PCTableEntry *TE) { + if (PcIsFuncEntry(TE)) + if (++ObservedFuncs[TE->PC] == 1 && NumPrintNewFuncs) + CoveredFuncs.push_back(TE->PC); + ObservePC(TE); + }; + + if (NumPCsInPCTables) { + if (NumInline8bitCounters == NumPCsInPCTables) { + for (size_t i = 0; i < NumModules; i++) { + auto &M = Modules[i]; + assert(M.Size() == + (size_t)(ModulePCTable[i].Stop - ModulePCTable[i].Start)); + for (size_t r = 0; r < M.NumRegions; r++) { + auto &R = M.Regions[r]; + if (!R.Enabled) continue; + for (uint8_t *P = R.Start; P < R.Stop; P++) + if (*P) + Observe(&ModulePCTable[i].Start[M.Idx(P)]); + } + } + } + } + + for (size_t i = 0, N = Min(CoveredFuncs.size(), NumPrintNewFuncs); i < N; + i++) { + Printf("\tNEW_FUNC[%zd/%zd]: ", i + 1, CoveredFuncs.size()); + PrintPC("%p %F %L", "%p", GetNextInstructionPc(CoveredFuncs[i])); + Printf("\n"); + } +} + +uintptr_t TracePC::PCTableEntryIdx(const PCTableEntry *TE) { + size_t TotalTEs = 0; + for (size_t i = 0; i < NumPCTables; i++) { + auto &M = ModulePCTable[i]; + if (TE >= M.Start && TE < M.Stop) + return TotalTEs + TE - M.Start; + TotalTEs += M.Stop - M.Start; + } + assert(0); + return 0; +} + +const TracePC::PCTableEntry *TracePC::PCTableEntryByIdx(uintptr_t Idx) { + for (size_t i = 0; i < NumPCTables; i++) { + auto &M = ModulePCTable[i]; + size_t Size = M.Stop - M.Start; + if (Idx < Size) return &M.Start[Idx]; + Idx -= Size; + } + return nullptr; +} + +static std::string GetModuleName(uintptr_t PC) { + char ModulePathRaw[4096] = ""; // What's PATH_MAX in portable C++? + void *OffsetRaw = nullptr; + if (!EF->__sanitizer_get_module_and_offset_for_pc( + reinterpret_cast(PC), ModulePathRaw, + sizeof(ModulePathRaw), &OffsetRaw)) + return ""; + return ModulePathRaw; +} + +template +void TracePC::IterateCoveredFunctions(CallBack CB) { + for (size_t i = 0; i < NumPCTables; i++) { + auto &M = ModulePCTable[i]; + assert(M.Start < M.Stop); + auto ModuleName = GetModuleName(M.Start->PC); + for (auto NextFE = M.Start; NextFE < M.Stop; ) { + auto FE = NextFE; + assert(PcIsFuncEntry(FE) && "Not a function entry point"); + do { + NextFE++; + } while (NextFE < M.Stop && !(PcIsFuncEntry(NextFE))); + CB(FE, NextFE, ObservedFuncs[FE->PC]); + } + } +} + +void TracePC::SetFocusFunction(const std::string &FuncName) { + // This function should be called once. + assert(!FocusFunctionCounterPtr); + // "auto" is not a valid function name. If this function is called with "auto" + // that means the auto focus functionality failed. + if (FuncName.empty() || FuncName == "auto") + return; + for (size_t M = 0; M < NumModules; M++) { + auto &PCTE = ModulePCTable[M]; + size_t N = PCTE.Stop - PCTE.Start; + for (size_t I = 0; I < N; I++) { + if (!(PcIsFuncEntry(&PCTE.Start[I]))) continue; // not a function entry. + auto Name = DescribePC("%F", GetNextInstructionPc(PCTE.Start[I].PC)); + if (Name[0] == 'i' && Name[1] == 'n' && Name[2] == ' ') + Name = Name.substr(3, std::string::npos); + if (FuncName != Name) continue; + Printf("INFO: Focus function is set to '%s'\n", Name.c_str()); + FocusFunctionCounterPtr = Modules[M].Start() + I; + return; + } + } + + Printf("ERROR: Failed to set focus function. Make sure the function name is " + "valid (%s) and symbolization is enabled.\n", FuncName.c_str()); + exit(1); +} + +bool TracePC::ObservedFocusFunction() { + return FocusFunctionCounterPtr && *FocusFunctionCounterPtr; +} + +void TracePC::PrintCoverage(bool PrintAllCounters) { + if (!EF->__sanitizer_symbolize_pc || + !EF->__sanitizer_get_module_and_offset_for_pc) { + Printf("INFO: __sanitizer_symbolize_pc or " + "__sanitizer_get_module_and_offset_for_pc is not available," + " not printing coverage\n"); + return; + } + Printf(PrintAllCounters ? "FULL COVERAGE:\n" : "COVERAGE:\n"); + auto CoveredFunctionCallback = [&](const PCTableEntry *First, + const PCTableEntry *Last, + uintptr_t Counter) { + assert(First < Last); + auto VisualizePC = GetNextInstructionPc(First->PC); + std::string FileStr = DescribePC("%s", VisualizePC); + if (!IsInterestingCoverageFile(FileStr)) + return; + std::string FunctionStr = DescribePC("%F", VisualizePC); + if (FunctionStr.find("in ") == 0) + FunctionStr = FunctionStr.substr(3); + std::string LineStr = DescribePC("%l", VisualizePC); + size_t NumEdges = Last - First; + Vector UncoveredPCs; + Vector CoveredPCs; + for (auto TE = First; TE < Last; TE++) + if (!ObservedPCs.count(TE)) + UncoveredPCs.push_back(TE->PC); + else + CoveredPCs.push_back(TE->PC); + + if (PrintAllCounters) { + Printf("U"); + for (auto PC : UncoveredPCs) + Printf(DescribePC(" %l", GetNextInstructionPc(PC)).c_str()); + Printf("\n"); + + Printf("C"); + for (auto PC : CoveredPCs) + Printf(DescribePC(" %l", GetNextInstructionPc(PC)).c_str()); + Printf("\n"); + } else { + Printf("%sCOVERED_FUNC: hits: %zd", Counter ? "" : "UN", Counter); + Printf(" edges: %zd/%zd", NumEdges - UncoveredPCs.size(), NumEdges); + Printf(" %s %s:%s\n", FunctionStr.c_str(), FileStr.c_str(), + LineStr.c_str()); + if (Counter) + for (auto PC : UncoveredPCs) + Printf(" UNCOVERED_PC: %s\n", + DescribePC("%s:%l", GetNextInstructionPc(PC)).c_str()); + } + }; + + IterateCoveredFunctions(CoveredFunctionCallback); +} + +// Value profile. +// We keep track of various values that affect control flow. +// These values are inserted into a bit-set-based hash map. +// Every new bit in the map is treated as a new coverage. +// +// For memcmp/strcmp/etc the interesting value is the length of the common +// prefix of the parameters. +// For cmp instructions the interesting value is a XOR of the parameters. +// The interesting value is mixed up with the PC and is then added to the map. + +ATTRIBUTE_NO_SANITIZE_ALL +void TracePC::AddValueForMemcmp(void *caller_pc, const void *s1, const void *s2, + size_t n, bool StopAtZero) { + if (!n) return; + size_t Len = std::min(n, Word::GetMaxSize()); + const uint8_t *A1 = reinterpret_cast(s1); + const uint8_t *A2 = reinterpret_cast(s2); + uint8_t B1[Word::kMaxSize]; + uint8_t B2[Word::kMaxSize]; + // Copy the data into locals in this non-msan-instrumented function + // to avoid msan complaining further. + size_t Hash = 0; // Compute some simple hash of both strings. + for (size_t i = 0; i < Len; i++) { + B1[i] = A1[i]; + B2[i] = A2[i]; + size_t T = B1[i]; + Hash ^= (T << 8) | B2[i]; + } + size_t I = 0; + uint8_t HammingDistance = 0; + for (; I < Len; I++) { + if (B1[I] != B2[I] || (StopAtZero && B1[I] == 0)) { + HammingDistance = static_cast(Popcountll(B1[I] ^ B2[I])); + break; + } + } + size_t PC = reinterpret_cast(caller_pc); + size_t Idx = (PC & 4095) | (I << 12); + Idx += HammingDistance; + ValueProfileMap.AddValue(Idx); + TORCW.Insert(Idx ^ Hash, Word(B1, Len), Word(B2, Len)); +} + +template +ATTRIBUTE_TARGET_POPCNT ALWAYS_INLINE +ATTRIBUTE_NO_SANITIZE_ALL +void TracePC::HandleCmp(uintptr_t PC, T Arg1, T Arg2) { + uint64_t ArgXor = Arg1 ^ Arg2; + if (sizeof(T) == 4) + TORC4.Insert(ArgXor, Arg1, Arg2); + else if (sizeof(T) == 8) + TORC8.Insert(ArgXor, Arg1, Arg2); + uint64_t HammingDistance = Popcountll(ArgXor); // [0,64] + uint64_t AbsoluteDistance = (Arg1 == Arg2 ? 0 : Clzll(Arg1 - Arg2) + 1); + ValueProfileMap.AddValue(PC * 128 + HammingDistance); + ValueProfileMap.AddValue(PC * 128 + 64 + AbsoluteDistance); +} + +static size_t InternalStrnlen(const char *S, size_t MaxLen) { + size_t Len = 0; + for (; Len < MaxLen && S[Len]; Len++) {} + return Len; +} + +// Finds min of (strlen(S1), strlen(S2)). +// Needed bacause one of these strings may actually be non-zero terminated. +static size_t InternalStrnlen2(const char *S1, const char *S2) { + size_t Len = 0; + for (; S1[Len] && S2[Len]; Len++) {} + return Len; +} + +void TracePC::ClearInlineCounters() { + IterateCounterRegions([](const Module::Region &R){ + if (R.Enabled) + memset(R.Start, 0, R.Stop - R.Start); + }); +} + +ATTRIBUTE_NO_SANITIZE_ALL +void TracePC::RecordInitialStack() { + int stack; + __sancov_lowest_stack = InitialStack = reinterpret_cast(&stack); +} + +uintptr_t TracePC::GetMaxStackOffset() const { + return InitialStack - __sancov_lowest_stack; // Stack grows down +} + +void WarnAboutDeprecatedInstrumentation(const char *flag) { + // Use RawPrint because Printf cannot be used on Windows before OutputFile is + // initialized. + RawPrint(flag); + RawPrint( + " is no longer supported by libFuzzer.\n" + "Please either migrate to a compiler that supports -fsanitize=fuzzer\n" + "or use an older version of libFuzzer\n"); + exit(1); +} + +} // namespace fuzzer + +extern "C" { +ATTRIBUTE_INTERFACE +ATTRIBUTE_NO_SANITIZE_ALL +void __sanitizer_cov_trace_pc_guard(uint32_t *Guard) { + fuzzer::WarnAboutDeprecatedInstrumentation( + "-fsanitize-coverage=trace-pc-guard"); +} + +// Best-effort support for -fsanitize-coverage=trace-pc, which is available +// in both Clang and GCC. +ATTRIBUTE_INTERFACE +ATTRIBUTE_NO_SANITIZE_ALL +void __sanitizer_cov_trace_pc() { + fuzzer::WarnAboutDeprecatedInstrumentation("-fsanitize-coverage=trace-pc"); +} + +ATTRIBUTE_INTERFACE +void __sanitizer_cov_trace_pc_guard_init(uint32_t *Start, uint32_t *Stop) { + fuzzer::WarnAboutDeprecatedInstrumentation( + "-fsanitize-coverage=trace-pc-guard"); +} + +ATTRIBUTE_INTERFACE +void __sanitizer_cov_8bit_counters_init(uint8_t *Start, uint8_t *Stop) { + fuzzer::TPC.HandleInline8bitCountersInit(Start, Stop); +} + +ATTRIBUTE_INTERFACE +void __sanitizer_cov_pcs_init(const uintptr_t *pcs_beg, + const uintptr_t *pcs_end) { + fuzzer::TPC.HandlePCsInit(pcs_beg, pcs_end); +} + +ATTRIBUTE_INTERFACE +ATTRIBUTE_NO_SANITIZE_ALL +void __sanitizer_cov_trace_pc_indir(uintptr_t Callee) { + uintptr_t PC = reinterpret_cast(GET_CALLER_PC()); + fuzzer::TPC.HandleCallerCallee(PC, Callee); +} + +ATTRIBUTE_INTERFACE +ATTRIBUTE_NO_SANITIZE_ALL +ATTRIBUTE_TARGET_POPCNT +void __sanitizer_cov_trace_cmp8(uint64_t Arg1, uint64_t Arg2) { + uintptr_t PC = reinterpret_cast(GET_CALLER_PC()); + fuzzer::TPC.HandleCmp(PC, Arg1, Arg2); +} + +ATTRIBUTE_INTERFACE +ATTRIBUTE_NO_SANITIZE_ALL +ATTRIBUTE_TARGET_POPCNT +// Now the __sanitizer_cov_trace_const_cmp[1248] callbacks just mimic +// the behaviour of __sanitizer_cov_trace_cmp[1248] ones. This, however, +// should be changed later to make full use of instrumentation. +void __sanitizer_cov_trace_const_cmp8(uint64_t Arg1, uint64_t Arg2) { + uintptr_t PC = reinterpret_cast(GET_CALLER_PC()); + fuzzer::TPC.HandleCmp(PC, Arg1, Arg2); +} + +ATTRIBUTE_INTERFACE +ATTRIBUTE_NO_SANITIZE_ALL +ATTRIBUTE_TARGET_POPCNT +void __sanitizer_cov_trace_cmp4(uint32_t Arg1, uint32_t Arg2) { + uintptr_t PC = reinterpret_cast(GET_CALLER_PC()); + fuzzer::TPC.HandleCmp(PC, Arg1, Arg2); +} + +ATTRIBUTE_INTERFACE +ATTRIBUTE_NO_SANITIZE_ALL +ATTRIBUTE_TARGET_POPCNT +void __sanitizer_cov_trace_const_cmp4(uint32_t Arg1, uint32_t Arg2) { + uintptr_t PC = reinterpret_cast(GET_CALLER_PC()); + fuzzer::TPC.HandleCmp(PC, Arg1, Arg2); +} + +ATTRIBUTE_INTERFACE +ATTRIBUTE_NO_SANITIZE_ALL +ATTRIBUTE_TARGET_POPCNT +void __sanitizer_cov_trace_cmp2(uint16_t Arg1, uint16_t Arg2) { + uintptr_t PC = reinterpret_cast(GET_CALLER_PC()); + fuzzer::TPC.HandleCmp(PC, Arg1, Arg2); +} + +ATTRIBUTE_INTERFACE +ATTRIBUTE_NO_SANITIZE_ALL +ATTRIBUTE_TARGET_POPCNT +void __sanitizer_cov_trace_const_cmp2(uint16_t Arg1, uint16_t Arg2) { + uintptr_t PC = reinterpret_cast(GET_CALLER_PC()); + fuzzer::TPC.HandleCmp(PC, Arg1, Arg2); +} + +ATTRIBUTE_INTERFACE +ATTRIBUTE_NO_SANITIZE_ALL +ATTRIBUTE_TARGET_POPCNT +void __sanitizer_cov_trace_cmp1(uint8_t Arg1, uint8_t Arg2) { + uintptr_t PC = reinterpret_cast(GET_CALLER_PC()); + fuzzer::TPC.HandleCmp(PC, Arg1, Arg2); +} + +ATTRIBUTE_INTERFACE +ATTRIBUTE_NO_SANITIZE_ALL +ATTRIBUTE_TARGET_POPCNT +void __sanitizer_cov_trace_const_cmp1(uint8_t Arg1, uint8_t Arg2) { + uintptr_t PC = reinterpret_cast(GET_CALLER_PC()); + fuzzer::TPC.HandleCmp(PC, Arg1, Arg2); +} + +ATTRIBUTE_INTERFACE +ATTRIBUTE_NO_SANITIZE_ALL +ATTRIBUTE_TARGET_POPCNT +void __sanitizer_cov_trace_switch(uint64_t Val, uint64_t *Cases) { + uint64_t N = Cases[0]; + uint64_t ValSizeInBits = Cases[1]; + uint64_t *Vals = Cases + 2; + // Skip the most common and the most boring case: all switch values are small. + // We may want to skip this at compile-time, but it will make the + // instrumentation less general. + if (Vals[N - 1] < 256) + return; + // Also skip small inputs values, they won't give good signal. + if (Val < 256) + return; + uintptr_t PC = reinterpret_cast(GET_CALLER_PC()); + size_t i; + uint64_t Smaller = 0; + uint64_t Larger = ~(uint64_t)0; + // Find two switch values such that Smaller < Val < Larger. + // Use 0 and 0xfff..f as the defaults. + for (i = 0; i < N; i++) { + if (Val < Vals[i]) { + Larger = Vals[i]; + break; + } + if (Val > Vals[i]) Smaller = Vals[i]; + } + + // Apply HandleCmp to {Val,Smaller} and {Val, Larger}, + // use i as the PC modifier for HandleCmp. + if (ValSizeInBits == 16) { + fuzzer::TPC.HandleCmp(PC + 2 * i, static_cast(Val), + (uint16_t)(Smaller)); + fuzzer::TPC.HandleCmp(PC + 2 * i + 1, static_cast(Val), + (uint16_t)(Larger)); + } else if (ValSizeInBits == 32) { + fuzzer::TPC.HandleCmp(PC + 2 * i, static_cast(Val), + (uint32_t)(Smaller)); + fuzzer::TPC.HandleCmp(PC + 2 * i + 1, static_cast(Val), + (uint32_t)(Larger)); + } else { + fuzzer::TPC.HandleCmp(PC + 2*i, Val, Smaller); + fuzzer::TPC.HandleCmp(PC + 2*i + 1, Val, Larger); + } +} + +ATTRIBUTE_INTERFACE +ATTRIBUTE_NO_SANITIZE_ALL +ATTRIBUTE_TARGET_POPCNT +void __sanitizer_cov_trace_div4(uint32_t Val) { + uintptr_t PC = reinterpret_cast(GET_CALLER_PC()); + fuzzer::TPC.HandleCmp(PC, Val, (uint32_t)0); +} + +ATTRIBUTE_INTERFACE +ATTRIBUTE_NO_SANITIZE_ALL +ATTRIBUTE_TARGET_POPCNT +void __sanitizer_cov_trace_div8(uint64_t Val) { + uintptr_t PC = reinterpret_cast(GET_CALLER_PC()); + fuzzer::TPC.HandleCmp(PC, Val, (uint64_t)0); +} + +ATTRIBUTE_INTERFACE +ATTRIBUTE_NO_SANITIZE_ALL +ATTRIBUTE_TARGET_POPCNT +void __sanitizer_cov_trace_gep(uintptr_t Idx) { + uintptr_t PC = reinterpret_cast(GET_CALLER_PC()); + fuzzer::TPC.HandleCmp(PC, Idx, (uintptr_t)0); +} + +ATTRIBUTE_INTERFACE ATTRIBUTE_NO_SANITIZE_MEMORY +void __sanitizer_weak_hook_memcmp(void *caller_pc, const void *s1, + const void *s2, size_t n, int result) { + if (!fuzzer::RunningUserCallback) return; + if (result == 0) return; // No reason to mutate. + if (n <= 1) return; // Not interesting. + fuzzer::TPC.AddValueForMemcmp(caller_pc, s1, s2, n, /*StopAtZero*/false); +} + +ATTRIBUTE_INTERFACE ATTRIBUTE_NO_SANITIZE_MEMORY +void __sanitizer_weak_hook_strncmp(void *caller_pc, const char *s1, + const char *s2, size_t n, int result) { + if (!fuzzer::RunningUserCallback) return; + if (result == 0) return; // No reason to mutate. + size_t Len1 = fuzzer::InternalStrnlen(s1, n); + size_t Len2 = fuzzer::InternalStrnlen(s2, n); + n = std::min(n, Len1); + n = std::min(n, Len2); + if (n <= 1) return; // Not interesting. + fuzzer::TPC.AddValueForMemcmp(caller_pc, s1, s2, n, /*StopAtZero*/true); +} + +ATTRIBUTE_INTERFACE ATTRIBUTE_NO_SANITIZE_MEMORY +void __sanitizer_weak_hook_strcmp(void *caller_pc, const char *s1, + const char *s2, int result) { + if (!fuzzer::RunningUserCallback) return; + if (result == 0) return; // No reason to mutate. + size_t N = fuzzer::InternalStrnlen2(s1, s2); + if (N <= 1) return; // Not interesting. + fuzzer::TPC.AddValueForMemcmp(caller_pc, s1, s2, N, /*StopAtZero*/true); +} + +ATTRIBUTE_INTERFACE ATTRIBUTE_NO_SANITIZE_MEMORY +void __sanitizer_weak_hook_strncasecmp(void *called_pc, const char *s1, + const char *s2, size_t n, int result) { + if (!fuzzer::RunningUserCallback) return; + return __sanitizer_weak_hook_strncmp(called_pc, s1, s2, n, result); +} + +ATTRIBUTE_INTERFACE ATTRIBUTE_NO_SANITIZE_MEMORY +void __sanitizer_weak_hook_strcasecmp(void *called_pc, const char *s1, + const char *s2, int result) { + if (!fuzzer::RunningUserCallback) return; + return __sanitizer_weak_hook_strcmp(called_pc, s1, s2, result); +} + +ATTRIBUTE_INTERFACE ATTRIBUTE_NO_SANITIZE_MEMORY +void __sanitizer_weak_hook_strstr(void *called_pc, const char *s1, + const char *s2, char *result) { + if (!fuzzer::RunningUserCallback) return; + fuzzer::TPC.MMT.Add(reinterpret_cast(s2), strlen(s2)); +} + +ATTRIBUTE_INTERFACE ATTRIBUTE_NO_SANITIZE_MEMORY +void __sanitizer_weak_hook_strcasestr(void *called_pc, const char *s1, + const char *s2, char *result) { + if (!fuzzer::RunningUserCallback) return; + fuzzer::TPC.MMT.Add(reinterpret_cast(s2), strlen(s2)); +} + +ATTRIBUTE_INTERFACE ATTRIBUTE_NO_SANITIZE_MEMORY +void __sanitizer_weak_hook_memmem(void *called_pc, const void *s1, size_t len1, + const void *s2, size_t len2, void *result) { + if (!fuzzer::RunningUserCallback) return; + fuzzer::TPC.MMT.Add(reinterpret_cast(s2), len2); +} +} // extern "C" diff --git a/core/lib/libfuzzer-sys/libfuzzer/FuzzerTracePC.h b/core/lib/libfuzzer-sys/libfuzzer/FuzzerTracePC.h new file mode 100644 index 0000000000..a93732972f --- /dev/null +++ b/core/lib/libfuzzer-sys/libfuzzer/FuzzerTracePC.h @@ -0,0 +1,298 @@ +//===- FuzzerTracePC.h - Internal header for the Fuzzer ---------*- C++ -* ===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// fuzzer::TracePC +//===----------------------------------------------------------------------===// + +#ifndef LLVM_FUZZER_TRACE_PC +#define LLVM_FUZZER_TRACE_PC + +#include "FuzzerDefs.h" +#include "FuzzerDictionary.h" +#include "FuzzerValueBitMap.h" + +#include +#include + +namespace fuzzer { + +// TableOfRecentCompares (TORC) remembers the most recently performed +// comparisons of type T. +// We record the arguments of CMP instructions in this table unconditionally +// because it seems cheaper this way than to compute some expensive +// conditions inside __sanitizer_cov_trace_cmp*. +// After the unit has been executed we may decide to use the contents of +// this table to populate a Dictionary. +template +struct TableOfRecentCompares { + static const size_t kSize = kSizeT; + struct Pair { + T A, B; + }; + ATTRIBUTE_NO_SANITIZE_ALL + void Insert(size_t Idx, const T &Arg1, const T &Arg2) { + Idx = Idx % kSize; + Table[Idx].A = Arg1; + Table[Idx].B = Arg2; + } + + Pair Get(size_t I) { return Table[I % kSize]; } + + Pair Table[kSize]; +}; + +template +struct MemMemTable { + static const size_t kSize = kSizeT; + Word MemMemWords[kSize]; + Word EmptyWord; + + void Add(const uint8_t *Data, size_t Size) { + if (Size <= 2) return; + Size = std::min(Size, Word::GetMaxSize()); + auto Idx = SimpleFastHash(Data, Size) % kSize; + MemMemWords[Idx].Set(Data, Size); + } + const Word &Get(size_t Idx) { + for (size_t i = 0; i < kSize; i++) { + const Word &W = MemMemWords[(Idx + i) % kSize]; + if (W.size()) return W; + } + EmptyWord.Set(nullptr, 0); + return EmptyWord; + } +}; + +class TracePC { + public: + void HandleInline8bitCountersInit(uint8_t *Start, uint8_t *Stop); + void HandlePCsInit(const uintptr_t *Start, const uintptr_t *Stop); + void HandleCallerCallee(uintptr_t Caller, uintptr_t Callee); + template void HandleCmp(uintptr_t PC, T Arg1, T Arg2); + size_t GetTotalPCCoverage(); + void SetUseCounters(bool UC) { UseCounters = UC; } + void SetUseValueProfileMask(uint32_t VPMask) { UseValueProfileMask = VPMask; } + void SetPrintNewPCs(bool P) { DoPrintNewPCs = P; } + void SetPrintNewFuncs(size_t P) { NumPrintNewFuncs = P; } + void UpdateObservedPCs(); + template size_t CollectFeatures(Callback CB) const; + + void ResetMaps() { + ValueProfileMap.Reset(); + ClearExtraCounters(); + ClearInlineCounters(); + } + + void ClearInlineCounters(); + + void UpdateFeatureSet(size_t CurrentElementIdx, size_t CurrentElementSize); + void PrintFeatureSet(); + + void PrintModuleInfo(); + + void PrintCoverage(bool PrintAllCounters); + + template + void IterateCoveredFunctions(CallBack CB); + + void AddValueForMemcmp(void *caller_pc, const void *s1, const void *s2, + size_t n, bool StopAtZero); + + TableOfRecentCompares TORC4; + TableOfRecentCompares TORC8; + TableOfRecentCompares TORCW; + MemMemTable<1024> MMT; + + void RecordInitialStack(); + uintptr_t GetMaxStackOffset() const; + + template + void ForEachObservedPC(CallBack CB) { + for (auto PC : ObservedPCs) + CB(PC); + } + + void SetFocusFunction(const std::string &FuncName); + bool ObservedFocusFunction(); + + struct PCTableEntry { + uintptr_t PC, PCFlags; + }; + + uintptr_t PCTableEntryIdx(const PCTableEntry *TE); + const PCTableEntry *PCTableEntryByIdx(uintptr_t Idx); + static uintptr_t GetNextInstructionPc(uintptr_t PC); + bool PcIsFuncEntry(const PCTableEntry *TE) { return TE->PCFlags & 1; } + +private: + bool UseCounters = false; + uint32_t UseValueProfileMask = false; + bool DoPrintNewPCs = false; + size_t NumPrintNewFuncs = 0; + + // Module represents the array of 8-bit counters split into regions + // such that every region, except maybe the first and the last one, is one + // full page. + struct Module { + struct Region { + uint8_t *Start, *Stop; + bool Enabled; + bool OneFullPage; + }; + Region *Regions; + size_t NumRegions; + uint8_t *Start() { return Regions[0].Start; } + uint8_t *Stop() { return Regions[NumRegions - 1].Stop; } + size_t Size() { return Stop() - Start(); } + size_t Idx(uint8_t *P) { + assert(P >= Start() && P < Stop()); + return P - Start(); + } + }; + + Module Modules[4096]; + size_t NumModules; // linker-initialized. + size_t NumInline8bitCounters; + + template + void IterateCounterRegions(Callback CB) { + for (size_t m = 0; m < NumModules; m++) + for (size_t r = 0; r < Modules[m].NumRegions; r++) + CB(Modules[m].Regions[r]); + } + + struct { const PCTableEntry *Start, *Stop; } ModulePCTable[4096]; + size_t NumPCTables; + size_t NumPCsInPCTables; + + Set ObservedPCs; + std::unordered_map ObservedFuncs; // PC => Counter. + + uint8_t *FocusFunctionCounterPtr = nullptr; + + ValueBitMap ValueProfileMap; + uintptr_t InitialStack; +}; + +template +// void Callback(size_t FirstFeature, size_t Idx, uint8_t Value); +ATTRIBUTE_NO_SANITIZE_ALL +size_t ForEachNonZeroByte(const uint8_t *Begin, const uint8_t *End, + size_t FirstFeature, Callback Handle8bitCounter) { + typedef uintptr_t LargeType; + const size_t Step = sizeof(LargeType) / sizeof(uint8_t); + const size_t StepMask = Step - 1; + auto P = Begin; + // Iterate by 1 byte until either the alignment boundary or the end. + for (; reinterpret_cast(P) & StepMask && P < End; P++) + if (uint8_t V = *P) + Handle8bitCounter(FirstFeature, P - Begin, V); + + // Iterate by Step bytes at a time. + for (; P + Step <= End; P += Step) + if (LargeType Bundle = *reinterpret_cast(P)) { + Bundle = HostToLE(Bundle); + for (size_t I = 0; I < Step; I++, Bundle >>= 8) + if (uint8_t V = Bundle & 0xff) + Handle8bitCounter(FirstFeature, P - Begin + I, V); + } + + // Iterate by 1 byte until the end. + for (; P < End; P++) + if (uint8_t V = *P) + Handle8bitCounter(FirstFeature, P - Begin, V); + return End - Begin; +} + +// Given a non-zero Counter returns a number in the range [0,7]. +template +unsigned CounterToFeature(T Counter) { + // Returns a feature number by placing Counters into buckets as illustrated + // below. + // + // Counter bucket: [1] [2] [3] [4-7] [8-15] [16-31] [32-127] [128+] + // Feature number: 0 1 2 3 4 5 6 7 + // + // This is a heuristic taken from AFL (see + // http://lcamtuf.coredump.cx/afl/technical_details.txt). + // + // This implementation may change in the future so clients should + // not rely on it. + assert(Counter); + unsigned Bit = 0; + /**/ if (Counter >= 128) Bit = 7; + else if (Counter >= 32) Bit = 6; + else if (Counter >= 16) Bit = 5; + else if (Counter >= 8) Bit = 4; + else if (Counter >= 4) Bit = 3; + else if (Counter >= 3) Bit = 2; + else if (Counter >= 2) Bit = 1; + return Bit; +} + +template // void Callback(uint32_t Feature) +ATTRIBUTE_NO_SANITIZE_ADDRESS ATTRIBUTE_NOINLINE size_t +TracePC::CollectFeatures(Callback HandleFeature) const { + auto Handle8bitCounter = [&](size_t FirstFeature, + size_t Idx, uint8_t Counter) { + if (UseCounters) + HandleFeature(static_cast(FirstFeature + Idx * 8 + + CounterToFeature(Counter))); + else + HandleFeature(static_cast(FirstFeature + Idx)); + }; + + size_t FirstFeature = 0; + + for (size_t i = 0; i < NumModules; i++) { + for (size_t r = 0; r < Modules[i].NumRegions; r++) { + if (!Modules[i].Regions[r].Enabled) continue; + FirstFeature += 8 * ForEachNonZeroByte(Modules[i].Regions[r].Start, + Modules[i].Regions[r].Stop, + FirstFeature, Handle8bitCounter); + } + } + + FirstFeature += + 8 * ForEachNonZeroByte(ExtraCountersBegin(), ExtraCountersEnd(), + FirstFeature, Handle8bitCounter); + + if (UseValueProfileMask) { + ValueProfileMap.ForEach([&](size_t Idx) { + HandleFeature(static_cast(FirstFeature + Idx)); + }); + FirstFeature += ValueProfileMap.SizeInBits(); + } + + // Step function, grows similar to 8 * Log_2(A). + auto StackDepthStepFunction = [](size_t A) -> size_t { + if (!A) + return A; + auto Log2 = Log(A); + if (Log2 < 3) + return A; + Log2 -= 3; + return (Log2 + 1) * 8 + ((A >> Log2) & 7); + }; + assert(StackDepthStepFunction(1024) == 64); + assert(StackDepthStepFunction(1024 * 4) == 80); + assert(StackDepthStepFunction(1024 * 1024) == 144); + + if (auto MaxStackOffset = GetMaxStackOffset()) { + HandleFeature(static_cast( + FirstFeature + StackDepthStepFunction(MaxStackOffset / 8))); + FirstFeature += StackDepthStepFunction(std::numeric_limits::max()); + } + + return FirstFeature; +} + +extern TracePC TPC; + +} // namespace fuzzer + +#endif // LLVM_FUZZER_TRACE_PC diff --git a/core/lib/libfuzzer-sys/libfuzzer/FuzzerUtil.cpp b/core/lib/libfuzzer-sys/libfuzzer/FuzzerUtil.cpp new file mode 100644 index 0000000000..05185499bd --- /dev/null +++ b/core/lib/libfuzzer-sys/libfuzzer/FuzzerUtil.cpp @@ -0,0 +1,237 @@ +//===- FuzzerUtil.cpp - Misc utils ----------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// Misc utils. +//===----------------------------------------------------------------------===// + +#include "FuzzerUtil.h" +#include "FuzzerIO.h" +#include "FuzzerInternal.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace fuzzer { + +void PrintHexArray(const uint8_t *Data, size_t Size, + const char *PrintAfter) { + for (size_t i = 0; i < Size; i++) + Printf("0x%x,", (unsigned)Data[i]); + Printf("%s", PrintAfter); +} + +void Print(const Unit &v, const char *PrintAfter) { + PrintHexArray(v.data(), v.size(), PrintAfter); +} + +void PrintASCIIByte(uint8_t Byte) { + if (Byte == '\\') + Printf("\\\\"); + else if (Byte == '"') + Printf("\\\""); + else if (Byte >= 32 && Byte < 127) + Printf("%c", Byte); + else + Printf("\\x%02x", Byte); +} + +void PrintASCII(const uint8_t *Data, size_t Size, const char *PrintAfter) { + for (size_t i = 0; i < Size; i++) + PrintASCIIByte(Data[i]); + Printf("%s", PrintAfter); +} + +void PrintASCII(const Unit &U, const char *PrintAfter) { + PrintASCII(U.data(), U.size(), PrintAfter); +} + +bool ToASCII(uint8_t *Data, size_t Size) { + bool Changed = false; + for (size_t i = 0; i < Size; i++) { + uint8_t &X = Data[i]; + auto NewX = X; + NewX &= 127; + if (!isspace(NewX) && !isprint(NewX)) + NewX = ' '; + Changed |= NewX != X; + X = NewX; + } + return Changed; +} + +bool IsASCII(const Unit &U) { return IsASCII(U.data(), U.size()); } + +bool IsASCII(const uint8_t *Data, size_t Size) { + for (size_t i = 0; i < Size; i++) + if (!(isprint(Data[i]) || isspace(Data[i]))) return false; + return true; +} + +bool ParseOneDictionaryEntry(const std::string &Str, Unit *U) { + U->clear(); + if (Str.empty()) return false; + size_t L = 0, R = Str.size() - 1; // We are parsing the range [L,R]. + // Skip spaces from both sides. + while (L < R && isspace(Str[L])) L++; + while (R > L && isspace(Str[R])) R--; + if (R - L < 2) return false; + // Check the closing " + if (Str[R] != '"') return false; + R--; + // Find the opening " + while (L < R && Str[L] != '"') L++; + if (L >= R) return false; + assert(Str[L] == '\"'); + L++; + assert(L <= R); + for (size_t Pos = L; Pos <= R; Pos++) { + uint8_t V = (uint8_t)Str[Pos]; + if (!isprint(V) && !isspace(V)) return false; + if (V =='\\') { + // Handle '\\' + if (Pos + 1 <= R && (Str[Pos + 1] == '\\' || Str[Pos + 1] == '"')) { + U->push_back(Str[Pos + 1]); + Pos++; + continue; + } + // Handle '\xAB' + if (Pos + 3 <= R && Str[Pos + 1] == 'x' + && isxdigit(Str[Pos + 2]) && isxdigit(Str[Pos + 3])) { + char Hex[] = "0xAA"; + Hex[2] = Str[Pos + 2]; + Hex[3] = Str[Pos + 3]; + U->push_back(static_cast(strtol(Hex, nullptr, 16))); + Pos += 3; + continue; + } + return false; // Invalid escape. + } else { + // Any other character. + U->push_back(V); + } + } + return true; +} + +bool ParseDictionaryFile(const std::string &Text, Vector *Units) { + if (Text.empty()) { + Printf("ParseDictionaryFile: file does not exist or is empty\n"); + return false; + } + std::istringstream ISS(Text); + Units->clear(); + Unit U; + int LineNo = 0; + std::string S; + while (std::getline(ISS, S, '\n')) { + LineNo++; + size_t Pos = 0; + while (Pos < S.size() && isspace(S[Pos])) Pos++; // Skip spaces. + if (Pos == S.size()) continue; // Empty line. + if (S[Pos] == '#') continue; // Comment line. + if (ParseOneDictionaryEntry(S, &U)) { + Units->push_back(U); + } else { + Printf("ParseDictionaryFile: error in line %d\n\t\t%s\n", LineNo, + S.c_str()); + return false; + } + } + return true; +} + +// Code duplicated (and tested) in llvm/include/llvm/Support/Base64.h +std::string Base64(const Unit &U) { + static const char Table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; + std::string Buffer; + Buffer.resize(((U.size() + 2) / 3) * 4); + + size_t i = 0, j = 0; + for (size_t n = U.size() / 3 * 3; i < n; i += 3, j += 4) { + uint32_t x = ((unsigned char)U[i] << 16) | ((unsigned char)U[i + 1] << 8) | + (unsigned char)U[i + 2]; + Buffer[j + 0] = Table[(x >> 18) & 63]; + Buffer[j + 1] = Table[(x >> 12) & 63]; + Buffer[j + 2] = Table[(x >> 6) & 63]; + Buffer[j + 3] = Table[x & 63]; + } + if (i + 1 == U.size()) { + uint32_t x = ((unsigned char)U[i] << 16); + Buffer[j + 0] = Table[(x >> 18) & 63]; + Buffer[j + 1] = Table[(x >> 12) & 63]; + Buffer[j + 2] = '='; + Buffer[j + 3] = '='; + } else if (i + 2 == U.size()) { + uint32_t x = ((unsigned char)U[i] << 16) | ((unsigned char)U[i + 1] << 8); + Buffer[j + 0] = Table[(x >> 18) & 63]; + Buffer[j + 1] = Table[(x >> 12) & 63]; + Buffer[j + 2] = Table[(x >> 6) & 63]; + Buffer[j + 3] = '='; + } + return Buffer; +} + +static std::mutex SymbolizeMutex; + +std::string DescribePC(const char *SymbolizedFMT, uintptr_t PC) { + std::unique_lock l(SymbolizeMutex, std::try_to_lock); + if (!EF->__sanitizer_symbolize_pc || !l.owns_lock()) + return ""; + char PcDescr[1024] = {}; + EF->__sanitizer_symbolize_pc(reinterpret_cast(PC), + SymbolizedFMT, PcDescr, sizeof(PcDescr)); + PcDescr[sizeof(PcDescr) - 1] = 0; // Just in case. + return PcDescr; +} + +void PrintPC(const char *SymbolizedFMT, const char *FallbackFMT, uintptr_t PC) { + if (EF->__sanitizer_symbolize_pc) + Printf("%s", DescribePC(SymbolizedFMT, PC).c_str()); + else + Printf(FallbackFMT, PC); +} + +void PrintStackTrace() { + std::unique_lock l(SymbolizeMutex, std::try_to_lock); + if (EF->__sanitizer_print_stack_trace && l.owns_lock()) + EF->__sanitizer_print_stack_trace(); +} + +void PrintMemoryProfile() { + std::unique_lock l(SymbolizeMutex, std::try_to_lock); + if (EF->__sanitizer_print_memory_profile && l.owns_lock()) + EF->__sanitizer_print_memory_profile(95, 8); +} + +unsigned NumberOfCpuCores() { + unsigned N = std::thread::hardware_concurrency(); + if (!N) { + Printf("WARNING: std::thread::hardware_concurrency not well defined for " + "your platform. Assuming CPU count of 1.\n"); + N = 1; + } + return N; +} + +uint64_t SimpleFastHash(const void *Data, size_t Size, uint64_t Initial) { + uint64_t Res = Initial; + const uint8_t *Bytes = static_cast(Data); + for (size_t i = 0; i < Size; i++) + Res = Res * 11 + Bytes[i]; + return Res; +} + +} // namespace fuzzer diff --git a/core/lib/libfuzzer-sys/libfuzzer/FuzzerUtil.h b/core/lib/libfuzzer-sys/libfuzzer/FuzzerUtil.h new file mode 100644 index 0000000000..a188a7be32 --- /dev/null +++ b/core/lib/libfuzzer-sys/libfuzzer/FuzzerUtil.h @@ -0,0 +1,119 @@ +//===- FuzzerUtil.h - Internal header for the Fuzzer Utils ------*- C++ -* ===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// Util functions. +//===----------------------------------------------------------------------===// + +#ifndef LLVM_FUZZER_UTIL_H +#define LLVM_FUZZER_UTIL_H + +#include "FuzzerBuiltins.h" +#include "FuzzerBuiltinsMsvc.h" +#include "FuzzerCommand.h" +#include "FuzzerDefs.h" + +namespace fuzzer { + +void PrintHexArray(const Unit &U, const char *PrintAfter = ""); + +void PrintHexArray(const uint8_t *Data, size_t Size, + const char *PrintAfter = ""); + +void PrintASCII(const uint8_t *Data, size_t Size, const char *PrintAfter = ""); + +void PrintASCII(const Unit &U, const char *PrintAfter = ""); + +// Changes U to contain only ASCII (isprint+isspace) characters. +// Returns true iff U has been changed. +bool ToASCII(uint8_t *Data, size_t Size); + +bool IsASCII(const Unit &U); + +bool IsASCII(const uint8_t *Data, size_t Size); + +std::string Base64(const Unit &U); + +void PrintPC(const char *SymbolizedFMT, const char *FallbackFMT, uintptr_t PC); + +std::string DescribePC(const char *SymbolizedFMT, uintptr_t PC); + +void PrintStackTrace(); + +void PrintMemoryProfile(); + +unsigned NumberOfCpuCores(); + +// Platform specific functions. +void SetSignalHandler(const FuzzingOptions& Options); + +void SleepSeconds(int Seconds); + +unsigned long GetPid(); + +size_t GetPeakRSSMb(); + +int ExecuteCommand(const Command &Cmd); +bool ExecuteCommand(const Command &Cmd, std::string *CmdOutput); + +// Fuchsia does not have popen/pclose. +FILE *OpenProcessPipe(const char *Command, const char *Mode); +int CloseProcessPipe(FILE *F); + +const void *SearchMemory(const void *haystack, size_t haystacklen, + const void *needle, size_t needlelen); + +std::string CloneArgsWithoutX(const Vector &Args, + const char *X1, const char *X2); + +inline std::string CloneArgsWithoutX(const Vector &Args, + const char *X) { + return CloneArgsWithoutX(Args, X, X); +} + +inline std::pair SplitBefore(std::string X, + std::string S) { + auto Pos = S.find(X); + if (Pos == std::string::npos) + return std::make_pair(S, ""); + return std::make_pair(S.substr(0, Pos), S.substr(Pos)); +} + +void DiscardOutput(int Fd); + +std::string DisassembleCmd(const std::string &FileName); + +std::string SearchRegexCmd(const std::string &Regex); + +uint64_t SimpleFastHash(const void *Data, size_t Size, uint64_t Initial = 0); + +inline size_t Log(size_t X) { + return static_cast((sizeof(unsigned long long) * 8) - Clzll(X) - 1); +} + +inline size_t PageSize() { return 4096; } +inline uint8_t *RoundUpByPage(uint8_t *P) { + uintptr_t X = reinterpret_cast(P); + size_t Mask = PageSize() - 1; + X = (X + Mask) & ~Mask; + return reinterpret_cast(X); +} +inline uint8_t *RoundDownByPage(uint8_t *P) { + uintptr_t X = reinterpret_cast(P); + size_t Mask = PageSize() - 1; + X = X & ~Mask; + return reinterpret_cast(X); +} + +#if __BYTE_ORDER == __LITTLE_ENDIAN +template T HostToLE(T X) { return X; } +#else +template T HostToLE(T X) { return Bswap(X); } +#endif + +} // namespace fuzzer + +#endif // LLVM_FUZZER_UTIL_H diff --git a/core/lib/libfuzzer-sys/libfuzzer/FuzzerUtilDarwin.cpp b/core/lib/libfuzzer-sys/libfuzzer/FuzzerUtilDarwin.cpp new file mode 100644 index 0000000000..a5bed658a4 --- /dev/null +++ b/core/lib/libfuzzer-sys/libfuzzer/FuzzerUtilDarwin.cpp @@ -0,0 +1,170 @@ +//===- FuzzerUtilDarwin.cpp - Misc utils ----------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// Misc utils for Darwin. +//===----------------------------------------------------------------------===// +#include "FuzzerPlatform.h" +#if LIBFUZZER_APPLE +#include "FuzzerCommand.h" +#include "FuzzerIO.h" +#include +#include +#include +#include +#include +#include +#include + +// There is no header for this on macOS so declare here +extern "C" char **environ; + +namespace fuzzer { + +static std::mutex SignalMutex; +// Global variables used to keep track of how signal handling should be +// restored. They should **not** be accessed without holding `SignalMutex`. +static int ActiveThreadCount = 0; +static struct sigaction OldSigIntAction; +static struct sigaction OldSigQuitAction; +static sigset_t OldBlockedSignalsSet; + +// This is a reimplementation of Libc's `system()`. On Darwin the Libc +// implementation contains a mutex which prevents it from being used +// concurrently. This implementation **can** be used concurrently. It sets the +// signal handlers when the first thread enters and restores them when the last +// thread finishes execution of the function and ensures this is not racey by +// using a mutex. +int ExecuteCommand(const Command &Cmd) { + std::string CmdLine = Cmd.toString(); + posix_spawnattr_t SpawnAttributes; + if (posix_spawnattr_init(&SpawnAttributes)) + return -1; + // Block and ignore signals of the current process when the first thread + // enters. + { + std::lock_guard Lock(SignalMutex); + if (ActiveThreadCount == 0) { + static struct sigaction IgnoreSignalAction; + sigset_t BlockedSignalsSet; + memset(&IgnoreSignalAction, 0, sizeof(IgnoreSignalAction)); + IgnoreSignalAction.sa_handler = SIG_IGN; + + if (sigaction(SIGINT, &IgnoreSignalAction, &OldSigIntAction) == -1) { + Printf("Failed to ignore SIGINT\n"); + (void)posix_spawnattr_destroy(&SpawnAttributes); + return -1; + } + if (sigaction(SIGQUIT, &IgnoreSignalAction, &OldSigQuitAction) == -1) { + Printf("Failed to ignore SIGQUIT\n"); + // Try our best to restore the signal handlers. + (void)sigaction(SIGINT, &OldSigIntAction, NULL); + (void)posix_spawnattr_destroy(&SpawnAttributes); + return -1; + } + + (void)sigemptyset(&BlockedSignalsSet); + (void)sigaddset(&BlockedSignalsSet, SIGCHLD); + if (sigprocmask(SIG_BLOCK, &BlockedSignalsSet, &OldBlockedSignalsSet) == + -1) { + Printf("Failed to block SIGCHLD\n"); + // Try our best to restore the signal handlers. + (void)sigaction(SIGQUIT, &OldSigQuitAction, NULL); + (void)sigaction(SIGINT, &OldSigIntAction, NULL); + (void)posix_spawnattr_destroy(&SpawnAttributes); + return -1; + } + } + ++ActiveThreadCount; + } + + // NOTE: Do not introduce any new `return` statements past this + // point. It is important that `ActiveThreadCount` always be decremented + // when leaving this function. + + // Make sure the child process uses the default handlers for the + // following signals rather than inheriting what the parent has. + sigset_t DefaultSigSet; + (void)sigemptyset(&DefaultSigSet); + (void)sigaddset(&DefaultSigSet, SIGQUIT); + (void)sigaddset(&DefaultSigSet, SIGINT); + (void)posix_spawnattr_setsigdefault(&SpawnAttributes, &DefaultSigSet); + // Make sure the child process doesn't block SIGCHLD + (void)posix_spawnattr_setsigmask(&SpawnAttributes, &OldBlockedSignalsSet); + short SpawnFlags = POSIX_SPAWN_SETSIGDEF | POSIX_SPAWN_SETSIGMASK; + (void)posix_spawnattr_setflags(&SpawnAttributes, SpawnFlags); + + pid_t Pid; + char **Environ = environ; // Read from global + const char *CommandCStr = CmdLine.c_str(); + char *const Argv[] = { + strdup("sh"), + strdup("-c"), + strdup(CommandCStr), + NULL + }; + int ErrorCode = 0, ProcessStatus = 0; + // FIXME: We probably shouldn't hardcode the shell path. + ErrorCode = posix_spawn(&Pid, "/bin/sh", NULL, &SpawnAttributes, + Argv, Environ); + (void)posix_spawnattr_destroy(&SpawnAttributes); + if (!ErrorCode) { + pid_t SavedPid = Pid; + do { + // Repeat until call completes uninterrupted. + Pid = waitpid(SavedPid, &ProcessStatus, /*options=*/0); + } while (Pid == -1 && errno == EINTR); + if (Pid == -1) { + // Fail for some other reason. + ProcessStatus = -1; + } + } else if (ErrorCode == ENOMEM || ErrorCode == EAGAIN) { + // Fork failure. + ProcessStatus = -1; + } else { + // Shell execution failure. + ProcessStatus = W_EXITCODE(127, 0); + } + for (unsigned i = 0, n = sizeof(Argv) / sizeof(Argv[0]); i < n; ++i) + free(Argv[i]); + + // Restore the signal handlers of the current process when the last thread + // using this function finishes. + { + std::lock_guard Lock(SignalMutex); + --ActiveThreadCount; + if (ActiveThreadCount == 0) { + bool FailedRestore = false; + if (sigaction(SIGINT, &OldSigIntAction, NULL) == -1) { + Printf("Failed to restore SIGINT handling\n"); + FailedRestore = true; + } + if (sigaction(SIGQUIT, &OldSigQuitAction, NULL) == -1) { + Printf("Failed to restore SIGQUIT handling\n"); + FailedRestore = true; + } + if (sigprocmask(SIG_BLOCK, &OldBlockedSignalsSet, NULL) == -1) { + Printf("Failed to unblock SIGCHLD\n"); + FailedRestore = true; + } + if (FailedRestore) + ProcessStatus = -1; + } + } + return ProcessStatus; +} + +void DiscardOutput(int Fd) { + FILE* Temp = fopen("/dev/null", "w"); + if (!Temp) + return; + dup2(fileno(Temp), Fd); + fclose(Temp); +} + +} // namespace fuzzer + +#endif // LIBFUZZER_APPLE diff --git a/core/lib/libfuzzer-sys/libfuzzer/FuzzerUtilFuchsia.cpp b/core/lib/libfuzzer-sys/libfuzzer/FuzzerUtilFuchsia.cpp new file mode 100644 index 0000000000..5034b4a28d --- /dev/null +++ b/core/lib/libfuzzer-sys/libfuzzer/FuzzerUtilFuchsia.cpp @@ -0,0 +1,550 @@ +//===- FuzzerUtilFuchsia.cpp - Misc utils for Fuchsia. --------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// Misc utils implementation using Fuchsia/Zircon APIs. +//===----------------------------------------------------------------------===// +#include "FuzzerPlatform.h" + +#if LIBFUZZER_FUCHSIA + +#include "FuzzerInternal.h" +#include "FuzzerUtil.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace fuzzer { + +// Given that Fuchsia doesn't have the POSIX signals that libFuzzer was written +// around, the general approach is to spin up dedicated threads to watch for +// each requested condition (alarm, interrupt, crash). Of these, the crash +// handler is the most involved, as it requires resuming the crashed thread in +// order to invoke the sanitizers to get the needed state. + +// Forward declaration of assembly trampoline needed to resume crashed threads. +// This appears to have external linkage to C++, which is why it's not in the +// anonymous namespace. The assembly definition inside MakeTrampoline() +// actually defines the symbol with internal linkage only. +void CrashTrampolineAsm() __asm__("CrashTrampolineAsm"); + +namespace { + +// Helper function to handle Zircon syscall failures. +void ExitOnErr(zx_status_t Status, const char *Syscall) { + if (Status != ZX_OK) { + Printf("libFuzzer: %s failed: %s\n", Syscall, + _zx_status_get_string(Status)); + exit(1); + } +} + +void AlarmHandler(int Seconds) { + while (true) { + SleepSeconds(Seconds); + Fuzzer::StaticAlarmCallback(); + } +} + +// CFAOffset is used to reference the stack pointer before entering the +// trampoline (Stack Pointer + CFAOffset = prev Stack Pointer). Before jumping +// to the trampoline we copy all the registers onto the stack. We need to make +// sure that the new stack has enough space to store all the registers. +// +// The trampoline holds CFI information regarding the registers stored in the +// stack, which is then used by the unwinder to restore them. +#if defined(__x86_64__) +// In x86_64 the crashing function might also be using the red zone (128 bytes +// on top of their rsp). +constexpr size_t CFAOffset = 128 + sizeof(zx_thread_state_general_regs_t); +#elif defined(__aarch64__) +// In aarch64 we need to always have the stack pointer aligned to 16 bytes, so we +// make sure that we are keeping that same alignment. +constexpr size_t CFAOffset = (sizeof(zx_thread_state_general_regs_t) + 15) & -(uintptr_t)16; +#endif + +// For the crash handler, we need to call Fuzzer::StaticCrashSignalCallback +// without POSIX signal handlers. To achieve this, we use an assembly function +// to add the necessary CFI unwinding information and a C function to bridge +// from that back into C++. + +// FIXME: This works as a short-term solution, but this code really shouldn't be +// architecture dependent. A better long term solution is to implement remote +// unwinding and expose the necessary APIs through sanitizer_common and/or ASAN +// to allow the exception handling thread to gather the crash state directly. +// +// Alternatively, Fuchsia may in future actually implement basic signal +// handling for the machine trap signals. +#if defined(__x86_64__) +#define FOREACH_REGISTER(OP_REG, OP_NUM) \ + OP_REG(rax) \ + OP_REG(rbx) \ + OP_REG(rcx) \ + OP_REG(rdx) \ + OP_REG(rsi) \ + OP_REG(rdi) \ + OP_REG(rbp) \ + OP_REG(rsp) \ + OP_REG(r8) \ + OP_REG(r9) \ + OP_REG(r10) \ + OP_REG(r11) \ + OP_REG(r12) \ + OP_REG(r13) \ + OP_REG(r14) \ + OP_REG(r15) \ + OP_REG(rip) + +#elif defined(__aarch64__) +#define FOREACH_REGISTER(OP_REG, OP_NUM) \ + OP_NUM(0) \ + OP_NUM(1) \ + OP_NUM(2) \ + OP_NUM(3) \ + OP_NUM(4) \ + OP_NUM(5) \ + OP_NUM(6) \ + OP_NUM(7) \ + OP_NUM(8) \ + OP_NUM(9) \ + OP_NUM(10) \ + OP_NUM(11) \ + OP_NUM(12) \ + OP_NUM(13) \ + OP_NUM(14) \ + OP_NUM(15) \ + OP_NUM(16) \ + OP_NUM(17) \ + OP_NUM(18) \ + OP_NUM(19) \ + OP_NUM(20) \ + OP_NUM(21) \ + OP_NUM(22) \ + OP_NUM(23) \ + OP_NUM(24) \ + OP_NUM(25) \ + OP_NUM(26) \ + OP_NUM(27) \ + OP_NUM(28) \ + OP_NUM(29) \ + OP_REG(sp) + +#else +#error "Unsupported architecture for fuzzing on Fuchsia" +#endif + +// Produces a CFI directive for the named or numbered register. +// The value used refers to an assembler immediate operand with the same name +// as the register (see ASM_OPERAND_REG). +#define CFI_OFFSET_REG(reg) ".cfi_offset " #reg ", %c[" #reg "]\n" +#define CFI_OFFSET_NUM(num) CFI_OFFSET_REG(x##num) + +// Produces an assembler immediate operand for the named or numbered register. +// This operand contains the offset of the register relative to the CFA. +#define ASM_OPERAND_REG(reg) \ + [reg] "i"(offsetof(zx_thread_state_general_regs_t, reg) - CFAOffset), +#define ASM_OPERAND_NUM(num) \ + [x##num] "i"(offsetof(zx_thread_state_general_regs_t, r[num]) - CFAOffset), + +// Trampoline to bridge from the assembly below to the static C++ crash +// callback. +__attribute__((noreturn)) +static void StaticCrashHandler() { + Fuzzer::StaticCrashSignalCallback(); + for (;;) { + _Exit(1); + } +} + +// Creates the trampoline with the necessary CFI information to unwind through +// to the crashing call stack: +// * Defining the CFA so that it points to the stack pointer at the point +// of crash. +// * Storing all registers at the point of crash in the stack and refer to them +// via CFI information (relative to the CFA). +// * Setting the return column so the unwinder knows how to continue unwinding. +// * (x86_64) making sure rsp is aligned before calling StaticCrashHandler. +// * Calling StaticCrashHandler that will trigger the unwinder. +// +// The __attribute__((used)) is necessary because the function +// is never called; it's just a container around the assembly to allow it to +// use operands for compile-time computed constants. +__attribute__((used)) +void MakeTrampoline() { + __asm__(".cfi_endproc\n" + ".pushsection .text.CrashTrampolineAsm\n" + ".type CrashTrampolineAsm,STT_FUNC\n" +"CrashTrampolineAsm:\n" + ".cfi_startproc simple\n" + ".cfi_signal_frame\n" +#if defined(__x86_64__) + ".cfi_return_column rip\n" + ".cfi_def_cfa rsp, %c[CFAOffset]\n" + FOREACH_REGISTER(CFI_OFFSET_REG, CFI_OFFSET_NUM) + "mov %%rsp, %%rbp\n" + ".cfi_def_cfa_register rbp\n" + "andq $-16, %%rsp\n" + "call %c[StaticCrashHandler]\n" + "ud2\n" +#elif defined(__aarch64__) + ".cfi_return_column 33\n" + ".cfi_def_cfa sp, %c[CFAOffset]\n" + FOREACH_REGISTER(CFI_OFFSET_REG, CFI_OFFSET_NUM) + ".cfi_offset 33, %c[pc]\n" + ".cfi_offset 30, %c[lr]\n" + "bl %c[StaticCrashHandler]\n" + "brk 1\n" +#else +#error "Unsupported architecture for fuzzing on Fuchsia" +#endif + ".cfi_endproc\n" + ".size CrashTrampolineAsm, . - CrashTrampolineAsm\n" + ".popsection\n" + ".cfi_startproc\n" + : // No outputs + : FOREACH_REGISTER(ASM_OPERAND_REG, ASM_OPERAND_NUM) +#if defined(__aarch64__) + ASM_OPERAND_REG(pc) + ASM_OPERAND_REG(lr) +#endif + [StaticCrashHandler] "i" (StaticCrashHandler), + [CFAOffset] "i" (CFAOffset)); +} + +void CrashHandler(zx_handle_t *Event) { + // This structure is used to ensure we close handles to objects we create in + // this handler. + struct ScopedHandle { + ~ScopedHandle() { _zx_handle_close(Handle); } + zx_handle_t Handle = ZX_HANDLE_INVALID; + }; + + // Create the exception channel. We need to claim to be a "debugger" so the + // kernel will allow us to modify and resume dying threads (see below). Once + // the channel is set, we can signal the main thread to continue and wait + // for the exception to arrive. + ScopedHandle Channel; + zx_handle_t Self = _zx_process_self(); + ExitOnErr(_zx_task_create_exception_channel( + Self, ZX_EXCEPTION_CHANNEL_DEBUGGER, &Channel.Handle), + "_zx_task_create_exception_channel"); + + ExitOnErr(_zx_object_signal(*Event, 0, ZX_USER_SIGNAL_0), + "_zx_object_signal"); + + // This thread lives as long as the process in order to keep handling + // crashes. In practice, the first crashed thread to reach the end of the + // StaticCrashHandler will end the process. + while (true) { + ExitOnErr(_zx_object_wait_one(Channel.Handle, ZX_CHANNEL_READABLE, + ZX_TIME_INFINITE, nullptr), + "_zx_object_wait_one"); + + zx_exception_info_t ExceptionInfo; + ScopedHandle Exception; + ExitOnErr(_zx_channel_read(Channel.Handle, 0, &ExceptionInfo, + &Exception.Handle, sizeof(ExceptionInfo), 1, + nullptr, nullptr), + "_zx_channel_read"); + + // Ignore informational synthetic exceptions. + if (ZX_EXCP_THREAD_STARTING == ExceptionInfo.type || + ZX_EXCP_THREAD_EXITING == ExceptionInfo.type || + ZX_EXCP_PROCESS_STARTING == ExceptionInfo.type) { + continue; + } + + // At this point, we want to get the state of the crashing thread, but + // libFuzzer and the sanitizers assume this will happen from that same + // thread via a POSIX signal handler. "Resurrecting" the thread in the + // middle of the appropriate callback is as simple as forcibly setting the + // instruction pointer/program counter, provided we NEVER EVER return from + // that function (since otherwise our stack will not be valid). + ScopedHandle Thread; + ExitOnErr(_zx_exception_get_thread(Exception.Handle, &Thread.Handle), + "_zx_exception_get_thread"); + + zx_thread_state_general_regs_t GeneralRegisters; + ExitOnErr(_zx_thread_read_state(Thread.Handle, ZX_THREAD_STATE_GENERAL_REGS, + &GeneralRegisters, + sizeof(GeneralRegisters)), + "_zx_thread_read_state"); + + // To unwind properly, we need to push the crashing thread's register state + // onto the stack and jump into a trampoline with CFI instructions on how + // to restore it. +#if defined(__x86_64__) + uintptr_t StackPtr = GeneralRegisters.rsp - CFAOffset; + __unsanitized_memcpy(reinterpret_cast(StackPtr), &GeneralRegisters, + sizeof(GeneralRegisters)); + GeneralRegisters.rsp = StackPtr; + GeneralRegisters.rip = reinterpret_cast(CrashTrampolineAsm); + +#elif defined(__aarch64__) + uintptr_t StackPtr = GeneralRegisters.sp - CFAOffset; + __unsanitized_memcpy(reinterpret_cast(StackPtr), &GeneralRegisters, + sizeof(GeneralRegisters)); + GeneralRegisters.sp = StackPtr; + GeneralRegisters.pc = reinterpret_cast(CrashTrampolineAsm); + +#else +#error "Unsupported architecture for fuzzing on Fuchsia" +#endif + + // Now force the crashing thread's state. + ExitOnErr( + _zx_thread_write_state(Thread.Handle, ZX_THREAD_STATE_GENERAL_REGS, + &GeneralRegisters, sizeof(GeneralRegisters)), + "_zx_thread_write_state"); + + // Set the exception to HANDLED so it resumes the thread on close. + uint32_t ExceptionState = ZX_EXCEPTION_STATE_HANDLED; + ExitOnErr(_zx_object_set_property(Exception.Handle, ZX_PROP_EXCEPTION_STATE, + &ExceptionState, sizeof(ExceptionState)), + "zx_object_set_property"); + } +} + +} // namespace + +// Platform specific functions. +void SetSignalHandler(const FuzzingOptions &Options) { + // Make sure information from libFuzzer and the sanitizers are easy to + // reassemble. `__sanitizer_log_write` has the added benefit of ensuring the + // DSO map is always available for the symbolizer. + // A uint64_t fits in 20 chars, so 64 is plenty. + char Buf[64]; + memset(Buf, 0, sizeof(Buf)); + snprintf(Buf, sizeof(Buf), "==%lu== INFO: libFuzzer starting.\n", GetPid()); + if (EF->__sanitizer_log_write) + __sanitizer_log_write(Buf, sizeof(Buf)); + Printf("%s", Buf); + + // Set up alarm handler if needed. + if (Options.HandleAlrm && Options.UnitTimeoutSec > 0) { + std::thread T(AlarmHandler, Options.UnitTimeoutSec / 2 + 1); + T.detach(); + } + + // Options.HandleInt and Options.HandleTerm are not supported on Fuchsia + + // Early exit if no crash handler needed. + if (!Options.HandleSegv && !Options.HandleBus && !Options.HandleIll && + !Options.HandleFpe && !Options.HandleAbrt) + return; + + // Set up the crash handler and wait until it is ready before proceeding. + zx_handle_t Event; + ExitOnErr(_zx_event_create(0, &Event), "_zx_event_create"); + + std::thread T(CrashHandler, &Event); + zx_status_t Status = + _zx_object_wait_one(Event, ZX_USER_SIGNAL_0, ZX_TIME_INFINITE, nullptr); + _zx_handle_close(Event); + ExitOnErr(Status, "_zx_object_wait_one"); + + T.detach(); +} + +void SleepSeconds(int Seconds) { + _zx_nanosleep(_zx_deadline_after(ZX_SEC(Seconds))); +} + +unsigned long GetPid() { + zx_status_t rc; + zx_info_handle_basic_t Info; + if ((rc = _zx_object_get_info(_zx_process_self(), ZX_INFO_HANDLE_BASIC, &Info, + sizeof(Info), NULL, NULL)) != ZX_OK) { + Printf("libFuzzer: unable to get info about self: %s\n", + _zx_status_get_string(rc)); + exit(1); + } + return Info.koid; +} + +size_t GetPeakRSSMb() { + zx_status_t rc; + zx_info_task_stats_t Info; + if ((rc = _zx_object_get_info(_zx_process_self(), ZX_INFO_TASK_STATS, &Info, + sizeof(Info), NULL, NULL)) != ZX_OK) { + Printf("libFuzzer: unable to get info about self: %s\n", + _zx_status_get_string(rc)); + exit(1); + } + return (Info.mem_private_bytes + Info.mem_shared_bytes) >> 20; +} + +template +class RunOnDestruction { + public: + explicit RunOnDestruction(Fn fn) : fn_(fn) {} + ~RunOnDestruction() { fn_(); } + + private: + Fn fn_; +}; + +template +RunOnDestruction at_scope_exit(Fn fn) { + return RunOnDestruction(fn); +} + +static fdio_spawn_action_t clone_fd_action(int localFd, int targetFd) { + return { + .action = FDIO_SPAWN_ACTION_CLONE_FD, + .fd = + { + .local_fd = localFd, + .target_fd = targetFd, + }, + }; +} + +int ExecuteCommand(const Command &Cmd) { + zx_status_t rc; + + // Convert arguments to C array + auto Args = Cmd.getArguments(); + size_t Argc = Args.size(); + assert(Argc != 0); + std::unique_ptr Argv(new const char *[Argc + 1]); + for (size_t i = 0; i < Argc; ++i) + Argv[i] = Args[i].c_str(); + Argv[Argc] = nullptr; + + // Determine output. On Fuchsia, the fuzzer is typically run as a component + // that lacks a mutable working directory. Fortunately, when this is the case + // a mutable output directory must be specified using "-artifact_prefix=...", + // so write the log file(s) there. + // However, we don't want to apply this logic for absolute paths. + int FdOut = STDOUT_FILENO; + bool discardStdout = false; + bool discardStderr = false; + + if (Cmd.hasOutputFile()) { + std::string Path = Cmd.getOutputFile(); + if (Path == getDevNull()) { + // On Fuchsia, there's no "/dev/null" like-file, so we + // just don't copy the FDs into the spawned process. + discardStdout = true; + } else { + bool IsAbsolutePath = Path.length() > 1 && Path[0] == '/'; + if (!IsAbsolutePath && Cmd.hasFlag("artifact_prefix")) + Path = Cmd.getFlagValue("artifact_prefix") + "/" + Path; + + FdOut = open(Path.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0); + if (FdOut == -1) { + Printf("libFuzzer: failed to open %s: %s\n", Path.c_str(), + strerror(errno)); + return ZX_ERR_IO; + } + } + } + auto CloseFdOut = at_scope_exit([FdOut]() { + if (FdOut != STDOUT_FILENO) + close(FdOut); + }); + + // Determine stderr + int FdErr = STDERR_FILENO; + if (Cmd.isOutAndErrCombined()) { + FdErr = FdOut; + if (discardStdout) + discardStderr = true; + } + + // Clone the file descriptors into the new process + std::vector SpawnActions; + SpawnActions.push_back(clone_fd_action(STDIN_FILENO, STDIN_FILENO)); + + if (!discardStdout) + SpawnActions.push_back(clone_fd_action(FdOut, STDOUT_FILENO)); + if (!discardStderr) + SpawnActions.push_back(clone_fd_action(FdErr, STDERR_FILENO)); + + // Start the process. + char ErrorMsg[FDIO_SPAWN_ERR_MSG_MAX_LENGTH]; + zx_handle_t ProcessHandle = ZX_HANDLE_INVALID; + rc = fdio_spawn_etc(ZX_HANDLE_INVALID, + FDIO_SPAWN_CLONE_ALL & (~FDIO_SPAWN_CLONE_STDIO), Argv[0], + Argv.get(), nullptr, SpawnActions.size(), + SpawnActions.data(), &ProcessHandle, ErrorMsg); + + if (rc != ZX_OK) { + Printf("libFuzzer: failed to launch '%s': %s, %s\n", Argv[0], ErrorMsg, + _zx_status_get_string(rc)); + return rc; + } + auto CloseHandle = at_scope_exit([&]() { _zx_handle_close(ProcessHandle); }); + + // Now join the process and return the exit status. + if ((rc = _zx_object_wait_one(ProcessHandle, ZX_PROCESS_TERMINATED, + ZX_TIME_INFINITE, nullptr)) != ZX_OK) { + Printf("libFuzzer: failed to join '%s': %s\n", Argv[0], + _zx_status_get_string(rc)); + return rc; + } + + zx_info_process_t Info; + if ((rc = _zx_object_get_info(ProcessHandle, ZX_INFO_PROCESS, &Info, + sizeof(Info), nullptr, nullptr)) != ZX_OK) { + Printf("libFuzzer: unable to get return code from '%s': %s\n", Argv[0], + _zx_status_get_string(rc)); + return rc; + } + + return static_cast(Info.return_code); +} + +bool ExecuteCommand(const Command &BaseCmd, std::string *CmdOutput) { + auto LogFilePath = TempPath("SimPopenOut", ".txt"); + Command Cmd(BaseCmd); + Cmd.setOutputFile(LogFilePath); + int Ret = ExecuteCommand(Cmd); + *CmdOutput = FileToString(LogFilePath); + RemoveFile(LogFilePath); + return Ret == 0; +} + +const void *SearchMemory(const void *Data, size_t DataLen, const void *Patt, + size_t PattLen) { + return memmem(Data, DataLen, Patt, PattLen); +} + +// In fuchsia, accessing /dev/null is not supported. There's nothing +// similar to a file that discards everything that is written to it. +// The way of doing something similar in fuchsia is by using +// fdio_null_create and binding that to a file descriptor. +void DiscardOutput(int Fd) { + fdio_t *fdio_null = fdio_null_create(); + if (fdio_null == nullptr) return; + int nullfd = fdio_bind_to_fd(fdio_null, -1, 0); + if (nullfd < 0) return; + dup2(nullfd, Fd); +} + +} // namespace fuzzer + +#endif // LIBFUZZER_FUCHSIA diff --git a/core/lib/libfuzzer-sys/libfuzzer/FuzzerUtilLinux.cpp b/core/lib/libfuzzer-sys/libfuzzer/FuzzerUtilLinux.cpp new file mode 100644 index 0000000000..981f9a8b42 --- /dev/null +++ b/core/lib/libfuzzer-sys/libfuzzer/FuzzerUtilLinux.cpp @@ -0,0 +1,41 @@ +//===- FuzzerUtilLinux.cpp - Misc utils for Linux. ------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// Misc utils for Linux. +//===----------------------------------------------------------------------===// +#include "FuzzerPlatform.h" +#if LIBFUZZER_LINUX || LIBFUZZER_NETBSD || LIBFUZZER_FREEBSD || \ + LIBFUZZER_EMSCRIPTEN +#include "FuzzerCommand.h" + +#include +#include +#include +#include + + +namespace fuzzer { + +int ExecuteCommand(const Command &Cmd) { + std::string CmdLine = Cmd.toString(); + int exit_code = system(CmdLine.c_str()); + if (WIFEXITED(exit_code)) + return WEXITSTATUS(exit_code); + return exit_code; +} + +void DiscardOutput(int Fd) { + FILE* Temp = fopen("/dev/null", "w"); + if (!Temp) + return; + dup2(fileno(Temp), Fd); + fclose(Temp); +} + +} // namespace fuzzer + +#endif diff --git a/core/lib/libfuzzer-sys/libfuzzer/FuzzerUtilPosix.cpp b/core/lib/libfuzzer-sys/libfuzzer/FuzzerUtilPosix.cpp new file mode 100644 index 0000000000..0446d732a9 --- /dev/null +++ b/core/lib/libfuzzer-sys/libfuzzer/FuzzerUtilPosix.cpp @@ -0,0 +1,188 @@ +//===- FuzzerUtilPosix.cpp - Misc utils for Posix. ------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// Misc utils implementation using Posix API. +//===----------------------------------------------------------------------===// +#include "FuzzerPlatform.h" +#if LIBFUZZER_POSIX +#include "FuzzerIO.h" +#include "FuzzerInternal.h" +#include "FuzzerTracePC.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace fuzzer { + +static void AlarmHandler(int, siginfo_t *, void *) { + Fuzzer::StaticAlarmCallback(); +} + +static void (*upstream_segv_handler)(int, siginfo_t *, void *); + +static void SegvHandler(int sig, siginfo_t *si, void *ucontext) { + assert(si->si_signo == SIGSEGV); + if (upstream_segv_handler) + return upstream_segv_handler(sig, si, ucontext); + Fuzzer::StaticCrashSignalCallback(); +} + +static void CrashHandler(int, siginfo_t *, void *) { + Fuzzer::StaticCrashSignalCallback(); +} + +static void InterruptHandler(int, siginfo_t *, void *) { + Fuzzer::StaticInterruptCallback(); +} + +static void GracefulExitHandler(int, siginfo_t *, void *) { + Fuzzer::StaticGracefulExitCallback(); +} + +static void FileSizeExceedHandler(int, siginfo_t *, void *) { + Fuzzer::StaticFileSizeExceedCallback(); +} + +static void SetSigaction(int signum, + void (*callback)(int, siginfo_t *, void *)) { + struct sigaction sigact = {}; + if (sigaction(signum, nullptr, &sigact)) { + Printf("libFuzzer: sigaction failed with %d\n", errno); + exit(1); + } + if (sigact.sa_flags & SA_SIGINFO) { + if (sigact.sa_sigaction) { + if (signum != SIGSEGV) + return; + upstream_segv_handler = sigact.sa_sigaction; + } + } else { + if (sigact.sa_handler != SIG_DFL && sigact.sa_handler != SIG_IGN && + sigact.sa_handler != SIG_ERR) + return; + } + + struct sigaction new_sigact = {}; + // Address sanitizer needs SA_ONSTACK (causing the signal handler to run on a + // dedicated stack) in order to be able to detect stack overflows; keep the + // flag if it's set. + new_sigact.sa_flags = SA_SIGINFO | (sigact.sa_flags & SA_ONSTACK); + new_sigact.sa_sigaction = callback; + if (sigaction(signum, &new_sigact, nullptr)) { + Printf("libFuzzer: sigaction failed with %d\n", errno); + exit(1); + } +} + +// Return true on success, false otherwise. +bool ExecuteCommand(const Command &Cmd, std::string *CmdOutput) { + FILE *Pipe = popen(Cmd.toString().c_str(), "r"); + if (!Pipe) + return false; + + if (CmdOutput) { + char TmpBuffer[128]; + while (fgets(TmpBuffer, sizeof(TmpBuffer), Pipe)) + CmdOutput->append(TmpBuffer); + } + return pclose(Pipe) == 0; +} + +void SetTimer(int Seconds) { + struct itimerval T { + {Seconds, 0}, { Seconds, 0 } + }; + if (setitimer(ITIMER_REAL, &T, nullptr)) { + Printf("libFuzzer: setitimer failed with %d\n", errno); + exit(1); + } + SetSigaction(SIGALRM, AlarmHandler); +} + +void SetSignalHandler(const FuzzingOptions& Options) { + // setitimer is not implemented in emscripten. + if (Options.HandleAlrm && Options.UnitTimeoutSec > 0 && !LIBFUZZER_EMSCRIPTEN) + SetTimer(Options.UnitTimeoutSec / 2 + 1); + if (Options.HandleInt) + SetSigaction(SIGINT, InterruptHandler); + if (Options.HandleTerm) + SetSigaction(SIGTERM, InterruptHandler); + if (Options.HandleSegv) + SetSigaction(SIGSEGV, SegvHandler); + if (Options.HandleBus) + SetSigaction(SIGBUS, CrashHandler); + if (Options.HandleAbrt) + SetSigaction(SIGABRT, CrashHandler); + if (Options.HandleIll) + SetSigaction(SIGILL, CrashHandler); + if (Options.HandleFpe) + SetSigaction(SIGFPE, CrashHandler); + if (Options.HandleXfsz) + SetSigaction(SIGXFSZ, FileSizeExceedHandler); + if (Options.HandleUsr1) + SetSigaction(SIGUSR1, GracefulExitHandler); + if (Options.HandleUsr2) + SetSigaction(SIGUSR2, GracefulExitHandler); +} + +void SleepSeconds(int Seconds) { + sleep(Seconds); // Use C API to avoid coverage from instrumented libc++. +} + +unsigned long GetPid() { return (unsigned long)getpid(); } + +size_t GetPeakRSSMb() { + struct rusage usage; + if (getrusage(RUSAGE_SELF, &usage)) + return 0; + if (LIBFUZZER_LINUX || LIBFUZZER_FREEBSD || LIBFUZZER_NETBSD || + LIBFUZZER_EMSCRIPTEN) { + // ru_maxrss is in KiB + return usage.ru_maxrss >> 10; + } else if (LIBFUZZER_APPLE) { + // ru_maxrss is in bytes + return usage.ru_maxrss >> 20; + } + assert(0 && "GetPeakRSSMb() is not implemented for your platform"); + return 0; +} + +FILE *OpenProcessPipe(const char *Command, const char *Mode) { + return popen(Command, Mode); +} + +int CloseProcessPipe(FILE *F) { + return pclose(F); +} + +const void *SearchMemory(const void *Data, size_t DataLen, const void *Patt, + size_t PattLen) { + return memmem(Data, DataLen, Patt, PattLen); +} + +std::string DisassembleCmd(const std::string &FileName) { + return "objdump -d " + FileName; +} + +std::string SearchRegexCmd(const std::string &Regex) { + return "grep '" + Regex + "'"; +} + +} // namespace fuzzer + +#endif // LIBFUZZER_POSIX diff --git a/core/lib/libfuzzer-sys/libfuzzer/FuzzerUtilWindows.cpp b/core/lib/libfuzzer-sys/libfuzzer/FuzzerUtilWindows.cpp new file mode 100644 index 0000000000..1a54bb569e --- /dev/null +++ b/core/lib/libfuzzer-sys/libfuzzer/FuzzerUtilWindows.cpp @@ -0,0 +1,229 @@ +//===- FuzzerUtilWindows.cpp - Misc utils for Windows. --------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// Misc utils implementation for Windows. +//===----------------------------------------------------------------------===// +#include "FuzzerPlatform.h" +#if LIBFUZZER_WINDOWS +#include "FuzzerCommand.h" +#include "FuzzerIO.h" +#include "FuzzerInternal.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// This must be included after windows.h. +#include + +namespace fuzzer { + +static const FuzzingOptions* HandlerOpt = nullptr; + +static LONG CALLBACK ExceptionHandler(PEXCEPTION_POINTERS ExceptionInfo) { + switch (ExceptionInfo->ExceptionRecord->ExceptionCode) { + case EXCEPTION_ACCESS_VIOLATION: + case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: + case EXCEPTION_STACK_OVERFLOW: + if (HandlerOpt->HandleSegv) + Fuzzer::StaticCrashSignalCallback(); + break; + case EXCEPTION_DATATYPE_MISALIGNMENT: + case EXCEPTION_IN_PAGE_ERROR: + if (HandlerOpt->HandleBus) + Fuzzer::StaticCrashSignalCallback(); + break; + case EXCEPTION_ILLEGAL_INSTRUCTION: + case EXCEPTION_PRIV_INSTRUCTION: + if (HandlerOpt->HandleIll) + Fuzzer::StaticCrashSignalCallback(); + break; + case EXCEPTION_FLT_DENORMAL_OPERAND: + case EXCEPTION_FLT_DIVIDE_BY_ZERO: + case EXCEPTION_FLT_INEXACT_RESULT: + case EXCEPTION_FLT_INVALID_OPERATION: + case EXCEPTION_FLT_OVERFLOW: + case EXCEPTION_FLT_STACK_CHECK: + case EXCEPTION_FLT_UNDERFLOW: + case EXCEPTION_INT_DIVIDE_BY_ZERO: + case EXCEPTION_INT_OVERFLOW: + if (HandlerOpt->HandleFpe) + Fuzzer::StaticCrashSignalCallback(); + break; + // This is an undocumented exception code corresponding to a Visual C++ + // Exception. + // + // See: https://devblogs.microsoft.com/oldnewthing/20100730-00/?p=13273 + case 0xE06D7363: + if (HandlerOpt->HandleWinExcept) + Fuzzer::StaticCrashSignalCallback(); + break; + // TODO: Handle (Options.HandleXfsz) + } + return EXCEPTION_CONTINUE_SEARCH; +} + +BOOL WINAPI CtrlHandler(DWORD dwCtrlType) { + switch (dwCtrlType) { + case CTRL_C_EVENT: + if (HandlerOpt->HandleInt) + Fuzzer::StaticInterruptCallback(); + return TRUE; + case CTRL_BREAK_EVENT: + if (HandlerOpt->HandleTerm) + Fuzzer::StaticInterruptCallback(); + return TRUE; + } + return FALSE; +} + +void CALLBACK AlarmHandler(PVOID, BOOLEAN) { + Fuzzer::StaticAlarmCallback(); +} + +class TimerQ { + HANDLE TimerQueue; + public: + TimerQ() : TimerQueue(NULL) {} + ~TimerQ() { + if (TimerQueue) + DeleteTimerQueueEx(TimerQueue, NULL); + } + void SetTimer(int Seconds) { + if (!TimerQueue) { + TimerQueue = CreateTimerQueue(); + if (!TimerQueue) { + Printf("libFuzzer: CreateTimerQueue failed.\n"); + exit(1); + } + } + HANDLE Timer; + if (!CreateTimerQueueTimer(&Timer, TimerQueue, AlarmHandler, NULL, + Seconds*1000, Seconds*1000, 0)) { + Printf("libFuzzer: CreateTimerQueueTimer failed.\n"); + exit(1); + } + } +}; + +static TimerQ Timer; + +static void CrashHandler(int) { Fuzzer::StaticCrashSignalCallback(); } + +void SetSignalHandler(const FuzzingOptions& Options) { + HandlerOpt = &Options; + + if (Options.HandleAlrm && Options.UnitTimeoutSec > 0) + Timer.SetTimer(Options.UnitTimeoutSec / 2 + 1); + + if (Options.HandleInt || Options.HandleTerm) + if (!SetConsoleCtrlHandler(CtrlHandler, TRUE)) { + DWORD LastError = GetLastError(); + Printf("libFuzzer: SetConsoleCtrlHandler failed (Error code: %lu).\n", + LastError); + exit(1); + } + + if (Options.HandleSegv || Options.HandleBus || Options.HandleIll || + Options.HandleFpe || Options.HandleWinExcept) + SetUnhandledExceptionFilter(ExceptionHandler); + + if (Options.HandleAbrt) + if (SIG_ERR == signal(SIGABRT, CrashHandler)) { + Printf("libFuzzer: signal failed with %d\n", errno); + exit(1); + } +} + +void SleepSeconds(int Seconds) { Sleep(Seconds * 1000); } + +unsigned long GetPid() { return GetCurrentProcessId(); } + +size_t GetPeakRSSMb() { + PROCESS_MEMORY_COUNTERS info; + if (!GetProcessMemoryInfo(GetCurrentProcess(), &info, sizeof(info))) + return 0; + return info.PeakWorkingSetSize >> 20; +} + +FILE *OpenProcessPipe(const char *Command, const char *Mode) { + return _popen(Command, Mode); +} + +int CloseProcessPipe(FILE *F) { + return _pclose(F); +} + +int ExecuteCommand(const Command &Cmd) { + std::string CmdLine = Cmd.toString(); + return system(CmdLine.c_str()); +} + +bool ExecuteCommand(const Command &Cmd, std::string *CmdOutput) { + FILE *Pipe = _popen(Cmd.toString().c_str(), "r"); + if (!Pipe) + return false; + + if (CmdOutput) { + char TmpBuffer[128]; + while (fgets(TmpBuffer, sizeof(TmpBuffer), Pipe)) + CmdOutput->append(TmpBuffer); + } + return _pclose(Pipe) == 0; +} + +const void *SearchMemory(const void *Data, size_t DataLen, const void *Patt, + size_t PattLen) { + // TODO: make this implementation more efficient. + const char *Cdata = (const char *)Data; + const char *Cpatt = (const char *)Patt; + + if (!Data || !Patt || DataLen == 0 || PattLen == 0 || DataLen < PattLen) + return NULL; + + if (PattLen == 1) + return memchr(Data, *Cpatt, DataLen); + + const char *End = Cdata + DataLen - PattLen + 1; + + for (const char *It = Cdata; It < End; ++It) + if (It[0] == Cpatt[0] && memcmp(It, Cpatt, PattLen) == 0) + return It; + + return NULL; +} + +std::string DisassembleCmd(const std::string &FileName) { + Vector command_vector; + command_vector.push_back("dumpbin /summary > nul"); + if (ExecuteCommand(Command(command_vector)) == 0) + return "dumpbin /disasm " + FileName; + Printf("libFuzzer: couldn't find tool to disassemble (dumpbin)\n"); + exit(1); +} + +std::string SearchRegexCmd(const std::string &Regex) { + return "findstr /r \"" + Regex + "\""; +} + +void DiscardOutput(int Fd) { + FILE* Temp = fopen("nul", "w"); + if (!Temp) + return; + _dup2(_fileno(Temp), Fd); + fclose(Temp); +} + +} // namespace fuzzer + +#endif // LIBFUZZER_WINDOWS diff --git a/core/lib/libfuzzer-sys/libfuzzer/FuzzerValueBitMap.h b/core/lib/libfuzzer-sys/libfuzzer/FuzzerValueBitMap.h new file mode 100644 index 0000000000..ddbfe200af --- /dev/null +++ b/core/lib/libfuzzer-sys/libfuzzer/FuzzerValueBitMap.h @@ -0,0 +1,73 @@ +//===- FuzzerValueBitMap.h - INTERNAL - Bit map -----------------*- C++ -* ===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// ValueBitMap. +//===----------------------------------------------------------------------===// + +#ifndef LLVM_FUZZER_VALUE_BIT_MAP_H +#define LLVM_FUZZER_VALUE_BIT_MAP_H + +#include "FuzzerPlatform.h" +#include + +namespace fuzzer { + +// A bit map containing kMapSizeInWords bits. +struct ValueBitMap { + static const size_t kMapSizeInBits = 1 << 16; + static const size_t kMapPrimeMod = 65371; // Largest Prime < kMapSizeInBits; + static const size_t kBitsInWord = (sizeof(uintptr_t) * 8); + static const size_t kMapSizeInWords = kMapSizeInBits / kBitsInWord; + public: + + // Clears all bits. + void Reset() { memset(Map, 0, sizeof(Map)); } + + // Computes a hash function of Value and sets the corresponding bit. + // Returns true if the bit was changed from 0 to 1. + ATTRIBUTE_NO_SANITIZE_ALL + inline bool AddValue(uintptr_t Value) { + uintptr_t Idx = Value % kMapSizeInBits; + uintptr_t WordIdx = Idx / kBitsInWord; + uintptr_t BitIdx = Idx % kBitsInWord; + uintptr_t Old = Map[WordIdx]; + uintptr_t New = Old | (1ULL << BitIdx); + Map[WordIdx] = New; + return New != Old; + } + + ATTRIBUTE_NO_SANITIZE_ALL + inline bool AddValueModPrime(uintptr_t Value) { + return AddValue(Value % kMapPrimeMod); + } + + inline bool Get(uintptr_t Idx) { + assert(Idx < kMapSizeInBits); + uintptr_t WordIdx = Idx / kBitsInWord; + uintptr_t BitIdx = Idx % kBitsInWord; + return Map[WordIdx] & (1ULL << BitIdx); + } + + size_t SizeInBits() const { return kMapSizeInBits; } + + template + ATTRIBUTE_NO_SANITIZE_ALL + void ForEach(Callback CB) const { + for (size_t i = 0; i < kMapSizeInWords; i++) + if (uintptr_t M = Map[i]) + for (size_t j = 0; j < sizeof(M) * 8; j++) + if (M & ((uintptr_t)1 << j)) + CB(i * sizeof(M) * 8 + j); + } + + private: + ATTRIBUTE_ALIGNED(512) uintptr_t Map[kMapSizeInWords]; +}; + +} // namespace fuzzer + +#endif // LLVM_FUZZER_VALUE_BIT_MAP_H diff --git a/core/lib/libfuzzer-sys/libfuzzer/README.txt b/core/lib/libfuzzer-sys/libfuzzer/README.txt new file mode 100644 index 0000000000..3eee01c776 --- /dev/null +++ b/core/lib/libfuzzer-sys/libfuzzer/README.txt @@ -0,0 +1 @@ +See http://llvm.org/docs/LibFuzzer.html diff --git a/core/lib/libfuzzer-sys/libfuzzer/afl/afl_driver.cpp b/core/lib/libfuzzer-sys/libfuzzer/afl/afl_driver.cpp new file mode 100644 index 0000000000..52aede7e07 --- /dev/null +++ b/core/lib/libfuzzer-sys/libfuzzer/afl/afl_driver.cpp @@ -0,0 +1,267 @@ +//===- afl_driver.cpp - a glue between AFL and libFuzzer --------*- C++ -* ===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +//===----------------------------------------------------------------------===// + +/* This file allows to fuzz libFuzzer-style target functions + (LLVMFuzzerTestOneInput) with AFL using AFL's persistent (in-process) mode. + +Usage: +################################################################################ +cat << EOF > test_fuzzer.cc +#include +#include +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + if (size > 0 && data[0] == 'H') + if (size > 1 && data[1] == 'I') + if (size > 2 && data[2] == '!') + __builtin_trap(); + return 0; +} +EOF +# Build your target with -fsanitize-coverage=trace-pc-guard using fresh clang. +clang -g -fsanitize-coverage=trace-pc-guard test_fuzzer.cc -c +# Build afl-llvm-rt.o.c from the AFL distribution. +clang -c -w $AFL_HOME/llvm_mode/afl-llvm-rt.o.c +# Build this file, link it with afl-llvm-rt.o.o and the target code. +clang++ afl_driver.cpp test_fuzzer.o afl-llvm-rt.o.o +# Run AFL: +rm -rf IN OUT; mkdir IN OUT; echo z > IN/z; +$AFL_HOME/afl-fuzz -i IN -o OUT ./a.out +################################################################################ +AFL_DRIVER_STDERR_DUPLICATE_FILENAME: Setting this *appends* stderr to the file +specified. If the file does not exist, it is created. This is useful for getting +stack traces (when using ASAN for example) or original error messages on hard +to reproduce bugs. Note that any content written to stderr will be written to +this file instead of stderr's usual location. + +AFL_DRIVER_CLOSE_FD_MASK: Similar to libFuzzer's -close_fd_mask behavior option. +If 1, close stdout at startup. If 2 close stderr; if 3 close both. + +*/ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +// Platform detection. Copied from FuzzerInternal.h +#ifdef __linux__ +#define LIBFUZZER_LINUX 1 +#define LIBFUZZER_APPLE 0 +#define LIBFUZZER_NETBSD 0 +#define LIBFUZZER_FREEBSD 0 +#elif __APPLE__ +#define LIBFUZZER_LINUX 0 +#define LIBFUZZER_APPLE 1 +#define LIBFUZZER_NETBSD 0 +#define LIBFUZZER_FREEBSD 0 +#elif __NetBSD__ +#define LIBFUZZER_LINUX 0 +#define LIBFUZZER_APPLE 0 +#define LIBFUZZER_NETBSD 1 +#define LIBFUZZER_FREEBSD 0 +#elif __FreeBSD__ +#define LIBFUZZER_LINUX 0 +#define LIBFUZZER_APPLE 0 +#define LIBFUZZER_NETBSD 0 +#define LIBFUZZER_FREEBSD 1 +#else +#error "Support for your platform has not been implemented" +#endif + +// libFuzzer interface is thin, so we don't include any libFuzzer headers. +extern "C" { +int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size); +__attribute__((weak)) int LLVMFuzzerInitialize(int *argc, char ***argv); +} + +// Notify AFL about persistent mode. +static volatile char AFL_PERSISTENT[] = "##SIG_AFL_PERSISTENT##"; +extern "C" int __afl_persistent_loop(unsigned int); +static volatile char suppress_warning2 = AFL_PERSISTENT[0]; + +// Notify AFL about deferred forkserver. +static volatile char AFL_DEFER_FORKSVR[] = "##SIG_AFL_DEFER_FORKSRV##"; +extern "C" void __afl_manual_init(); +static volatile char suppress_warning1 = AFL_DEFER_FORKSVR[0]; + +// Input buffer. +static const size_t kMaxAflInputSize = 1 << 20; +static uint8_t AflInputBuf[kMaxAflInputSize]; + +// Use this optionally defined function to output sanitizer messages even if +// user asks to close stderr. +extern "C" __attribute__((weak)) void __sanitizer_set_report_fd(void *); + +// Keep track of where stderr content is being written to, so that +// dup_and_close_stderr can use the correct one. +static FILE *output_file = stderr; + +// Experimental feature to use afl_driver without AFL's deferred mode. +// Needs to run before __afl_auto_init. +__attribute__((constructor(0))) static void __decide_deferred_forkserver(void) { + if (getenv("AFL_DRIVER_DONT_DEFER")) { + if (unsetenv("__AFL_DEFER_FORKSRV")) { + perror("Failed to unset __AFL_DEFER_FORKSRV"); + abort(); + } + } +} + +// If the user asks us to duplicate stderr, then do it. +static void maybe_duplicate_stderr() { + char *stderr_duplicate_filename = + getenv("AFL_DRIVER_STDERR_DUPLICATE_FILENAME"); + + if (!stderr_duplicate_filename) + return; + + FILE *stderr_duplicate_stream = + freopen(stderr_duplicate_filename, "a+", stderr); + + if (!stderr_duplicate_stream) { + fprintf( + stderr, + "Failed to duplicate stderr to AFL_DRIVER_STDERR_DUPLICATE_FILENAME"); + abort(); + } + output_file = stderr_duplicate_stream; +} + +// Most of these I/O functions were inspired by/copied from libFuzzer's code. +static void discard_output(int fd) { + FILE *temp = fopen("/dev/null", "w"); + if (!temp) + abort(); + dup2(fileno(temp), fd); + fclose(temp); +} + +static void close_stdout() { discard_output(STDOUT_FILENO); } + +// Prevent the targeted code from writing to "stderr" but allow sanitizers and +// this driver to do so. +static void dup_and_close_stderr() { + int output_fileno = fileno(output_file); + int output_fd = dup(output_fileno); + if (output_fd <= 0) + abort(); + FILE *new_output_file = fdopen(output_fd, "w"); + if (!new_output_file) + abort(); + if (!__sanitizer_set_report_fd) + return; + __sanitizer_set_report_fd(reinterpret_cast(output_fd)); + discard_output(output_fileno); +} + +static void Printf(const char *Fmt, ...) { + va_list ap; + va_start(ap, Fmt); + vfprintf(output_file, Fmt, ap); + va_end(ap); + fflush(output_file); +} + +// Close stdout and/or stderr if user asks for it. +static void maybe_close_fd_mask() { + char *fd_mask_str = getenv("AFL_DRIVER_CLOSE_FD_MASK"); + if (!fd_mask_str) + return; + int fd_mask = atoi(fd_mask_str); + if (fd_mask & 2) + dup_and_close_stderr(); + if (fd_mask & 1) + close_stdout(); +} + +// Define LLVMFuzzerMutate to avoid link failures for targets that use it +// with libFuzzer's LLVMFuzzerCustomMutator. +extern "C" size_t LLVMFuzzerMutate(uint8_t *Data, size_t Size, size_t MaxSize) { + assert(false && "LLVMFuzzerMutate should not be called from afl_driver"); + return 0; +} + +// Execute any files provided as parameters. +static int ExecuteFilesOnyByOne(int argc, char **argv) { + for (int i = 1; i < argc; i++) { + std::ifstream in(argv[i], std::ios::binary); + in.seekg(0, in.end); + size_t length = in.tellg(); + in.seekg (0, in.beg); + std::cout << "Reading " << length << " bytes from " << argv[i] << std::endl; + // Allocate exactly length bytes so that we reliably catch buffer overflows. + std::vector bytes(length); + in.read(bytes.data(), bytes.size()); + assert(in); + LLVMFuzzerTestOneInput(reinterpret_cast(bytes.data()), + bytes.size()); + std::cout << "Execution successful" << std::endl; + } + return 0; +} + +int main(int argc, char **argv) { + Printf( + "======================= INFO =========================\n" + "This binary is built for AFL-fuzz.\n" + "To run the target function on individual input(s) execute this:\n" + " %s < INPUT_FILE\n" + "or\n" + " %s INPUT_FILE1 [INPUT_FILE2 ... ]\n" + "To fuzz with afl-fuzz execute this:\n" + " afl-fuzz [afl-flags] %s [-N]\n" + "afl-fuzz will run N iterations before " + "re-spawning the process (default: 1000)\n" + "======================================================\n", + argv[0], argv[0], argv[0]); + + maybe_duplicate_stderr(); + maybe_close_fd_mask(); + if (LLVMFuzzerInitialize) + LLVMFuzzerInitialize(&argc, &argv); + // Do any other expensive one-time initialization here. + + if (!getenv("AFL_DRIVER_DONT_DEFER")) + __afl_manual_init(); + + int N = 1000; + if (argc == 2 && argv[1][0] == '-') + N = atoi(argv[1] + 1); + else if(argc == 2 && (N = atoi(argv[1])) > 0) + Printf("WARNING: using the deprecated call style `%s %d`\n", argv[0], N); + else if (argc > 1) + return ExecuteFilesOnyByOne(argc, argv); + + assert(N > 0); + + // Call LLVMFuzzerTestOneInput here so that coverage caused by initialization + // on the first execution of LLVMFuzzerTestOneInput is ignored. + uint8_t dummy_input[1] = {0}; + LLVMFuzzerTestOneInput(dummy_input, 1); + + int num_runs = 0; + while (__afl_persistent_loop(N)) { + ssize_t n_read = read(0, AflInputBuf, kMaxAflInputSize); + if (n_read > 0) { + // Copy AflInputBuf into a separate buffer to let asan find buffer + // overflows. Don't use unique_ptr/etc to avoid extra dependencies. + uint8_t *copy = new uint8_t[n_read]; + memcpy(copy, AflInputBuf, n_read); + num_runs++; + LLVMFuzzerTestOneInput(copy, n_read); + delete[] copy; + } + } + Printf("%s: successfully executed %d input(s)\n", argv[0], num_runs); +} diff --git a/core/lib/libfuzzer-sys/libfuzzer/build.sh b/core/lib/libfuzzer-sys/libfuzzer/build.sh new file mode 100755 index 0000000000..5cb4ece0fb --- /dev/null +++ b/core/lib/libfuzzer-sys/libfuzzer/build.sh @@ -0,0 +1,10 @@ +#!/bin/sh +LIBFUZZER_SRC_DIR=$(dirname $0) +CXX="${CXX:-clang}" +for f in $LIBFUZZER_SRC_DIR/*.cpp; do + $CXX -g -O2 -fno-omit-frame-pointer -std=c++11 $f -c & +done +wait +rm -f libFuzzer.a +ar ru libFuzzer.a Fuzzer*.o +rm -f Fuzzer*.o diff --git a/core/lib/libfuzzer-sys/libfuzzer/dataflow/DataFlow.cpp b/core/lib/libfuzzer-sys/libfuzzer/dataflow/DataFlow.cpp new file mode 100644 index 0000000000..7e5f041c91 --- /dev/null +++ b/core/lib/libfuzzer-sys/libfuzzer/dataflow/DataFlow.cpp @@ -0,0 +1,205 @@ +/*===- DataFlow.cpp - a standalone DataFlow tracer -------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// An experimental data-flow tracer for fuzz targets. +// It is based on DFSan and SanitizerCoverage. +// https://clang.llvm.org/docs/DataFlowSanitizer.html +// https://clang.llvm.org/docs/SanitizerCoverage.html#tracing-data-flow +// +// It executes the fuzz target on the given input while monitoring the +// data flow for every instrumented comparison instruction. +// +// The output shows which functions depend on which bytes of the input, +// and also provides basic-block coverage for every input. +// +// Build: +// 1. Compile this file (DataFlow.cpp) with -fsanitize=dataflow and -O2. +// 2. Compile DataFlowCallbacks.cpp with -O2 -fPIC. +// 3. Build the fuzz target with -g -fsanitize=dataflow +// -fsanitize-coverage=trace-pc-guard,pc-table,bb,trace-cmp +// 4. Link those together with -fsanitize=dataflow +// +// -fsanitize-coverage=trace-cmp inserts callbacks around every comparison +// instruction, DFSan modifies the calls to pass the data flow labels. +// The callbacks update the data flow label for the current function. +// See e.g. __dfsw___sanitizer_cov_trace_cmp1 below. +// +// -fsanitize-coverage=trace-pc-guard,pc-table,bb instruments function +// entries so that the comparison callback knows that current function. +// -fsanitize-coverage=...,bb also allows to collect basic block coverage. +// +// +// Run: +// # Collect data flow and coverage for INPUT_FILE +// # write to OUTPUT_FILE (default: stdout) +// export DFSAN_OPTIONS=warn_unimplemented=0 +// ./a.out INPUT_FILE [OUTPUT_FILE] +// +// # Print all instrumented functions. llvm-symbolizer must be present in PATH +// ./a.out +// +// Example output: +// =============== +// F0 11111111111111 +// F1 10000000000000 +// C0 1 2 3 4 5 +// C1 8 +// =============== +// "FN xxxxxxxxxx": tells what bytes of the input does the function N depend on. +// "CN X Y Z T": tells that a function N has basic blocks X, Y, and Z covered +// in addition to the function's entry block, out of T total instrumented +// blocks. +// +//===----------------------------------------------------------------------===*/ + +#include +#include +#include +#include +#include + +#include // backtrace_symbols_fd + +#include "DataFlow.h" + +extern "C" { +extern int LLVMFuzzerTestOneInput(const unsigned char *Data, size_t Size); +__attribute__((weak)) extern int LLVMFuzzerInitialize(int *argc, char ***argv); +} // extern "C" + +CallbackData __dft; +static size_t InputLen; +static size_t NumIterations; +static dfsan_label **FuncLabelsPerIter; // NumIterations x NumFuncs; + +static inline bool BlockIsEntry(size_t BlockIdx) { + return __dft.PCsBeg[BlockIdx * 2 + 1] & PCFLAG_FUNC_ENTRY; +} + +const int kNumLabels = 8; + +// Prints all instrumented functions. +static int PrintFunctions() { + // We don't have the symbolizer integrated with dfsan yet. + // So use backtrace_symbols_fd and pipe it through llvm-symbolizer. + // TODO(kcc): this is pretty ugly and may break in lots of ways. + // We'll need to make a proper in-process symbolizer work with DFSan. + FILE *Pipe = popen("sed 's/(+/ /g; s/).*//g' " + "| llvm-symbolizer " + "| grep '\\.dfsan' " + "| sed 's/\\.dfsan//g' " + "| c++filt", + "w"); + for (size_t I = 0; I < __dft.NumGuards; I++) { + uintptr_t PC = __dft.PCsBeg[I * 2]; + if (!BlockIsEntry(I)) continue; + void *const Buf[1] = {(void*)PC}; + backtrace_symbols_fd(Buf, 1, fileno(Pipe)); + } + pclose(Pipe); + return 0; +} + +static void PrintBinary(FILE *Out, dfsan_label L, size_t Len) { + char buf[kNumLabels + 1]; + assert(Len <= kNumLabels); + for (int i = 0; i < kNumLabels; i++) + buf[i] = (L & (1 << i)) ? '1' : '0'; + buf[Len] = 0; + fprintf(Out, "%s", buf); +} + +static void PrintDataFlow(FILE *Out) { + for (size_t Func = 0; Func < __dft.NumFuncs; Func++) { + bool HasAny = false; + for (size_t Iter = 0; Iter < NumIterations; Iter++) + if (FuncLabelsPerIter[Iter][Func]) + HasAny = true; + if (!HasAny) + continue; + fprintf(Out, "F%zd ", Func); + size_t LenOfLastIteration = kNumLabels; + if (auto Tail = InputLen % kNumLabels) + LenOfLastIteration = Tail; + for (size_t Iter = 0; Iter < NumIterations; Iter++) + PrintBinary(Out, FuncLabelsPerIter[Iter][Func], + Iter == NumIterations - 1 ? LenOfLastIteration : kNumLabels); + fprintf(Out, "\n"); + } +} + +static void PrintCoverage(FILE *Out) { + ssize_t CurrentFuncGuard = -1; + ssize_t CurrentFuncNum = -1; + ssize_t NumBlocksInCurrentFunc = -1; + for (size_t FuncBeg = 0; FuncBeg < __dft.NumGuards;) { + CurrentFuncNum++; + assert(BlockIsEntry(FuncBeg)); + size_t FuncEnd = FuncBeg + 1; + for (; FuncEnd < __dft.NumGuards && !BlockIsEntry(FuncEnd); FuncEnd++) + ; + if (__dft.BBExecuted[FuncBeg]) { + fprintf(Out, "C%zd", CurrentFuncNum); + for (size_t I = FuncBeg + 1; I < FuncEnd; I++) + if (__dft.BBExecuted[I]) + fprintf(Out, " %zd", I - FuncBeg); + fprintf(Out, " %zd\n", FuncEnd - FuncBeg); + } + FuncBeg = FuncEnd; + } +} + +int main(int argc, char **argv) { + if (LLVMFuzzerInitialize) + LLVMFuzzerInitialize(&argc, &argv); + if (argc == 1) + return PrintFunctions(); + assert(argc == 2 || argc == 3); + + const char *Input = argv[1]; + fprintf(stderr, "INFO: reading '%s'\n", Input); + FILE *In = fopen(Input, "r"); + assert(In); + fseek(In, 0, SEEK_END); + InputLen = ftell(In); + fseek(In, 0, SEEK_SET); + unsigned char *Buf = (unsigned char*)malloc(InputLen); + size_t NumBytesRead = fread(Buf, 1, InputLen, In); + assert(NumBytesRead == InputLen); + fclose(In); + + NumIterations = (NumBytesRead + kNumLabels - 1) / kNumLabels; + FuncLabelsPerIter = + (dfsan_label **)calloc(NumIterations, sizeof(dfsan_label *)); + for (size_t Iter = 0; Iter < NumIterations; Iter++) + FuncLabelsPerIter[Iter] = + (dfsan_label *)calloc(__dft.NumFuncs, sizeof(dfsan_label)); + + for (size_t Iter = 0; Iter < NumIterations; Iter++) { + fprintf(stderr, "INFO: running '%s' %zd/%zd\n", Input, Iter, NumIterations); + dfsan_flush(); + dfsan_set_label(0, Buf, InputLen); + __dft.FuncLabels = FuncLabelsPerIter[Iter]; + + size_t BaseIdx = Iter * kNumLabels; + size_t LastIdx = BaseIdx + kNumLabels < NumBytesRead ? BaseIdx + kNumLabels + : NumBytesRead; + assert(BaseIdx < LastIdx); + for (size_t Idx = BaseIdx; Idx < LastIdx; Idx++) + dfsan_set_label(1 << (Idx - BaseIdx), Buf + Idx, 1); + LLVMFuzzerTestOneInput(Buf, InputLen); + } + free(Buf); + + bool OutIsStdout = argc == 2; + fprintf(stderr, "INFO: writing dataflow to %s\n", + OutIsStdout ? "" : argv[2]); + FILE *Out = OutIsStdout ? stdout : fopen(argv[2], "w"); + PrintDataFlow(Out); + PrintCoverage(Out); + if (!OutIsStdout) fclose(Out); +} diff --git a/core/lib/libfuzzer-sys/libfuzzer/dataflow/DataFlow.h b/core/lib/libfuzzer-sys/libfuzzer/dataflow/DataFlow.h new file mode 100644 index 0000000000..35849fd8d8 --- /dev/null +++ b/core/lib/libfuzzer-sys/libfuzzer/dataflow/DataFlow.h @@ -0,0 +1,32 @@ +/*===- DataFlow.h - a standalone DataFlow trace -------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// Internal header file to connect DataFlow.cpp and DataFlowCallbacks.cpp. +//===----------------------------------------------------------------------===*/ + +#ifndef __LIBFUZZER_DATAFLOW_H +#define __LIBFUZZER_DATAFLOW_H + +#include +#include +#include + +// This data is shared between DataFlowCallbacks.cpp and DataFlow.cpp. +struct CallbackData { + size_t NumFuncs, NumGuards; + const uintptr_t *PCsBeg, *PCsEnd; + dfsan_label *FuncLabels; // Array of NumFuncs elements. + bool *BBExecuted; // Array of NumGuards elements. +}; + +extern CallbackData __dft; + +enum { + PCFLAG_FUNC_ENTRY = 1, +}; + +#endif // __LIBFUZZER_DATAFLOW_H diff --git a/core/lib/libfuzzer-sys/libfuzzer/dataflow/DataFlowCallbacks.cpp b/core/lib/libfuzzer-sys/libfuzzer/dataflow/DataFlowCallbacks.cpp new file mode 100644 index 0000000000..170b4d891d --- /dev/null +++ b/core/lib/libfuzzer-sys/libfuzzer/dataflow/DataFlowCallbacks.cpp @@ -0,0 +1,86 @@ +/*===- DataFlowCallbacks.cpp - a standalone DataFlow trace -------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// Instrumentation callbacks for DataFlow.cpp. +// These functions should not be instrumented by DFSan, so we +// keep them in a separate file and compile it w/o DFSan. +//===----------------------------------------------------------------------===*/ +#include "DataFlow.h" + +#include +#include +#include + +static __thread size_t CurrentFunc; +static uint32_t *GuardsBeg, *GuardsEnd; +static inline bool BlockIsEntry(size_t BlockIdx) { + return __dft.PCsBeg[BlockIdx * 2 + 1] & PCFLAG_FUNC_ENTRY; +} + +extern "C" { + +void __sanitizer_cov_trace_pc_guard_init(uint32_t *start, + uint32_t *stop) { + assert(__dft.NumFuncs == 0 && "This tool does not support DSOs"); + assert(start < stop && "The code is not instrumented for coverage"); + if (start == stop || *start) return; // Initialize only once. + GuardsBeg = start; + GuardsEnd = stop; +} + +void __sanitizer_cov_pcs_init(const uintptr_t *pcs_beg, + const uintptr_t *pcs_end) { + if (__dft.NumGuards) return; // Initialize only once. + __dft.NumGuards = GuardsEnd - GuardsBeg; + __dft.PCsBeg = pcs_beg; + __dft.PCsEnd = pcs_end; + assert(__dft.NumGuards == (__dft.PCsEnd - __dft.PCsBeg) / 2); + for (size_t i = 0; i < __dft.NumGuards; i++) { + if (BlockIsEntry(i)) { + __dft.NumFuncs++; + GuardsBeg[i] = __dft.NumFuncs; + } + } + __dft.BBExecuted = (bool*)calloc(__dft.NumGuards, sizeof(bool)); + fprintf(stderr, "INFO: %zd instrumented function(s) observed " + "and %zd basic blocks\n", __dft.NumFuncs, __dft.NumGuards); +} + +void __sanitizer_cov_trace_pc_indir(uint64_t x){} // unused. + +void __sanitizer_cov_trace_pc_guard(uint32_t *guard) { + size_t GuardIdx = guard - GuardsBeg; + // assert(GuardIdx < __dft.NumGuards); + __dft.BBExecuted[GuardIdx] = true; + if (!*guard) return; // not a function entry. + uint32_t FuncNum = *guard - 1; // Guards start from 1. + // assert(FuncNum < __dft.NumFuncs); + CurrentFunc = FuncNum; +} + +void __dfsw___sanitizer_cov_trace_switch(uint64_t Val, uint64_t *Cases, + dfsan_label L1, dfsan_label UnusedL) { + assert(CurrentFunc < __dft.NumFuncs); + __dft.FuncLabels[CurrentFunc] |= L1; +} + +#define HOOK(Name, Type) \ + void Name(Type Arg1, Type Arg2, dfsan_label L1, dfsan_label L2) { \ + __dft.FuncLabels[CurrentFunc] |= L1 | L2; \ + } + //assert(CurrentFunc < __dft.NumFuncs); + +HOOK(__dfsw___sanitizer_cov_trace_const_cmp1, uint8_t) +HOOK(__dfsw___sanitizer_cov_trace_const_cmp2, uint16_t) +HOOK(__dfsw___sanitizer_cov_trace_const_cmp4, uint32_t) +HOOK(__dfsw___sanitizer_cov_trace_const_cmp8, uint64_t) +HOOK(__dfsw___sanitizer_cov_trace_cmp1, uint8_t) +HOOK(__dfsw___sanitizer_cov_trace_cmp2, uint16_t) +HOOK(__dfsw___sanitizer_cov_trace_cmp4, uint32_t) +HOOK(__dfsw___sanitizer_cov_trace_cmp8, uint64_t) + +} // extern "C" diff --git a/core/lib/libfuzzer-sys/libfuzzer/scripts/unbalanced_allocs.py b/core/lib/libfuzzer-sys/libfuzzer/scripts/unbalanced_allocs.py new file mode 100755 index 0000000000..579e481a23 --- /dev/null +++ b/core/lib/libfuzzer-sys/libfuzzer/scripts/unbalanced_allocs.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python +#===- lib/fuzzer/scripts/unbalanced_allocs.py ------------------------------===# +# +# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +# See https://llvm.org/LICENSE.txt for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +# +#===------------------------------------------------------------------------===# +# +# Post-process -trace_malloc=2 output and printout only allocations and frees +# unbalanced inside of fuzzer runs. +# Usage: +# my_fuzzer -trace_malloc=2 -runs=10 2>&1 | unbalanced_allocs.py -skip=5 +# +#===------------------------------------------------------------------------===# + +import argparse +import sys + +_skip = 0 + +def PrintStack(line, stack): + global _skip + if _skip > 0: + return + print('Unbalanced ' + line.rstrip()); + for l in stack: + print(l.rstrip()) + +def ProcessStack(line, f): + stack = [] + while line and line.startswith(' #'): + stack += [line] + line = f.readline() + return line, stack + +def ProcessFree(line, f, allocs): + if not line.startswith('FREE['): + return f.readline() + + addr = int(line.split()[1], 16) + next_line, stack = ProcessStack(f.readline(), f) + if addr in allocs: + del allocs[addr] + else: + PrintStack(line, stack) + return next_line + +def ProcessMalloc(line, f, allocs): + if not line.startswith('MALLOC['): + return ProcessFree(line, f, allocs) + + addr = int(line.split()[1], 16) + assert not addr in allocs + + next_line, stack = ProcessStack(f.readline(), f) + allocs[addr] = (line, stack) + return next_line + +def ProcessRun(line, f): + if not line.startswith('MallocFreeTracer: START'): + return ProcessMalloc(line, f, {}) + + allocs = {} + print(line.rstrip()) + line = f.readline() + while line: + if line.startswith('MallocFreeTracer: STOP'): + global _skip + _skip = _skip - 1 + for _, (l, s) in allocs.items(): + PrintStack(l, s) + print(line.rstrip()) + return f.readline() + line = ProcessMalloc(line, f, allocs) + return line + +def ProcessFile(f): + line = f.readline() + while line: + line = ProcessRun(line, f); + +def main(argv): + parser = argparse.ArgumentParser() + parser.add_argument('--skip', default=0, help='number of runs to ignore') + args = parser.parse_args() + global _skip + _skip = int(args.skip) + 1 + ProcessFile(sys.stdin) + +if __name__ == '__main__': + main(sys.argv) diff --git a/core/lib/libfuzzer-sys/libfuzzer/standalone/StandaloneFuzzTargetMain.c b/core/lib/libfuzzer-sys/libfuzzer/standalone/StandaloneFuzzTargetMain.c new file mode 100644 index 0000000000..efe512cfe8 --- /dev/null +++ b/core/lib/libfuzzer-sys/libfuzzer/standalone/StandaloneFuzzTargetMain.c @@ -0,0 +1,41 @@ +/*===- StandaloneFuzzTargetMain.c - standalone main() for fuzz targets. ---===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// This main() function can be linked to a fuzz target (i.e. a library +// that exports LLVMFuzzerTestOneInput() and possibly LLVMFuzzerInitialize()) +// instead of libFuzzer. This main() function will not perform any fuzzing +// but will simply feed all input files one by one to the fuzz target. +// +// Use this file to provide reproducers for bugs when linking against libFuzzer +// or other fuzzing engine is undesirable. +//===----------------------------------------------------------------------===*/ +#include +#include +#include + +extern int LLVMFuzzerTestOneInput(const unsigned char *data, size_t size); +__attribute__((weak)) extern int LLVMFuzzerInitialize(int *argc, char ***argv); +int main(int argc, char **argv) { + fprintf(stderr, "StandaloneFuzzTargetMain: running %d inputs\n", argc - 1); + if (LLVMFuzzerInitialize) + LLVMFuzzerInitialize(&argc, &argv); + for (int i = 1; i < argc; i++) { + fprintf(stderr, "Running: %s\n", argv[i]); + FILE *f = fopen(argv[i], "r"); + assert(f); + fseek(f, 0, SEEK_END); + size_t len = ftell(f); + fseek(f, 0, SEEK_SET); + unsigned char *buf = (unsigned char*)malloc(len); + size_t n_read = fread(buf, 1, len, f); + fclose(f); + assert(n_read == len); + LLVMFuzzerTestOneInput(buf, len); + free(buf); + fprintf(stderr, "Done: %s: (%zd bytes)\n", argv[i], n_read); + } +} diff --git a/core/lib/libfuzzer-sys/libfuzzer/tests/CMakeLists.txt b/core/lib/libfuzzer-sys/libfuzzer/tests/CMakeLists.txt new file mode 100644 index 0000000000..195f3157a0 --- /dev/null +++ b/core/lib/libfuzzer-sys/libfuzzer/tests/CMakeLists.txt @@ -0,0 +1,91 @@ +include(CompilerRTCompile) + +set(LIBFUZZER_UNITTEST_CFLAGS + ${COMPILER_RT_UNITTEST_CFLAGS} + ${COMPILER_RT_GTEST_CFLAGS} + -I${COMPILER_RT_SOURCE_DIR}/lib/fuzzer + -fno-rtti + -O2) + +if (APPLE) + set(FUZZER_SUPPORTED_OS osx) +endif() + +add_custom_target(FuzzerUnitTests) +set_target_properties(FuzzerUnitTests PROPERTIES FOLDER "Compiler-RT Tests") + +add_custom_target(FuzzedDataProviderUnitTests) +set_target_properties(FuzzedDataProviderUnitTests PROPERTIES FOLDER "Compiler-RT Tests") + +set(LIBFUZZER_UNITTEST_LINK_FLAGS ${COMPILER_RT_UNITTEST_LINK_FLAGS}) +list(APPEND LIBFUZZER_UNITTEST_LINK_FLAGS --driver-mode=g++) + +if(WIN32) + list(APPEND LIBFUZZER_UNITTEST_LINK_FLAGS -Wl,-defaultlib:libcmt,-defaultlib:oldnames) +else() + if (APPLE) + list(APPEND LIBFUZZER_UNITTEST_CFLAGS -isysroot ${DARWIN_osx_SYSROOT}) + list(APPEND LIBFUZZER_UNITTEST_LINK_FLAGS -isysroot ${DARWIN_osx_SYSROOT}) + endif() + list(APPEND LIBFUZZER_UNITTEST_LINK_FLAGS -lpthread) +endif() + +if(CMAKE_SYSTEM_NAME STREQUAL "Linux" AND + COMPILER_RT_LIBCXX_PATH AND + COMPILER_RT_LIBCXXABI_PATH) + list(APPEND LIBFUZZER_UNITTEST_CFLAGS -nostdinc++) +endif() + +if ("-fvisibility=hidden" IN_LIST LIBFUZZER_CFLAGS) + # Match visibility settings. + list(APPEND LIBFUZZER_UNITTEST_CFLAGS "-fvisibility=hidden") +endif() + +if(COMPILER_RT_DEFAULT_TARGET_ARCH IN_LIST FUZZER_SUPPORTED_ARCH) + # libFuzzer unit tests are only run on the host machine. + set(arch ${COMPILER_RT_DEFAULT_TARGET_ARCH}) + + set(LIBFUZZER_TEST_RUNTIME RTFuzzerTest.${arch}) + if(APPLE) + set(LIBFUZZER_TEST_RUNTIME_OBJECTS + $) + else() + set(LIBFUZZER_TEST_RUNTIME_OBJECTS + $) + endif() + add_library(${LIBFUZZER_TEST_RUNTIME} STATIC + ${LIBFUZZER_TEST_RUNTIME_OBJECTS}) + set_target_properties(${LIBFUZZER_TEST_RUNTIME} PROPERTIES + ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + FOLDER "Compiler-RT Runtime tests") + + if(CMAKE_SYSTEM_NAME STREQUAL "Linux" AND + COMPILER_RT_LIBCXX_PATH AND + COMPILER_RT_LIBCXXABI_PATH) + file(GLOB libfuzzer_headers ../*.h) + set(LIBFUZZER_TEST_RUNTIME_DEPS libcxx_fuzzer_${arch}-build ${libfuzzer_headers}) + set(LIBFUZZER_TEST_RUNTIME_CFLAGS -isystem ${LIBCXX_${arch}_PREFIX}/include/c++/v1) + set(LIBFUZZER_TEST_RUNTIME_LINK_FLAGS ${LIBCXX_${arch}_PREFIX}/lib/libc++.a) + endif() + + set(FuzzerTestObjects) + generate_compiler_rt_tests(FuzzerTestObjects + FuzzerUnitTests "Fuzzer-${arch}-Test" ${arch} + SOURCES FuzzerUnittest.cpp ${COMPILER_RT_GTEST_SOURCE} + RUNTIME ${LIBFUZZER_TEST_RUNTIME} + DEPS gtest ${LIBFUZZER_TEST_RUNTIME_DEPS} + CFLAGS ${LIBFUZZER_UNITTEST_CFLAGS} ${LIBFUZZER_TEST_RUNTIME_CFLAGS} + LINK_FLAGS ${LIBFUZZER_UNITTEST_LINK_FLAGS} ${LIBFUZZER_TEST_RUNTIME_LINK_FLAGS}) + set_target_properties(FuzzerUnitTests PROPERTIES + RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + + set(FuzzedDataProviderTestObjects) + generate_compiler_rt_tests(FuzzedDataProviderTestObjects + FuzzedDataProviderUnitTests "FuzzerUtils-${arch}-Test" ${arch} + SOURCES FuzzedDataProviderUnittest.cpp ${COMPILER_RT_GTEST_SOURCE} + DEPS gtest ${LIBFUZZER_TEST_RUNTIME_DEPS} ${COMPILER_RT_SOURCE_DIR}/include/fuzzer/FuzzedDataProvider.h + CFLAGS ${LIBFUZZER_UNITTEST_CFLAGS} ${LIBFUZZER_TEST_RUNTIME_CFLAGS} + LINK_FLAGS ${LIBFUZZER_UNITTEST_LINK_FLAGS} ${LIBFUZZER_TEST_RUNTIME_LINK_FLAGS}) + set_target_properties(FuzzedDataProviderUnitTests PROPERTIES + RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) +endif() diff --git a/core/lib/libfuzzer-sys/libfuzzer/tests/FuzzedDataProviderUnittest.cpp b/core/lib/libfuzzer-sys/libfuzzer/tests/FuzzedDataProviderUnittest.cpp new file mode 100644 index 0000000000..ea6774e5a5 --- /dev/null +++ b/core/lib/libfuzzer-sys/libfuzzer/tests/FuzzedDataProviderUnittest.cpp @@ -0,0 +1,450 @@ +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include "gtest/gtest.h" +#include +#include + +#include + +// The test is intentionally extensive, as behavior of |FuzzedDataProvider| must +// not be broken, given than many fuzz targets depend on it. Changing the +// behavior might invalidate existing corpora and make the fuzz targets using +// |FuzzedDataProvider| to lose code coverage accumulated over time. + +/* A random 1KB buffer generated by: +$ python -c "import os; print ',\n'.join([', '.join(['0x%02X' % ord(i) for i \ + in list(os.urandom(8))]) for _ in xrange(128)])" +*/ +const uint8_t Data[] = { + 0x8A, 0x19, 0x0D, 0x44, 0x37, 0x0D, 0x38, 0x5E, 0x9B, 0xAA, 0xF3, 0xDA, + 0xAA, 0x88, 0xF2, 0x9B, 0x6C, 0xBA, 0xBE, 0xB1, 0xF2, 0xCF, 0x13, 0xB8, + 0xAC, 0x1A, 0x7F, 0x1C, 0xC9, 0x90, 0xD0, 0xD9, 0x5C, 0x42, 0xB3, 0xFD, + 0xE3, 0x05, 0xA4, 0x03, 0x37, 0x49, 0x50, 0x4B, 0xBC, 0x39, 0xA2, 0x09, + 0x6C, 0x2F, 0xAF, 0xD1, 0xB5, 0x47, 0xBF, 0x92, 0xBD, 0x79, 0xE5, 0xC5, + 0x6E, 0x51, 0xA4, 0xED, 0xE9, 0xBD, 0x40, 0x4A, 0xFC, 0x25, 0x7A, 0x27, + 0xC8, 0x92, 0xF7, 0x30, 0xDE, 0x40, 0x66, 0x66, 0xE8, 0x5F, 0x65, 0x39, + 0x7E, 0x9E, 0x80, 0x2B, 0x01, 0x71, 0x2A, 0xFF, 0xD3, 0x0A, 0xAC, 0x6E, + 0x49, 0x32, 0x79, 0x10, 0x6A, 0x6F, 0x97, 0x96, 0x70, 0x7E, 0x50, 0x65, + 0xC9, 0x1D, 0xBD, 0x4E, 0x17, 0x04, 0x1E, 0xBA, 0x26, 0xAC, 0x1F, 0xE3, + 0x37, 0x1C, 0x15, 0x43, 0x60, 0x41, 0x2A, 0x7C, 0xCA, 0x70, 0xCE, 0xAB, + 0x20, 0x24, 0xF8, 0xD9, 0x1F, 0x14, 0x7C, 0x5C, 0xDD, 0x6F, 0xB3, 0xD7, + 0x8B, 0x63, 0x10, 0xB7, 0xDA, 0x99, 0xAF, 0x99, 0x01, 0x21, 0xE6, 0xE1, + 0x86, 0x27, 0xBE, 0x8D, 0xDF, 0x1E, 0xEA, 0x80, 0x0B, 0x8A, 0x60, 0xC3, + 0x3A, 0x85, 0x33, 0x53, 0x59, 0xE1, 0xB5, 0xF1, 0x62, 0xA6, 0x7B, 0x24, + 0x94, 0xE3, 0x8C, 0x10, 0x93, 0xF8, 0x6E, 0xC2, 0x00, 0x91, 0x90, 0x0B, + 0x5D, 0x52, 0x4F, 0x21, 0xE3, 0x40, 0x3A, 0x6E, 0xB6, 0x32, 0x15, 0xDB, + 0x5D, 0x01, 0x86, 0x63, 0x83, 0x24, 0xC5, 0xDE, 0xAB, 0x31, 0x84, 0xAA, + 0xE5, 0x64, 0x02, 0x8D, 0x23, 0x82, 0x86, 0x14, 0x16, 0x18, 0x9F, 0x3D, + 0x31, 0xBE, 0x3B, 0xF0, 0x6C, 0x26, 0x42, 0x9A, 0x67, 0xFE, 0x28, 0xEC, + 0x28, 0xDB, 0x01, 0xB4, 0x52, 0x41, 0x81, 0x7C, 0x54, 0xD3, 0xC8, 0x00, + 0x01, 0x66, 0xB0, 0x2C, 0x3F, 0xBC, 0xAF, 0xAC, 0x87, 0xCD, 0x83, 0xCF, + 0x23, 0xFC, 0xC8, 0x97, 0x8C, 0x71, 0x32, 0x8B, 0xBF, 0x70, 0xC0, 0x48, + 0x31, 0x92, 0x18, 0xFE, 0xE5, 0x33, 0x48, 0x82, 0x98, 0x1E, 0x30, 0xCC, + 0xAD, 0x5D, 0x97, 0xC4, 0xB4, 0x39, 0x7C, 0xCD, 0x39, 0x44, 0xF1, 0xA9, + 0xD0, 0xF4, 0x27, 0xB7, 0x78, 0x85, 0x9E, 0x72, 0xFC, 0xCC, 0xEE, 0x98, + 0x25, 0x3B, 0x69, 0x6B, 0x0C, 0x11, 0xEA, 0x22, 0xB6, 0xD0, 0xCD, 0xBF, + 0x6D, 0xBE, 0x12, 0xDE, 0xFE, 0x78, 0x2E, 0x54, 0xCB, 0xBA, 0xD7, 0x2E, + 0x54, 0x25, 0x14, 0x84, 0xFE, 0x1A, 0x10, 0xCE, 0xCC, 0x20, 0xE6, 0xE2, + 0x7F, 0xE0, 0x5F, 0xDB, 0xA7, 0xF3, 0xE2, 0x4C, 0x52, 0x82, 0xFC, 0x0B, + 0xA0, 0xBD, 0x34, 0x21, 0xF7, 0xEB, 0x1C, 0x5B, 0x67, 0xD0, 0xAF, 0x22, + 0x15, 0xA1, 0xFF, 0xC2, 0x68, 0x25, 0x5B, 0xB2, 0x13, 0x3F, 0xFF, 0x98, + 0x53, 0x25, 0xC5, 0x58, 0x39, 0xD0, 0x43, 0x86, 0x6C, 0x5B, 0x57, 0x8E, + 0x83, 0xBA, 0xB9, 0x09, 0x09, 0x14, 0x0C, 0x9E, 0x99, 0x83, 0x88, 0x53, + 0x79, 0xFD, 0xF7, 0x49, 0xE9, 0x2C, 0xCE, 0xE6, 0x7B, 0xF5, 0xC2, 0x27, + 0x5E, 0x56, 0xB5, 0xB4, 0x46, 0x90, 0x91, 0x7F, 0x99, 0x88, 0xA7, 0x23, + 0xC1, 0x80, 0xB8, 0x2D, 0xCD, 0xF7, 0x6F, 0x9A, 0xEC, 0xBD, 0x16, 0x9F, + 0x7D, 0x87, 0x1E, 0x15, 0x51, 0xC4, 0x96, 0xE2, 0xBF, 0x61, 0x66, 0xB5, + 0xFD, 0x01, 0x67, 0xD6, 0xFF, 0xD2, 0x14, 0x20, 0x98, 0x8E, 0xEF, 0xF3, + 0x22, 0xDB, 0x7E, 0xCE, 0x70, 0x2D, 0x4C, 0x06, 0x5A, 0xA0, 0x4F, 0xC8, + 0xB0, 0x4D, 0xA6, 0x52, 0xB2, 0xD6, 0x2F, 0xD8, 0x57, 0xE5, 0xEF, 0xF9, + 0xEE, 0x52, 0x0F, 0xEC, 0xC4, 0x90, 0x33, 0xAD, 0x25, 0xDA, 0xCD, 0x12, + 0x44, 0x5F, 0x32, 0xF6, 0x6F, 0xEF, 0x85, 0xB8, 0xDC, 0x3C, 0x01, 0x48, + 0x28, 0x5D, 0x2D, 0x9C, 0x9B, 0xC0, 0x49, 0x36, 0x1E, 0x6A, 0x0A, 0x0C, + 0xB0, 0x6E, 0x81, 0x89, 0xCB, 0x0A, 0x89, 0xCF, 0x73, 0xC6, 0x63, 0x3D, + 0x8E, 0x13, 0x57, 0x91, 0x4E, 0xA3, 0x93, 0x8C, 0x61, 0x67, 0xFD, 0x13, + 0xE0, 0x14, 0x72, 0xB3, 0xE4, 0x23, 0x45, 0x08, 0x4E, 0x4E, 0xF5, 0xA7, + 0xA8, 0xEE, 0x30, 0xFD, 0x81, 0x80, 0x1F, 0xF3, 0x4F, 0xD7, 0xE7, 0xF2, + 0x16, 0xC0, 0xD6, 0x15, 0x6A, 0x0F, 0x89, 0x15, 0xA9, 0xCF, 0x35, 0x50, + 0x6B, 0x49, 0x3E, 0x12, 0x4A, 0x72, 0xE4, 0x59, 0x9D, 0xD7, 0xDB, 0xD2, + 0xD1, 0x61, 0x7D, 0x52, 0x4A, 0x36, 0xF6, 0xBA, 0x0E, 0xFA, 0x88, 0x6F, + 0x3C, 0x82, 0x16, 0xF0, 0xD5, 0xED, 0x4D, 0x78, 0xEF, 0x38, 0x17, 0x90, + 0xEA, 0x28, 0x32, 0xA9, 0x79, 0x40, 0xFF, 0xAA, 0xE6, 0xF5, 0xC7, 0x96, + 0x56, 0x65, 0x61, 0x83, 0x3D, 0xBD, 0xD7, 0xED, 0xD6, 0xB6, 0xC0, 0xED, + 0x34, 0xAA, 0x60, 0xA9, 0xE8, 0x82, 0x78, 0xEA, 0x69, 0xF6, 0x47, 0xAF, + 0x39, 0xAB, 0x11, 0xDB, 0xE9, 0xFB, 0x68, 0x0C, 0xFE, 0xDF, 0x97, 0x9F, + 0x3A, 0xF4, 0xF3, 0x32, 0x27, 0x30, 0x57, 0x0E, 0xF7, 0xB2, 0xEE, 0xFB, + 0x1E, 0x98, 0xA8, 0xA3, 0x25, 0x45, 0xE4, 0x6D, 0x2D, 0xAE, 0xFE, 0xDA, + 0xB3, 0x32, 0x9B, 0x5D, 0xF5, 0x32, 0x74, 0xEA, 0xE5, 0x02, 0x30, 0x53, + 0x95, 0x13, 0x7A, 0x23, 0x1F, 0x10, 0x30, 0xEA, 0x78, 0xE4, 0x36, 0x1D, + 0x92, 0x96, 0xB9, 0x91, 0x2D, 0xFA, 0x43, 0xAB, 0xE6, 0xEF, 0x14, 0x14, + 0xC9, 0xBC, 0x46, 0xC6, 0x05, 0x7C, 0xC6, 0x11, 0x23, 0xCF, 0x3D, 0xC8, + 0xBE, 0xEC, 0xA3, 0x58, 0x31, 0x55, 0x65, 0x14, 0xA7, 0x94, 0x93, 0xDD, + 0x2D, 0x76, 0xC9, 0x66, 0x06, 0xBD, 0xF5, 0xE7, 0x30, 0x65, 0x42, 0x52, + 0xA2, 0x50, 0x9B, 0xE6, 0x40, 0xA2, 0x4B, 0xEC, 0xA6, 0xB7, 0x39, 0xAA, + 0xD7, 0x61, 0x2C, 0xBF, 0x37, 0x5A, 0xDA, 0xB3, 0x5D, 0x2F, 0x5D, 0x11, + 0x82, 0x97, 0x32, 0x8A, 0xC1, 0xA1, 0x13, 0x20, 0x17, 0xBD, 0xA2, 0x91, + 0x94, 0x2A, 0x4E, 0xBE, 0x3E, 0x77, 0x63, 0x67, 0x5C, 0x0A, 0xE1, 0x22, + 0x0A, 0x4F, 0x63, 0xE2, 0x84, 0xE9, 0x9F, 0x14, 0x86, 0xE2, 0x4B, 0x20, + 0x9F, 0x50, 0xB3, 0x56, 0xED, 0xDE, 0x39, 0xD8, 0x75, 0x64, 0x45, 0x54, + 0xE5, 0x34, 0x57, 0x8C, 0x3B, 0xF2, 0x0E, 0x94, 0x1B, 0x10, 0xA2, 0xA2, + 0x38, 0x76, 0x21, 0x8E, 0x2A, 0x57, 0x64, 0x58, 0x0A, 0x27, 0x6D, 0x4C, + 0xD0, 0xB5, 0xC1, 0xFC, 0x75, 0xD0, 0x01, 0x86, 0x66, 0xA8, 0xF1, 0x98, + 0x58, 0xFB, 0xFC, 0x64, 0xD2, 0x31, 0x77, 0xAD, 0x0E, 0x46, 0x87, 0xCC, + 0x9B, 0x86, 0x90, 0xFF, 0xB6, 0x64, 0x35, 0xA5, 0x5D, 0x9E, 0x44, 0x51, + 0x87, 0x9E, 0x1E, 0xEE, 0xF3, 0x3B, 0x5C, 0xDD, 0x94, 0x03, 0xAA, 0x18, + 0x2C, 0xB7, 0xC4, 0x37, 0xD5, 0x53, 0x28, 0x60, 0xEF, 0x77, 0xEF, 0x3B, + 0x9E, 0xD2, 0xCE, 0xE9, 0x53, 0x2D, 0xF5, 0x19, 0x7E, 0xBB, 0xB5, 0x46, + 0xE2, 0xF7, 0xD6, 0x4D, 0x6D, 0x5B, 0x81, 0x56, 0x6B, 0x12, 0x55, 0x63, + 0xC3, 0xAB, 0x08, 0xBB, 0x2E, 0xD5, 0x11, 0xBC, 0x18, 0xCB, 0x8B, 0x12, + 0x2E, 0x3E, 0x75, 0x32, 0x98, 0x8A, 0xDE, 0x3C, 0xEA, 0x33, 0x46, 0xE7, + 0x7A, 0xA5, 0x12, 0x09, 0x26, 0x7E, 0x7E, 0x03, 0x4F, 0xFD, 0xC0, 0xFD, + 0xEA, 0x4F, 0x83, 0x85, 0x39, 0x62, 0xFB, 0xA2, 0x33, 0xD9, 0x2D, 0xB1, + 0x30, 0x6F, 0x88, 0xAB, 0x61, 0xCB, 0x32, 0xEB, 0x30, 0xF9, 0x51, 0xF6, + 0x1F, 0x3A, 0x11, 0x4D, 0xFD, 0x54, 0xD6, 0x3D, 0x43, 0x73, 0x39, 0x16, + 0xCF, 0x3D, 0x29, 0x4A}; + +TEST(FuzzedDataProvider, ConsumeBytes) { + FuzzedDataProvider DataProv(Data, sizeof(Data)); + EXPECT_EQ(std::vector(1, 0x8A), + DataProv.ConsumeBytes(1)); + EXPECT_EQ(std::vector( + {0x19, 0x0D, 0x44, 0x37, 0x0D, 0x38, 0x5E, 0x9B, 0xAA, 0xF3}), + DataProv.ConsumeBytes(10)); + + std::vector UChars = DataProv.ConsumeBytes(24); + EXPECT_EQ(std::vector({0xDA, 0xAA, 0x88, 0xF2, 0x9B, 0x6C, + 0xBA, 0xBE, 0xB1, 0xF2, 0xCF, 0x13, + 0xB8, 0xAC, 0x1A, 0x7F, 0x1C, 0xC9, + 0x90, 0xD0, 0xD9, 0x5C, 0x42, 0xB3}), + UChars); + + EXPECT_EQ(std::vector(Data + 1 + 10 + 24, Data + sizeof(Data)), + DataProv.ConsumeBytes(31337)); +} + +TEST(FuzzedDataProvider, ConsumeBytesWithTerminator) { + FuzzedDataProvider DataProv(Data, sizeof(Data)); + EXPECT_EQ(std::vector({0x8A, 0x00}), + DataProv.ConsumeBytesWithTerminator(1)); + EXPECT_EQ(std::vector({0x19, 0x0D, 0x44, 0x37, 0x0D, 0x38, 0x5E, + 0x9B, 0xAA, 0xF3, 111}), + DataProv.ConsumeBytesWithTerminator(10, 111)); + + std::vector UChars = + DataProv.ConsumeBytesWithTerminator(24); + EXPECT_EQ(std::vector( + {0xDA, 0xAA, 0x88, 0xF2, 0x9B, 0x6C, 0xBA, 0xBE, 0xB1, + 0xF2, 0xCF, 0x13, 0xB8, 0xAC, 0x1A, 0x7F, 0x1C, 0xC9, + 0x90, 0xD0, 0xD9, 0x5C, 0x42, 0xB3, 0x00}), + UChars); + + std::vector Expected(Data + 1 + 10 + 24, Data + sizeof(Data)); + Expected.push_back(65); + EXPECT_EQ(Expected, + DataProv.ConsumeBytesWithTerminator(31337, 65)); +} + +TEST(FuzzedDataProvider, ConsumeBytesAsString) { + FuzzedDataProvider DataProv(Data, sizeof(Data)); + EXPECT_EQ(std::string("\x8A\x19\x0D\x44\x37\x0D\x38\x5E\x9B\xAA\xF3\xDA"), + DataProv.ConsumeBytesAsString(12)); + EXPECT_EQ(std::string(Data + 12, Data + sizeof(Data)), + DataProv.ConsumeBytesAsString(31337)); +} + +TEST(FuzzedDataProvider, ConsumeIntegralInRange) { + FuzzedDataProvider DataProv(Data, sizeof(Data)); + EXPECT_EQ(int32_t(21), DataProv.ConsumeIntegralInRange(10, 30)); + EXPECT_EQ(int32_t(1337), + DataProv.ConsumeIntegralInRange(1337, 1337)); + EXPECT_EQ(int8_t(-59), DataProv.ConsumeIntegralInRange(-100, 100)); + EXPECT_EQ(uint16_t(15823), + DataProv.ConsumeIntegralInRange(0, 65535)); + EXPECT_EQ((signed char)(-101), + DataProv.ConsumeIntegralInRange(-123, 123)); + EXPECT_EQ(int64_t(-53253077544), DataProv.ConsumeIntegralInRange( + -99999999999, 99999999999)); + + // Exhaust the buffer. + auto String = DataProv.ConsumeBytesAsString(31337); + EXPECT_EQ(size_t(1014), String.length()); + EXPECT_EQ(uint64_t(123456789), + DataProv.ConsumeIntegralInRange(123456789, 987654321)); +} + +TEST(FuzzedDataProvider, ConsumeRandomLengthString) { + FuzzedDataProvider DataProv(Data, sizeof(Data)); + EXPECT_EQ( + std::string( + "\x8A\x19\x0D\x44\x37\x0D\x38\x5E\x9B\xAA\xF3\xDA\xAA\x88\xF2\x9B\x6C" + "\xBA\xBE\xB1\xF2\xCF\x13\xB8\xAC\x1A\x7F\x1C\xC9\x90\xD0\xD9"), + DataProv.ConsumeRandomLengthString(1337)); + EXPECT_EQ(std::string( + "\xB3\xFD\xE3\x05\xA4\x03\x37\x49\x50\x4B\xBC\x39\xA2\x09\x6C" + "\x2F\xAF\xD1\xB5\x47\xBF\x92\xBD\x79\xE5\xC5\x6E\x51\xA4\xED" + "\xE9\xBD\x40\x4A\xFC\x25\x7A\x27\xC8\x92\xF7\x30\xDE\x40\x66" + "\x66\xE8\x5F\x65\x39\x7E\x9E\x80\x2B\x01\x71\x2A\xFF\xD3\x0A" + "\xAC\x6E\x49\x32\x79\x10\x6A\x6F\x97\x96\x70\x7E\x50\x65\xC9" + "\x1D\xBD\x4E\x17\x04\x1E\xBA\x26\xAC\x1F\xE3\x37\x1C\x15\x43" + "\x60\x41\x2A\x7C\xCA\x70\xCE\xAB\x20\x24\xF8\xD9\x1F\x14\x7C"), + DataProv.ConsumeRandomLengthString(31337)); + size_t Offset = 141; + EXPECT_EQ(std::string(Data + Offset, Data + Offset + 5), + DataProv.ConsumeRandomLengthString(5)); + Offset += 5; + EXPECT_EQ(std::string(Data + Offset, Data + Offset + 2), + DataProv.ConsumeRandomLengthString(2)); + Offset += 2; + + // Call the overloaded method without arguments (uses max length available). + EXPECT_EQ(std::string(Data + Offset, Data + Offset + 664), + DataProv.ConsumeRandomLengthString()); + Offset += 664 + 2; // +2 because of '\' character followed by any other byte. + + EXPECT_EQ(std::string(Data + Offset, Data + Offset + 92), + DataProv.ConsumeRandomLengthString()); + Offset += 92 + 2; + + // Exhaust the buffer. + auto String = DataProv.ConsumeBytesAsString(31337); + EXPECT_EQ(size_t(116), String.length()); + EXPECT_EQ(std::string(), DataProv.ConsumeRandomLengthString(1)); +} + +TEST(FuzzedDataProvider, ConsumeRemainingBytes) { + { + FuzzedDataProvider DataProv(Data, sizeof(Data)); + EXPECT_EQ(std::vector(Data, Data + sizeof(Data)), + DataProv.ConsumeRemainingBytes()); + EXPECT_EQ(std::vector(), + DataProv.ConsumeRemainingBytes()); + } + + { + FuzzedDataProvider DataProv(Data, sizeof(Data)); + EXPECT_EQ(std::vector(Data, Data + 123), + DataProv.ConsumeBytes(123)); + EXPECT_EQ(std::vector(Data + 123, Data + sizeof(Data)), + DataProv.ConsumeRemainingBytes()); + } +} + +TEST(FuzzedDataProvider, ConsumeRemainingBytesAsString) { + { + FuzzedDataProvider DataProv(Data, sizeof(Data)); + EXPECT_EQ(std::string(Data, Data + sizeof(Data)), + DataProv.ConsumeRemainingBytesAsString()); + EXPECT_EQ(std::string(""), DataProv.ConsumeRemainingBytesAsString()); + } + + { + FuzzedDataProvider DataProv(Data, sizeof(Data)); + EXPECT_EQ(std::vector(Data, Data + 123), + DataProv.ConsumeBytes(123)); + EXPECT_EQ(std::string(Data + 123, Data + sizeof(Data)), + DataProv.ConsumeRemainingBytesAsString()); + } +} + +TEST(FuzzedDataProvider, ConsumeIntegral) { + FuzzedDataProvider DataProv(Data, sizeof(Data)); + EXPECT_EQ(int32_t(-903266865), DataProv.ConsumeIntegral()); + EXPECT_EQ(uint32_t(372863811), DataProv.ConsumeIntegral()); + EXPECT_EQ(uint8_t(61), DataProv.ConsumeIntegral()); + EXPECT_EQ(int16_t(22100), DataProv.ConsumeIntegral()); + EXPECT_EQ(uint64_t(18252263806144500217u), + DataProv.ConsumeIntegral()); + + // Exhaust the buffer. + auto String = DataProv.ConsumeBytesAsString(31337); + EXPECT_EQ(size_t(1005), String.length()); + EXPECT_EQ(std::numeric_limits::min(), + DataProv.ConsumeIntegral()); +} + +TEST(FuzzedDataProvider, ConsumeBool) { + FuzzedDataProvider DataProv(Data, sizeof(Data)); + EXPECT_EQ(false, DataProv.ConsumeBool()); + EXPECT_EQ(true, DataProv.ConsumeBool()); + EXPECT_EQ(true, DataProv.ConsumeBool()); + EXPECT_EQ(true, DataProv.ConsumeBool()); + EXPECT_EQ(false, DataProv.ConsumeBool()); + EXPECT_EQ(true, DataProv.ConsumeBool()); + EXPECT_EQ(true, DataProv.ConsumeBool()); + EXPECT_EQ(true, DataProv.ConsumeBool()); + EXPECT_EQ(true, DataProv.ConsumeBool()); + EXPECT_EQ(false, DataProv.ConsumeBool()); + + // Exhaust the buffer. + auto String = DataProv.ConsumeBytesAsString(31337); + EXPECT_EQ(size_t(1014), String.length()); + EXPECT_EQ(false, DataProv.ConsumeBool()); +} + +TEST(FuzzedDataProvider, PickValueInStdArray) { + FuzzedDataProvider DataProv(Data, sizeof(Data)); + const std::array Array = {1, 2, 3, 4, 5}; + EXPECT_EQ(5, DataProv.PickValueInArray(Array)); + EXPECT_EQ(2, DataProv.PickValueInArray(Array)); + EXPECT_EQ(2, DataProv.PickValueInArray(Array)); + EXPECT_EQ(3, DataProv.PickValueInArray(Array)); + EXPECT_EQ(3, DataProv.PickValueInArray(Array)); + EXPECT_EQ(3, DataProv.PickValueInArray(Array)); + EXPECT_EQ(1, DataProv.PickValueInArray(Array)); + EXPECT_EQ(3, DataProv.PickValueInArray(Array)); + EXPECT_EQ(2, DataProv.PickValueInArray(Array)); +} + +TEST(FuzzedDataProvider, PickValueInArray) { + FuzzedDataProvider DataProv(Data, sizeof(Data)); + const int Array[] = {1, 2, 3, 4, 5}; + EXPECT_EQ(5, DataProv.PickValueInArray(Array)); + EXPECT_EQ(2, DataProv.PickValueInArray(Array)); + EXPECT_EQ(2, DataProv.PickValueInArray(Array)); + EXPECT_EQ(3, DataProv.PickValueInArray(Array)); + EXPECT_EQ(3, DataProv.PickValueInArray(Array)); + EXPECT_EQ(3, DataProv.PickValueInArray(Array)); + EXPECT_EQ(1, DataProv.PickValueInArray(Array)); + EXPECT_EQ(3, DataProv.PickValueInArray(Array)); + EXPECT_EQ(2, DataProv.PickValueInArray(Array)); + + EXPECT_EQ(uint8_t(0x9D), DataProv.PickValueInArray(Data)); + EXPECT_EQ(uint8_t(0xBA), DataProv.PickValueInArray(Data)); + EXPECT_EQ(uint8_t(0x69), DataProv.PickValueInArray(Data)); + EXPECT_EQ(uint8_t(0xD6), DataProv.PickValueInArray(Data)); + + EXPECT_EQ(uint32_t(777), DataProv.PickValueInArray({1337, 777})); + EXPECT_EQ(uint32_t(777), DataProv.PickValueInArray({1337, 777})); + EXPECT_EQ(uint64_t(1337), DataProv.PickValueInArray({1337, 777})); + EXPECT_EQ(size_t(777), DataProv.PickValueInArray({1337, 777})); + EXPECT_EQ(int16_t(1337), DataProv.PickValueInArray({1337, 777})); + EXPECT_EQ(int32_t(777), DataProv.PickValueInArray({1337, 777})); + EXPECT_EQ(int64_t(777), DataProv.PickValueInArray({1337, 777})); + + // Exhaust the buffer. + auto String = DataProv.ConsumeBytesAsString(31337); + EXPECT_EQ(size_t(1000), String.length()); + EXPECT_EQ(uint8_t(0x8A), DataProv.PickValueInArray(Data)); +} + +TEST(FuzzedDataProvider, ConsumeEnum) { + FuzzedDataProvider DataProv(Data, sizeof(Data)); + enum class Enum { + Zero, + One, + Two, + Three, + Four, + Five, + Six, + Seven, + kMaxValue = Seven + }; + EXPECT_EQ(Enum::Two, DataProv.ConsumeEnum()); + EXPECT_EQ(Enum::One, DataProv.ConsumeEnum()); + EXPECT_EQ(Enum::Five, DataProv.ConsumeEnum()); + EXPECT_EQ(Enum::Seven, DataProv.ConsumeEnum()); + EXPECT_EQ(Enum::Six, DataProv.ConsumeEnum()); + EXPECT_EQ(Enum::One, DataProv.ConsumeEnum()); + EXPECT_EQ(Enum::Three, DataProv.ConsumeEnum()); + EXPECT_EQ(Enum::Three, DataProv.ConsumeEnum()); + EXPECT_EQ(Enum::Five, DataProv.ConsumeEnum()); + EXPECT_EQ(Enum::Six, DataProv.ConsumeEnum()); + + // Exhaust the buffer. + auto String = DataProv.ConsumeBytesAsString(31337); + EXPECT_EQ(size_t(1014), String.length()); + EXPECT_EQ(Enum::Zero, DataProv.ConsumeEnum()); +} + +TEST(FuzzedDataProvider, remaining_bytes) { + FuzzedDataProvider DataProv(Data, sizeof(Data)); + EXPECT_EQ(size_t(1024), DataProv.remaining_bytes()); + EXPECT_EQ(false, DataProv.ConsumeBool()); + EXPECT_EQ(size_t(1024 - 1), DataProv.remaining_bytes()); + EXPECT_EQ(std::vector(Data, Data + 8), + DataProv.ConsumeBytes(8)); + EXPECT_EQ(size_t(1024 - 1 - 8), DataProv.remaining_bytes()); + + // Exhaust the buffer. + EXPECT_EQ(std::vector(Data + 8, Data + sizeof(Data) - 1), + DataProv.ConsumeRemainingBytes()); + EXPECT_EQ(size_t(0), DataProv.remaining_bytes()); +} + +TEST(FuzzedDataProvider, ConsumeProbability) { + FuzzedDataProvider DataProv(Data, sizeof(Data)); + ASSERT_FLOAT_EQ(float(0.28969181), DataProv.ConsumeProbability()); + ASSERT_DOUBLE_EQ(double(0.086814121166605432), + DataProv.ConsumeProbability()); + ASSERT_FLOAT_EQ(float(0.30104411), DataProv.ConsumeProbability()); + ASSERT_DOUBLE_EQ(double(0.96218831486039413), + DataProv.ConsumeProbability()); + ASSERT_FLOAT_EQ(float(0.67005056), DataProv.ConsumeProbability()); + ASSERT_DOUBLE_EQ(double(0.69210584173832279), + DataProv.ConsumeProbability()); + + // Exhaust the buffer. + EXPECT_EQ(std::vector(Data, Data + sizeof(Data) - 36), + DataProv.ConsumeRemainingBytes()); + ASSERT_FLOAT_EQ(float(0.0), DataProv.ConsumeProbability()); +} + +TEST(FuzzedDataProvider, ConsumeFloatingPoint) { + FuzzedDataProvider DataProv(Data, sizeof(Data)); + ASSERT_FLOAT_EQ(float(-2.8546307e+38), + DataProv.ConsumeFloatingPoint()); + ASSERT_DOUBLE_EQ(double(8.0940194040236032e+307), + DataProv.ConsumeFloatingPoint()); + ASSERT_FLOAT_EQ(float(271.49084), + DataProv.ConsumeFloatingPointInRange(123.0, 777.0)); + ASSERT_DOUBLE_EQ(double(30.859126145478349), + DataProv.ConsumeFloatingPointInRange(13.37, 31.337)); + ASSERT_FLOAT_EQ( + float(-903.47729), + DataProv.ConsumeFloatingPointInRange(-999.9999, -777.77)); + ASSERT_DOUBLE_EQ( + double(24.561393182922771), + DataProv.ConsumeFloatingPointInRange(-13.37, 31.337)); + ASSERT_FLOAT_EQ(float(1.0), + DataProv.ConsumeFloatingPointInRange(1.0, 1.0)); + ASSERT_DOUBLE_EQ(double(-1.0), + DataProv.ConsumeFloatingPointInRange(-1.0, -1.0)); + + // Exhaust the buffer. + EXPECT_EQ((std::vector(Data, Data + sizeof(Data) - 50)).size(), + DataProv.ConsumeRemainingBytes().size()); + ASSERT_FLOAT_EQ(float(0.0), DataProv.ConsumeProbability()); + ASSERT_NEAR(std::numeric_limits::lowest(), + DataProv.ConsumeFloatingPoint(), 1e-10); + ASSERT_FLOAT_EQ(float(123.0), + DataProv.ConsumeFloatingPointInRange(123.0, 777.0)); + ASSERT_DOUBLE_EQ(double(-13.37), DataProv.ConsumeFloatingPointInRange( + -13.37, 31.337)); +} + +TEST(FuzzedDataProvider, ConsumeData) { + FuzzedDataProvider DataProv(Data, sizeof(Data)); + uint8_t Buffer[10] = {}; + EXPECT_EQ(sizeof(Buffer), DataProv.ConsumeData(Buffer, sizeof(Buffer))); + std::vector Expected(Data, Data + sizeof(Buffer)); + EXPECT_EQ(Expected, std::vector(Buffer, Buffer + sizeof(Buffer))); + + EXPECT_EQ(size_t(2), DataProv.ConsumeData(Buffer, 2)); + Expected[0] = Data[sizeof(Buffer)]; + Expected[1] = Data[sizeof(Buffer) + 1]; + EXPECT_EQ(Expected, std::vector(Buffer, Buffer + sizeof(Buffer))); + + // Exhaust the buffer. + EXPECT_EQ(std::vector(Data + 12, Data + sizeof(Data)), + DataProv.ConsumeRemainingBytes()); + EXPECT_EQ(size_t(0), DataProv.ConsumeData(Buffer, sizeof(Buffer))); + EXPECT_EQ(Expected, std::vector(Buffer, Buffer + sizeof(Buffer))); +} + +int main(int argc, char **argv) { + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/core/lib/libfuzzer-sys/libfuzzer/tests/FuzzerUnittest.cpp b/core/lib/libfuzzer-sys/libfuzzer/tests/FuzzerUnittest.cpp new file mode 100644 index 0000000000..974a01ff4a --- /dev/null +++ b/core/lib/libfuzzer-sys/libfuzzer/tests/FuzzerUnittest.cpp @@ -0,0 +1,1219 @@ +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +// Avoid ODR violations (LibFuzzer is built without ASan and this test is built +// with ASan) involving C++ standard library types when using libcxx. +#define _LIBCPP_HAS_NO_ASAN + +// Do not attempt to use LLVM ostream etc from gtest. +#define GTEST_NO_LLVM_SUPPORT 1 + +#include "FuzzerCorpus.h" +#include "FuzzerDictionary.h" +#include "FuzzerInternal.h" +#include "FuzzerMerge.h" +#include "FuzzerMutate.h" +#include "FuzzerRandom.h" +#include "FuzzerTracePC.h" +#include "gtest/gtest.h" +#include +#include +#include + +using namespace fuzzer; + +// For now, have LLVMFuzzerTestOneInput just to make it link. +// Later we may want to make unittests that actually call LLVMFuzzerTestOneInput. +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { + abort(); +} + +TEST(Fuzzer, Basename) { + EXPECT_EQ(Basename("foo/bar"), "bar"); + EXPECT_EQ(Basename("bar"), "bar"); + EXPECT_EQ(Basename("/bar"), "bar"); + EXPECT_EQ(Basename("foo/x"), "x"); + EXPECT_EQ(Basename("foo/"), ""); +#if LIBFUZZER_WINDOWS + EXPECT_EQ(Basename("foo\\bar"), "bar"); + EXPECT_EQ(Basename("foo\\bar/baz"), "baz"); + EXPECT_EQ(Basename("\\bar"), "bar"); + EXPECT_EQ(Basename("foo\\x"), "x"); + EXPECT_EQ(Basename("foo\\"), ""); +#endif +} + +TEST(Fuzzer, CrossOver) { + std::unique_ptr t(new ExternalFunctions()); + fuzzer::EF = t.get(); + Random Rand(0); + std::unique_ptr MD(new MutationDispatcher(Rand, {})); + Unit A({0, 1, 2}), B({5, 6, 7}); + Unit C; + Unit Expected[] = { + { 0 }, + { 0, 1 }, + { 0, 5 }, + { 0, 1, 2 }, + { 0, 1, 5 }, + { 0, 5, 1 }, + { 0, 5, 6 }, + { 0, 1, 2, 5 }, + { 0, 1, 5, 2 }, + { 0, 1, 5, 6 }, + { 0, 5, 1, 2 }, + { 0, 5, 1, 6 }, + { 0, 5, 6, 1 }, + { 0, 5, 6, 7 }, + { 0, 1, 2, 5, 6 }, + { 0, 1, 5, 2, 6 }, + { 0, 1, 5, 6, 2 }, + { 0, 1, 5, 6, 7 }, + { 0, 5, 1, 2, 6 }, + { 0, 5, 1, 6, 2 }, + { 0, 5, 1, 6, 7 }, + { 0, 5, 6, 1, 2 }, + { 0, 5, 6, 1, 7 }, + { 0, 5, 6, 7, 1 }, + { 0, 1, 2, 5, 6, 7 }, + { 0, 1, 5, 2, 6, 7 }, + { 0, 1, 5, 6, 2, 7 }, + { 0, 1, 5, 6, 7, 2 }, + { 0, 5, 1, 2, 6, 7 }, + { 0, 5, 1, 6, 2, 7 }, + { 0, 5, 1, 6, 7, 2 }, + { 0, 5, 6, 1, 2, 7 }, + { 0, 5, 6, 1, 7, 2 }, + { 0, 5, 6, 7, 1, 2 } + }; + for (size_t Len = 1; Len < 8; Len++) { + Set FoundUnits, ExpectedUnitsWitThisLength; + for (int Iter = 0; Iter < 3000; Iter++) { + C.resize(Len); + size_t NewSize = MD->CrossOver(A.data(), A.size(), B.data(), B.size(), + C.data(), C.size()); + C.resize(NewSize); + FoundUnits.insert(C); + } + for (const Unit &U : Expected) + if (U.size() <= Len) + ExpectedUnitsWitThisLength.insert(U); + EXPECT_EQ(ExpectedUnitsWitThisLength, FoundUnits); + } +} + +TEST(Fuzzer, Hash) { + uint8_t A[] = {'a', 'b', 'c'}; + fuzzer::Unit U(A, A + sizeof(A)); + EXPECT_EQ("a9993e364706816aba3e25717850c26c9cd0d89d", fuzzer::Hash(U)); + U.push_back('d'); + EXPECT_EQ("81fe8bfe87576c3ecb22426f8e57847382917acf", fuzzer::Hash(U)); +} + +typedef size_t (MutationDispatcher::*Mutator)(uint8_t *Data, size_t Size, + size_t MaxSize); + +void TestEraseBytes(Mutator M, int NumIter) { + std::unique_ptr t(new ExternalFunctions()); + fuzzer::EF = t.get(); + uint8_t REM0[8] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77}; + uint8_t REM1[8] = {0x00, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77}; + uint8_t REM2[8] = {0x00, 0x11, 0x33, 0x44, 0x55, 0x66, 0x77}; + uint8_t REM3[8] = {0x00, 0x11, 0x22, 0x44, 0x55, 0x66, 0x77}; + uint8_t REM4[8] = {0x00, 0x11, 0x22, 0x33, 0x55, 0x66, 0x77}; + uint8_t REM5[8] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x66, 0x77}; + uint8_t REM6[8] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x77}; + uint8_t REM7[8] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66}; + + uint8_t REM8[6] = {0x22, 0x33, 0x44, 0x55, 0x66, 0x77}; + uint8_t REM9[6] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55}; + uint8_t REM10[6] = {0x00, 0x11, 0x22, 0x55, 0x66, 0x77}; + + uint8_t REM11[5] = {0x33, 0x44, 0x55, 0x66, 0x77}; + uint8_t REM12[5] = {0x00, 0x11, 0x22, 0x33, 0x44}; + uint8_t REM13[5] = {0x00, 0x44, 0x55, 0x66, 0x77}; + + + Random Rand(0); + std::unique_ptr MD(new MutationDispatcher(Rand, {})); + int FoundMask = 0; + for (int i = 0; i < NumIter; i++) { + uint8_t T[8] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77}; + size_t NewSize = (*MD.*M)(T, sizeof(T), sizeof(T)); + if (NewSize == 7 && !memcmp(REM0, T, 7)) FoundMask |= 1 << 0; + if (NewSize == 7 && !memcmp(REM1, T, 7)) FoundMask |= 1 << 1; + if (NewSize == 7 && !memcmp(REM2, T, 7)) FoundMask |= 1 << 2; + if (NewSize == 7 && !memcmp(REM3, T, 7)) FoundMask |= 1 << 3; + if (NewSize == 7 && !memcmp(REM4, T, 7)) FoundMask |= 1 << 4; + if (NewSize == 7 && !memcmp(REM5, T, 7)) FoundMask |= 1 << 5; + if (NewSize == 7 && !memcmp(REM6, T, 7)) FoundMask |= 1 << 6; + if (NewSize == 7 && !memcmp(REM7, T, 7)) FoundMask |= 1 << 7; + + if (NewSize == 6 && !memcmp(REM8, T, 6)) FoundMask |= 1 << 8; + if (NewSize == 6 && !memcmp(REM9, T, 6)) FoundMask |= 1 << 9; + if (NewSize == 6 && !memcmp(REM10, T, 6)) FoundMask |= 1 << 10; + + if (NewSize == 5 && !memcmp(REM11, T, 5)) FoundMask |= 1 << 11; + if (NewSize == 5 && !memcmp(REM12, T, 5)) FoundMask |= 1 << 12; + if (NewSize == 5 && !memcmp(REM13, T, 5)) FoundMask |= 1 << 13; + } + EXPECT_EQ(FoundMask, (1 << 14) - 1); +} + +TEST(FuzzerMutate, EraseBytes1) { + TestEraseBytes(&MutationDispatcher::Mutate_EraseBytes, 200); +} +TEST(FuzzerMutate, EraseBytes2) { + TestEraseBytes(&MutationDispatcher::Mutate, 2000); +} + +void TestInsertByte(Mutator M, int NumIter) { + std::unique_ptr t(new ExternalFunctions()); + fuzzer::EF = t.get(); + Random Rand(0); + std::unique_ptr MD(new MutationDispatcher(Rand, {})); + int FoundMask = 0; + uint8_t INS0[8] = {0xF1, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66}; + uint8_t INS1[8] = {0x00, 0xF2, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66}; + uint8_t INS2[8] = {0x00, 0x11, 0xF3, 0x22, 0x33, 0x44, 0x55, 0x66}; + uint8_t INS3[8] = {0x00, 0x11, 0x22, 0xF4, 0x33, 0x44, 0x55, 0x66}; + uint8_t INS4[8] = {0x00, 0x11, 0x22, 0x33, 0xF5, 0x44, 0x55, 0x66}; + uint8_t INS5[8] = {0x00, 0x11, 0x22, 0x33, 0x44, 0xF6, 0x55, 0x66}; + uint8_t INS6[8] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0xF7, 0x66}; + uint8_t INS7[8] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0xF8}; + for (int i = 0; i < NumIter; i++) { + uint8_t T[8] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66}; + size_t NewSize = (*MD.*M)(T, 7, 8); + if (NewSize == 8 && !memcmp(INS0, T, 8)) FoundMask |= 1 << 0; + if (NewSize == 8 && !memcmp(INS1, T, 8)) FoundMask |= 1 << 1; + if (NewSize == 8 && !memcmp(INS2, T, 8)) FoundMask |= 1 << 2; + if (NewSize == 8 && !memcmp(INS3, T, 8)) FoundMask |= 1 << 3; + if (NewSize == 8 && !memcmp(INS4, T, 8)) FoundMask |= 1 << 4; + if (NewSize == 8 && !memcmp(INS5, T, 8)) FoundMask |= 1 << 5; + if (NewSize == 8 && !memcmp(INS6, T, 8)) FoundMask |= 1 << 6; + if (NewSize == 8 && !memcmp(INS7, T, 8)) FoundMask |= 1 << 7; + } + EXPECT_EQ(FoundMask, 255); +} + +TEST(FuzzerMutate, InsertByte1) { + TestInsertByte(&MutationDispatcher::Mutate_InsertByte, 1 << 15); +} +TEST(FuzzerMutate, InsertByte2) { + TestInsertByte(&MutationDispatcher::Mutate, 1 << 17); +} + +void TestInsertRepeatedBytes(Mutator M, int NumIter) { + std::unique_ptr t(new ExternalFunctions()); + fuzzer::EF = t.get(); + Random Rand(0); + std::unique_ptr MD(new MutationDispatcher(Rand, {})); + int FoundMask = 0; + uint8_t INS0[7] = {0x00, 0x11, 0x22, 0x33, 'a', 'a', 'a'}; + uint8_t INS1[7] = {0x00, 0x11, 0x22, 'a', 'a', 'a', 0x33}; + uint8_t INS2[7] = {0x00, 0x11, 'a', 'a', 'a', 0x22, 0x33}; + uint8_t INS3[7] = {0x00, 'a', 'a', 'a', 0x11, 0x22, 0x33}; + uint8_t INS4[7] = {'a', 'a', 'a', 0x00, 0x11, 0x22, 0x33}; + + uint8_t INS5[8] = {0x00, 0x11, 0x22, 0x33, 'b', 'b', 'b', 'b'}; + uint8_t INS6[8] = {0x00, 0x11, 0x22, 'b', 'b', 'b', 'b', 0x33}; + uint8_t INS7[8] = {0x00, 0x11, 'b', 'b', 'b', 'b', 0x22, 0x33}; + uint8_t INS8[8] = {0x00, 'b', 'b', 'b', 'b', 0x11, 0x22, 0x33}; + uint8_t INS9[8] = {'b', 'b', 'b', 'b', 0x00, 0x11, 0x22, 0x33}; + + for (int i = 0; i < NumIter; i++) { + uint8_t T[8] = {0x00, 0x11, 0x22, 0x33}; + size_t NewSize = (*MD.*M)(T, 4, 8); + if (NewSize == 7 && !memcmp(INS0, T, 7)) FoundMask |= 1 << 0; + if (NewSize == 7 && !memcmp(INS1, T, 7)) FoundMask |= 1 << 1; + if (NewSize == 7 && !memcmp(INS2, T, 7)) FoundMask |= 1 << 2; + if (NewSize == 7 && !memcmp(INS3, T, 7)) FoundMask |= 1 << 3; + if (NewSize == 7 && !memcmp(INS4, T, 7)) FoundMask |= 1 << 4; + + if (NewSize == 8 && !memcmp(INS5, T, 8)) FoundMask |= 1 << 5; + if (NewSize == 8 && !memcmp(INS6, T, 8)) FoundMask |= 1 << 6; + if (NewSize == 8 && !memcmp(INS7, T, 8)) FoundMask |= 1 << 7; + if (NewSize == 8 && !memcmp(INS8, T, 8)) FoundMask |= 1 << 8; + if (NewSize == 8 && !memcmp(INS9, T, 8)) FoundMask |= 1 << 9; + + } + EXPECT_EQ(FoundMask, (1 << 10) - 1); +} + +TEST(FuzzerMutate, InsertRepeatedBytes1) { + TestInsertRepeatedBytes(&MutationDispatcher::Mutate_InsertRepeatedBytes, 10000); +} +TEST(FuzzerMutate, InsertRepeatedBytes2) { + TestInsertRepeatedBytes(&MutationDispatcher::Mutate, 300000); +} + +void TestChangeByte(Mutator M, int NumIter) { + std::unique_ptr t(new ExternalFunctions()); + fuzzer::EF = t.get(); + Random Rand(0); + std::unique_ptr MD(new MutationDispatcher(Rand, {})); + int FoundMask = 0; + uint8_t CH0[8] = {0xF0, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77}; + uint8_t CH1[8] = {0x00, 0xF1, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77}; + uint8_t CH2[8] = {0x00, 0x11, 0xF2, 0x33, 0x44, 0x55, 0x66, 0x77}; + uint8_t CH3[8] = {0x00, 0x11, 0x22, 0xF3, 0x44, 0x55, 0x66, 0x77}; + uint8_t CH4[8] = {0x00, 0x11, 0x22, 0x33, 0xF4, 0x55, 0x66, 0x77}; + uint8_t CH5[8] = {0x00, 0x11, 0x22, 0x33, 0x44, 0xF5, 0x66, 0x77}; + uint8_t CH6[8] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0xF5, 0x77}; + uint8_t CH7[8] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0xF7}; + for (int i = 0; i < NumIter; i++) { + uint8_t T[9] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77}; + size_t NewSize = (*MD.*M)(T, 8, 9); + if (NewSize == 8 && !memcmp(CH0, T, 8)) FoundMask |= 1 << 0; + if (NewSize == 8 && !memcmp(CH1, T, 8)) FoundMask |= 1 << 1; + if (NewSize == 8 && !memcmp(CH2, T, 8)) FoundMask |= 1 << 2; + if (NewSize == 8 && !memcmp(CH3, T, 8)) FoundMask |= 1 << 3; + if (NewSize == 8 && !memcmp(CH4, T, 8)) FoundMask |= 1 << 4; + if (NewSize == 8 && !memcmp(CH5, T, 8)) FoundMask |= 1 << 5; + if (NewSize == 8 && !memcmp(CH6, T, 8)) FoundMask |= 1 << 6; + if (NewSize == 8 && !memcmp(CH7, T, 8)) FoundMask |= 1 << 7; + } + EXPECT_EQ(FoundMask, 255); +} + +TEST(FuzzerMutate, ChangeByte1) { + TestChangeByte(&MutationDispatcher::Mutate_ChangeByte, 1 << 15); +} +TEST(FuzzerMutate, ChangeByte2) { + TestChangeByte(&MutationDispatcher::Mutate, 1 << 17); +} + +void TestChangeBit(Mutator M, int NumIter) { + std::unique_ptr t(new ExternalFunctions()); + fuzzer::EF = t.get(); + Random Rand(0); + std::unique_ptr MD(new MutationDispatcher(Rand, {})); + int FoundMask = 0; + uint8_t CH0[8] = {0x01, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77}; + uint8_t CH1[8] = {0x00, 0x13, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77}; + uint8_t CH2[8] = {0x00, 0x11, 0x02, 0x33, 0x44, 0x55, 0x66, 0x77}; + uint8_t CH3[8] = {0x00, 0x11, 0x22, 0x37, 0x44, 0x55, 0x66, 0x77}; + uint8_t CH4[8] = {0x00, 0x11, 0x22, 0x33, 0x54, 0x55, 0x66, 0x77}; + uint8_t CH5[8] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x54, 0x66, 0x77}; + uint8_t CH6[8] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x76, 0x77}; + uint8_t CH7[8] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0xF7}; + for (int i = 0; i < NumIter; i++) { + uint8_t T[9] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77}; + size_t NewSize = (*MD.*M)(T, 8, 9); + if (NewSize == 8 && !memcmp(CH0, T, 8)) FoundMask |= 1 << 0; + if (NewSize == 8 && !memcmp(CH1, T, 8)) FoundMask |= 1 << 1; + if (NewSize == 8 && !memcmp(CH2, T, 8)) FoundMask |= 1 << 2; + if (NewSize == 8 && !memcmp(CH3, T, 8)) FoundMask |= 1 << 3; + if (NewSize == 8 && !memcmp(CH4, T, 8)) FoundMask |= 1 << 4; + if (NewSize == 8 && !memcmp(CH5, T, 8)) FoundMask |= 1 << 5; + if (NewSize == 8 && !memcmp(CH6, T, 8)) FoundMask |= 1 << 6; + if (NewSize == 8 && !memcmp(CH7, T, 8)) FoundMask |= 1 << 7; + } + EXPECT_EQ(FoundMask, 255); +} + +TEST(FuzzerMutate, ChangeBit1) { + TestChangeBit(&MutationDispatcher::Mutate_ChangeBit, 1 << 16); +} +TEST(FuzzerMutate, ChangeBit2) { + TestChangeBit(&MutationDispatcher::Mutate, 1 << 18); +} + +void TestShuffleBytes(Mutator M, int NumIter) { + std::unique_ptr t(new ExternalFunctions()); + fuzzer::EF = t.get(); + Random Rand(0); + std::unique_ptr MD(new MutationDispatcher(Rand, {})); + int FoundMask = 0; + uint8_t CH0[7] = {0x00, 0x22, 0x11, 0x33, 0x44, 0x55, 0x66}; + uint8_t CH1[7] = {0x11, 0x00, 0x33, 0x22, 0x44, 0x55, 0x66}; + uint8_t CH2[7] = {0x00, 0x33, 0x11, 0x22, 0x44, 0x55, 0x66}; + uint8_t CH3[7] = {0x00, 0x11, 0x22, 0x44, 0x55, 0x66, 0x33}; + uint8_t CH4[7] = {0x00, 0x11, 0x22, 0x33, 0x55, 0x44, 0x66}; + for (int i = 0; i < NumIter; i++) { + uint8_t T[7] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66}; + size_t NewSize = (*MD.*M)(T, 7, 7); + if (NewSize == 7 && !memcmp(CH0, T, 7)) FoundMask |= 1 << 0; + if (NewSize == 7 && !memcmp(CH1, T, 7)) FoundMask |= 1 << 1; + if (NewSize == 7 && !memcmp(CH2, T, 7)) FoundMask |= 1 << 2; + if (NewSize == 7 && !memcmp(CH3, T, 7)) FoundMask |= 1 << 3; + if (NewSize == 7 && !memcmp(CH4, T, 7)) FoundMask |= 1 << 4; + } + EXPECT_EQ(FoundMask, 31); +} + +TEST(FuzzerMutate, ShuffleBytes1) { + TestShuffleBytes(&MutationDispatcher::Mutate_ShuffleBytes, 1 << 17); +} +TEST(FuzzerMutate, ShuffleBytes2) { + TestShuffleBytes(&MutationDispatcher::Mutate, 1 << 20); +} + +void TestCopyPart(Mutator M, int NumIter) { + std::unique_ptr t(new ExternalFunctions()); + fuzzer::EF = t.get(); + Random Rand(0); + std::unique_ptr MD(new MutationDispatcher(Rand, {})); + int FoundMask = 0; + uint8_t CH0[7] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x00, 0x11}; + uint8_t CH1[7] = {0x55, 0x66, 0x22, 0x33, 0x44, 0x55, 0x66}; + uint8_t CH2[7] = {0x00, 0x55, 0x66, 0x33, 0x44, 0x55, 0x66}; + uint8_t CH3[7] = {0x00, 0x11, 0x22, 0x00, 0x11, 0x22, 0x66}; + uint8_t CH4[7] = {0x00, 0x11, 0x11, 0x22, 0x33, 0x55, 0x66}; + + for (int i = 0; i < NumIter; i++) { + uint8_t T[7] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66}; + size_t NewSize = (*MD.*M)(T, 7, 7); + if (NewSize == 7 && !memcmp(CH0, T, 7)) FoundMask |= 1 << 0; + if (NewSize == 7 && !memcmp(CH1, T, 7)) FoundMask |= 1 << 1; + if (NewSize == 7 && !memcmp(CH2, T, 7)) FoundMask |= 1 << 2; + if (NewSize == 7 && !memcmp(CH3, T, 7)) FoundMask |= 1 << 3; + if (NewSize == 7 && !memcmp(CH4, T, 7)) FoundMask |= 1 << 4; + } + + uint8_t CH5[8] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x00, 0x11, 0x22}; + uint8_t CH6[8] = {0x22, 0x33, 0x44, 0x00, 0x11, 0x22, 0x33, 0x44}; + uint8_t CH7[8] = {0x00, 0x11, 0x22, 0x00, 0x11, 0x22, 0x33, 0x44}; + uint8_t CH8[8] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x22, 0x33, 0x44}; + uint8_t CH9[8] = {0x00, 0x11, 0x22, 0x22, 0x33, 0x44, 0x33, 0x44}; + + for (int i = 0; i < NumIter; i++) { + uint8_t T[8] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77}; + size_t NewSize = (*MD.*M)(T, 5, 8); + if (NewSize == 8 && !memcmp(CH5, T, 8)) FoundMask |= 1 << 5; + if (NewSize == 8 && !memcmp(CH6, T, 8)) FoundMask |= 1 << 6; + if (NewSize == 8 && !memcmp(CH7, T, 8)) FoundMask |= 1 << 7; + if (NewSize == 8 && !memcmp(CH8, T, 8)) FoundMask |= 1 << 8; + if (NewSize == 8 && !memcmp(CH9, T, 8)) FoundMask |= 1 << 9; + } + + EXPECT_EQ(FoundMask, 1023); +} + +TEST(FuzzerMutate, CopyPart1) { + TestCopyPart(&MutationDispatcher::Mutate_CopyPart, 1 << 10); +} +TEST(FuzzerMutate, CopyPart2) { + TestCopyPart(&MutationDispatcher::Mutate, 1 << 13); +} +TEST(FuzzerMutate, CopyPartNoInsertAtMaxSize) { + // This (non exhaustively) tests if `Mutate_CopyPart` tries to perform an + // insert on an input of size `MaxSize`. Performing an insert in this case + // will lead to the mutation failing. + std::unique_ptr t(new ExternalFunctions()); + fuzzer::EF = t.get(); + Random Rand(0); + std::unique_ptr MD(new MutationDispatcher(Rand, {})); + uint8_t Data[8] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x00, 0x11, 0x22}; + size_t MaxSize = sizeof(Data); + for (int count = 0; count < (1 << 18); ++count) { + size_t NewSize = MD->Mutate_CopyPart(Data, MaxSize, MaxSize); + ASSERT_EQ(NewSize, MaxSize); + } +} + +void TestAddWordFromDictionary(Mutator M, int NumIter) { + std::unique_ptr t(new ExternalFunctions()); + fuzzer::EF = t.get(); + Random Rand(0); + std::unique_ptr MD(new MutationDispatcher(Rand, {})); + uint8_t Word1[4] = {0xAA, 0xBB, 0xCC, 0xDD}; + uint8_t Word2[3] = {0xFF, 0xEE, 0xEF}; + MD->AddWordToManualDictionary(Word(Word1, sizeof(Word1))); + MD->AddWordToManualDictionary(Word(Word2, sizeof(Word2))); + int FoundMask = 0; + uint8_t CH0[7] = {0x00, 0x11, 0x22, 0xAA, 0xBB, 0xCC, 0xDD}; + uint8_t CH1[7] = {0x00, 0x11, 0xAA, 0xBB, 0xCC, 0xDD, 0x22}; + uint8_t CH2[7] = {0x00, 0xAA, 0xBB, 0xCC, 0xDD, 0x11, 0x22}; + uint8_t CH3[7] = {0xAA, 0xBB, 0xCC, 0xDD, 0x00, 0x11, 0x22}; + uint8_t CH4[6] = {0x00, 0x11, 0x22, 0xFF, 0xEE, 0xEF}; + uint8_t CH5[6] = {0x00, 0x11, 0xFF, 0xEE, 0xEF, 0x22}; + uint8_t CH6[6] = {0x00, 0xFF, 0xEE, 0xEF, 0x11, 0x22}; + uint8_t CH7[6] = {0xFF, 0xEE, 0xEF, 0x00, 0x11, 0x22}; + for (int i = 0; i < NumIter; i++) { + uint8_t T[7] = {0x00, 0x11, 0x22}; + size_t NewSize = (*MD.*M)(T, 3, 7); + if (NewSize == 7 && !memcmp(CH0, T, 7)) FoundMask |= 1 << 0; + if (NewSize == 7 && !memcmp(CH1, T, 7)) FoundMask |= 1 << 1; + if (NewSize == 7 && !memcmp(CH2, T, 7)) FoundMask |= 1 << 2; + if (NewSize == 7 && !memcmp(CH3, T, 7)) FoundMask |= 1 << 3; + if (NewSize == 6 && !memcmp(CH4, T, 6)) FoundMask |= 1 << 4; + if (NewSize == 6 && !memcmp(CH5, T, 6)) FoundMask |= 1 << 5; + if (NewSize == 6 && !memcmp(CH6, T, 6)) FoundMask |= 1 << 6; + if (NewSize == 6 && !memcmp(CH7, T, 6)) FoundMask |= 1 << 7; + } + EXPECT_EQ(FoundMask, 255); +} + +TEST(FuzzerMutate, AddWordFromDictionary1) { + TestAddWordFromDictionary( + &MutationDispatcher::Mutate_AddWordFromManualDictionary, 1 << 15); +} + +TEST(FuzzerMutate, AddWordFromDictionary2) { + TestAddWordFromDictionary(&MutationDispatcher::Mutate, 1 << 15); +} + +void TestChangeASCIIInteger(Mutator M, int NumIter) { + std::unique_ptr t(new ExternalFunctions()); + fuzzer::EF = t.get(); + Random Rand(0); + std::unique_ptr MD(new MutationDispatcher(Rand, {})); + + uint8_t CH0[8] = {'1', '2', '3', '4', '5', '6', '7', '7'}; + uint8_t CH1[8] = {'1', '2', '3', '4', '5', '6', '7', '9'}; + uint8_t CH2[8] = {'2', '4', '6', '9', '1', '3', '5', '6'}; + uint8_t CH3[8] = {'0', '6', '1', '7', '2', '8', '3', '9'}; + int FoundMask = 0; + for (int i = 0; i < NumIter; i++) { + uint8_t T[8] = {'1', '2', '3', '4', '5', '6', '7', '8'}; + size_t NewSize = (*MD.*M)(T, 8, 8); + /**/ if (NewSize == 8 && !memcmp(CH0, T, 8)) FoundMask |= 1 << 0; + else if (NewSize == 8 && !memcmp(CH1, T, 8)) FoundMask |= 1 << 1; + else if (NewSize == 8 && !memcmp(CH2, T, 8)) FoundMask |= 1 << 2; + else if (NewSize == 8 && !memcmp(CH3, T, 8)) FoundMask |= 1 << 3; + else if (NewSize == 8) FoundMask |= 1 << 4; + } + EXPECT_EQ(FoundMask, 31); +} + +TEST(FuzzerMutate, ChangeASCIIInteger1) { + TestChangeASCIIInteger(&MutationDispatcher::Mutate_ChangeASCIIInteger, + 1 << 15); +} + +TEST(FuzzerMutate, ChangeASCIIInteger2) { + TestChangeASCIIInteger(&MutationDispatcher::Mutate, 1 << 15); +} + +void TestChangeBinaryInteger(Mutator M, int NumIter) { + std::unique_ptr t(new ExternalFunctions()); + fuzzer::EF = t.get(); + Random Rand(0); + std::unique_ptr MD(new MutationDispatcher(Rand, {})); + + uint8_t CH0[8] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x79}; + uint8_t CH1[8] = {0x00, 0x11, 0x22, 0x31, 0x44, 0x55, 0x66, 0x77}; + uint8_t CH2[8] = {0xff, 0x10, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77}; + uint8_t CH3[8] = {0x00, 0x11, 0x2a, 0x33, 0x44, 0x55, 0x66, 0x77}; + uint8_t CH4[8] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x4f, 0x66, 0x77}; + uint8_t CH5[8] = {0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88}; + uint8_t CH6[8] = {0x00, 0x11, 0x22, 0x00, 0x00, 0x00, 0x08, 0x77}; // Size + uint8_t CH7[8] = {0x00, 0x08, 0x00, 0x33, 0x44, 0x55, 0x66, 0x77}; // Sw(Size) + + int FoundMask = 0; + for (int i = 0; i < NumIter; i++) { + uint8_t T[8] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77}; + size_t NewSize = (*MD.*M)(T, 8, 8); + /**/ if (NewSize == 8 && !memcmp(CH0, T, 8)) FoundMask |= 1 << 0; + else if (NewSize == 8 && !memcmp(CH1, T, 8)) FoundMask |= 1 << 1; + else if (NewSize == 8 && !memcmp(CH2, T, 8)) FoundMask |= 1 << 2; + else if (NewSize == 8 && !memcmp(CH3, T, 8)) FoundMask |= 1 << 3; + else if (NewSize == 8 && !memcmp(CH4, T, 8)) FoundMask |= 1 << 4; + else if (NewSize == 8 && !memcmp(CH5, T, 8)) FoundMask |= 1 << 5; + else if (NewSize == 8 && !memcmp(CH6, T, 8)) FoundMask |= 1 << 6; + else if (NewSize == 8 && !memcmp(CH7, T, 8)) FoundMask |= 1 << 7; + } + EXPECT_EQ(FoundMask, 255); +} + +TEST(FuzzerMutate, ChangeBinaryInteger1) { + TestChangeBinaryInteger(&MutationDispatcher::Mutate_ChangeBinaryInteger, + 1 << 12); +} + +TEST(FuzzerMutate, ChangeBinaryInteger2) { + TestChangeBinaryInteger(&MutationDispatcher::Mutate, 1 << 15); +} + + +TEST(FuzzerDictionary, ParseOneDictionaryEntry) { + Unit U; + EXPECT_FALSE(ParseOneDictionaryEntry("", &U)); + EXPECT_FALSE(ParseOneDictionaryEntry(" ", &U)); + EXPECT_FALSE(ParseOneDictionaryEntry("\t ", &U)); + EXPECT_FALSE(ParseOneDictionaryEntry(" \" ", &U)); + EXPECT_FALSE(ParseOneDictionaryEntry(" zz\" ", &U)); + EXPECT_FALSE(ParseOneDictionaryEntry(" \"zz ", &U)); + EXPECT_FALSE(ParseOneDictionaryEntry(" \"\" ", &U)); + EXPECT_TRUE(ParseOneDictionaryEntry("\"a\"", &U)); + EXPECT_EQ(U, Unit({'a'})); + EXPECT_TRUE(ParseOneDictionaryEntry("\"abc\"", &U)); + EXPECT_EQ(U, Unit({'a', 'b', 'c'})); + EXPECT_TRUE(ParseOneDictionaryEntry("abc=\"abc\"", &U)); + EXPECT_EQ(U, Unit({'a', 'b', 'c'})); + EXPECT_FALSE(ParseOneDictionaryEntry("\"\\\"", &U)); + EXPECT_TRUE(ParseOneDictionaryEntry("\"\\\\\"", &U)); + EXPECT_EQ(U, Unit({'\\'})); + EXPECT_TRUE(ParseOneDictionaryEntry("\"\\xAB\"", &U)); + EXPECT_EQ(U, Unit({0xAB})); + EXPECT_TRUE(ParseOneDictionaryEntry("\"\\xABz\\xDE\"", &U)); + EXPECT_EQ(U, Unit({0xAB, 'z', 0xDE})); + EXPECT_TRUE(ParseOneDictionaryEntry("\"#\"", &U)); + EXPECT_EQ(U, Unit({'#'})); + EXPECT_TRUE(ParseOneDictionaryEntry("\"\\\"\"", &U)); + EXPECT_EQ(U, Unit({'"'})); +} + +TEST(FuzzerDictionary, ParseDictionaryFile) { + Vector Units; + EXPECT_FALSE(ParseDictionaryFile("zzz\n", &Units)); + EXPECT_FALSE(ParseDictionaryFile("", &Units)); + EXPECT_TRUE(ParseDictionaryFile("\n", &Units)); + EXPECT_EQ(Units.size(), 0U); + EXPECT_TRUE(ParseDictionaryFile("#zzzz a b c d\n", &Units)); + EXPECT_EQ(Units.size(), 0U); + EXPECT_TRUE(ParseDictionaryFile(" #zzzz\n", &Units)); + EXPECT_EQ(Units.size(), 0U); + EXPECT_TRUE(ParseDictionaryFile(" #zzzz\n", &Units)); + EXPECT_EQ(Units.size(), 0U); + EXPECT_TRUE(ParseDictionaryFile(" #zzzz\naaa=\"aa\"", &Units)); + EXPECT_EQ(Units, Vector({Unit({'a', 'a'})})); + EXPECT_TRUE( + ParseDictionaryFile(" #zzzz\naaa=\"aa\"\n\nabc=\"abc\"", &Units)); + EXPECT_EQ(Units, + Vector({Unit({'a', 'a'}), Unit({'a', 'b', 'c'})})); +} + +TEST(FuzzerUtil, Base64) { + EXPECT_EQ("", Base64({})); + EXPECT_EQ("YQ==", Base64({'a'})); + EXPECT_EQ("eA==", Base64({'x'})); + EXPECT_EQ("YWI=", Base64({'a', 'b'})); + EXPECT_EQ("eHk=", Base64({'x', 'y'})); + EXPECT_EQ("YWJj", Base64({'a', 'b', 'c'})); + EXPECT_EQ("eHl6", Base64({'x', 'y', 'z'})); + EXPECT_EQ("YWJjeA==", Base64({'a', 'b', 'c', 'x'})); + EXPECT_EQ("YWJjeHk=", Base64({'a', 'b', 'c', 'x', 'y'})); + EXPECT_EQ("YWJjeHl6", Base64({'a', 'b', 'c', 'x', 'y', 'z'})); +} + +TEST(Corpus, Distribution) { + DataFlowTrace DFT; + Random Rand(0); + struct EntropicOptions Entropic = {false, 0xFF, 100, false}; + std::unique_ptr C(new InputCorpus("", Entropic)); + size_t N = 10; + size_t TriesPerUnit = 1<<16; + for (size_t i = 0; i < N; i++) + C->AddToCorpus(Unit{static_cast(i)}, /*NumFeatures*/ 1, + /*MayDeleteFile*/ false, /*HasFocusFunction*/ false, + /*ForceAddToCorpus*/ false, + /*TimeOfUnit*/ std::chrono::microseconds(0), + /*FeatureSet*/ {}, DFT, + /*BaseII*/ nullptr); + + Vector Hist(N); + for (size_t i = 0; i < N * TriesPerUnit; i++) { + Hist[C->ChooseUnitIdxToMutate(Rand)]++; + } + for (size_t i = 0; i < N; i++) { + // A weak sanity check that every unit gets invoked. + EXPECT_GT(Hist[i], TriesPerUnit / N / 3); + } +} + +template void EQ(const Vector &A, const Vector &B) { + EXPECT_EQ(A, B); +} + +template void EQ(const Set &A, const Vector &B) { + EXPECT_EQ(A, Set(B.begin(), B.end())); +} + +void EQ(const Vector &A, const Vector &B) { + Set a; + for (const auto &File : A) + a.insert(File.Name); + Set b(B.begin(), B.end()); + EXPECT_EQ(a, b); +} + +#define TRACED_EQ(A, ...) \ + { \ + SCOPED_TRACE(#A); \ + EQ(A, __VA_ARGS__); \ + } + +TEST(Merger, Parse) { + Merger M; + + const char *kInvalidInputs[] = { + // Bad file numbers + "", + "x", + "0\n0", + "3\nx", + "2\n3", + "2\n2", + // Bad file names + "2\n2\nA\n", + "2\n2\nA\nB\nC\n", + // Unknown markers + "2\n1\nA\nSTARTED 0\nBAD 0 0x0", + // Bad file IDs + "1\n1\nA\nSTARTED 1", + "2\n1\nA\nSTARTED 0\nFT 1 0x0", + }; + for (auto S : kInvalidInputs) { + SCOPED_TRACE(S); + EXPECT_FALSE(M.Parse(S, false)); + } + + // Parse initial control file + EXPECT_TRUE(M.Parse("1\n0\nAA\n", false)); + ASSERT_EQ(M.Files.size(), 1U); + EXPECT_EQ(M.NumFilesInFirstCorpus, 0U); + EXPECT_EQ(M.Files[0].Name, "AA"); + EXPECT_TRUE(M.LastFailure.empty()); + EXPECT_EQ(M.FirstNotProcessedFile, 0U); + + // Parse control file that failed on first attempt + EXPECT_TRUE(M.Parse("2\n1\nAA\nBB\nSTARTED 0 42\n", false)); + ASSERT_EQ(M.Files.size(), 2U); + EXPECT_EQ(M.NumFilesInFirstCorpus, 1U); + EXPECT_EQ(M.Files[0].Name, "AA"); + EXPECT_EQ(M.Files[1].Name, "BB"); + EXPECT_EQ(M.LastFailure, "AA"); + EXPECT_EQ(M.FirstNotProcessedFile, 1U); + + // Parse control file that failed on later attempt + EXPECT_TRUE(M.Parse("3\n1\nAA\nBB\nC\n" + "STARTED 0 1000\n" + "FT 0 1 2 3\n" + "STARTED 1 1001\n" + "FT 1 4 5 6 \n" + "STARTED 2 1002\n" + "", + true)); + ASSERT_EQ(M.Files.size(), 3U); + EXPECT_EQ(M.NumFilesInFirstCorpus, 1U); + EXPECT_EQ(M.Files[0].Name, "AA"); + EXPECT_EQ(M.Files[0].Size, 1000U); + EXPECT_EQ(M.Files[1].Name, "BB"); + EXPECT_EQ(M.Files[1].Size, 1001U); + EXPECT_EQ(M.Files[2].Name, "C"); + EXPECT_EQ(M.Files[2].Size, 1002U); + EXPECT_EQ(M.LastFailure, "C"); + EXPECT_EQ(M.FirstNotProcessedFile, 3U); + TRACED_EQ(M.Files[0].Features, {1, 2, 3}); + TRACED_EQ(M.Files[1].Features, {4, 5, 6}); + + // Parse control file without features or PCs + EXPECT_TRUE(M.Parse("2\n0\nAA\nBB\n" + "STARTED 0 1000\n" + "FT 0\n" + "COV 0\n" + "STARTED 1 1001\n" + "FT 1\n" + "COV 1\n" + "", + true)); + ASSERT_EQ(M.Files.size(), 2U); + EXPECT_EQ(M.NumFilesInFirstCorpus, 0U); + EXPECT_TRUE(M.LastFailure.empty()); + EXPECT_EQ(M.FirstNotProcessedFile, 2U); + EXPECT_TRUE(M.Files[0].Features.empty()); + EXPECT_TRUE(M.Files[0].Cov.empty()); + EXPECT_TRUE(M.Files[1].Features.empty()); + EXPECT_TRUE(M.Files[1].Cov.empty()); + + // Parse features and PCs + EXPECT_TRUE(M.Parse("3\n2\nAA\nBB\nC\n" + "STARTED 0 1000\n" + "FT 0 1 2 3\n" + "COV 0 11 12 13\n" + "STARTED 1 1001\n" + "FT 1 4 5 6\n" + "COV 1 7 8 9\n" + "STARTED 2 1002\n" + "FT 2 6 1 3\n" + "COV 2 16 11 13\n" + "", + true)); + ASSERT_EQ(M.Files.size(), 3U); + EXPECT_EQ(M.NumFilesInFirstCorpus, 2U); + EXPECT_TRUE(M.LastFailure.empty()); + EXPECT_EQ(M.FirstNotProcessedFile, 3U); + TRACED_EQ(M.Files[0].Features, {1, 2, 3}); + TRACED_EQ(M.Files[0].Cov, {11, 12, 13}); + TRACED_EQ(M.Files[1].Features, {4, 5, 6}); + TRACED_EQ(M.Files[1].Cov, {7, 8, 9}); + TRACED_EQ(M.Files[2].Features, {1, 3, 6}); + TRACED_EQ(M.Files[2].Cov, {16}); +} + +TEST(Merger, Merge) { + Merger M; + Set Features, NewFeatures; + Set Cov, NewCov; + Vector NewFiles; + + // Adds new files and features + EXPECT_TRUE(M.Parse("3\n0\nA\nB\nC\n" + "STARTED 0 1000\n" + "FT 0 1 2 3\n" + "STARTED 1 1001\n" + "FT 1 4 5 6 \n" + "STARTED 2 1002\n" + "FT 2 6 1 3\n" + "", + true)); + EXPECT_EQ(M.Merge(Features, &NewFeatures, Cov, &NewCov, &NewFiles), 6U); + TRACED_EQ(M.Files, {"A", "B", "C"}); + TRACED_EQ(NewFiles, {"A", "B"}); + TRACED_EQ(NewFeatures, {1, 2, 3, 4, 5, 6}); + + // Doesn't return features or files in the initial corpus. + EXPECT_TRUE(M.Parse("3\n1\nA\nB\nC\n" + "STARTED 0 1000\n" + "FT 0 1 2 3\n" + "STARTED 1 1001\n" + "FT 1 4 5 6 \n" + "STARTED 2 1002\n" + "FT 2 6 1 3\n" + "", + true)); + EXPECT_EQ(M.Merge(Features, &NewFeatures, Cov, &NewCov, &NewFiles), 3U); + TRACED_EQ(M.Files, {"A", "B", "C"}); + TRACED_EQ(NewFiles, {"B"}); + TRACED_EQ(NewFeatures, {4, 5, 6}); + + // No new features, so no new files + EXPECT_TRUE(M.Parse("3\n2\nA\nB\nC\n" + "STARTED 0 1000\n" + "FT 0 1 2 3\n" + "STARTED 1 1001\n" + "FT 1 4 5 6 \n" + "STARTED 2 1002\n" + "FT 2 6 1 3\n" + "", + true)); + EXPECT_EQ(M.Merge(Features, &NewFeatures, Cov, &NewCov, &NewFiles), 0U); + TRACED_EQ(M.Files, {"A", "B", "C"}); + TRACED_EQ(NewFiles, {}); + TRACED_EQ(NewFeatures, {}); + + // Can pass initial features and coverage. + Features = {1, 2, 3}; + Cov = {}; + EXPECT_TRUE(M.Parse("2\n0\nA\nB\n" + "STARTED 0 1000\n" + "FT 0 1 2 3\n" + "STARTED 1 1001\n" + "FT 1 4 5 6\n" + "", + true)); + EXPECT_EQ(M.Merge(Features, &NewFeatures, Cov, &NewCov, &NewFiles), 3U); + TRACED_EQ(M.Files, {"A", "B"}); + TRACED_EQ(NewFiles, {"B"}); + TRACED_EQ(NewFeatures, {4, 5, 6}); + Features.clear(); + Cov.clear(); + + // Parse smaller files first + EXPECT_TRUE(M.Parse("3\n0\nA\nB\nC\n" + "STARTED 0 2000\n" + "FT 0 1 2 3\n" + "STARTED 1 1001\n" + "FT 1 4 5 6 \n" + "STARTED 2 1002\n" + "FT 2 6 1 3 \n" + "", + true)); + EXPECT_EQ(M.Merge(Features, &NewFeatures, Cov, &NewCov, &NewFiles), 6U); + TRACED_EQ(M.Files, {"B", "C", "A"}); + TRACED_EQ(NewFiles, {"B", "C", "A"}); + TRACED_EQ(NewFeatures, {1, 2, 3, 4, 5, 6}); + + EXPECT_TRUE(M.Parse("4\n0\nA\nB\nC\nD\n" + "STARTED 0 2000\n" + "FT 0 1 2 3\n" + "STARTED 1 1101\n" + "FT 1 4 5 6 \n" + "STARTED 2 1102\n" + "FT 2 6 1 3 100 \n" + "STARTED 3 1000\n" + "FT 3 1 \n" + "", + true)); + EXPECT_EQ(M.Merge(Features, &NewFeatures, Cov, &NewCov, &NewFiles), 7U); + TRACED_EQ(M.Files, {"A", "B", "C", "D"}); + TRACED_EQ(NewFiles, {"D", "B", "C", "A"}); + TRACED_EQ(NewFeatures, {1, 2, 3, 4, 5, 6, 100}); + + // For same sized file, parse more features first + EXPECT_TRUE(M.Parse("4\n1\nA\nB\nC\nD\n" + "STARTED 0 2000\n" + "FT 0 4 5 6 7 8\n" + "STARTED 1 1100\n" + "FT 1 1 2 3 \n" + "STARTED 2 1100\n" + "FT 2 2 3 \n" + "STARTED 3 1000\n" + "FT 3 1 \n" + "", + true)); + EXPECT_EQ(M.Merge(Features, &NewFeatures, Cov, &NewCov, &NewFiles), 3U); + TRACED_EQ(M.Files, {"A", "B", "C", "D"}); + TRACED_EQ(NewFiles, {"D", "B"}); + TRACED_EQ(NewFeatures, {1, 2, 3}); +} + +#undef TRACED_EQ + +TEST(DFT, BlockCoverage) { + BlockCoverage Cov; + // Assuming C0 has 5 instrumented blocks, + // C1: 7 blocks, C2: 4, C3: 9, C4 never covered, C5: 15, + + // Add C0 + EXPECT_TRUE(Cov.AppendCoverage("C0 5\n")); + EXPECT_EQ(Cov.GetCounter(0, 0), 1U); + EXPECT_EQ(Cov.GetCounter(0, 1), 0U); // not seen this BB yet. + EXPECT_EQ(Cov.GetCounter(0, 5), 0U); // BB ID out of bounds. + EXPECT_EQ(Cov.GetCounter(1, 0), 0U); // not seen this function yet. + + EXPECT_EQ(Cov.GetNumberOfBlocks(0), 5U); + EXPECT_EQ(Cov.GetNumberOfCoveredBlocks(0), 1U); + EXPECT_EQ(Cov.GetNumberOfBlocks(1), 0U); + + // Various errors. + EXPECT_FALSE(Cov.AppendCoverage("C0\n")); // No total number. + EXPECT_FALSE(Cov.AppendCoverage("C0 7\n")); // No total number. + EXPECT_FALSE(Cov.AppendCoverage("CZ\n")); // Wrong function number. + EXPECT_FALSE(Cov.AppendCoverage("C1 7 7")); // BB ID is too big. + EXPECT_FALSE(Cov.AppendCoverage("C1 100 7")); // BB ID is too big. + + // Add C0 more times. + EXPECT_TRUE(Cov.AppendCoverage("C0 5\n")); + EXPECT_EQ(Cov.GetCounter(0, 0), 2U); + EXPECT_TRUE(Cov.AppendCoverage("C0 1 2 5\n")); + EXPECT_EQ(Cov.GetCounter(0, 0), 3U); + EXPECT_EQ(Cov.GetCounter(0, 1), 1U); + EXPECT_EQ(Cov.GetCounter(0, 2), 1U); + EXPECT_EQ(Cov.GetCounter(0, 3), 0U); + EXPECT_EQ(Cov.GetCounter(0, 4), 0U); + EXPECT_EQ(Cov.GetNumberOfCoveredBlocks(0), 3U); + EXPECT_TRUE(Cov.AppendCoverage("C0 1 3 4 5\n")); + EXPECT_EQ(Cov.GetCounter(0, 0), 4U); + EXPECT_EQ(Cov.GetCounter(0, 1), 2U); + EXPECT_EQ(Cov.GetCounter(0, 2), 1U); + EXPECT_EQ(Cov.GetCounter(0, 3), 1U); + EXPECT_EQ(Cov.GetCounter(0, 4), 1U); + EXPECT_EQ(Cov.GetNumberOfCoveredBlocks(0), 5U); + + EXPECT_TRUE(Cov.AppendCoverage("C1 7\nC2 4\nC3 9\nC5 15\nC0 5\n")); + EXPECT_EQ(Cov.GetCounter(0, 0), 5U); + EXPECT_EQ(Cov.GetCounter(1, 0), 1U); + EXPECT_EQ(Cov.GetCounter(2, 0), 1U); + EXPECT_EQ(Cov.GetCounter(3, 0), 1U); + EXPECT_EQ(Cov.GetCounter(4, 0), 0U); + EXPECT_EQ(Cov.GetCounter(5, 0), 1U); + + EXPECT_TRUE(Cov.AppendCoverage("C3 4 5 9\nC5 11 12 15")); + EXPECT_EQ(Cov.GetCounter(0, 0), 5U); + EXPECT_EQ(Cov.GetCounter(1, 0), 1U); + EXPECT_EQ(Cov.GetCounter(2, 0), 1U); + EXPECT_EQ(Cov.GetCounter(3, 0), 2U); + EXPECT_EQ(Cov.GetCounter(3, 4), 1U); + EXPECT_EQ(Cov.GetCounter(3, 5), 1U); + EXPECT_EQ(Cov.GetCounter(3, 6), 0U); + EXPECT_EQ(Cov.GetCounter(4, 0), 0U); + EXPECT_EQ(Cov.GetCounter(5, 0), 2U); + EXPECT_EQ(Cov.GetCounter(5, 10), 0U); + EXPECT_EQ(Cov.GetCounter(5, 11), 1U); + EXPECT_EQ(Cov.GetCounter(5, 12), 1U); +} + +TEST(DFT, FunctionWeights) { + BlockCoverage Cov; + // unused function gets zero weight. + EXPECT_TRUE(Cov.AppendCoverage("C0 5\n")); + auto Weights = Cov.FunctionWeights(2); + EXPECT_GT(Weights[0], 0.); + EXPECT_EQ(Weights[1], 0.); + + // Less frequently used function gets less weight. + Cov.clear(); + EXPECT_TRUE(Cov.AppendCoverage("C0 5\nC1 5\nC1 5\n")); + Weights = Cov.FunctionWeights(2); + EXPECT_GT(Weights[0], Weights[1]); + + // A function with more uncovered blocks gets more weight. + Cov.clear(); + EXPECT_TRUE(Cov.AppendCoverage("C0 1 2 3 5\nC1 2 4\n")); + Weights = Cov.FunctionWeights(2); + EXPECT_GT(Weights[1], Weights[0]); + + // A function with DFT gets more weight than the function w/o DFT. + Cov.clear(); + EXPECT_TRUE(Cov.AppendCoverage("F1 111\nC0 3\nC1 1 2 3\n")); + Weights = Cov.FunctionWeights(2); + EXPECT_GT(Weights[1], Weights[0]); +} + + +TEST(Fuzzer, ForEachNonZeroByte) { + const size_t N = 64; + alignas(64) uint8_t Ar[N + 8] = { + 0, 0, 0, 0, 0, 0, 0, 0, + 1, 2, 0, 0, 0, 0, 0, 0, + 0, 0, 3, 0, 4, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 5, 0, 6, 0, 0, + 0, 0, 0, 0, 0, 0, 7, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 8, + 9, 9, 9, 9, 9, 9, 9, 9, + }; + typedef Vector > Vec; + Vec Res, Expected; + auto CB = [&](size_t FirstFeature, size_t Idx, uint8_t V) { + Res.push_back({FirstFeature + Idx, V}); + }; + ForEachNonZeroByte(Ar, Ar + N, 100, CB); + Expected = {{108, 1}, {109, 2}, {118, 3}, {120, 4}, + {135, 5}, {137, 6}, {146, 7}, {163, 8}}; + EXPECT_EQ(Res, Expected); + + Res.clear(); + ForEachNonZeroByte(Ar + 9, Ar + N, 109, CB); + Expected = { {109, 2}, {118, 3}, {120, 4}, + {135, 5}, {137, 6}, {146, 7}, {163, 8}}; + EXPECT_EQ(Res, Expected); + + Res.clear(); + ForEachNonZeroByte(Ar + 9, Ar + N - 9, 109, CB); + Expected = { {109, 2}, {118, 3}, {120, 4}, + {135, 5}, {137, 6}, {146, 7}}; + EXPECT_EQ(Res, Expected); +} + +// FuzzerCommand unit tests. The arguments in the two helper methods below must +// match. +static void makeCommandArgs(Vector *ArgsToAdd) { + assert(ArgsToAdd); + ArgsToAdd->clear(); + ArgsToAdd->push_back("foo"); + ArgsToAdd->push_back("-bar=baz"); + ArgsToAdd->push_back("qux"); + ArgsToAdd->push_back(Command::ignoreRemainingArgs()); + ArgsToAdd->push_back("quux"); + ArgsToAdd->push_back("-grault=garply"); +} + +static std::string makeCmdLine(const char *separator, const char *suffix) { + std::string CmdLine("foo -bar=baz qux "); + if (strlen(separator) != 0) { + CmdLine += separator; + CmdLine += " "; + } + CmdLine += Command::ignoreRemainingArgs(); + CmdLine += " quux -grault=garply"; + if (strlen(suffix) != 0) { + CmdLine += " "; + CmdLine += suffix; + } + return CmdLine; +} + +TEST(FuzzerCommand, Create) { + std::string CmdLine; + + // Default constructor + Command DefaultCmd; + + CmdLine = DefaultCmd.toString(); + EXPECT_EQ(CmdLine, ""); + + // Explicit constructor + Vector ArgsToAdd; + makeCommandArgs(&ArgsToAdd); + Command InitializedCmd(ArgsToAdd); + + CmdLine = InitializedCmd.toString(); + EXPECT_EQ(CmdLine, makeCmdLine("", "")); + + // Compare each argument + auto InitializedArgs = InitializedCmd.getArguments(); + auto i = ArgsToAdd.begin(); + auto j = InitializedArgs.begin(); + while (i != ArgsToAdd.end() && j != InitializedArgs.end()) { + EXPECT_EQ(*i++, *j++); + } + EXPECT_EQ(i, ArgsToAdd.end()); + EXPECT_EQ(j, InitializedArgs.end()); + + // Copy constructor + Command CopiedCmd(InitializedCmd); + + CmdLine = CopiedCmd.toString(); + EXPECT_EQ(CmdLine, makeCmdLine("", "")); + + // Assignment operator + Command AssignedCmd; + AssignedCmd = CopiedCmd; + + CmdLine = AssignedCmd.toString(); + EXPECT_EQ(CmdLine, makeCmdLine("", "")); +} + +TEST(FuzzerCommand, ModifyArguments) { + Vector ArgsToAdd; + makeCommandArgs(&ArgsToAdd); + Command Cmd; + std::string CmdLine; + + Cmd.addArguments(ArgsToAdd); + CmdLine = Cmd.toString(); + EXPECT_EQ(CmdLine, makeCmdLine("", "")); + + Cmd.addArgument("waldo"); + EXPECT_TRUE(Cmd.hasArgument("waldo")); + + CmdLine = Cmd.toString(); + EXPECT_EQ(CmdLine, makeCmdLine("waldo", "")); + + Cmd.removeArgument("waldo"); + EXPECT_FALSE(Cmd.hasArgument("waldo")); + + CmdLine = Cmd.toString(); + EXPECT_EQ(CmdLine, makeCmdLine("", "")); +} + +TEST(FuzzerCommand, ModifyFlags) { + Vector ArgsToAdd; + makeCommandArgs(&ArgsToAdd); + Command Cmd(ArgsToAdd); + std::string Value, CmdLine; + ASSERT_FALSE(Cmd.hasFlag("fred")); + + Value = Cmd.getFlagValue("fred"); + EXPECT_EQ(Value, ""); + + CmdLine = Cmd.toString(); + EXPECT_EQ(CmdLine, makeCmdLine("", "")); + + Cmd.addFlag("fred", "plugh"); + EXPECT_TRUE(Cmd.hasFlag("fred")); + + Value = Cmd.getFlagValue("fred"); + EXPECT_EQ(Value, "plugh"); + + CmdLine = Cmd.toString(); + EXPECT_EQ(CmdLine, makeCmdLine("-fred=plugh", "")); + + Cmd.removeFlag("fred"); + EXPECT_FALSE(Cmd.hasFlag("fred")); + + Value = Cmd.getFlagValue("fred"); + EXPECT_EQ(Value, ""); + + CmdLine = Cmd.toString(); + EXPECT_EQ(CmdLine, makeCmdLine("", "")); +} + +TEST(FuzzerCommand, SetOutput) { + Vector ArgsToAdd; + makeCommandArgs(&ArgsToAdd); + Command Cmd(ArgsToAdd); + std::string CmdLine; + ASSERT_FALSE(Cmd.hasOutputFile()); + ASSERT_FALSE(Cmd.isOutAndErrCombined()); + + Cmd.combineOutAndErr(true); + EXPECT_TRUE(Cmd.isOutAndErrCombined()); + + CmdLine = Cmd.toString(); + EXPECT_EQ(CmdLine, makeCmdLine("", "2>&1")); + + Cmd.combineOutAndErr(false); + EXPECT_FALSE(Cmd.isOutAndErrCombined()); + + Cmd.setOutputFile("xyzzy"); + EXPECT_TRUE(Cmd.hasOutputFile()); + + CmdLine = Cmd.toString(); + EXPECT_EQ(CmdLine, makeCmdLine("", ">xyzzy")); + + Cmd.setOutputFile("thud"); + EXPECT_TRUE(Cmd.hasOutputFile()); + + CmdLine = Cmd.toString(); + EXPECT_EQ(CmdLine, makeCmdLine("", ">thud")); + + Cmd.combineOutAndErr(); + EXPECT_TRUE(Cmd.isOutAndErrCombined()); + + CmdLine = Cmd.toString(); + EXPECT_EQ(CmdLine, makeCmdLine("", ">thud 2>&1")); +} + +TEST(Entropic, UpdateFrequency) { + const size_t One = 1, Two = 2; + const size_t FeatIdx1 = 0, FeatIdx2 = 42, FeatIdx3 = 12, FeatIdx4 = 26; + size_t Index; + // Create input corpus with default entropic configuration + struct EntropicOptions Entropic = {true, 0xFF, 100, false}; + std::unique_ptr C(new InputCorpus("", Entropic)); + std::unique_ptr II(new InputInfo()); + + C->AddRareFeature(FeatIdx1); + C->UpdateFeatureFrequency(II.get(), FeatIdx1); + EXPECT_EQ(II->FeatureFreqs.size(), One); + C->AddRareFeature(FeatIdx2); + C->UpdateFeatureFrequency(II.get(), FeatIdx1); + C->UpdateFeatureFrequency(II.get(), FeatIdx2); + EXPECT_EQ(II->FeatureFreqs.size(), Two); + EXPECT_EQ(II->FeatureFreqs[0].second, 2); + EXPECT_EQ(II->FeatureFreqs[1].second, 1); + + C->AddRareFeature(FeatIdx3); + C->AddRareFeature(FeatIdx4); + C->UpdateFeatureFrequency(II.get(), FeatIdx3); + C->UpdateFeatureFrequency(II.get(), FeatIdx3); + C->UpdateFeatureFrequency(II.get(), FeatIdx3); + C->UpdateFeatureFrequency(II.get(), FeatIdx4); + + for (Index = 1; Index < II->FeatureFreqs.size(); Index++) + EXPECT_LT(II->FeatureFreqs[Index - 1].first, II->FeatureFreqs[Index].first); + + II->DeleteFeatureFreq(FeatIdx3); + for (Index = 1; Index < II->FeatureFreqs.size(); Index++) + EXPECT_LT(II->FeatureFreqs[Index - 1].first, II->FeatureFreqs[Index].first); +} + +double SubAndSquare(double X, double Y) { + double R = X - Y; + R = R * R; + return R; +} + +TEST(Entropic, ComputeEnergy) { + const double Precision = 0.01; + struct EntropicOptions Entropic = {true, 0xFF, 100, false}; + std::unique_ptr C(new InputCorpus("", Entropic)); + std::unique_ptr II(new InputInfo()); + Vector> FeatureFreqs = {{1, 3}, {2, 3}, {3, 3}}; + II->FeatureFreqs = FeatureFreqs; + II->NumExecutedMutations = 0; + II->UpdateEnergy(4, false, std::chrono::microseconds(0)); + EXPECT_LT(SubAndSquare(II->Energy, 1.450805), Precision); + + II->NumExecutedMutations = 9; + II->UpdateEnergy(5, false, std::chrono::microseconds(0)); + EXPECT_LT(SubAndSquare(II->Energy, 1.525496), Precision); + + II->FeatureFreqs[0].second++; + II->FeatureFreqs.push_back(std::pair(42, 6)); + II->NumExecutedMutations = 20; + II->UpdateEnergy(10, false, std::chrono::microseconds(0)); + EXPECT_LT(SubAndSquare(II->Energy, 1.792831), Precision); +} + +int main(int argc, char **argv) { + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/core/lib/libfuzzer-sys/rust-toolchain b/core/lib/libfuzzer-sys/rust-toolchain new file mode 100644 index 0000000000..bf867e0ae5 --- /dev/null +++ b/core/lib/libfuzzer-sys/rust-toolchain @@ -0,0 +1 @@ +nightly diff --git a/core/lib/libfuzzer-sys/src/lib.rs b/core/lib/libfuzzer-sys/src/lib.rs new file mode 100644 index 0000000000..6badc391f3 --- /dev/null +++ b/core/lib/libfuzzer-sys/src/lib.rs @@ -0,0 +1,426 @@ +//! Bindings to [libFuzzer](http://llvm.org/docs/LibFuzzer.html): a runtime for +//! coverage-guided fuzzing. +//! +//! See [the `cargo-fuzz` +//! guide](https://rust-fuzz.github.io/book/cargo-fuzz.html) for a usage +//! tutorial. +//! +//! The main export of this crate is [the `fuzz_target!` +//! macro](./macro.fuzz_target.html), which allows you to define targets for +//! libFuzzer to exercise. + +#![deny(missing_docs, missing_debug_implementations)] + +pub use arbitrary; +use once_cell::sync::OnceCell; + +extern "C" { + // We do not actually cross the FFI bound here. + #[allow(improper_ctypes)] + fn rust_fuzzer_test_input(input: &[u8]); + + fn LLVMFuzzerMutate(data: *mut u8, size: usize, max_size: usize) -> usize; +} + +#[doc(hidden)] +#[export_name = "LLVMFuzzerTestOneInput"] +pub fn test_input_wrap(data: *const u8, size: usize) -> i32 { + let test_input = ::std::panic::catch_unwind(|| unsafe { + let data_slice = ::std::slice::from_raw_parts(data, size); + rust_fuzzer_test_input(data_slice); + }); + if test_input.err().is_some() { + // hopefully the custom panic hook will be called before and abort the + // process before the stack frames are unwinded. + ::std::process::abort(); + } + 0 +} + +#[doc(hidden)] +pub static RUST_LIBFUZZER_DEBUG_PATH: OnceCell = OnceCell::new(); + +#[doc(hidden)] +#[export_name = "LLVMFuzzerInitialize"] +pub fn initialize(_argc: *const isize, _argv: *const *const *const u8) -> isize { + // Registers a panic hook that aborts the process before unwinding. + // It is useful to abort before unwinding so that the fuzzer will then be + // able to analyse the process stack frames to tell different bugs appart. + // + // HACK / FIXME: it would be better to use `-C panic=abort` but it's currently + // impossible to build code using compiler plugins with this flag. + // We will be able to remove this code when + // https://github.com/rust-lang/cargo/issues/5423 is fixed. + let default_hook = ::std::panic::take_hook(); + ::std::panic::set_hook(Box::new(move |panic_info| { + default_hook(panic_info); + ::std::process::abort(); + })); + + // Initialize the `RUST_LIBFUZZER_DEBUG_PATH` cell with the path so it can be + // reused with little overhead. + if let Ok(path) = std::env::var("RUST_LIBFUZZER_DEBUG_PATH") { + RUST_LIBFUZZER_DEBUG_PATH + .set(path) + .expect("Since this is initialize it is only called once so can never fail"); + } + 0 +} + +/// Define a fuzz target. +/// +/// ## Example +/// +/// This example takes a `&[u8]` slice and attempts to parse it. The parsing +/// might fail and return an `Err`, but it shouldn't ever panic or segfault. +/// +/// ```no_run +/// #![no_main] +/// +/// use libfuzzer_sys::fuzz_target; +/// +/// // Note: `|input|` is short for `|input: &[u8]|`. +/// fuzz_target!(|input| { +/// let _result: Result<_, _> = my_crate::parse(input); +/// }); +/// # mod my_crate { pub fn parse(_: &[u8]) -> Result<(), ()> { unimplemented!() } } +/// ``` +/// +/// ## Arbitrary Input Types +/// +/// The input is a `&[u8]` slice by default, but you can take arbitrary input +/// types, as long as the type implements [the `arbitrary` crate's `Arbitrary` +/// trait](https://docs.rs/arbitrary/*/arbitrary/trait.Arbitrary.html) (which is +/// also re-exported as `libfuzzer_sys::arbitrary::Arbitrary` for convenience). +/// +/// For example, if you wanted to take an arbitrary RGB color, you could do the +/// following: +/// +/// ```no_run +/// #![no_main] +/// # mod foo { +/// +/// use libfuzzer_sys::{arbitrary::{Arbitrary, Error, Unstructured}, fuzz_target}; +/// +/// #[derive(Debug)] +/// pub struct Rgb { +/// r: u8, +/// g: u8, +/// b: u8, +/// } +/// +/// impl<'a> Arbitrary<'a> for Rgb { +/// fn arbitrary(raw: &mut Unstructured<'a>) -> Result { +/// let mut buf = [0; 3]; +/// raw.fill_buffer(&mut buf)?; +/// let r = buf[0]; +/// let g = buf[1]; +/// let b = buf[2]; +/// Ok(Rgb { r, g, b }) +/// } +/// } +/// +/// // Write a fuzz target that works with RGB colors instead of raw bytes. +/// fuzz_target!(|color: Rgb| { +/// my_crate::convert_color(color); +/// }); +/// # mod my_crate { +/// # use super::Rgb; +/// # pub fn convert_color(_: Rgb) {} +/// # } +/// # } +/// ``` +/// +/// You can also enable the `arbitrary` crate's custom derive via this crate's +/// `"arbitrary-derive"` cargo feature. +#[macro_export] +macro_rules! fuzz_target { + (|$bytes:ident| $body:block) => { + /// Auto-generated function + #[no_mangle] + pub extern "C" fn rust_fuzzer_test_input($bytes: &[u8]) { + // When `RUST_LIBFUZZER_DEBUG_PATH` is set, write the debug + // formatting of the input to that file. This is only intended for + // `cargo fuzz`'s use! + + // `RUST_LIBFUZZER_DEBUG_PATH` is set in initialization. + if let Some(path) = $crate::RUST_LIBFUZZER_DEBUG_PATH.get() { + use std::io::Write; + let mut file = std::fs::File::create(path) + .expect("failed to create `RUST_LIBFUZZER_DEBUG_PATH` file"); + writeln!(&mut file, "{:?}", $bytes) + .expect("failed to write to `RUST_LIBFUZZER_DEBUG_PATH` file"); + return; + } + + $body + } + }; + + (|$data:ident: &[u8]| $body:block) => { + fuzz_target!(|$data| $body); + }; + + (|$data:ident: $dty: ty| $body:block) => { + /// Auto-generated function + #[no_mangle] + pub extern "C" fn rust_fuzzer_test_input(bytes: &[u8]) { + use $crate::arbitrary::{Arbitrary, Unstructured}; + + // Early exit if we don't have enough bytes for the `Arbitrary` + // implementation. This helps the fuzzer avoid exploring all the + // different not-enough-input-bytes paths inside the `Arbitrary` + // implementation. Additionally, it exits faster, letting the fuzzer + // get to longer inputs that actually lead to interesting executions + // quicker. + if bytes.len() < <$dty as Arbitrary>::size_hint(0).0 { + return; + } + + let mut u = Unstructured::new(bytes); + let data = <$dty as Arbitrary>::arbitrary_take_rest(u); + + // When `RUST_LIBFUZZER_DEBUG_PATH` is set, write the debug + // formatting of the input to that file. This is only intended for + // `cargo fuzz`'s use! + + // `RUST_LIBFUZZER_DEBUG_PATH` is set in initialization. + if let Some(path) = $crate::RUST_LIBFUZZER_DEBUG_PATH.get() { + use std::io::Write; + let mut file = std::fs::File::create(path) + .expect("failed to create `RUST_LIBFUZZER_DEBUG_PATH` file"); + (match data { + Ok(data) => writeln!(&mut file, "{:#?}", data), + Err(err) => writeln!(&mut file, "Arbitrary Error: {}", err), + }) + .expect("failed to write to `RUST_LIBFUZZER_DEBUG_PATH` file"); + return; + } + + let $data = match data { + Ok(d) => d, + Err(_) => return, + }; + + $body + } + }; +} + +/// Define a custom mutator. +/// +/// This is optional, and libFuzzer will use its own, default mutation strategy +/// if this is not provided. +/// +/// You might consider using a custom mutator when your fuzz target is very +/// particular about the shape of its input: +/// +/// * You want to fuzz "deeper" than just the parser. +/// * The input contains checksums that have to match the hash of some subset of +/// the data or else the whole thing is invalid, and therefore mutating any of +/// that subset means you need to recompute the checksums. +/// * Small random changes to the input buffer make it invalid. +/// +/// That is, a custom mutator is useful in similar situations where [a `T: +/// Arbitrary` input type](macro.fuzz_target.html#arbitrary-input-types) is +/// useful. Note that the two approaches are not mutually exclusive; you can use +/// whichever is easier for your problem domain or both! +/// +/// ## Implementation Contract +/// +/// The original, unmodified input is given in `data[..size]`. +/// +/// You must modify the data in place and return the new size. +/// +/// The new size should not be greater than `max_size`. If this is not the case, +/// then the `data` will be truncated to fit within `max_size`. Note that +/// `max_size < size` is possible when shrinking test cases. +/// +/// You must produce the same mutation given the same `seed`. Generally, when +/// choosing what kind of mutation to make or where to mutate, you should start +/// by creating a random number generator (RNG) that is seeded with the given +/// `seed` and then consult the RNG whenever making a decision: +/// +/// ```no_run +/// #![no_main] +/// +/// use rand::{rngs::StdRng, Rng, SeedableRng}; +/// +/// libfuzzer_sys::fuzz_mutator!(|data: &mut [u8], size: usize, max_size: usize, seed: u32| { +/// let mut rng = StdRng::seed_from_u64(seed as u64); +/// +/// # let first_mutation = |_, _, _, _| todo!(); +/// # let second_mutation = |_, _, _, _| todo!(); +/// # let third_mutation = |_, _, _, _| todo!(); +/// # let fourth_mutation = |_, _, _, _| todo!(); +/// // Choose which of our four supported kinds of mutations we want to make. +/// match rng.gen_range(0..4) { +/// 0 => first_mutation(rng, data, size, max_size), +/// 1 => second_mutation(rng, data, size, max_size), +/// 2 => third_mutation(rng, data, size, max_size), +/// 3 => fourth_mutation(rng, data, size, max_size), +/// _ => unreachable!() +/// } +/// }); +/// ``` +/// +/// ## Example: Compression +/// +/// Consider a simple fuzz target that takes compressed data as input, +/// decompresses it, and then asserts that the decompressed data doesn't begin +/// with "boom". It is difficult for `libFuzzer` (or any other fuzzer) to crash +/// this fuzz target because nearly all mutations it makes will invalidate the +/// compression format. Therefore, we use a custom mutator that decompresses the +/// raw input, mutates the decompressed data, and then recompresses it. This +/// allows `libFuzzer` to quickly discover crashing inputs. +/// +/// ```no_run +/// #![no_main] +/// +/// use flate2::{read::GzDecoder, write::GzEncoder, Compression}; +/// use libfuzzer_sys::{fuzz_mutator, fuzz_target}; +/// use std::io::{Read, Write}; +/// +/// fuzz_target!(|data: &[u8]| { +/// // Decompress the input data and crash if it starts with "boom". +/// if let Some(data) = decompress(data) { +/// if data.starts_with(b"boom") { +/// panic!(); +/// } +/// } +/// }); +/// +/// fuzz_mutator!( +/// |data: &mut [u8], size: usize, max_size: usize, _seed: u32| { +/// // Decompress the input data. If that fails, use a dummy value. +/// let mut decompressed = decompress(&data[..size]).unwrap_or_else(|| b"hi".to_vec()); +/// +/// // Mutate the decompressed data with `libFuzzer`'s default mutator. Make +/// // the `decompressed` vec's extra capacity available for insertion +/// // mutations via `resize`. +/// let len = decompressed.len(); +/// let cap = decompressed.capacity(); +/// decompressed.resize(cap, 0); +/// let new_decompressed_size = libfuzzer_sys::fuzzer_mutate(&mut decompressed, len, cap); +/// +/// // Recompress the mutated data. +/// let compressed = compress(&decompressed[..new_decompressed_size]); +/// +/// // Copy the recompressed mutated data into `data` and return the new size. +/// let new_size = std::cmp::min(max_size, compressed.len()); +/// data[..new_size].copy_from_slice(&compressed[..new_size]); +/// new_size +/// } +/// ); +/// +/// fn decompress(compressed_data: &[u8]) -> Option> { +/// let mut decoder = GzDecoder::new(compressed_data); +/// let mut decompressed = Vec::new(); +/// if decoder.read_to_end(&mut decompressed).is_ok() { +/// Some(decompressed) +/// } else { +/// None +/// } +/// } +/// +/// fn compress(data: &[u8]) -> Vec { +/// let mut encoder = GzEncoder::new(Vec::new(), Compression::default()); +/// encoder +/// .write_all(data) +/// .expect("writing into a vec is infallible"); +/// encoder.finish().expect("writing into a vec is infallible") +/// } +/// ``` +/// +/// This example is inspired by [a similar example from the official `libFuzzer` +/// docs](https://github.com/google/fuzzing/blob/master/docs/structure-aware-fuzzing.md#example-compression). +/// +/// ## More Example Ideas +/// +/// * A PNG custom mutator that decodes a PNG, mutates the image, and then +/// re-encodes the mutated image as a new PNG. +/// +/// * A [`serde`](https://serde.rs/) custom mutator that deserializes your +/// structure, mutates it, and then reserializes it. +/// +/// * A Wasm binary custom mutator that inserts, replaces, and removes a +/// bytecode instruction in a function's body. +/// +/// * An HTTP request custom mutator that inserts, replaces, and removes a +/// header from an HTTP request. +#[macro_export] +macro_rules! fuzz_mutator { + ( + | + $data:ident : &mut [u8] , + $size:ident : usize , + $max_size:ident : usize , + $seed:ident : u32 $(,)* + | + $body:block + ) => { + /// Auto-generated function. + #[export_name = "LLVMFuzzerCustomMutator"] + pub fn rust_fuzzer_custom_mutator( + $data: *mut u8, + $size: usize, + $max_size: usize, + $seed: std::os::raw::c_uint, + ) -> usize { + // Depending on if we are growing or shrinking the test case, `size` + // might be larger or smaller than `max_size`. The `data`'s capacity + // is the maximum of the two. + let len = std::cmp::max($max_size, $size); + let $data: &mut [u8] = unsafe { std::slice::from_raw_parts_mut($data, len) }; + + // `unsigned int` is generally a `u32`, but not on all targets. Do + // an infallible (and potentially lossy, but that's okay because it + // preserves determinism) conversion. + let $seed = $seed as u32; + + // Truncate the new size if it is larger than the max. + let new_size = { $body }; + std::cmp::min(new_size, $max_size) + } + }; +} + +/// The default `libFuzzer` mutator. +/// +/// You generally don't have to use this at all unless you're defining a +/// custom mutator with [the `fuzz_mutator!` macro][crate::fuzz_mutator]. +/// +/// Mutates `data[..size]` in place such that the mutated data is no larger than +/// `max_size` and returns the new size of the mutated data. +/// +/// To only allow shrinking mutations, make `max_size < size`. +/// +/// To additionally allow mutations that grow the size of the data, make +/// `max_size > size`. +/// +/// Both `size` and `max_size` must be less than or equal to `data.len()`. +/// +/// # Example +/// +/// ```no_run +/// // Create some data in a buffer. +/// let mut data = vec![0; 128]; +/// data[..b"hello".len()].copy_from_slice(b"hello"); +/// +/// // Ask `libFuzzer` to mutate the data. By setting `max_size` to our buffer's +/// // full length, we are allowing `libFuzzer` to perform mutations that grow +/// // the size of the data, such as insertions. +/// let size = b"hello".len(); +/// let max_size = data.len(); +/// let new_size = libfuzzer_sys::fuzzer_mutate(&mut data, size, max_size); +/// +/// // Get the mutated data out of the buffer. +/// let mutated_data = &data[..new_size]; +/// ``` +pub fn fuzzer_mutate(data: &mut [u8], size: usize, max_size: usize) -> usize { + assert!(size <= data.len()); + assert!(max_size <= data.len()); + let new_size = unsafe { LLVMFuzzerMutate(data.as_mut_ptr(), size, max_size) }; + assert!(new_size <= data.len()); + new_size +} diff --git a/core/lib/libfuzzer-sys/update-libfuzzer.sh b/core/lib/libfuzzer-sys/update-libfuzzer.sh new file mode 100755 index 0000000000..57944c4632 --- /dev/null +++ b/core/lib/libfuzzer-sys/update-libfuzzer.sh @@ -0,0 +1,20 @@ +#!/bin/bash -ex + +# Usage: +# +# ./update-libfuzzer $commit_hash +# +# Where `$commit_hash` is a commit hash from +# https://github.com/llvm-mirror/llvm-project + +set -ex + +cd "$(dirname $0)" +project_dir="$(pwd)" + +tmp_dir="$(mktemp -d)" + +git clone -b "$1" --single-branch https://github.com/llvm/llvm-project.git "$tmp_dir" \ +&& mv "$project_dir/libfuzzer" "$project_dir/libfuzzer.$(date +%Y%m%d%H%M%S)" \ +&& mv "$tmp_dir/compiler-rt/lib/fuzzer" "$project_dir/libfuzzer" +[ -d "$tmp_dir" ] && rm -rf "$tmp_dir" diff --git a/core/lib/src/config/config.rs b/core/lib/src/config/config.rs index bc386d178a..2d772c8b60 100644 --- a/core/lib/src/config/config.rs +++ b/core/lib/src/config/config.rs @@ -70,6 +70,8 @@ pub struct Config { pub port: u16, /// Number of threads to use for executing futures. **(default: `num_cores`)** pub workers: usize, + /// Number of threads to use for executing blocking futures. **(default: `512`)** + pub blocking_workers: usize, /// How, if at all, to identify the server via the `Server` header. /// **(default: `"Rocket"`)** pub ident: Ident, @@ -167,6 +169,7 @@ impl Config { address: Ipv4Addr::new(127, 0, 0, 1).into(), port: 8000, workers: num_cpus::get(), + blocking_workers: 512, ident: Ident::default(), limits: Limits::default(), temp_dir: std::env::temp_dir().into(), @@ -355,6 +358,7 @@ impl Config { launch_info_!("address: {}", bold(&self.address)); launch_info_!("port: {}", bold(&self.port)); launch_info_!("workers: {}", bold(self.workers)); + launch_info_!("blocking_workers: {}", bold(self.blocking_workers)); launch_info_!("ident: {}", bold(&self.ident)); launch_info_!("limits: {}", bold(&self.limits)); launch_info_!("temp dir: {}", bold(&self.temp_dir.relative().display())); @@ -447,6 +451,9 @@ impl Config { /// The stringy parameter name for setting/extracting [`Config::workers`]. pub const WORKERS: &'static str = "workers"; + /// The stringy parameter name for setting/extracting [`Config::blocking_workers`]. + pub const BLOCKING_WORKERS: &'static str = "blocking_workers"; + /// The stringy parameter name for setting/extracting [`Config::keep_alive`]. pub const KEEP_ALIVE: &'static str = "keep_alive"; diff --git a/core/lib/src/config/tls.rs b/core/lib/src/config/tls.rs index 3c833a0a93..4700d1cc45 100644 --- a/core/lib/src/config/tls.rs +++ b/core/lib/src/config/tls.rs @@ -1,6 +1,6 @@ use figment::value::magic::{Either, RelativePathBuf}; -use serde::{Deserialize, Serialize}; use indexmap::IndexSet; +use serde::{Deserialize, Serialize}; /// TLS configuration: certificate chain, key, and ciphersuites. /// @@ -211,7 +211,6 @@ impl CipherSuite { CipherSuite::TLS_CHACHA20_POLY1305_SHA256, CipherSuite::TLS_AES_256_GCM_SHA384, CipherSuite::TLS_AES_128_GCM_SHA256, - // TLS v1.2 suites... CipherSuite::TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, CipherSuite::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, @@ -270,7 +269,9 @@ impl TlsConfig { /// let tls_config = TlsConfig::from_paths("/ssl/certs.pem", "/ssl/key.pem"); /// ``` pub fn from_paths(certs: C, key: K) -> Self - where C: AsRef, K: AsRef + where + C: AsRef, + K: AsRef, { TlsConfig { certs: Either::Left(certs.as_ref().to_path_buf().into()), @@ -361,7 +362,8 @@ impl TlsConfig { /// ]); /// ``` pub fn with_ciphers(mut self, ciphers: I) -> Self - where I: IntoIterator + where + I: IntoIterator, { self.ciphers = ciphers.into_iter().collect(); self @@ -557,7 +559,7 @@ impl MutualTls { pub fn from_path>(ca_certs: C) -> Self { MutualTls { ca_certs: Either::Left(ca_certs.as_ref().to_path_buf().into()), - mandatory: Default::default() + mandatory: Default::default(), } } @@ -580,7 +582,7 @@ impl MutualTls { pub fn from_bytes(ca_certs: &[u8]) -> Self { MutualTls { ca_certs: Either::Right(ca_certs.to_vec()), - mandatory: Default::default() + mandatory: Default::default(), } } @@ -628,13 +630,13 @@ mod with_tls_feature { use std::fs; use std::io::{self, Error}; - use crate::http::tls::Config; + use crate::http::tls::rustls::cipher_suite as rustls; use crate::http::tls::rustls::SupportedCipherSuite as RustlsCipher; - use crate::http::tls::rustls::ciphersuite as rustls; + use crate::http::tls::Config; use yansi::Paint; - use super::{Either, RelativePathBuf, TlsConfig, CipherSuite}; + use super::{CipherSuite, Either, RelativePathBuf, TlsConfig}; type Reader = Box; @@ -643,8 +645,14 @@ mod with_tls_feature { Either::Left(path) => { let path = path.relative(); let file = fs::File::open(&path).map_err(move |e| { - Error::new(e.kind(), format!("error reading TLS file `{}`: {}", - Paint::white(figment::Source::File(path)), e)) + Error::new( + e.kind(), + format!( + "error reading TLS file `{}`: {}", + Paint::white(figment::Source::File(path)), + e + ), + ) })?; Ok(Box::new(io::BufReader::new(file))) @@ -670,31 +678,36 @@ mod with_tls_feature { #[cfg(feature = "mtls")] ca_certs: match self.mutual { Some(ref mtls) => Some(to_reader(&mtls.ca_certs)?), - None => None + None => None, }, }) } fn rustls_ciphers(&self) -> impl Iterator + '_ { self.ciphers().map(|ciphersuite| match ciphersuite { - CipherSuite::TLS_CHACHA20_POLY1305_SHA256 => - &rustls::TLS13_CHACHA20_POLY1305_SHA256, - CipherSuite::TLS_AES_256_GCM_SHA384 => - &rustls::TLS13_AES_256_GCM_SHA384, - CipherSuite::TLS_AES_128_GCM_SHA256 => - &rustls::TLS13_AES_128_GCM_SHA256, - CipherSuite::TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 => - &rustls::TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, - CipherSuite::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 => - &rustls::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, - CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 => - &rustls::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, - CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 => - &rustls::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, - CipherSuite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 => - &rustls::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, - CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 => - &rustls::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + CipherSuite::TLS_CHACHA20_POLY1305_SHA256 => { + &rustls::TLS13_CHACHA20_POLY1305_SHA256 + } + CipherSuite::TLS_AES_256_GCM_SHA384 => &rustls::TLS13_AES_256_GCM_SHA384, + CipherSuite::TLS_AES_128_GCM_SHA256 => &rustls::TLS13_AES_128_GCM_SHA256, + CipherSuite::TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 => { + &rustls::TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 + } + CipherSuite::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 => { + &rustls::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 + } + CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 => { + &rustls::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 + } + CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 => { + &rustls::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 + } + CipherSuite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 => { + &rustls::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 + } + CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 => { + &rustls::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 + } }) } } diff --git a/core/lib/src/cookies.rs b/core/lib/src/cookies.rs index 42ce00fb6d..78857ac8a0 100644 --- a/core/lib/src/cookies.rs +++ b/core/lib/src/cookies.rs @@ -197,7 +197,7 @@ impl<'a> CookieJar<'a> { /// Returns a reference to the _original_ `Cookie` inside this container /// with the name `name`. If no such cookie exists, returns `None`. /// - /// **Note:** This method _does not_ obverse changes made via additions and + /// **Note:** This method _does not_ observe changes made via additions and /// removals to the cookie jar. To observe those changes, use /// [`CookieJar::get_pending()`]. /// @@ -216,12 +216,12 @@ impl<'a> CookieJar<'a> { self.jar.get(name) } - /// Retrives the _original_ `Cookie` inside this collection with the name + /// Retrieves the _original_ `Cookie` inside this collection with the name /// `name` and authenticates and decrypts the cookie's value. If the cookie /// cannot be found, or the cookie fails to authenticate or decrypt, `None` /// is returned. /// - /// **Note:** This method _does not_ obverse changes made via additions and + /// **Note:** This method _does not_ observe changes made via additions and /// removals to the cookie jar. To observe those changes, use /// [`CookieJar::get_private_pending()`]. /// @@ -270,7 +270,7 @@ impl<'a> CookieJar<'a> { self.get(name).cloned() } - /// Retrives the _original or pending_ `Cookie` inside this collection with + /// Retrieves the _original or pending_ `Cookie` inside this collection with /// the name `name` and authenticates and decrypts the cookie's value. If /// the cookie cannot be found, or the cookie fails to authenticate or /// decrypt, `None` is returned. @@ -290,7 +290,9 @@ impl<'a> CookieJar<'a> { #[cfg_attr(nightly, doc(cfg(feature = "secrets")))] pub fn get_private_pending(&self, name: &str) -> Option> { let cookie = self.get_pending(name)?; - self.jar.private(&self.config.secret_key.key).decrypt(cookie) + // no operation on cookie occured, which happens before server responses + // so no decryption required and possible + Some(cookie) } /// Adds `cookie` to this collection. @@ -419,7 +421,7 @@ impl<'a> CookieJar<'a> { /// Returns an iterator over all of the _original_ cookies present in this /// collection. /// - /// **Note:** This method _does not_ obverse changes made via additions and + /// **Note:** This method _does not_ observe changes made via additions and /// removals to the cookie jar. /// /// # Example diff --git a/core/lib/src/ext.rs b/core/lib/src/ext.rs index 0433c6984f..f3bd954861 100644 --- a/core/lib/src/ext.rs +++ b/core/lib/src/ext.rs @@ -10,6 +10,10 @@ use tokio::time::{sleep, Sleep}; use futures::stream::Stream; use futures::future::{self, Future, FutureExt}; +use crate::http::private::{Listener, Connection}; +#[cfg(feature = "tls")] +use crate::http::private::RawCertificate; + pin_project! { pub struct ReaderStream { #[pin] @@ -291,13 +295,12 @@ impl AsyncWrite for CancellableIo { } } -use crate::http::private::{Listener, Connection, RawCertificate}; - impl Connection for CancellableIo { fn peer_address(&self) -> Option { self.io.peer_address() } + #[cfg(feature = "tls")] fn peer_certificates(&self) -> Option> { self.io.peer_certificates() } diff --git a/core/lib/src/lib.rs b/core/lib/src/lib.rs index 1479a40fff..0f2566de14 100644 --- a/core/lib/src/lib.rs +++ b/core/lib/src/lib.rs @@ -29,7 +29,7 @@ //! //! ```toml //! [dependencies] -//! rocket = "0.5.0-rc.1" +//! rocket = "0.5.0-rc.2" //! ``` //! //! Note that development versions, tagged with `-dev`, are not published @@ -59,20 +59,21 @@ //! To avoid compiling unused dependencies, Rocket gates certain features, all //! of which are disabled by default: //! -//! | Feature | Description | -//! |-----------|---------------------------------------------------------| -//! | `secrets` | Support for authenticated, encrypted [private cookies]. | -//! | `tls` | Support for [TLS] encrypted connections. | -//! | `mtls` | Support for verified clients via [mutual TLS]. | -//! | `json` | Support for [JSON (de)serialization]. | -//! | `msgpack` | Support for [MessagePack (de)serialization]. | -//! | `uuid` | Support for [UUID value parsing and (de)serialization]. | +//! | Feature | Description | +//! |-------------------|---------------------------------------------------------| +//! | `secrets` | Support for authenticated, encrypted [private cookies]. | +//! | `tls` | Support for [TLS] encrypted connections. | +//! | `mtls` | Support for verified clients via [mutual TLS]. | +//! | `json` | Support for [JSON (de)serialization]. | +//! | `msgpack` | Support for [MessagePack (de)serialization]. | +//! | `msgpack-compact` | Support for compact [MessagePack (de)serialization]. | +//! | `uuid` | Support for [UUID value parsing and (de)serialization]. | //! //! Features can be selectively enabled in `Cargo.toml`: //! //! ```toml //! [dependencies] -//! rocket = { version = "0.5.0-rc.1", features = ["secrets", "tls", "json"] } +//! rocket = { version = "0.5.0-rc.2", features = ["secrets", "tls", "json"] } //! ``` //! //! [JSON (de)serialization]: crate::serde::json @@ -232,6 +233,7 @@ pub fn async_main(fut: impl std::future::Future + Send) -> R { // See tokio-rs/tokio#3329 for a necessary solution in `tokio`. tokio::runtime::Builder::new_multi_thread() .worker_threads(Config::from(Config::figment()).workers) + .max_blocking_threads(Config::from(Config::figment()).blocking_workers) // NOTE: graceful shutdown depends on the "rocket-worker" prefix. .thread_name("rocket-worker-thread") .enable_all() diff --git a/core/lib/src/log.rs b/core/lib/src/log.rs index ab501fdbbb..ce7864e829 100644 --- a/core/lib/src/log.rs +++ b/core/lib/src/log.rs @@ -1,6 +1,7 @@ //! Rocket's logging infrastructure. use std::fmt; +use std::io::{self, Write}; use std::str::FromStr; use std::sync::atomic::{AtomicBool, Ordering}; @@ -32,8 +33,33 @@ define_log_macro!(warn, warn_); define_log_macro!(info, info_); define_log_macro!(debug, debug_); define_log_macro!(trace, trace_); -define_log_macro!(launch_info: warn, "rocket::launch", $); -define_log_macro!(launch_info_: warn, "rocket::launch_", $); +define_log_macro!(launch_info: info, "rocket::launch", $); +define_log_macro!(launch_info_: info, "rocket::launch_", $); + +// print macro panics on not available stdout, this shouldn't +// https://github.com/SergioBenitez/Rocket/issues/2019 +macro_rules! print_no_panic { + ($($arg:tt)*) => ({ + write!(io::stdout(), "{}", format_args!($($arg)*)) + .unwrap_or_else(|e| { + // fire and forget now + let _ = io::stdout().write(e.to_string().as_bytes()); + }); + }) +} + +// println macro panics on not available stdout, this shouldn't +// https://github.com/SergioBenitez/Rocket/issues/2019 +macro_rules! println_no_panic { + () => (print_no_panic!("\n")); + ($($arg:tt)*) => ({ + write!(io::stdout(), "{}\n", format_args!($($arg)*)) + .unwrap_or_else(|e| { + // fire and forget now + let _ = io::stdout().write(e.to_string().as_bytes()); + }); + }) +} #[derive(Debug)] struct RocketLogger; @@ -86,7 +112,7 @@ impl log::Log for RocketLogger { // In Rocket, we abuse targets with suffix "_" to indicate indentation. let indented = record.target().ends_with('_'); if indented { - print!(" {} ", Paint::default(">>").bold()); + print_no_panic!(" {} ", Paint::default(">>").bold()); } // Downgrade a physical launch `warn` to logical `info`. @@ -96,30 +122,34 @@ impl log::Log for RocketLogger { match level { log::Level::Error if !indented => { - println!("{} {}", - Paint::red("Error:").bold(), - Paint::red(record.args()).wrap()) + println_no_panic!( + "{} {}", + Paint::red("Error:").bold(), + Paint::red(record.args()).wrap() + ); } log::Level::Warn if !indented => { - println!("{} {}", - Paint::yellow("Warning:").bold(), - Paint::yellow(record.args()).wrap()) + println_no_panic!( + "{} {}", + Paint::yellow("Warning:").bold(), + Paint::yellow(record.args()).wrap() + ); } - log::Level::Info => println!("{}", Paint::blue(record.args()).wrap()), - log::Level::Trace => println!("{}", Paint::magenta(record.args()).wrap()), - log::Level::Warn => println!("{}", Paint::yellow(record.args()).wrap()), - log::Level::Error => println!("{}", Paint::red(record.args()).wrap()), + log::Level::Info => println_no_panic!("{}", Paint::blue(record.args()).wrap()), + log::Level::Trace => println_no_panic!("{}", Paint::magenta(record.args()).wrap()), + log::Level::Warn => println_no_panic!("{}", Paint::yellow(record.args()).wrap()), + log::Level::Error => println_no_panic!("{}", Paint::red(record.args()).wrap()), log::Level::Debug => { - print!("\n{} ", Paint::blue("-->").bold()); + print_no_panic!("\n{} ", Paint::blue("-->").bold()); if let Some(file) = record.file() { - print!("{}", Paint::blue(file)); + print_no_panic!("{}", Paint::blue(file)); } if let Some(line) = record.line() { - println!(":{}", Paint::blue(line)); + println_no_panic!(":{}", Paint::blue(line)); } - println!("\t{}", record.args()); + println_no_panic!("\t{}", record.args()); } } } diff --git a/core/lib/src/request/request.rs b/core/lib/src/request/request.rs index c54169b34c..564b4ebcef 100644 --- a/core/lib/src/request/request.rs +++ b/core/lib/src/request/request.rs @@ -16,6 +16,7 @@ use crate::data::Limits; use crate::http::{hyper, Method, Header, HeaderMap}; use crate::http::{ContentType, Accept, MediaType, CookieJar, Cookie}; use crate::http::uncased::UncasedStr; +#[cfg(feature = "tls")] use crate::http::private::RawCertificate; use crate::http::uri::{fmt::Path, Origin, Segments, Host, Authority}; @@ -37,6 +38,7 @@ pub struct Request<'r> { #[derive(Clone)] pub(crate) struct ConnectionMeta { pub remote: Option, + #[cfg(feature = "tls")] pub client_certificates: Option>>, } @@ -91,6 +93,7 @@ impl<'r> Request<'r> { headers: HeaderMap::new(), connection: ConnectionMeta { remote: None, + #[cfg(feature = "tls")] client_certificates: None, }, state: RequestState { @@ -713,10 +716,11 @@ impl<'r> Request<'r> { /// # let request = c.get("/"); /// // The first store into local cache for a given type wins. /// let value = request.local_cache(|| "hello"); - /// assert_eq!(*request.local_cache(|| "hello"), "hello"); + /// assert_eq!(value, &"hello"); + /// assert_eq!(request.local_cache(|| "hello"), &"hello"); /// /// // The following return the cached, previously stored value for the type. - /// assert_eq!(*request.local_cache(|| "goodbye"), "hello"); + /// assert_eq!(request.local_cache(|| "goodbye"), &"hello"); /// ``` #[inline] pub fn local_cache(&self, f: F) -> &T @@ -766,6 +770,40 @@ impl<'r> Request<'r> { } } + /// Retrieves the cached value for type `T` from the request-local cached + /// state of `self`. If no such value has previously been cached for this + /// request, `None` is returned. + /// + /// Different values of the same type _cannot_ be cached without using a + /// proxy, wrapper type. + /// + /// # Example + /// + /// ```rust + /// # let c = rocket::local::blocking::Client::debug_with(vec![]).unwrap(); + /// # let request = c.get("/"); + /// // Without cached value it is None + /// let value: Option<&String> = request.try_local_cache(); + /// assert_eq!(value, None); + /// // The first store into local cache for a given type wins. + /// let value = request.local_cache(|| String::from("hello")); + /// assert_eq!(value.as_str(), "hello"); + /// + /// assert_eq!(request.try_local_cache(), Some(&String::from("hello"))); + /// + /// // The following return the cached, previously stored value for the type. + /// assert_eq!(request.local_cache(|| String::from("goodbye")).as_str(), "hello"); + /// // This is also the first cached value + /// assert_eq!(request.try_local_cache(), Some(&String::from("hello"))); + /// ``` + #[inline] + pub fn try_local_cache(&self) -> Option<&T> + where + T: Send + Sync + 'static, + { + self.state.cache.try_get() + } + /// Retrieves and parses into `T` the 0-indexed `n`th non-empty segment from /// the _routed_ request, that is, the `n`th segment _after_ the mount /// point. If the request has not been routed, then this is simply the `n`th diff --git a/core/lib/src/response/stream/sse.rs b/core/lib/src/response/stream/sse.rs index c2c397e563..897522d52c 100644 --- a/core/lib/src/response/stream/sse.rs +++ b/core/lib/src/response/stream/sse.rs @@ -1,4 +1,3 @@ -use std::array; use std::borrow::Cow; use tokio::io::AsyncRead; @@ -337,7 +336,7 @@ impl Event { Some(RawLinedEvent::raw("")), ]; - stream::iter(array::IntoIter::new(events)).filter_map(ready) + stream::iter(IntoIterator::into_iter(events)).filter_map(ready) } } @@ -790,17 +789,23 @@ mod sse_tests { assert!(string.contains("data:foo\n\n")); assert!(string.contains("data:bar\n\n")); + // The heartbeat comment races with the data in the events on the tests below. + // So testing for part of event because there could be a heartbeat comment + // before or after the data. + // We shouldn't send a heartbeat if a message is immediately available. let stream = EventStream::from(once(ready(Event::data("hello")))); let string = stream.heartbeat(Duration::from_secs(1)).into_string(); - assert_eq!(string, "data:hello\n\n"); + let heartbeats = string.matches(HEARTBEAT).count(); + assert!(heartbeats <= 1); + assert!(string.contains("data:hello\n"), "string: {}", string.as_str()); // It's okay if we do it with two, though. let stream = EventStream::from(iter(vec![Event::data("a"), Event::data("b")])); let string = stream.heartbeat(Duration::from_secs(1)).into_string(); let heartbeats = string.matches(HEARTBEAT).count(); assert!(heartbeats <= 1); - assert!(string.contains("data:a\n\n")); - assert!(string.contains("data:b\n\n")); + assert!(string.contains("data:a\n"), "string: {}", string.as_str()); + assert!(string.contains("data:b\n"), "string: {}", string.as_str()); } } diff --git a/core/lib/src/route/uri.rs b/core/lib/src/route/uri.rs index b3d1900e49..d111b473d2 100644 --- a/core/lib/src/route/uri.rs +++ b/core/lib/src/route/uri.rs @@ -77,13 +77,14 @@ pub(crate) enum Color { Wild = 1, } +#[allow(dead_code)] // reason = TODO: maybe there will come some usage #[derive(Debug, Clone)] pub(crate) struct Metadata { /// Segments in the base. pub base_segs: Vec, /// Segments in the path, including base. pub path_segs: Vec, - /// Segments in the query. + /// Segments in the query. TODO: remove it if there is no purpose coming. pub query_segs: Vec, /// `(name, value)` of the query segments that are static. pub static_query_fields: Vec<(String, String)>, diff --git a/core/lib/src/serde/json.rs b/core/lib/src/serde/json.rs index 70ff50f007..8ce5f7f704 100644 --- a/core/lib/src/serde/json.rs +++ b/core/lib/src/serde/json.rs @@ -9,7 +9,7 @@ //! //! ```toml //! [dependencies.rocket] -//! version = "0.5.0-rc.1" +//! version = "0.5.0-rc.2" //! features = ["json"] //! ``` //! diff --git a/core/lib/src/serde/msgpack.rs b/core/lib/src/serde/msgpack.rs index 188041b967..d6025ef03f 100644 --- a/core/lib/src/serde/msgpack.rs +++ b/core/lib/src/serde/msgpack.rs @@ -4,15 +4,26 @@ //! //! # Enabling //! -//! This module is only available when the `json` feature is enabled. Enable it +//! This module is only available when the `msgpack` feature is enabled. Enable it //! in `Cargo.toml` as follows: //! //! ```toml //! [dependencies.rocket] -//! version = "0.5.0-rc.1" +//! version = "0.5.0-rc.2" //! features = ["msgpack"] //! ``` //! +//! If you prefer a compact format of `MsgPack` in your `Response`, +//! this won't have any field description and your client needs to know +//! before about the data structure, +//! use feature `msgpack-compact`: +//! +//! ```toml +//! [dependencies.rocket] +//! version = "0.5.0-rc.2" +//! features = ["msgpack-compact"] +//! ``` +//! //! # Testing //! //! The [`LocalRequest`] and [`LocalResponse`] types provide [`msgpack()`] and @@ -27,14 +38,14 @@ use std::io; use std::ops::{Deref, DerefMut}; -use crate::request::{Request, local_cache}; -use crate::data::{Limits, Data, FromData, Outcome}; -use crate::response::{self, Responder, content}; -use crate::http::Status; +use crate::data::{Data, FromData, Limits, Outcome}; use crate::form::prelude as form; +use crate::http::Status; +use crate::request::{local_cache, Request}; +use crate::response::{self, content, Responder}; // use crate::http::uri::fmt; -use serde::{Serialize, Deserialize}; +use serde::{Deserialize, Serialize}; #[doc(inline)] pub use rmp_serde::decode::Error; @@ -153,8 +164,11 @@ impl<'r, T: Deserialize<'r>> MsgPack { Ok(buf) if buf.is_complete() => buf.into_inner(), Ok(_) => { let eof = io::ErrorKind::UnexpectedEof; - return Err(Error::InvalidDataRead(io::Error::new(eof, "data limit exceeded"))); - }, + return Err(Error::InvalidDataRead(io::Error::new( + eof, + "data limit exceeded", + ))); + } Err(e) => return Err(Error::InvalidDataRead(e)), }; @@ -171,13 +185,12 @@ impl<'r, T: Deserialize<'r>> FromData<'r> for MsgPack { Ok(value) => Outcome::Success(value), Err(Error::InvalidDataRead(e)) if e.kind() == io::ErrorKind::UnexpectedEof => { Outcome::Failure((Status::PayloadTooLarge, Error::InvalidDataRead(e))) - }, - | Err(e@Error::TypeMismatch(_)) - | Err(e@Error::OutOfRange) - | Err(e@Error::LengthMismatch(_)) - => { + } + Err(e @ Error::TypeMismatch(_)) + | Err(e @ Error::OutOfRange) + | Err(e @ Error::LengthMismatch(_)) => { Outcome::Failure((Status::UnprocessableEntity, e)) - }, + } Err(e) => Outcome::Failure((Status::BadRequest, e)), } } @@ -188,11 +201,15 @@ impl<'r, T: Deserialize<'r>> FromData<'r> for MsgPack { /// serialization fails, an `Err` of `Status::InternalServerError` is returned. impl<'r, T: Serialize> Responder<'r, 'static> for MsgPack { fn respond_to(self, req: &'r Request<'_>) -> response::Result<'static> { - let buf = rmp_serde::to_vec(&self.0) - .map_err(|e| { - error_!("MsgPack failed to serialize: {:?}", e); - Status::InternalServerError - })?; + #[cfg(feature = "msgpack-compact")] + let res_vec = to_vec_compact(&self.0); + #[cfg(not(feature = "msgpack-compact"))] + let res_vec = to_vec_named(&self.0); + + let buf = res_vec.map_err(|e| { + error_!("MsgPack failed to serialize: {:?}", e); + Status::InternalServerError + })?; content::RawMsgPack(buf).respond_to(req) } @@ -204,13 +221,13 @@ impl<'v, T: Deserialize<'v> + Send> form::FromFormField<'v> for MsgPack { // decode it into bytes as opposed to a string as it won't be UTF-8. async fn from_data(f: form::DataField<'v, '_>) -> Result> { - Self::from_data(f.request, f.data).await.map_err(|e| { - match e { + Self::from_data(f.request, f.data) + .await + .map_err(|e| match e { Error::InvalidMarkerRead(e) | Error::InvalidDataRead(e) => e.into(), Error::Utf8Error(e) => e.into(), _ => form::Error::custom(e).into(), - } - }) + }) } } @@ -278,7 +295,8 @@ impl DerefMut for MsgPack { /// otherwise. #[inline(always)] pub fn from_slice<'a, T>(v: &'a [u8]) -> Result - where T: Deserialize<'a>, +where + T: Deserialize<'a>, { rmp_serde::from_read_ref(v) } @@ -289,12 +307,18 @@ pub fn from_slice<'a, T>(v: &'a [u8]) -> Result /// /// **_Always_ use [`MsgPack`] to serialize MessagePack response data.** /// +/// # Deprecated +/// +/// To match the provided API of crate rmp-serde and to prevent misunderstanding +/// between `msgpack::to_vec()` and `rmp_serde::to_vec()` there are new +/// functions `msgpack::to_vec_compact()` and `msgpack::to_vec_named()`. +/// /// # Example /// /// ``` -/// use rocket::serde::{Deserialize, Serialize, msgpack}; +/// use rocket::serde::{msgpack, Deserialize, Serialize}; /// -/// #[derive(Deserialize, Serialize)] +/// #[derive(Debug, Deserialize, PartialEq, Serialize)] /// #[serde(crate = "rocket::serde")] /// struct Data<'r> { /// framework: &'r str, @@ -305,14 +329,53 @@ pub fn from_slice<'a, T>(v: &'a [u8]) -> Result /// let data: Data = msgpack::from_slice(bytes).unwrap(); /// let byte_vec = msgpack::to_compact_vec(&data).unwrap(); /// assert_eq!(bytes, &byte_vec[..]); +/// assert_eq!(data, Data { framework: "Rocket", stars: 5 }); /// ``` /// /// # Errors /// /// Serialization fails if `T`'s `Serialize` implementation fails. +#[deprecated(since = "0.5.0-rc.2", note = "use to_vec_compact()")] #[inline(always)] pub fn to_compact_vec(value: &T) -> Result, rmp_serde::encode::Error> - where T: Serialize + ?Sized +where + T: Serialize + ?Sized, +{ + rmp_serde::to_vec(value) +} + +/// Serialize a `T` into a MessagePack byte vector with compact representation. +/// +/// The compact representation represents structs as arrays. +/// +/// **_Always_ use [`MsgPack`] to serialize MessagePack response data.** +/// +/// # Example +/// +/// ``` +/// use rocket::serde::{msgpack, Deserialize, Serialize}; +/// +/// #[derive(Debug, Deserialize, PartialEq, Serialize)] +/// #[serde(crate = "rocket::serde")] +/// struct Data<'r> { +/// framework: &'r str, +/// stars: usize, +/// } +/// +/// let bytes = &[146, 166, 82, 111, 99, 107, 101, 116, 5]; +/// let data: Data = msgpack::from_slice(bytes).unwrap(); +/// let byte_vec = msgpack::to_vec_compact(&data).unwrap(); +/// assert_eq!(bytes, &byte_vec[..]); +/// assert_eq!(data, Data { framework: "Rocket", stars: 5 }); +/// ``` +/// +/// # Errors +/// +/// Serialization fails if `T`'s `Serialize` implementation fails. +#[inline(always)] +pub fn to_vec_compact(value: &T) -> Result, rmp_serde::encode::Error> +where + T: Serialize + ?Sized, { rmp_serde::to_vec(value) } @@ -323,12 +386,18 @@ pub fn to_compact_vec(value: &T) -> Result, rmp_serde::encode::Error> /// /// **_Always_ use [`MsgPack`] to serialize MessagePack response data.** /// +/// # Deprecated +/// +/// To match the provided API of crate rmp-serde and to prevent misunderstanding +/// between `msgpack::to_vec()` and `rmp_serde::to_vec()` there are new +/// functions `msgpack::to_vec_compact()` and `msgpack::to_vec_named()`. +/// /// # Example /// /// ``` -/// use rocket::serde::{Deserialize, Serialize, msgpack}; +/// use rocket::serde::{msgpack, Deserialize, Serialize}; /// -/// #[derive(Deserialize, Serialize)] +/// #[derive(Debug, Deserialize, PartialEq, Serialize)] /// #[serde(crate = "rocket::serde")] /// struct Data<'r> { /// framework: &'r str, @@ -343,14 +412,57 @@ pub fn to_compact_vec(value: &T) -> Result, rmp_serde::encode::Error> /// let data: Data = msgpack::from_slice(bytes).unwrap(); /// let byte_vec = msgpack::to_vec(&data).unwrap(); /// assert_eq!(bytes, &byte_vec[..]); +/// assert_eq!(data, Data { framework: "Rocket", stars: 5 }); /// ``` /// /// # Errors /// /// Serialization fails if `T`'s `Serialize` implementation fails. +#[deprecated(since = "0.5.0-rc.2", note = "use to_vec_named()")] #[inline(always)] pub fn to_vec(value: &T) -> Result, rmp_serde::encode::Error> - where T: Serialize + ?Sized +where + T: Serialize + ?Sized, +{ + rmp_serde::to_vec_named(value) +} + +/// Serialize a `T` into a MessagePack byte vector with named representation. +/// +/// The named representation represents structs as maps with field names. +/// +/// **_Always_ use [`MsgPack`] to serialize MessagePack response data.** +/// +/// # Example +/// +/// ``` +/// use rocket::serde::{msgpack, Deserialize, Serialize}; +/// +/// #[derive(Debug, Deserialize, PartialEq, Serialize)] +/// #[serde(crate = "rocket::serde")] +/// struct Data<'r> { +/// framework: &'r str, +/// stars: usize, +/// } +/// +/// let bytes = &[ +/// 130, 169, 102, 114, 97, 109, 101, 119, 111, 114, 107, 166, 82, 111, +/// 99, 107, 101, 116, 165, 115, 116, 97, 114, 115, 5 +/// ]; +/// +/// let data: Data = msgpack::from_slice(bytes).unwrap(); +/// let byte_vec = msgpack::to_vec_named(&data).unwrap(); +/// assert_eq!(bytes, &byte_vec[..]); +/// assert_eq!(data, Data { framework: "Rocket", stars: 5 }); +/// ``` +/// +/// # Errors +/// +/// Serialization fails if `T`'s `Serialize` implementation fails. +#[inline(always)] +pub fn to_vec_named(value: &T) -> Result, rmp_serde::encode::Error> +where + T: Serialize + ?Sized, { rmp_serde::to_vec_named(value) } diff --git a/core/lib/src/serde/uuid.rs b/core/lib/src/serde/uuid.rs index 21e429d4d2..3e38681933 100644 --- a/core/lib/src/serde/uuid.rs +++ b/core/lib/src/serde/uuid.rs @@ -7,7 +7,7 @@ //! //! ```toml //! [dependencies.rocket] -//! version = "0.5.0-rc.1" +//! version = "0.5.0-rc.2" //! features = ["uuid"] //! ``` //! diff --git a/core/lib/src/server.rs b/core/lib/src/server.rs index ba2b78e9d8..4072143321 100644 --- a/core/lib/src/server.rs +++ b/core/lib/src/server.rs @@ -444,6 +444,7 @@ impl Rocket { let rocket = rocket.clone(); let connection = ConnectionMeta { remote: conn.peer_address(), + #[cfg(feature = "tls")] client_certificates: conn.peer_certificates().map(Arc::new), }; diff --git a/core/lib/src/shield/policy.rs b/core/lib/src/shield/policy.rs index fa2f72a362..ac94436b75 100644 --- a/core/lib/src/shield/policy.rs +++ b/core/lib/src/shield/policy.rs @@ -317,7 +317,10 @@ impl From<&Hsts> for Header<'static> { Hsts::IncludeSubDomains(age) => { format!("max-age={}; includeSubDomains", age.whole_seconds()) } - Hsts::Preload(age) => format!("max-age={}; preload", age.whole_seconds()), + Hsts::Preload(age) => format!( + "max-age={}; includeSubDomains; preload", + age.whole_seconds() + ), }; Header::new(Hsts::NAME, policy_string) diff --git a/core/lib/tests/cookies-private.rs b/core/lib/tests/cookies-private.rs new file mode 100644 index 0000000000..217cda78ac --- /dev/null +++ b/core/lib/tests/cookies-private.rs @@ -0,0 +1,112 @@ +#![cfg(feature = "secrets")] +#![deny(warnings)] + +use rocket::http::{Cookie, CookieJar, SameSite}; +use rocket::{get, post, routes}; + +#[post("/")] +fn cookie_add_private(jar: &CookieJar<'_>) { + let mut cookie_a = Cookie::new("a", "v1"); + jar.add(cookie_a.clone()); + let mut cookie_b = Cookie::new("b", "v2"); + jar.add_private(cookie_b.clone()); + jar.add(Cookie::new("c", "v3")); + + // private: CookieJar::set_defaults(&mut cookie_a); + cookie_a.set_path("/"); + cookie_a.set_same_site(SameSite::Strict); + assert_eq!(jar.get_pending(cookie_a.name()), Some(cookie_a)); + + // private: CookieJar::set_private_defaults(&mut cookie_b); + cookie_b.set_path("/"); + cookie_b.set_same_site(SameSite::Strict); + cookie_b.set_http_only(true); + let expires = time::OffsetDateTime::now_utc() + time::Duration::weeks(1); + cookie_b.set_expires(expires); + let mut cookie_b_pending = jar + .get_private_pending(cookie_b.name()) + .expect("cookie_b_pending None"); + cookie_b_pending.set_expires(expires); + assert_eq!(cookie_b_pending, cookie_b); +} + +#[get("/")] +fn cookie_get_private(jar: &CookieJar<'_>) -> String { + let (a, b, c) = (jar.get("a"), jar.get_private("b"), jar.get("c")); + assert_ne!(a, b.as_ref()); + assert_ne!(a, c); + assert_ne!(b.as_ref(), c); + + format!( + "{}{}{}", + a.unwrap().value(), + b.unwrap().value(), + c.unwrap().value() + ) +} + +/// For test if we got really a private cookie +#[get("/oh-no")] +fn cookie_get(jar: &CookieJar<'_>) -> String { + let (a, b, c) = (jar.get("a"), jar.get("b"), jar.get("c")); + + format!( + "{}{}{}", + a.unwrap().value(), + b.unwrap().value(), + c.unwrap().value() + ) +} + +#[cfg(test)] +mod cookies_private_tests { + use super::*; + use rocket::local::blocking::Client; + use rocket::{Build, Rocket}; + + fn rocket() -> Rocket { + rocket::build().mount( + "/", + routes![cookie_add_private, cookie_get, cookie_get_private], + ) + } + + #[test] + fn test_cookie_add_private() { + let client = Client::debug(rocket()).unwrap(); + let response = client.post("/").dispatch(); + let cookies = response.cookies(); + assert_eq!(cookies.iter().count(), 3); + assert_eq!(cookies.get("a").unwrap().value(), "v1"); + assert_eq!(cookies.get_private("b").unwrap().value(), "v2"); + assert_ne!(cookies.get("b").unwrap().value(), "v2"); + assert_eq!(cookies.get("c").unwrap().value(), "v3"); + } + + #[test] + fn test_cookie_get_private() { + let client = Client::debug(rocket()).unwrap(); + let response = client + .get("/") + .cookie(Cookie::new("a", "Cookie")) + .private_cookie(Cookie::new("b", " tastes ")) + .cookie(Cookie::new("c", "good!")) + .dispatch(); + + assert_eq!(response.into_string().unwrap(), "Cookie tastes good!"); + } + + /// Test if we got really a private cookie + #[test] + fn test_cookie_get_ohno() { + let client = Client::debug(rocket()).unwrap(); + let response = client + .get("/oh-no") + .cookie(Cookie::new("a", "Cookie")) + .private_cookie(Cookie::new("b", " tastes ")) + .cookie(Cookie::new("c", "good!")) + .dispatch(); + + assert_ne!(response.into_string().unwrap(), "Cookie tastes good!"); + } +} diff --git a/core/lib/tests/form-validation-names.rs b/core/lib/tests/form-validation-names.rs index 9438c43844..485cf8a617 100644 --- a/core/lib/tests/form-validation-names.rs +++ b/core/lib/tests/form-validation-names.rs @@ -65,6 +65,16 @@ fn test_form_validation_context() { count(c, n, kind, false) } + // check if parse works and get rid of unused warnings + let mut ok_str = String::from("cats[0].name=BobDerStreuner&cats[0].nick=kitty"); + ok_str += "&kitty.name=HiKitty&kitty.nick=kitty&dog.name=Bulldog"; + let person = Form::::parse(&ok_str).expect("parse"); + let _ = person.cats; + let _ = person.dog.name; + let _ = person.kitty.name; + let _ = person.kitty.nick; + + // check validation let c = errors::("name=littlebobby"); assert_eq!(exact(&c, "nick", Missing), 1); assert_eq!(fuzzy(&c, "nick", Missing), 1); @@ -128,11 +138,3 @@ fn test_form_validation_context() { assert_eq!(exact(&c, "cats", InvalidLength { min: Some(1), max: None }), 1); assert_eq!(fuzzy(&c, "cats[0].name", None), 1); } - -// #[derive(Debug, FromForm)] -// struct Person<'v> { -// kitty: Cat<'v>, -// #[field(validate = len(1..))] -// cats: Vec>, -// dog: Dog<'v>, -// } diff --git a/core/lib/tests/msgpack.rs b/core/lib/tests/msgpack.rs new file mode 100644 index 0000000000..d5b4bce10a --- /dev/null +++ b/core/lib/tests/msgpack.rs @@ -0,0 +1,86 @@ +#![cfg(feature = "msgpack")] + +use rocket::serde::{msgpack::MsgPack, Deserialize, Serialize}; +use rocket::{get, routes}; + +#[derive(Debug, Deserialize, PartialEq, Serialize)] +#[serde(crate = "rocket::serde")] +struct Data { + framework: String, + stars: usize, +} + +#[get("/", format = "msgpack")] +fn top_route() -> MsgPack { + MsgPack(Data { + framework: "Rocket".to_string(), + stars: 5, + }) +} + +#[cfg(test)] +mod msgpack_tests { + use super::*; + use rocket::local::blocking::Client; + use rocket::{Build, Rocket}; + use rocket_http::{ContentType, Status}; + + fn rocket() -> Rocket { + rocket::build().mount("/", routes![top_route]) + } + + #[cfg(feature = "msgpack-compact")] + #[test] + fn test_msgpack_compact() { + let client = Client::debug(rocket()).unwrap(); + let response = client.get("/").header(ContentType::MsgPack).dispatch(); + assert_eq!(response.status(), Status::Ok); + assert_eq!(response.content_type(), Some(ContentType::MsgPack)); + + let data = response.into_msgpack::(); + assert_eq!( + data, + Some(Data { + framework: "Rocket".to_string(), + stars: 5, + }) + ); + + let response = client.get("/").header(ContentType::MsgPack).dispatch(); + + let bytes = response.into_bytes(); + assert_eq!( + bytes, + Some(vec!(146_u8, 166, 82, 111, 99, 107, 101, 116, 5)) + ); + } + + #[cfg(not(feature = "msgpack-compact"))] + #[test] + fn test_msgpack_named() { + let client = Client::debug(rocket()).unwrap(); + let response = client.get("/").header(ContentType::MsgPack).dispatch(); + assert_eq!(response.status(), Status::Ok); + assert_eq!(response.content_type(), Some(ContentType::MsgPack)); + + let data = response.into_msgpack::(); + assert_eq!( + data, + Some(Data { + framework: "Rocket".to_string(), + stars: 5, + }) + ); + + let response = client.get("/").header(ContentType::MsgPack).dispatch(); + + let bytes = response.into_bytes(); + assert_eq!( + bytes, + Some(vec!( + 130_u8, 169, 102, 114, 97, 109, 101, 119, 111, 114, 107, 166, 82, 111, 99, 107, + 101, 116, 165, 115, 116, 97, 114, 115, 5 + )) + ); + } +} diff --git a/core/lib/tests/request_local_cache.rs b/core/lib/tests/request_local_cache.rs new file mode 100644 index 0000000000..e996db5dca --- /dev/null +++ b/core/lib/tests/request_local_cache.rs @@ -0,0 +1,52 @@ +#![deny(warnings)] + +use rocket::{ + async_trait, get, + request::{FromRequest, Outcome}, + routes, Request, +}; +use std::convert::Infallible; + +struct State(String); + +#[async_trait] +impl<'r> FromRequest<'r> for State { + type Error = Infallible; + + async fn from_request(req: &'r Request<'_>) -> Outcome { + let mut state: Option<&String> = req.try_local_cache(); + if state.is_none() { + let _ = req.local_cache(|| String::from("state set")); + } + let _ = req.local_cache(|| String::from("no state reset")); + state = req.try_local_cache(); + + Outcome::Success(State(state.expect("some state").clone())) + } +} + +#[get("/")] +fn top_route(state: State) -> String { + state.0 +} + +#[cfg(test)] +mod request_local_cache_tests { + use super::*; + use rocket::local::blocking::Client; + use rocket::{Build, Rocket}; + use rocket_http::Status; + + fn rocket() -> Rocket { + rocket::build().mount("/", routes![top_route]) + } + + #[test] + fn test_request_local_cache() { + let client = Client::debug(rocket()).unwrap(); + let response = client.get("/").dispatch(); + assert_eq!(response.status(), Status::Ok); + let body = response.into_string(); + assert_eq!(body, Some("state set".into())); + } +} diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 34d66552f3..2f2e61a2df 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -17,6 +17,5 @@ members = [ "tls", "pastebin", - "todo", "chat", ] diff --git a/examples/config/src/main.rs b/examples/config/src/main.rs index de7bee0ab2..12abf36464 100644 --- a/examples/config/src/main.rs +++ b/examples/config/src/main.rs @@ -15,6 +15,7 @@ struct AppConfig { #[get("/")] fn read_config(rocket_config: &Config, app_config: &State) -> String { + let (_, _) = (&app_config.key, &app_config.port); format!("{:#?}\n{:#?}", app_config, rocket_config) } diff --git a/examples/databases/Cargo.toml b/examples/databases/Cargo.toml index 1556db3235..d571162772 100644 --- a/examples/databases/Cargo.toml +++ b/examples/databases/Cargo.toml @@ -7,8 +7,6 @@ publish = false [dependencies] rocket = { path = "../../core/lib", features = ["json"] } -diesel = { version = "1.3", features = ["sqlite", "r2d2"] } -diesel_migrations = "1.3" [dependencies.sqlx] version = "0.5.1" @@ -21,4 +19,4 @@ features = ["sqlx_sqlite"] [dependencies.rocket_sync_db_pools] path = "../../contrib/sync_db_pools/lib/" -features = ["diesel_sqlite_pool", "sqlite_pool"] +features = ["sqlite_pool"] diff --git a/examples/databases/Rocket.toml b/examples/databases/Rocket.toml index 1409c8841d..7e9b653df7 100644 --- a/examples/databases/Rocket.toml +++ b/examples/databases/Rocket.toml @@ -4,6 +4,6 @@ url = "file:rusqlite?mode=memory&cache=shared" [default.databases.sqlx] url = "db/sqlx/db.sqlite" -[default.databases.diesel] -url = "db/diesel/db.sqlite" -timeout = 10 +# [default.databases.diesel] +# url = "db/diesel/db.sqlite" +# timeout = 10 diff --git a/examples/databases/db/sqlx/.gitignore b/examples/databases/db/sqlx/.gitignore new file mode 100644 index 0000000000..3a72db8821 --- /dev/null +++ b/examples/databases/db/sqlx/.gitignore @@ -0,0 +1 @@ +db.sqlite-* diff --git a/examples/databases/src/main.rs b/examples/databases/src/main.rs index 32ff22e241..e8d5bdc65f 100644 --- a/examples/databases/src/main.rs +++ b/examples/databases/src/main.rs @@ -1,12 +1,9 @@ #[macro_use] extern crate rocket; #[macro_use] extern crate rocket_sync_db_pools; -#[macro_use] extern crate diesel_migrations; -#[macro_use] extern crate diesel; #[cfg(test)] mod tests; mod sqlx; -mod diesel_sqlite; mod rusqlite; #[launch] @@ -14,5 +11,4 @@ fn rocket() -> _ { rocket::build() .attach(sqlx::stage()) .attach(rusqlite::stage()) - .attach(diesel_sqlite::stage()) } diff --git a/examples/databases/src/tests.rs b/examples/databases/src/tests.rs index 98457e7f29..98d61f4e56 100644 --- a/examples/databases/src/tests.rs +++ b/examples/databases/src/tests.rs @@ -67,10 +67,10 @@ fn test_sqlx() { test("/sqlx", crate::sqlx::stage()) } -#[test] -fn test_diesel() { - test("/diesel", crate::diesel_sqlite::stage()) -} +// #[test] +// fn test_diesel() { +// test("/diesel", crate::diesel_sqlite::stage()) +// } #[test] fn test_rusqlite() { diff --git a/examples/diesel/Cargo.toml b/examples/diesel/Cargo.toml new file mode 100644 index 0000000000..f991480b37 --- /dev/null +++ b/examples/diesel/Cargo.toml @@ -0,0 +1,5 @@ +[workspace] +members = [ + "databases-diesel", + "todo", +] diff --git a/examples/diesel/databases-diesel/Cargo.toml b/examples/diesel/databases-diesel/Cargo.toml new file mode 100644 index 0000000000..5c143215bc --- /dev/null +++ b/examples/diesel/databases-diesel/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "databases-diesel" +version = "0.0.0" +workspace = "../" +edition = "2018" +publish = false + +[dependencies] +rocket = { path = "../../../core/lib", features = ["json"] } +diesel = { version = "1.4", features = ["sqlite", "r2d2"] } +diesel_migrations = { version = "1.4" } + +[dependencies.rocket_sync_db_pools] +path = "../../../contrib/sync_db_pools/lib/" +features = ["diesel_sqlite_pool"] diff --git a/examples/diesel/databases-diesel/README.md b/examples/diesel/databases-diesel/README.md new file mode 100644 index 0000000000..43dcdb8ff2 --- /dev/null +++ b/examples/diesel/databases-diesel/README.md @@ -0,0 +1,8 @@ +# Databases Example + +This example makes use of SQLite. You'll need `sqlite3` and its development +headers installed: + + * **macOS:** `brew install sqlite` + * **Debian**, **Ubuntu:** `apt-get install libsqlite3-dev` + * **Arch:** `pacman -S sqlite` diff --git a/examples/diesel/databases-diesel/Rocket.toml b/examples/diesel/databases-diesel/Rocket.toml new file mode 100644 index 0000000000..8f8dc2d419 --- /dev/null +++ b/examples/diesel/databases-diesel/Rocket.toml @@ -0,0 +1,9 @@ +# [default.databases.rusqlite] +# url = "file:rusqlite?mode=memory&cache=shared" + +[default.databases.sqlx] +url = "db/sqlx/db.sqlite" + +[default.databases.diesel] +url = "db/diesel/db.sqlite" +timeout = 10 diff --git a/examples/databases/db/diesel/migrations/.gitkeep b/examples/diesel/databases-diesel/db/diesel/migrations/.gitkeep similarity index 100% rename from examples/databases/db/diesel/migrations/.gitkeep rename to examples/diesel/databases-diesel/db/diesel/migrations/.gitkeep diff --git a/examples/databases/db/diesel/migrations/20210329150332_create_posts_table/down.sql b/examples/diesel/databases-diesel/db/diesel/migrations/20210329150332_create_posts_table/down.sql similarity index 100% rename from examples/databases/db/diesel/migrations/20210329150332_create_posts_table/down.sql rename to examples/diesel/databases-diesel/db/diesel/migrations/20210329150332_create_posts_table/down.sql diff --git a/examples/databases/db/diesel/migrations/20210329150332_create_posts_table/up.sql b/examples/diesel/databases-diesel/db/diesel/migrations/20210329150332_create_posts_table/up.sql similarity index 100% rename from examples/databases/db/diesel/migrations/20210329150332_create_posts_table/up.sql rename to examples/diesel/databases-diesel/db/diesel/migrations/20210329150332_create_posts_table/up.sql diff --git a/examples/diesel/databases-diesel/sqlx-data.json b/examples/diesel/databases-diesel/sqlx-data.json new file mode 100644 index 0000000000..9dbd494590 --- /dev/null +++ b/examples/diesel/databases-diesel/sqlx-data.json @@ -0,0 +1,81 @@ +{ + "db": "SQLite", + "11e3096becb72f427c8d3911ef4327afd9516143806981e11f8e34d069c14472": { + "query": "SELECT id, title, text FROM posts WHERE id = ?", + "describe": { + "columns": [ + { + "name": "id", + "ordinal": 0, + "type_info": "Int64" + }, + { + "name": "title", + "ordinal": 1, + "type_info": "Text" + }, + { + "name": "text", + "ordinal": 2, + "type_info": "Text" + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + false, + false, + false + ] + } + }, + "3c289da9873097a11191dbedc5c78d86afd6a6d36771bfeb12f331abca6279cf": { + "query": "INSERT INTO posts (title, text) VALUES (?, ?)", + "describe": { + "columns": [], + "parameters": { + "Right": 2 + }, + "nullable": [] + } + }, + "4415c35941e52a981b10707fe2e1ceb0bad0e473701e51ef21ecb2973c76b4df": { + "query": "SELECT id FROM posts", + "describe": { + "columns": [ + { + "name": "id", + "ordinal": 0, + "type_info": "Int64" + } + ], + "parameters": { + "Right": 0 + }, + "nullable": [ + false + ] + } + }, + "668690acaca0a0c0b4ac306b14d82aa1bee940f0776fae3f9962639b78328858": { + "query": "DELETE FROM posts", + "describe": { + "columns": [], + "parameters": { + "Right": 0 + }, + "nullable": [] + } + }, + "79301b44b77802e0096efd73b1e9adac27b27a3cf7bf853af3a9f130b1684d91": { + "query": "DELETE FROM posts WHERE id = ?", + "describe": { + "columns": [], + "parameters": { + "Right": 1 + }, + "nullable": [] + } + } +} \ No newline at end of file diff --git a/examples/databases/src/diesel_sqlite.rs b/examples/diesel/databases-diesel/src/diesel_sqlite.rs similarity index 100% rename from examples/databases/src/diesel_sqlite.rs rename to examples/diesel/databases-diesel/src/diesel_sqlite.rs diff --git a/examples/diesel/databases-diesel/src/main.rs b/examples/diesel/databases-diesel/src/main.rs new file mode 100644 index 0000000000..33710375ef --- /dev/null +++ b/examples/diesel/databases-diesel/src/main.rs @@ -0,0 +1,18 @@ +#[macro_use] extern crate rocket; +#[macro_use] extern crate rocket_sync_db_pools; +#[macro_use] extern crate diesel_migrations; +#[macro_use] extern crate diesel; + +#[cfg(test)] mod tests; + +// mod sqlx; +mod diesel_sqlite; +// mod rusqlite; + +#[launch] +fn rocket() -> _ { + rocket::build() + // .attach(sqlx::stage()) + // .attach(rusqlite::stage()) + .attach(diesel_sqlite::stage()) +} diff --git a/examples/diesel/databases-diesel/src/tests.rs b/examples/diesel/databases-diesel/src/tests.rs new file mode 100644 index 0000000000..e7207d5772 --- /dev/null +++ b/examples/diesel/databases-diesel/src/tests.rs @@ -0,0 +1,78 @@ +use rocket::fairing::AdHoc; +use rocket::local::blocking::Client; +use rocket::serde::{Serialize, Deserialize}; +use rocket::http::Status; + +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +#[serde(crate = "rocket::serde")] +struct Post { + title: String, + text: String, +} + +fn test(base: &str, stage: AdHoc) { + // Number of posts we're going to create/read/delete. + const N: usize = 20; + + // NOTE: If we had more than one test running concurently that dispatches + // DB-accessing requests, we'd need transactions or to serialize all tests. + let client = Client::tracked(rocket::build().attach(stage)).unwrap(); + + // Clear everything from the database. + assert_eq!(client.delete(base).dispatch().status(), Status::Ok); + assert_eq!(client.get(base).dispatch().into_json::>(), Some(vec![])); + + // Add some random posts, ensure they're listable and readable. + for i in 1..=N{ + let title = format!("My Post - {}", i); + let text = format!("Once upon a time, at {}'o clock...", i); + let post = Post { title: title.clone(), text: text.clone() }; + + // Create a new post. + let response = client.post(base).json(&post).dispatch().into_json::(); + assert_eq!(response.unwrap(), post); + + // Ensure the index shows one more post. + let list = client.get(base).dispatch().into_json::>().unwrap(); + assert_eq!(list.len(), i); + + // The last in the index is the new one; ensure contents match. + let last = list.last().unwrap(); + let response = client.get(format!("{}/{}", base, last)).dispatch(); + assert_eq!(response.into_json::().unwrap(), post); + } + + // Now delete all of the posts. + for _ in 1..=N { + // Get a valid ID from the index. + let list = client.get(base).dispatch().into_json::>().unwrap(); + let id = list.get(0).expect("have post"); + + // Delete that post. + let response = client.delete(format!("{}/{}", base, id)).dispatch(); + assert_eq!(response.status(), Status::Ok); + } + + // Ensure they're all gone. + let list = client.get(base).dispatch().into_json::>().unwrap(); + assert!(list.is_empty()); + + // Trying to delete should now 404. + let response = client.delete(format!("{}/{}", base, 1)).dispatch(); + assert_eq!(response.status(), Status::NotFound); +} + +// #[test] +// fn test_sqlx() { +// test("/sqlx", crate::sqlx::stage()) +// } + +#[test] +fn test_diesel() { + test("/diesel", crate::diesel_sqlite::stage()) +} + +// #[test] +// fn test_rusqlite() { +// test("/rusqlite", crate::rusqlite::stage()) +// } diff --git a/examples/todo/Cargo.toml b/examples/diesel/todo/Cargo.toml similarity index 56% rename from examples/todo/Cargo.toml rename to examples/diesel/todo/Cargo.toml index a6bc43a3da..53a4ae69c3 100644 --- a/examples/todo/Cargo.toml +++ b/examples/diesel/todo/Cargo.toml @@ -6,18 +6,18 @@ edition = "2018" publish = false [dependencies] -rocket = { path = "../../core/lib" } -diesel = { version = "1.3", features = ["sqlite", "r2d2"] } -diesel_migrations = "1.3" +rocket = { path = "../../../core/lib" } +diesel = { version = "1.4", features = ["sqlite", "r2d2"] } +diesel_migrations = { version = "1.4" } [dev-dependencies] parking_lot = "0.11" rand = "0.8" [dependencies.rocket_sync_db_pools] -path = "../../contrib/sync_db_pools/lib/" +path = "../../../contrib/sync_db_pools/lib/" features = ["diesel_sqlite_pool"] [dependencies.rocket_dyn_templates] -path = "../../contrib/dyn_templates" +path = "../../../contrib/dyn_templates" features = ["tera"] diff --git a/examples/todo/README.md b/examples/diesel/todo/README.md similarity index 100% rename from examples/todo/README.md rename to examples/diesel/todo/README.md diff --git a/examples/todo/Rocket.toml b/examples/diesel/todo/Rocket.toml similarity index 100% rename from examples/todo/Rocket.toml rename to examples/diesel/todo/Rocket.toml diff --git a/examples/todo/db/DB_LIVES_HERE b/examples/diesel/todo/db/DB_LIVES_HERE similarity index 100% rename from examples/todo/db/DB_LIVES_HERE rename to examples/diesel/todo/db/DB_LIVES_HERE diff --git a/examples/todo/migrations/.gitkeep b/examples/diesel/todo/migrations/.gitkeep similarity index 100% rename from examples/todo/migrations/.gitkeep rename to examples/diesel/todo/migrations/.gitkeep diff --git a/examples/todo/migrations/20160720150332_create_tasks_table/down.sql b/examples/diesel/todo/migrations/20160720150332_create_tasks_table/down.sql similarity index 100% rename from examples/todo/migrations/20160720150332_create_tasks_table/down.sql rename to examples/diesel/todo/migrations/20160720150332_create_tasks_table/down.sql diff --git a/examples/todo/migrations/20160720150332_create_tasks_table/up.sql b/examples/diesel/todo/migrations/20160720150332_create_tasks_table/up.sql similarity index 100% rename from examples/todo/migrations/20160720150332_create_tasks_table/up.sql rename to examples/diesel/todo/migrations/20160720150332_create_tasks_table/up.sql diff --git a/examples/todo/src/main.rs b/examples/diesel/todo/src/main.rs similarity index 100% rename from examples/todo/src/main.rs rename to examples/diesel/todo/src/main.rs diff --git a/examples/todo/src/task.rs b/examples/diesel/todo/src/task.rs similarity index 100% rename from examples/todo/src/task.rs rename to examples/diesel/todo/src/task.rs diff --git a/examples/todo/src/tests.rs b/examples/diesel/todo/src/tests.rs similarity index 83% rename from examples/todo/src/tests.rs rename to examples/diesel/todo/src/tests.rs index 286f73aed8..a17d60b8b0 100644 --- a/examples/todo/src/tests.rs +++ b/examples/diesel/todo/src/tests.rs @@ -3,7 +3,7 @@ use super::task::Task; use rand::{Rng, thread_rng, distributions::Alphanumeric}; use rocket::local::asynchronous::Client; -use rocket::http::{Status, ContentType}; +use rocket::http::{Status, ContentType, Cookie}; // We use a lock to synchronize between tests so DB operations don't collide. // For now. In the future, we'll have a nice way to run each test in a DB @@ -151,7 +151,33 @@ fn test_bad_form_submissions() { .await; let mut cookies = res.headers().get("Set-Cookie"); - assert!(cookies.any(|value| value.contains("error"))); + let cookie = cookies + .find(|value| value.contains("error")) + .map(|value| Cookie::parse_encoded(value).expect("cookie")) + .expect("cookie"); + assert_eq!(cookie.name(), "_flash"); // private const + + // Check that the index page now contains the flash message that we were expecting. + // A client supporting cookies will set the cookie by the receiving _Set-Cookie_ header. + let body = client + .get("/") + .cookie(cookie) // set cookie + .dispatch() + .await + .into_string() + .await + .expect("body"); + assert!(body.contains("Description cannot be empty.")); + + // Check that the flash message was cleared upon another visit to the index page. + let body = client + .get("/") + .dispatch() + .await + .into_string() + .await + .expect("body"); + assert!(!body.contains("Description cannot be empty.")); // Submit a form without a description. Expect a 422 but no flash error. let res = client.post("/todo") diff --git a/examples/todo/static/css/normalize.css b/examples/diesel/todo/static/css/normalize.css similarity index 100% rename from examples/todo/static/css/normalize.css rename to examples/diesel/todo/static/css/normalize.css diff --git a/examples/todo/static/css/skeleton.css b/examples/diesel/todo/static/css/skeleton.css similarity index 100% rename from examples/todo/static/css/skeleton.css rename to examples/diesel/todo/static/css/skeleton.css diff --git a/examples/todo/static/css/style.css b/examples/diesel/todo/static/css/style.css similarity index 100% rename from examples/todo/static/css/style.css rename to examples/diesel/todo/static/css/style.css diff --git a/examples/todo/static/images/favicon.png b/examples/diesel/todo/static/images/favicon.png similarity index 100% rename from examples/todo/static/images/favicon.png rename to examples/diesel/todo/static/images/favicon.png diff --git a/examples/todo/static/index.html.tera b/examples/diesel/todo/static/index.html.tera similarity index 92% rename from examples/todo/static/index.html.tera rename to examples/diesel/todo/static/index.html.tera index 369ae3643c..a6f77f1db0 100644 --- a/examples/todo/static/index.html.tera +++ b/examples/diesel/todo/static/index.html.tera @@ -23,10 +23,10 @@
- {% if msg %} - - {{ msg.1 }} + class="u-full-width {% if flash %}field-{{flash.0}}{% endif %}" /> + {% if flash %} + + {{ flash.1 }} {% endif %}
diff --git a/examples/forms/src/main.rs b/examples/forms/src/main.rs index 9dc93bfe44..9ee9a8a5cc 100644 --- a/examples/forms/src/main.rs +++ b/examples/forms/src/main.rs @@ -32,6 +32,7 @@ enum Category { ComputerScience, } +#[allow(dead_code)] // reason = "fields are used in tera templates" #[derive(Debug, FromForm)] struct Submission<'v> { #[field(validate = len(1..))] @@ -47,6 +48,7 @@ struct Submission<'v> { ready: bool, } +#[allow(dead_code)] // reason = "fields are used in tera templates" #[derive(Debug, FromForm)] struct Account<'v> { #[field(validate = len(1..))] @@ -56,6 +58,7 @@ struct Account<'v> { email: &'v str, } +#[allow(dead_code)] // reason = "fields are used in tera templates" #[derive(Debug, FromForm)] struct Submit<'v> { account: Account<'v>, diff --git a/scripts/config.sh b/scripts/config.sh index 8eb6e78d9b..7bfbb7b715 100755 --- a/scripts/config.sh +++ b/scripts/config.sh @@ -35,6 +35,9 @@ function future_date() { fi } +[ -n "$CARGO" ] || CARGO="cargo" +[ -n "$RUSTC" ] || RUSTC="rustc" + # Root of workspace-like directories. PROJECT_ROOT=$(relative "") || exit $? CORE_ROOT=$(relative "core") || exit $? @@ -54,6 +57,9 @@ EXAMPLES_DIR=$(relative "examples") || exit $? DOC_DIR=$(relative "target/doc") || exit $? # Versioning information. These are changed as versions change. +CARGO_VERSION="$($CARGO --version)" +RUSTC_VERSION="$($RUSTC --version)" +RUST_NIGHTLY=$([ "X${RUSTC_VERSION%-nightly*}" != "X${RUSTC_VERSION}" ] && echo "nightly" || echo "") VERSION=$(git grep -h "^version" "${CORE_LIB_ROOT}" | head -n 1 | cut -d '"' -f2) MAJOR_VERSION=$(echo "${VERSION}" | cut -d'.' -f1-2) VIRTUAL_CODENAME="$(git branch --show-current)" @@ -99,7 +105,9 @@ ALL_CRATE_ROOTS=( ) function print_environment() { - echo " VERSION: ${VERSION}" + echo " CARGO VERSION: ${CARGO_VERSION}" + echo " RUSTC VERSION: ${RUSTC_VERSION}" + echo " ROCKET VERSION: ${VERSION}" echo " MAJOR_VERSION: ${MAJOR_VERSION}" echo " CODENAME: ${CODENAME}" echo " DOC_VERSION: ${DOC_VERSION}" diff --git a/scripts/test.sh b/scripts/test.sh index 1e559f73fe..39f9af5e7d 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -1,6 +1,15 @@ #!/usr/bin/env bash set -e +# set CARGO before sourcing config.sh +CARGO="cargo" +RUSTC="rustc" +if [[ $1 == +* ]]; then + CARGO="$CARGO $1" + RUSTC="$RUSTC $1" + shift +fi + # Brings in _ROOT, _DIR, _DIRS globals. SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" source "${SCRIPT_DIR}/config.sh" @@ -8,7 +17,6 @@ source "${SCRIPT_DIR}/config.sh" # Add Cargo to PATH. export PATH=${HOME}/.cargo/bin:${PATH} export CARGO_INCREMENTAL=0 -CARGO="cargo" # Checks that the versions for Cargo projects $@ all match function check_versions_match() { @@ -102,11 +110,20 @@ function test_contrib() { $CARGO test -p rocket_db_pools --no-default-features --features $feature $@ done + echo ":: Building and testing rocket_db_pools_codegen..." + $CARGO test -p rocket_db_pools_codegen $@ + for feature in "${SYNC_DB_POOLS_FEATURES[@]}"; do echo ":: Building and testing sync_db_pools [$feature]..." $CARGO test -p rocket_sync_db_pools --no-default-features --features $feature $@ done + echo ":: Building and testing rocket_sync_db_pools_codegen..." + $CARGO test -p rocket_sync_db_pools_codegen $@ + + echo ":: Building and testing rocket_sync_db_pools_codegen_diesel_tests..." + $CARGO test -p rocket_sync_db_pools_codegen_diesel_tests $@ + for feature in "${DYN_TEMPLATES_FEATURES[@]}"; do echo ":: Building and testing dyn_templates [$feature]..." $CARGO test -p rocket_dyn_templates --no-default-features --features $feature $@ @@ -120,16 +137,20 @@ function test_core() { mtls json msgpack + msgpack-compact uuid ) + RUSTDOCFLAGS="$RUSTDOCFLAGS" + [ -n "$RUST_NIGHTLY" ] && RUSTDOCFLAGS="-Zunstable-options --no-run $RUSTDOCFLAGS" + echo ":: Building and checking core [no features]..." - RUSTDOCFLAGS="-Zunstable-options --no-run" \ + RUSTDOCFLAGS="$RUSTDOCFLAGS" \ indir "${CORE_LIB_ROOT}" $CARGO test --no-default-features $@ for feature in "${FEATURES[@]}"; do echo ":: Building and checking core [${feature}]..." - RUSTDOCFLAGS="-Zunstable-options --no-run" \ + RUSTDOCFLAGS="$RUSTDOCFLAGS" \ indir "${CORE_LIB_ROOT}" $CARGO test --no-default-features --features "${feature}" $@ done } @@ -142,12 +163,57 @@ function test_examples() { echo ":: Building and testing examples..." indir "${EXAMPLES_DIR}" $CARGO update ROCKET_SECRET_KEY="itlYmFR2vYKrOmFhupMIn/hyB6lYCCTXz4yaQX89XVg=" \ - indir "${EXAMPLES_DIR}" $CARGO test --all $@ - } + indir "${EXAMPLES_DIR}" $CARGO test --workspace $@ + indir "${EXAMPLES_DIR}/diesel" $CARGO update + ROCKET_SECRET_KEY="itlYmFR2vYKrOmFhupMIn/hyB6lYCCTXz4yaQX89XVg=" \ + indir "${EXAMPLES_DIR}/diesel" $CARGO test --workspace $@ +} function test_default() { + FEATURES=( + tls + mtls + secrets + json + msgpack + uuid + private-cookies + serde + deadpool_postgres + deadpool_redis + sqlx_mysql + sqlx_postgres + sqlx_mssql + sqlx_macros + postgres_pool + memcache_pool + tera + handlebars + ) + COMPATIBLE_FEATURES=( + sqlx_sqlite + sqlite_pool + diesel_postgres_pool + diesel_mysql_pool + ) + DIESEL_SQLITE_FEATURES=( + diesel_sqlite_pool + ) + + compatible_features=$(printf ",%s" "${FEATURES[@]}") + compatible_features=${compatible_features:1} + all_compatible_features="$compatible_features" + for feature in "${COMPATIBLE_FEATURES[@]}"; do + all_compatible_features="$all_compatible_features,$feature" + done echo ":: Building and testing core libraries..." - indir "${PROJECT_ROOT}" $CARGO test --all --all-features $@ + indir "${PROJECT_ROOT}" $CARGO test --workspace --features $all_compatible_features \ + --exclude rocket_guide_tests --exclude rocket_sync_db_pools_codegen_diesel_tests $@ + + echo ":: Building and testing rocket_guide_tests (diesel-sqlite)..." + indir "${PROJECT_ROOT}" $CARGO test -p rocket_guide_tests --all-features $@ + echo ":: Building and testing rocket_sync_db_pools_codegen_diesel_tests (diesel-sqlite)..." + indir "${PROJECT_ROOT}" $CARGO test -p rocket_sync_db_pools_codegen_diesel_tests --all-features $@ echo ":: Checking benchmarks..." indir "${BENCHMARKS_ROOT}" $CARGO update @@ -155,7 +221,7 @@ function test_default() { echo ":: Checking fuzzers..." indir "${FUZZ_ROOT}" $CARGO update - indir "${FUZZ_ROOT}" $CARGO check --all --all-features $@ + indir "${FUZZ_ROOT}" $CARGO check --workspace --all-features $@ } function run_benchmarks() { @@ -164,11 +230,6 @@ function run_benchmarks() { indir "${BENCHMARKS_ROOT}" $CARGO bench $@ } -if [[ $1 == +* ]]; then - CARGO="$CARGO $1" - shift -fi - # The kind of test we'll be running. TEST_KIND="default" KINDS=("contrib" "benchmarks" "core" "examples" "default" "all") diff --git a/site/guide/3-overview.md b/site/guide/3-overview.md index dd21fe3cf0..9e5b29735c 100644 --- a/site/guide/3-overview.md +++ b/site/guide/3-overview.md @@ -228,7 +228,7 @@ is desired, or when the return value of [`launch()`] is to be inspected. The Rocket uses Rust [`Future`]s for concurrency. Asynchronous programming with `Future`s and `async/await` allows route handlers to perform wait-heavy I/O such -as filesystem and network access while still allowing other requests to be make +as filesystem and network access while still allowing other requests to make progress. For an overview of Rust `Future`s, see [Asynchronous Programming in Rust](https://rust-lang.github.io/async-book/). diff --git a/site/guide/4-requests.md b/site/guide/4-requests.md index 9ec066ea3f..59da6b2e13 100644 --- a/site/guide/4-requests.md +++ b/site/guide/4-requests.md @@ -149,7 +149,7 @@ async fn files(file: PathBuf) -> Option { } ``` -[path traversal attacks]: https://www.owasp.org/index.php/Path_Traversal +[path traversal attacks]: https://owasp.org/www-community/attacks/Path_Traversal ! tip: Rocket makes it even _easier_ to serve static files! @@ -1716,12 +1716,13 @@ form field. ### Static Parameters -A request matches a route _iff_ its query string contains all of the static +A request matches a route [_iff_](https://en.wikipedia.org/wiki/If_and_only_if) +its query string contains all of the static parameters in the route's query string. A route with a static parameter `param` (any UTF-8 text string) in a query will only match requests with that exact path segment in its query string. -! note: This is truly an _iff_! +! note: This is truly an [_iff_](https://en.wikipedia.org/wiki/If_and_only_if)! Only the static parameters in query route string affect routing. Dynamic parameters are allowed to be missing by default. diff --git a/site/guide/9-configuration.md b/site/guide/9-configuration.md index 849d5ec01c..6e8b5d4f24 100644 --- a/site/guide/9-configuration.md +++ b/site/guide/9-configuration.md @@ -17,21 +17,22 @@ is configured with. This means that no matter which configuration provider Rocket is asked to use, it must be able to read the following configuration values: -| key | kind | description | debug/release default | -|----------------|-------------------|-------------------------------------------------|-------------------------| -| `address` | `IpAddr` | IP address to serve on | `127.0.0.1` | -| `port` | `u16` | Port to serve on. | `8000` | -| `workers` | `usize` | Number of threads to use for executing futures. | cpu core count | -| `ident` | `string`, `false` | If and how to identify via the `Server` header. | `"Rocket"` | -| `keep_alive` | `u32` | Keep-alive timeout seconds; disabled when `0`. | `5` | -| `log_level` | [`LogLevel`] | Max level to log. (off/normal/debug/critical) | `normal`/`critical` | -| `cli_colors` | `bool` | Whether to use colors and emoji when logging. | `true` | -| `secret_key` | [`SecretKey`] | Secret key for signing and encrypting values. | `None` | -| `tls` | [`TlsConfig`] | TLS configuration, if any. | `None` | -| `limits` | [`Limits`] | Streaming read size limits. | [`Limits::default()`] | -| `limits.$name` | `&str`/`uint` | Read limit for `$name`. | forms = "32KiB" | -| `ctrlc` | `bool` | Whether `ctrl-c` initiates a server shutdown. | `true` | -| `shutdown` | [`Shutdown`] | Graceful shutdown configuration. | [`Shutdown::default()`] | +| key | kind | description | debug/release default | +|-------------------------|-------------------|----------------------------------------------------------|-------------------------| +| `address` | `IpAddr` | IP address to serve on | `127.0.0.1` | +| `port` | `u16` | Port to serve on. | `8000` | +| `workers` | `usize` | Number of threads to use for executing futures. | cpu core count | +| `blocking_workers` | `usize` | Number of threads to use for executing blocking futures. | 512 | +| `ident` | `string`, `false` | If and how to identify via the `Server` header. | `"Rocket"` | +| `keep_alive` | `u32` | Keep-alive timeout seconds; disabled when `0`. | `5` | +| `log_level` | [`LogLevel`] | Max level to log. (off/normal/debug/critical) | `normal`/`critical` | +| `cli_colors` | `bool` | Whether to use colors and emoji when logging. | `true` | +| `secret_key` | [`SecretKey`] | Secret key for signing and encrypting values. | `None` | +| `tls` | [`TlsConfig`] | TLS configuration, if any. | `None` | +| `limits` | [`Limits`] | Streaming read size limits. | [`Limits::default()`] | +| `limits.$name` | `&str`/`uint` | Read limit for `$name`. | form = "32KiB" | +| `ctrlc` | `bool` | Whether `ctrl-c` initiates a server shutdown. | `true` | +| `shutdown` | [`Shutdown`] | Graceful shutdown configuration. | [`Shutdown::default()`] | ### Profiles @@ -106,12 +107,12 @@ file might look like: ## defaults for _all_ profiles [default] address = "0.0.0.0" -limits = { forms = "64 kB", json = "1 MiB" } +limits = { form = "64 kB", json = "1 MiB" } ## set only when compiled in debug mode, i.e, `cargo build` [debug] port = 8000 -## only the `json` key from `default` will be overridden; `forms` will remain +## only the `json` key from `default` will be overridden; `form` will remain limits = { json = "10MiB" } ## set only when the `nyc` profile is selected @@ -135,6 +136,7 @@ sensible. address = "127.0.0.1" port = 8000 workers = 16 +blocking_workers = 512 keep_alive = 5 ident = "Rocket" log_level = "normal" @@ -144,7 +146,7 @@ cli_colors = true secret_key = "hPRYyVRiMyxpw5sBB1XeCMN1kFsDCqKvBi2QJxBVHQk=" [default.limits] -forms = "64 kB" +form = "64 kB" json = "1 MiB" msgpack = "2 MiB" "file/jpg" = "5 MiB" @@ -179,7 +181,7 @@ ROCKET_IDENT=Rocket ROCKET_IDENT="Hello Rocket" ROCKET_IDENT=false ROCKET_TLS={certs="abc",key="foo/bar"} -ROCKET_LIMITS={forms="64 KiB"} +ROCKET_LIMITS={form="64 KiB"} ``` ### Secret Key @@ -208,7 +210,7 @@ key corresponds to a data type and each value corresponds to the maximum size in bytes Rocket should accept for that type. Rocket can parse both integers (`32768`) or SI unit based strings (`"32KiB"`) as limits. -By default, Rocket specifies a `32 KiB` limit for incoming forms. Since Rocket +By default, Rocket specifies a `32 KiB` limit for incoming form(s). Since Rocket requires specifying a read limit whenever data is read, external data guards may also choose to have a configure limit via the `limits` parameter. The [`Json`](@api/rocket/serde/json/struct.Json.html) type, for instance, uses the @@ -338,6 +340,15 @@ than those provided by [`Config::figment()`]. In other words, only the values set by the `ROCKET_WORKERS` environment variable or in the `workers` property of `Rocket.toml` will be considered - all other `workers` values are ignored. +The `blocking_workers` parameter sets the maximum number of threads tokio will +spawn to execute futures spawned with `spawn_blocking`. Similar to the `workers` +parameter, this cannot be reconfigured. The runtime will only spawn these threads +if there are futures to execute on them, and leaving this at it's default value +is generally prefered. Rocket rarely spawns any of these, so running into the limit +is unlikely, although in some situations the OS will limit the number of threads +Rocket can spawn. In these situations, either the OS limit needs to be raised or +the Rocket limit needs to be lowered. + ## Extracting Values Your application can extract any configuration that implements [`Deserialize`] diff --git a/site/overview.toml b/site/overview.toml index 96b239ae59..ebba127bec 100644 --- a/site/overview.toml +++ b/site/overview.toml @@ -17,7 +17,7 @@ As an example, consider the simple route below: ```rust #[get("/")] fn index() -> &'static str { - "Hello, world!" + "Hello, world!" } ``` @@ -175,7 +175,7 @@ customizable **404** error is returned. ```rust #[post("/user", data = "")] fn new_user(admin: AdminUser, new_user: Form) -> T { - ... + let _validated_user = new_user; // type safety } ``` diff --git a/site/tests/Cargo.toml b/site/tests/Cargo.toml index e95491bfa9..1a31ad4772 100644 --- a/site/tests/Cargo.toml +++ b/site/tests/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rocket_guide_tests" -version = "0.5.0-rc.1" +version = "0.5.0-rc.2" workspace = "../../" edition = "2018" publish = false