Experimenting with Rust in Chromium
source link: https://chromium.googlesource.com/chromium/src/+/refs/heads/main/docs/security/rust-toolchain.md
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Experimenting with Rust in Chromium
Parsing untrustworthy data is a major source of security bugs, and it's therefore against Chromium rules to do it in the browser process unless you can use a memory safe language.
For teams building browser process features which need to handle untrustworthy data, they usually have to do the parsing in a utility process which incurs a performance overhead and adds engineering complexity.
The Chrome security team is working to make a cross-platform memory safe language available to Chromium developers. This document describes how to use that language in Chromium. The language, at least for now, is Rust.
Guidelines
Support for Rust in Chromium is experimental. We appreciate your help in these experiments, but please remember that Rust is not supported for production use cases.
- any experiments must be reversible (you may have to write a C++ equivalent in order to ship)
- Rust code must not affect production Chrome binaries nor be shipped to Chrome users (we provide
#if defined(...)
and other facilities to make this easy) - so if you put Rust code in Chrome, the sole purpose is to help experiment and provide data for evaluation of future memory safe language options - Rust is not yet available on all Chromium platforms (just Linux and Android for now)
- Facilities and tooling in Rust are not as rich as other languages yet.
That said, if presence of Rust would make your feature easier, we are keen for you to join in our experiments. Here's how. Please also let us know your interest via [email protected]
.
Building with Rust support
Add enable_rust = true
in your gn
arguments. At the moment, this works only for Linux platforms (but see below for how to enable on other platforms).
Also add "use_rust": True
to your .gclient
file to enable fetching required tools and libraries:
solutions = [ { ... "custom_vars": { ... "use_rust": True, }, }, ]
See also Using VSCode.
GN support
Assume you want to add some Rust code to an existing C++ source_set
. Simply:
import("//build/rust/mixed_source_set.gni")
- Replace
source_set
withmixed_source_set
- Add
rs_sources = [ "src/lib.rs" ]
(and likelyrs_cxx_bindings
, see below) - Add your Rust code in
src/lib.rs
- In your C++ code, make Rust calls based on the
#if defined(RUST_ENABLED)
.
In toolchains with Rust disabled, your source_set
will continue to be a plain C++ source set and absolutely nothing will change.
In toolchains with Rust, RUST_ENABLED
will be defined and then you can call into Rust code (again, see the section on C++/Rust interop bindings below).
A note on source code naming
Within a mixed code source set, it‘s (currently) normal to have C/C++ code in its main directory, whilst Rust code goes into a subdirectory called src
(and the main file is always called lib.rs
.) This follows the practice of other teams, but if you don’t like it, that's fine: feel free to store your .rs
code alongside your .cc
code, but specify also rs_crate_root
in your mixed_source_set
.
I'm not using a source_set
There are equivalent templates for mixed_component
and mixed_executable
. But if you need to do something more sophisticated, you can create a new pure-Rust language target - see //build/rust/rust_source_set.gni
. C++ targets can simply depend on this Rust target but with the suffix _cpp_bindings
appended to the target name:
deps = [ "//path/to/my_rust_target:my_rust_target_cpp_bindings" ]
If your Rust code calls back into C++, this is more complex in order to avoid layering violations - look into mutually_dependent_target
in that .gni
file.
Unit tests
Rust supports unit tests within the primary source code files (e.g. see an example here). This section describes how to build and run such unit tests.
Automatically generated targets
GN templates that work with Rust sources will automatically generate a bonus gn
target called <your target name>_unittests
(for pure-Rust targets like cargo_crate
, executable
, or rust_source_set
) or <your target name>_rs_unittests
(for mixed C++/Rust targets like (mixed_component
, mixed_executable
, or mixed_source_set
). This bonus target builds:
- An
out/Default/<bonus target name>
executable containing Rust unit tests from your code - An
out/Default/bin/run_<bonus target name>
script that enables running the tests on Chromium bots.
Explicitly defined groups of tests
To group multiple Rust unit test executables into a single test step, please use the rust_unit_tests_group("my_test_group")
template:
rust_unit_tests_group("my_group_of_rust_unit_tests") { deps = [ "my_rust_source_set1", "my_rust_source_set2", "my_rust_executable", # ... ] }
The example above will build all the deps
. This will also generate a wrapper script that wraps all the Rust unit test executables from deps
and their transitive dependencies. In the example above, the script will be generated at out/Default/bin/run_my_group_of_rust_unit_tests
.
The generated script can be used for integration with Chromium bots, but can also be used as a convenience to manually/locally run all tests from the group. Run the script with the --help
argument to see more details (e.g. how to filter which tests to run).
Configuring running Rust unit tests on bots
To manually configure running Rust unit tests on bots, please follow the pattern from https://crrev.com/c/3322199:
- Define a new isolate in
//testing/buildbot/gn_isolate_map.pyl
:- Set
label
to the fully qualified name of either the implicit bonus target (e.g...._rs_unittests
) or the explicitrust_unit_tests_group
target. - Set
type
togenerated_script
- There are no requirements on the name of the new isolate, but typically it will have the same name as the target mentioned in the
label
.
- Set
- Define a new test step, or extend an existing test step in
//testing/buildbot/test_suites.pyl
(adding an entry referring to the new isolate above). Note that the tests grouped under the test step need to have uniform kind (e.g. cannot mix GTest and Rust tests). - Ensure that
//testing/buildbot/waterfalls.pyl
asks to run the test step on specific bots. The test step needs to be listed under theisolated_scripts
key (rather than undergtest_tests
key). - Run
//testing/buildbot/generate_buildbot_json.py
.
Future work:
- At present, there is no automatic integration of such unit tests into our existing test infrastructure, but this is something we're working on.
- At present, the bot integration only supports reporting whether the tests passed or failed, and doesn't capture results or output of individual tests.
- At present, there is no support for running native Rust unit tests on Android.
Third party dependencies
Adding Rust third party dependencies follows the same protocols as for C++ or other languages. But practically, Rust libraries are almost always distributed as cargo “crates” which have build scripts and metadata in Cargo.toml
files.
The crate you need may already be listed in //third_party/rust/third_party.toml
- if so, just depend upon it like this:
deps = [ "//third_party/rust/cxx/v1:lib" ]
(Only those crates explicitly listed in //third_party/rust/third_party.toml
are visible to first-party code; other crates in //third_party/rust
are transitive dependencies).
If you need to add new Rust third-party dependencies, there are scripts and gn templates to make it nearly automatic (except of course for review). Please reach out to [email protected]
for advice.
C++/Rust interop
There are multiple different solutions for Rust/C++ interop. In this phase of our experiments, we're supporting just one: cxx, described in this excellent online book.
To use this interop facility in Chromium:
- define your
#[cxx::bridge]
module in your.rs
file - in your
mixed_source_set
, addrs_cxx_bindings = [ "src/lib.rs" ]
- from your C++,
#ifdef RUST_ENABLED #include "path/to/your/target/src/lib.rs.h` #endif
You can now simply call functions and use types declared/defined in your CXX bridge. A typical usage might be to pass a const std::string&
or rust::Slice<const uint8_t>
from C++ into Rust and then return a struct with the parsed results.
If you need to call back into C++ from Rust, this is also supported - include!
directives within an extern "C++"
section should work:
#[cxx::bridge] mod ffi { unsafe extern "C++" { include!("path/to/my_cpp_header.h"); fn some_function_defined_in_cpp(); } } // Rust code calls ffi::some_function_defined_in_cpp()
Future work may expose existing C++ Chromium APIs to Rust with no need to declare the interface in a #[cxx::bridge]
module.
Dependencies between Rust targets
If your rust_source_set
exposes Rust APIs for other Rust targets in Chromium, those targets should be able to depend directly on your rust_source_set
target.
If you have a mixed_source_set
or any other component which is intended for both Rust and C++ consumers, please reach out to [email protected]
with your use-case. (This should be possible with the current gn rules but layering here is fragile so we'd rather discuss it.)
Example
To see an example of all this, look at //build/rust/tests/test_variable_source_set
.
Tooling
Known cases which don't work
- At the moment LTO doesn‘t work, so you can’t use
is_official_build = true
. (Bug.) - Windows doesn't work just yet. (Bug.)
Building on non-Linux platforms
The Rust toolchain is provided only for Linux and Android. To use it on other platforms, you will need to provide your own nightly Rust toolchain. You can then tell gn
about it using these gn
arguments:
enable_rust=true rust_sysroot_absolute="/Users/you/.rustup/toolchains/<toolchain name>" rustc_version="<your rustc version>" # add output of rustc -V # added_rust_stdlib_libs=[] # removed_rust_stdlib_libs=[]
The last two arguments are any Rust standard library .rlibs which have been added or removed between the version that‘s distributed for Linux/Android, and the version you’re using. They should rarely be necessary; if you get errors about missing standard libraries then adjust removed_rust_stdlib_libs
; if you get errors about undefined symbols then have a look in your equivalent of the .rustup/toolchains/<toolchain name>/lib/rustlib/<target>/lib
directory and add any new libraries which are not listed in //build/rust/std/BUILD.gn
to the added_rust_stlib_libs
list.
Using VSCode
- Ensure you're using the
rust-analyzer
extension for VSCode, rather than earlier forms of Rust support. - Run
gn
with this extra flag:gn gen out/Release --export-rust-project
. ln -s out/Release/rust-project.json rust-project.json
- When you run VSCode, or any other IDE that uses rust-analyzer it should detect the
rust-project.json
and use this to give you rich browsing, autocompletion, type annotations etc. for all the Rust within the Chromium codebase.
Source code format
git cl format
andgit cl presubmit
have been extended to automatically cover.rs
files on Linux. (Currently whole files are re-formatted and/or checked; support for only looking at the modified lines is not yet implemented.)- Rust defaults to 100-columns-wide formatting (similarly to Java). This may necessitate tweaking settings in various tools - e.g. in Gerrit you might want to increase the default “Diff width” under https://chromium-review.googlesource.com/settings/
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK