Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,10 @@ jobs:
- {VERSION: "3.14", NOXSESSION: "tests", OPENSSL: {TYPE: "openssl", VERSION: "f31510e95333b33a8765cbb81a147df7572c88b2"}}
# Builds with various Rust versions. Includes MSRV and next
# potential future MSRV.
# - 1.85: 2024 edition
# - 1.95: cfg_select
- {VERSION: "3.14", NOXSESSION: "rust,tests", RUST: "1.83.0"}
- {VERSION: "3.14", NOXSESSION: "rust,tests", RUST: "1.83.0", OPENSSL: {TYPE: "aws-lc", VERSION: "v5.1.0"}}
- {VERSION: "3.14", NOXSESSION: "rust,tests", RUST: "1.83.0", OPENSSL: {TYPE: "boringssl", VERSION: "1c7d52ef3e3f373302cb957089fa783d1e5fd8cd"}}
- {VERSION: "3.14", NOXSESSION: "rust,tests", RUST: "1.85.0"}
- {VERSION: "3.14", NOXSESSION: "rust,tests", RUST: "1.85.0", OPENSSL: {TYPE: "aws-lc", VERSION: "v5.1.0"}}
- {VERSION: "3.14", NOXSESSION: "rust,tests", RUST: "1.85.0", OPENSSL: {TYPE: "boringssl", VERSION: "1c7d52ef3e3f373302cb957089fa783d1e5fd8cd"}}
- {VERSION: "3.14", NOXSESSION: "rust,tests", RUST: "beta"}
- {VERSION: "3.14", NOXSESSION: "rust,tests", RUST: "nightly"}
- {VERSION: "3.14", NOXSESSION: "tests-rust-debug"}
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ Changelog
classes and the classes in
:mod:`~cryptography.hazmat.primitives.asymmetric.padding` can now be
compared with ``==``.
* Updated the minimum supported Rust version (MSRV) to 1.85.0, from 1.83.0.

.. _v49-0-0:

Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ authors = ["The cryptography developers <cryptography-dev@python.org>"]
edition = "2021"
publish = false
# This specifies the MSRV
rust-version = "1.83.0"
rust-version = "1.85.0"
license = "Apache-2.0 OR BSD-3-Clause"

[workspace.dependencies]
Expand Down
8 changes: 4 additions & 4 deletions docs/installation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ available<installation:Rust>`.

.. warning::

The Rust available by default in Alpine < 3.21 is older than the
The Rust available by default in Alpine < 3.22 is older than the
minimum supported version. See the :ref:`Rust installation instructions
<installation:Rust>` for information about installing a newer Rust.

Expand All @@ -137,9 +137,9 @@ available<installation:Rust>`.

.. warning::

For RHEL and CentOS you must be on version 9.6 or newer for the command
For RHEL and CentOS you must be on version 9.7 or newer for the command
below to install a sufficiently new Rust. If your Rust is less than
1.83.0 please see the :ref:`Rust installation instructions
1.85.0 please see the :ref:`Rust installation instructions
<installation:Rust>` for information about installing a newer Rust.

.. code-block:: console
Expand Down Expand Up @@ -321,7 +321,7 @@ Rust
a Rust toolchain.

Building ``cryptography`` requires having a working Rust toolchain. The current
minimum supported Rust version is 1.83.0. **This is newer than the Rust some
minimum supported Rust version is 1.85.0. **This is newer than the Rust some
package managers ship**, so users may need to install with the
instructions below.

Expand Down
117 changes: 117 additions & 0 deletions noxfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,111 @@ def local(session: nox.Session):
BIN_EXT = ".exe" if sys.platform == "win32" else ""


def _parse_profdata_function_counts(prof_dump: str) -> dict[str, int]:
# Parses `llvm-profdata show --all-functions` output, which contains
# a block like this for every instrumented function:
# <mangled name>:
# Hash: 0x0123456789abcdef
# Counters: 7
# Function count: 8
counts: dict[str, int] = {}
name = None
for line in prof_dump.splitlines():
if line.startswith(" ") and not line.startswith(" "):
name = line.strip().removesuffix(":")
elif name is not None and line.strip().startswith("Function count: "):
count = int(line.rsplit(":", 1)[1])
counts[name] = max(counts.get(name, 0), count)
name = None
return counts


def _repair_expanded_code_coverage(
lcov_data: str, prof_counts: dict[str, int]
) -> str:
"""
Starting with Rust 1.84, a function consisting entirely of
proc-macro expanded code (e.g. the impls generated by
``#[derive(...)]`` or ``#[pyo3::pymethods]``) gets a coverage
mapping containing only a residual region on the attribute line,
wired to a counter that never executes. llvm-cov therefore reports
the function -- and with it the attribute line -- as unexecuted,
even when the raw profile proves the function ran (its counters
are live; only the mapping is defective). We repair the two ways
this corrupts line coverage:

- If the raw profile's count for a function disagrees with the
lcov function summary (FNDA), trust the profile and mark the
function's start line as executed.
- If every function on a zero-count line is absent from the raw
profile, the compiler emitted only "unused function" placeholder
records for macro-generated impls that were never codegen'd
(e.g. an unused generated trait impl); the line's executability
is an artifact of the residual region, so drop it. Handwritten
dead code is unaffected: it is codegen'd, so it registers a
profile record (with count 0) and its body lines keep their
zero-count records either way.
"""
result: list[str] = []
record: list[str] = []
for line in lcov_data.splitlines():
record.append(line)
if line == "end_of_record":
result.extend(_repair_lcov_record(record, prof_counts))
record = []
assert not record
return "".join(f"{line}\n" for line in result)


def _repair_lcov_record(
record: list[str], prof_counts: dict[str, int]
) -> list[str]:
fn_start_lines: dict[str, int] = {}
mapped_counts: dict[str, int] = {}
for line in record:
if line.startswith("FN:"):
start, _, name = line.removeprefix("FN:").partition(",")
fn_start_lines[name] = int(start)
elif line.startswith("FNDA:"):
count, _, name = line.removeprefix("FNDA:").partition(",")
mapped_counts[name] = max(mapped_counts.get(name, 0), int(count))

repaired_lines: dict[int, int] = {}
line_has_profiled_fn: dict[int, bool] = {}
for fn_name, fn_start in fn_start_lines.items():
if fn_name in prof_counts:
line_has_profiled_fn[fn_start] = True
else:
line_has_profiled_fn.setdefault(fn_start, False)
if mapped_counts.get(fn_name) == 0:
true_count = prof_counts.get(fn_name, 0)
if true_count > 0:
repaired_lines[fn_start] = max(
repaired_lines.get(fn_start, 0), true_count
)
unmapped_lines = {
start
for start, profiled in line_has_profiled_fn.items()
if not profiled
}
if not repaired_lines and not unmapped_lines:
return record

result = []
for line in record:
if line.startswith("DA:"):
line_number, _, count = line.removeprefix("DA:").partition(",")
if int(count) == 0:
if int(line_number) in repaired_lines:
line = (
f"DA:{line_number},{repaired_lines[int(line_number)]}"
)
elif int(line_number) in unmapped_lines:
continue
result.append(line)
return result


def process_rust_coverage(
session: nox.Session,
rust_binaries: list[str],
Expand Down Expand Up @@ -393,5 +498,17 @@ def process_rust_coverage(
lambda m: "SF:src/rust/" + m.group(1).replace("\\", "/"),
lcov_data.replace("\r\n", "\n"),
)
prof_dump = session.run(
str(target_bindir / ("llvm-profdata" + BIN_EXT)),
"show",
"--all-functions",
"rust-cov.profdata",
silent=True,
external=True,
)
assert isinstance(prof_dump, str)
lcov_data = _repair_expanded_code_coverage(
lcov_data, _parse_profdata_function_counts(prof_dump)
)
with open(f"{uuid.uuid4()}.lcov", "w") as f:
f.write(lcov_data)