1use crate::cst::CstNode;
23use crate::errors::Span;
24use crate::features::IdlFeatures;
25use crate::grammar::ProductionId;
26
27#[derive(Debug, Clone, PartialEq, Eq)]
30pub struct FeatureGateError {
31 pub production_id: ProductionId,
33 pub production_name: &'static str,
35 pub required_feature: &'static str,
37 pub span: Span,
39}
40
41impl core::fmt::Display for FeatureGateError {
42 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
43 write!(
44 f,
45 "use of `{}` requires feature `{}` (currently disabled in ParserConfig::features)",
46 self.production_name, self.required_feature
47 )
48 }
49}
50
51impl std::error::Error for FeatureGateError {}
52
53#[must_use]
60pub fn alternative_required_feature(prod_id: ProductionId, alt_idx: usize) -> Option<&'static str> {
61 use crate::grammar::idl42::{
62 ID_INTERFACE_KIND, ID_OP_DCL, ID_VALUE_HEADER, ID_VALUE_INHERITANCE_SPEC,
63 };
64 match prod_id {
65 id if id == ID_OP_DCL && (alt_idx == 0 || alt_idx == 1) => Some("corba_oneway_op"),
71 id if id == ID_INTERFACE_KIND && alt_idx == 0 => Some("corba_abstract_interface"),
75 id if id == ID_INTERFACE_KIND && alt_idx == 1 => Some("corba_local_interface"),
76 id if id == ID_VALUE_HEADER && (alt_idx == 0 || alt_idx == 2) => {
81 Some("corba_value_types_extras")
82 }
83 id if id == ID_VALUE_INHERITANCE_SPEC && alt_idx == 0 => Some("corba_value_types_extras"),
85 _ => None,
86 }
87}
88
89#[must_use]
92pub fn production_required_feature(prod_id: ProductionId) -> Option<&'static str> {
93 use crate::grammar::idl42::{
94 ID_COMPONENT_BODY, ID_COMPONENT_DCL, ID_COMPONENT_DEF, ID_COMPONENT_EXPORT,
95 ID_COMPONENT_FORWARD_DCL, ID_COMPONENT_HEADER, ID_COMPONENT_INHERITANCE_SPEC,
96 ID_CONNECTOR_DCL, ID_CONNECTOR_EXPORT, ID_CONNECTOR_HEADER, ID_CONNECTOR_INHERIT_SPEC,
97 ID_CONSUMES_DCL, ID_EMITS_DCL, ID_EVENT_DCL, ID_EVENT_DEF, ID_EVENT_FORWARD_DCL,
98 ID_EVENT_HEADER, ID_FACTORY_DCL, ID_FACTORY_PARAM_DCLS, ID_FINDER_DCL, ID_HOME_BODY,
99 ID_HOME_DCL, ID_HOME_EXPORT, ID_HOME_HEADER, ID_HOME_INHERITANCE_SPEC, ID_IMPORT_DCL,
100 ID_IMPORTED_SCOPE, ID_INIT_DCL, ID_INIT_PARAM_DCL, ID_INIT_PARAM_DCLS, ID_INTERFACE_TYPE,
101 ID_NATIVE_DCL, ID_PORT_BODY, ID_PORT_DCL, ID_PORT_EXPORT, ID_PORT_REF, ID_PORTTYPE_DCL,
102 ID_PRIMARY_KEY_SPEC, ID_PROVIDES_DCL, ID_PUBLISHES_DCL, ID_STATE_MEMBER,
103 ID_SUPPORTED_INTERFACE_SPEC, ID_TEMPLATE_MODULE_DCL, ID_TEMPLATE_MODULE_INST,
104 ID_TYPE_ID_DCL, ID_TYPE_PREFIX_DCL, ID_USES_DCL, ID_VALUE_DEF, ID_VALUE_ELEMENT,
105 ID_VALUE_HEADER, ID_VALUE_INHERITANCE_NAME_LIST, ID_VALUE_INHERITANCE_SPEC,
106 };
107 match prod_id {
108 id if id == ID_NATIVE_DCL => Some("corba_native"),
109 id if id == ID_VALUE_DEF
111 || id == ID_VALUE_HEADER
112 || id == ID_VALUE_INHERITANCE_SPEC
113 || id == ID_VALUE_INHERITANCE_NAME_LIST
114 || id == ID_VALUE_ELEMENT
115 || id == ID_STATE_MEMBER
116 || id == ID_INIT_DCL
117 || id == ID_INIT_PARAM_DCLS
118 || id == ID_INIT_PARAM_DCL =>
119 {
120 Some("corba_value_types_full")
121 }
122 id if id == ID_TYPE_ID_DCL || id == ID_TYPE_PREFIX_DCL => Some("corba_repository_ids"),
124 id if id == ID_IMPORT_DCL || id == ID_IMPORTED_SCOPE => Some("corba_import"),
126 id if id == ID_COMPONENT_DCL
128 || id == ID_COMPONENT_FORWARD_DCL
129 || id == ID_COMPONENT_DEF
130 || id == ID_COMPONENT_HEADER
131 || id == ID_COMPONENT_INHERITANCE_SPEC
132 || id == ID_COMPONENT_BODY
133 || id == ID_COMPONENT_EXPORT
134 || id == ID_PROVIDES_DCL
135 || id == ID_USES_DCL
136 || id == ID_INTERFACE_TYPE
137 || id == ID_SUPPORTED_INTERFACE_SPEC =>
138 {
139 Some("corba_components")
140 }
141 id if id == ID_HOME_DCL
143 || id == ID_HOME_HEADER
144 || id == ID_HOME_INHERITANCE_SPEC
145 || id == ID_PRIMARY_KEY_SPEC
146 || id == ID_HOME_BODY
147 || id == ID_HOME_EXPORT
148 || id == ID_FACTORY_DCL
149 || id == ID_FACTORY_PARAM_DCLS
150 || id == ID_FINDER_DCL =>
151 {
152 Some("corba_homes")
153 }
154 id if id == ID_EVENT_DCL
156 || id == ID_EVENT_FORWARD_DCL
157 || id == ID_EVENT_DEF
158 || id == ID_EVENT_HEADER
159 || id == ID_EMITS_DCL
160 || id == ID_PUBLISHES_DCL
161 || id == ID_CONSUMES_DCL =>
162 {
163 Some("corba_eventtypes")
164 }
165 id if id == ID_PORTTYPE_DCL
167 || id == ID_PORT_BODY
168 || id == ID_PORT_REF
169 || id == ID_PORT_EXPORT
170 || id == ID_PORT_DCL
171 || id == ID_CONNECTOR_DCL
172 || id == ID_CONNECTOR_HEADER
173 || id == ID_CONNECTOR_INHERIT_SPEC
174 || id == ID_CONNECTOR_EXPORT =>
175 {
176 Some("corba_ports")
177 }
178 id if id == ID_TEMPLATE_MODULE_DCL || id == ID_TEMPLATE_MODULE_INST => {
183 Some("corba_template_modules")
184 }
185 _ => None,
186 }
187}
188
189#[must_use]
195pub fn is_feature_enabled(features: &IdlFeatures, name: &str) -> bool {
196 match name {
197 "corba_value_types_full" => features.corba_value_types_full,
198 "corba_value_types_extras" => features.corba_value_types_extras,
199 "corba_repository_ids" => features.corba_repository_ids,
200 "corba_import" => features.corba_import,
201 "corba_local_interface" => features.corba_local_interface,
202 "corba_abstract_interface" => features.corba_abstract_interface,
203 "corba_object_base" => features.corba_object_base,
204 "corba_oneway_op" => features.corba_oneway_op,
205 "corba_context" => features.corba_context,
206 "corba_components" => features.corba_components,
207 "corba_homes" => features.corba_homes,
208 "corba_eventtypes" => features.corba_eventtypes,
209 "corba_ports" => features.corba_ports,
210 "corba_template_modules" => features.corba_template_modules,
211 "corba_native" => features.corba_native,
212 "preprocessor_full" => features.preprocessor_full,
213 "preprocessor_warning_line" => features.preprocessor_warning_line,
214 "vendor_rti" => features.vendor_rti,
215 "vendor_opensplice" => features.vendor_opensplice,
216 "vendor_opensplice_legacy" => features.vendor_opensplice_legacy,
217 "vendor_cyclonedds" => features.vendor_cyclonedds,
218 "vendor_fastdds" => features.vendor_fastdds,
219 _ => false,
220 }
221}
222
223#[must_use]
232pub fn validate<'a>(cst: &'a CstNode<'a>, features: &IdlFeatures) -> Vec<FeatureGateError> {
233 let mut errors = Vec::new();
234 let mut stack: Vec<&CstNode<'_>> = vec![cst];
235 while let Some(node) = stack.pop() {
236 if let Some(prod_id) = node.production() {
237 if let Some(required) = production_required_feature(prod_id) {
239 if !is_feature_enabled(features, required) {
240 errors.push(FeatureGateError {
241 production_id: prod_id,
242 production_name: production_name_of(prod_id),
243 required_feature: required,
244 span: node.span,
245 });
246 }
247 }
248 if let Some(alt_idx) = node.alternative_index() {
250 if let Some(required) = alternative_required_feature(prod_id, alt_idx) {
251 if !is_feature_enabled(features, required) {
252 errors.push(FeatureGateError {
253 production_id: prod_id,
254 production_name: production_name_of(prod_id),
255 required_feature: required,
256 span: node.span,
257 });
258 }
259 }
260 }
261 }
262 for child in &node.children {
263 stack.push(child);
264 }
265 }
266 errors
267}
268
269fn production_name_of(prod_id: ProductionId) -> &'static str {
270 crate::grammar::idl42::IDL_42
271 .production(prod_id)
272 .map(|p| p.name)
273 .unwrap_or("unknown")
274}
275
276#[cfg(test)]
277mod tests {
278 #![allow(clippy::expect_used, clippy::panic, clippy::unwrap_used)]
279
280 use super::*;
281 use crate::cst::build_cst;
282 use crate::engine::Engine;
283 use crate::grammar::idl42::IDL_42;
284 use crate::lexer::Tokenizer;
285
286 fn check(src: &str, features: IdlFeatures) -> Vec<FeatureGateError> {
287 let src = src.to_string();
290 std::thread::Builder::new()
291 .stack_size(64 * 1024 * 1024)
292 .spawn(move || {
293 let tokenizer = Tokenizer::for_grammar(&IDL_42);
294 let stream = tokenizer.tokenize(&src).expect("lex");
295 let engine = Engine::new(&IDL_42);
296 let result = engine.recognize(stream.tokens()).expect("recognize");
297 let cst =
301 build_cst(engine.compiled_grammar(), stream.tokens(), &result).expect("cst");
302 validate(&cst, &features)
303 })
304 .expect("spawn")
305 .join()
306 .expect("join")
307 }
308
309 #[test]
310 fn no_corba_use_no_errors_in_dds_basic() {
311 let src = "module svc { struct Topic { long id; }; };";
314 let errors = check(src, IdlFeatures::dds_basic());
315 assert!(errors.is_empty(), "got {errors:?}");
316 }
317
318 #[test]
319 fn no_corba_use_no_errors_in_dds_extensible() {
320 let src = "struct Foo { long x; long y; };";
321 let errors = check(src, IdlFeatures::dds_extensible());
322 assert!(errors.is_empty(), "got {errors:?}");
323 }
324
325 #[test]
326 fn dds_extensible_allows_native() {
327 let errors = check("native Handle;", IdlFeatures::dds_extensible());
329 assert!(errors.is_empty(), "got {errors:?}");
330 }
331
332 #[test]
333 fn dds_basic_rejects_native() {
334 let errors = check("native Handle;", IdlFeatures::dds_basic());
336 assert!(
337 errors
338 .iter()
339 .any(|e| e.required_feature == "corba_native" && e.production_name == "native_dcl"),
340 "expected corba_native gate error, got {errors:?}"
341 );
342 }
343
344 #[test]
345 fn corba_full_allows_native() {
346 let errors = check("native Handle;", IdlFeatures::corba_full());
347 assert!(errors.is_empty(), "got {errors:?}");
348 }
349
350 #[test]
351 fn opensplice_legacy_allows_native() {
352 let errors = check("native Handle;", IdlFeatures::opensplice_legacy());
353 assert!(errors.is_empty(), "got {errors:?}");
354 }
355
356 #[test]
357 fn is_feature_enabled_matches_struct_fields() {
358 let f = IdlFeatures::all();
359 assert!(is_feature_enabled(&f, "corba_native"));
360 assert!(is_feature_enabled(&f, "vendor_rti"));
361 assert!(!is_feature_enabled(&f, "unknown_feature"));
362 }
363
364 #[test]
365 fn feature_gate_error_has_helpful_message() {
366 let errors = check("native Handle;", IdlFeatures::dds_basic());
367 let msg = errors[0].to_string();
368 assert!(msg.contains("native_dcl"), "got: {msg}");
369 assert!(msg.contains("corba_native"), "got: {msg}");
370 assert!(msg.contains("disabled"), "got: {msg}");
371 }
372
373 #[test]
378 fn dds_extensible_rejects_value_def() {
379 let errors = check("valuetype V {};", IdlFeatures::dds_extensible());
381 assert!(
382 errors
383 .iter()
384 .any(|e| e.required_feature == "corba_value_types_full"),
385 "expected corba_value_types_full gate error, got {errors:?}"
386 );
387 }
388
389 #[test]
390 fn corba_full_allows_value_def() {
391 let errors = check("valuetype V { public long x; };", IdlFeatures::corba_full());
392 assert!(errors.is_empty(), "got {errors:?}");
393 }
394
395 #[test]
396 fn opensplice_legacy_allows_value_def_with_factory() {
397 let errors = check(
398 "valuetype V { exception E {}; factory create() raises (E); };",
399 IdlFeatures::opensplice_legacy(),
400 );
401 assert!(errors.is_empty(), "got {errors:?}");
402 }
403
404 #[test]
405 fn dds_basic_allows_value_box() {
406 let errors = check("valuetype Box long;", IdlFeatures::dds_basic());
408 assert!(
409 errors.is_empty(),
410 "value_box bleibt ungated, got {errors:?}"
411 );
412 }
413
414 #[test]
419 fn dds_extensible_rejects_typeid() {
420 let errors = check(
421 r#"typeid Foo "IDL:foo:1.0";"#,
422 IdlFeatures::dds_extensible(),
423 );
424 assert!(
425 errors
426 .iter()
427 .any(|e| e.required_feature == "corba_repository_ids"),
428 "expected corba_repository_ids gate error, got {errors:?}"
429 );
430 }
431
432 #[test]
433 fn dds_extensible_rejects_typeprefix() {
434 let errors = check(
435 r#"typeprefix Foo "company";"#,
436 IdlFeatures::dds_extensible(),
437 );
438 assert!(
439 errors
440 .iter()
441 .any(|e| e.required_feature == "corba_repository_ids"),
442 "got {errors:?}"
443 );
444 }
445
446 #[test]
447 fn dds_extensible_rejects_import() {
448 let errors = check("import Foo;", IdlFeatures::dds_extensible());
449 assert!(
450 errors.iter().any(|e| e.required_feature == "corba_import"),
451 "got {errors:?}"
452 );
453 }
454
455 #[test]
456 fn corba_full_allows_repository_ids_and_import() {
457 let errors = check(
458 r#"typeid Foo "IDL:foo:1.0"; typeprefix M "company"; import Bar;"#,
459 IdlFeatures::corba_full(),
460 );
461 assert!(errors.is_empty(), "got {errors:?}");
462 }
463
464 #[test]
465 fn opensplice_legacy_allows_repository_ids() {
466 let errors = check(
467 r#"typeid Foo "IDL:foo:1.0";"#,
468 IdlFeatures::opensplice_legacy(),
469 );
470 assert!(errors.is_empty(), "got {errors:?}");
471 }
472
473 #[test]
478 fn dds_extensible_rejects_oneway_op() {
479 let errors = check(
480 "interface S { oneway void log(in string m); };",
481 IdlFeatures::dds_extensible(),
482 );
483 assert!(
484 errors
485 .iter()
486 .any(|e| e.required_feature == "corba_oneway_op"),
487 "expected corba_oneway_op gate error, got {errors:?}"
488 );
489 }
490
491 #[test]
492 fn dds_extensible_rejects_abstract_interface() {
493 let errors = check(
494 "abstract interface IBase {};",
495 IdlFeatures::dds_extensible(),
496 );
497 assert!(
498 errors
499 .iter()
500 .any(|e| e.required_feature == "corba_abstract_interface"),
501 "got {errors:?}"
502 );
503 }
504
505 #[test]
506 fn dds_extensible_rejects_local_interface() {
507 let errors = check("local interface ILocal {};", IdlFeatures::dds_extensible());
508 assert!(
509 errors
510 .iter()
511 .any(|e| e.required_feature == "corba_local_interface"),
512 "got {errors:?}"
513 );
514 }
515
516 #[test]
517 fn corba_full_allows_oneway_and_abstract_local() {
518 let errors = check(
519 "abstract interface IA {}; local interface IL {}; \
520 interface S { oneway void log(in string m); };",
521 IdlFeatures::corba_full(),
522 );
523 assert!(errors.is_empty(), "got {errors:?}");
524 }
525
526 #[test]
527 fn dds_extensible_allows_plain_op() {
528 let errors = check(
530 "interface S { void log(in string m); };",
531 IdlFeatures::dds_extensible(),
532 );
533 assert!(errors.is_empty(), "got {errors:?}");
534 }
535
536 #[test]
537 fn opensplice_legacy_allows_oneway_and_abstract_interface() {
538 let errors = check(
539 "abstract interface IA {}; interface S { oneway void log(in string m); };",
540 IdlFeatures::opensplice_legacy(),
541 );
542 assert!(errors.is_empty(), "got {errors:?}");
543 }
544
545 #[test]
550 fn corba_full_minus_extras_rejects_custom_valuetype() {
551 let mut f = IdlFeatures::corba_full();
553 f.corba_value_types_extras = false;
554 let errors = check("custom valuetype V {};", f);
555 assert!(
556 errors
557 .iter()
558 .any(|e| e.required_feature == "corba_value_types_extras"),
559 "expected corba_value_types_extras gate, got {errors:?}"
560 );
561 }
562
563 #[test]
564 fn corba_full_minus_extras_rejects_abstract_valuetype() {
565 let mut f = IdlFeatures::corba_full();
566 f.corba_value_types_extras = false;
567 let errors = check("abstract valuetype V {};", f);
568 assert!(
569 errors
570 .iter()
571 .any(|e| e.required_feature == "corba_value_types_extras"),
572 "got {errors:?}"
573 );
574 }
575
576 #[test]
577 fn corba_full_minus_extras_rejects_truncatable() {
578 let mut f = IdlFeatures::corba_full();
579 f.corba_value_types_extras = false;
580 let errors = check("valuetype Derived : truncatable Base {};", f);
581 assert!(
582 errors
583 .iter()
584 .any(|e| e.required_feature == "corba_value_types_extras"),
585 "got {errors:?}"
586 );
587 }
588
589 #[test]
590 fn corba_full_allows_custom_abstract_truncatable() {
591 let errors = check(
592 "abstract valuetype IA {}; \
593 custom valuetype V : truncatable Base {};",
594 IdlFeatures::corba_full(),
595 );
596 assert!(errors.is_empty(), "got {errors:?}");
597 }
598}