Skip to content

Commit 8013d7d

Browse files
pkhrygui1117bkchr
authored
Disallow duplicate indexes using constant evaluation (#653)
* init * Improve error message (#681) * improve error message * Apply suggestions from code review Co-authored-by: Bastian Köcher <[email protected]> * finally understanding rust const environment * fmt --------- Co-authored-by: Bastian Köcher <[email protected]> --------- Co-authored-by: Guillaume Thiolliere <[email protected]> Co-authored-by: Bastian Köcher <[email protected]>
1 parent f43924c commit 8013d7d

11 files changed

+206
-32
lines changed

Cargo.lock

Lines changed: 27 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ byte-slice-cast = { version = "1.2.2", default-features = false }
2020
generic-array = { version = "0.14.7", optional = true }
2121
arbitrary = { version = "1.4.1", features = ["derive"], optional = true }
2222
impl-trait-for-tuples = "0.2.3"
23+
const_format = { version = "0.2.34" }
2324

2425
[dev-dependencies]
2526
criterion = "0.4.0"

derive/src/decode.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,12 +70,20 @@ pub fn quote(
7070
},
7171
}
7272
});
73+
let recurse_indices = variants
74+
.iter()
75+
.enumerate()
76+
.map(|(i, v)| (v.ident.clone(), utils::variant_index(v, i)));
77+
78+
let const_eval_check =
79+
utils::const_eval_check_variant_indexes(recurse_indices, crate_path);
7380

7481
let read_byte_err_msg =
7582
format!("Could not decode `{type_name}`, failed to read variant byte");
7683
let invalid_variant_err_msg =
7784
format!("Could not decode `{type_name}`, variant doesn't exist");
7885
quote! {
86+
#const_eval_check
7987
match #input.read_byte()
8088
.map_err(|e| e.chain(#read_byte_err_msg))?
8189
{

derive/src/encode.rs

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use std::str::from_utf8;
1717
use proc_macro2::{Ident, Span, TokenStream};
1818
use syn::{punctuated::Punctuated, spanned::Spanned, token::Comma, Data, Error, Field, Fields};
1919

20-
use crate::utils;
20+
use crate::utils::{self, const_eval_check_variant_indexes};
2121

2222
type FieldsList = Punctuated<Field, Comma>;
2323

@@ -338,7 +338,7 @@ fn impl_encode(data: &Data, type_name: &Ident, crate_path: &syn::Path) -> TokenS
338338
}
339339
};
340340

341-
[hinting, encoding]
341+
(hinting, encoding, index, name.clone())
342342
},
343343
Fields::Unnamed(ref fields) => {
344344
let fields = &fields.unnamed;
@@ -371,7 +371,7 @@ fn impl_encode(data: &Data, type_name: &Ident, crate_path: &syn::Path) -> TokenS
371371
}
372372
};
373373

374-
[hinting, encoding]
374+
(hinting, encoding, index, name.clone())
375375
},
376376
Fields::Unit => {
377377
let hinting = quote_spanned! { f.span() =>
@@ -387,13 +387,14 @@ fn impl_encode(data: &Data, type_name: &Ident, crate_path: &syn::Path) -> TokenS
387387
}
388388
};
389389

390-
[hinting, encoding]
390+
(hinting, encoding, index, name.clone())
391391
},
392392
}
393393
});
394394

395-
let recurse_hinting = recurse.clone().map(|[hinting, _]| hinting);
396-
let recurse_encoding = recurse.clone().map(|[_, encoding]| encoding);
395+
let recurse_hinting = recurse.clone().map(|(hinting, _, _, _)| hinting);
396+
let recurse_encoding = recurse.clone().map(|(_, encoding, _, _)| encoding);
397+
let recurse_variant_indices = recurse.clone().map(|(_, _, index, name)| (name, index));
397398

398399
let hinting = quote! {
399400
// The variant index uses 1 byte.
@@ -403,7 +404,11 @@ fn impl_encode(data: &Data, type_name: &Ident, crate_path: &syn::Path) -> TokenS
403404
}
404405
};
405406

407+
let const_eval_check =
408+
const_eval_check_variant_indexes(recurse_variant_indices, crate_path);
409+
406410
let encoding = quote! {
411+
#const_eval_check
407412
match *#self_ {
408413
#( #recurse_encoding )*,
409414
_ => (),

derive/src/utils.rs

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,61 @@ where
3838
})
3939
}
4040

41+
pub fn const_eval_check_variant_indexes(
42+
recurse_variant_indices: impl Iterator<Item = (syn::Ident, TokenStream)>,
43+
crate_path: &syn::Path,
44+
) -> TokenStream {
45+
let mut recurse_indices = vec![];
46+
for (ident, index) in recurse_variant_indices {
47+
let ident_str = ident.to_string();
48+
recurse_indices.push(quote! { (#index, #ident_str) });
49+
}
50+
let len = recurse_indices.len();
51+
52+
if len == 0 {
53+
return quote! {};
54+
}
55+
56+
quote! {
57+
const _: () = {
58+
const indices: [(usize, &'static str); #len] = [#( #recurse_indices ,)*];
59+
60+
// Returns if there is duplicate, and if there is some the duplicate indexes.
61+
const fn duplicate_info(array: &[(usize, &'static str); #len]) -> (bool, usize, usize) {
62+
let len = array.len();
63+
let mut i = 0;
64+
while i < len {
65+
let mut j = i + 1;
66+
while j < len {
67+
if array[i].0 == array[j].0 {
68+
return (true, i, j);
69+
}
70+
j += 1;
71+
}
72+
i += 1;
73+
}
74+
(false, 0, 0)
75+
}
76+
77+
const DUP_INFO: (bool, usize, usize) = duplicate_info(&indices);
78+
79+
if DUP_INFO.0 {
80+
let msg = #crate_path::__private::concatcp!(
81+
"Found variants that have duplicate indexes. Both `",
82+
indices[DUP_INFO.1].1,
83+
"` and `",
84+
indices[DUP_INFO.2].1,
85+
"` have the index `",
86+
indices[DUP_INFO.1].0,
87+
"`. Use different indexes for each variant."
88+
);
89+
90+
::core::panic!("{}", msg);
91+
}
92+
};
93+
}
94+
}
95+
4196
/// Look for a `#[scale(index = $int)]` attribute on a variant. If no attribute
4297
/// is found, fall back to the discriminant or just the variant index.
4398
pub fn variant_index(v: &Variant, i: usize) -> TokenStream {
@@ -47,7 +102,7 @@ pub fn variant_index(v: &Variant, i: usize) -> TokenStream {
47102
if nv.path.is_ident("index") {
48103
if let Expr::Lit(ExprLit { lit: Lit::Int(ref v), .. }) = nv.value {
49104
let byte = v
50-
.base10_parse::<u8>()
105+
.base10_parse::<usize>()
51106
.expect("Internal error, index attribute must have been checked");
52107
return Some(byte);
53108
}

src/lib.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,14 @@ pub mod alloc {
4141
pub use std::{alloc, borrow, boxed, collections, rc, string, sync, vec};
4242
}
4343

44+
/// Private module to reexport items used by derive macros.
45+
// We don't feature gate this module with `derive` to avoid compilation error when
46+
// `parity-scale-codec-derive` is used on its own and this crate doesn't have the feature enabled.
47+
#[doc(hidden)]
48+
pub mod __private {
49+
pub use const_format::concatcp;
50+
}
51+
4452
#[cfg(feature = "bit-vec")]
4553
mod bit_vec;
4654
mod btree_utils;
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#[derive(::parity_scale_codec::Decode, ::parity_scale_codec::Encode)]
2+
#[codec(crate = ::parity_scale_codec)]
3+
enum T {
4+
A = 1,
5+
B,
6+
}
7+
8+
#[derive(::parity_scale_codec::Decode, ::parity_scale_codec::Encode)]
9+
#[codec(crate = ::parity_scale_codec)]
10+
enum T2 {
11+
#[codec(index = 1)]
12+
A,
13+
B,
14+
}
15+
16+
fn main() {}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
error[E0080]: evaluation of constant value failed
2+
--> tests/scale_codec_ui/codec_discriminiant_variant_counted_in_default_index.rs:1:10
3+
|
4+
1 | #[derive(::parity_scale_codec::Decode, ::parity_scale_codec::Encode)]
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at 'Found variants that have duplicate indexes. Both `A` and `B` have the index `1`. Use different indexes for each variant.', $DIR/tests/scale_codec_ui/codec_discriminiant_variant_counted_in_default_index.rs:1:10
6+
|
7+
= note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `::core::panic` (in Nightly builds, run with -Z macro-backtrace for more info)
8+
9+
error[E0080]: evaluation of constant value failed
10+
--> tests/scale_codec_ui/codec_discriminiant_variant_counted_in_default_index.rs:1:40
11+
|
12+
1 | #[derive(::parity_scale_codec::Decode, ::parity_scale_codec::Encode)]
13+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at 'Found variants that have duplicate indexes. Both `A` and `B` have the index `1`. Use different indexes for each variant.', $DIR/tests/scale_codec_ui/codec_discriminiant_variant_counted_in_default_index.rs:1:40
14+
|
15+
= note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `::core::panic` (in Nightly builds, run with -Z macro-backtrace for more info)
16+
17+
error[E0080]: evaluation of constant value failed
18+
--> tests/scale_codec_ui/codec_discriminiant_variant_counted_in_default_index.rs:8:10
19+
|
20+
8 | #[derive(::parity_scale_codec::Decode, ::parity_scale_codec::Encode)]
21+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at 'Found variants that have duplicate indexes. Both `A` and `B` have the index `1`. Use different indexes for each variant.', $DIR/tests/scale_codec_ui/codec_discriminiant_variant_counted_in_default_index.rs:8:10
22+
|
23+
= note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `::core::panic` (in Nightly builds, run with -Z macro-backtrace for more info)
24+
25+
error[E0080]: evaluation of constant value failed
26+
--> tests/scale_codec_ui/codec_discriminiant_variant_counted_in_default_index.rs:8:40
27+
|
28+
8 | #[derive(::parity_scale_codec::Decode, ::parity_scale_codec::Encode)]
29+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at 'Found variants that have duplicate indexes. Both `A` and `B` have the index `1`. Use different indexes for each variant.', $DIR/tests/scale_codec_ui/codec_discriminiant_variant_counted_in_default_index.rs:8:40
30+
|
31+
= note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `::core::panic` (in Nightly builds, run with -Z macro-backtrace for more info)
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#[derive(::parity_scale_codec::Decode, ::parity_scale_codec::Encode)]
2+
#[codec(crate = ::parity_scale_codec)]
3+
enum T {
4+
A = 3,
5+
#[codec(index = 3)]
6+
B,
7+
}
8+
9+
#[derive(::parity_scale_codec::Decode, ::parity_scale_codec::Encode)]
10+
#[codec(crate = ::parity_scale_codec)]
11+
enum T1 {
12+
A,
13+
#[codec(index = 0)]
14+
B,
15+
}
16+
17+
fn main() {}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
error[E0080]: evaluation of constant value failed
2+
--> tests/scale_codec_ui/codec_duplicate_index.rs:1:10
3+
|
4+
1 | #[derive(::parity_scale_codec::Decode, ::parity_scale_codec::Encode)]
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at 'Found variants that have duplicate indexes. Both `A` and `B` have the index `3`. Use different indexes for each variant.', $DIR/tests/scale_codec_ui/codec_duplicate_index.rs:1:10
6+
|
7+
= note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `::core::panic` (in Nightly builds, run with -Z macro-backtrace for more info)
8+
9+
error[E0080]: evaluation of constant value failed
10+
--> tests/scale_codec_ui/codec_duplicate_index.rs:1:40
11+
|
12+
1 | #[derive(::parity_scale_codec::Decode, ::parity_scale_codec::Encode)]
13+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at 'Found variants that have duplicate indexes. Both `A` and `B` have the index `3`. Use different indexes for each variant.', $DIR/tests/scale_codec_ui/codec_duplicate_index.rs:1:40
14+
|
15+
= note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `::core::panic` (in Nightly builds, run with -Z macro-backtrace for more info)
16+
17+
error[E0080]: evaluation of constant value failed
18+
--> tests/scale_codec_ui/codec_duplicate_index.rs:9:10
19+
|
20+
9 | #[derive(::parity_scale_codec::Decode, ::parity_scale_codec::Encode)]
21+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at 'Found variants that have duplicate indexes. Both `A` and `B` have the index `0`. Use different indexes for each variant.', $DIR/tests/scale_codec_ui/codec_duplicate_index.rs:9:10
22+
|
23+
= note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `::core::panic` (in Nightly builds, run with -Z macro-backtrace for more info)
24+
25+
error[E0080]: evaluation of constant value failed
26+
--> tests/scale_codec_ui/codec_duplicate_index.rs:9:40
27+
|
28+
9 | #[derive(::parity_scale_codec::Decode, ::parity_scale_codec::Encode)]
29+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at 'Found variants that have duplicate indexes. Both `A` and `B` have the index `0`. Use different indexes for each variant.', $DIR/tests/scale_codec_ui/codec_duplicate_index.rs:9:40
30+
|
31+
= note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `::core::panic` (in Nightly builds, run with -Z macro-backtrace for more info)

tests/variant_number.rs

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,6 @@
11
use parity_scale_codec::Encode;
22
use parity_scale_codec_derive::Encode as DeriveEncode;
33

4-
#[test]
5-
fn discriminant_variant_counted_in_default_index() {
6-
#[derive(DeriveEncode)]
7-
enum T {
8-
A = 1,
9-
B,
10-
}
11-
12-
assert_eq!(T::A.encode(), vec![1]);
13-
assert_eq!(T::B.encode(), vec![1]);
14-
}
15-
164
#[test]
175
fn skipped_variant_not_counted_in_default_index() {
186
#[derive(DeriveEncode)]
@@ -25,16 +13,3 @@ fn skipped_variant_not_counted_in_default_index() {
2513
assert_eq!(T::A.encode(), vec![]);
2614
assert_eq!(T::B.encode(), vec![0]);
2715
}
28-
29-
#[test]
30-
fn index_attr_variant_counted_and_reused_in_default_index() {
31-
#[derive(DeriveEncode)]
32-
enum T {
33-
#[codec(index = 1)]
34-
A,
35-
B,
36-
}
37-
38-
assert_eq!(T::A.encode(), vec![1]);
39-
assert_eq!(T::B.encode(), vec![1]);
40-
}

0 commit comments

Comments
 (0)