From 55b059e88a02fb5bc1d90ca33df343531db098cc Mon Sep 17 00:00:00 2001 From: Shriyans S Sahoo Date: Tue, 21 Apr 2026 23:00:09 +0530 Subject: [PATCH 1/5] Fixed special charcter escaping --- src/util.rs | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/src/util.rs b/src/util.rs index a90b83b0..cf9863d2 100644 --- a/src/util.rs +++ b/src/util.rs @@ -179,11 +179,24 @@ pub fn escape_brackets(s: &str) -> String { /// Escape basic html tags and brackets pub fn escape_special_chars(s: &str) -> Cow<'_, str> { - if s.contains('[') { - escape_brackets(s).into() - } else { - s.into() + if !s.contains('[') && !s.contains('&') && !s.contains('<') && !s.contains('>'){ + return s.into(); + } + let mut escaped = s.to_string(); + if escaped.contains('&') { + escaped = escaped.replace('&', "&"); + } + if escaped.contains('<') { + escaped = escaped.replace('<', "<"); } + if escaped.contains('>') { + escaped = escaped.replace('>', ">"); + } + if escaped.contains('[') { + escaped = escape_brackets(&escaped); + } + escaped.into() + } pub fn name_of(maybe_array: &MaybeArray, ignore_group: bool) -> String { @@ -513,3 +526,11 @@ fn pascalcase() { assert_eq!(to_pascal_case("FOO_BAR_1_2"), "FooBar1_2"); assert_eq!(to_pascal_case("FOO_BAR_1_2_"), "FooBar1_2_"); } + +fn test_escape_special_chars(){ + assert_eq!(escape_special_chars("Array[0]"), "Array\\[0\\]"); + assert_eq!(escape_special_chars("Enable & disable"), "Enable & disable"); + assert_eq!(escape_special_chars("Wait<10"), "Wait <10"); + assert_eq!(escape_special_chars("Delay>5"), "Delay >5"); + assert_eq!(escape_special_chars("Flags & [Status] >100"), "Flags & \\[Status\\] > 1"); +} \ No newline at end of file From 6749f3bb40766c46d81e420cc79d6737f77f9941 Mon Sep 17 00:00:00 2001 From: Shriyans S Sahoo Date: Tue, 21 Apr 2026 23:07:24 +0530 Subject: [PATCH 2/5] Fixed special charcter escaping --- src/util.rs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/util.rs b/src/util.rs index cf9863d2..9caf0dc5 100644 --- a/src/util.rs +++ b/src/util.rs @@ -527,10 +527,15 @@ fn pascalcase() { assert_eq!(to_pascal_case("FOO_BAR_1_2_"), "FooBar1_2_"); } -fn test_escape_special_chars(){ +#[test] +fn test_escape_special_chars() { assert_eq!(escape_special_chars("Array[0]"), "Array\\[0\\]"); assert_eq!(escape_special_chars("Enable & disable"), "Enable & disable"); - assert_eq!(escape_special_chars("Wait<10"), "Wait <10"); - assert_eq!(escape_special_chars("Delay>5"), "Delay >5"); - assert_eq!(escape_special_chars("Flags & [Status] >100"), "Flags & \\[Status\\] > 1"); -} \ No newline at end of file + assert_eq!(escape_special_chars("Wait < 10"), "Wait < 10"); + assert_eq!(escape_special_chars("Delay > 5"), "Delay > 5"); + assert_eq!( + escape_special_chars("Flags & [Status] > 100"), + "Flags & \\[Status\\] > 100" + ); +} + From 81c05a46a22f20fada59babf331e2c5c5b4d6135 Mon Sep 17 00:00:00 2001 From: Shriyans S Sahoo Date: Sat, 25 Apr 2026 01:54:43 +0530 Subject: [PATCH 3/5] added WCH ch32v30x to risc-v regression tests --- svd2rust-regress/src/tests.rs | 4 ++++ svd2rust-regress/tests.yml | 6 +++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/svd2rust-regress/src/tests.rs b/svd2rust-regress/src/tests.rs index f6c2f73d..68135b2b 100644 --- a/svd2rust-regress/src/tests.rs +++ b/svd2rust-regress/src/tests.rs @@ -27,6 +27,8 @@ pub enum Manufacturer { RaspberryPi, Renesas, Unknown, + GigaDevice, + WCH, } impl Manufacturer { @@ -51,6 +53,8 @@ impl Manufacturer { Renesas, TexasInstruments, Espressif, + GigaDevice, + WCH, ] } } diff --git a/svd2rust-regress/tests.yml b/svd2rust-regress/tests.yml index bf17e088..6b50d249 100644 --- a/svd2rust-regress/tests.yml +++ b/svd2rust-regress/tests.yml @@ -443,7 +443,11 @@ mfgr: SiFive chip: fu540 svd_url: https://raw.githubusercontent.com/riscv-rust/fu540-pac/master/fu540.svd - +- arch: riscv + mfgr: WCH + chip: ch32v30x + svd_url: https://raw.githubusercontent.com/ch32-rs/ch32v30x/main/ch32v30x.svd + # SiliconLabs - arch: cortex-m mfgr: SiliconLabs From af121360291c5c35147b20e20c0af05ee99a4d10 Mon Sep 17 00:00:00 2001 From: Shriyans S Sahoo Date: Sat, 25 Apr 2026 13:03:05 +0530 Subject: [PATCH 4/5] Add --strict-safe-access feature --- src/config.rs | 1 + src/generate/device.rs | 11 +++++++++-- src/generate/generic.rs | 2 ++ src/generate/generic_reg_vcell.rs | 24 ++++++++++++++++++++---- src/generate/register.rs | 13 +++++++++++-- src/main.rs | 7 +++++++ 6 files changed, 50 insertions(+), 8 deletions(-) diff --git a/src/config.rs b/src/config.rs index 820c8763..cae6b790 100644 --- a/src/config.rs +++ b/src/config.rs @@ -46,6 +46,7 @@ pub struct Config { pub settings_file: Option, /// Chip-specific settings pub settings: Settings, + pub strict_safe_access: bool, } impl Config { diff --git a/src/generate/device.rs b/src/generate/device.rs index c16a8eea..7427f0bc 100644 --- a/src/generate/device.rs +++ b/src/generate/device.rs @@ -137,7 +137,14 @@ pub fn render(d: &Device, config: &Config, device_x: &mut String) -> Result Result Reg { } } +impl Reg { + /// Reads the contents of a `ReadableSideEffect` register. + /// + /// # Safety + /// + /// Reading this register has side effects. + #[inline(always)] + pub unsafe fn read(&self) -> R { + R { + bits: self.register.get(), + _reg: marker::PhantomData, + } + } +} + impl Reg { /// Writes the reset value to `Writable` register. /// @@ -74,7 +89,7 @@ impl Reg { /// ``` /// In the latter case, other fields will be set to their reset value. #[inline(always)] - pub fn write(&self, f: F) -> REG::Ux + _WRITE_SAFETY_ fn write(&self, f: F) -> REG::Ux where F: FnOnce(&mut W) -> &mut W, { @@ -117,7 +132,7 @@ impl Reg { /// let state = periph.reg.write_and(|w| State::set(w.field1())); /// ``` #[inline(always)] - pub fn from_write(&self, f: F) -> T + _WRITE_SAFETY_ fn from_write(&self, f: F) -> T where F: FnOnce(&mut W) -> T, { @@ -208,7 +223,7 @@ impl Reg { /// ``` /// Other fields will have the value they had before the call to `modify`. #[inline(always)] - pub fn modify(&self, f: F) -> REG::Ux + _WRITE_SAFETY_ fn modify(&self, f: F) -> REG::Ux where for<'w> F: FnOnce(&R, &'w mut W) -> &'w mut W, { @@ -228,6 +243,7 @@ impl Reg { value } + /// Modifies the contents of the register by reading and then writing it /// and produces a value. /// @@ -260,7 +276,7 @@ impl Reg { /// ``` /// Other fields will have the value they had before the call to `modify`. #[inline(always)] - pub fn from_modify(&self, f: F) -> T + _WRITE_SAFETY_ fn from_modify(&self, f: F) -> T where for<'w> F: FnOnce(&R, &'w mut W) -> T, { diff --git a/src/generate/register.rs b/src/generate/register.rs index 4bff2b8f..c96f9179 100644 --- a/src/generate/register.rs +++ b/src/generate/register.rs @@ -385,10 +385,19 @@ pub fn render_register_mod( }); if can_read { - let doc = format!("`read()` method returns [`{mod_ty}::R`](R) reader structure",); + let is_side_effect = register.read_action.is_some() && config.strict_safe_access; + let (trait_name, read_safety_doc) = if is_side_effect { + (quote!(ReadableSideEffect), " (unsafe)") + } else { + (quote!(Readable), "") + }; + + let doc = format!( + "`read()` method returns [`{mod_ty}::R`](R) reader structure{read_safety_doc}", + ); mod_items.extend(quote! { #[doc = #doc] - impl crate::Readable for #regspec_ty {} + impl crate::#trait_name for #regspec_ty {} }); } if can_write { diff --git a/src/main.rs b/src/main.rs index 07de10cb..5eb01444 100755 --- a/src/main.rs +++ b/src/main.rs @@ -301,6 +301,13 @@ Ignore this option if you are not building your own FPGA based soft-cores."), .action(ArgAction::Set) .value_parser(["off", "error", "warn", "info", "debug", "trace"]), ) + .arg( + Arg::new("strict_safe_access") + .long("strict-safe-access") + .help("Marks all write accesses and read accesses with side effects as unsafe") + .action(ArgAction::SetTrue), + ) + .version(concat!( env!("CARGO_PKG_VERSION"), include_str!(concat!(env!("OUT_DIR"), "/commit-info.txt")) From dced5b83859e94a8aa6230d3498a2d799db374c2 Mon Sep 17 00:00:00 2001 From: Shriyans S Sahoo Date: Sat, 25 Apr 2026 22:59:09 +0530 Subject: [PATCH 5/5] Editing according to advice special char escaping to preserve HTML tags --- src/util.rs | 48 ++++++++++++++++++++++++++++++------------------ 1 file changed, 30 insertions(+), 18 deletions(-) diff --git a/src/util.rs b/src/util.rs index 9caf0dc5..151e9f63 100644 --- a/src/util.rs +++ b/src/util.rs @@ -15,6 +15,10 @@ use syn::{ punctuated::Punctuated, token::PathSep, Lit, LitInt, PathArguments, PathSegment, Type, TypePath, }; +use regex::Regex; +use std::sync::OnceLock; + + use anyhow::{anyhow, Result}; pub const BITS_PER_BYTE: u32 = 8; @@ -177,25 +181,34 @@ pub fn escape_brackets(s: &str) -> String { }) } +static TAG_RE: OnceLock = OnceLock::new(); + /// Escape basic html tags and brackets pub fn escape_special_chars(s: &str) -> Cow<'_, str> { - if !s.contains('[') && !s.contains('&') && !s.contains('<') && !s.contains('>'){ + let tag_re= TAG_RE.get_or_init(|| Regex::new(r"<[^>]+>|&[a-zA-Z0-9#]+;").unwrap()); + + if !s.contains('[') && !s.contains('&') && !s.contains('<') && !s.contains('>') { return s.into(); } - let mut escaped = s.to_string(); - if escaped.contains('&') { - escaped = escaped.replace('&', "&"); - } - if escaped.contains('<') { - escaped = escaped.replace('<', "<"); - } - if escaped.contains('>') { - escaped = escaped.replace('>', ">"); + + let mut last_end = 0; + let mut escaped = String::new(); + + for mat in tag_re.find_iter(s) { + let part = &s[last_end..mat.start()]; + escaped.push_str(&part.replace('&', "&").replace('<', "<").replace('>', ">")); + escaped.push_str(mat.as_str()); + last_end = mat.end(); } + let remaining = &s[last_end..]; + + escaped.push_str(&remaining.replace('&', "&").replace('<', "<").replace('>', ">")); + if escaped.contains('[') { - escaped = escape_brackets(&escaped); + escape_brackets(&escaped).into() + } else { + escaped.into() } - escaped.into() } @@ -529,13 +542,12 @@ fn pascalcase() { #[test] fn test_escape_special_chars() { - assert_eq!(escape_special_chars("Array[0]"), "Array\\[0\\]"); + assert_eq!(escape_special_chars("Enable & disable"), "Enable & disable"); assert_eq!(escape_special_chars("Wait < 10"), "Wait < 10"); - assert_eq!(escape_special_chars("Delay > 5"), "Delay > 5"); - assert_eq!( - escape_special_chars("Flags & [Status] > 100"), - "Flags & \\[Status\\] > 100" - ); + assert_eq!(escape_special_chars("This is bold"), "This is bold"); + assert_eq!(escape_special_chars("Use
"), "Use
"); + assert_eq!(escape_special_chars("A & B"), "A & B"); + assert_eq!(escape_special_chars("Array[0]"), "Array\\[0\\]"); }