From 4b7a8daedcc53a1e650cec268b015b6e83adaed5 Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Thu, 5 Nov 2026 12:49:45 +0100 Subject: [PATCH 01/11] Rust: preserve locations in literal include expansions Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- rust/extractor/src/rust_analyzer.rs | 22 +- rust/extractor/src/translate/base.rs | 196 ++++++++++++++---- .../PathResolutionInlineExpectationsTest.qll | 2 + .../macro-expansion/PrintAst.expected | 34 +-- .../macro-expansion/test.expected | 4 +- .../included/tree/included_module.rs | 1 + .../path-resolution/included/tree/mod.rs | 1 + .../library-tests/path-resolution/main.rs | 3 + .../path-resolution/path-resolution.expected | 13 +- 9 files changed, 213 insertions(+), 63 deletions(-) create mode 100644 rust/ql/test/library-tests/path-resolution/included/tree/included_module.rs create mode 100644 rust/ql/test/library-tests/path-resolution/included/tree/mod.rs diff --git a/rust/extractor/src/rust_analyzer.rs b/rust/extractor/src/rust_analyzer.rs index d3129af12796..340f7ed03dd0 100644 --- a/rust/extractor/src/rust_analyzer.rs +++ b/rust/extractor/src/rust_analyzer.rs @@ -68,6 +68,7 @@ impl From for RustAnalyzer<'static> { pub struct FileSemanticInformation<'a> { pub file_id: EditionedFileId, pub semantics: &'a Semantics<'a, RootDatabase>, + pub vfs: &'a Vfs, } pub struct ParseResult<'a> { @@ -99,8 +100,15 @@ impl<'a> RustAnalyzer<'a> { fn get_file_data( &self, path: &Path, - ) -> Result<(&'a Semantics<'a, RootDatabase>, EditionedFileId, FileText), RustAnalyzerNoSemantics> - { + ) -> Result< + ( + &'a Semantics<'a, RootDatabase>, + &'a Vfs, + EditionedFileId, + FileText, + ), + RustAnalyzerNoSemantics, + > { match self { RustAnalyzer::WithoutSemantics { severity, reason } => Err(RustAnalyzerNoSemantics { severity: *severity, @@ -118,14 +126,14 @@ impl<'a> RustAnalyzer<'a> { let editioned_file_id = semantics.attach_first_edition_opt(file_id).ok_or( RustAnalyzerNoSemantics::warning("failed to determine rust edition"), )?; - Ok((*semantics, editioned_file_id, input)) + Ok((*semantics, *vfs, editioned_file_id, input)) } } } pub fn parse(&self, path: &Path) -> ParseResult<'a> { match self.get_file_data(path) { - Ok((semantics, file_id, input)) => { + Ok((semantics, vfs, file_id, input)) => { let source_file = semantics.parse(file_id); let errors = file_id .parse_errors(semantics.db) @@ -137,7 +145,11 @@ impl<'a> RustAnalyzer<'a> { ast: source_file, text: input.text(semantics.db).clone(), errors, - semantics_info: Ok(FileSemanticInformation { file_id, semantics }), + semantics_info: Ok(FileSemanticInformation { + file_id, + semantics, + vfs, + }), } } Err(reason) => { diff --git a/rust/extractor/src/translate/base.rs b/rust/extractor/src/translate/base.rs index 2dd7c358a2f3..d48a7ae1005f 100644 --- a/rust/extractor/src/translate/base.rs +++ b/rust/extractor/src/translate/base.rs @@ -1,9 +1,9 @@ use super::mappings::Emission; use crate::generated::{self}; -use crate::rust_analyzer::FileSemanticInformation; +use crate::rust_analyzer::{FileSemanticInformation, path_to_file_id}; use crate::trap::{DiagnosticSeverity, TrapFile, TrapId}; use crate::trap::{Label, TrapClass}; -use ra_ap_base_db::EditionedFileId; +use ra_ap_base_db::{EditionedFileId, SourceDatabase}; use ra_ap_hir::Semantics; use ra_ap_hir_expand::builtin::{BuiltinDeriveExpander, find_builtin_derive}; use ra_ap_hir_expand::span_map::ExpansionSpanMap; @@ -14,12 +14,13 @@ use ra_ap_parser::{SyntaxKind, TopEntryPoint}; use ra_ap_span::TextSize; use ra_ap_syntax::ast::HasAttrs; use ra_ap_syntax::{ - AstNode, NodeOrToken, SyntaxElementChildren, SyntaxError, SyntaxNode, SyntaxToken, TextRange, - ast, + AstNode, AstToken, NodeOrToken, SyntaxElementChildren, SyntaxError, SyntaxNode, SyntaxToken, + TextRange, ast, }; use ra_ap_syntax_bridge::{ DocCommentDesugarMode, syntax_node_to_token_tree, token_tree_to_syntax_node, }; +use ra_ap_vfs::Vfs; use std::collections::HashMap; use std::path::{Path, PathBuf}; @@ -130,8 +131,11 @@ pub struct Translator<'db> { label: Label, line_index: LineIndex, file_id: Option, + vfs: Option<&'db Vfs>, pub semantics: Option<&'db Semantics<'db, RootDatabase>>, source_kind: SourceKind, + location_file_labels: HashMap>, + included_file_context: Option, pub(crate) macro_context_depth: usize, parenthesized_expr_attrs: HashMap>>, diagnostic_count: usize, @@ -141,6 +145,11 @@ pub struct Translator<'db> { builtin_derive_span_map: Option, } +struct IncludedFileContext { + root: SyntaxNode, + file_id: EditionedFileId, +} + const UNKNOWN_LOCATION: (LineCol, LineCol) = (LineCol { line: 0, col: 0 }, LineCol { line: 0, col: 0 }); @@ -161,8 +170,11 @@ impl<'db> Translator<'db> { label, line_index, file_id: semantic_info.map(|i| i.file_id), + vfs: semantic_info.map(|i| i.vfs), semantics: semantic_info.map(|i| i.semantics), source_kind, + location_file_labels: HashMap::new(), + included_file_context: None, macro_context_depth: 0, parenthesized_expr_attrs: HashMap::new(), diagnostic_count: 0, @@ -170,7 +182,91 @@ impl<'db> Translator<'db> { } } fn location(&self, range: TextRange) -> Option<(LineCol, LineCol)> { - let start = self.line_index.try_line_col(range.start())?; + Self::location_in_index(&self.line_index, range) + } + + fn file_range_for_node( + &self, + node: &impl ast::AstNode, + ) -> Option<(Option, TextRange)> { + if let Some(context) = self.included_file_context.as_ref() + && node.syntax().ancestors().last().as_ref() == Some(&context.root) + { + return Some((Some(context.file_id), node.syntax().text_range())); + } + if let Some(span_map) = self.builtin_derive_span_map.as_ref() { + // Nodes synthesized by a reconstructed built-in derive expansion are not in the + // semantics cache; resolve their original source range through the expansion span map. + let semantics = self.semantics.as_ref()?; + let file_range = + map_node_range_up_rooted(semantics.db, span_map, node.syntax().text_range())?; + return Some((Some(file_range.file_id), file_range.range)); + } + if let Some(semantics) = self.semantics.as_ref() { + let file_range = semantics.original_range(node.syntax()); + Some((Some(file_range.file_id), file_range.range)) + } else { + Some((None, node.syntax().text_range())) + } + } + + pub fn text_range_for_node(&mut self, node: &impl ast::AstNode) -> Option { + let (file_id, range) = self.file_range_for_node(node)?; + (self.semantics.is_none() || self.file_id == file_id).then_some(range) + } + + fn location_in_file( + &mut self, + file_id: Option, + range: TextRange, + ) -> Option<(Label, LineCol, LineCol)> { + let Some(file_id) = file_id else { + let (start, end) = self.location(range)?; + return Some((self.label, start, end)); + }; + if Some(file_id) == self.file_id { + let (start, end) = self.location(range)?; + return Some((self.label, start, end)); + } + if !self + .included_file_context + .as_ref() + .is_some_and(|context| context.file_id == file_id) + { + return None; + } + + let (path, line_index) = { + let semantics = self.semantics.as_ref()?; + let vfs = self.vfs.as_ref()?; + let file_id = file_id.file_id(semantics.db); + let vfs_path = vfs.file_path(file_id); + let path = vfs_path.as_path()?; + let path = dunce::canonicalize(path).unwrap_or_else(|_| path.to_path_buf().into()); + let text = semantics.db.file_text(file_id); + let line_index = LineIndex::new(text.text(semantics.db).as_ref()); + (path, line_index) + }; + + let (start, end) = Self::location_in_index(&line_index, range)?; + let file_label = match self.location_file_labels.get(&path) { + Some(label) => *label, + None => { + let label = self.trap.emit_file(&path); + self.location_file_labels.insert(path, label); + label + } + }; + Some((file_label, start, end)) + } + + fn location_for_node(&mut self, node: &impl ast::AstNode) -> Option<(LineCol, LineCol)> { + self.text_range_for_node(node) + .and_then(|r| self.location(r)) + } + + fn location_in_index(line_index: &LineIndex, range: TextRange) -> Option<(LineCol, LineCol)> { + let start = line_index.try_line_col(range.start())?; let range_end = range.end(); // QL end positions are inclusive, while TextRange offsets are exclusive and point at the position // right after the last character of the range. We need to shift the end offset one character to the left to @@ -180,45 +276,62 @@ impl<'db> Translator<'db> { for i in 1..4 { if let Some(end) = range_end .checked_sub(i.into()) - .and_then(|x| self.line_index.try_line_col(x)) + .and_then(|x| line_index.try_line_col(x)) { return Some((start, end)); } } - let end = self.line_index.try_line_col(range_end)?; + let end = line_index.try_line_col(range_end)?; Some((start, end)) } - pub fn text_range_for_node(&mut self, node: &impl ast::AstNode) -> Option { - if let Some(span_map) = self.builtin_derive_span_map.as_ref() { - // Nodes synthesized by a reconstructed built-in derive expansion are not in the - // semantics cache; resolve their original source range through the expansion span map. - let semantics = self.semantics.as_ref()?; - let file_id = self.file_id?; - let file_range = - map_node_range_up_rooted(semantics.db, span_map, node.syntax().text_range())?; - return (file_id == file_range.file_id).then_some(file_range.range); + fn included_file_context_for( + &self, + mcall: &ast::MacroCall, + expanded: &SyntaxNode, + ) -> Option { + if mcall.path()?.to_string() != "include" { + return None; } - if let Some(semantics) = self.semantics.as_ref() { - let file_range = semantics.original_range(node.syntax()); - let file_id = self.file_id?; - if file_id == file_range.file_id { - Some(file_range.range) - } else { - None - } - } else { - Some(node.syntax().text_range()) + let token_tree = mcall.token_tree()?; + let strings = token_tree + .syntax() + .descendants_with_tokens() + .filter_map(NodeOrToken::into_token) + .filter_map(ast::String::cast) + .collect::>(); + let [include_path] = strings.as_slice() else { + return None; + }; + let argument = token_tree + .syntax() + .text() + .to_string() + .trim() + .strip_prefix('(')? + .strip_suffix(')')? + .trim() + .to_owned(); + if argument != include_path.to_string() { + return None; } - } - fn location_for_node(&mut self, node: &impl ast::AstNode) -> Option<(LineCol, LineCol)> { - self.text_range_for_node(node) - .and_then(|r| self.location(r)) + let semantics = self.semantics.as_ref()?; + let vfs = self.vfs.as_ref()?; + let include_path = include_path.value().ok()?.into_owned(); + let path = self.path.parent()?.join(include_path); + let path = dunce::canonicalize(path).ok()?; + let file_id = path_to_file_id(&path, vfs)?; + let file_id = semantics.attach_first_edition_opt(file_id)?; + let root = expanded.ancestors().last()?.clone(); + Some(IncludedFileContext { root, file_id }) } pub fn emit_location(&mut self, label: Label, node: &impl ast::AstNode) { - match self.location_for_node(node) { + let location = self + .file_range_for_node(node) + .and_then(|(file_id, range)| self.location_in_file(file_id, range)); + match location { None => self.emit_diagnostic( DiagnosticSeverity::Debug, "locations".to_owned(), @@ -226,7 +339,7 @@ impl<'db> Translator<'db> { "missing location for AstNode".to_owned(), UNKNOWN_LOCATION, ), - Some((start, end)) => self.trap.emit_location(self.label, label, start, end), + Some((file, start, end)) => self.trap.emit_location(file, label, start, end), }; } pub fn emit_location_token( @@ -238,11 +351,11 @@ impl<'db> Translator<'db> { let parent_range = parent.syntax().text_range(); let token_range = token.text_range(); if let Some(clipped_range) = token_range.intersect(parent_range) - && let Some(parent_range2) = self.text_range_for_node(parent) + && let Some((file_id, parent_range2)) = self.file_range_for_node(parent) { let token_range = clipped_range + parent_range2.start() - parent_range.start(); - if let Some((start, end)) = self.location(token_range) { - self.trap.emit_location(self.label, label, start, end) + if let Some((file, start, end)) = self.location_in_file(file_id, token_range) { + self.trap.emit_location(file, label, start, end) } } } @@ -442,9 +555,17 @@ impl<'db> Translator<'db> { .as_ref() .and_then(|s| s.expand_macro_call(mcall)) { - self.emit_macro_expansion_parse_errors(mcall, &expanded.value); let expand_to = ra_ap_hir_expand::ExpandTo::from_call_site(mcall); let kind = expanded.kind(); + let previous_include_context = if expand_to == ra_ap_hir_expand::ExpandTo::Items { + self.included_file_context_for(mcall, &expanded.value) + .map(|context| { + std::mem::replace(&mut self.included_file_context, Some(context)) + }) + } else { + None + }; + self.emit_macro_expansion_parse_errors(mcall, &expanded.value); if let Some(value) = self.emit_expanded_as(expand_to, expanded.value) { generated::MacroCall::emit_macro_call_expansion( label, @@ -462,6 +583,9 @@ impl<'db> Translator<'db> { range.unwrap_or_else(|| TextRange::empty(TextSize::from(0))), )); } + if let Some(previous_context) = previous_include_context { + self.included_file_context = previous_context; + } } else if self.semantics.is_some() { // let's not spam warnings if we don't have semantics, we already emitted one let range = self.text_range_for_node(mcall); diff --git a/rust/ql/lib/utils/test/PathResolutionInlineExpectationsTest.qll b/rust/ql/lib/utils/test/PathResolutionInlineExpectationsTest.qll index a6ab8cd48cab..e0dfd473932d 100644 --- a/rust/ql/lib/utils/test/PathResolutionInlineExpectationsTest.qll +++ b/rust/ql/lib/utils/test/PathResolutionInlineExpectationsTest.qll @@ -48,6 +48,8 @@ private module ResolveTest implements TestSig { not n = any(Path parent).getQualifier() and location = n.getLocation() and n.fromSource() and + // The built-in include! macro path is unrelated to module/item path resolution. + not exists(MacroCall mc | mc.getPath() = n and mc.getPath().toString() = "include") and not location.getFile().getAbsolutePath().matches("%proc_macro.rs") and not n.isFromMacroExpansion() and element = n.toString() and diff --git a/rust/ql/test/extractor-tests/macro-expansion/PrintAst.expected b/rust/ql/test/extractor-tests/macro-expansion/PrintAst.expected index 5a1568f12d83..482adc43f95f 100644 --- a/rust/ql/test/extractor-tests/macro-expansion/PrintAst.expected +++ b/rust/ql/test/extractor-tests/macro-expansion/PrintAst.expected @@ -611,22 +611,24 @@ macro_expansion.rs: # 61| getSegment(): [PathSegment] include # 61| getIdentifier(): [NameRef] include # 61| getTokenTree(): [TokenTree] TokenTree -# 61| getMacroCallExpansion(): [MacroItems] MacroItems -# 61| getItem(0): [Function] fn included -# 61| getParamList(): [ParamList] ParamList -# 61| getFunctionBody(): [BlockExpr] { ... } -# 61| getStmtList(): [StmtList] StmtList -# 61| getStatement(0): [ExprStmt] ExprStmt -# 61| getExpr(): [AssignmentExpr] ... = ... -# 61| getLhs(): [UnderscoreExpr] _ -# 61| getRhs(): [MacroExpr] MacroExpr -# 61| getMacroCall(): [MacroCall] concat!... -# 61| getPath(): [Path] concat -# 61| getSegment(): [PathSegment] concat -# 61| getIdentifier(): [NameRef] concat -# 61| getTokenTree(): [TokenTree] TokenTree -# 61| getMacroCallExpansion(): [StringLiteralExpr] "Hello world!" -# 61| getName(): [Name] included +included/included.rs: +# 1| getMacroCallExpansion(): [MacroItems] MacroItems +# 1| getItem(0): [Function] fn included +# 1| getParamList(): [ParamList] ParamList +# 1| getFunctionBody(): [BlockExpr] { ... } +# 1| getStmtList(): [StmtList] StmtList +# 1| getStatement(0): [ExprStmt] ExprStmt +# 1| getExpr(): [AssignmentExpr] ... = ... +# 1| getLhs(): [UnderscoreExpr] _ +# 1| getRhs(): [MacroExpr] MacroExpr +# 1| getMacroCall(): [MacroCall] concat!... +# 1| getPath(): [Path] concat +# 1| getSegment(): [PathSegment] concat +# 1| getIdentifier(): [NameRef] concat +# 2| getTokenTree(): [TokenTree] TokenTree +# 2| getMacroCallExpansion(): [StringLiteralExpr] "Hello world!" +# 1| getName(): [Name] included +macro_expansion.rs: # 63| getItem(12): [Function] fn documented # 64| getParamList(): [ParamList] ParamList # 63| getAttr(0): [Attr] Attr diff --git a/rust/ql/test/extractor-tests/macro-expansion/test.expected b/rust/ql/test/extractor-tests/macro-expansion/test.expected index 65ee3f3e7fad..8b14ce006792 100644 --- a/rust/ql/test/extractor-tests/macro-expansion/test.expected +++ b/rust/ql/test/extractor-tests/macro-expansion/test.expected @@ -21,6 +21,7 @@ derive_macros | macro_expansion.rs:98:1:102:1 | union MyDeriveUnion | 0 | 0 | macro_expansion.rs:99:7:99:19 | const CONST_MyDeriveUnion | | macro_expansion.rs:98:1:102:1 | union MyDeriveUnion | 0 | 1 | macro_expansion.rs:99:7:99:19 | impl MyTrait for MyDeriveUnion { ... } | macro_calls +| included/included.rs:1:16:2:28 | concat!... | included/included.rs:2:17:2:38 | "Hello world!" | | macro_expansion.rs:5:9:5:35 | concat!... | macro_expansion.rs:5:17:5:34 | "Hello world!" | | macro_expansion.rs:5:9:5:35 | concat!... | macro_expansion.rs:5:17:5:34 | "Hello world!" | | macro_expansion.rs:31:7:31:15 | ...::format_args_nl!... | macro_expansion.rs:31:7:31:15 | FormatArgsExpr | @@ -37,8 +38,7 @@ macro_calls | macro_expansion.rs:55:9:58:5 | my_macro!... | macro_expansion.rs:56:9:57:13 | MacroExpr | | macro_expansion.rs:56:9:57:13 | ...::format_args!... | macro_expansion.rs:56:9:57:13 | FormatArgsExpr | | macro_expansion.rs:56:9:57:13 | format!... | macro_expansion.rs:56:9:57:13 | ...::must_use(...) | -| macro_expansion.rs:61:1:61:33 | concat!... | macro_expansion.rs:61:1:61:33 | "Hello world!" | -| macro_expansion.rs:61:1:61:33 | include!... | macro_expansion.rs:61:1:61:33 | MacroItems | +| macro_expansion.rs:61:1:61:33 | include!... | included/included.rs:1:1:2:30 | MacroItems | | macro_expansion.rs:70:16:70:24 | my_int!... | macro_expansion.rs:70:16:70:22 | i32 | | macro_expansion.rs:71:12:71:20 | my_int!... | macro_expansion.rs:71:12:71:18 | i32 | | macro_expansion.rs:72:10:72:18 | my_int!... | macro_expansion.rs:72:10:72:16 | i32 | diff --git a/rust/ql/test/library-tests/path-resolution/included/tree/included_module.rs b/rust/ql/test/library-tests/path-resolution/included/tree/included_module.rs new file mode 100644 index 000000000000..23921c0ac2a7 --- /dev/null +++ b/rust/ql/test/library-tests/path-resolution/included/tree/included_module.rs @@ -0,0 +1 @@ +pub fn hidden() {} // I23009 diff --git a/rust/ql/test/library-tests/path-resolution/included/tree/mod.rs b/rust/ql/test/library-tests/path-resolution/included/tree/mod.rs new file mode 100644 index 000000000000..53148baefbcd --- /dev/null +++ b/rust/ql/test/library-tests/path-resolution/included/tree/mod.rs @@ -0,0 +1 @@ +pub mod included_module; diff --git a/rust/ql/test/library-tests/path-resolution/main.rs b/rust/ql/test/library-tests/path-resolution/main.rs index 781641e98ce0..edc6b0d042a5 100644 --- a/rust/ql/test/library-tests/path-resolution/main.rs +++ b/rust/ql/test/library-tests/path-resolution/main.rs @@ -1190,4 +1190,7 @@ fn main() { AStruct {}.z_on_instance(); // $ item=I123 target=I125 impl_with_attribute_macro::test(); // $ item=impl_with_attribute_macro::test patterns::test(); // $ item=patterns::test + included_module::hidden(); // $ item=I23009 } + +include!("included/tree/mod.rs"); diff --git a/rust/ql/test/library-tests/path-resolution/path-resolution.expected b/rust/ql/test/library-tests/path-resolution/path-resolution.expected index 9df115b9bdb1..fb934bab3118 100644 --- a/rust/ql/test/library-tests/path-resolution/path-resolution.expected +++ b/rust/ql/test/library-tests/path-resolution/path-resolution.expected @@ -1,4 +1,6 @@ mod +| included/tree/mod.rs:1:1:1:22 | mod included_module | +| included/tree/mod.rs:1:1:1:24 | mod included_module | | invalid/main.rs:8:1:18:1 | mod self_import_from_struct | | lib.rs:1:1:1:11 | mod my | | main.rs:1:1:1:7 | mod my | @@ -83,7 +85,7 @@ resolvePath | main.rs:37:17:37:24 | ...::f | main.rs:26:9:28:9 | fn f | | main.rs:39:17:39:23 | println | {EXTERNAL LOCATION} | MacroRules | | main.rs:40:17:40:17 | f | main.rs:26:9:28:9 | fn f | -| main.rs:47:9:47:13 | super | main.rs:1:1:1193:2 | SourceFile | +| main.rs:47:9:47:13 | super | main.rs:1:1:1196:34 | SourceFile | | main.rs:47:9:47:17 | ...::m1 | main.rs:20:1:44:1 | mod m1 | | main.rs:47:9:47:21 | ...::m2 | main.rs:25:5:43:5 | mod m2 | | main.rs:47:9:47:24 | ...::g | main.rs:30:9:34:9 | fn g | @@ -98,7 +100,7 @@ resolvePath | main.rs:68:17:68:19 | Foo | main.rs:66:9:66:21 | struct Foo | | main.rs:71:13:71:15 | Foo | main.rs:60:5:60:17 | struct Foo | | main.rs:73:5:73:5 | f | main.rs:62:5:69:5 | fn f | -| main.rs:75:5:75:8 | self | main.rs:1:1:1193:2 | SourceFile | +| main.rs:75:5:75:8 | self | main.rs:1:1:1196:34 | SourceFile | | main.rs:75:5:75:11 | ...::i | main.rs:78:1:90:1 | fn i | | main.rs:79:5:79:11 | println | {EXTERNAL LOCATION} | MacroRules | | main.rs:81:13:81:15 | Foo | main.rs:55:1:55:13 | struct Foo | @@ -120,7 +122,7 @@ resolvePath | main.rs:112:9:112:15 | println | {EXTERNAL LOCATION} | MacroRules | | main.rs:118:9:118:15 | println | {EXTERNAL LOCATION} | MacroRules | | main.rs:122:9:122:15 | println | {EXTERNAL LOCATION} | MacroRules | -| main.rs:125:13:125:17 | super | main.rs:1:1:1193:2 | SourceFile | +| main.rs:125:13:125:17 | super | main.rs:1:1:1196:34 | SourceFile | | main.rs:125:13:125:21 | ...::m5 | main.rs:110:1:114:1 | mod m5 | | main.rs:126:9:126:9 | f | main.rs:111:5:113:5 | fn f | | main.rs:126:9:126:9 | f | main.rs:117:5:119:5 | fn f | @@ -697,6 +699,9 @@ resolvePath | main.rs:1191:5:1191:35 | ...::test | main.rs:984:5:987:5 | fn test | | main.rs:1192:5:1192:12 | patterns | main.rs:990:1:1031:1 | mod patterns | | main.rs:1192:5:1192:18 | ...::test | main.rs:991:5:1005:5 | fn test | +| main.rs:1193:5:1193:19 | included_module | included/tree/mod.rs:1:1:1:22 | mod included_module | +| main.rs:1193:5:1193:27 | ...::hidden | included/tree/included_module.rs:1:1:1:18 | fn hidden | +| main.rs:1196:1:1196:7 | include | {EXTERNAL LOCATION} | MacroRules | | my2/mod.rs:4:5:4:11 | println | {EXTERNAL LOCATION} | MacroRules | | my2/mod.rs:5:5:5:11 | nested2 | my2/mod.rs:1:1:1:16 | mod nested2 | | my2/mod.rs:5:5:5:20 | ...::nested3 | my2/nested2.rs:1:1:11:1 | mod nested3 | @@ -722,7 +727,7 @@ resolvePath | my2/my3/mod.rs:3:5:3:5 | g | my2/mod.rs:3:1:6:1 | fn g | | my2/my3/mod.rs:4:5:4:5 | h | main.rs:57:1:76:1 | fn h | | my2/my3/mod.rs:7:5:7:9 | super | my2/mod.rs:1:1:25:34 | SourceFile | -| my2/my3/mod.rs:7:5:7:16 | ...::super | main.rs:1:1:1193:2 | SourceFile | +| my2/my3/mod.rs:7:5:7:16 | ...::super | main.rs:1:1:1196:34 | SourceFile | | my2/my3/mod.rs:7:5:7:19 | ...::h | main.rs:57:1:76:1 | fn h | | my2/my3/mod.rs:8:5:8:9 | super | my2/mod.rs:1:1:25:34 | SourceFile | | my2/my3/mod.rs:8:5:8:12 | ...::g | my2/mod.rs:3:1:6:1 | fn g | From ea209d574f780fa7173b3f2abb1fd645ffcce359 Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Thu, 12 Nov 2026 13:11:29 +0100 Subject: [PATCH 02/11] Rust: use Option::replace for include context Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- rust/extractor/src/translate/base.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/rust/extractor/src/translate/base.rs b/rust/extractor/src/translate/base.rs index d48a7ae1005f..197ee03980f5 100644 --- a/rust/extractor/src/translate/base.rs +++ b/rust/extractor/src/translate/base.rs @@ -559,9 +559,7 @@ impl<'db> Translator<'db> { let kind = expanded.kind(); let previous_include_context = if expand_to == ra_ap_hir_expand::ExpandTo::Items { self.included_file_context_for(mcall, &expanded.value) - .map(|context| { - std::mem::replace(&mut self.included_file_context, Some(context)) - }) + .map(|context| self.included_file_context.replace(context)) } else { None }; From 47e8dd2d9f994a2deb79da95e15dcfa79cac4347 Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Thu, 12 Nov 2026 13:34:35 +0100 Subject: [PATCH 03/11] Rust: update included macro item locations Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../generated/MacroItems/MacroItems.expected | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rust/ql/test/extractor-tests/generated/MacroItems/MacroItems.expected b/rust/ql/test/extractor-tests/generated/MacroItems/MacroItems.expected index a3560c0fdef1..00d5fec0cc53 100644 --- a/rust/ql/test/extractor-tests/generated/MacroItems/MacroItems.expected +++ b/rust/ql/test/extractor-tests/generated/MacroItems/MacroItems.expected @@ -1,7 +1,7 @@ instances -| gen_macro_items.rs:5:5:5:12 | MacroItems | +| common_definitions.rs:1:1:4:16 | MacroItems | | gen_macro_items.rs:12:5:13:15 | MacroItems | getItem -| gen_macro_items.rs:5:5:5:12 | MacroItems | 0 | gen_macro_items.rs:5:5:5:38 | use ...::Path | -| gen_macro_items.rs:5:5:5:12 | MacroItems | 1 | gen_macro_items.rs:5:5:5:38 | fn get_parent | +| common_definitions.rs:1:1:4:16 | MacroItems | 0 | common_definitions.rs:1:1:1:19 | use ...::Path | +| common_definitions.rs:1:1:4:16 | MacroItems | 1 | common_definitions.rs:1:20:4:16 | fn get_parent | | gen_macro_items.rs:12:5:13:15 | MacroItems | 0 | gen_macro_items.rs:12:5:13:15 | impl ...::Debug for Bar::<...> { ... } | From 3fa6995025157e6b732ca6460df0397241b90972 Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Fri, 13 Nov 2026 09:52:34 +0100 Subject: [PATCH 04/11] Rust: resolve included items through source spans Use rust-analyzer's resolved include targets and source spans, cache file locations, and follow included files during QL crate and module resolution. Cover nested, qualified, delimited, computed, and shadowed includes with regression tests. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- rust/extractor/src/translate/base.rs | 176 ++++++++---------- .../2026-11-13-include-module-resolution.md | 4 + .../codeql/rust/internal/PathResolution.qll | 22 ++- .../generated/MacroItems/MacroItems.expected | 6 +- .../macro-expansion/PrintAst.expected | 134 ++++++++++++- .../macro-expansion/included/included.rs | 5 + .../macro-expansion/test.expected | 5 +- .../path-resolution/included/forms/child.rs | 3 + .../path-resolution/included/forms/mod.rs | 1 + .../path-resolution/included/tree/mod.rs | 9 + .../included/tree/nested/mod.rs | 1 + .../included/tree/nested/nested_module.rs | 4 + .../library-tests/path-resolution/main.rs | 32 ++++ .../path-resolution/path-resolution.expected | 61 +++++- 14 files changed, 344 insertions(+), 119 deletions(-) create mode 100644 rust/ql/lib/change-notes/2026-11-13-include-module-resolution.md create mode 100644 rust/ql/test/library-tests/path-resolution/included/forms/child.rs create mode 100644 rust/ql/test/library-tests/path-resolution/included/forms/mod.rs create mode 100644 rust/ql/test/library-tests/path-resolution/included/tree/nested/mod.rs create mode 100644 rust/ql/test/library-tests/path-resolution/included/tree/nested/nested_module.rs diff --git a/rust/extractor/src/translate/base.rs b/rust/extractor/src/translate/base.rs index 197ee03980f5..0c1a53872c8c 100644 --- a/rust/extractor/src/translate/base.rs +++ b/rust/extractor/src/translate/base.rs @@ -1,21 +1,23 @@ use super::mappings::Emission; use crate::generated::{self}; -use crate::rust_analyzer::{FileSemanticInformation, path_to_file_id}; +use crate::rust_analyzer::FileSemanticInformation; use crate::trap::{DiagnosticSeverity, TrapFile, TrapId}; use crate::trap::{Label, TrapClass}; use ra_ap_base_db::{EditionedFileId, SourceDatabase}; use ra_ap_hir::Semantics; use ra_ap_hir_expand::builtin::{BuiltinDeriveExpander, find_builtin_derive}; use ra_ap_hir_expand::span_map::ExpansionSpanMap; -use ra_ap_hir_expand::{ExpandResult, ExpandTo, HirFileId, InFile, map_node_range_up_rooted}; +use ra_ap_hir_expand::{ + ExpandResult, ExpandTo, HirFileId, InFile, map_node_range_up_rooted, span_for_offset, +}; use ra_ap_ide_db::RootDatabase; use ra_ap_ide_db::line_index::{LineCol, LineIndex}; use ra_ap_parser::{SyntaxKind, TopEntryPoint}; use ra_ap_span::TextSize; use ra_ap_syntax::ast::HasAttrs; use ra_ap_syntax::{ - AstNode, AstToken, NodeOrToken, SyntaxElementChildren, SyntaxError, SyntaxNode, SyntaxToken, - TextRange, ast, + AstNode, NodeOrToken, SyntaxElementChildren, SyntaxError, SyntaxNode, SyntaxToken, TextRange, + ast, }; use ra_ap_syntax_bridge::{ DocCommentDesugarMode, syntax_node_to_token_tree, token_tree_to_syntax_node, @@ -134,8 +136,8 @@ pub struct Translator<'db> { vfs: Option<&'db Vfs>, pub semantics: Option<&'db Semantics<'db, RootDatabase>>, source_kind: SourceKind, - location_file_labels: HashMap>, - included_file_context: Option, + included_files: HashMap, + included_file_context: Option, pub(crate) macro_context_depth: usize, parenthesized_expr_attrs: HashMap>>, diagnostic_count: usize, @@ -145,9 +147,9 @@ pub struct Translator<'db> { builtin_derive_span_map: Option, } -struct IncludedFileContext { - root: SyntaxNode, - file_id: EditionedFileId, +struct FileLocation { + label: Label, + line_index: LineIndex, } const UNKNOWN_LOCATION: (LineCol, LineCol) = @@ -173,7 +175,7 @@ impl<'db> Translator<'db> { vfs: semantic_info.map(|i| i.vfs), semantics: semantic_info.map(|i| i.semantics), source_kind, - location_file_labels: HashMap::new(), + included_files: HashMap::new(), included_file_context: None, macro_context_depth: 0, parenthesized_expr_attrs: HashMap::new(), @@ -189,24 +191,45 @@ impl<'db> Translator<'db> { &self, node: &impl ast::AstNode, ) -> Option<(Option, TextRange)> { - if let Some(context) = self.included_file_context.as_ref() - && node.syntax().ancestors().last().as_ref() == Some(&context.root) - { - return Some((Some(context.file_id), node.syntax().text_range())); - } + self.file_range_for_syntax_range(node.syntax(), node.syntax().text_range()) + } + + fn file_range_for_syntax_range( + &self, + node: &SyntaxNode, + range: TextRange, + ) -> Option<(Option, TextRange)> { if let Some(span_map) = self.builtin_derive_span_map.as_ref() { // Nodes synthesized by a reconstructed built-in derive expansion are not in the // semantics cache; resolve their original source range through the expansion span map. let semantics = self.semantics.as_ref()?; - let file_range = - map_node_range_up_rooted(semantics.db, span_map, node.syntax().text_range())?; + let file_range = map_node_range_up_rooted(semantics.db, span_map, range)?; return Some((Some(file_range.file_id), file_range.range)); } + if self.included_file_context.is_some() + && !range.is_empty() + && let Some(semantics) = self.semantics + && let Some(macro_file) = semantics.hir_file_for(node).macro_file() + && let Some(end) = range.end().checked_sub(1.into()) + { + // Include expansions reformat tokens and give them non-root syntax contexts. + // Resolve boundary spans (which may have different anchors) to source offsets. + let span_map = macro_file.expansion_span_map(semantics.db); + let (start, _) = span_for_offset(semantics.db, span_map, range.start()); + let (end, _) = span_for_offset(semantics.db, span_map, end); + if start.file_id == end.file_id + && (self.included_files.contains_key(&start.file_id) + || Some(start.file_id) == self.file_id) + { + return Some((Some(start.file_id), start.range.cover(end.range))); + } + } if let Some(semantics) = self.semantics.as_ref() { - let file_range = semantics.original_range(node.syntax()); + let file_range = InFile::new(semantics.hir_file_for(node), range) + .original_node_file_range_rooted(semantics.db); Some((Some(file_range.file_id), file_range.range)) } else { - Some((None, node.syntax().text_range())) + Some((None, range)) } } @@ -228,36 +251,9 @@ impl<'db> Translator<'db> { let (start, end) = self.location(range)?; return Some((self.label, start, end)); } - if !self - .included_file_context - .as_ref() - .is_some_and(|context| context.file_id == file_id) - { - return None; - } - - let (path, line_index) = { - let semantics = self.semantics.as_ref()?; - let vfs = self.vfs.as_ref()?; - let file_id = file_id.file_id(semantics.db); - let vfs_path = vfs.file_path(file_id); - let path = vfs_path.as_path()?; - let path = dunce::canonicalize(path).unwrap_or_else(|_| path.to_path_buf().into()); - let text = semantics.db.file_text(file_id); - let line_index = LineIndex::new(text.text(semantics.db).as_ref()); - (path, line_index) - }; - - let (start, end) = Self::location_in_index(&line_index, range)?; - let file_label = match self.location_file_labels.get(&path) { - Some(label) => *label, - None => { - let label = self.trap.emit_file(&path); - self.location_file_labels.insert(path, label); - label - } - }; - Some((file_label, start, end)) + let file = self.included_files.get(&file_id)?; + let (start, end) = Self::location_in_index(&file.line_index, range)?; + Some((file.label, start, end)) } fn location_for_node(&mut self, node: &impl ast::AstNode) -> Option<(LineCol, LineCol)> { @@ -285,46 +281,28 @@ impl<'db> Translator<'db> { Some((start, end)) } - fn included_file_context_for( - &self, - mcall: &ast::MacroCall, - expanded: &SyntaxNode, - ) -> Option { - if mcall.path()?.to_string() != "include" { - return None; - } - let token_tree = mcall.token_tree()?; - let strings = token_tree - .syntax() - .descendants_with_tokens() - .filter_map(NodeOrToken::into_token) - .filter_map(ast::String::cast) - .collect::>(); - let [include_path] = strings.as_slice() else { - return None; - }; - let argument = token_tree - .syntax() - .text() - .to_string() - .trim() - .strip_prefix('(')? - .strip_suffix(')')? - .trim() - .to_owned(); - if argument != include_path.to_string() { - return None; - } - + fn included_file_context_for(&mut self, expanded: &SyntaxNode) -> Option { let semantics = self.semantics.as_ref()?; - let vfs = self.vfs.as_ref()?; - let include_path = include_path.value().ok()?.into_owned(); - let path = self.path.parent()?.join(include_path); - let path = dunce::canonicalize(path).ok()?; - let file_id = path_to_file_id(&path, vfs)?; - let file_id = semantics.attach_first_edition_opt(file_id)?; - let root = expanded.ancestors().last()?.clone(); - Some(IncludedFileContext { root, file_id }) + let macro_file = semantics.hir_file_for(expanded).macro_file()?; + // rust-analyzer resolves the built-in, its argument, and nested include paths. + let file_id = macro_file + .loc(semantics.db) + .include_file_id(semantics.db, macro_file)?; + if Some(file_id) != self.file_id + && let std::collections::hash_map::Entry::Vacant(entry) = + self.included_files.entry(file_id) + { + let vfs = self.vfs.as_ref()?; + let source_file_id = file_id.file_id(semantics.db); + let path = vfs.file_path(source_file_id).as_path()?; + let path = dunce::canonicalize(path).unwrap_or_else(|_| path.to_path_buf().into()); + let text = semantics.db.file_text(source_file_id); + entry.insert(FileLocation { + label: self.trap.emit_file(&path), + line_index: LineIndex::new(text.text(semantics.db).as_ref()), + }); + } + Some(file_id) } pub fn emit_location(&mut self, label: Label, node: &impl ast::AstNode) { @@ -350,11 +328,21 @@ impl<'db> Translator<'db> { ) { let parent_range = parent.syntax().text_range(); let token_range = token.text_range(); - if let Some(clipped_range) = token_range.intersect(parent_range) - && let Some((file_id, parent_range2)) = self.file_range_for_node(parent) - { - let token_range = clipped_range + parent_range2.start() - parent_range.start(); - if let Some((file, start, end)) = self.location_in_file(file_id, token_range) { + if let Some(clipped_range) = token_range.intersect(parent_range) { + let location = if self.included_file_context.is_some() { + self.file_range_for_syntax_range(parent.syntax(), clipped_range) + } else { + self.file_range_for_node(parent) + .map(|(file_id, parent_range2)| { + ( + file_id, + clipped_range + parent_range2.start() - parent_range.start(), + ) + }) + }; + if let Some((file_id, range)) = location + && let Some((file, start, end)) = self.location_in_file(file_id, range) + { self.trap.emit_location(file, label, start, end) } } @@ -558,7 +546,7 @@ impl<'db> Translator<'db> { let expand_to = ra_ap_hir_expand::ExpandTo::from_call_site(mcall); let kind = expanded.kind(); let previous_include_context = if expand_to == ra_ap_hir_expand::ExpandTo::Items { - self.included_file_context_for(mcall, &expanded.value) + self.included_file_context_for(&expanded.value) .map(|context| self.included_file_context.replace(context)) } else { None diff --git a/rust/ql/lib/change-notes/2026-11-13-include-module-resolution.md b/rust/ql/lib/change-notes/2026-11-13-include-module-resolution.md new file mode 100644 index 000000000000..a684874b122d --- /dev/null +++ b/rust/ql/lib/change-notes/2026-11-13-include-module-resolution.md @@ -0,0 +1,4 @@ +--- +category: minorAnalysis +--- +* Module declarations and crate-root references in files included with `include!` now resolve correctly, including nested includes and exported macros. \ No newline at end of file diff --git a/rust/ql/lib/codeql/rust/internal/PathResolution.qll b/rust/ql/lib/codeql/rust/internal/PathResolution.qll index 30ddb6f12c36..92c4da7ce3b7 100644 --- a/rust/ql/lib/codeql/rust/internal/PathResolution.qll +++ b/rust/ql/lib/codeql/rust/internal/PathResolution.qll @@ -554,17 +554,27 @@ class CrateItemNode extends NamedItemNode instanceof Crate { * Gets a source file that belongs to this crate. * * This is calculated as those source files that can be reached from the entry - * file of this crate using zero or more `mod` imports, without going through - * the entry point of some other crate. + * file of this crate using zero or more `mod` imports or macro expansions + * (such as `include!`), without going through the entry point of some other crate. */ pragma[nomagic] SourceFileItemNode getASourceFile() { result = super.getSourceFile() or - exists(SourceFileItemNode mid, Module mod | + exists(SourceFileItemNode mid | mid = this.getASourceFile() and - mod.getFile() = mid.getFile() and - fileImport(mod, result) and + ( + exists(Module mod | + mod.getFile() = mid.getFile() and + fileImport(mod, result) + ) + or + exists(MacroCall mc, Item expanded | + mc.getFile() = mid.getFile() and + expanded = mc.getMacroCallExpansion().(MacroItems).getAnItem() and + expanded.getFile() = result.getFile() + ) + ) and not result = any(Crate other).getSourceFile() ) } @@ -1435,6 +1445,8 @@ private predicate modImport0(Module m, string name, Folder lookup) { * nested module, and `pred -> succ` is a module edge leading to `m`. */ private predicate modImportNested(ModuleItemNode m, ModuleItemNode pred, ModuleItemNode succ) { + // An include expansion starts a new file-relative lookup, even inside an inline module. + pred.getFile() = m.getFile() and pred.getAnItemInScope() = succ and ( modImport0(m, _, _) and diff --git a/rust/ql/test/extractor-tests/generated/MacroItems/MacroItems.expected b/rust/ql/test/extractor-tests/generated/MacroItems/MacroItems.expected index 00d5fec0cc53..836b8db4bc30 100644 --- a/rust/ql/test/extractor-tests/generated/MacroItems/MacroItems.expected +++ b/rust/ql/test/extractor-tests/generated/MacroItems/MacroItems.expected @@ -1,7 +1,7 @@ instances -| common_definitions.rs:1:1:4:16 | MacroItems | +| common_definitions.rs:1:1:5:1 | MacroItems | | gen_macro_items.rs:12:5:13:15 | MacroItems | getItem -| common_definitions.rs:1:1:4:16 | MacroItems | 0 | common_definitions.rs:1:1:1:19 | use ...::Path | -| common_definitions.rs:1:1:4:16 | MacroItems | 1 | common_definitions.rs:1:20:4:16 | fn get_parent | +| common_definitions.rs:1:1:5:1 | MacroItems | 0 | common_definitions.rs:1:1:1:20 | use ...::Path | +| common_definitions.rs:1:1:5:1 | MacroItems | 1 | common_definitions.rs:3:1:5:1 | fn get_parent | | gen_macro_items.rs:12:5:13:15 | MacroItems | 0 | gen_macro_items.rs:12:5:13:15 | impl ...::Debug for Bar::<...> { ... } | diff --git a/rust/ql/test/extractor-tests/macro-expansion/PrintAst.expected b/rust/ql/test/extractor-tests/macro-expansion/PrintAst.expected index 482adc43f95f..de02b61806ab 100644 --- a/rust/ql/test/extractor-tests/macro-expansion/PrintAst.expected +++ b/rust/ql/test/extractor-tests/macro-expansion/PrintAst.expected @@ -141,6 +141,21 @@ included/included.rs: # 2| getIdentifier(): [NameRef] concat # 2| getTokenTree(): [TokenTree] TokenTree # 1| getName(): [Name] included +# 5| getItem(1): [Struct] struct IncludedDerived +# 5| getAttr(0): [Attr] Attr +# 5| getMeta(): [TokenTreeMeta] TokenTreeMeta +# 5| getPath(): [Path] derive +# 5| getSegment(): [PathSegment] derive +# 5| getIdentifier(): [NameRef] derive +# 5| getTokenTree(): [TokenTree] TokenTree +# 6| getName(): [Name] IncludedDerived +# 6| getFieldList(): [StructFieldList] StructFieldList +# 7| getField(0): [StructField] value: usize +# 7| getName(): [Name] value +# 7| getTypeRepr(): [PathTypeRepr] usize +# 7| getPath(): [Path] usize +# 7| getSegment(): [PathSegment] usize +# 7| getIdentifier(): [NameRef] usize # 2| [Comment] //... lib.rs: # 1| [SourceFile] SourceFile @@ -617,17 +632,120 @@ included/included.rs: # 1| getParamList(): [ParamList] ParamList # 1| getFunctionBody(): [BlockExpr] { ... } # 1| getStmtList(): [StmtList] StmtList -# 1| getStatement(0): [ExprStmt] ExprStmt -# 1| getExpr(): [AssignmentExpr] ... = ... -# 1| getLhs(): [UnderscoreExpr] _ -# 1| getRhs(): [MacroExpr] MacroExpr -# 1| getMacroCall(): [MacroCall] concat!... -# 1| getPath(): [Path] concat -# 1| getSegment(): [PathSegment] concat -# 1| getIdentifier(): [NameRef] concat +# 2| getStatement(0): [ExprStmt] ExprStmt +# 2| getExpr(): [AssignmentExpr] ... = ... +# 2| getLhs(): [UnderscoreExpr] _ +# 2| getRhs(): [MacroExpr] MacroExpr +# 2| getMacroCall(): [MacroCall] concat!... +# 2| getPath(): [Path] concat +# 2| getSegment(): [PathSegment] concat +# 2| getIdentifier(): [NameRef] concat # 2| getTokenTree(): [TokenTree] TokenTree # 2| getMacroCallExpansion(): [StringLiteralExpr] "Hello world!" # 1| getName(): [Name] included +# 5| getItem(1): [Struct] struct IncludedDerived +# 5| getDeriveMacroExpansion(0): [MacroItems] MacroItems +# 5| getItem(0): [Impl] impl ...::Debug for IncludedDerived::<...> { ... } +# 5| getAssocItemList(): [AssocItemList] AssocItemList +# 5| getAssocItem(0): [Function] fn fmt +# 5| getParamList(): [ParamList] ParamList +# 5| getParam(0): [Param] ...: ... +# 5| getTypeRepr(): [RefTypeRepr] RefTypeRepr +# 5| getTypeRepr(): [PathTypeRepr] ...::Formatter +# 5| getPath(): [Path] ...::Formatter +# 5| getQualifier(): [Path] ...::fmt +# 5| getQualifier(): [Path] $crate +# 5| getSegment(): [PathSegment] $crate +# 5| getIdentifier(): [NameRef] $crate +# 5| getSegment(): [PathSegment] fmt +# 5| getIdentifier(): [NameRef] fmt +# 5| getSegment(): [PathSegment] Formatter +# 5| getIdentifier(): [NameRef] Formatter +# 5| getPat(): [IdentPat] f +# 5| getName(): [Name] f +# 5| getSelfParam(): [SelfParam] SelfParam +# 5| getName(): [Name] self +# 5| getFunctionBody(): [BlockExpr] { ... } +# 5| getStmtList(): [StmtList] StmtList +# 5| getTailExpr(): [MatchExpr] match self { ... } +# 5| getScrutinee(): [VariableAccess] self +# 5| getPath(): [Path] self +# 5| getSegment(): [PathSegment] self +# 5| getIdentifier(): [NameRef] self +# 5| getMatchArmList(): [MatchArmList] MatchArmList +# 5| getArm(0): [MatchArm] ... => ... +# 5| getExpr(): [MethodCallExpr] ... .finish() +# 5| getArgList(): [ArgList] ArgList +# 5| getIdentifier(): [NameRef] finish +# 5| getReceiver(): [MethodCallExpr] ... .field(...) +# 5| getArgList(): [ArgList] ArgList +# 5| getArg(0): [StringLiteralExpr] "value" +# 5| getArg(1): [RefExpr] &value +# 7| getExpr(): [VariableAccess] value +# 7| getPath(): [Path] value +# 7| getSegment(): [PathSegment] value +# 7| getIdentifier(): [NameRef] value +# 5| getIdentifier(): [NameRef] field +# 5| getReceiver(): [MethodCallExpr] f.debug_struct(...) +# 5| getArgList(): [ArgList] ArgList +# 5| getArg(0): [StringLiteralExpr] "IncludedDerived" +# 5| getIdentifier(): [NameRef] debug_struct +# 5| getReceiver(): [VariableAccess] f +# 5| getPath(): [Path] f +# 5| getSegment(): [PathSegment] f +# 5| getIdentifier(): [NameRef] f +# 5| getPat(): [StructPat] IncludedDerived {...} +# 6| getPath(): [Path] IncludedDerived +# 6| getSegment(): [PathSegment] IncludedDerived +# 6| getIdentifier(): [NameRef] IncludedDerived +# 5| getStructPatFieldList(): [StructPatFieldList] StructPatFieldList +# 5| getField(0): [StructPatField] value: ... +# 7| getIdentifier(): [NameRef] value +# 7| getPat(): [IdentPat] value +# 7| getName(): [Name] value +# 5| getName(): [Name] fmt +# 5| getRetType(): [RetTypeRepr] RetTypeRepr +# 5| getTypeRepr(): [PathTypeRepr] ...::Result +# 5| getPath(): [Path] ...::Result +# 5| getQualifier(): [Path] ...::fmt +# 5| getQualifier(): [Path] $crate +# 5| getSegment(): [PathSegment] $crate +# 5| getIdentifier(): [NameRef] $crate +# 5| getSegment(): [PathSegment] fmt +# 5| getIdentifier(): [NameRef] fmt +# 5| getSegment(): [PathSegment] Result +# 5| getIdentifier(): [NameRef] Result +# 5| getGenericParamList(): [GenericParamList] <...> +# 5| getSelfTy(): [PathTypeRepr] IncludedDerived::<...> +# 5| getPath(): [Path] IncludedDerived::<...> +# 5| getSegment(): [PathSegment] IncludedDerived::<...> +# 5| getGenericArgList(): [GenericArgList] <...> +# 6| getIdentifier(): [NameRef] IncludedDerived +# 5| getTraitTy(): [PathTypeRepr] ...::Debug +# 5| getPath(): [Path] ...::Debug +# 5| getQualifier(): [Path] ...::fmt +# 5| getQualifier(): [Path] $crate +# 5| getSegment(): [PathSegment] $crate +# 5| getIdentifier(): [NameRef] $crate +# 5| getSegment(): [PathSegment] fmt +# 5| getIdentifier(): [NameRef] fmt +# 5| getSegment(): [PathSegment] Debug +# 5| getIdentifier(): [NameRef] Debug +# 5| getWhereClause(): [WhereClause] WhereClause +# 5| getAttr(0): [Attr] Attr +# 5| getMeta(): [TokenTreeMeta] TokenTreeMeta +# 5| getPath(): [Path] derive +# 5| getSegment(): [PathSegment] derive +# 5| getIdentifier(): [NameRef] derive +# 5| getTokenTree(): [TokenTree] TokenTree +# 6| getName(): [Name] IncludedDerived +# 6| getFieldList(): [StructFieldList] StructFieldList +# 7| getField(0): [StructField] value: usize +# 7| getName(): [Name] value +# 7| getTypeRepr(): [PathTypeRepr] usize +# 7| getPath(): [Path] usize +# 7| getSegment(): [PathSegment] usize +# 7| getIdentifier(): [NameRef] usize macro_expansion.rs: # 63| getItem(12): [Function] fn documented # 64| getParamList(): [ParamList] ParamList diff --git a/rust/ql/test/extractor-tests/macro-expansion/included/included.rs b/rust/ql/test/extractor-tests/macro-expansion/included/included.rs index 7397e24bd81e..e703da4b3cdc 100644 --- a/rust/ql/test/extractor-tests/macro-expansion/included/included.rs +++ b/rust/ql/test/extractor-tests/macro-expansion/included/included.rs @@ -1,3 +1,8 @@ fn included() { _ = concat!("Hello", " ", "world!"); // this doesn't expand (in included.rs) since 0.0.274 } + +#[derive(Debug)] +struct IncludedDerived { + value: usize, +} diff --git a/rust/ql/test/extractor-tests/macro-expansion/test.expected b/rust/ql/test/extractor-tests/macro-expansion/test.expected index 8b14ce006792..a89becb9d3ca 100644 --- a/rust/ql/test/extractor-tests/macro-expansion/test.expected +++ b/rust/ql/test/extractor-tests/macro-expansion/test.expected @@ -15,13 +15,14 @@ attribute_macros | macro_expansion.rs:31:5:34:5 | fn | 1 | macro_expansion.rs:32:5:34:5 | fn bzz_1 | | macro_expansion.rs:31:5:34:5 | fn | 2 | macro_expansion.rs:32:5:34:5 | fn bzz_2 | derive_macros +| included/included.rs:5:1:8:1 | struct IncludedDerived | 0 | 0 | included/included.rs:5:1:7:9 | impl ...::Debug for IncludedDerived::<...> { ... } | | macro_expansion.rs:83:1:86:1 | struct MyDerive | 0 | 0 | macro_expansion.rs:83:1:86:1 | impl ...::Debug for MyDerive::<...> { ... } | | macro_expansion.rs:88:1:92:1 | enum MyDeriveEnum | 0 | 0 | macro_expansion.rs:88:1:92:1 | impl ...::PartialEq for MyDeriveEnum::<...> { ... } | | macro_expansion.rs:88:1:92:1 | enum MyDeriveEnum | 1 | 0 | macro_expansion.rs:88:1:92:1 | impl ...::Eq for MyDeriveEnum::<...> { ... } | | macro_expansion.rs:98:1:102:1 | union MyDeriveUnion | 0 | 0 | macro_expansion.rs:99:7:99:19 | const CONST_MyDeriveUnion | | macro_expansion.rs:98:1:102:1 | union MyDeriveUnion | 0 | 1 | macro_expansion.rs:99:7:99:19 | impl MyTrait for MyDeriveUnion { ... } | macro_calls -| included/included.rs:1:16:2:28 | concat!... | included/included.rs:2:17:2:38 | "Hello world!" | +| included/included.rs:2:9:2:39 | concat!... | included/included.rs:2:17:2:38 | "Hello world!" | | macro_expansion.rs:5:9:5:35 | concat!... | macro_expansion.rs:5:17:5:34 | "Hello world!" | | macro_expansion.rs:5:9:5:35 | concat!... | macro_expansion.rs:5:17:5:34 | "Hello world!" | | macro_expansion.rs:31:7:31:15 | ...::format_args_nl!... | macro_expansion.rs:31:7:31:15 | FormatArgsExpr | @@ -38,7 +39,7 @@ macro_calls | macro_expansion.rs:55:9:58:5 | my_macro!... | macro_expansion.rs:56:9:57:13 | MacroExpr | | macro_expansion.rs:56:9:57:13 | ...::format_args!... | macro_expansion.rs:56:9:57:13 | FormatArgsExpr | | macro_expansion.rs:56:9:57:13 | format!... | macro_expansion.rs:56:9:57:13 | ...::must_use(...) | -| macro_expansion.rs:61:1:61:33 | include!... | included/included.rs:1:1:2:30 | MacroItems | +| macro_expansion.rs:61:1:61:33 | include!... | included/included.rs:1:1:8:1 | MacroItems | | macro_expansion.rs:70:16:70:24 | my_int!... | macro_expansion.rs:70:16:70:22 | i32 | | macro_expansion.rs:71:12:71:20 | my_int!... | macro_expansion.rs:71:12:71:18 | i32 | | macro_expansion.rs:72:10:72:18 | my_int!... | macro_expansion.rs:72:10:72:16 | i32 | diff --git a/rust/ql/test/library-tests/path-resolution/included/forms/child.rs b/rust/ql/test/library-tests/path-resolution/included/forms/child.rs new file mode 100644 index 000000000000..f10c4d2ebbff --- /dev/null +++ b/rust/ql/test/library-tests/path-resolution/included/forms/child.rs @@ -0,0 +1,3 @@ +pub fn form() { + crate::h(); // $ item=I25 +} // included_form diff --git a/rust/ql/test/library-tests/path-resolution/included/forms/mod.rs b/rust/ql/test/library-tests/path-resolution/included/forms/mod.rs new file mode 100644 index 000000000000..21d0b0d40613 --- /dev/null +++ b/rust/ql/test/library-tests/path-resolution/included/forms/mod.rs @@ -0,0 +1 @@ +pub mod child; diff --git a/rust/ql/test/library-tests/path-resolution/included/tree/mod.rs b/rust/ql/test/library-tests/path-resolution/included/tree/mod.rs index 53148baefbcd..cd0008471bda 100644 --- a/rust/ql/test/library-tests/path-resolution/included/tree/mod.rs +++ b/rust/ql/test/library-tests/path-resolution/included/tree/mod.rs @@ -1 +1,10 @@ pub mod included_module; + +include!("nested/mod.rs"); + +pub mod inline_export { + #[macro_export] + macro_rules! included_export { + () => {}; + } +} diff --git a/rust/ql/test/library-tests/path-resolution/included/tree/nested/mod.rs b/rust/ql/test/library-tests/path-resolution/included/tree/nested/mod.rs new file mode 100644 index 000000000000..4601c546bdcb --- /dev/null +++ b/rust/ql/test/library-tests/path-resolution/included/tree/nested/mod.rs @@ -0,0 +1 @@ +pub mod nested_module; diff --git a/rust/ql/test/library-tests/path-resolution/included/tree/nested/nested_module.rs b/rust/ql/test/library-tests/path-resolution/included/tree/nested/nested_module.rs new file mode 100644 index 000000000000..d1f4bef05632 --- /dev/null +++ b/rust/ql/test/library-tests/path-resolution/included/tree/nested/nested_module.rs @@ -0,0 +1,4 @@ +pub fn nested() { + crate::h(); // $ item=I25 + crate::included_export!(); // $ item=included_export +} // included_nested diff --git a/rust/ql/test/library-tests/path-resolution/main.rs b/rust/ql/test/library-tests/path-resolution/main.rs index edc6b0d042a5..aad7633f6464 100644 --- a/rust/ql/test/library-tests/path-resolution/main.rs +++ b/rust/ql/test/library-tests/path-resolution/main.rs @@ -1191,6 +1191,38 @@ fn main() { impl_with_attribute_macro::test(); // $ item=impl_with_attribute_macro::test patterns::test(); // $ item=patterns::test included_module::hidden(); // $ item=I23009 + nested_module::nested(); // $ item=included_nested + qualified_include::child::form(); // $ item=included_form + braced_include::child::form(); // $ item=included_form + bracket_include::child::form(); // $ item=included_form + computed_include::child::form(); // $ item=included_form + shadowed_include::from_shadow(); // $ item=from_shadow + crate::included_export!(); // $ item=included_export } include!("included/tree/mod.rs"); + +mod qualified_include { + std::include!("included/forms/mod.rs"); // $ item=include +} + +mod braced_include { + include! { /* comment */ "included/forms/mod.rs", } +} + +mod bracket_include { + include!["included/forms/mod.rs"]; +} + +mod computed_include { + include!(concat!("included/", "forms/mod.rs")); +} + +mod shadowed_include { + macro_rules! include { + ($path:literal) => { + pub fn from_shadow() {} + }; + } + include!("included/tree/mod.rs"); +} diff --git a/rust/ql/test/library-tests/path-resolution/path-resolution.expected b/rust/ql/test/library-tests/path-resolution/path-resolution.expected index fb934bab3118..66b2abf4eb89 100644 --- a/rust/ql/test/library-tests/path-resolution/path-resolution.expected +++ b/rust/ql/test/library-tests/path-resolution/path-resolution.expected @@ -1,6 +1,15 @@ mod -| included/tree/mod.rs:1:1:1:22 | mod included_module | +| included/forms/mod.rs:1:1:1:14 | mod child | +| included/forms/mod.rs:1:1:1:14 | mod child | +| included/forms/mod.rs:1:1:1:14 | mod child | +| included/forms/mod.rs:1:1:1:14 | mod child | +| included/forms/mod.rs:1:1:1:14 | mod child | | included/tree/mod.rs:1:1:1:24 | mod included_module | +| included/tree/mod.rs:1:1:1:24 | mod included_module | +| included/tree/mod.rs:5:1:10:1 | mod inline_export | +| included/tree/mod.rs:5:1:10:1 | mod inline_export | +| included/tree/nested/mod.rs:1:1:1:22 | mod nested_module | +| included/tree/nested/mod.rs:1:1:1:22 | mod nested_module | | invalid/main.rs:8:1:18:1 | mod self_import_from_struct | | lib.rs:1:1:1:11 | mod my | | main.rs:1:1:1:7 | mod my | @@ -41,6 +50,11 @@ mod | main.rs:1033:1:1077:1 | mod self_constructors | | main.rs:1079:1:1108:1 | mod self_types | | main.rs:1110:1:1154:1 | mod const_static | +| main.rs:1205:1:1207:1 | mod qualified_include | +| main.rs:1209:1:1211:1 | mod braced_include | +| main.rs:1213:1:1215:1 | mod bracket_include | +| main.rs:1217:1:1219:1 | mod computed_include | +| main.rs:1221:1:1228:1 | mod shadowed_include | | my2/mod.rs:1:1:1:16 | mod nested2 | | my2/mod.rs:20:1:20:12 | mod my3 | | my2/mod.rs:22:1:23:10 | mod mymod | @@ -56,6 +70,14 @@ mod | my/nested.rs:1:1:17:1 | mod nested1 | | my/nested.rs:2:5:11:5 | mod nested2 | resolvePath +| included/forms/child.rs:2:5:2:9 | crate | main.rs:0:0:0:0 | Crate(main@0.0.1) | +| included/forms/child.rs:2:5:2:12 | ...::h | main.rs:57:1:76:1 | fn h | +| included/tree/mod.rs:3:1:3:7 | include | {EXTERNAL LOCATION} | MacroRules | +| included/tree/nested/nested_module.rs:2:5:2:9 | crate | main.rs:0:0:0:0 | Crate(main@0.0.1) | +| included/tree/nested/nested_module.rs:2:5:2:12 | ...::h | main.rs:57:1:76:1 | fn h | +| included/tree/nested/nested_module.rs:3:5:3:9 | crate | main.rs:0:0:0:0 | Crate(main@0.0.1) | +| included/tree/nested/nested_module.rs:3:5:3:26 | ...::included_export | included/tree/mod.rs:6:5:9:5 | MacroRules | +| included/tree/nested/nested_module.rs:3:5:3:26 | ...::included_export | included/tree/mod.rs:6:5:9:5 | MacroRules | | invalid/main.rs:6:9:6:9 | A | invalid/main.rs:3:11:4:9 | struct A | | invalid/main.rs:15:9:15:12 | self | invalid/main.rs:8:1:18:1 | mod self_import_from_struct | | invalid/main.rs:15:9:15:22 | ...::MyStruct | invalid/main.rs:12:5:12:20 | struct MyStruct | @@ -85,7 +107,7 @@ resolvePath | main.rs:37:17:37:24 | ...::f | main.rs:26:9:28:9 | fn f | | main.rs:39:17:39:23 | println | {EXTERNAL LOCATION} | MacroRules | | main.rs:40:17:40:17 | f | main.rs:26:9:28:9 | fn f | -| main.rs:47:9:47:13 | super | main.rs:1:1:1196:34 | SourceFile | +| main.rs:47:9:47:13 | super | main.rs:1:1:1228:2 | SourceFile | | main.rs:47:9:47:17 | ...::m1 | main.rs:20:1:44:1 | mod m1 | | main.rs:47:9:47:21 | ...::m2 | main.rs:25:5:43:5 | mod m2 | | main.rs:47:9:47:24 | ...::g | main.rs:30:9:34:9 | fn g | @@ -100,7 +122,7 @@ resolvePath | main.rs:68:17:68:19 | Foo | main.rs:66:9:66:21 | struct Foo | | main.rs:71:13:71:15 | Foo | main.rs:60:5:60:17 | struct Foo | | main.rs:73:5:73:5 | f | main.rs:62:5:69:5 | fn f | -| main.rs:75:5:75:8 | self | main.rs:1:1:1196:34 | SourceFile | +| main.rs:75:5:75:8 | self | main.rs:1:1:1228:2 | SourceFile | | main.rs:75:5:75:11 | ...::i | main.rs:78:1:90:1 | fn i | | main.rs:79:5:79:11 | println | {EXTERNAL LOCATION} | MacroRules | | main.rs:81:13:81:15 | Foo | main.rs:55:1:55:13 | struct Foo | @@ -122,7 +144,7 @@ resolvePath | main.rs:112:9:112:15 | println | {EXTERNAL LOCATION} | MacroRules | | main.rs:118:9:118:15 | println | {EXTERNAL LOCATION} | MacroRules | | main.rs:122:9:122:15 | println | {EXTERNAL LOCATION} | MacroRules | -| main.rs:125:13:125:17 | super | main.rs:1:1:1196:34 | SourceFile | +| main.rs:125:13:125:17 | super | main.rs:1:1:1228:2 | SourceFile | | main.rs:125:13:125:21 | ...::m5 | main.rs:110:1:114:1 | mod m5 | | main.rs:126:9:126:9 | f | main.rs:111:5:113:5 | fn f | | main.rs:126:9:126:9 | f | main.rs:117:5:119:5 | fn f | @@ -699,9 +721,34 @@ resolvePath | main.rs:1191:5:1191:35 | ...::test | main.rs:984:5:987:5 | fn test | | main.rs:1192:5:1192:12 | patterns | main.rs:990:1:1031:1 | mod patterns | | main.rs:1192:5:1192:18 | ...::test | main.rs:991:5:1005:5 | fn test | -| main.rs:1193:5:1193:19 | included_module | included/tree/mod.rs:1:1:1:22 | mod included_module | +| main.rs:1193:5:1193:19 | included_module | included/tree/mod.rs:1:1:1:24 | mod included_module | | main.rs:1193:5:1193:27 | ...::hidden | included/tree/included_module.rs:1:1:1:18 | fn hidden | -| main.rs:1196:1:1196:7 | include | {EXTERNAL LOCATION} | MacroRules | +| main.rs:1194:5:1194:17 | nested_module | included/tree/nested/mod.rs:1:1:1:22 | mod nested_module | +| main.rs:1194:5:1194:25 | ...::nested | included/tree/nested/nested_module.rs:1:1:4:1 | fn nested | +| main.rs:1195:5:1195:21 | qualified_include | main.rs:1205:1:1207:1 | mod qualified_include | +| main.rs:1195:5:1195:28 | ...::child | included/forms/mod.rs:1:1:1:14 | mod child | +| main.rs:1195:5:1195:34 | ...::form | included/forms/child.rs:1:1:3:1 | fn form | +| main.rs:1196:5:1196:18 | braced_include | main.rs:1209:1:1211:1 | mod braced_include | +| main.rs:1196:5:1196:25 | ...::child | included/forms/mod.rs:1:1:1:14 | mod child | +| main.rs:1196:5:1196:31 | ...::form | included/forms/child.rs:1:1:3:1 | fn form | +| main.rs:1197:5:1197:19 | bracket_include | main.rs:1213:1:1215:1 | mod bracket_include | +| main.rs:1197:5:1197:26 | ...::child | included/forms/mod.rs:1:1:1:14 | mod child | +| main.rs:1197:5:1197:32 | ...::form | included/forms/child.rs:1:1:3:1 | fn form | +| main.rs:1198:5:1198:20 | computed_include | main.rs:1217:1:1219:1 | mod computed_include | +| main.rs:1198:5:1198:27 | ...::child | included/forms/mod.rs:1:1:1:14 | mod child | +| main.rs:1198:5:1198:33 | ...::form | included/forms/child.rs:1:1:3:1 | fn form | +| main.rs:1199:5:1199:20 | shadowed_include | main.rs:1221:1:1228:1 | mod shadowed_include | +| main.rs:1199:5:1199:33 | ...::from_shadow | main.rs:1227:5:1227:12 | fn from_shadow | +| main.rs:1200:5:1200:9 | crate | main.rs:0:0:0:0 | Crate(main@0.0.1) | +| main.rs:1200:5:1200:26 | ...::included_export | included/tree/mod.rs:6:5:9:5 | MacroRules | +| main.rs:1200:5:1200:26 | ...::included_export | included/tree/mod.rs:6:5:9:5 | MacroRules | +| main.rs:1203:1:1203:7 | include | {EXTERNAL LOCATION} | MacroRules | +| main.rs:1206:5:1206:7 | std | {EXTERNAL LOCATION} | Crate(std@0.0.0) | +| main.rs:1206:5:1206:16 | ...::include | {EXTERNAL LOCATION} | MacroRules | +| main.rs:1210:5:1210:11 | include | {EXTERNAL LOCATION} | MacroRules | +| main.rs:1214:5:1214:11 | include | {EXTERNAL LOCATION} | MacroRules | +| main.rs:1218:5:1218:11 | include | {EXTERNAL LOCATION} | MacroRules | +| main.rs:1227:5:1227:11 | include | main.rs:1222:5:1226:5 | MacroRules | | my2/mod.rs:4:5:4:11 | println | {EXTERNAL LOCATION} | MacroRules | | my2/mod.rs:5:5:5:11 | nested2 | my2/mod.rs:1:1:1:16 | mod nested2 | | my2/mod.rs:5:5:5:20 | ...::nested3 | my2/nested2.rs:1:1:11:1 | mod nested3 | @@ -727,7 +774,7 @@ resolvePath | my2/my3/mod.rs:3:5:3:5 | g | my2/mod.rs:3:1:6:1 | fn g | | my2/my3/mod.rs:4:5:4:5 | h | main.rs:57:1:76:1 | fn h | | my2/my3/mod.rs:7:5:7:9 | super | my2/mod.rs:1:1:25:34 | SourceFile | -| my2/my3/mod.rs:7:5:7:16 | ...::super | main.rs:1:1:1196:34 | SourceFile | +| my2/my3/mod.rs:7:5:7:16 | ...::super | main.rs:1:1:1228:2 | SourceFile | | my2/my3/mod.rs:7:5:7:19 | ...::h | main.rs:57:1:76:1 | fn h | | my2/my3/mod.rs:8:5:8:9 | super | my2/mod.rs:1:1:25:34 | SourceFile | | my2/my3/mod.rs:8:5:8:12 | ...::g | my2/mod.rs:3:1:6:1 | fn g | From 1e316ba406b0846e07cc59242da4ec0c9129aad8 Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Fri, 13 Nov 2026 10:52:37 +0100 Subject: [PATCH 05/11] Rust: preserve active scopes and source metadata for includes Map ordinary macros through their include-site invocation, archive included targets and preserve extraction classification, and keep diagnostics file-aware. Track active crate items rather than physical files, and resolve non-mod.rs include modules relative to the included directory. Add regressions for generated output, disabled and duplicate exports, shared library roots, macro-generated modules, and included-file warnings. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- rust/extractor/src/main.rs | 6 + rust/extractor/src/translate/base.rs | 155 ++++++++++++------ .../2026-11-13-include-module-resolution.md | 2 +- .../codeql/rust/internal/PathResolution.qll | 68 ++++---- .../include-diagnostics/Cargo.lock | 7 + .../include-diagnostics/main.rs | 9 + .../include-diagnostics/options.yml | 1 + .../include-diagnostics/test.expected | 5 + .../include-diagnostics/test.ql | 4 + .../include-diagnostics/tree/generated.rs | 3 + .../include-out-dir/Cargo.lock | 7 + .../extractor-tests/include-out-dir/build.rs | 25 +++ .../extractor-tests/include-out-dir/main.rs | 10 ++ .../include-out-dir/test.expected | 9 + .../extractor-tests/include-out-dir/test.ql | 37 +++++ .../macro-expansion/test.expected | 2 +- .../include-library/Cargo.lock | 7 + .../path-resolution/include-library/main.rs | 6 + .../path-resolution/include-library/shared.rs | 10 ++ .../include-library/test.expected | 4 + .../path-resolution/include-library/test.ql | 11 ++ .../included/non_mod/generated.rs | 1 + .../non_mod/generated/non_mod_child.rs | 1 + .../included/non_mod/non_mod_child.rs | 1 + .../included/tree/disabled_child.rs | 4 + .../included/tree/macro_child.rs | 1 + .../path-resolution/included/tree/mod.rs | 10 ++ .../library-tests/path-resolution/main.rs | 9 + .../path-resolution/path-resolution.expected | 79 +++++---- .../path-resolution/path-resolution.ql | 9 + 30 files changed, 392 insertions(+), 111 deletions(-) create mode 100644 rust/ql/test/extractor-tests/include-diagnostics/Cargo.lock create mode 100644 rust/ql/test/extractor-tests/include-diagnostics/main.rs create mode 100644 rust/ql/test/extractor-tests/include-diagnostics/options.yml create mode 100644 rust/ql/test/extractor-tests/include-diagnostics/test.expected create mode 100644 rust/ql/test/extractor-tests/include-diagnostics/test.ql create mode 100644 rust/ql/test/extractor-tests/include-diagnostics/tree/generated.rs create mode 100644 rust/ql/test/extractor-tests/include-out-dir/Cargo.lock create mode 100644 rust/ql/test/extractor-tests/include-out-dir/build.rs create mode 100644 rust/ql/test/extractor-tests/include-out-dir/main.rs create mode 100644 rust/ql/test/extractor-tests/include-out-dir/test.expected create mode 100644 rust/ql/test/extractor-tests/include-out-dir/test.ql create mode 100644 rust/ql/test/library-tests/path-resolution/include-library/Cargo.lock create mode 100644 rust/ql/test/library-tests/path-resolution/include-library/main.rs create mode 100644 rust/ql/test/library-tests/path-resolution/include-library/shared.rs create mode 100644 rust/ql/test/library-tests/path-resolution/include-library/test.expected create mode 100644 rust/ql/test/library-tests/path-resolution/include-library/test.ql create mode 100644 rust/ql/test/library-tests/path-resolution/included/non_mod/generated.rs create mode 100644 rust/ql/test/library-tests/path-resolution/included/non_mod/generated/non_mod_child.rs create mode 100644 rust/ql/test/library-tests/path-resolution/included/non_mod/non_mod_child.rs create mode 100644 rust/ql/test/library-tests/path-resolution/included/tree/disabled_child.rs create mode 100644 rust/ql/test/library-tests/path-resolution/included/tree/macro_child.rs diff --git a/rust/extractor/src/main.rs b/rust/extractor/src/main.rs index 4ede43224d35..1eb685647d28 100644 --- a/rust/extractor/src/main.rs +++ b/rust/extractor/src/main.rs @@ -96,6 +96,12 @@ impl<'a> Extractor<'a> { } translator.emit_source_file(&ast); translator.emit_truncated_diagnostics_message(); + for step in &translator.included_file_steps { + if let Some(file) = &step.file { + self.archiver.archive(file); + } + } + self.steps.append(&mut translator.included_file_steps); translator.trap.commit().unwrap_or_else(|err| { error!( "Failed to write trap file for: {}: {}", diff --git a/rust/extractor/src/translate/base.rs b/rust/extractor/src/translate/base.rs index 0c1a53872c8c..524a50f74507 100644 --- a/rust/extractor/src/translate/base.rs +++ b/rust/extractor/src/translate/base.rs @@ -1,4 +1,5 @@ use super::mappings::Emission; +use crate::diagnostics::ExtractionStep; use crate::generated::{self}; use crate::rust_analyzer::FileSemanticInformation; use crate::trap::{DiagnosticSeverity, TrapFile, TrapId}; @@ -13,7 +14,6 @@ use ra_ap_hir_expand::{ use ra_ap_ide_db::RootDatabase; use ra_ap_ide_db::line_index::{LineCol, LineIndex}; use ra_ap_parser::{SyntaxKind, TopEntryPoint}; -use ra_ap_span::TextSize; use ra_ap_syntax::ast::HasAttrs; use ra_ap_syntax::{ AstNode, NodeOrToken, SyntaxElementChildren, SyntaxError, SyntaxNode, SyntaxToken, TextRange, @@ -25,6 +25,7 @@ use ra_ap_syntax_bridge::{ use ra_ap_vfs::Vfs; use std::collections::HashMap; use std::path::{Path, PathBuf}; +use std::time::Instant; impl Emission for Translator<'_> { fn pre_emit(&mut self, node: &ast::Item) -> Option> { @@ -138,6 +139,7 @@ pub struct Translator<'db> { source_kind: SourceKind, included_files: HashMap, included_file_context: Option, + pub(crate) included_file_steps: Vec, pub(crate) macro_context_depth: usize, parenthesized_expr_attrs: HashMap>>, diagnostic_count: usize, @@ -148,6 +150,7 @@ pub struct Translator<'db> { } struct FileLocation { + path: PathBuf, label: Label, line_index: LineIndex, } @@ -177,6 +180,7 @@ impl<'db> Translator<'db> { source_kind, included_files: HashMap::new(), included_file_context: None, + included_file_steps: Vec::new(), macro_context_depth: 0, parenthesized_expr_attrs: HashMap::new(), diagnostic_count: 0, @@ -209,20 +213,10 @@ impl<'db> Translator<'db> { if self.included_file_context.is_some() && !range.is_empty() && let Some(semantics) = self.semantics - && let Some(macro_file) = semantics.hir_file_for(node).macro_file() - && let Some(end) = range.end().checked_sub(1.into()) + && let Some((file_id, range)) = + self.included_file_range(semantics.hir_file_for(node), range) { - // Include expansions reformat tokens and give them non-root syntax contexts. - // Resolve boundary spans (which may have different anchors) to source offsets. - let span_map = macro_file.expansion_span_map(semantics.db); - let (start, _) = span_for_offset(semantics.db, span_map, range.start()); - let (end, _) = span_for_offset(semantics.db, span_map, end); - if start.file_id == end.file_id - && (self.included_files.contains_key(&start.file_id) - || Some(start.file_id) == self.file_id) - { - return Some((Some(start.file_id), start.range.cover(end.range))); - } + return Some((Some(file_id), range)); } if let Some(semantics) = self.semantics.as_ref() { let file_range = InFile::new(semantics.hir_file_for(node), range) @@ -233,9 +227,28 @@ impl<'db> Translator<'db> { } } - pub fn text_range_for_node(&mut self, node: &impl ast::AstNode) -> Option { - let (file_id, range) = self.file_range_for_node(node)?; - (self.semantics.is_none() || self.file_id == file_id).then_some(range) + fn included_file_range( + &self, + file_id: HirFileId, + range: TextRange, + ) -> Option<(EditionedFileId, TextRange)> { + let semantics = self.semantics?; + let macro_file = file_id.macro_file()?; + let call = macro_file.loc(semantics.db); + if !call.def.is_include() { + // Ordinary macros retain invocation-site locations, not definition-site spans. + let invocation = call.to_node(semantics.db); + return self.included_file_range(invocation.file_id, invocation.value.text_range()); + } + // Include expansions reformat tokens and give them non-root syntax contexts. + // Resolve boundary spans (which may have different anchors) to source offsets. + let span_map = macro_file.expansion_span_map(semantics.db); + let (start, _) = span_for_offset(semantics.db, span_map, range.start()); + let (end, _) = span_for_offset(semantics.db, span_map, range.end().checked_sub(1.into())?); + (start.file_id == end.file_id + && (self.included_files.contains_key(&start.file_id) + || Some(start.file_id) == self.file_id)) + .then(|| (start.file_id, start.range.cover(end.range))) } fn location_in_file( @@ -256,11 +269,6 @@ impl<'db> Translator<'db> { Some((file.label, start, end)) } - fn location_for_node(&mut self, node: &impl ast::AstNode) -> Option<(LineCol, LineCol)> { - self.text_range_for_node(node) - .and_then(|r| self.location(r)) - } - fn location_in_index(line_index: &LineIndex, range: TextRange) -> Option<(LineCol, LineCol)> { let start = line_index.try_line_col(range.start())?; let range_end = range.end(); @@ -300,6 +308,7 @@ impl<'db> Translator<'db> { entry.insert(FileLocation { label: self.trap.emit_file(&path), line_index: LineIndex::new(text.text(semantics.db).as_ref()), + path, }); } Some(file_id) @@ -354,6 +363,23 @@ impl<'db> Translator<'db> { message: String, full_message: String, location: (LineCol, LineCol), + ) { + self.emit_diagnostic_at( + severity, + tag, + message, + full_message, + (self.label, location.0, location.1), + ); + } + + fn emit_diagnostic_at( + &mut self, + severity: DiagnosticSeverity, + tag: String, + message: String, + full_message: String, + location: (Label, LineCol, LineCol), ) { let severity = if self.source_kind == SourceKind::Library { match severity { @@ -381,7 +407,7 @@ impl<'db> Translator<'db> { format!( "Too many diagnostic messages, {count} diagnostic messages were suppressed" ), - UNKNOWN_LOCATION, + (self.label, UNKNOWN_LOCATION.0, UNKNOWN_LOCATION.1), ); } } @@ -391,20 +417,30 @@ impl<'db> Translator<'db> { tag: String, message: String, full_message: String, - location: (LineCol, LineCol), + location: (Label, LineCol, LineCol), ) { - let (start, end) = location; + let (file, start, end) = location; + let path = if file.as_untyped() == self.label.as_untyped() { + &self.path + } else { + &self + .included_files + .values() + .find(|f| f.label.as_untyped() == file.as_untyped()) + .expect("diagnostic file must have cached location data") + .path + }; dispatch_to_tracing!( severity, "{}:{}:{}: {}", - self.path.display(), + path.display(), start.line + 1, start.col + 1, &full_message, ); if severity > DiagnosticSeverity::Debug { - let location = self.trap.emit_location_label(self.label, start, end); + let location = self.trap.emit_location_label(file, start, end); self.trap .emit_diagnostic(severity, tag, message, full_message, location); } @@ -418,35 +454,32 @@ impl<'db> Translator<'db> { message: String, full_message: String, ) { - let location = self.location_for_node(node); - self.emit_diagnostic( + let location = self + .file_range_for_node(node) + .and_then(|(file, range)| self.location_in_file(file, range)); + self.emit_diagnostic_at( severity, tag, message, full_message, - location.unwrap_or(UNKNOWN_LOCATION), + location.unwrap_or((self.label, UNKNOWN_LOCATION.0, UNKNOWN_LOCATION.1)), ); } pub fn emit_parse_error(&mut self, owner: &impl ast::AstNode, err: &SyntaxError) { let owner_range: TextRange = owner.syntax().text_range(); - let err_range = err.range(); - if let Some(owner_range2) = self.text_range_for_node(owner) { - let location = if let Some(clipped_range) = err_range.intersect(owner_range) { - let err_range = clipped_range + owner_range2.start() - owner_range.start(); - self.location(err_range) - } else { - self.location(owner_range2) - }; - let message = err.to_string(); - self.emit_diagnostic( - DiagnosticSeverity::Warning, - "parse_error".to_owned(), - message.clone(), - message, - location.unwrap_or(UNKNOWN_LOCATION), - ); - } + let range = err.range().intersect(owner_range).unwrap_or(owner_range); + let location = self + .file_range_for_syntax_range(owner.syntax(), range) + .and_then(|(file, range)| self.location_in_file(file, range)); + let message = err.to_string(); + self.emit_diagnostic_at( + DiagnosticSeverity::Warning, + "parse_error".to_owned(), + message.clone(), + message, + location.unwrap_or((self.label, UNKNOWN_LOCATION.0, UNKNOWN_LOCATION.1)), + ); } pub fn emit_tokens( &mut self, @@ -491,6 +524,14 @@ impl<'db> Translator<'db> { .text_range() .start(); self.emit_parse_error(node, &SyntaxError::new(error.message, location)); + } else if self.included_file_context.is_some() { + self.emit_diagnostic_for_node( + node, + DiagnosticSeverity::Warning, + "parse_error".to_owned(), + error.message.clone(), + error.message, + ); }; } for err in value.value.iter() { @@ -545,6 +586,7 @@ impl<'db> Translator<'db> { { let expand_to = ra_ap_hir_expand::ExpandTo::from_call_site(mcall); let kind = expanded.kind(); + let before_expand = Instant::now(); let previous_include_context = if expand_to == ra_ap_hir_expand::ExpandTo::Items { self.included_file_context_for(&expanded.value) .map(|context| self.included_file_context.replace(context)) @@ -559,22 +601,33 @@ impl<'db> Translator<'db> { &mut self.trap.writer, ); } else { - let range = self.text_range_for_node(mcall); self.emit_parse_error(mcall, &SyntaxError::new( format!( "macro expansion failed: the macro '{}' expands to {:?} but a {:?} was expected", mcall.path().map(|p| p.to_string()).unwrap_or_default(), kind, expand_to ), - range.unwrap_or_else(|| TextRange::empty(TextSize::from(0))), + mcall.syntax().text_range(), )); } if let Some(previous_context) = previous_include_context { + let file_id = self + .included_file_context + .expect("include context must be set"); + let path = if Some(file_id) == self.file_id { + &self.path + } else { + &self.included_files[&file_id].path + }; + self.included_file_steps.push(ExtractionStep::extract( + before_expand, + self.source_kind, + path, + )); self.included_file_context = previous_context; } } else if self.semantics.is_some() { // let's not spam warnings if we don't have semantics, we already emitted one - let range = self.text_range_for_node(mcall); self.emit_parse_error( mcall, &SyntaxError::new( @@ -582,7 +635,7 @@ impl<'db> Translator<'db> { "macro expansion failed for '{}'", mcall.path().map(|p| p.to_string()).unwrap_or_default() ), - range.unwrap_or_else(|| TextRange::empty(TextSize::from(0))), + mcall.syntax().text_range(), ), ); } diff --git a/rust/ql/lib/change-notes/2026-11-13-include-module-resolution.md b/rust/ql/lib/change-notes/2026-11-13-include-module-resolution.md index a684874b122d..f311e2078d0d 100644 --- a/rust/ql/lib/change-notes/2026-11-13-include-module-resolution.md +++ b/rust/ql/lib/change-notes/2026-11-13-include-module-resolution.md @@ -1,4 +1,4 @@ --- category: minorAnalysis --- -* Module declarations and crate-root references in files included with `include!` now resolve correctly, including nested includes and exported macros. \ No newline at end of file +* Module declarations and crate-root references in files included with `include!` now resolve correctly, including nested includes, build-script output, and active exported macros. Included source files are archived and retain their source classification and extraction diagnostics. \ No newline at end of file diff --git a/rust/ql/lib/codeql/rust/internal/PathResolution.qll b/rust/ql/lib/codeql/rust/internal/PathResolution.qll index 92c4da7ce3b7..7403e2407ba9 100644 --- a/rust/ql/lib/codeql/rust/internal/PathResolution.qll +++ b/rust/ql/lib/codeql/rust/internal/PathResolution.qll @@ -551,34 +551,26 @@ class CrateItemNode extends NamedItemNode instanceof Crate { SourceFileItemNode getSourceFile() { result = super.getSourceFile() } /** - * Gets a source file that belongs to this crate. - * - * This is calculated as those source files that can be reached from the entry - * file of this crate using zero or more `mod` imports or macro expansions - * (such as `include!`), without going through the entry point of some other crate. + * Holds if `item` belongs to this crate's active item scope, following macro + * expansions and `mod` imports but not importing another crate's entry point. */ pragma[nomagic] - SourceFileItemNode getASourceFile() { - result = super.getSourceFile() + predicate containsItem(ItemNode item) { + item = super.getSourceFile() or - exists(SourceFileItemNode mid | - mid = this.getASourceFile() and - ( - exists(Module mod | - mod.getFile() = mid.getFile() and - fileImport(mod, result) - ) - or - exists(MacroCall mc, Item expanded | - mc.getFile() = mid.getFile() and - expanded = mc.getMacroCallExpansion().(MacroItems).getAnItem() and - expanded.getFile() = result.getFile() - ) - ) and - not result = any(Crate other).getSourceFile() + this.containsItem(item.getImmediateParent()) + or + exists(Module mod | + this.containsItem(mod) and + fileImport(mod, item.(SourceFileItemNode)) and + not item = any(Crate other).getSourceFile() ) } + /** Gets a source file in this crate's active item scope. */ + pragma[nomagic] + SourceFileItemNode getASourceFile() { this.containsItem(result) } + pragma[nomagic] predicate isLatestVersion(string name) { this = @@ -899,7 +891,7 @@ final class ImplItemNode extends ImplOrTraitItemNode instanceof Impl { this.resolveSelfTy().hasCanonicalPathPrefix(c) or this.isBlanketImplementation() and - c.getASourceFile().getFile() = this.getFile() + c.(CrateItemNode).containsItem(this) } /** @@ -1414,6 +1406,16 @@ private Meta getPathAttrMeta(Module m) { result.getMetaPath().getText() = "path" } +/** Holds if `node` belongs to an included file's active expansion. */ +private predicate isInIncludeExpansion(AstNode node) { + exists(MacroItems included, MacroCall call | + included = node.getParentNode*() and + included = call.getMacroCallExpansion() and + included.getFile() = node.getFile() and + included.getFile() != call.getFile() + ) +} + /** * Holds if `m` is a `mod name;` module declaration, where the corresponding * module file needs to be looked up in `lookup` or one of its descandants. @@ -1433,10 +1435,13 @@ private predicate modImport0(Module m, string name, Folder lookup) { m.getFile() = any(CrateItemNode c).getSourceFile().getFile() or m.getFile().getBaseName() = "mod.rs" + or + isInIncludeExpansion(m) ) or // child import - lookup = parent.getFolder(fileName) + lookup = parent.getFolder(fileName) and + not isInIncludeExpansion(m) ) } @@ -1875,11 +1880,11 @@ private module DollarCrateResolution { expansion) pragma[nomagic] - private predicate isInDollarCrateMacroExpansionFromFile(File macroDefFile, PathExt p) { + private predicate isInDollarCrateMacroExpansionFromItem(ItemNode macroDef, PathExt p) { exists(Path macroDefPath, AstNode expansion | isDollarCrateSupportedMacroExpansion(macroDefPath, expansion) and isInDollarCrateMacroExpansion(p, expansion) and - macroDefFile = resolvePathCand(macroDefPath).getFile() + macroDef = resolvePathCand(macroDefPath) ) } @@ -1891,7 +1896,10 @@ private module DollarCrateResolution { */ pragma[nomagic] predicate resolveDollarCrate(PathExt p, CrateItemNode crate) { - isInDollarCrateMacroExpansionFromFile(crate.getASourceFile().getFile(), p) + exists(ItemNode macroDef | + isInDollarCrateMacroExpansionFromItem(macroDef, p) and + crate.containsItem(macroDef) + ) } } @@ -2029,7 +2037,7 @@ private predicate pathUsesNamespace(PathExt p, Namespace n) { */ pragma[nomagic] private predicate macroExportEdge(CrateItemNode crate, string name, MacroItemNode macro) { - crate.getASourceFile().getFile() = macro.getFile() and + crate.containsItem(macro) and macro.hasAttr("macro_export") and name = macro.getName() } @@ -2292,14 +2300,14 @@ private predicate externCrateEdge( name = ec.getName() and (if ec.isPublic() then kind.isBoth() else kind.isInternal()) and exists(SourceFile f, string s | - ec.getFile() = f.getFile() and + f = ec.getImmediateParent*() and s = ec.(ExternCrate).getIdentifier().getText() | crateDependencyEdge(f, s, crate) or // `extern crate` is used to import the current crate s = "self" and - ec.getFile() = crate.getASourceFile().getFile() + crate.containsItem(ec) ) } diff --git a/rust/ql/test/extractor-tests/include-diagnostics/Cargo.lock b/rust/ql/test/extractor-tests/include-diagnostics/Cargo.lock new file mode 100644 index 000000000000..b9856cfaf77d --- /dev/null +++ b/rust/ql/test/extractor-tests/include-diagnostics/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "test" +version = "0.0.1" diff --git a/rust/ql/test/extractor-tests/include-diagnostics/main.rs b/rust/ql/test/extractor-tests/include-diagnostics/main.rs new file mode 100644 index 000000000000..875311da5e3e --- /dev/null +++ b/rust/ql/test/extractor-tests/include-diagnostics/main.rs @@ -0,0 +1,9 @@ +macro_rules! malformed { + () => { + fn broken() { let = ; } + }; +} + +include!("tree/generated.rs"); + +fn main() {} diff --git a/rust/ql/test/extractor-tests/include-diagnostics/options.yml b/rust/ql/test/extractor-tests/include-diagnostics/options.yml new file mode 100644 index 000000000000..cf148dd35f86 --- /dev/null +++ b/rust/ql/test/extractor-tests/include-diagnostics/options.yml @@ -0,0 +1 @@ +qltest_cargo_check: false diff --git a/rust/ql/test/extractor-tests/include-diagnostics/test.expected b/rust/ql/test/extractor-tests/include-diagnostics/test.expected new file mode 100644 index 000000000000..c9d6712679c8 --- /dev/null +++ b/rust/ql/test/extractor-tests/include-diagnostics/test.expected @@ -0,0 +1,5 @@ +| tree/generated.rs:1:1:1:26 | macro expansion failed for 'unknown_included_macro' | +| tree/generated.rs:2:1:2:13 | expected SEMICOLON | +| tree/generated.rs:2:1:2:13 | expected expression | +| tree/generated.rs:2:1:2:13 | expected pattern | +| tree/generated.rs:3:1:3:35 | included failure | diff --git a/rust/ql/test/extractor-tests/include-diagnostics/test.ql b/rust/ql/test/extractor-tests/include-diagnostics/test.ql new file mode 100644 index 000000000000..557f3bd8e781 --- /dev/null +++ b/rust/ql/test/extractor-tests/include-diagnostics/test.ql @@ -0,0 +1,4 @@ +import rust +import codeql.rust.Diagnostics + +query predicate warnings(ExtractionWarning w) { w.getLocation().fromSource() } diff --git a/rust/ql/test/extractor-tests/include-diagnostics/tree/generated.rs b/rust/ql/test/extractor-tests/include-diagnostics/tree/generated.rs new file mode 100644 index 000000000000..73b7bac24c0a --- /dev/null +++ b/rust/ql/test/extractor-tests/include-diagnostics/tree/generated.rs @@ -0,0 +1,3 @@ +unknown_included_macro!(); +malformed!(); +compile_error!("included failure"); diff --git a/rust/ql/test/extractor-tests/include-out-dir/Cargo.lock b/rust/ql/test/extractor-tests/include-out-dir/Cargo.lock new file mode 100644 index 000000000000..b9856cfaf77d --- /dev/null +++ b/rust/ql/test/extractor-tests/include-out-dir/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "test" +version = "0.0.1" diff --git a/rust/ql/test/extractor-tests/include-out-dir/build.rs b/rust/ql/test/extractor-tests/include-out-dir/build.rs new file mode 100644 index 000000000000..efc465eef8c3 --- /dev/null +++ b/rust/ql/test/extractor-tests/include-out-dir/build.rs @@ -0,0 +1,25 @@ +fn main() { + let out = std::path::PathBuf::from(std::env::var_os("OUT_DIR").unwrap()); + std::fs::write( + out.join("generated.rs"), + r#" +extern crate self as generated_self; + +pub fn generated_function() -> i32 { + crate::root_function() +} + +pub fn generated_alias() -> i32 { + generated_self::root_function() +} + +pub mod inline { + #[macro_export] + macro_rules! generated_export { + () => { $crate::root_function(); }; + } +} +"#, + ) + .unwrap(); +} diff --git a/rust/ql/test/extractor-tests/include-out-dir/main.rs b/rust/ql/test/extractor-tests/include-out-dir/main.rs new file mode 100644 index 000000000000..d0d8f9d74a5c --- /dev/null +++ b/rust/ql/test/extractor-tests/include-out-dir/main.rs @@ -0,0 +1,10 @@ +include!(concat!(env!("OUT_DIR"), "/generated.rs")); + +fn root_function() -> i32 { + 42 +} + +fn main() { + generated_function(); + crate::generated_export!(); +} diff --git a/rust/ql/test/extractor-tests/include-out-dir/test.expected b/rust/ql/test/extractor-tests/include-out-dir/test.expected new file mode 100644 index 000000000000..3d2661ca861b --- /dev/null +++ b/rust/ql/test/extractor-tests/include-out-dir/test.expected @@ -0,0 +1,9 @@ +generatedFunction +| true | 3 | main::generated_function | +rootReference +| generated_alias | root_function | +| generated_function | root_function | +macroReference +| root_function | +exportedMacro +| generated_export | 1 | diff --git a/rust/ql/test/extractor-tests/include-out-dir/test.ql b/rust/ql/test/extractor-tests/include-out-dir/test.ql new file mode 100644 index 000000000000..f7e19bc6d21d --- /dev/null +++ b/rust/ql/test/extractor-tests/include-out-dir/test.ql @@ -0,0 +1,37 @@ +import rust +import codeql.rust.internal.PathResolution +import codeql.rust.controlflow.ControlFlowGraph + +query predicate generatedFunction(boolean fromSource, int cfgNodes, string canonicalPath) { + exists(Function f | + f.getName().getText() = "generated_function" and + (if f.fromSource() then fromSource = true else fromSource = false) and + cfgNodes = count(CfgNode n | n.getAstNode().getEnclosingCallable() = f) and + canonicalPath = f.getCanonicalPath() + ) +} + +query predicate rootReference(string source, string target) { + exists(CallExpr call | + source = call.getEnclosingCallable().(Function).getName().getText() and + source = ["generated_function", "generated_alias"] and + target = call.getResolvedTarget().(Function).getName().getText() + ) +} + +query predicate macroReference(string target) { + exists(CallExpr call | + call.fromSource() and + call.isInMacroExpansion() and + call.getFunction().(PathExpr).getPath().getQualifier().getText() = "$crate" and + target = call.getResolvedTarget().(Function).getName().getText() + ) +} + +query predicate exportedMacro(string name, int definitions) { + exists(CrateItemNode c | + c.getName() = "main" and + name = "generated_export" and + definitions = count(MacroRules m | m = c.getASuccessor(name)) + ) +} diff --git a/rust/ql/test/extractor-tests/macro-expansion/test.expected b/rust/ql/test/extractor-tests/macro-expansion/test.expected index a89becb9d3ca..804b859726bd 100644 --- a/rust/ql/test/extractor-tests/macro-expansion/test.expected +++ b/rust/ql/test/extractor-tests/macro-expansion/test.expected @@ -22,7 +22,7 @@ derive_macros | macro_expansion.rs:98:1:102:1 | union MyDeriveUnion | 0 | 0 | macro_expansion.rs:99:7:99:19 | const CONST_MyDeriveUnion | | macro_expansion.rs:98:1:102:1 | union MyDeriveUnion | 0 | 1 | macro_expansion.rs:99:7:99:19 | impl MyTrait for MyDeriveUnion { ... } | macro_calls -| included/included.rs:2:9:2:39 | concat!... | included/included.rs:2:17:2:38 | "Hello world!" | +| included/included.rs:2:9:2:39 | concat!... | included/included.rs:2:9:2:39 | "Hello world!" | | macro_expansion.rs:5:9:5:35 | concat!... | macro_expansion.rs:5:17:5:34 | "Hello world!" | | macro_expansion.rs:5:9:5:35 | concat!... | macro_expansion.rs:5:17:5:34 | "Hello world!" | | macro_expansion.rs:31:7:31:15 | ...::format_args_nl!... | macro_expansion.rs:31:7:31:15 | FormatArgsExpr | diff --git a/rust/ql/test/library-tests/path-resolution/include-library/Cargo.lock b/rust/ql/test/library-tests/path-resolution/include-library/Cargo.lock new file mode 100644 index 000000000000..b9856cfaf77d --- /dev/null +++ b/rust/ql/test/library-tests/path-resolution/include-library/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "test" +version = "0.0.1" diff --git a/rust/ql/test/library-tests/path-resolution/include-library/main.rs b/rust/ql/test/library-tests/path-resolution/include-library/main.rs new file mode 100644 index 000000000000..e1ac8f453484 --- /dev/null +++ b/rust/ql/test/library-tests/path-resolution/include-library/main.rs @@ -0,0 +1,6 @@ +include!("lib.rs"); + +fn main() { + shared::check(); // $ item=check + crate::included_library_export!(); // $ item=included_library_export +} diff --git a/rust/ql/test/library-tests/path-resolution/include-library/shared.rs b/rust/ql/test/library-tests/path-resolution/include-library/shared.rs new file mode 100644 index 000000000000..57a86f7d2163 --- /dev/null +++ b/rust/ql/test/library-tests/path-resolution/include-library/shared.rs @@ -0,0 +1,10 @@ +#[macro_export] +macro_rules! included_library_export { + () => {}; +} + +pub fn helper() {} + +pub fn check() { + crate::shared::helper(); // $ item=helper +} diff --git a/rust/ql/test/library-tests/path-resolution/include-library/test.expected b/rust/ql/test/library-tests/path-resolution/include-library/test.expected new file mode 100644 index 000000000000..c3a456c80ace --- /dev/null +++ b/rust/ql/test/library-tests/path-resolution/include-library/test.expected @@ -0,0 +1,4 @@ +testFailures +exportedMacro +| main | 1 | +| test | 1 | diff --git a/rust/ql/test/library-tests/path-resolution/include-library/test.ql b/rust/ql/test/library-tests/path-resolution/include-library/test.ql new file mode 100644 index 000000000000..a5c5e5494037 --- /dev/null +++ b/rust/ql/test/library-tests/path-resolution/include-library/test.ql @@ -0,0 +1,11 @@ +import rust +import codeql.rust.internal.PathResolution +import utils.test.PathResolutionInlineExpectationsTest + +query predicate exportedMacro(string crateName, int definitions) { + exists(CrateItemNode c | + crateName = c.getName() and + c.fromSource() and + definitions = count(MacroRules m | m = c.getASuccessor("included_library_export")) + ) +} diff --git a/rust/ql/test/library-tests/path-resolution/included/non_mod/generated.rs b/rust/ql/test/library-tests/path-resolution/included/non_mod/generated.rs new file mode 100644 index 000000000000..911e3d5f944f --- /dev/null +++ b/rust/ql/test/library-tests/path-resolution/included/non_mod/generated.rs @@ -0,0 +1 @@ +pub mod non_mod_child; diff --git a/rust/ql/test/library-tests/path-resolution/included/non_mod/generated/non_mod_child.rs b/rust/ql/test/library-tests/path-resolution/included/non_mod/generated/non_mod_child.rs new file mode 100644 index 000000000000..c588ab3646b2 --- /dev/null +++ b/rust/ql/test/library-tests/path-resolution/included/non_mod/generated/non_mod_child.rs @@ -0,0 +1 @@ +pub fn wrong_directory() {} diff --git a/rust/ql/test/library-tests/path-resolution/included/non_mod/non_mod_child.rs b/rust/ql/test/library-tests/path-resolution/included/non_mod/non_mod_child.rs new file mode 100644 index 000000000000..5aec067d71cd --- /dev/null +++ b/rust/ql/test/library-tests/path-resolution/included/non_mod/non_mod_child.rs @@ -0,0 +1 @@ +pub fn from_non_mod() {} // included_non_mod_child diff --git a/rust/ql/test/library-tests/path-resolution/included/tree/disabled_child.rs b/rust/ql/test/library-tests/path-resolution/included/tree/disabled_child.rs new file mode 100644 index 000000000000..57c513c323e8 --- /dev/null +++ b/rust/ql/test/library-tests/path-resolution/included/tree/disabled_child.rs @@ -0,0 +1,4 @@ +#[macro_export] +macro_rules! disabled_child_export { + () => {}; +} diff --git a/rust/ql/test/library-tests/path-resolution/included/tree/macro_child.rs b/rust/ql/test/library-tests/path-resolution/included/tree/macro_child.rs new file mode 100644 index 000000000000..4804ccac4338 --- /dev/null +++ b/rust/ql/test/library-tests/path-resolution/included/tree/macro_child.rs @@ -0,0 +1 @@ +pub fn from_macro() {} // included_macro_child diff --git a/rust/ql/test/library-tests/path-resolution/included/tree/mod.rs b/rust/ql/test/library-tests/path-resolution/included/tree/mod.rs index cd0008471bda..cbabe9a4e024 100644 --- a/rust/ql/test/library-tests/path-resolution/included/tree/mod.rs +++ b/rust/ql/test/library-tests/path-resolution/included/tree/mod.rs @@ -1,10 +1,20 @@ pub mod included_module; include!("nested/mod.rs"); +declare_included_child!(); + +#[cfg(any())] +pub mod disabled_child; pub mod inline_export { #[macro_export] macro_rules! included_export { () => {}; } + + #[cfg(any())] + #[macro_export] + macro_rules! disabled_export { + () => {}; + } } diff --git a/rust/ql/test/library-tests/path-resolution/main.rs b/rust/ql/test/library-tests/path-resolution/main.rs index aad7633f6464..eec8945c9c8f 100644 --- a/rust/ql/test/library-tests/path-resolution/main.rs +++ b/rust/ql/test/library-tests/path-resolution/main.rs @@ -1198,9 +1198,18 @@ fn main() { computed_include::child::form(); // $ item=included_form shadowed_include::from_shadow(); // $ item=from_shadow crate::included_export!(); // $ item=included_export + macro_child::from_macro(); // $ item=included_macro_child + non_mod_child::from_non_mod(); // $ item=included_non_mod_child +} + +macro_rules! declare_included_child { + () => { + pub mod macro_child; + }; } include!("included/tree/mod.rs"); +include!("included/non_mod/generated.rs"); mod qualified_include { std::include!("included/forms/mod.rs"); // $ item=include diff --git a/rust/ql/test/library-tests/path-resolution/path-resolution.expected b/rust/ql/test/library-tests/path-resolution/path-resolution.expected index 66b2abf4eb89..98b54a571a7c 100644 --- a/rust/ql/test/library-tests/path-resolution/path-resolution.expected +++ b/rust/ql/test/library-tests/path-resolution/path-resolution.expected @@ -1,13 +1,23 @@ +includedMacroExports +| disabled_child_export | 0 | +| disabled_export | 0 | +| included_export | 1 | mod +| include-library/lib.rs:1:1:1:15 | mod shared | +| include-library/lib.rs:1:1:1:15 | mod shared | | included/forms/mod.rs:1:1:1:14 | mod child | | included/forms/mod.rs:1:1:1:14 | mod child | | included/forms/mod.rs:1:1:1:14 | mod child | | included/forms/mod.rs:1:1:1:14 | mod child | | included/forms/mod.rs:1:1:1:14 | mod child | +| included/non_mod/generated.rs:1:1:1:22 | mod non_mod_child | +| included/non_mod/generated.rs:1:1:1:22 | mod non_mod_child | | included/tree/mod.rs:1:1:1:24 | mod included_module | | included/tree/mod.rs:1:1:1:24 | mod included_module | -| included/tree/mod.rs:5:1:10:1 | mod inline_export | -| included/tree/mod.rs:5:1:10:1 | mod inline_export | +| included/tree/mod.rs:4:1:4:26 | mod macro_child | +| included/tree/mod.rs:6:1:7:23 | mod disabled_child | +| included/tree/mod.rs:9:1:20:1 | mod inline_export | +| included/tree/mod.rs:9:1:20:1 | mod inline_export | | included/tree/nested/mod.rs:1:1:1:22 | mod nested_module | | included/tree/nested/mod.rs:1:1:1:22 | mod nested_module | | invalid/main.rs:8:1:18:1 | mod self_import_from_struct | @@ -50,11 +60,11 @@ mod | main.rs:1033:1:1077:1 | mod self_constructors | | main.rs:1079:1:1108:1 | mod self_types | | main.rs:1110:1:1154:1 | mod const_static | -| main.rs:1205:1:1207:1 | mod qualified_include | -| main.rs:1209:1:1211:1 | mod braced_include | -| main.rs:1213:1:1215:1 | mod bracket_include | -| main.rs:1217:1:1219:1 | mod computed_include | -| main.rs:1221:1:1228:1 | mod shadowed_include | +| main.rs:1214:1:1216:1 | mod qualified_include | +| main.rs:1218:1:1220:1 | mod braced_include | +| main.rs:1222:1:1224:1 | mod bracket_include | +| main.rs:1226:1:1228:1 | mod computed_include | +| main.rs:1230:1:1237:1 | mod shadowed_include | | my2/mod.rs:1:1:1:16 | mod nested2 | | my2/mod.rs:20:1:20:12 | mod my3 | | my2/mod.rs:22:1:23:10 | mod mymod | @@ -70,14 +80,23 @@ mod | my/nested.rs:1:1:17:1 | mod nested1 | | my/nested.rs:2:5:11:5 | mod nested2 | resolvePath +| include-library/main.rs:1:1:1:7 | include | {EXTERNAL LOCATION} | MacroRules | +| include-library/main.rs:4:5:4:10 | shared | include-library/lib.rs:1:1:1:15 | mod shared | +| include-library/main.rs:4:5:4:17 | ...::check | include-library/shared.rs:8:1:10:1 | fn check | +| include-library/main.rs:5:5:5:9 | crate | include-library/main.rs:0:0:0:0 | Crate(main@0.0.1) | +| include-library/main.rs:5:5:5:34 | ...::included_library_export | include-library/shared.rs:1:1:4:1 | MacroRules | +| include-library/shared.rs:9:5:9:9 | crate | include-library/lib.rs:0:0:0:0 | Crate(test@0.0.1) | +| include-library/shared.rs:9:5:9:9 | crate | include-library/main.rs:0:0:0:0 | Crate(main@0.0.1) | +| include-library/shared.rs:9:5:9:17 | ...::shared | include-library/lib.rs:1:1:1:15 | mod shared | +| include-library/shared.rs:9:5:9:17 | ...::shared | include-library/lib.rs:1:1:1:15 | mod shared | +| include-library/shared.rs:9:5:9:25 | ...::helper | include-library/shared.rs:6:1:6:18 | fn helper | | included/forms/child.rs:2:5:2:9 | crate | main.rs:0:0:0:0 | Crate(main@0.0.1) | | included/forms/child.rs:2:5:2:12 | ...::h | main.rs:57:1:76:1 | fn h | | included/tree/mod.rs:3:1:3:7 | include | {EXTERNAL LOCATION} | MacroRules | | included/tree/nested/nested_module.rs:2:5:2:9 | crate | main.rs:0:0:0:0 | Crate(main@0.0.1) | | included/tree/nested/nested_module.rs:2:5:2:12 | ...::h | main.rs:57:1:76:1 | fn h | | included/tree/nested/nested_module.rs:3:5:3:9 | crate | main.rs:0:0:0:0 | Crate(main@0.0.1) | -| included/tree/nested/nested_module.rs:3:5:3:26 | ...::included_export | included/tree/mod.rs:6:5:9:5 | MacroRules | -| included/tree/nested/nested_module.rs:3:5:3:26 | ...::included_export | included/tree/mod.rs:6:5:9:5 | MacroRules | +| included/tree/nested/nested_module.rs:3:5:3:26 | ...::included_export | included/tree/mod.rs:10:5:13:5 | MacroRules | | invalid/main.rs:6:9:6:9 | A | invalid/main.rs:3:11:4:9 | struct A | | invalid/main.rs:15:9:15:12 | self | invalid/main.rs:8:1:18:1 | mod self_import_from_struct | | invalid/main.rs:15:9:15:22 | ...::MyStruct | invalid/main.rs:12:5:12:20 | struct MyStruct | @@ -107,7 +126,7 @@ resolvePath | main.rs:37:17:37:24 | ...::f | main.rs:26:9:28:9 | fn f | | main.rs:39:17:39:23 | println | {EXTERNAL LOCATION} | MacroRules | | main.rs:40:17:40:17 | f | main.rs:26:9:28:9 | fn f | -| main.rs:47:9:47:13 | super | main.rs:1:1:1228:2 | SourceFile | +| main.rs:47:9:47:13 | super | main.rs:1:1:1237:2 | SourceFile | | main.rs:47:9:47:17 | ...::m1 | main.rs:20:1:44:1 | mod m1 | | main.rs:47:9:47:21 | ...::m2 | main.rs:25:5:43:5 | mod m2 | | main.rs:47:9:47:24 | ...::g | main.rs:30:9:34:9 | fn g | @@ -122,7 +141,7 @@ resolvePath | main.rs:68:17:68:19 | Foo | main.rs:66:9:66:21 | struct Foo | | main.rs:71:13:71:15 | Foo | main.rs:60:5:60:17 | struct Foo | | main.rs:73:5:73:5 | f | main.rs:62:5:69:5 | fn f | -| main.rs:75:5:75:8 | self | main.rs:1:1:1228:2 | SourceFile | +| main.rs:75:5:75:8 | self | main.rs:1:1:1237:2 | SourceFile | | main.rs:75:5:75:11 | ...::i | main.rs:78:1:90:1 | fn i | | main.rs:79:5:79:11 | println | {EXTERNAL LOCATION} | MacroRules | | main.rs:81:13:81:15 | Foo | main.rs:55:1:55:13 | struct Foo | @@ -144,7 +163,7 @@ resolvePath | main.rs:112:9:112:15 | println | {EXTERNAL LOCATION} | MacroRules | | main.rs:118:9:118:15 | println | {EXTERNAL LOCATION} | MacroRules | | main.rs:122:9:122:15 | println | {EXTERNAL LOCATION} | MacroRules | -| main.rs:125:13:125:17 | super | main.rs:1:1:1228:2 | SourceFile | +| main.rs:125:13:125:17 | super | main.rs:1:1:1237:2 | SourceFile | | main.rs:125:13:125:21 | ...::m5 | main.rs:110:1:114:1 | mod m5 | | main.rs:126:9:126:9 | f | main.rs:111:5:113:5 | fn f | | main.rs:126:9:126:9 | f | main.rs:117:5:119:5 | fn f | @@ -725,30 +744,34 @@ resolvePath | main.rs:1193:5:1193:27 | ...::hidden | included/tree/included_module.rs:1:1:1:18 | fn hidden | | main.rs:1194:5:1194:17 | nested_module | included/tree/nested/mod.rs:1:1:1:22 | mod nested_module | | main.rs:1194:5:1194:25 | ...::nested | included/tree/nested/nested_module.rs:1:1:4:1 | fn nested | -| main.rs:1195:5:1195:21 | qualified_include | main.rs:1205:1:1207:1 | mod qualified_include | +| main.rs:1195:5:1195:21 | qualified_include | main.rs:1214:1:1216:1 | mod qualified_include | | main.rs:1195:5:1195:28 | ...::child | included/forms/mod.rs:1:1:1:14 | mod child | | main.rs:1195:5:1195:34 | ...::form | included/forms/child.rs:1:1:3:1 | fn form | -| main.rs:1196:5:1196:18 | braced_include | main.rs:1209:1:1211:1 | mod braced_include | +| main.rs:1196:5:1196:18 | braced_include | main.rs:1218:1:1220:1 | mod braced_include | | main.rs:1196:5:1196:25 | ...::child | included/forms/mod.rs:1:1:1:14 | mod child | | main.rs:1196:5:1196:31 | ...::form | included/forms/child.rs:1:1:3:1 | fn form | -| main.rs:1197:5:1197:19 | bracket_include | main.rs:1213:1:1215:1 | mod bracket_include | +| main.rs:1197:5:1197:19 | bracket_include | main.rs:1222:1:1224:1 | mod bracket_include | | main.rs:1197:5:1197:26 | ...::child | included/forms/mod.rs:1:1:1:14 | mod child | | main.rs:1197:5:1197:32 | ...::form | included/forms/child.rs:1:1:3:1 | fn form | -| main.rs:1198:5:1198:20 | computed_include | main.rs:1217:1:1219:1 | mod computed_include | +| main.rs:1198:5:1198:20 | computed_include | main.rs:1226:1:1228:1 | mod computed_include | | main.rs:1198:5:1198:27 | ...::child | included/forms/mod.rs:1:1:1:14 | mod child | | main.rs:1198:5:1198:33 | ...::form | included/forms/child.rs:1:1:3:1 | fn form | -| main.rs:1199:5:1199:20 | shadowed_include | main.rs:1221:1:1228:1 | mod shadowed_include | -| main.rs:1199:5:1199:33 | ...::from_shadow | main.rs:1227:5:1227:12 | fn from_shadow | +| main.rs:1199:5:1199:20 | shadowed_include | main.rs:1230:1:1237:1 | mod shadowed_include | +| main.rs:1199:5:1199:33 | ...::from_shadow | main.rs:1236:5:1236:12 | fn from_shadow | | main.rs:1200:5:1200:9 | crate | main.rs:0:0:0:0 | Crate(main@0.0.1) | -| main.rs:1200:5:1200:26 | ...::included_export | included/tree/mod.rs:6:5:9:5 | MacroRules | -| main.rs:1200:5:1200:26 | ...::included_export | included/tree/mod.rs:6:5:9:5 | MacroRules | -| main.rs:1203:1:1203:7 | include | {EXTERNAL LOCATION} | MacroRules | -| main.rs:1206:5:1206:7 | std | {EXTERNAL LOCATION} | Crate(std@0.0.0) | -| main.rs:1206:5:1206:16 | ...::include | {EXTERNAL LOCATION} | MacroRules | -| main.rs:1210:5:1210:11 | include | {EXTERNAL LOCATION} | MacroRules | -| main.rs:1214:5:1214:11 | include | {EXTERNAL LOCATION} | MacroRules | -| main.rs:1218:5:1218:11 | include | {EXTERNAL LOCATION} | MacroRules | -| main.rs:1227:5:1227:11 | include | main.rs:1222:5:1226:5 | MacroRules | +| main.rs:1200:5:1200:26 | ...::included_export | included/tree/mod.rs:10:5:13:5 | MacroRules | +| main.rs:1201:5:1201:15 | macro_child | included/tree/mod.rs:4:1:4:26 | mod macro_child | +| main.rs:1201:5:1201:27 | ...::from_macro | included/tree/macro_child.rs:1:1:1:22 | fn from_macro | +| main.rs:1202:5:1202:17 | non_mod_child | included/non_mod/generated.rs:1:1:1:22 | mod non_mod_child | +| main.rs:1202:5:1202:31 | ...::from_non_mod | included/non_mod/non_mod_child.rs:1:1:1:24 | fn from_non_mod | +| main.rs:1211:1:1211:7 | include | {EXTERNAL LOCATION} | MacroRules | +| main.rs:1212:1:1212:7 | include | {EXTERNAL LOCATION} | MacroRules | +| main.rs:1215:5:1215:7 | std | {EXTERNAL LOCATION} | Crate(std@0.0.0) | +| main.rs:1215:5:1215:16 | ...::include | {EXTERNAL LOCATION} | MacroRules | +| main.rs:1219:5:1219:11 | include | {EXTERNAL LOCATION} | MacroRules | +| main.rs:1223:5:1223:11 | include | {EXTERNAL LOCATION} | MacroRules | +| main.rs:1227:5:1227:11 | include | {EXTERNAL LOCATION} | MacroRules | +| main.rs:1236:5:1236:11 | include | main.rs:1231:5:1235:5 | MacroRules | | my2/mod.rs:4:5:4:11 | println | {EXTERNAL LOCATION} | MacroRules | | my2/mod.rs:5:5:5:11 | nested2 | my2/mod.rs:1:1:1:16 | mod nested2 | | my2/mod.rs:5:5:5:20 | ...::nested3 | my2/nested2.rs:1:1:11:1 | mod nested3 | @@ -774,7 +797,7 @@ resolvePath | my2/my3/mod.rs:3:5:3:5 | g | my2/mod.rs:3:1:6:1 | fn g | | my2/my3/mod.rs:4:5:4:5 | h | main.rs:57:1:76:1 | fn h | | my2/my3/mod.rs:7:5:7:9 | super | my2/mod.rs:1:1:25:34 | SourceFile | -| my2/my3/mod.rs:7:5:7:16 | ...::super | main.rs:1:1:1228:2 | SourceFile | +| my2/my3/mod.rs:7:5:7:16 | ...::super | main.rs:1:1:1237:2 | SourceFile | | my2/my3/mod.rs:7:5:7:19 | ...::h | main.rs:57:1:76:1 | fn h | | my2/my3/mod.rs:8:5:8:9 | super | my2/mod.rs:1:1:25:34 | SourceFile | | my2/my3/mod.rs:8:5:8:12 | ...::g | my2/mod.rs:3:1:6:1 | fn g | diff --git a/rust/ql/test/library-tests/path-resolution/path-resolution.ql b/rust/ql/test/library-tests/path-resolution/path-resolution.ql index f7955929b839..cdb75c7bec9f 100644 --- a/rust/ql/test/library-tests/path-resolution/path-resolution.ql +++ b/rust/ql/test/library-tests/path-resolution/path-resolution.ql @@ -10,3 +10,12 @@ query predicate resolvePath(PathExt p, ItemNode i) { not p.isFromMacroExpansion() and i = resolvePath(p) } + +query predicate includedMacroExports(string name, int definitions) { + exists(CrateItemNode c | + c.getName() = "main" and + c.getSourceFile().getFile().getRelativePath() = "main.rs" and + name = ["included_export", "disabled_export", "disabled_child_export"] and + definitions = count(MacroRules m | m = c.getASuccessor(name)) + ) +} From 327743601f4a7b9822cd0671c42e8713de3d816d Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Fri, 13 Nov 2026 11:41:40 +0100 Subject: [PATCH 06/11] Rust: extract generated include modules and preserve argument spans Track semantic availability for included files and isolate the library-root include regression from its parent test. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- rust/extractor/src/main.rs | 29 +++++- rust/extractor/src/translate/base.rs | 35 ++++++-- .../2026-11-13-include-module-resolution.md | 2 +- rust/ql/lib/codeql/files/FileSystem.qll | 5 +- .../extractor-tests/include-out-dir/build.rs | 19 ++++ .../extractor-tests/include-out-dir/main.rs | 1 + .../include-out-dir/test.expected | 4 + .../extractor-tests/include-out-dir/test.ql | 4 +- .../macro-expansion/PrintAst.expected | 89 +++++++++++++++++++ .../macro-expansion/included/included.rs | 24 +++++ .../macro-expansion/test.expected | 21 ++++- .../extractor-tests/macro-expansion/test.ql | 9 ++ .../Cargo.lock | 0 .../main.rs | 0 .../shared.rs | 0 .../test.expected | 0 .../test.ql | 0 .../path-resolution/path-resolution.expected | 15 +--- .../path-resolution/path-resolution.ql | 9 ++ 19 files changed, 240 insertions(+), 26 deletions(-) rename rust/ql/test/library-tests/{path-resolution/include-library => path-resolution-include-library}/Cargo.lock (100%) rename rust/ql/test/library-tests/{path-resolution/include-library => path-resolution-include-library}/main.rs (100%) rename rust/ql/test/library-tests/{path-resolution/include-library => path-resolution-include-library}/shared.rs (100%) rename rust/ql/test/library-tests/{path-resolution/include-library => path-resolution-include-library}/test.expected (100%) rename rust/ql/test/library-tests/{path-resolution/include-library => path-resolution-include-library}/test.ql (100%) diff --git a/rust/extractor/src/main.rs b/rust/extractor/src/main.rs index 1eb685647d28..04d16bd7b27c 100644 --- a/rust/extractor/src/main.rs +++ b/rust/extractor/src/main.rs @@ -14,7 +14,7 @@ use ra_ap_paths::{AbsPathBuf, Utf8PathBuf}; use ra_ap_project_model::{CargoConfig, ProjectManifest}; use ra_ap_vfs::Vfs; use rust_analyzer::{ParseResult, RustAnalyzer}; -use std::collections::{BTreeMap, HashSet}; +use std::collections::{BTreeMap, HashMap, HashSet}; use std::fs::File; use std::io::{self, BufRead, BufReader}; use std::path::{Path, PathBuf}; @@ -41,6 +41,7 @@ struct Extractor<'a> { archiver: &'a Archiver, traps: &'a trap::TrapFileProvider, steps: Vec, + extracted_with_semantics: HashMap, } impl<'a> Extractor<'a> { @@ -49,10 +50,16 @@ impl<'a> Extractor<'a> { archiver, traps, steps: Vec::new(), + extracted_with_semantics: HashMap::new(), } } - fn extract(&mut self, rust_analyzer: &RustAnalyzer, file: &Path, source_kind: SourceKind) { + fn extract( + &mut self, + rust_analyzer: &RustAnalyzer, + file: &Path, + source_kind: SourceKind, + ) -> Vec { self.archiver.archive(file); let before_parse = Instant::now(); let ParseResult { @@ -111,6 +118,7 @@ impl<'a> Extractor<'a> { }); self.steps .push(ExtractionStep::extract(before_extract, source_kind, file)); + translator.module_files } pub fn extract_with_semantics<'db>( @@ -120,7 +128,19 @@ impl<'a> Extractor<'a> { vfs: &'db Vfs, source_kind: SourceKind, ) { - self.extract(&RustAnalyzer::new(vfs, semantics), file, source_kind); + let mut pending = vec![file.to_path_buf()]; + while let Some(file) = pending.pop() { + if self + .extracted_with_semantics + .get(&file) + .is_some_and(|kind| *kind == SourceKind::Source || *kind == source_kind) + { + continue; + } + self.extracted_with_semantics + .insert(file.clone(), source_kind); + pending.extend(self.extract(&RustAnalyzer::new(vfs, semantics), &file, source_kind)); + } } pub fn extract_without_semantics( @@ -139,6 +159,9 @@ impl<'a> Extractor<'a> { load_config: &LoadCargoConfig, ) -> Option<(RootDatabase, Vfs)> { let before = Instant::now(); + // Dependency workspaces must not overwrite generated modules extracted as source. + self.extracted_with_semantics + .retain(|_, kind| *kind == SourceKind::Source); let ret = RustAnalyzer::load_workspace(project, config, load_config); self.steps .push(ExtractionStep::load_manifest(before, project)); diff --git a/rust/extractor/src/translate/base.rs b/rust/extractor/src/translate/base.rs index 524a50f74507..4e6fc37a739c 100644 --- a/rust/extractor/src/translate/base.rs +++ b/rust/extractor/src/translate/base.rs @@ -140,6 +140,7 @@ pub struct Translator<'db> { included_files: HashMap, included_file_context: Option, pub(crate) included_file_steps: Vec, + pub(crate) module_files: Vec, pub(crate) macro_context_depth: usize, parenthesized_expr_attrs: HashMap>>, diagnostic_count: usize, @@ -181,6 +182,7 @@ impl<'db> Translator<'db> { included_files: HashMap::new(), included_file_context: None, included_file_steps: Vec::new(), + module_files: Vec::new(), macro_context_depth: 0, parenthesized_expr_attrs: HashMap::new(), diagnostic_count: 0, @@ -235,16 +237,25 @@ impl<'db> Translator<'db> { let semantics = self.semantics?; let macro_file = file_id.macro_file()?; let call = macro_file.loc(semantics.db); + let span_map = macro_file.expansion_span_map(semantics.db); + let (start, _) = span_for_offset(semantics.db, span_map, range.start()); + let (end, _) = span_for_offset(semantics.db, span_map, range.end().checked_sub(1.into())?); if !call.def.is_include() { - // Ordinary macros retain invocation-site locations, not definition-site spans. let invocation = call.to_node(semantics.db); - return self.included_file_range(invocation.file_id, invocation.value.text_range()); + let (invocation_file, invocation_range) = + self.included_file_range(invocation.file_id, invocation.value.text_range())?; + // Preserve copied arguments, but attribute definition-origin tokens to the call. + if start.file_id == invocation_file + && end.file_id == invocation_file + && invocation_range.contains_range(start.range) + && invocation_range.contains_range(end.range) + { + return Some((invocation_file, start.range.cover(end.range))); + } + return Some((invocation_file, invocation_range)); } // Include expansions reformat tokens and give them non-root syntax contexts. // Resolve boundary spans (which may have different anchors) to source offsets. - let span_map = macro_file.expansion_span_map(semantics.db); - let (start, _) = span_for_offset(semantics.db, span_map, range.start()); - let (end, _) = span_for_offset(semantics.db, span_map, range.end().checked_sub(1.into())?); (start.file_id == end.file_id && (self.included_files.contains_key(&start.file_id) || Some(start.file_id) == self.file_id)) @@ -624,6 +635,8 @@ impl<'db> Translator<'db> { self.source_kind, path, )); + self.included_file_steps + .push(ExtractionStep::load_source(before_expand, path)); self.included_file_context = previous_context; } } else if self.semantics.is_some() { @@ -915,6 +928,18 @@ impl<'db> Translator<'db> { } pub(crate) fn item_post_emit(&mut self, node: &ast::Item, label: Label) { + if self.macro_context_depth == 0 + && let ast::Item::Module(node) = node + && node.semicolon_token().is_some() + && let Some(semantics) = self.semantics + && let Some(module) = semantics.to_def(node) + && let Some(file_id) = module.as_source_file_id(semantics.db) + && let Some(path) = self + .vfs + .and_then(|vfs| vfs.file_path(file_id.file_id(semantics.db)).as_path()) + { + self.module_files.push(path.to_path_buf().into()); + } if !self.is_attribute_macro_target(node) { return; } diff --git a/rust/ql/lib/change-notes/2026-11-13-include-module-resolution.md b/rust/ql/lib/change-notes/2026-11-13-include-module-resolution.md index f311e2078d0d..0f06fb4ddefc 100644 --- a/rust/ql/lib/change-notes/2026-11-13-include-module-resolution.md +++ b/rust/ql/lib/change-notes/2026-11-13-include-module-resolution.md @@ -1,4 +1,4 @@ --- category: minorAnalysis --- -* Module declarations and crate-root references in files included with `include!` now resolve correctly, including nested includes, build-script output, and active exported macros. Included source files are archived and retain their source classification and extraction diagnostics. \ No newline at end of file +* Module declarations and crate-root references in files included with `include!` now resolve correctly, including nested includes, build-script output and its child modules, and active exported macros. Included source files are archived and retain their source classification, semantic availability, and extraction diagnostics. Macro arguments in included files retain their original source locations. \ No newline at end of file diff --git a/rust/ql/lib/codeql/files/FileSystem.qll b/rust/ql/lib/codeql/files/FileSystem.qll index 883ef83ae546..863bd3e06063 100644 --- a/rust/ql/lib/codeql/files/FileSystem.qll +++ b/rust/ql/lib/codeql/files/FileSystem.qll @@ -89,7 +89,10 @@ class ExtractedFile extends File { ExtractedFile() { this.fromSource() } private Diagnostic getNoSemanticsDiagnostic() { - result.getTag() = "semantics" and result.getLocation().getFile() = this + result.getTag() = "semantics" and + result.getLocation().getFile() = this and + // A standalone parse may lack semantics even though an include expansion has them. + not exists(ExtractorStep s | s.getAction() = "LoadSource" and s.getFile() = this) } /** diff --git a/rust/ql/test/extractor-tests/include-out-dir/build.rs b/rust/ql/test/extractor-tests/include-out-dir/build.rs index efc465eef8c3..dc9e0df2891d 100644 --- a/rust/ql/test/extractor-tests/include-out-dir/build.rs +++ b/rust/ql/test/extractor-tests/include-out-dir/build.rs @@ -5,6 +5,8 @@ fn main() { r#" extern crate self as generated_self; +pub mod child; + pub fn generated_function() -> i32 { crate::root_function() } @@ -22,4 +24,21 @@ pub mod inline { "#, ) .unwrap(); + std::fs::write( + out.join("child.rs"), + r#" +pub mod grandchild; + +pub fn child_function() -> i32 { + grandchild::grandchild_function() +} +"#, + ) + .unwrap(); + std::fs::create_dir_all(out.join("child")).unwrap(); + std::fs::write( + out.join("child/grandchild.rs"), + "pub fn grandchild_function() -> i32 { crate::root_function() }\n", + ) + .unwrap(); } diff --git a/rust/ql/test/extractor-tests/include-out-dir/main.rs b/rust/ql/test/extractor-tests/include-out-dir/main.rs index d0d8f9d74a5c..1079a64fb2f9 100644 --- a/rust/ql/test/extractor-tests/include-out-dir/main.rs +++ b/rust/ql/test/extractor-tests/include-out-dir/main.rs @@ -6,5 +6,6 @@ fn root_function() -> i32 { fn main() { generated_function(); + child::child_function(); crate::generated_export!(); } diff --git a/rust/ql/test/extractor-tests/include-out-dir/test.expected b/rust/ql/test/extractor-tests/include-out-dir/test.expected index 3d2661ca861b..06ae6412f1e6 100644 --- a/rust/ql/test/extractor-tests/include-out-dir/test.expected +++ b/rust/ql/test/extractor-tests/include-out-dir/test.expected @@ -1,8 +1,12 @@ generatedFunction +| true | 3 | main::child::child_function | +| true | 3 | main::child::grandchild::grandchild_function | | true | 3 | main::generated_function | rootReference +| child_function | grandchild_function | | generated_alias | root_function | | generated_function | root_function | +| grandchild_function | root_function | macroReference | root_function | exportedMacro diff --git a/rust/ql/test/extractor-tests/include-out-dir/test.ql b/rust/ql/test/extractor-tests/include-out-dir/test.ql index f7e19bc6d21d..1a445e3626e2 100644 --- a/rust/ql/test/extractor-tests/include-out-dir/test.ql +++ b/rust/ql/test/extractor-tests/include-out-dir/test.ql @@ -4,7 +4,7 @@ import codeql.rust.controlflow.ControlFlowGraph query predicate generatedFunction(boolean fromSource, int cfgNodes, string canonicalPath) { exists(Function f | - f.getName().getText() = "generated_function" and + f.getName().getText() = ["generated_function", "child_function", "grandchild_function"] and (if f.fromSource() then fromSource = true else fromSource = false) and cfgNodes = count(CfgNode n | n.getAstNode().getEnclosingCallable() = f) and canonicalPath = f.getCanonicalPath() @@ -14,7 +14,7 @@ query predicate generatedFunction(boolean fromSource, int cfgNodes, string canon query predicate rootReference(string source, string target) { exists(CallExpr call | source = call.getEnclosingCallable().(Function).getName().getText() and - source = ["generated_function", "generated_alias"] and + source = ["generated_function", "generated_alias", "child_function", "grandchild_function"] and target = call.getResolvedTarget().(Function).getName().getText() ) } diff --git a/rust/ql/test/extractor-tests/macro-expansion/PrintAst.expected b/rust/ql/test/extractor-tests/macro-expansion/PrintAst.expected index de02b61806ab..50e3657b029f 100644 --- a/rust/ql/test/extractor-tests/macro-expansion/PrintAst.expected +++ b/rust/ql/test/extractor-tests/macro-expansion/PrintAst.expected @@ -156,6 +156,41 @@ included/included.rs: # 7| getPath(): [Path] usize # 7| getSegment(): [PathSegment] usize # 7| getIdentifier(): [NameRef] usize +# 10| getItem(2): [MacroRules] MacroRules +# 10| getName(): [Name] identity +# 10| getTokenTree(): [TokenTree] TokenTree +# 16| getItem(3): [Function] fn argument_location +# 16| getParamList(): [ParamList] ParamList +# 16| getFunctionBody(): [BlockExpr] { ... } +# 16| getStmtList(): [StmtList] StmtList +# 17| getStatement(0): [ExprStmt] ExprStmt +# 17| getExpr(): [MacroExpr] MacroExpr +# 17| getMacroCall(): [MacroCall] identity!... +# 17| getPath(): [Path] identity +# 17| getSegment(): [PathSegment] identity +# 17| getIdentifier(): [NameRef] identity +# 17| getTokenTree(): [TokenTree] TokenTree +# 16| getName(): [Name] argument_location +# 22| getItem(4): [Function] fn dangerous_call +# 22| getParamList(): [ParamList] ParamList +# 22| getFunctionBody(): [BlockExpr] { ... } +# 22| getStmtList(): [StmtList] StmtList +# 22| getName(): [Name] dangerous_call +# 24| getItem(5): [MacroRules] MacroRules +# 24| getName(): [Name] definition_location +# 24| getTokenTree(): [TokenTree] TokenTree +# 30| getItem(6): [Function] fn definition_origin +# 30| getParamList(): [ParamList] ParamList +# 30| getFunctionBody(): [BlockExpr] { ... } +# 30| getStmtList(): [StmtList] StmtList +# 31| getStatement(0): [ExprStmt] ExprStmt +# 31| getExpr(): [MacroExpr] MacroExpr +# 31| getMacroCall(): [MacroCall] definition_location!... +# 31| getPath(): [Path] definition_location +# 31| getSegment(): [PathSegment] definition_location +# 31| getIdentifier(): [NameRef] definition_location +# 31| getTokenTree(): [TokenTree] TokenTree +# 30| getName(): [Name] definition_origin # 2| [Comment] //... lib.rs: # 1| [SourceFile] SourceFile @@ -746,6 +781,60 @@ included/included.rs: # 7| getPath(): [Path] usize # 7| getSegment(): [PathSegment] usize # 7| getIdentifier(): [NameRef] usize +# 10| getItem(2): [MacroRules] MacroRules +# 10| getName(): [Name] identity +# 10| getTokenTree(): [TokenTree] TokenTree +# 16| getItem(3): [Function] fn argument_location +# 16| getParamList(): [ParamList] ParamList +# 16| getFunctionBody(): [BlockExpr] { ... } +# 16| getStmtList(): [StmtList] StmtList +# 17| getStatement(0): [ExprStmt] ExprStmt +# 17| getExpr(): [MacroExpr] MacroExpr +# 17| getMacroCall(): [MacroCall] identity!... +# 17| getPath(): [Path] identity +# 17| getSegment(): [PathSegment] identity +# 17| getIdentifier(): [NameRef] identity +# 17| getTokenTree(): [TokenTree] TokenTree +# 17| getMacroCallExpansion(): [BlockExpr] { ... } +# 17| getStmtList(): [StmtList] StmtList +# 17| getTailExpr(): [BlockExpr] { ... } +# 17| getStmtList(): [StmtList] StmtList +# 18| getStatement(0): [ExprStmt] ExprStmt +# 18| getExpr(): [CallExpr] dangerous_call(...) +# 18| getArgList(): [ArgList] ArgList +# 18| getFunction(): [PathExpr] dangerous_call +# 18| getPath(): [Path] dangerous_call +# 18| getSegment(): [PathSegment] dangerous_call +# 18| getIdentifier(): [NameRef] dangerous_call +# 16| getName(): [Name] argument_location +# 22| getItem(4): [Function] fn dangerous_call +# 22| getParamList(): [ParamList] ParamList +# 22| getFunctionBody(): [BlockExpr] { ... } +# 22| getStmtList(): [StmtList] StmtList +# 22| getName(): [Name] dangerous_call +# 24| getItem(5): [MacroRules] MacroRules +# 24| getName(): [Name] definition_location +# 24| getTokenTree(): [TokenTree] TokenTree +# 30| getItem(6): [Function] fn definition_origin +# 30| getParamList(): [ParamList] ParamList +# 30| getFunctionBody(): [BlockExpr] { ... } +# 30| getStmtList(): [StmtList] StmtList +# 31| getStatement(0): [ExprStmt] ExprStmt +# 31| getExpr(): [MacroExpr] MacroExpr +# 31| getMacroCall(): [MacroCall] definition_location!... +# 31| getPath(): [Path] definition_location +# 31| getSegment(): [PathSegment] definition_location +# 31| getIdentifier(): [NameRef] definition_location +# 31| getTokenTree(): [TokenTree] TokenTree +# 31| getMacroCallExpansion(): [BlockExpr] { ... } +# 31| getStmtList(): [StmtList] StmtList +# 31| getTailExpr(): [CallExpr] dangerous_call(...) +# 31| getArgList(): [ArgList] ArgList +# 31| getFunction(): [PathExpr] dangerous_call +# 31| getPath(): [Path] dangerous_call +# 31| getSegment(): [PathSegment] dangerous_call +# 31| getIdentifier(): [NameRef] dangerous_call +# 30| getName(): [Name] definition_origin macro_expansion.rs: # 63| getItem(12): [Function] fn documented # 64| getParamList(): [ParamList] ParamList diff --git a/rust/ql/test/extractor-tests/macro-expansion/included/included.rs b/rust/ql/test/extractor-tests/macro-expansion/included/included.rs index e703da4b3cdc..7f603237b2d9 100644 --- a/rust/ql/test/extractor-tests/macro-expansion/included/included.rs +++ b/rust/ql/test/extractor-tests/macro-expansion/included/included.rs @@ -6,3 +6,27 @@ fn included() { struct IncludedDerived { value: usize, } + +macro_rules! identity { + ($value:expr) => { + $value + }; +} + +fn argument_location() { + identity!({ + dangerous_call(); + }); +} + +fn dangerous_call() {} + +macro_rules! definition_location { + () => { + dangerous_call() + }; +} + +fn definition_origin() { + definition_location!(); +} diff --git a/rust/ql/test/extractor-tests/macro-expansion/test.expected b/rust/ql/test/extractor-tests/macro-expansion/test.expected index 804b859726bd..f75ae56fa85b 100644 --- a/rust/ql/test/extractor-tests/macro-expansion/test.expected +++ b/rust/ql/test/extractor-tests/macro-expansion/test.expected @@ -22,7 +22,9 @@ derive_macros | macro_expansion.rs:98:1:102:1 | union MyDeriveUnion | 0 | 0 | macro_expansion.rs:99:7:99:19 | const CONST_MyDeriveUnion | | macro_expansion.rs:98:1:102:1 | union MyDeriveUnion | 0 | 1 | macro_expansion.rs:99:7:99:19 | impl MyTrait for MyDeriveUnion { ... } | macro_calls -| included/included.rs:2:9:2:39 | concat!... | included/included.rs:2:9:2:39 | "Hello world!" | +| included/included.rs:2:9:2:39 | concat!... | included/included.rs:2:17:2:38 | "Hello world!" | +| included/included.rs:17:5:19:6 | identity!... | included/included.rs:17:15:19:5 | { ... } | +| included/included.rs:31:5:31:26 | definition_location!... | included/included.rs:31:5:31:26 | { ... } | | macro_expansion.rs:5:9:5:35 | concat!... | macro_expansion.rs:5:17:5:34 | "Hello world!" | | macro_expansion.rs:5:9:5:35 | concat!... | macro_expansion.rs:5:17:5:34 | "Hello world!" | | macro_expansion.rs:31:7:31:15 | ...::format_args_nl!... | macro_expansion.rs:31:7:31:15 | FormatArgsExpr | @@ -39,7 +41,7 @@ macro_calls | macro_expansion.rs:55:9:58:5 | my_macro!... | macro_expansion.rs:56:9:57:13 | MacroExpr | | macro_expansion.rs:56:9:57:13 | ...::format_args!... | macro_expansion.rs:56:9:57:13 | FormatArgsExpr | | macro_expansion.rs:56:9:57:13 | format!... | macro_expansion.rs:56:9:57:13 | ...::must_use(...) | -| macro_expansion.rs:61:1:61:33 | include!... | included/included.rs:1:1:8:1 | MacroItems | +| macro_expansion.rs:61:1:61:33 | include!... | included/included.rs:1:1:32:1 | MacroItems | | macro_expansion.rs:70:16:70:24 | my_int!... | macro_expansion.rs:70:16:70:22 | i32 | | macro_expansion.rs:71:12:71:20 | my_int!... | macro_expansion.rs:71:12:71:18 | i32 | | macro_expansion.rs:72:10:72:18 | my_int!... | macro_expansion.rs:72:10:72:16 | i32 | @@ -47,7 +49,22 @@ macro_calls | macro_expansion.rs:79:12:79:20 | my_int!... | macro_expansion.rs:79:12:79:18 | i32 | unexpanded_macro_calls | included/included.rs:2:9:2:39 | concat!... | +| included/included.rs:17:5:19:6 | identity!... | +| included/included.rs:31:5:31:26 | definition_location!... | | macro_expansion.rs:56:9:56:31 | concat!... | | macro_expansion.rs:63:9:63:32 | include_str!... | warnings | macro_expansion.rs:56:9:56:31 | macro expansion failed for 'concat' | +includedArgumentLocation +| included/included.rs:18:9:18:22 | dangerous_call | 18 | +| included/included.rs:18:9:18:22 | dangerous_call | 18 | +| included/included.rs:18:9:18:22 | dangerous_call | 18 | +| included/included.rs:18:9:18:22 | dangerous_call | 18 | +| included/included.rs:18:9:18:24 | dangerous_call(...) | 18 | +| included/included.rs:18:23:18:24 | ArgList | 18 | +| included/included.rs:31:5:31:26 | ArgList | 31 | +| included/included.rs:31:5:31:26 | dangerous_call | 31 | +| included/included.rs:31:5:31:26 | dangerous_call | 31 | +| included/included.rs:31:5:31:26 | dangerous_call | 31 | +| included/included.rs:31:5:31:26 | dangerous_call | 31 | +| included/included.rs:31:5:31:26 | dangerous_call(...) | 31 | diff --git a/rust/ql/test/extractor-tests/macro-expansion/test.ql b/rust/ql/test/extractor-tests/macro-expansion/test.ql index 6372dc2e93fa..c285daf6bd2d 100644 --- a/rust/ql/test/extractor-tests/macro-expansion/test.ql +++ b/rust/ql/test/extractor-tests/macro-expansion/test.ql @@ -20,3 +20,12 @@ query predicate unexpanded_macro_calls(MacroCall c) { } query predicate warnings(ExtractionWarning w) { any() } + +query predicate includedArgumentLocation(AstNode node, int line) { + exists(CallExpr call | + call.isInMacroExpansion() and + call.getFunction().(PathExpr).getPath().getText() = "dangerous_call" and + call = node.getParentNode*() and + line = node.getLocation().getStartLine() + ) +} diff --git a/rust/ql/test/library-tests/path-resolution/include-library/Cargo.lock b/rust/ql/test/library-tests/path-resolution-include-library/Cargo.lock similarity index 100% rename from rust/ql/test/library-tests/path-resolution/include-library/Cargo.lock rename to rust/ql/test/library-tests/path-resolution-include-library/Cargo.lock diff --git a/rust/ql/test/library-tests/path-resolution/include-library/main.rs b/rust/ql/test/library-tests/path-resolution-include-library/main.rs similarity index 100% rename from rust/ql/test/library-tests/path-resolution/include-library/main.rs rename to rust/ql/test/library-tests/path-resolution-include-library/main.rs diff --git a/rust/ql/test/library-tests/path-resolution/include-library/shared.rs b/rust/ql/test/library-tests/path-resolution-include-library/shared.rs similarity index 100% rename from rust/ql/test/library-tests/path-resolution/include-library/shared.rs rename to rust/ql/test/library-tests/path-resolution-include-library/shared.rs diff --git a/rust/ql/test/library-tests/path-resolution/include-library/test.expected b/rust/ql/test/library-tests/path-resolution-include-library/test.expected similarity index 100% rename from rust/ql/test/library-tests/path-resolution/include-library/test.expected rename to rust/ql/test/library-tests/path-resolution-include-library/test.expected diff --git a/rust/ql/test/library-tests/path-resolution/include-library/test.ql b/rust/ql/test/library-tests/path-resolution-include-library/test.ql similarity index 100% rename from rust/ql/test/library-tests/path-resolution/include-library/test.ql rename to rust/ql/test/library-tests/path-resolution-include-library/test.ql diff --git a/rust/ql/test/library-tests/path-resolution/path-resolution.expected b/rust/ql/test/library-tests/path-resolution/path-resolution.expected index 98b54a571a7c..94845f1e88c4 100644 --- a/rust/ql/test/library-tests/path-resolution/path-resolution.expected +++ b/rust/ql/test/library-tests/path-resolution/path-resolution.expected @@ -2,9 +2,10 @@ includedMacroExports | disabled_child_export | 0 | | disabled_export | 0 | | included_export | 1 | +includedSemantics +| included/tree/disabled_child.rs | false | true | +| included/tree/mod.rs | true | false | mod -| include-library/lib.rs:1:1:1:15 | mod shared | -| include-library/lib.rs:1:1:1:15 | mod shared | | included/forms/mod.rs:1:1:1:14 | mod child | | included/forms/mod.rs:1:1:1:14 | mod child | | included/forms/mod.rs:1:1:1:14 | mod child | @@ -80,16 +81,6 @@ mod | my/nested.rs:1:1:17:1 | mod nested1 | | my/nested.rs:2:5:11:5 | mod nested2 | resolvePath -| include-library/main.rs:1:1:1:7 | include | {EXTERNAL LOCATION} | MacroRules | -| include-library/main.rs:4:5:4:10 | shared | include-library/lib.rs:1:1:1:15 | mod shared | -| include-library/main.rs:4:5:4:17 | ...::check | include-library/shared.rs:8:1:10:1 | fn check | -| include-library/main.rs:5:5:5:9 | crate | include-library/main.rs:0:0:0:0 | Crate(main@0.0.1) | -| include-library/main.rs:5:5:5:34 | ...::included_library_export | include-library/shared.rs:1:1:4:1 | MacroRules | -| include-library/shared.rs:9:5:9:9 | crate | include-library/lib.rs:0:0:0:0 | Crate(test@0.0.1) | -| include-library/shared.rs:9:5:9:9 | crate | include-library/main.rs:0:0:0:0 | Crate(main@0.0.1) | -| include-library/shared.rs:9:5:9:17 | ...::shared | include-library/lib.rs:1:1:1:15 | mod shared | -| include-library/shared.rs:9:5:9:17 | ...::shared | include-library/lib.rs:1:1:1:15 | mod shared | -| include-library/shared.rs:9:5:9:25 | ...::helper | include-library/shared.rs:6:1:6:18 | fn helper | | included/forms/child.rs:2:5:2:9 | crate | main.rs:0:0:0:0 | Crate(main@0.0.1) | | included/forms/child.rs:2:5:2:12 | ...::h | main.rs:57:1:76:1 | fn h | | included/tree/mod.rs:3:1:3:7 | include | {EXTERNAL LOCATION} | MacroRules | diff --git a/rust/ql/test/library-tests/path-resolution/path-resolution.ql b/rust/ql/test/library-tests/path-resolution/path-resolution.ql index cdb75c7bec9f..fa92e8fb9369 100644 --- a/rust/ql/test/library-tests/path-resolution/path-resolution.ql +++ b/rust/ql/test/library-tests/path-resolution/path-resolution.ql @@ -19,3 +19,12 @@ query predicate includedMacroExports(string name, int definitions) { definitions = count(MacroRules m | m = c.getASuccessor(name)) ) } + +query predicate includedSemantics(string file, boolean hasSemantics, boolean skipped) { + exists(ExtractedFile f | + file = f.getRelativePath() and + file = ["included/tree/mod.rs", "included/tree/disabled_child.rs"] and + (if f.hasSemantics() then hasSemantics = true else hasSemantics = false) and + (if f.isSkippedByCompilation() then skipped = true else skipped = false) + ) +} From f1c0b0331b68362331b5375158eff50496ee408b Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Fri, 13 Nov 2026 12:36:20 +0100 Subject: [PATCH 07/11] Fix include extraction review issues Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- rust/extractor/src/main.rs | 85 +- .../file-exclusions/src/lib.rs | 2 + rust/ql/lib/codeql/files/FileSystem.qll | 31 +- .../rust/elements/internal/AstNodeImpl.qll | 7 +- .../codeql/rust/internal/PathResolution.qll | 38 +- .../src/queries/telemetry/DatabaseQuality.qll | 20 +- .../extractor-tests/include-out-dir/build.rs | 3 + .../extractor-tests/include-out-dir/main.rs | 4 + .../include-out-dir/test.expected | 3 + .../extractor-tests/include-out-dir/test.ql | 16 + .../macro-expansion/PrintAst.expected | 800 +++++++++--------- .../macro-expansion/included/included.rs | 6 + .../macro-expansion/macro_expansion.rs | 4 + .../macro-expansion/test.expected | 39 +- .../extractor-tests/macro-expansion/test.ql | 28 + 15 files changed, 645 insertions(+), 441 deletions(-) diff --git a/rust/extractor/src/main.rs b/rust/extractor/src/main.rs index 04d16bd7b27c..ff0b357d459f 100644 --- a/rust/extractor/src/main.rs +++ b/rust/extractor/src/main.rs @@ -14,7 +14,7 @@ use ra_ap_paths::{AbsPathBuf, Utf8PathBuf}; use ra_ap_project_model::{CargoConfig, ProjectManifest}; use ra_ap_vfs::Vfs; use rust_analyzer::{ParseResult, RustAnalyzer}; -use std::collections::{BTreeMap, HashMap, HashSet}; +use std::collections::{BTreeMap, HashSet}; use std::fs::File; use std::io::{self, BufRead, BufReader}; use std::path::{Path, PathBuf}; @@ -41,19 +41,32 @@ struct Extractor<'a> { archiver: &'a Archiver, traps: &'a trap::TrapFileProvider, steps: Vec, - extracted_with_semantics: HashMap, + source_files: HashSet, + generated_source_root: PathBuf, + extracted_source_files: HashSet, } impl<'a> Extractor<'a> { - pub fn new(archiver: &'a Archiver, traps: &'a trap::TrapFileProvider) -> Self { + pub fn new( + archiver: &'a Archiver, + traps: &'a trap::TrapFileProvider, + source_files: HashSet, + generated_source_root: PathBuf, + ) -> Self { Self { archiver, traps, steps: Vec::new(), - extracted_with_semantics: HashMap::new(), + source_files, + generated_source_root, + extracted_source_files: HashSet::new(), } } + fn is_selected_source(&self, file: &Path) -> bool { + self.source_files.contains(file) || file.starts_with(&self.generated_source_root) + } + fn extract( &mut self, rust_analyzer: &RustAnalyzer, @@ -104,7 +117,9 @@ impl<'a> Extractor<'a> { translator.emit_source_file(&ast); translator.emit_truncated_diagnostics_message(); for step in &translator.included_file_steps { - if let Some(file) = &step.file { + if let Some(file) = &step.file + && (source_kind == SourceKind::Library || self.is_selected_source(file)) + { self.archiver.archive(file); } } @@ -127,19 +142,28 @@ impl<'a> Extractor<'a> { semantics: &'db Semantics<'db, RootDatabase>, vfs: &'db Vfs, source_kind: SourceKind, + selected_source: bool, ) { let mut pending = vec![file.to_path_buf()]; + let mut visited = HashSet::new(); while let Some(file) = pending.pop() { - if self - .extracted_with_semantics - .get(&file) - .is_some_and(|kind| *kind == SourceKind::Source || *kind == source_kind) + if !visited.insert(file.clone()) + || selected_source && !self.is_selected_source(&file) + || !selected_source && self.extracted_source_files.contains(&file) { continue; } - self.extracted_with_semantics - .insert(file.clone(), source_kind); - pending.extend(self.extract(&RustAnalyzer::new(vfs, semantics), &file, source_kind)); + if selected_source { + self.extracted_source_files.insert(file.clone()); + } + let modules = self.extract(&RustAnalyzer::new(vfs, semantics), &file, source_kind); + if selected_source { + pending.extend( + modules + .into_iter() + .filter(|module| self.is_selected_source(module)), + ); + } } } @@ -159,9 +183,6 @@ impl<'a> Extractor<'a> { load_config: &LoadCargoConfig, ) -> Option<(RootDatabase, Vfs)> { let before = Instant::now(); - // Dependency workspaces must not overwrite generated modules extracted as source. - self.extracted_with_semantics - .retain(|_, kind| *kind == SourceKind::Source); let ret = RustAnalyzer::load_workspace(project, config, load_config); self.steps .push(ExtractionStep::load_manifest(before, project)); @@ -303,11 +324,6 @@ fn main() -> anyhow::Result<()> { info!("{config:#?}\n"); info!("number of inputs: {}", inputs.len()); - let traps = trap::TrapFileProvider::new(&config).context("failed to set up trap files")?; - let archiver = archive::Archiver { - root: config.source_archive_dir.clone(), - }; - let mut extractor = Extractor::new(&archiver, &traps); let files = inputs .iter() .map(|file| { @@ -318,6 +334,20 @@ fn main() -> anyhow::Result<()> { dunce::canonicalize(&file).unwrap_or(file) }) .collect::>(); + let source_files = HashSet::from_iter(files.iter().cloned()); + let generated_source_root = std::path::absolute(config.cargo_target_dir())?; + let generated_source_root = + dunce::canonicalize(&generated_source_root).unwrap_or(generated_source_root); + let traps = trap::TrapFileProvider::new(&config).context("failed to set up trap files")?; + let archiver = archive::Archiver { + root: config.source_archive_dir.clone(), + }; + let mut extractor = Extractor::new( + &archiver, + &traps, + source_files.clone(), + generated_source_root, + ); let manifests = extractor.find_manifests(&files)?; let workspaces = extractor.group_files(&manifests, &files); let cwd = cwd()?; @@ -333,7 +363,6 @@ fn main() -> anyhow::Result<()> { } else { SourceKind::Source }; - let source_files = HashSet::from_iter(files.iter().cloned()); let mut dependencies = DependencyFiles::default(); @@ -353,14 +382,16 @@ fn main() -> anyhow::Result<()> { let semantics = Semantics::new(&db); for file in files { match extractor.load_source(file, &semantics, &vfs) { - Ok(()) => extractor.extract_with_semantics(file, &semantics, &vfs, source_mode), + Ok(()) => { + extractor.extract_with_semantics(file, &semantics, &vfs, source_mode, true) + } Err(e) => extractor.extract_without_semantics(file, source_mode, e), }; } // Fast path when there is no ambiguity in dependency extraction. if dependency_extraction_ambiguity { for (_, file) in dependency_files(&db, &vfs, &source_files) { - extractor.extract_with_semantics(file, &semantics, &vfs, library_mode); + extractor.extract_with_semantics(file, &semantics, &vfs, library_mode, false); } } else { dependencies.add_manifest(manifest, &semantics, &vfs, &source_files); @@ -401,7 +432,13 @@ fn main() -> anyhow::Result<()> { .and_then(|id| semantics.file_to_module_def(id)) .is_some() { - extractor.extract_with_semantics(&file, &semantics, &vfs, library_mode); + extractor.extract_with_semantics( + &file, + &semantics, + &vfs, + library_mode, + false, + ); dependencies.extracted(&file); } } diff --git a/rust/ql/integration-tests/file-exclusions/src/lib.rs b/rust/ql/integration-tests/file-exclusions/src/lib.rs index 94334e348c65..fe9c258c3282 100644 --- a/rust/ql/integration-tests/file-exclusions/src/lib.rs +++ b/rust/ql/integration-tests/file-exclusions/src/lib.rs @@ -1,5 +1,7 @@ mod maybe_ignore; +include!("maybe_ignore/a_source.rs"); + pub fn add(left: u64, right: u64) -> u64 { left + right } diff --git a/rust/ql/lib/codeql/files/FileSystem.qll b/rust/ql/lib/codeql/files/FileSystem.qll index 863bd3e06063..41486c595f40 100644 --- a/rust/ql/lib/codeql/files/FileSystem.qll +++ b/rust/ql/lib/codeql/files/FileSystem.qll @@ -6,6 +6,8 @@ private import codeql.rust.elements.SourceFile private import codeql.rust.elements.AstNode private import codeql.rust.elements.Comment private import codeql.rust.elements.DocComment +private import codeql.rust.elements.MacroCall +private import codeql.rust.elements.MacroItems private import codeql.rust.Diagnostics private import codeql.rust.elements.internal.ExtractorStep @@ -90,9 +92,7 @@ class ExtractedFile extends File { private Diagnostic getNoSemanticsDiagnostic() { result.getTag() = "semantics" and - result.getLocation().getFile() = this and - // A standalone parse may lack semantics even though an include expansion has them. - not exists(ExtractorStep s | s.getAction() = "LoadSource" and s.getFile() = this) + result.getLocation().getFile() = this } /** @@ -101,14 +101,35 @@ class ExtractedFile extends File { * * expand any macros * * skip any blocks that are conditionally compiled out */ - predicate hasSemantics() { not exists(this.getNoSemanticsDiagnostic()) } + predicate hasSemantics() { + not exists(this.getNoSemanticsDiagnostic()) + or + exists(ExtractorStep s | s.getAction() = "LoadSource" and s.getFile() = this) + } + + /** Holds if this file's standalone parse has semantic information. */ + predicate hasStandaloneSemantics() { not exists(this.getNoSemanticsDiagnostic()) } + + /** Holds if `node` belongs to this file's active include expansion. */ + predicate isInActiveIncludeExpansion(AstNode node) { + node.getFile() = this and + exists(MacroItems included, MacroCall call | + included = node.getParentNode*() and + included = call.getMacroCallExpansion() and + included.getFile() = node.getFile() and + included.getFile() != call.getFile() + ) + } /** * Holds if we know this file was skipped by conditional compilation. * This is not the same as `not this.hasSemantics()`, as a file * might not have semantics because of some error. */ - predicate isSkippedByCompilation() { this.getNoSemanticsDiagnostic().getSeverityText() = "Info" } + predicate isSkippedByCompilation() { + this.getNoSemanticsDiagnostic().getSeverityText() = "Info" and + not exists(ExtractorStep s | s.getAction() = "LoadSource" and s.getFile() = this) + } } /** diff --git a/rust/ql/lib/codeql/rust/elements/internal/AstNodeImpl.qll b/rust/ql/lib/codeql/rust/elements/internal/AstNodeImpl.qll index 0c9d736cc57c..d158d8b93a92 100644 --- a/rust/ql/lib/codeql/rust/elements/internal/AstNodeImpl.qll +++ b/rust/ql/lib/codeql/rust/elements/internal/AstNodeImpl.qll @@ -70,7 +70,12 @@ module Impl { result = p.getEnclosingCfgScope() ) or - this.isConstOrStatic(result.(SourceFile).getFile()) + exists(SourceFile source | + this.isConstOrStatic(_) and + source = this.getParentNode*() + | + result = source + ) } /** Holds if this node is inside a CFG scope. */ diff --git a/rust/ql/lib/codeql/rust/internal/PathResolution.qll b/rust/ql/lib/codeql/rust/internal/PathResolution.qll index 7403e2407ba9..14940d5a5091 100644 --- a/rust/ql/lib/codeql/rust/internal/PathResolution.qll +++ b/rust/ql/lib/codeql/rust/internal/PathResolution.qll @@ -133,13 +133,34 @@ private ItemNode getAChildSuccessor0(ItemNode item, string name, SuccessorKind k pragma[nomagic] private NamedItemNode getANamedNonModuleChildSuccessor( - ItemNode item, string name, Namespace ns, int startline, int startcolumn, int endline, - int endcolumn + ItemNode item, string name, Namespace ns, int orderLine, int orderColumn, int sourceLine, + int sourceColumn, int endline, int endcolumn ) { - result.getLocation().hasLocationInfo(_, startline, startcolumn, endline, endcolumn) and + result.getLocation().hasLocationInfo(_, sourceLine, sourceColumn, endline, endcolumn) and result = getAChildSuccessor0(item, name, _) and ns = result.getNamespace() and - not result instanceof ModuleItemNode + not result instanceof ModuleItemNode and + getEffectivePositionNode(result).getLocation().hasLocationInfo(_, orderLine, orderColumn, _, _) +} + +private AstNode getEffectivePositionNode(AstNode node) { + result = node and + not exists(MacroItems included | + included = node.getParentNode*() and + exists(MacroCall call | + included = call.getMacroCallExpansion() and + included.getFile() = node.getFile() and + included.getFile() != call.getFile() + ) + ) + or + exists(MacroItems included, MacroCall call | + included = node.getParentNode*() and + included = call.getMacroCallExpansion() and + included.getFile() = node.getFile() and + included.getFile() != call.getFile() and + result = getEffectivePositionNode(call) + ) } pragma[nomagic] @@ -155,12 +176,13 @@ private ItemNode getAChildSuccessor(ItemNode item, string name, SuccessorKind ki or exists(Namespace ns | result = - max(NamedItemNode i, int startline, int startcolumn, int endline, int endcolumn | + max(NamedItemNode i, int orderLine, int orderColumn, int sourceLine, int sourceColumn, + int endline, int endcolumn | i = - getANamedNonModuleChildSuccessor(item, name, ns, startline, startcolumn, endline, - endcolumn) + getANamedNonModuleChildSuccessor(item, name, ns, orderLine, orderColumn, sourceLine, + sourceColumn, endline, endcolumn) | - i order by startline, startcolumn, endline, endcolumn + i order by orderLine, orderColumn, sourceLine, sourceColumn, endline, endcolumn ) ) ) diff --git a/rust/ql/src/queries/telemetry/DatabaseQuality.qll b/rust/ql/src/queries/telemetry/DatabaseQuality.qll index 37163ffd7dda..f3d14847def1 100644 --- a/rust/ql/src/queries/telemetry/DatabaseQuality.qll +++ b/rust/ql/src/queries/telemetry/DatabaseQuality.qll @@ -20,6 +20,12 @@ private class RelevantFile extends File { } } +private predicate hasActiveSemantics(AstNode node) { + node.getFile().(ExtractedFile).hasStandaloneSemantics() + or + node.getFile().(ExtractedFile).isInActiveIncludeExpansion(node) +} + module CallTargetStats implements StatsSig { /** * A call-like expression that is relevant for call target statistics. @@ -30,6 +36,7 @@ module CallTargetStats implements StatsSig { private class RelevantInvocationExpr extends InvocationExpr { RelevantInvocationExpr() { this.getFile() instanceof RelevantFile and + hasActiveSemantics(this) and not this instanceof CallExprImpl::DynamicCallExpr and not this = any(Operation o | not o.isOverloaded(_, _, _)) and not this = any(DerefExpr de | TypeInference::inferType(de.getExpr()) instanceof PtrType) @@ -49,11 +56,18 @@ module CallTargetStats implements StatsSig { module MacroCallTargetStats implements StatsSig { int getNumberOfOk() { - result = count(MacroCall c | c.getFile() instanceof RelevantFile and c.hasMacroCallExpansion()) + result = + count(MacroCall c | + c.getFile() instanceof RelevantFile and + hasActiveSemantics(c) and + c.hasMacroCallExpansion() + ) } additional predicate isNotOkCall(MacroCall c) { - c.getFile() instanceof RelevantFile and not c.hasMacroCallExpansion() + c.getFile() instanceof RelevantFile and + hasActiveSemantics(c) and + not c.hasMacroCallExpansion() } int getNumberOfNotOk() { result = count(MacroCall c | isNotOkCall(c)) } @@ -70,6 +84,7 @@ module ExprTypeStats implements StatsSig { result = count(Expr e | e.getFile() instanceof RelevantFile and + hasActiveSemantics(e) and e.fromSource() and hasGoodType(e) ) @@ -79,6 +94,7 @@ module ExprTypeStats implements StatsSig { result = count(Expr e | e.getFile() instanceof RelevantFile and + hasActiveSemantics(e) and e.fromSource() and not hasGoodType(e) ) diff --git a/rust/ql/test/extractor-tests/include-out-dir/build.rs b/rust/ql/test/extractor-tests/include-out-dir/build.rs index dc9e0df2891d..ab243f19a531 100644 --- a/rust/ql/test/extractor-tests/include-out-dir/build.rs +++ b/rust/ql/test/extractor-tests/include-out-dir/build.rs @@ -15,6 +15,9 @@ pub fn generated_alias() -> i32 { generated_self::root_function() } +pub const INCLUDED_CONST: i32 = crate::root_const(); +pub static INCLUDED_STATIC: i32 = crate::root_const(); + pub mod inline { #[macro_export] macro_rules! generated_export { diff --git a/rust/ql/test/extractor-tests/include-out-dir/main.rs b/rust/ql/test/extractor-tests/include-out-dir/main.rs index 1079a64fb2f9..6fa20169140a 100644 --- a/rust/ql/test/extractor-tests/include-out-dir/main.rs +++ b/rust/ql/test/extractor-tests/include-out-dir/main.rs @@ -4,6 +4,10 @@ fn root_function() -> i32 { 42 } +const fn root_const() -> i32 { + 42 +} + fn main() { generated_function(); child::child_function(); diff --git a/rust/ql/test/extractor-tests/include-out-dir/test.expected b/rust/ql/test/extractor-tests/include-out-dir/test.expected index 06ae6412f1e6..358705b6bc95 100644 --- a/rust/ql/test/extractor-tests/include-out-dir/test.expected +++ b/rust/ql/test/extractor-tests/include-out-dir/test.expected @@ -11,3 +11,6 @@ macroReference | root_function | exportedMacro | generated_export | 1 | +includedInitializerScope +| INCLUDED_CONST | main.rs | +| INCLUDED_STATIC | main.rs | diff --git a/rust/ql/test/extractor-tests/include-out-dir/test.ql b/rust/ql/test/extractor-tests/include-out-dir/test.ql index 1a445e3626e2..f8ca240c76ec 100644 --- a/rust/ql/test/extractor-tests/include-out-dir/test.ql +++ b/rust/ql/test/extractor-tests/include-out-dir/test.ql @@ -1,6 +1,7 @@ import rust import codeql.rust.internal.PathResolution import codeql.rust.controlflow.ControlFlowGraph +import codeql.rust.elements.SourceFile query predicate generatedFunction(boolean fromSource, int cfgNodes, string canonicalPath) { exists(Function f | @@ -35,3 +36,18 @@ query predicate exportedMacro(string name, int definitions) { definitions = count(MacroRules m | m = c.getASuccessor(name)) ) } + +query predicate includedInitializerScope(string name, string scopeFile) { + exists(CallExpr call, AstNode initializer | + initializer = call.getParentNode*() and + (initializer instanceof Const or initializer instanceof Static) and + ( + name = initializer.(Const).getName().getText() + or + name = initializer.(Static).getName().getText() + ) and + name = ["INCLUDED_CONST", "INCLUDED_STATIC"] and + call.getResolvedTarget().(Function).getName().getText() = "root_const" and + scopeFile = call.getEnclosingCfgScope().(SourceFile).getFile().getRelativePath() + ) +} diff --git a/rust/ql/test/extractor-tests/macro-expansion/PrintAst.expected b/rust/ql/test/extractor-tests/macro-expansion/PrintAst.expected index 50e3657b029f..ba0798529bf9 100644 --- a/rust/ql/test/extractor-tests/macro-expansion/PrintAst.expected +++ b/rust/ql/test/extractor-tests/macro-expansion/PrintAst.expected @@ -191,6 +191,14 @@ included/included.rs: # 31| getIdentifier(): [NameRef] definition_location # 31| getTokenTree(): [TokenTree] TokenTree # 30| getName(): [Name] definition_origin +# 34| getItem(7): [MacroRules] MacroRules +# 34| getName(): [Name] shadowed +# 34| getTokenTree(): [TokenTree] TokenTree +# 38| getItem(8): [MacroCall] shadowed!... +# 38| getPath(): [Path] shadowed +# 38| getSegment(): [PathSegment] shadowed +# 38| getIdentifier(): [NameRef] shadowed +# 38| getTokenTree(): [TokenTree] TokenTree # 2| [Comment] //... lib.rs: # 1| [SourceFile] SourceFile @@ -656,11 +664,14 @@ macro_expansion.rs: # 55| getSegment(): [PathSegment] must_use # 55| getIdentifier(): [NameRef] must_use # 52| getName(): [Name] test -# 61| getItem(11): [MacroCall] include!... -# 61| getPath(): [Path] include -# 61| getSegment(): [PathSegment] include -# 61| getIdentifier(): [NameRef] include +# 61| getItem(11): [MacroRules] MacroRules +# 61| getName(): [Name] shadowed # 61| getTokenTree(): [TokenTree] TokenTree +# 65| getItem(12): [MacroCall] include!... +# 65| getPath(): [Path] include +# 65| getSegment(): [PathSegment] include +# 65| getIdentifier(): [NameRef] include +# 65| getTokenTree(): [TokenTree] TokenTree included/included.rs: # 1| getMacroCallExpansion(): [MacroItems] MacroItems # 1| getItem(0): [Function] fn included @@ -835,395 +846,406 @@ included/included.rs: # 31| getSegment(): [PathSegment] dangerous_call # 31| getIdentifier(): [NameRef] dangerous_call # 30| getName(): [Name] definition_origin +# 34| getItem(7): [MacroRules] MacroRules +# 34| getName(): [Name] shadowed +# 34| getTokenTree(): [TokenTree] TokenTree +# 38| getItem(8): [MacroCall] shadowed!... +# 38| getPath(): [Path] shadowed +# 38| getSegment(): [PathSegment] shadowed +# 38| getIdentifier(): [NameRef] shadowed +# 38| getTokenTree(): [TokenTree] TokenTree +# 38| getMacroCallExpansion(): [MacroItems] MacroItems +# 38| getItem(0): [Struct] struct IncludedShadowed +# 38| getName(): [Name] IncludedShadowed macro_expansion.rs: -# 63| getItem(12): [Function] fn documented -# 64| getParamList(): [ParamList] ParamList -# 63| getAttr(0): [Attr] Attr -# 63| getMeta(): [KeyValueMeta] KeyValueMeta -# 63| getExpr(): [MacroExpr] MacroExpr -# 63| getMacroCall(): [MacroCall] include_str!... -# 63| getPath(): [Path] include_str -# 63| getSegment(): [PathSegment] include_str -# 63| getIdentifier(): [NameRef] include_str -# 63| getTokenTree(): [TokenTree] TokenTree -# 63| getPath(): [Path] doc -# 63| getSegment(): [PathSegment] doc -# 63| getIdentifier(): [NameRef] doc -# 64| getFunctionBody(): [BlockExpr] { ... } -# 64| getStmtList(): [StmtList] StmtList -# 64| getName(): [Name] documented -# 66| getItem(13): [MacroRules] MacroRules -# 66| getName(): [Name] my_int -# 66| getTokenTree(): [TokenTree] TokenTree -# 70| getItem(14): [Function] fn answer -# 70| getParamList(): [ParamList] ParamList -# 70| getFunctionBody(): [BlockExpr] { ... } -# 70| getStmtList(): [StmtList] StmtList -# 71| getStatement(0): [LetStmt] let ... = 42 -# 71| getInitializer(): [IntegerLiteralExpr] 42 -# 71| getPat(): [IdentPat] a -# 71| getName(): [Name] a -# 71| getTypeRepr(): [MacroTypeRepr] MacroTypeRepr -# 71| getMacroCall(): [MacroCall] my_int!... -# 71| getPath(): [Path] my_int -# 71| getSegment(): [PathSegment] my_int -# 71| getIdentifier(): [NameRef] my_int -# 71| getTokenTree(): [TokenTree] TokenTree -# 71| getMacroCallExpansion(): [PathTypeRepr] i32 -# 71| getPath(): [Path] i32 -# 71| getSegment(): [PathSegment] i32 -# 71| getIdentifier(): [NameRef] i32 -# 72| getTailExpr(): [CastExpr] a as ... -# 72| getExpr(): [VariableAccess] a -# 72| getPath(): [Path] a -# 72| getSegment(): [PathSegment] a -# 72| getIdentifier(): [NameRef] a -# 72| getTypeRepr(): [MacroTypeRepr] MacroTypeRepr -# 72| getMacroCall(): [MacroCall] my_int!... -# 72| getPath(): [Path] my_int -# 72| getSegment(): [PathSegment] my_int -# 72| getIdentifier(): [NameRef] my_int -# 72| getTokenTree(): [TokenTree] TokenTree -# 72| getMacroCallExpansion(): [PathTypeRepr] i32 -# 72| getPath(): [Path] i32 -# 72| getSegment(): [PathSegment] i32 -# 72| getIdentifier(): [NameRef] i32 -# 70| getName(): [Name] answer -# 70| getRetType(): [RetTypeRepr] RetTypeRepr -# 70| getTypeRepr(): [MacroTypeRepr] MacroTypeRepr -# 70| getMacroCall(): [MacroCall] my_int!... -# 70| getPath(): [Path] my_int -# 70| getSegment(): [PathSegment] my_int -# 70| getIdentifier(): [NameRef] my_int -# 70| getTokenTree(): [TokenTree] TokenTree -# 70| getMacroCallExpansion(): [PathTypeRepr] i32 -# 70| getPath(): [Path] i32 -# 70| getSegment(): [PathSegment] i32 -# 70| getIdentifier(): [NameRef] i32 -# 76| getItem(15): [TypeAlias] type MyInt -# 76| getName(): [Name] MyInt -# 76| getTypeRepr(): [MacroTypeRepr] MacroTypeRepr -# 76| getMacroCall(): [MacroCall] my_int!... -# 76| getPath(): [Path] my_int -# 76| getSegment(): [PathSegment] my_int -# 76| getIdentifier(): [NameRef] my_int -# 76| getTokenTree(): [TokenTree] TokenTree -# 76| getMacroCallExpansion(): [PathTypeRepr] i32 -# 76| getPath(): [Path] i32 -# 76| getSegment(): [PathSegment] i32 -# 76| getIdentifier(): [NameRef] i32 -# 78| getItem(16): [Struct] struct MyStruct -# 78| getName(): [Name] MyStruct -# 78| getFieldList(): [StructFieldList] StructFieldList -# 79| getField(0): [StructField] field: ... -# 79| getName(): [Name] field -# 79| getTypeRepr(): [MacroTypeRepr] MacroTypeRepr -# 79| getMacroCall(): [MacroCall] my_int!... -# 79| getPath(): [Path] my_int -# 79| getSegment(): [PathSegment] my_int -# 79| getIdentifier(): [NameRef] my_int -# 79| getTokenTree(): [TokenTree] TokenTree -# 79| getMacroCallExpansion(): [PathTypeRepr] i32 -# 79| getPath(): [Path] i32 -# 79| getSegment(): [PathSegment] i32 -# 79| getIdentifier(): [NameRef] i32 -# 83| getItem(17): [Struct] struct MyDerive -# 83| getDeriveMacroExpansion(0): [MacroItems] MacroItems -# 83| getItem(0): [Impl] impl ...::Debug for MyDerive::<...> { ... } -# 83| getAssocItemList(): [AssocItemList] AssocItemList -# 83| getAssocItem(0): [Function] fn fmt -# 83| getParamList(): [ParamList] ParamList -# 83| getParam(0): [Param] ...: ... -# 83| getTypeRepr(): [RefTypeRepr] RefTypeRepr -# 83| getTypeRepr(): [PathTypeRepr] ...::Formatter -# 83| getPath(): [Path] ...::Formatter -# 83| getQualifier(): [Path] ...::fmt -# 83| getQualifier(): [Path] $crate -# 83| getSegment(): [PathSegment] $crate -# 83| getIdentifier(): [NameRef] $crate -# 83| getSegment(): [PathSegment] fmt -# 83| getIdentifier(): [NameRef] fmt -# 83| getSegment(): [PathSegment] Formatter -# 83| getIdentifier(): [NameRef] Formatter -# 83| getPat(): [IdentPat] f -# 83| getName(): [Name] f -# 83| getSelfParam(): [SelfParam] SelfParam -# 83| getName(): [Name] self -# 83| getFunctionBody(): [BlockExpr] { ... } -# 83| getStmtList(): [StmtList] StmtList -# 83| getTailExpr(): [MatchExpr] match self { ... } -# 83| getScrutinee(): [VariableAccess] self -# 83| getPath(): [Path] self -# 83| getSegment(): [PathSegment] self -# 83| getIdentifier(): [NameRef] self -# 83| getMatchArmList(): [MatchArmList] MatchArmList -# 83| getArm(0): [MatchArm] ... => ... -# 83| getExpr(): [MethodCallExpr] ... .finish() -# 83| getArgList(): [ArgList] ArgList -# 83| getIdentifier(): [NameRef] finish -# 83| getReceiver(): [MethodCallExpr] ... .field(...) -# 83| getArgList(): [ArgList] ArgList -# 83| getArg(0): [StringLiteralExpr] "field" -# 83| getArg(1): [RefExpr] &field -# 85| getExpr(): [VariableAccess] field -# 85| getPath(): [Path] field -# 85| getSegment(): [PathSegment] field -# 85| getIdentifier(): [NameRef] field -# 83| getIdentifier(): [NameRef] field -# 83| getReceiver(): [MethodCallExpr] f.debug_struct(...) -# 83| getArgList(): [ArgList] ArgList -# 83| getArg(0): [StringLiteralExpr] "MyDerive" -# 83| getIdentifier(): [NameRef] debug_struct -# 83| getReceiver(): [VariableAccess] f -# 83| getPath(): [Path] f -# 83| getSegment(): [PathSegment] f -# 83| getIdentifier(): [NameRef] f -# 83| getPat(): [StructPat] MyDerive {...} -# 84| getPath(): [Path] MyDerive -# 84| getSegment(): [PathSegment] MyDerive -# 84| getIdentifier(): [NameRef] MyDerive -# 83| getStructPatFieldList(): [StructPatFieldList] StructPatFieldList -# 83| getField(0): [StructPatField] field: ... -# 85| getIdentifier(): [NameRef] field -# 85| getPat(): [IdentPat] field -# 85| getName(): [Name] field -# 83| getName(): [Name] fmt -# 83| getRetType(): [RetTypeRepr] RetTypeRepr -# 83| getTypeRepr(): [PathTypeRepr] ...::Result -# 83| getPath(): [Path] ...::Result -# 83| getQualifier(): [Path] ...::fmt -# 83| getQualifier(): [Path] $crate -# 83| getSegment(): [PathSegment] $crate -# 83| getIdentifier(): [NameRef] $crate -# 83| getSegment(): [PathSegment] fmt -# 83| getIdentifier(): [NameRef] fmt -# 83| getSegment(): [PathSegment] Result -# 83| getIdentifier(): [NameRef] Result -# 83| getGenericParamList(): [GenericParamList] <...> -# 83| getSelfTy(): [PathTypeRepr] MyDerive::<...> -# 83| getPath(): [Path] MyDerive::<...> -# 83| getSegment(): [PathSegment] MyDerive::<...> -# 83| getGenericArgList(): [GenericArgList] <...> -# 84| getIdentifier(): [NameRef] MyDerive -# 83| getTraitTy(): [PathTypeRepr] ...::Debug -# 83| getPath(): [Path] ...::Debug -# 83| getQualifier(): [Path] ...::fmt -# 83| getQualifier(): [Path] $crate -# 83| getSegment(): [PathSegment] $crate -# 83| getIdentifier(): [NameRef] $crate -# 83| getSegment(): [PathSegment] fmt -# 83| getIdentifier(): [NameRef] fmt -# 83| getSegment(): [PathSegment] Debug -# 83| getIdentifier(): [NameRef] Debug -# 83| getWhereClause(): [WhereClause] WhereClause -# 83| getAttr(0): [Attr] Attr -# 83| getMeta(): [TokenTreeMeta] TokenTreeMeta -# 83| getPath(): [Path] derive -# 83| getSegment(): [PathSegment] derive -# 83| getIdentifier(): [NameRef] derive -# 83| getTokenTree(): [TokenTree] TokenTree -# 84| getName(): [Name] MyDerive -# 84| getFieldList(): [StructFieldList] StructFieldList -# 85| getField(0): [StructField] field: usize -# 85| getName(): [Name] field -# 85| getTypeRepr(): [PathTypeRepr] usize -# 85| getPath(): [Path] usize -# 85| getSegment(): [PathSegment] usize -# 85| getIdentifier(): [NameRef] usize -# 88| getItem(18): [Enum] enum MyDeriveEnum -# 88| getDeriveMacroExpansion(0): [MacroItems] MacroItems -# 88| getItem(0): [Impl] impl ...::PartialEq for MyDeriveEnum::<...> { ... } -# 88| getAssocItemList(): [AssocItemList] AssocItemList -# 88| getAssocItem(0): [Function] fn eq -# 88| getParamList(): [ParamList] ParamList -# 88| getParam(0): [Param] ...: ... -# 88| getTypeRepr(): [RefTypeRepr] RefTypeRepr -# 88| getTypeRepr(): [PathTypeRepr] Self -# 88| getPath(): [Path] Self -# 88| getSegment(): [PathSegment] Self -# 88| getIdentifier(): [NameRef] Self -# 88| getPat(): [IdentPat] other -# 88| getName(): [Name] other -# 88| getSelfParam(): [SelfParam] SelfParam -# 88| getName(): [Name] self -# 88| getFunctionBody(): [BlockExpr] { ... } -# 88| getStmtList(): [StmtList] StmtList -# 88| getTailExpr(): [MatchExpr] match ... { ... } -# 88| getScrutinee(): [TupleExpr] TupleExpr -# 88| getField(0): [VariableAccess] self -# 88| getPath(): [Path] self -# 88| getSegment(): [PathSegment] self -# 88| getIdentifier(): [NameRef] self -# 88| getField(1): [VariableAccess] other -# 88| getPath(): [Path] other -# 88| getSegment(): [PathSegment] other -# 88| getIdentifier(): [NameRef] other -# 88| getMatchArmList(): [MatchArmList] MatchArmList -# 88| getArm(0): [MatchArm] ... => true -# 88| getExpr(): [BooleanLiteralExpr] true -# 88| getPat(): [TuplePat] TuplePat -# 88| getField(0): [PathPat] ...::Variant1 -# 88| getPath(): [Path] ...::Variant1 -# 89| getQualifier(): [Path] MyDeriveEnum -# 89| getSegment(): [PathSegment] MyDeriveEnum -# 89| getIdentifier(): [NameRef] MyDeriveEnum -# 90| getSegment(): [PathSegment] Variant1 -# 90| getIdentifier(): [NameRef] Variant1 -# 88| getField(1): [PathPat] ...::Variant1 -# 88| getPath(): [Path] ...::Variant1 -# 89| getQualifier(): [Path] MyDeriveEnum -# 89| getSegment(): [PathSegment] MyDeriveEnum -# 89| getIdentifier(): [NameRef] MyDeriveEnum -# 90| getSegment(): [PathSegment] Variant1 -# 90| getIdentifier(): [NameRef] Variant1 -# 88| getArm(1): [MatchArm] ... => true -# 88| getExpr(): [BooleanLiteralExpr] true -# 88| getPat(): [TuplePat] TuplePat -# 88| getField(0): [PathPat] ...::Variant2 -# 88| getPath(): [Path] ...::Variant2 -# 89| getQualifier(): [Path] MyDeriveEnum -# 89| getSegment(): [PathSegment] MyDeriveEnum -# 89| getIdentifier(): [NameRef] MyDeriveEnum -# 91| getSegment(): [PathSegment] Variant2 -# 91| getIdentifier(): [NameRef] Variant2 -# 88| getField(1): [PathPat] ...::Variant2 -# 88| getPath(): [Path] ...::Variant2 -# 89| getQualifier(): [Path] MyDeriveEnum -# 89| getSegment(): [PathSegment] MyDeriveEnum -# 89| getIdentifier(): [NameRef] MyDeriveEnum -# 91| getSegment(): [PathSegment] Variant2 -# 91| getIdentifier(): [NameRef] Variant2 -# 88| getArm(2): [MatchArm] ... => false -# 88| getExpr(): [BooleanLiteralExpr] false -# 88| getPat(): [IdentPat] _unused -# 88| getName(): [Name] _unused -# 88| getName(): [Name] eq -# 88| getRetType(): [RetTypeRepr] RetTypeRepr -# 88| getTypeRepr(): [PathTypeRepr] bool -# 88| getPath(): [Path] bool -# 88| getSegment(): [PathSegment] bool -# 88| getIdentifier(): [NameRef] bool -# 88| getGenericParamList(): [GenericParamList] <...> -# 88| getSelfTy(): [PathTypeRepr] MyDeriveEnum::<...> -# 88| getPath(): [Path] MyDeriveEnum::<...> -# 88| getSegment(): [PathSegment] MyDeriveEnum::<...> -# 88| getGenericArgList(): [GenericArgList] <...> -# 89| getIdentifier(): [NameRef] MyDeriveEnum -# 88| getTraitTy(): [PathTypeRepr] ...::PartialEq -# 88| getPath(): [Path] ...::PartialEq -# 88| getQualifier(): [Path] ...::cmp -# 88| getQualifier(): [Path] $crate -# 88| getSegment(): [PathSegment] $crate -# 88| getIdentifier(): [NameRef] $crate -# 88| getSegment(): [PathSegment] cmp -# 88| getIdentifier(): [NameRef] cmp -# 88| getSegment(): [PathSegment] PartialEq -# 88| getIdentifier(): [NameRef] PartialEq -# 88| getWhereClause(): [WhereClause] WhereClause -# 88| getDeriveMacroExpansion(1): [MacroItems] MacroItems -# 88| getItem(0): [Impl] impl ...::Eq for MyDeriveEnum::<...> { ... } -# 88| getAssocItemList(): [AssocItemList] AssocItemList -# 88| getGenericParamList(): [GenericParamList] <...> -# 88| getSelfTy(): [PathTypeRepr] MyDeriveEnum::<...> -# 88| getPath(): [Path] MyDeriveEnum::<...> -# 88| getSegment(): [PathSegment] MyDeriveEnum::<...> -# 88| getGenericArgList(): [GenericArgList] <...> -# 89| getIdentifier(): [NameRef] MyDeriveEnum -# 88| getTraitTy(): [PathTypeRepr] ...::Eq -# 88| getPath(): [Path] ...::Eq -# 88| getQualifier(): [Path] ...::cmp -# 88| getQualifier(): [Path] $crate -# 88| getSegment(): [PathSegment] $crate -# 88| getIdentifier(): [NameRef] $crate -# 88| getSegment(): [PathSegment] cmp -# 88| getIdentifier(): [NameRef] cmp -# 88| getSegment(): [PathSegment] Eq -# 88| getIdentifier(): [NameRef] Eq -# 88| getWhereClause(): [WhereClause] WhereClause -# 88| getAttr(0): [Attr] Attr -# 88| getMeta(): [TokenTreeMeta] TokenTreeMeta -# 88| getPath(): [Path] derive -# 88| getSegment(): [PathSegment] derive -# 88| getIdentifier(): [NameRef] derive -# 88| getTokenTree(): [TokenTree] TokenTree -# 89| getName(): [Name] MyDeriveEnum -# 89| getVariantList(): [VariantList] VariantList -# 90| getVariant(0): [Variant] Variant1 -# 90| getName(): [Name] Variant1 -# 91| getVariant(1): [Variant] Variant2 -# 91| getName(): [Name] Variant2 -# 94| getItem(19): [Trait] trait MyTrait -# 94| getAssocItemList(): [AssocItemList] AssocItemList -# 95| getAssocItem(0): [Function] fn my_method -# 95| getParamList(): [ParamList] ParamList -# 95| getName(): [Name] my_method -# 95| getRetType(): [RetTypeRepr] RetTypeRepr -# 95| getTypeRepr(): [PathTypeRepr] u32 -# 95| getPath(): [Path] u32 -# 95| getSegment(): [PathSegment] u32 -# 95| getIdentifier(): [NameRef] u32 -# 94| getName(): [Name] MyTrait -# 98| getItem(20): [Union] union MyDeriveUnion -# 99| getDeriveMacroExpansion(0): [MacroItems] MacroItems -# 99| getItem(0): [Const] const CONST_MyDeriveUnion -# 98| getBody(): [IntegerLiteralExpr] 42 -# 99| getName(): [Name] CONST_MyDeriveUnion -# 98| getTypeRepr(): [PathTypeRepr] u32 -# 98| getPath(): [Path] u32 -# 98| getSegment(): [PathSegment] u32 -# 98| getIdentifier(): [NameRef] u32 -# 99| getItem(1): [Impl] impl MyTrait for MyDeriveUnion { ... } -# 99| getAssocItemList(): [AssocItemList] AssocItemList -# 99| getAssocItem(0): [Function] fn my_method -# 98| getParamList(): [ParamList] ParamList -# 99| getFunctionBody(): [BlockExpr] { ... } -# 99| getStmtList(): [StmtList] StmtList -# 99| getTailExpr(): [ConstAccess] CONST_MyDeriveUnion -# 99| getPath(): [Path] CONST_MyDeriveUnion -# 99| getSegment(): [PathSegment] CONST_MyDeriveUnion -# 99| getIdentifier(): [NameRef] CONST_MyDeriveUnion -# 98| getName(): [Name] my_method -# 98| getRetType(): [RetTypeRepr] RetTypeRepr -# 98| getTypeRepr(): [PathTypeRepr] u32 -# 98| getPath(): [Path] u32 -# 98| getSegment(): [PathSegment] u32 -# 98| getIdentifier(): [NameRef] u32 -# 99| getSelfTy(): [PathTypeRepr] MyDeriveUnion -# 99| getPath(): [Path] MyDeriveUnion -# 99| getSegment(): [PathSegment] MyDeriveUnion -# 99| getIdentifier(): [NameRef] MyDeriveUnion -# 98| getTraitTy(): [PathTypeRepr] MyTrait -# 98| getPath(): [Path] MyTrait -# 98| getSegment(): [PathSegment] MyTrait -# 98| getIdentifier(): [NameRef] MyTrait -# 98| getAttr(0): [Attr] Attr -# 98| getMeta(): [TokenTreeMeta] TokenTreeMeta -# 98| getPath(): [Path] derive -# 98| getSegment(): [PathSegment] derive -# 98| getIdentifier(): [NameRef] derive -# 98| getTokenTree(): [TokenTree] TokenTree -# 99| getName(): [Name] MyDeriveUnion -# 99| getStructFieldList(): [StructFieldList] StructFieldList -# 100| getField(0): [StructField] field1: usize -# 100| getName(): [Name] field1 -# 100| getTypeRepr(): [PathTypeRepr] usize -# 100| getPath(): [Path] usize -# 100| getSegment(): [PathSegment] usize -# 100| getIdentifier(): [NameRef] usize -# 101| getField(1): [StructField] field2: f64 -# 101| getName(): [Name] field2 -# 101| getTypeRepr(): [PathTypeRepr] f64 -# 101| getPath(): [Path] f64 -# 101| getSegment(): [PathSegment] f64 -# 101| getIdentifier(): [NameRef] f64 +# 67| getItem(13): [Function] fn documented +# 68| getParamList(): [ParamList] ParamList +# 67| getAttr(0): [Attr] Attr +# 67| getMeta(): [KeyValueMeta] KeyValueMeta +# 67| getExpr(): [MacroExpr] MacroExpr +# 67| getMacroCall(): [MacroCall] include_str!... +# 67| getPath(): [Path] include_str +# 67| getSegment(): [PathSegment] include_str +# 67| getIdentifier(): [NameRef] include_str +# 67| getTokenTree(): [TokenTree] TokenTree +# 67| getPath(): [Path] doc +# 67| getSegment(): [PathSegment] doc +# 67| getIdentifier(): [NameRef] doc +# 68| getFunctionBody(): [BlockExpr] { ... } +# 68| getStmtList(): [StmtList] StmtList +# 68| getName(): [Name] documented +# 70| getItem(14): [MacroRules] MacroRules +# 70| getName(): [Name] my_int +# 70| getTokenTree(): [TokenTree] TokenTree +# 74| getItem(15): [Function] fn answer +# 74| getParamList(): [ParamList] ParamList +# 74| getFunctionBody(): [BlockExpr] { ... } +# 74| getStmtList(): [StmtList] StmtList +# 75| getStatement(0): [LetStmt] let ... = 42 +# 75| getInitializer(): [IntegerLiteralExpr] 42 +# 75| getPat(): [IdentPat] a +# 75| getName(): [Name] a +# 75| getTypeRepr(): [MacroTypeRepr] MacroTypeRepr +# 75| getMacroCall(): [MacroCall] my_int!... +# 75| getPath(): [Path] my_int +# 75| getSegment(): [PathSegment] my_int +# 75| getIdentifier(): [NameRef] my_int +# 75| getTokenTree(): [TokenTree] TokenTree +# 75| getMacroCallExpansion(): [PathTypeRepr] i32 +# 75| getPath(): [Path] i32 +# 75| getSegment(): [PathSegment] i32 +# 75| getIdentifier(): [NameRef] i32 +# 76| getTailExpr(): [CastExpr] a as ... +# 76| getExpr(): [VariableAccess] a +# 76| getPath(): [Path] a +# 76| getSegment(): [PathSegment] a +# 76| getIdentifier(): [NameRef] a +# 76| getTypeRepr(): [MacroTypeRepr] MacroTypeRepr +# 76| getMacroCall(): [MacroCall] my_int!... +# 76| getPath(): [Path] my_int +# 76| getSegment(): [PathSegment] my_int +# 76| getIdentifier(): [NameRef] my_int +# 76| getTokenTree(): [TokenTree] TokenTree +# 76| getMacroCallExpansion(): [PathTypeRepr] i32 +# 76| getPath(): [Path] i32 +# 76| getSegment(): [PathSegment] i32 +# 76| getIdentifier(): [NameRef] i32 +# 74| getName(): [Name] answer +# 74| getRetType(): [RetTypeRepr] RetTypeRepr +# 74| getTypeRepr(): [MacroTypeRepr] MacroTypeRepr +# 74| getMacroCall(): [MacroCall] my_int!... +# 74| getPath(): [Path] my_int +# 74| getSegment(): [PathSegment] my_int +# 74| getIdentifier(): [NameRef] my_int +# 74| getTokenTree(): [TokenTree] TokenTree +# 74| getMacroCallExpansion(): [PathTypeRepr] i32 +# 74| getPath(): [Path] i32 +# 74| getSegment(): [PathSegment] i32 +# 74| getIdentifier(): [NameRef] i32 +# 80| getItem(16): [TypeAlias] type MyInt +# 80| getName(): [Name] MyInt +# 80| getTypeRepr(): [MacroTypeRepr] MacroTypeRepr +# 80| getMacroCall(): [MacroCall] my_int!... +# 80| getPath(): [Path] my_int +# 80| getSegment(): [PathSegment] my_int +# 80| getIdentifier(): [NameRef] my_int +# 80| getTokenTree(): [TokenTree] TokenTree +# 80| getMacroCallExpansion(): [PathTypeRepr] i32 +# 80| getPath(): [Path] i32 +# 80| getSegment(): [PathSegment] i32 +# 80| getIdentifier(): [NameRef] i32 +# 82| getItem(17): [Struct] struct MyStruct +# 82| getName(): [Name] MyStruct +# 82| getFieldList(): [StructFieldList] StructFieldList +# 83| getField(0): [StructField] field: ... +# 83| getName(): [Name] field +# 83| getTypeRepr(): [MacroTypeRepr] MacroTypeRepr +# 83| getMacroCall(): [MacroCall] my_int!... +# 83| getPath(): [Path] my_int +# 83| getSegment(): [PathSegment] my_int +# 83| getIdentifier(): [NameRef] my_int +# 83| getTokenTree(): [TokenTree] TokenTree +# 83| getMacroCallExpansion(): [PathTypeRepr] i32 +# 83| getPath(): [Path] i32 +# 83| getSegment(): [PathSegment] i32 +# 83| getIdentifier(): [NameRef] i32 +# 87| getItem(18): [Struct] struct MyDerive +# 87| getDeriveMacroExpansion(0): [MacroItems] MacroItems +# 87| getItem(0): [Impl] impl ...::Debug for MyDerive::<...> { ... } +# 87| getAssocItemList(): [AssocItemList] AssocItemList +# 87| getAssocItem(0): [Function] fn fmt +# 87| getParamList(): [ParamList] ParamList +# 87| getParam(0): [Param] ...: ... +# 87| getTypeRepr(): [RefTypeRepr] RefTypeRepr +# 87| getTypeRepr(): [PathTypeRepr] ...::Formatter +# 87| getPath(): [Path] ...::Formatter +# 87| getQualifier(): [Path] ...::fmt +# 87| getQualifier(): [Path] $crate +# 87| getSegment(): [PathSegment] $crate +# 87| getIdentifier(): [NameRef] $crate +# 87| getSegment(): [PathSegment] fmt +# 87| getIdentifier(): [NameRef] fmt +# 87| getSegment(): [PathSegment] Formatter +# 87| getIdentifier(): [NameRef] Formatter +# 87| getPat(): [IdentPat] f +# 87| getName(): [Name] f +# 87| getSelfParam(): [SelfParam] SelfParam +# 87| getName(): [Name] self +# 87| getFunctionBody(): [BlockExpr] { ... } +# 87| getStmtList(): [StmtList] StmtList +# 87| getTailExpr(): [MatchExpr] match self { ... } +# 87| getScrutinee(): [VariableAccess] self +# 87| getPath(): [Path] self +# 87| getSegment(): [PathSegment] self +# 87| getIdentifier(): [NameRef] self +# 87| getMatchArmList(): [MatchArmList] MatchArmList +# 87| getArm(0): [MatchArm] ... => ... +# 87| getExpr(): [MethodCallExpr] ... .finish() +# 87| getArgList(): [ArgList] ArgList +# 87| getIdentifier(): [NameRef] finish +# 87| getReceiver(): [MethodCallExpr] ... .field(...) +# 87| getArgList(): [ArgList] ArgList +# 87| getArg(0): [StringLiteralExpr] "field" +# 87| getArg(1): [RefExpr] &field +# 89| getExpr(): [VariableAccess] field +# 89| getPath(): [Path] field +# 89| getSegment(): [PathSegment] field +# 89| getIdentifier(): [NameRef] field +# 87| getIdentifier(): [NameRef] field +# 87| getReceiver(): [MethodCallExpr] f.debug_struct(...) +# 87| getArgList(): [ArgList] ArgList +# 87| getArg(0): [StringLiteralExpr] "MyDerive" +# 87| getIdentifier(): [NameRef] debug_struct +# 87| getReceiver(): [VariableAccess] f +# 87| getPath(): [Path] f +# 87| getSegment(): [PathSegment] f +# 87| getIdentifier(): [NameRef] f +# 87| getPat(): [StructPat] MyDerive {...} +# 88| getPath(): [Path] MyDerive +# 88| getSegment(): [PathSegment] MyDerive +# 88| getIdentifier(): [NameRef] MyDerive +# 87| getStructPatFieldList(): [StructPatFieldList] StructPatFieldList +# 87| getField(0): [StructPatField] field: ... +# 89| getIdentifier(): [NameRef] field +# 89| getPat(): [IdentPat] field +# 89| getName(): [Name] field +# 87| getName(): [Name] fmt +# 87| getRetType(): [RetTypeRepr] RetTypeRepr +# 87| getTypeRepr(): [PathTypeRepr] ...::Result +# 87| getPath(): [Path] ...::Result +# 87| getQualifier(): [Path] ...::fmt +# 87| getQualifier(): [Path] $crate +# 87| getSegment(): [PathSegment] $crate +# 87| getIdentifier(): [NameRef] $crate +# 87| getSegment(): [PathSegment] fmt +# 87| getIdentifier(): [NameRef] fmt +# 87| getSegment(): [PathSegment] Result +# 87| getIdentifier(): [NameRef] Result +# 87| getGenericParamList(): [GenericParamList] <...> +# 87| getSelfTy(): [PathTypeRepr] MyDerive::<...> +# 87| getPath(): [Path] MyDerive::<...> +# 87| getSegment(): [PathSegment] MyDerive::<...> +# 87| getGenericArgList(): [GenericArgList] <...> +# 88| getIdentifier(): [NameRef] MyDerive +# 87| getTraitTy(): [PathTypeRepr] ...::Debug +# 87| getPath(): [Path] ...::Debug +# 87| getQualifier(): [Path] ...::fmt +# 87| getQualifier(): [Path] $crate +# 87| getSegment(): [PathSegment] $crate +# 87| getIdentifier(): [NameRef] $crate +# 87| getSegment(): [PathSegment] fmt +# 87| getIdentifier(): [NameRef] fmt +# 87| getSegment(): [PathSegment] Debug +# 87| getIdentifier(): [NameRef] Debug +# 87| getWhereClause(): [WhereClause] WhereClause +# 87| getAttr(0): [Attr] Attr +# 87| getMeta(): [TokenTreeMeta] TokenTreeMeta +# 87| getPath(): [Path] derive +# 87| getSegment(): [PathSegment] derive +# 87| getIdentifier(): [NameRef] derive +# 87| getTokenTree(): [TokenTree] TokenTree +# 88| getName(): [Name] MyDerive +# 88| getFieldList(): [StructFieldList] StructFieldList +# 89| getField(0): [StructField] field: usize +# 89| getName(): [Name] field +# 89| getTypeRepr(): [PathTypeRepr] usize +# 89| getPath(): [Path] usize +# 89| getSegment(): [PathSegment] usize +# 89| getIdentifier(): [NameRef] usize +# 92| getItem(19): [Enum] enum MyDeriveEnum +# 92| getDeriveMacroExpansion(0): [MacroItems] MacroItems +# 92| getItem(0): [Impl] impl ...::PartialEq for MyDeriveEnum::<...> { ... } +# 92| getAssocItemList(): [AssocItemList] AssocItemList +# 92| getAssocItem(0): [Function] fn eq +# 92| getParamList(): [ParamList] ParamList +# 92| getParam(0): [Param] ...: ... +# 92| getTypeRepr(): [RefTypeRepr] RefTypeRepr +# 92| getTypeRepr(): [PathTypeRepr] Self +# 92| getPath(): [Path] Self +# 92| getSegment(): [PathSegment] Self +# 92| getIdentifier(): [NameRef] Self +# 92| getPat(): [IdentPat] other +# 92| getName(): [Name] other +# 92| getSelfParam(): [SelfParam] SelfParam +# 92| getName(): [Name] self +# 92| getFunctionBody(): [BlockExpr] { ... } +# 92| getStmtList(): [StmtList] StmtList +# 92| getTailExpr(): [MatchExpr] match ... { ... } +# 92| getScrutinee(): [TupleExpr] TupleExpr +# 92| getField(0): [VariableAccess] self +# 92| getPath(): [Path] self +# 92| getSegment(): [PathSegment] self +# 92| getIdentifier(): [NameRef] self +# 92| getField(1): [VariableAccess] other +# 92| getPath(): [Path] other +# 92| getSegment(): [PathSegment] other +# 92| getIdentifier(): [NameRef] other +# 92| getMatchArmList(): [MatchArmList] MatchArmList +# 92| getArm(0): [MatchArm] ... => true +# 92| getExpr(): [BooleanLiteralExpr] true +# 92| getPat(): [TuplePat] TuplePat +# 92| getField(0): [PathPat] ...::Variant1 +# 92| getPath(): [Path] ...::Variant1 +# 93| getQualifier(): [Path] MyDeriveEnum +# 93| getSegment(): [PathSegment] MyDeriveEnum +# 93| getIdentifier(): [NameRef] MyDeriveEnum +# 94| getSegment(): [PathSegment] Variant1 +# 94| getIdentifier(): [NameRef] Variant1 +# 92| getField(1): [PathPat] ...::Variant1 +# 92| getPath(): [Path] ...::Variant1 +# 93| getQualifier(): [Path] MyDeriveEnum +# 93| getSegment(): [PathSegment] MyDeriveEnum +# 93| getIdentifier(): [NameRef] MyDeriveEnum +# 94| getSegment(): [PathSegment] Variant1 +# 94| getIdentifier(): [NameRef] Variant1 +# 92| getArm(1): [MatchArm] ... => true +# 92| getExpr(): [BooleanLiteralExpr] true +# 92| getPat(): [TuplePat] TuplePat +# 92| getField(0): [PathPat] ...::Variant2 +# 92| getPath(): [Path] ...::Variant2 +# 93| getQualifier(): [Path] MyDeriveEnum +# 93| getSegment(): [PathSegment] MyDeriveEnum +# 93| getIdentifier(): [NameRef] MyDeriveEnum +# 95| getSegment(): [PathSegment] Variant2 +# 95| getIdentifier(): [NameRef] Variant2 +# 92| getField(1): [PathPat] ...::Variant2 +# 92| getPath(): [Path] ...::Variant2 +# 93| getQualifier(): [Path] MyDeriveEnum +# 93| getSegment(): [PathSegment] MyDeriveEnum +# 93| getIdentifier(): [NameRef] MyDeriveEnum +# 95| getSegment(): [PathSegment] Variant2 +# 95| getIdentifier(): [NameRef] Variant2 +# 92| getArm(2): [MatchArm] ... => false +# 92| getExpr(): [BooleanLiteralExpr] false +# 92| getPat(): [IdentPat] _unused +# 92| getName(): [Name] _unused +# 92| getName(): [Name] eq +# 92| getRetType(): [RetTypeRepr] RetTypeRepr +# 92| getTypeRepr(): [PathTypeRepr] bool +# 92| getPath(): [Path] bool +# 92| getSegment(): [PathSegment] bool +# 92| getIdentifier(): [NameRef] bool +# 92| getGenericParamList(): [GenericParamList] <...> +# 92| getSelfTy(): [PathTypeRepr] MyDeriveEnum::<...> +# 92| getPath(): [Path] MyDeriveEnum::<...> +# 92| getSegment(): [PathSegment] MyDeriveEnum::<...> +# 92| getGenericArgList(): [GenericArgList] <...> +# 93| getIdentifier(): [NameRef] MyDeriveEnum +# 92| getTraitTy(): [PathTypeRepr] ...::PartialEq +# 92| getPath(): [Path] ...::PartialEq +# 92| getQualifier(): [Path] ...::cmp +# 92| getQualifier(): [Path] $crate +# 92| getSegment(): [PathSegment] $crate +# 92| getIdentifier(): [NameRef] $crate +# 92| getSegment(): [PathSegment] cmp +# 92| getIdentifier(): [NameRef] cmp +# 92| getSegment(): [PathSegment] PartialEq +# 92| getIdentifier(): [NameRef] PartialEq +# 92| getWhereClause(): [WhereClause] WhereClause +# 92| getDeriveMacroExpansion(1): [MacroItems] MacroItems +# 92| getItem(0): [Impl] impl ...::Eq for MyDeriveEnum::<...> { ... } +# 92| getAssocItemList(): [AssocItemList] AssocItemList +# 92| getGenericParamList(): [GenericParamList] <...> +# 92| getSelfTy(): [PathTypeRepr] MyDeriveEnum::<...> +# 92| getPath(): [Path] MyDeriveEnum::<...> +# 92| getSegment(): [PathSegment] MyDeriveEnum::<...> +# 92| getGenericArgList(): [GenericArgList] <...> +# 93| getIdentifier(): [NameRef] MyDeriveEnum +# 92| getTraitTy(): [PathTypeRepr] ...::Eq +# 92| getPath(): [Path] ...::Eq +# 92| getQualifier(): [Path] ...::cmp +# 92| getQualifier(): [Path] $crate +# 92| getSegment(): [PathSegment] $crate +# 92| getIdentifier(): [NameRef] $crate +# 92| getSegment(): [PathSegment] cmp +# 92| getIdentifier(): [NameRef] cmp +# 92| getSegment(): [PathSegment] Eq +# 92| getIdentifier(): [NameRef] Eq +# 92| getWhereClause(): [WhereClause] WhereClause +# 92| getAttr(0): [Attr] Attr +# 92| getMeta(): [TokenTreeMeta] TokenTreeMeta +# 92| getPath(): [Path] derive +# 92| getSegment(): [PathSegment] derive +# 92| getIdentifier(): [NameRef] derive +# 92| getTokenTree(): [TokenTree] TokenTree +# 93| getName(): [Name] MyDeriveEnum +# 93| getVariantList(): [VariantList] VariantList +# 94| getVariant(0): [Variant] Variant1 +# 94| getName(): [Name] Variant1 +# 95| getVariant(1): [Variant] Variant2 +# 95| getName(): [Name] Variant2 +# 98| getItem(20): [Trait] trait MyTrait +# 98| getAssocItemList(): [AssocItemList] AssocItemList +# 99| getAssocItem(0): [Function] fn my_method +# 99| getParamList(): [ParamList] ParamList +# 99| getName(): [Name] my_method +# 99| getRetType(): [RetTypeRepr] RetTypeRepr +# 99| getTypeRepr(): [PathTypeRepr] u32 +# 99| getPath(): [Path] u32 +# 99| getSegment(): [PathSegment] u32 +# 99| getIdentifier(): [NameRef] u32 +# 98| getName(): [Name] MyTrait +# 102| getItem(21): [Union] union MyDeriveUnion +# 103| getDeriveMacroExpansion(0): [MacroItems] MacroItems +# 103| getItem(0): [Const] const CONST_MyDeriveUnion +# 102| getBody(): [IntegerLiteralExpr] 42 +# 103| getName(): [Name] CONST_MyDeriveUnion +# 102| getTypeRepr(): [PathTypeRepr] u32 +# 102| getPath(): [Path] u32 +# 102| getSegment(): [PathSegment] u32 +# 102| getIdentifier(): [NameRef] u32 +# 103| getItem(1): [Impl] impl MyTrait for MyDeriveUnion { ... } +# 103| getAssocItemList(): [AssocItemList] AssocItemList +# 103| getAssocItem(0): [Function] fn my_method +# 102| getParamList(): [ParamList] ParamList +# 103| getFunctionBody(): [BlockExpr] { ... } +# 103| getStmtList(): [StmtList] StmtList +# 103| getTailExpr(): [ConstAccess] CONST_MyDeriveUnion +# 103| getPath(): [Path] CONST_MyDeriveUnion +# 103| getSegment(): [PathSegment] CONST_MyDeriveUnion +# 103| getIdentifier(): [NameRef] CONST_MyDeriveUnion +# 102| getName(): [Name] my_method +# 102| getRetType(): [RetTypeRepr] RetTypeRepr +# 102| getTypeRepr(): [PathTypeRepr] u32 +# 102| getPath(): [Path] u32 +# 102| getSegment(): [PathSegment] u32 +# 102| getIdentifier(): [NameRef] u32 +# 103| getSelfTy(): [PathTypeRepr] MyDeriveUnion +# 103| getPath(): [Path] MyDeriveUnion +# 103| getSegment(): [PathSegment] MyDeriveUnion +# 103| getIdentifier(): [NameRef] MyDeriveUnion +# 102| getTraitTy(): [PathTypeRepr] MyTrait +# 102| getPath(): [Path] MyTrait +# 102| getSegment(): [PathSegment] MyTrait +# 102| getIdentifier(): [NameRef] MyTrait +# 102| getAttr(0): [Attr] Attr +# 102| getMeta(): [TokenTreeMeta] TokenTreeMeta +# 102| getPath(): [Path] derive +# 102| getSegment(): [PathSegment] derive +# 102| getIdentifier(): [NameRef] derive +# 102| getTokenTree(): [TokenTree] TokenTree +# 103| getName(): [Name] MyDeriveUnion +# 103| getStructFieldList(): [StructFieldList] StructFieldList +# 104| getField(0): [StructField] field1: usize +# 104| getName(): [Name] field1 +# 104| getTypeRepr(): [PathTypeRepr] usize +# 104| getPath(): [Path] usize +# 104| getSegment(): [PathSegment] usize +# 104| getIdentifier(): [NameRef] usize +# 105| getField(1): [StructField] field2: f64 +# 105| getName(): [Name] field2 +# 105| getTypeRepr(): [PathTypeRepr] f64 +# 105| getPath(): [Path] f64 +# 105| getSegment(): [PathSegment] f64 +# 105| getIdentifier(): [NameRef] f64 # 44| [Comment] //... # 56| [Comment] //... -# 63| [Comment] //... -# 70| [Comment] //... -# 71| [Comment] //... -# 72| [Comment] //... +# 67| [Comment] //... +# 74| [Comment] //... +# 75| [Comment] //... # 76| [Comment] //... -# 79| [Comment] //... +# 80| [Comment] //... +# 83| [Comment] //... proc_macro.rs: # 1| [SourceFile] SourceFile # 1| getItem(0): [Use] use ...::TokenStream diff --git a/rust/ql/test/extractor-tests/macro-expansion/included/included.rs b/rust/ql/test/extractor-tests/macro-expansion/included/included.rs index 7f603237b2d9..54e1ef029f67 100644 --- a/rust/ql/test/extractor-tests/macro-expansion/included/included.rs +++ b/rust/ql/test/extractor-tests/macro-expansion/included/included.rs @@ -30,3 +30,9 @@ macro_rules! definition_location { fn definition_origin() { definition_location!(); } + +macro_rules! shadowed { + () => { struct IncludedShadowed; }; +} + +shadowed!(); diff --git a/rust/ql/test/extractor-tests/macro-expansion/macro_expansion.rs b/rust/ql/test/extractor-tests/macro-expansion/macro_expansion.rs index 145672528837..19cb19c51296 100644 --- a/rust/ql/test/extractor-tests/macro-expansion/macro_expansion.rs +++ b/rust/ql/test/extractor-tests/macro-expansion/macro_expansion.rs @@ -58,6 +58,10 @@ fn test() { ); } +macro_rules! shadowed { + () => { struct RootShadowed; }; +} + include!("included/included.rs"); #[doc = include_str!("some.txt")] // this doesn't expand since 0.0.274 diff --git a/rust/ql/test/extractor-tests/macro-expansion/test.expected b/rust/ql/test/extractor-tests/macro-expansion/test.expected index f75ae56fa85b..f5ddff7ced74 100644 --- a/rust/ql/test/extractor-tests/macro-expansion/test.expected +++ b/rust/ql/test/extractor-tests/macro-expansion/test.expected @@ -16,15 +16,16 @@ attribute_macros | macro_expansion.rs:31:5:34:5 | fn | 2 | macro_expansion.rs:32:5:34:5 | fn bzz_2 | derive_macros | included/included.rs:5:1:8:1 | struct IncludedDerived | 0 | 0 | included/included.rs:5:1:7:9 | impl ...::Debug for IncludedDerived::<...> { ... } | -| macro_expansion.rs:83:1:86:1 | struct MyDerive | 0 | 0 | macro_expansion.rs:83:1:86:1 | impl ...::Debug for MyDerive::<...> { ... } | -| macro_expansion.rs:88:1:92:1 | enum MyDeriveEnum | 0 | 0 | macro_expansion.rs:88:1:92:1 | impl ...::PartialEq for MyDeriveEnum::<...> { ... } | -| macro_expansion.rs:88:1:92:1 | enum MyDeriveEnum | 1 | 0 | macro_expansion.rs:88:1:92:1 | impl ...::Eq for MyDeriveEnum::<...> { ... } | -| macro_expansion.rs:98:1:102:1 | union MyDeriveUnion | 0 | 0 | macro_expansion.rs:99:7:99:19 | const CONST_MyDeriveUnion | -| macro_expansion.rs:98:1:102:1 | union MyDeriveUnion | 0 | 1 | macro_expansion.rs:99:7:99:19 | impl MyTrait for MyDeriveUnion { ... } | +| macro_expansion.rs:87:1:90:1 | struct MyDerive | 0 | 0 | macro_expansion.rs:87:1:90:1 | impl ...::Debug for MyDerive::<...> { ... } | +| macro_expansion.rs:92:1:96:1 | enum MyDeriveEnum | 0 | 0 | macro_expansion.rs:92:1:96:1 | impl ...::PartialEq for MyDeriveEnum::<...> { ... } | +| macro_expansion.rs:92:1:96:1 | enum MyDeriveEnum | 1 | 0 | macro_expansion.rs:92:1:96:1 | impl ...::Eq for MyDeriveEnum::<...> { ... } | +| macro_expansion.rs:102:1:106:1 | union MyDeriveUnion | 0 | 0 | macro_expansion.rs:103:7:103:19 | const CONST_MyDeriveUnion | +| macro_expansion.rs:102:1:106:1 | union MyDeriveUnion | 0 | 1 | macro_expansion.rs:103:7:103:19 | impl MyTrait for MyDeriveUnion { ... } | macro_calls | included/included.rs:2:9:2:39 | concat!... | included/included.rs:2:17:2:38 | "Hello world!" | | included/included.rs:17:5:19:6 | identity!... | included/included.rs:17:15:19:5 | { ... } | | included/included.rs:31:5:31:26 | definition_location!... | included/included.rs:31:5:31:26 | { ... } | +| included/included.rs:38:1:38:12 | shadowed!... | included/included.rs:38:1:38:12 | MacroItems | | macro_expansion.rs:5:9:5:35 | concat!... | macro_expansion.rs:5:17:5:34 | "Hello world!" | | macro_expansion.rs:5:9:5:35 | concat!... | macro_expansion.rs:5:17:5:34 | "Hello world!" | | macro_expansion.rs:31:7:31:15 | ...::format_args_nl!... | macro_expansion.rs:31:7:31:15 | FormatArgsExpr | @@ -41,18 +42,19 @@ macro_calls | macro_expansion.rs:55:9:58:5 | my_macro!... | macro_expansion.rs:56:9:57:13 | MacroExpr | | macro_expansion.rs:56:9:57:13 | ...::format_args!... | macro_expansion.rs:56:9:57:13 | FormatArgsExpr | | macro_expansion.rs:56:9:57:13 | format!... | macro_expansion.rs:56:9:57:13 | ...::must_use(...) | -| macro_expansion.rs:61:1:61:33 | include!... | included/included.rs:1:1:32:1 | MacroItems | -| macro_expansion.rs:70:16:70:24 | my_int!... | macro_expansion.rs:70:16:70:22 | i32 | -| macro_expansion.rs:71:12:71:20 | my_int!... | macro_expansion.rs:71:12:71:18 | i32 | -| macro_expansion.rs:72:10:72:18 | my_int!... | macro_expansion.rs:72:10:72:16 | i32 | -| macro_expansion.rs:76:14:76:22 | my_int!... | macro_expansion.rs:76:14:76:20 | i32 | -| macro_expansion.rs:79:12:79:20 | my_int!... | macro_expansion.rs:79:12:79:18 | i32 | +| macro_expansion.rs:65:1:65:33 | include!... | included/included.rs:1:1:38:12 | MacroItems | +| macro_expansion.rs:74:16:74:24 | my_int!... | macro_expansion.rs:74:16:74:22 | i32 | +| macro_expansion.rs:75:12:75:20 | my_int!... | macro_expansion.rs:75:12:75:18 | i32 | +| macro_expansion.rs:76:10:76:18 | my_int!... | macro_expansion.rs:76:10:76:16 | i32 | +| macro_expansion.rs:80:14:80:22 | my_int!... | macro_expansion.rs:80:14:80:20 | i32 | +| macro_expansion.rs:83:12:83:20 | my_int!... | macro_expansion.rs:83:12:83:18 | i32 | unexpanded_macro_calls | included/included.rs:2:9:2:39 | concat!... | | included/included.rs:17:5:19:6 | identity!... | | included/included.rs:31:5:31:26 | definition_location!... | +| included/included.rs:38:1:38:12 | shadowed!... | | macro_expansion.rs:56:9:56:31 | concat!... | -| macro_expansion.rs:63:9:63:32 | include_str!... | +| macro_expansion.rs:67:9:67:32 | include_str!... | warnings | macro_expansion.rs:56:9:56:31 | macro expansion failed for 'concat' | includedArgumentLocation @@ -68,3 +70,16 @@ includedArgumentLocation | included/included.rs:31:5:31:26 | dangerous_call | 31 | | included/included.rs:31:5:31:26 | dangerous_call | 31 | | included/included.rs:31:5:31:26 | dangerous_call(...) | 31 | +includedMacroShadowing +| included/included.rs | +includedStandaloneMacroCount +| 4 | +includedMacroExpansionMembership +| concat | false | +| concat | true | +| definition_location | false | +| definition_location | true | +| identity | false | +| identity | true | +| shadowed | false | +| shadowed | true | diff --git a/rust/ql/test/extractor-tests/macro-expansion/test.ql b/rust/ql/test/extractor-tests/macro-expansion/test.ql index c285daf6bd2d..2255aea550e6 100644 --- a/rust/ql/test/extractor-tests/macro-expansion/test.ql +++ b/rust/ql/test/extractor-tests/macro-expansion/test.ql @@ -29,3 +29,31 @@ query predicate includedArgumentLocation(AstNode node, int line) { line = node.getLocation().getStartLine() ) } + +query predicate includedMacroShadowing(string definitionFile) { + exists(MacroCall call | + call.getPath().getText() = "shadowed" and + call.getFile().getRelativePath().matches("%included/included.rs") and + definitionFile = call.resolveMacro().getFile().getRelativePath() + ) +} + +query predicate includedStandaloneMacroCount(int macros) { + macros = + count(MacroCall call | + call.getFile().getRelativePath().matches("%included/included.rs") and + not call.getFile().(ExtractedFile).isInActiveIncludeExpansion(call) + ) +} + +query predicate includedMacroExpansionMembership(string name, boolean active) { + exists(MacroCall call | + call.getFile().getRelativePath().matches("%included/included.rs") and + name = call.getPath().getText() and + ( + if call.getFile().(ExtractedFile).isInActiveIncludeExpansion(call) + then active = true + else active = false + ) + ) +} From 51c7d1e0118890c61e4c87b8fc1fa83ff453fd2b Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Fri, 13 Nov 2026 13:42:13 +0100 Subject: [PATCH 08/11] Address Rust include review feedback Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- rust/extractor/src/main.rs | 33 +++++++++------ .../src/maybe_ignore/a_source.rs | 1 + rust/ql/lib/codeql/files/FileSystem.qll | 3 -- .../codeql/rust/internal/PathResolution.qll | 42 ++++++++++++++----- .../src/queries/telemetry/DatabaseQuality.qll | 22 ++++++---- .../include-nested-shadowing/Cargo.lock | 7 ++++ .../include-nested-shadowing/included/a.rs | 3 ++ .../include-nested-shadowing/included/b.rs | 3 ++ .../included/outer.rs | 3 ++ .../include-nested-shadowing/main.rs | 1 + .../include-nested-shadowing/options.yml | 2 + .../include-nested-shadowing/test.expected | 1 + .../include-nested-shadowing/test.ql | 9 ++++ 13 files changed, 96 insertions(+), 34 deletions(-) create mode 100644 rust/ql/test/extractor-tests/include-nested-shadowing/Cargo.lock create mode 100644 rust/ql/test/extractor-tests/include-nested-shadowing/included/a.rs create mode 100644 rust/ql/test/extractor-tests/include-nested-shadowing/included/b.rs create mode 100644 rust/ql/test/extractor-tests/include-nested-shadowing/included/outer.rs create mode 100644 rust/ql/test/extractor-tests/include-nested-shadowing/main.rs create mode 100644 rust/ql/test/extractor-tests/include-nested-shadowing/options.yml create mode 100644 rust/ql/test/extractor-tests/include-nested-shadowing/test.expected create mode 100644 rust/ql/test/extractor-tests/include-nested-shadowing/test.ql diff --git a/rust/extractor/src/main.rs b/rust/extractor/src/main.rs index ff0b357d459f..9bfc15f6272f 100644 --- a/rust/extractor/src/main.rs +++ b/rust/extractor/src/main.rs @@ -67,6 +67,14 @@ impl<'a> Extractor<'a> { self.source_files.contains(file) || file.starts_with(&self.generated_source_root) } + fn should_extract_included_file(&self, file: &Path, source_kind: SourceKind) -> bool { + source_kind == SourceKind::Library + || self.is_selected_source(file) + || file + .extension() + .is_some_and(|ext| !ext.eq_ignore_ascii_case("rs")) + } + fn extract( &mut self, rust_analyzer: &RustAnalyzer, @@ -116,13 +124,16 @@ impl<'a> Extractor<'a> { } translator.emit_source_file(&ast); translator.emit_truncated_diagnostics_message(); - for step in &translator.included_file_steps { - if let Some(file) = &step.file - && (source_kind == SourceKind::Library || self.is_selected_source(file)) - { - self.archiver.archive(file); - } - } + translator.included_file_steps.retain(|step| { + step.file.as_ref().is_none_or(|file| { + if self.should_extract_included_file(file, source_kind) { + self.archiver.archive(file); + true + } else { + false + } + }) + }); self.steps.append(&mut translator.included_file_steps); translator.trap.commit().unwrap_or_else(|err| { error!( @@ -158,11 +169,9 @@ impl<'a> Extractor<'a> { } let modules = self.extract(&RustAnalyzer::new(vfs, semantics), &file, source_kind); if selected_source { - pending.extend( - modules - .into_iter() - .filter(|module| self.is_selected_source(module)), - ); + pending.extend(modules.into_iter().filter(|module| { + self.is_selected_source(module) && !self.source_files.contains(module) + })); } } } diff --git a/rust/ql/integration-tests/file-exclusions/src/maybe_ignore/a_source.rs b/rust/ql/integration-tests/file-exclusions/src/maybe_ignore/a_source.rs index e69de29bb2d1..59a5e34456d7 100644 --- a/rust/ql/integration-tests/file-exclusions/src/maybe_ignore/a_source.rs +++ b/rust/ql/integration-tests/file-exclusions/src/maybe_ignore/a_source.rs @@ -0,0 +1 @@ +pub fn ignored_source() {} diff --git a/rust/ql/lib/codeql/files/FileSystem.qll b/rust/ql/lib/codeql/files/FileSystem.qll index 41486c595f40..13d9f389ddc6 100644 --- a/rust/ql/lib/codeql/files/FileSystem.qll +++ b/rust/ql/lib/codeql/files/FileSystem.qll @@ -107,9 +107,6 @@ class ExtractedFile extends File { exists(ExtractorStep s | s.getAction() = "LoadSource" and s.getFile() = this) } - /** Holds if this file's standalone parse has semantic information. */ - predicate hasStandaloneSemantics() { not exists(this.getNoSemanticsDiagnostic()) } - /** Holds if `node` belongs to this file's active include expansion. */ predicate isInActiveIncludeExpansion(AstNode node) { node.getFile() = this and diff --git a/rust/ql/lib/codeql/rust/internal/PathResolution.qll b/rust/ql/lib/codeql/rust/internal/PathResolution.qll index 14940d5a5091..e7c5361cce58 100644 --- a/rust/ql/lib/codeql/rust/internal/PathResolution.qll +++ b/rust/ql/lib/codeql/rust/internal/PathResolution.qll @@ -133,17 +133,18 @@ private ItemNode getAChildSuccessor0(ItemNode item, string name, SuccessorKind k pragma[nomagic] private NamedItemNode getANamedNonModuleChildSuccessor( - ItemNode item, string name, Namespace ns, int orderLine, int orderColumn, int sourceLine, - int sourceColumn, int endline, int endcolumn + ItemNode item, string name, Namespace ns, int outerLine, int outerColumn, int innerLine, + int innerColumn, int sourceLine, int sourceColumn, int endline, int endcolumn ) { result.getLocation().hasLocationInfo(_, sourceLine, sourceColumn, endline, endcolumn) and result = getAChildSuccessor0(item, name, _) and ns = result.getNamespace() and not result instanceof ModuleItemNode and - getEffectivePositionNode(result).getLocation().hasLocationInfo(_, orderLine, orderColumn, _, _) + getOutermostIncludePositionNode(result).getLocation().hasLocationInfo(_, outerLine, outerColumn, _, _) and + getImmediateIncludePositionNode(result).getLocation().hasLocationInfo(_, innerLine, innerColumn, _, _) } -private AstNode getEffectivePositionNode(AstNode node) { +private AstNode getOutermostIncludePositionNode(AstNode node) { result = node and not exists(MacroItems included | included = node.getParentNode*() and @@ -159,7 +160,27 @@ private AstNode getEffectivePositionNode(AstNode node) { included = call.getMacroCallExpansion() and included.getFile() = node.getFile() and included.getFile() != call.getFile() and - result = getEffectivePositionNode(call) + result = getOutermostIncludePositionNode(call) + ) +} + +private AstNode getImmediateIncludePositionNode(AstNode node) { + result = node and + not exists(MacroItems included | + included = node.getParentNode*() and + exists(MacroCall call | + included = call.getMacroCallExpansion() and + included.getFile() = node.getFile() and + included.getFile() != call.getFile() + ) + ) + or + exists(MacroItems included, MacroCall call | + included = node.getParentNode*() and + included = call.getMacroCallExpansion() and + included.getFile() = node.getFile() and + included.getFile() != call.getFile() and + result = call ) } @@ -176,13 +197,14 @@ private ItemNode getAChildSuccessor(ItemNode item, string name, SuccessorKind ki or exists(Namespace ns | result = - max(NamedItemNode i, int orderLine, int orderColumn, int sourceLine, int sourceColumn, - int endline, int endcolumn | + max(NamedItemNode i, int outerLine, int outerColumn, int innerLine, int innerColumn, + int sourceLine, int sourceColumn, int endline, int endcolumn | i = - getANamedNonModuleChildSuccessor(item, name, ns, orderLine, orderColumn, sourceLine, - sourceColumn, endline, endcolumn) + getANamedNonModuleChildSuccessor(item, name, ns, outerLine, outerColumn, innerLine, + innerColumn, sourceLine, sourceColumn, endline, endcolumn) | - i order by orderLine, orderColumn, sourceLine, sourceColumn, endline, endcolumn + i order by outerLine, outerColumn, innerLine, innerColumn, sourceLine, sourceColumn, + endline, endcolumn ) ) ) diff --git a/rust/ql/src/queries/telemetry/DatabaseQuality.qll b/rust/ql/src/queries/telemetry/DatabaseQuality.qll index f3d14847def1..b71180f6a660 100644 --- a/rust/ql/src/queries/telemetry/DatabaseQuality.qll +++ b/rust/ql/src/queries/telemetry/DatabaseQuality.qll @@ -20,10 +20,14 @@ private class RelevantFile extends File { } } -private predicate hasActiveSemantics(AstNode node) { - node.getFile().(ExtractedFile).hasStandaloneSemantics() - or - node.getFile().(ExtractedFile).isInActiveIncludeExpansion(node) +private predicate isNotInactiveStandaloneCopy(AstNode node) { + not exists(ExtractedFile file | + file = node.getFile() and + exists(AstNode included | + file.isInActiveIncludeExpansion(included) + ) and + not file.isInActiveIncludeExpansion(node) + ) } module CallTargetStats implements StatsSig { @@ -36,7 +40,7 @@ module CallTargetStats implements StatsSig { private class RelevantInvocationExpr extends InvocationExpr { RelevantInvocationExpr() { this.getFile() instanceof RelevantFile and - hasActiveSemantics(this) and + isNotInactiveStandaloneCopy(this) and not this instanceof CallExprImpl::DynamicCallExpr and not this = any(Operation o | not o.isOverloaded(_, _, _)) and not this = any(DerefExpr de | TypeInference::inferType(de.getExpr()) instanceof PtrType) @@ -59,14 +63,14 @@ module MacroCallTargetStats implements StatsSig { result = count(MacroCall c | c.getFile() instanceof RelevantFile and - hasActiveSemantics(c) and + isNotInactiveStandaloneCopy(c) and c.hasMacroCallExpansion() ) } additional predicate isNotOkCall(MacroCall c) { c.getFile() instanceof RelevantFile and - hasActiveSemantics(c) and + isNotInactiveStandaloneCopy(c) and not c.hasMacroCallExpansion() } @@ -84,7 +88,7 @@ module ExprTypeStats implements StatsSig { result = count(Expr e | e.getFile() instanceof RelevantFile and - hasActiveSemantics(e) and + isNotInactiveStandaloneCopy(e) and e.fromSource() and hasGoodType(e) ) @@ -94,7 +98,7 @@ module ExprTypeStats implements StatsSig { result = count(Expr e | e.getFile() instanceof RelevantFile and - hasActiveSemantics(e) and + isNotInactiveStandaloneCopy(e) and e.fromSource() and not hasGoodType(e) ) diff --git a/rust/ql/test/extractor-tests/include-nested-shadowing/Cargo.lock b/rust/ql/test/extractor-tests/include-nested-shadowing/Cargo.lock new file mode 100644 index 000000000000..b9856cfaf77d --- /dev/null +++ b/rust/ql/test/extractor-tests/include-nested-shadowing/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "test" +version = "0.0.1" diff --git a/rust/ql/test/extractor-tests/include-nested-shadowing/included/a.rs b/rust/ql/test/extractor-tests/include-nested-shadowing/included/a.rs new file mode 100644 index 000000000000..4b011d91ffc6 --- /dev/null +++ b/rust/ql/test/extractor-tests/include-nested-shadowing/included/a.rs @@ -0,0 +1,3 @@ +macro_rules! nested_shadow { + () => { struct FromA; }; +} diff --git a/rust/ql/test/extractor-tests/include-nested-shadowing/included/b.rs b/rust/ql/test/extractor-tests/include-nested-shadowing/included/b.rs new file mode 100644 index 000000000000..29c36c0d26ad --- /dev/null +++ b/rust/ql/test/extractor-tests/include-nested-shadowing/included/b.rs @@ -0,0 +1,3 @@ +macro_rules! nested_shadow { + () => { struct FromB; }; +} diff --git a/rust/ql/test/extractor-tests/include-nested-shadowing/included/outer.rs b/rust/ql/test/extractor-tests/include-nested-shadowing/included/outer.rs new file mode 100644 index 000000000000..d409dbf39df5 --- /dev/null +++ b/rust/ql/test/extractor-tests/include-nested-shadowing/included/outer.rs @@ -0,0 +1,3 @@ +include!("a.rs"); +include!("b.rs"); +nested_shadow!(); diff --git a/rust/ql/test/extractor-tests/include-nested-shadowing/main.rs b/rust/ql/test/extractor-tests/include-nested-shadowing/main.rs new file mode 100644 index 000000000000..c60f3bc4346e --- /dev/null +++ b/rust/ql/test/extractor-tests/include-nested-shadowing/main.rs @@ -0,0 +1 @@ +include!("included/outer.rs"); diff --git a/rust/ql/test/extractor-tests/include-nested-shadowing/options.yml b/rust/ql/test/extractor-tests/include-nested-shadowing/options.yml new file mode 100644 index 000000000000..ef13b95abfb0 --- /dev/null +++ b/rust/ql/test/extractor-tests/include-nested-shadowing/options.yml @@ -0,0 +1,2 @@ +# The duplicate macros intentionally make this fixture invalid Rust. +qltest_cargo_check: false diff --git a/rust/ql/test/extractor-tests/include-nested-shadowing/test.expected b/rust/ql/test/extractor-tests/include-nested-shadowing/test.expected new file mode 100644 index 000000000000..a35194b182a2 --- /dev/null +++ b/rust/ql/test/extractor-tests/include-nested-shadowing/test.expected @@ -0,0 +1 @@ +| included/b.rs | diff --git a/rust/ql/test/extractor-tests/include-nested-shadowing/test.ql b/rust/ql/test/extractor-tests/include-nested-shadowing/test.ql new file mode 100644 index 000000000000..cc416f16de12 --- /dev/null +++ b/rust/ql/test/extractor-tests/include-nested-shadowing/test.ql @@ -0,0 +1,9 @@ +import rust + +query predicate nestedIncludeMacroResolvesToLaterDefinition(string definitionFile) { + exists(MacroCall call | + call.getPath().getText() = "nested_shadow" and + call.getFile().getRelativePath().matches("%included/outer.rs") and + definitionFile = call.resolveMacro().getFile().getRelativePath() + ) +} From 7c3aee74c1e3e0d6e6f80877d013b4ef9a333cca Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Fri, 13 Nov 2026 15:44:32 +0100 Subject: [PATCH 09/11] Fix Rust include review feedback Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- rust/extractor/src/main.rs | 111 +++++++++++++++++- rust/ql/lib/codeql/files/FileSystem.qll | 13 ++ .../codeql/rust/internal/PathResolution.qll | 69 +++++------ .../src/queries/telemetry/DatabaseQuality.qll | 8 +- .../included/inner.rs | 3 + .../included/middle.rs | 5 + .../included/outer.rs | 4 + .../include-nested-shadowing/test.expected | 3 +- .../include-nested-shadowing/test.ql | 16 ++- .../include-standalone-and-active/Cargo.lock | 7 ++ .../include-standalone-and-active/main.rs | 5 + .../include-standalone-and-active/shared.rs | 5 + .../test.expected | 2 + .../include-standalone-and-active/test.ql | 11 ++ 14 files changed, 208 insertions(+), 54 deletions(-) create mode 100644 rust/ql/test/extractor-tests/include-nested-shadowing/included/inner.rs create mode 100644 rust/ql/test/extractor-tests/include-nested-shadowing/included/middle.rs create mode 100644 rust/ql/test/extractor-tests/include-standalone-and-active/Cargo.lock create mode 100644 rust/ql/test/extractor-tests/include-standalone-and-active/main.rs create mode 100644 rust/ql/test/extractor-tests/include-standalone-and-active/shared.rs create mode 100644 rust/ql/test/extractor-tests/include-standalone-and-active/test.expected create mode 100644 rust/ql/test/extractor-tests/include-standalone-and-active/test.ql diff --git a/rust/extractor/src/main.rs b/rust/extractor/src/main.rs index 9bfc15f6272f..8aac92ba0cf7 100644 --- a/rust/extractor/src/main.rs +++ b/rust/extractor/src/main.rs @@ -46,6 +46,58 @@ struct Extractor<'a> { extracted_source_files: HashSet, } +fn is_non_rust_include_target(file: &Path) -> bool { + file.extension() + .is_none_or(|ext| !ext.eq_ignore_ascii_case("rs")) +} + +fn is_excluded_source_directory(source_files: &HashSet, file: &Path) -> bool { + let mut directory = file.parent(); + while let Some(path) = directory { + let mut has_selected_source = false; + let mut has_unselected_source = false; + match fs::read_dir(path) { + Ok(entries) => { + for entry in entries.flatten() { + let source = entry.path(); + if source.extension().is_some_and(|ext| ext == "rs") { + let source = dunce::canonicalize(&source).unwrap_or(source); + if source_files.contains(&source) { + has_selected_source = true; + } else { + has_unselected_source = true; + } + } + } + } + Err(error) => warn!( + "unable to inspect source directory {} for excluded files: {error}", + path.display() + ), + } + if has_unselected_source && !has_selected_source { + return true; + } + if has_selected_source { + return false; + } + directory = path.parent(); + } + false +} + +fn should_extract_included_file( + source_files: &HashSet, + generated_source_root: &Path, + file: &Path, + source_kind: SourceKind, +) -> bool { + source_kind == SourceKind::Library + || source_files.contains(file) + || file.starts_with(generated_source_root) + || (is_non_rust_include_target(file) && !is_excluded_source_directory(source_files, file)) +} + impl<'a> Extractor<'a> { pub fn new( archiver: &'a Archiver, @@ -68,11 +120,12 @@ impl<'a> Extractor<'a> { } fn should_extract_included_file(&self, file: &Path, source_kind: SourceKind) -> bool { - source_kind == SourceKind::Library - || self.is_selected_source(file) - || file - .extension() - .is_some_and(|ext| !ext.eq_ignore_ascii_case("rs")) + should_extract_included_file( + &self.source_files, + &self.generated_source_root, + file, + source_kind, + ) } fn extract( @@ -477,3 +530,51 @@ fn main() -> anyhow::Result<()> { extractor.emit_extraction_diagnostics(start, &config) } + +#[cfg(test)] +mod tests { + use super::*; + use std::time::{SystemTime, UNIX_EPOCH}; + + #[test] + fn included_non_rust_files_respect_source_selection() { + let unique = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_nanos(); + let root = env::temp_dir().join(format!( + "codeql-rust-included-files-{}-{unique}", + std::process::id() + )); + let selected_source = root.join("main.rs"); + let extensionless_target = root.join("included/items"); + let ignored_source = root.join("ignored/a_source.rs"); + let ignored_target = root.join("ignored/items.inc"); + for path in [ + &selected_source, + &extensionless_target, + &ignored_source, + &ignored_target, + ] { + fs::create_dir_all(path.parent().unwrap()).unwrap(); + fs::write(path, "").unwrap(); + } + let source_files = HashSet::from([dunce::canonicalize(&selected_source).unwrap()]); + let generated_source_root = root.join("target"); + + assert!(should_extract_included_file( + &source_files, + &generated_source_root, + &extensionless_target, + SourceKind::Source + )); + assert!(!should_extract_included_file( + &source_files, + &generated_source_root, + &ignored_target, + SourceKind::Source + )); + + fs::remove_dir_all(root).unwrap(); + } +} diff --git a/rust/ql/lib/codeql/files/FileSystem.qll b/rust/ql/lib/codeql/files/FileSystem.qll index 13d9f389ddc6..bf36e0e5c488 100644 --- a/rust/ql/lib/codeql/files/FileSystem.qll +++ b/rust/ql/lib/codeql/files/FileSystem.qll @@ -95,6 +95,9 @@ class ExtractedFile extends File { result.getLocation().getFile() = this } + /** Holds if the standalone extraction reported that semantic information was unavailable. */ + predicate hasNoStandaloneSemanticsDiagnostic() { exists(this.getNoSemanticsDiagnostic()) } + /** * Holds if we have semantical information about this file, which means * we should be able to @@ -118,6 +121,16 @@ class ExtractedFile extends File { ) } + /** Holds if `node` is not an inactive standalone copy of an include expansion. */ + predicate isNotInactiveStandaloneCopy(AstNode node) { + node.getFile() = this and + not ( + this.hasNoStandaloneSemanticsDiagnostic() and + this.isInActiveIncludeExpansion(_) and + not this.isInActiveIncludeExpansion(node) + ) + } + /** * Holds if we know this file was skipped by conditional compilation. * This is not the same as `not this.hasSemantics()`, as a file diff --git a/rust/ql/lib/codeql/rust/internal/PathResolution.qll b/rust/ql/lib/codeql/rust/internal/PathResolution.qll index e7c5361cce58..65b878a83279 100644 --- a/rust/ql/lib/codeql/rust/internal/PathResolution.qll +++ b/rust/ql/lib/codeql/rust/internal/PathResolution.qll @@ -133,55 +133,51 @@ private ItemNode getAChildSuccessor0(ItemNode item, string name, SuccessorKind k pragma[nomagic] private NamedItemNode getANamedNonModuleChildSuccessor( - ItemNode item, string name, Namespace ns, int outerLine, int outerColumn, int innerLine, - int innerColumn, int sourceLine, int sourceColumn, int endline, int endcolumn + ItemNode item, string name, Namespace ns, string positionKey ) { - result.getLocation().hasLocationInfo(_, sourceLine, sourceColumn, endline, endcolumn) and result = getAChildSuccessor0(item, name, _) and ns = result.getNamespace() and not result instanceof ModuleItemNode and - getOutermostIncludePositionNode(result).getLocation().hasLocationInfo(_, outerLine, outerColumn, _, _) and - getImmediateIncludePositionNode(result).getLocation().hasLocationInfo(_, innerLine, innerColumn, _, _) + getEffectivePositionKey(result, positionKey) } -private AstNode getOutermostIncludePositionNode(AstNode node) { - result = node and - not exists(MacroItems included | - included = node.getParentNode*() and - exists(MacroCall call | - included = call.getMacroCallExpansion() and - included.getFile() = node.getFile() and - included.getFile() != call.getFile() - ) +bindingset[component] +private string padLocationComponent(int component) { + exists(string value | + value = component.toString() and + result = ("0000000000" + value).substring(value.length(), value.length() + 10) ) - or - exists(MacroItems included, MacroCall call | - included = node.getParentNode*() and - included = call.getMacroCallExpansion() and - included.getFile() = node.getFile() and - included.getFile() != call.getFile() and - result = getOutermostIncludePositionNode(call) +} + +bindingset[node] +private string getLocationKey(AstNode node) { + exists(int startLine, int startColumn, int endLine, int endColumn | + node.getLocation().hasLocationInfo(_, startLine, startColumn, endLine, endColumn) and + result = + padLocationComponent(startLine) + padLocationComponent(startColumn) + + padLocationComponent(endLine) + padLocationComponent(endColumn) ) } -private AstNode getImmediateIncludePositionNode(AstNode node) { - result = node and - not exists(MacroItems included | +cached +private predicate getEffectivePositionKey(AstNode node, string key) { + exists(MacroItems included, MacroCall call, string parentKey | included = node.getParentNode*() and - exists(MacroCall call | - included = call.getMacroCallExpansion() and - included.getFile() = node.getFile() and - included.getFile() != call.getFile() - ) + included = call.getMacroCallExpansion() and + included.getFile() = node.getFile() and + included.getFile() != call.getFile() and + getEffectivePositionKey(call, parentKey) and + key = parentKey + "/" + getLocationKey(node) ) or - exists(MacroItems included, MacroCall call | + not exists(MacroItems included, MacroCall call | included = node.getParentNode*() and included = call.getMacroCallExpansion() and included.getFile() = node.getFile() and - included.getFile() != call.getFile() and - result = call + included.getFile() != call.getFile() ) + and + key = getLocationKey(node) } pragma[nomagic] @@ -197,14 +193,11 @@ private ItemNode getAChildSuccessor(ItemNode item, string name, SuccessorKind ki or exists(Namespace ns | result = - max(NamedItemNode i, int outerLine, int outerColumn, int innerLine, int innerColumn, - int sourceLine, int sourceColumn, int endline, int endcolumn | + max(NamedItemNode i, string positionKey | i = - getANamedNonModuleChildSuccessor(item, name, ns, outerLine, outerColumn, innerLine, - innerColumn, sourceLine, sourceColumn, endline, endcolumn) + getANamedNonModuleChildSuccessor(item, name, ns, positionKey) | - i order by outerLine, outerColumn, innerLine, innerColumn, sourceLine, sourceColumn, - endline, endcolumn + i order by positionKey ) ) ) diff --git a/rust/ql/src/queries/telemetry/DatabaseQuality.qll b/rust/ql/src/queries/telemetry/DatabaseQuality.qll index b71180f6a660..5a6bff7c2e5a 100644 --- a/rust/ql/src/queries/telemetry/DatabaseQuality.qll +++ b/rust/ql/src/queries/telemetry/DatabaseQuality.qll @@ -21,13 +21,7 @@ private class RelevantFile extends File { } private predicate isNotInactiveStandaloneCopy(AstNode node) { - not exists(ExtractedFile file | - file = node.getFile() and - exists(AstNode included | - file.isInActiveIncludeExpansion(included) - ) and - not file.isInActiveIncludeExpansion(node) - ) + node.getFile().(ExtractedFile).isNotInactiveStandaloneCopy(node) } module CallTargetStats implements StatsSig { diff --git a/rust/ql/test/extractor-tests/include-nested-shadowing/included/inner.rs b/rust/ql/test/extractor-tests/include-nested-shadowing/included/inner.rs new file mode 100644 index 000000000000..1318430e001f --- /dev/null +++ b/rust/ql/test/extractor-tests/include-nested-shadowing/included/inner.rs @@ -0,0 +1,3 @@ +macro_rules! nested_shadow { + () => { struct FromInner; }; +} diff --git a/rust/ql/test/extractor-tests/include-nested-shadowing/included/middle.rs b/rust/ql/test/extractor-tests/include-nested-shadowing/included/middle.rs new file mode 100644 index 000000000000..d27083024d37 --- /dev/null +++ b/rust/ql/test/extractor-tests/include-nested-shadowing/included/middle.rs @@ -0,0 +1,5 @@ +macro_rules! nested_shadow { + () => { struct FromMiddle; }; +} +include!("inner.rs"); +nested_shadow!(); diff --git a/rust/ql/test/extractor-tests/include-nested-shadowing/included/outer.rs b/rust/ql/test/extractor-tests/include-nested-shadowing/included/outer.rs index d409dbf39df5..4c76f7b79a02 100644 --- a/rust/ql/test/extractor-tests/include-nested-shadowing/included/outer.rs +++ b/rust/ql/test/extractor-tests/include-nested-shadowing/included/outer.rs @@ -1,3 +1,7 @@ +macro_rules! nested_shadow { + () => { struct FromOuter; }; +} include!("a.rs"); include!("b.rs"); +include!("middle.rs"); nested_shadow!(); diff --git a/rust/ql/test/extractor-tests/include-nested-shadowing/test.expected b/rust/ql/test/extractor-tests/include-nested-shadowing/test.expected index a35194b182a2..720ebc5d26ab 100644 --- a/rust/ql/test/extractor-tests/include-nested-shadowing/test.expected +++ b/rust/ql/test/extractor-tests/include-nested-shadowing/test.expected @@ -1 +1,2 @@ -| included/b.rs | +| included/middle.rs | 5 | included/inner.rs | 1 | true | +| included/outer.rs | 7 | included/inner.rs | 1 | true | diff --git a/rust/ql/test/extractor-tests/include-nested-shadowing/test.ql b/rust/ql/test/extractor-tests/include-nested-shadowing/test.ql index cc416f16de12..066c940bd3c9 100644 --- a/rust/ql/test/extractor-tests/include-nested-shadowing/test.ql +++ b/rust/ql/test/extractor-tests/include-nested-shadowing/test.ql @@ -1,9 +1,19 @@ import rust -query predicate nestedIncludeMacroResolvesToLaterDefinition(string definitionFile) { +query predicate nestedIncludeMacroResolvesToLaterDefinition( + string callFile, int callLine, string definitionFile, int definitionLine, boolean inMacroExpansion +) { exists(MacroCall call | call.getPath().getText() = "nested_shadow" and - call.getFile().getRelativePath().matches("%included/outer.rs") and - definitionFile = call.resolveMacro().getFile().getRelativePath() + call.getFile().(ExtractedFile).isInActiveIncludeExpansion(call) and + ( + call.getFile().getRelativePath().matches("%included/outer.rs") or + call.getFile().getRelativePath().matches("%included/middle.rs") + ) and + callFile = call.getFile().getRelativePath() and + call.getLocation().hasLocationInfo(_, callLine, _, _, _) and + definitionFile = call.resolveMacro().getFile().getRelativePath() and + call.resolveMacro().getLocation().hasLocationInfo(_, definitionLine, _, _, _) and + (if call.isInMacroExpansion() then inMacroExpansion = true else inMacroExpansion = false) ) } diff --git a/rust/ql/test/extractor-tests/include-standalone-and-active/Cargo.lock b/rust/ql/test/extractor-tests/include-standalone-and-active/Cargo.lock new file mode 100644 index 000000000000..b9856cfaf77d --- /dev/null +++ b/rust/ql/test/extractor-tests/include-standalone-and-active/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "test" +version = "0.0.1" diff --git a/rust/ql/test/extractor-tests/include-standalone-and-active/main.rs b/rust/ql/test/extractor-tests/include-standalone-and-active/main.rs new file mode 100644 index 000000000000..2329a8c8bb2b --- /dev/null +++ b/rust/ql/test/extractor-tests/include-standalone-and-active/main.rs @@ -0,0 +1,5 @@ +mod shared; + +include!("shared.rs"); + +fn main() {} diff --git a/rust/ql/test/extractor-tests/include-standalone-and-active/shared.rs b/rust/ql/test/extractor-tests/include-standalone-and-active/shared.rs new file mode 100644 index 000000000000..ddba4d4519ae --- /dev/null +++ b/rust/ql/test/extractor-tests/include-standalone-and-active/shared.rs @@ -0,0 +1,5 @@ +pub fn shared_function() { + shared_callee(); +} + +fn shared_callee() {} diff --git a/rust/ql/test/extractor-tests/include-standalone-and-active/test.expected b/rust/ql/test/extractor-tests/include-standalone-and-active/test.expected new file mode 100644 index 000000000000..d7533c9de223 --- /dev/null +++ b/rust/ql/test/extractor-tests/include-standalone-and-active/test.expected @@ -0,0 +1,2 @@ +| false | +| true | diff --git a/rust/ql/test/extractor-tests/include-standalone-and-active/test.ql b/rust/ql/test/extractor-tests/include-standalone-and-active/test.ql new file mode 100644 index 000000000000..56d75c58218d --- /dev/null +++ b/rust/ql/test/extractor-tests/include-standalone-and-active/test.ql @@ -0,0 +1,11 @@ +import rust + +query predicate telemetryKeepsStandaloneAndIncludedCopies(boolean included) { + exists(Function function, ExtractedFile file | + function.getName().getText() = "shared_function" and + file = function.getFile() and + file.getRelativePath().matches("%shared.rs") and + file.isNotInactiveStandaloneCopy(function) and + (if file.isInActiveIncludeExpansion(function) then included = true else included = false) + ) +} From 3294bbb937254cc1a431cc777cae8d283b9eb599 Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Fri, 13 Nov 2026 15:44:46 +0100 Subject: [PATCH 10/11] Remove generated QL test lockfile Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../include-standalone-and-active/Cargo.lock | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 rust/ql/test/extractor-tests/include-standalone-and-active/Cargo.lock diff --git a/rust/ql/test/extractor-tests/include-standalone-and-active/Cargo.lock b/rust/ql/test/extractor-tests/include-standalone-and-active/Cargo.lock deleted file mode 100644 index b9856cfaf77d..000000000000 --- a/rust/ql/test/extractor-tests/include-standalone-and-active/Cargo.lock +++ /dev/null @@ -1,7 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "test" -version = "0.0.1" From c4214ca7bbcd798e5a24d615146a295f52a157bc Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Fri, 13 Nov 2026 15:49:49 +0100 Subject: [PATCH 11/11] Format Rust include path resolution query Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- rust/ql/lib/codeql/rust/internal/PathResolution.qll | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/rust/ql/lib/codeql/rust/internal/PathResolution.qll b/rust/ql/lib/codeql/rust/internal/PathResolution.qll index 65b878a83279..572673f601a5 100644 --- a/rust/ql/lib/codeql/rust/internal/PathResolution.qll +++ b/rust/ql/lib/codeql/rust/internal/PathResolution.qll @@ -155,7 +155,7 @@ private string getLocationKey(AstNode node) { node.getLocation().hasLocationInfo(_, startLine, startColumn, endLine, endColumn) and result = padLocationComponent(startLine) + padLocationComponent(startColumn) + - padLocationComponent(endLine) + padLocationComponent(endColumn) + padLocationComponent(endLine) + padLocationComponent(endColumn) ) } @@ -175,8 +175,7 @@ private predicate getEffectivePositionKey(AstNode node, string key) { included = call.getMacroCallExpansion() and included.getFile() = node.getFile() and included.getFile() != call.getFile() - ) - and + ) and key = getLocationKey(node) } @@ -194,8 +193,7 @@ private ItemNode getAChildSuccessor(ItemNode item, string name, SuccessorKind ki exists(Namespace ns | result = max(NamedItemNode i, string positionKey | - i = - getANamedNonModuleChildSuccessor(item, name, ns, positionKey) + i = getANamedNonModuleChildSuccessor(item, name, ns, positionKey) | i order by positionKey )