Skip to content

Commit 223a9e0

Browse files
authored
feat: validate if template instance are configured (#1320)
* feat: Validate unconfigured template variables Adds a validation to find unconfigured template variables, for example the following code would return an error because `main.fb2.foo` is not configured.
1 parent 9e55aed commit 223a9e0

13 files changed

+571
-31
lines changed

compiler/plc_ast/src/ast.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,7 @@ impl PouType {
292292
}
293293
}
294294

295-
#[derive(Debug, PartialEq)]
295+
#[derive(Debug, PartialEq, Clone)]
296296
pub struct ConfigVariable {
297297
pub reference: AstNode,
298298
pub data_type: DataTypeDeclaration,

compiler/plc_diagnostics/src/diagnostics/diagnostics_registry.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,8 @@ lazy_static! {
207207
E103, Error, include_str!("./error_codes/E103.md"), // Immutable Hardware Binding
208208
E104, Error, include_str!("./error_codes/E104.md"), // Config Variable With Incomplete Address
209209
E105, Error, include_str!("./error_codes/E105.md"), // CONSTANT keyword in POU
210+
E107, Error, include_str!("./error_codes/E107.md"), // Missing configuration for template variable
211+
E108, Error, include_str!("./error_codes/E108.md"), // Template variable is configured multiple times
210212
);
211213
}
212214

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Missing configuration for template variable
2+
3+
A template variable was left unconfigured.
4+
5+
Erroneous code example:
6+
```
7+
VAR_CONFIG
8+
main.foo.bar AT %IX1.0 : BOOL;
9+
END_VAR
10+
11+
PROGRAM main
12+
VAR
13+
foo : foo_fb;
14+
END_VAR
15+
END_PROGRAM
16+
17+
FUNCTION_BLOCK foo_fb
18+
VAR
19+
bar AT %I* : BOOL;
20+
qux AT %I* : BOOL;
21+
END_VAR
22+
END_FUNCTION_BLOCK
23+
```
24+
25+
In this example a variable named `main.foo.qux` is declared as a template, however the `VAR_CONFIG`-block does not contain
26+
an address-configuration for it. Each template variable needs to be configured, otherwise it could lead to segmentation faults at runtime.
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Template variable is configured multiple times
2+
3+
A template variable is configured more than once, leading to ambiguity.
4+
5+
Erroneous code example:
6+
```
7+
VAR_CONFIG
8+
main.foo.bar AT %IX1.0 : BOOL;
9+
main.foo.bar AT %IX1.1 : BOOL;
10+
END_VAR
11+
12+
PROGRAM main
13+
VAR
14+
foo : foo_fb;
15+
END_VAR
16+
END_PROGRAM
17+
18+
FUNCTION_BLOCK foo_fb
19+
VAR
20+
bar AT %I* : BOOL;
21+
END_VAR
22+
END_FUNCTION_BLOCK
23+
```
24+
25+
In this example a variable named `main.foo.bar` has multiple configurations in the `VAR_CONFIG`-block. It is not clear which address this variable should map to - only a single configuration entry per instance-variable is allowed.

src/expression_path.rs

Lines changed: 95 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,22 @@
1-
use std::fmt::Display;
1+
use std::{fmt::Display, vec};
2+
3+
use plc_ast::ast::{AstNode, AstStatement, ConfigVariable, ReferenceAccess, ReferenceExpr};
4+
use plc_diagnostics::diagnostics::Diagnostic;
25

36
use crate::{index::Index, typesystem::Dimension};
47

5-
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
8+
#[derive(Clone, Debug, PartialEq, Eq)]
69
pub enum ExpressionPathElement<'idx> {
710
Name(&'idx str),
8-
ArrayAccess(&'idx [Dimension]),
11+
ArrayDimensions(&'idx [Dimension]),
12+
ArrayAccess(Vec<i128>),
913
}
1014

1115
impl Display for ExpressionPathElement<'_> {
1216
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1317
match self {
1418
ExpressionPathElement::Name(name) => write!(f, "{name}"),
19+
ExpressionPathElement::ArrayDimensions(_) => unimplemented!(),
1520
ExpressionPathElement::ArrayAccess(_) => unimplemented!(),
1621
}
1722
}
@@ -25,7 +30,7 @@ impl<'idx> From<&'idx str> for ExpressionPathElement<'idx> {
2530

2631
impl<'idx> From<&'idx [Dimension]> for ExpressionPathElement<'idx> {
2732
fn from(dims: &'idx [Dimension]) -> Self {
28-
ExpressionPathElement::ArrayAccess(dims)
33+
ExpressionPathElement::ArrayDimensions(dims)
2934
}
3035
}
3136

@@ -51,8 +56,8 @@ impl<'idx> ExpressionPath<'idx> {
5156
let mut levels: Vec<Vec<String>> = vec![];
5257
for seg in self.names.iter() {
5358
let level = match seg {
54-
crate::expression_path::ExpressionPathElement::Name(s) => vec![s.to_string()],
55-
crate::expression_path::ExpressionPathElement::ArrayAccess(dimensions) => {
59+
ExpressionPathElement::Name(s) => vec![s.to_string()],
60+
ExpressionPathElement::ArrayDimensions(dimensions) => {
5661
let mut array = dimensions.iter().map(|it| it.get_range_inclusive(index).unwrap()).fold(
5762
vec![],
5863
|curr, it| {
@@ -74,6 +79,14 @@ impl<'idx> ExpressionPath<'idx> {
7479
array.iter_mut().for_each(|s| *s = format!("[{s}]"));
7580
array
7681
}
82+
ExpressionPathElement::ArrayAccess(idx) => {
83+
let Some(first) = idx.first() else { unreachable!("Caught at the parsing stage") };
84+
85+
let access =
86+
idx.iter().skip(1).fold(format!("{first}"), |acc, idx| format!("{acc},{idx}"));
87+
88+
vec![format!("[{access}]")]
89+
}
7790
};
7891
levels.push(level);
7992
}
@@ -94,6 +107,76 @@ impl<'idx> ExpressionPath<'idx> {
94107
}
95108
}
96109

110+
impl<'a> TryFrom<&'a ConfigVariable> for ExpressionPath<'a> {
111+
type Error = Vec<Diagnostic>;
112+
113+
fn try_from(value: &'a ConfigVariable) -> Result<Self, Self::Error> {
114+
let mut names = get_expression_path_segments(&value.reference)?;
115+
names.reverse();
116+
Ok(Self { names })
117+
}
118+
}
119+
120+
// Transforms a `ConfigVariable`'s 'AstNode' into a collection of corresponding `ExpressionPathElement`s.
121+
// This function will traverse the AST top-to-bottom, collecting segments along the way, which means the order of the collection
122+
// needs to be reversed by the caller to match the written expression.
123+
fn get_expression_path_segments(node: &AstNode) -> Result<Vec<ExpressionPathElement>, Vec<Diagnostic>> {
124+
let mut paths = vec![];
125+
let mut diagnostics = vec![];
126+
let mut add_diagnostic = |location| {
127+
diagnostics.push(
128+
Diagnostic::new("VAR_CONFIG array access must be a literal integer").with_location(location),
129+
);
130+
};
131+
match &node.stmt {
132+
AstStatement::ReferenceExpr(ReferenceExpr { access: ReferenceAccess::Member(reference), base }) => {
133+
paths.push(ExpressionPathElement::Name(reference.get_flat_reference_name().unwrap_or_default()));
134+
if let Some(base) = base {
135+
match get_expression_path_segments(base) {
136+
Ok(v) => paths.extend(v),
137+
Err(e) => diagnostics.extend(e),
138+
};
139+
}
140+
}
141+
AstStatement::ReferenceExpr(ReferenceExpr { access: ReferenceAccess::Index(idx), base }) => {
142+
match &idx.as_ref().stmt {
143+
AstStatement::Literal(_) => {
144+
if let Some(v) = idx.get_literal_integer_value().map(|it| vec![it]) {
145+
paths.push(ExpressionPathElement::ArrayAccess(v))
146+
} else {
147+
add_diagnostic(&idx.location);
148+
}
149+
}
150+
AstStatement::ExpressionList(vec) => {
151+
let mut res = vec![];
152+
vec.iter().for_each(|idx: &AstNode| {
153+
if let Some(v) = idx.get_literal_integer_value() {
154+
res.push(v);
155+
} else {
156+
add_diagnostic(&idx.location);
157+
}
158+
});
159+
paths.push(ExpressionPathElement::ArrayAccess(res));
160+
}
161+
_ => add_diagnostic(&idx.location),
162+
}
163+
if let Some(base) = base {
164+
match get_expression_path_segments(base) {
165+
Ok(v) => paths.extend(v),
166+
Err(e) => diagnostics.extend(e),
167+
};
168+
}
169+
}
170+
_ => add_diagnostic(&node.location),
171+
};
172+
173+
if !diagnostics.is_empty() {
174+
return Err(diagnostics);
175+
}
176+
177+
Ok(paths)
178+
}
179+
97180
impl<'a> From<Vec<ExpressionPathElement<'a>>> for ExpressionPath<'a> {
98181
fn from(v: Vec<ExpressionPathElement<'a>>) -> Self {
99182
ExpressionPath { names: v }
@@ -132,7 +215,7 @@ mod tests {
132215
}];
133216

134217
let name = ExpressionPath {
135-
names: vec![ExpressionPathElement::Name("a"), ExpressionPathElement::ArrayAccess(&dims)],
218+
names: vec![ExpressionPathElement::Name("a"), ExpressionPathElement::ArrayDimensions(&dims)],
136219
};
137220
let index = Index::default();
138221
assert_eq!(name.expand(&index), vec!["a[-1]".to_string(), "a[0]".to_string(), "a[1]".to_string(),])
@@ -146,7 +229,7 @@ mod tests {
146229
}];
147230

148231
let name = ExpressionPath {
149-
names: vec![ExpressionPathElement::Name("a"), ExpressionPathElement::ArrayAccess(&dims)],
232+
names: vec![ExpressionPathElement::Name("a"), ExpressionPathElement::ArrayDimensions(&dims)],
150233
};
151234
let index = Index::default();
152235
assert_eq!(name.expand(&index), vec!["a[1]".to_string(),])
@@ -161,7 +244,7 @@ mod tests {
161244
];
162245

163246
let name = ExpressionPath {
164-
names: vec![ExpressionPathElement::Name("a"), ExpressionPathElement::ArrayAccess(&dims)],
247+
names: vec![ExpressionPathElement::Name("a"), ExpressionPathElement::ArrayDimensions(&dims)],
165248
};
166249
let index = Index::default();
167250
let mut res = name.expand(&index);
@@ -197,9 +280,9 @@ mod tests {
197280
let name = ExpressionPath {
198281
names: vec![
199282
ExpressionPathElement::Name("a"),
200-
ExpressionPathElement::ArrayAccess(&dims1),
201-
ExpressionPathElement::ArrayAccess(&dims2),
202-
ExpressionPathElement::ArrayAccess(&dims3),
283+
ExpressionPathElement::ArrayDimensions(&dims1),
284+
ExpressionPathElement::ArrayDimensions(&dims2),
285+
ExpressionPathElement::ArrayDimensions(&dims3),
203286
],
204287
};
205288
let index = Index::default();

src/index.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ use itertools::Itertools;
66
use rustc_hash::{FxHashSet, FxHasher};
77

88
use plc_ast::ast::{
9-
AstId, AstNode, AstStatement, DirectAccessType, GenericBinding, HardwareAccessType, LinkageType, PouType,
10-
TypeNature,
9+
AstId, AstNode, AstStatement, ConfigVariable, DirectAccessType, GenericBinding, HardwareAccessType,
10+
LinkageType, PouType, TypeNature,
1111
};
1212
use plc_diagnostics::diagnostics::Diagnostic;
1313
use plc_source::source_location::SourceLocation;
@@ -891,6 +891,8 @@ pub struct Index {
891891

892892
/// The labels contained in each pou
893893
labels: FxIndexMap<String, SymbolMap<String, Label>>,
894+
895+
config_variables: Vec<ConfigVariable>,
894896
}
895897

896898
impl Index {
@@ -1002,6 +1004,8 @@ impl Index {
10021004
//labels
10031005
self.labels.extend(other.labels);
10041006

1007+
self.config_variables.extend(other.config_variables);
1008+
10051009
//Constant expressions are intentionally not imported
10061010
// self.constant_expressions.import(other.constant_expressions)
10071011
}
@@ -1678,6 +1682,10 @@ impl Index {
16781682
pub fn get_labels(&self, pou_name: &str) -> Option<&SymbolMap<String, Label>> {
16791683
self.labels.get(pou_name)
16801684
}
1685+
1686+
pub fn get_config_variables(&self) -> &Vec<ConfigVariable> {
1687+
&self.config_variables
1688+
}
16811689
}
16821690

16831691
/// Returns a default initialization name for a variable or type

src/index/instance_iterator.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ impl<'idx> InstanceIterator<'idx> {
9191
self.inner = InstanceIterator::inner(
9292
self.index,
9393
variable.get_type_name(),
94-
&vec![entry].into(),
94+
&vec![entry.clone()].into(),
9595
self.filter,
9696
)
9797
.map(Box::new);

src/index/tests/snapshots/rusty__index__tests__instance_resolver_tests__array_instances_are_repeated.snap

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ expression: "index.find_instances().collect::<Vec<Instance<'_>>>()"
8787
Name(
8888
"aFb",
8989
),
90-
ArrayAccess(
90+
ArrayDimensions(
9191
[
9292
Dimension {
9393
start_offset: ConstExpression(
@@ -147,7 +147,7 @@ expression: "index.find_instances().collect::<Vec<Instance<'_>>>()"
147147
Name(
148148
"aFb",
149149
),
150-
ArrayAccess(
150+
ArrayDimensions(
151151
[
152152
Dimension {
153153
start_offset: ConstExpression(
@@ -246,7 +246,7 @@ expression: "index.find_instances().collect::<Vec<Instance<'_>>>()"
246246
Name(
247247
"aFb1",
248248
),
249-
ArrayAccess(
249+
ArrayDimensions(
250250
[
251251
Dimension {
252252
start_offset: ConstExpression(
@@ -320,7 +320,7 @@ expression: "index.find_instances().collect::<Vec<Instance<'_>>>()"
320320
Name(
321321
"aFb1",
322322
),
323-
ArrayAccess(
323+
ArrayDimensions(
324324
[
325325
Dimension {
326326
start_offset: ConstExpression(

src/index/tests/snapshots/rusty__index__tests__instance_resolver_tests__array_with_const_instances_are_repeated.snap

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ expression: "index.find_instances().collect::<Vec<Instance<'_>>>()"
131131
Name(
132132
"aFb",
133133
),
134-
ArrayAccess(
134+
ArrayDimensions(
135135
[
136136
Dimension {
137137
start_offset: ConstExpression(
@@ -191,7 +191,7 @@ expression: "index.find_instances().collect::<Vec<Instance<'_>>>()"
191191
Name(
192192
"aFb",
193193
),
194-
ArrayAccess(
194+
ArrayDimensions(
195195
[
196196
Dimension {
197197
start_offset: ConstExpression(

src/index/visitor.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@ pub fn visit(unit: &CompilationUnit) -> Index {
3232
for implementation in &unit.implementations {
3333
visit_implementation(&mut index, implementation);
3434
}
35+
36+
for config_variable in &unit.var_config {
37+
index.config_variables.push(config_variable.clone());
38+
}
39+
3540
index
3641
}
3742

0 commit comments

Comments
 (0)