Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 92a82a8a authored by Henri Chataing's avatar Henri Chataing
Browse files

pdl: Implement support for tag ranges in parser and analyzer

Bug: 267339120
Test: cargo test
Ignore-AOSP-First: API breaking change in PDL w/ UWB dependant
Change-Id: Ib21df69550fb6634c5f45b163c8cb4a692f63d6b
Merged-In: Ib21df69550fb6634c5f45b163c8cb4a692f63d6b
(cherry picked from commit 2d2c1a7c)
parent 65936719
Loading
Loading
Loading
Loading
+31 −9
Original line number Original line Diff line number Diff line
@@ -161,22 +161,44 @@ A declaration is either:
>    enum_tag (`,` enum_tag)* `,`?
>    enum_tag (`,` enum_tag)* `,`?
>
>
> enum_tag:\
> enum_tag:\
>    enum_range | enum_value
>
> enum_range:\
>    [IDENTIFIER](#identifier) `=` [INTEGER](#integer) `..` [INTEGER](#integer)) (`{`\
>      enum_value_list\
>    `}`)?
>
> enum_value_list:\
>    enum_value (`,` enum_value)* `,`?
>
> enum_value:\
>    [IDENTIFIER](#identifier) `=` [INTEGER](#integer)
>    [IDENTIFIER](#identifier) `=` [INTEGER](#integer)


An *enumeration* or for short *enum*, is a declaration of a set of named [integer](#integer) constants.
An *enumeration* or for short *enum*, is a declaration of a set of named [integer](#integer) constants
or named [integer](#integer) ranges. [integer](#integer) ranges are inclusive in both ends.
[integer](#integer) value within a range *must* be unique. [integer](#integer) ranges
*must not* overlap.


The [integer](#integer) following the name specifies the bit size of the values.
The [integer](#integer) following the name specifies the bit size of the values.


```
```
enum CoffeeAddition: 3 {
enum CoffeeAddition: 5 {
  Empty = 0,
  Empty = 0,

  NonAlcoholic = 1..9 {
    Cream = 1,
    Cream = 1,
    Vanilla = 2,
    Vanilla = 2,
    Chocolate = 3,
    Chocolate = 3,
  Whisky = 4,
  },
  Rum = 5,

  Kahlua = 6,
  Alcoholic = 10..19 {
  Aquavit = 7
    Whisky = 10,
    Rum = 11,
    Kahlua = 12,
    Aquavit = 13,
  },

  Custom = 20..29,
}
}
```
```


+6 −2
Original line number Original line Diff line number Diff line
from dataclasses import dataclass, field
from dataclasses import dataclass, field
from typing import Optional, List, Dict
from typing import Optional, List, Dict, Tuple


constructors_ = dict()
constructors_ = dict()


@@ -37,7 +37,9 @@ class Node:
@node('tag')
@node('tag')
class Tag(Node):
class Tag(Node):
    id: str
    id: str
    value: int
    value: Optional[int] = field(default=None)
    range: Optional[Tuple[int, int]] = field(default=None)
    tags: Optional[List['Tag']] = field(default=None)




@node('constraint')
@node('constraint')
@@ -247,6 +249,8 @@ def convert_(obj: object) -> object:
    if isinstance(obj, list):
    if isinstance(obj, list):
        return [convert_(elt) for elt in obj]
        return [convert_(elt) for elt in obj]
    if isinstance(obj, object):
    if isinstance(obj, object):
        if 'start' in obj.keys() and 'end' in obj.keys():
            return (objs.start, obj.end)
        kind = obj['kind']
        kind = obj['kind']
        loc = obj['loc']
        loc = obj['loc']
        loc = SourceRange(loc['file'], SourceLocation(**loc['start']), SourceLocation(**loc['end']))
        loc = SourceRange(loc['file'], SourceLocation(**loc['start']), SourceLocation(**loc['end']))
+415 −51
Original line number Original line Diff line number Diff line
@@ -139,6 +139,10 @@ pub enum ErrorCode {
    MissingPayloadField = 37,
    MissingPayloadField = 37,
    RedundantArraySize = 38,
    RedundantArraySize = 38,
    InvalidPaddingField = 39,
    InvalidPaddingField = 39,
    InvalidTagRange = 40,
    DuplicateTagRange = 41,
    E42 = 42,
    E43 = 43,
}
}


impl From<ErrorCode> for String {
impl From<ErrorCode> for String {
@@ -279,6 +283,15 @@ fn bit_width(value: usize) -> usize {
    usize::BITS as usize - value.leading_zeros() as usize
    usize::BITS as usize - value.leading_zeros() as usize
}
}


/// Return the maximum value for a scalar value.
fn scalar_max(width: usize) -> usize {
    if width >= usize::BITS as usize {
        usize::MAX
    } else {
        (1 << width) - 1
    }
}

/// Check declaration identifiers.
/// Check declaration identifiers.
/// Raises error diagnostics for the following cases:
/// Raises error diagnostics for the following cases:
///      - undeclared parent identifier
///      - undeclared parent identifier
@@ -500,52 +513,193 @@ fn check_field_identifiers(file: &parser_ast::File) -> Result<(), Diagnostics> {
///      - duplicate tag identifier
///      - duplicate tag identifier
///      - duplicate tag value
///      - duplicate tag value
fn check_enum_declarations(file: &parser_ast::File) -> Result<(), Diagnostics> {
fn check_enum_declarations(file: &parser_ast::File) -> Result<(), Diagnostics> {
    let mut diagnostics: Diagnostics = Default::default();
    // Return the inclusive range with bounds correctly ordered.
    for decl in &file.declarations {
    // The analyzer will raise an error if the bounds are incorrectly ordered, but this
        if let DeclDesc::Enum { tags, width, .. } = &decl.desc {
    // will enable additional checks.
            let mut tags_by_id = HashMap::new();
    fn ordered_range(range: &std::ops::RangeInclusive<usize>) -> std::ops::RangeInclusive<usize> {
            let mut tags_by_value = HashMap::new();
        *std::cmp::min(range.start(), range.end())..=*std::cmp::max(range.start(), range.end())

    }
            for tag in tags {

                if let Some(prev) = tags_by_id.insert(&tag.id, tag) {
    fn check_tag_value<'a>(
        tag: &'a TagValue,
        range: std::ops::RangeInclusive<usize>,
        reserved_ranges: impl Iterator<Item = &'a TagRange>,
        tags_by_id: &mut HashMap<&'a str, SourceRange>,
        tags_by_value: &mut HashMap<usize, SourceRange>,
        diagnostics: &mut Diagnostics,
    ) {
        if let Some(prev) = tags_by_id.insert(&tag.id, tag.loc) {
            diagnostics.push(
            diagnostics.push(
                Diagnostic::error()
                Diagnostic::error()
                    .with_code(ErrorCode::DuplicateTagIdentifier)
                    .with_code(ErrorCode::DuplicateTagIdentifier)
                    .with_message(format!("duplicate tag identifier `{}`", tag.id))
                    .with_message(format!("duplicate tag identifier `{}`", tag.id))
                    .with_labels(vec![
                    .with_labels(vec![
                        tag.loc.primary(),
                        tag.loc.primary(),
                                prev.loc
                        prev.secondary()
                                    .secondary()
                            .with_message(format!("`{}` is first declared here", tag.id)),
                            .with_message(format!("`{}` is first declared here", tag.id)),
                    ]),
                    ]),
            )
            )
        }
        }
                if let Some(prev) = tags_by_value.insert(&tag.value, tag) {
        if let Some(prev) = tags_by_value.insert(tag.value, tag.loc) {
            diagnostics.push(
            diagnostics.push(
                Diagnostic::error()
                Diagnostic::error()
                    .with_code(ErrorCode::DuplicateTagValue)
                    .with_code(ErrorCode::DuplicateTagValue)
                    .with_message(format!("duplicate tag value `{}`", tag.value))
                    .with_message(format!("duplicate tag value `{}`", tag.value))
                    .with_labels(vec![
                    .with_labels(vec![
                        tag.loc.primary(),
                        tag.loc.primary(),
                                prev.loc.secondary().with_message(format!(
                        prev.secondary()
                                    "`{}` is first declared here",
                            .with_message(format!("`{}` is first declared here", tag.value)),
                                    tag.value
                                )),
                    ]),
                    ]),
            )
            )
        }
        }

        if !range.contains(&tag.value) {
                if bit_width(tag.value) > *width {
            diagnostics.push(
            diagnostics.push(
                Diagnostic::error()
                Diagnostic::error()
                    .with_code(ErrorCode::InvalidTagValue)
                    .with_code(ErrorCode::InvalidTagValue)
                    .with_message(format!(
                    .with_message(format!(
                                "tag value `{}` is larger than the maximum value",
                        "tag value `{}` is outside the range of valid values `{}..{}`",
                                tag.value
                        tag.value,
                        range.start(),
                        range.end()
                    ))
                    .with_labels(vec![tag.loc.primary()]),
            )
        }
        for reserved_range in reserved_ranges {
            if ordered_range(&reserved_range.range).contains(&tag.value) {
                diagnostics.push(
                    Diagnostic::error()
                        .with_code(ErrorCode::E43)
                        .with_message(format!(
                            "tag value `{}` is declared inside the reserved range `{} = {}..{}`",
                            tag.value,
                            reserved_range.id,
                            reserved_range.range.start(),
                            reserved_range.range.end()
                        ))
                        .with_labels(vec![tag.loc.primary()]),
                )
            }
        }
    }

    fn check_tag_range<'a>(
        tag: &'a TagRange,
        range: std::ops::RangeInclusive<usize>,
        tags_by_id: &mut HashMap<&'a str, SourceRange>,
        tags_by_value: &mut HashMap<usize, SourceRange>,
        diagnostics: &mut Diagnostics,
    ) {
        if let Some(prev) = tags_by_id.insert(&tag.id, tag.loc) {
            diagnostics.push(
                Diagnostic::error()
                    .with_code(ErrorCode::DuplicateTagIdentifier)
                    .with_message(format!("duplicate tag identifier `{}`", tag.id))
                    .with_labels(vec![
                        tag.loc.primary(),
                        prev.secondary()
                            .with_message(format!("`{}` is first declared here", tag.id)),
                    ]),
            )
        }
        if !range.contains(tag.range.start()) || !range.contains(tag.range.end()) {
            diagnostics.push(
                Diagnostic::error()
                    .with_code(ErrorCode::InvalidTagRange)
                    .with_message(format!(
                        "tag range `{}..{}` has bounds outside the range of valid values `{}..{}`",
                        tag.range.start(),
                        tag.range.end(),
                        range.start(),
                        range.end(),
                    ))
                    ))
                    .with_labels(vec![tag.loc.primary()]),
                    .with_labels(vec![tag.loc.primary()]),
            )
            )
        }
        }
        if tag.range.start() >= tag.range.end() {
            diagnostics.push(
                Diagnostic::error()
                    .with_code(ErrorCode::InvalidTagRange)
                    .with_message(format!(
                        "tag start value `{}` is greater than or equal to the end value `{}`",
                        tag.range.start(),
                        tag.range.end()
                    ))
                    .with_labels(vec![tag.loc.primary()]),
            )
        }

        let range = ordered_range(&tag.range);
        for tag in tag.tags.iter() {
            check_tag_value(tag, range.clone(), [].iter(), tags_by_id, tags_by_value, diagnostics)
        }
    }

    let mut diagnostics: Diagnostics = Default::default();
    for decl in &file.declarations {
        if let DeclDesc::Enum { tags, width, .. } = &decl.desc {
            let mut tags_by_id = HashMap::new();
            let mut tags_by_value = HashMap::new();
            let mut tags_by_range = tags
                .iter()
                .filter_map(|tag| match tag {
                    Tag::Range(tag) => Some(tag),
                    _ => None,
                })
                .collect::<Vec<_>>();

            for tag in tags {
                match tag {
                    Tag::Value(value) => check_tag_value(
                        value,
                        0..=scalar_max(*width),
                        tags_by_range.iter().copied(),
                        &mut tags_by_id,
                        &mut tags_by_value,
                        &mut diagnostics,
                    ),
                    Tag::Range(range) => check_tag_range(
                        range,
                        0..=scalar_max(*width),
                        &mut tags_by_id,
                        &mut tags_by_value,
                        &mut diagnostics,
                    ),
                }
            }

            // Order tag ranges by increasing bounds in order to check for intersecting ranges.
            tags_by_range.sort_by(|lhs, rhs| {
                ordered_range(&lhs.range).into_inner().cmp(&ordered_range(&rhs.range).into_inner())
            });

            // Iterate to check for overlap between tag ranges.
            // Not all potential errors are reported, but the check will report
            // at least one error if the values are incorrect.
            for tag in tags_by_range.windows(2) {
                let left_tag = tag[0];
                let right_tag = tag[1];
                let left = ordered_range(&left_tag.range);
                let right = ordered_range(&right_tag.range);
                if !(left.end() < right.start() || right.end() < left.start()) {
                    diagnostics.push(
                        Diagnostic::error()
                            .with_code(ErrorCode::DuplicateTagRange)
                            .with_message(format!(
                                "overlapping tag range `{}..{}`",
                                right.start(),
                                right.end()
                            ))
                            .with_labels(vec![
                                right_tag.loc.primary(),
                                left_tag.loc.secondary().with_message(format!(
                                    "`{}..{}` is first declared here",
                                    left.start(),
                                    left.end()
                                )),
                            ]),
                    )
                }
            }
            }
        }
        }
    }
    }
@@ -644,13 +798,24 @@ fn check_constraints(
                                    ])
                                    ])
                                    .with_notes(vec!["hint: expected enum value".to_owned()]),
                                    .with_notes(vec!["hint: expected enum value".to_owned()]),
                            ),
                            ),
                            Some(tag_id) => {
                            Some(tag_id) => match tags.iter().find(|tag| tag.id() == tag_id) {
                                if !tags.iter().any(|tag| &tag.id == tag_id) {
                                None => diagnostics.push(
                                    diagnostics.push(
                                    Diagnostic::error()
                                    Diagnostic::error()
                                        .with_code(ErrorCode::E20)
                                        .with_code(ErrorCode::E20)
                                        .with_message(format!("undeclared enum tag `{}`", tag_id))
                                        .with_labels(vec![
                                            constraint.loc.primary(),
                                            field.loc.secondary().with_message(format!(
                                                "`{}` is declared here",
                                                constraint.id
                                            )),
                                        ]),
                                ),
                                Some(Tag::Range { .. }) => diagnostics.push(
                                    Diagnostic::error()
                                        .with_code(ErrorCode::E42)
                                        .with_message(format!(
                                        .with_message(format!(
                                                "undeclared enum tag `{}`",
                                            "enum tag `{}` defines a range",
                                            tag_id
                                            tag_id
                                        ))
                                        ))
                                        .with_labels(vec![
                                        .with_labels(vec![
@@ -659,10 +824,13 @@ fn check_constraints(
                                                "`{}` is declared here",
                                                "`{}` is declared here",
                                                constraint.id
                                                constraint.id
                                            )),
                                            )),
                                        ])
                                        .with_notes(vec![
                                            "hint: expected enum tag with value".to_owned()
                                        ]),
                                        ]),
                                    )
                                ),
                                }
                                Some(_) => (),
                            }
                            },
                        }
                        }
                    }
                    }
                    Some(decl) => diagnostics.push(
                    Some(decl) => diagnostics.push(
@@ -921,7 +1089,7 @@ fn check_fixed_fields(
                            .with_notes(vec!["hint: expected enum identifier".to_owned()]),
                            .with_notes(vec!["hint: expected enum identifier".to_owned()]),
                    ),
                    ),
                    Some(enum_decl @ Decl { desc: DeclDesc::Enum { tags, .. }, .. }) => {
                    Some(enum_decl @ Decl { desc: DeclDesc::Enum { tags, .. }, .. }) => {
                        if !tags.iter().any(|tag| &tag.id == tag_id) {
                        if !tags.iter().any(|tag| tag.id() == tag_id) {
                            diagnostics.push(
                            diagnostics.push(
                                Diagnostic::error()
                                Diagnostic::error()
                                    .with_code(ErrorCode::E34)
                                    .with_code(ErrorCode::E34)
@@ -1578,6 +1746,30 @@ mod test {
        }
        }
        "#
        "#
        );
        );

        raises!(
            DuplicateTagIdentifier,
            r#"
        little_endian_packets
        enum A : 8 {
            X = 0,
            A = 1..10 {
                X = 1,
            }
        }
        "#
        );

        raises!(
            DuplicateTagIdentifier,
            r#"
        little_endian_packets
        enum A : 8 {
            X = 0,
            X = 1..10,
        }
        "#
        );
    }
    }


    #[test]
    #[test]
@@ -1592,6 +1784,19 @@ mod test {
        }
        }
        "#
        "#
        );
        );

        raises!(
            DuplicateTagValue,
            r#"
        little_endian_packets
        enum A : 8 {
            A = 1..10 {
                X = 1,
                Y = 1,
            }
        }
        "#
        );
    }
    }


    #[test]
    #[test]
@@ -1605,6 +1810,19 @@ mod test {
        }
        }
        "#
        "#
        );
        );

        raises!(
            InvalidTagValue,
            r#"
        little_endian_packets
        enum A : 8 {
            A = 0,
            X = 10..20 {
                B = 1,
            },
        }
        "#
        );
    }
    }


    #[test]
    #[test]
@@ -2120,6 +2338,152 @@ mod test {
        );
        );
    }
    }


    #[test]
    fn test_e40() {
        raises!(
            InvalidTagRange,
            r#"
        little_endian_packets
        enum A : 8 {
            X = 4..2,
        }
        "#
        );

        raises!(
            InvalidTagRange,
            r#"
        little_endian_packets
        enum A : 8 {
            X = 2..2,
        }
        "#
        );

        raises!(
            InvalidTagRange,
            r#"
        little_endian_packets
        enum A : 8 {
            X = 258..259,
        }
        "#
        );
    }

    #[test]
    fn test_e41() {
        raises!(
            DuplicateTagRange,
            r#"
        little_endian_packets
        enum A : 8 {
            X = 0..15,
            Y = 8..31,
        }
        "#
        );

        raises!(
            DuplicateTagRange,
            r#"
        little_endian_packets
        enum A : 8 {
            X = 8..31,
            Y = 0..15,
        }
        "#
        );

        raises!(
            DuplicateTagRange,
            r#"
        little_endian_packets
        enum A : 8 {
            X = 1..9,
            Y = 9..11,
        }
        "#
        );
    }

    #[test]
    fn test_e42() {
        raises!(
            E42,
            r#"
        little_endian_packets
        enum C : 8 { X = 0..15 }
        packet A { x : C }
        packet B : A (x = X) { }
        "#
        );

        raises!(
            E42,
            r#"
        little_endian_packets
        enum C : 8 { X = 0..15 }
        group A { x : C }
        packet B {
            A { x = X }
        }
        "#
        );
    }

    #[test]
    fn test_e43() {
        raises!(
            E43,
            r#"
        little_endian_packets
        enum A : 8 {
            A = 0,
            B = 1,
            X = 1..15,
        }
        "#
        );
    }

    #[test]
    fn test_enum_declaration() {
        valid!(
            r#"
        little_endian_packets
        enum A : 7 {
            X = 0,
            Y = 1,
            Z = 127,
        }
        "#
        );

        valid!(
            r#"
        little_endian_packets
        enum A : 7 {
            A = 50..100 {
                X = 50,
                Y = 100,
            },
            Z = 101,
        }
        "#
        );

        valid!(
            r#"
        little_endian_packets
        enum A : 7 {
            A = 50..100,
            X = 101,
        }
        "#
        );
    }

    fn desugar(text: &str) -> analyzer::ast::File {
    fn desugar(text: &str) -> analyzer::ast::File {
        let mut db = SourceDatabase::new();
        let mut db = SourceDatabase::new();
        let file =
        let file =
+49 −4
Original line number Original line Diff line number Diff line
@@ -55,14 +55,30 @@ pub struct Endianness {
    pub value: EndiannessValue,
    pub value: EndiannessValue,
}
}


#[derive(Debug, Serialize, Clone)]
#[derive(Debug, Clone, Serialize)]
#[serde(tag = "kind", rename = "tag")]
#[serde(tag = "kind", rename = "tag")]
pub struct Tag {
pub struct TagValue {
    pub id: String,
    pub id: String,
    pub loc: SourceRange,
    pub loc: SourceRange,
    pub value: usize,
    pub value: usize,
}
}


#[derive(Debug, Clone, Serialize)]
#[serde(tag = "kind", rename = "tag")]
pub struct TagRange {
    pub id: String,
    pub loc: SourceRange,
    pub range: std::ops::RangeInclusive<usize>,
    pub tags: Vec<TagValue>,
}

#[derive(Debug, Serialize, Clone, PartialEq, Eq)]
#[serde(untagged)]
pub enum Tag {
    Value(TagValue),
    Range(TagRange),
}

#[derive(Debug, Serialize, Clone)]
#[derive(Debug, Serialize, Clone)]
#[serde(tag = "kind", rename = "constraint")]
#[serde(tag = "kind", rename = "constraint")]
pub struct Constraint {
pub struct Constraint {
@@ -242,14 +258,43 @@ impl PartialEq for Endianness {
    }
    }
}
}


impl Eq for Tag {}
impl Eq for TagValue {}
impl PartialEq for Tag {
impl PartialEq for TagValue {
    fn eq(&self, other: &Self) -> bool {
    fn eq(&self, other: &Self) -> bool {
        // Implement structual equality, leave out loc.
        // Implement structual equality, leave out loc.
        self.id == other.id && self.value == other.value
        self.id == other.id && self.value == other.value
    }
    }
}
}


impl Eq for TagRange {}
impl PartialEq for TagRange {
    fn eq(&self, other: &Self) -> bool {
        // Implement structual equality, leave out loc.
        self.id == other.id && self.range == other.range && self.tags == other.tags
    }
}

impl Tag {
    pub fn id(&self) -> &str {
        match self {
            Tag::Value(TagValue { id, .. }) | Tag::Range(TagRange { id, .. }) => id,
        }
    }

    pub fn loc(&self) -> &SourceRange {
        match self {
            Tag::Value(TagValue { loc, .. }) | Tag::Range(TagRange { loc, .. }) => loc,
        }
    }

    pub fn value(&self) -> Option<usize> {
        match self {
            Tag::Value(TagValue { value, .. }) => Some(*value),
            Tag::Range(_) => None,
        }
    }
}

impl Eq for Constraint {}
impl Eq for Constraint {}
impl PartialEq for Constraint {
impl PartialEq for Constraint {
    fn eq(&self, other: &Self) -> bool {
    fn eq(&self, other: &Self) -> bool {
+2 −2
Original line number Original line Diff line number Diff line
@@ -683,10 +683,10 @@ fn generate_struct_decl(
fn generate_enum_decl(id: &str, tags: &[ast::Tag]) -> proc_macro2::TokenStream {
fn generate_enum_decl(id: &str, tags: &[ast::Tag]) -> proc_macro2::TokenStream {
    let name = format_ident!("{id}");
    let name = format_ident!("{id}");
    let variants =
    let variants =
        tags.iter().map(|t| format_ident!("{}", t.id.to_upper_camel_case())).collect::<Vec<_>>();
        tags.iter().map(|t| format_ident!("{}", t.id().to_upper_camel_case())).collect::<Vec<_>>();
    let values = tags
    let values = tags
        .iter()
        .iter()
        .map(|t| syn::parse_str::<syn::LitInt>(&format!("{:#x}", t.value)).unwrap())
        .map(|t| syn::parse_str::<syn::LitInt>(&format!("{:#x}", t.value().unwrap())).unwrap())
        .collect::<Vec<_>>();
        .collect::<Vec<_>>();
    let visitor_name = format_ident!("{id}Visitor");
    let visitor_name = format_ident!("{id}Visitor");


Loading