Skip to content

Commit 11d5f46

Browse files
Merge pull request #878 from DataDog/jdelgo/panic-on-error-fix
[K9VULN-13253] Panic on error node fix Co-authored-by: jdelgo <joshua.delgado@datadoghq.com>
2 parents d4086c1 + b037789 commit 11d5f46

1 file changed

Lines changed: 53 additions & 32 deletions

File tree

  • crates/static-analysis-kernel/src/analysis/languages/python

crates/static-analysis-kernel/src/analysis/languages/python/imports.rs

Lines changed: 53 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -232,14 +232,13 @@ fn parse_import_statement<'tree, 'text: 'tree>(
232232
// We loop so that if creating the `Import` fails, we can try any subsequent imports within
233233
// this node (e.g. in a comma-separated list) to avoid prematurely yielding `None` on the iterator.
234234
while idx < node.named_child_count() {
235-
debug_assert_eq!(node.field_name_for_named_child(idx as u32), Some("name"));
236235
let name_node = node.named_child(idx).expect("should be in-bounds");
237-
let parsed = parse_field_child_node(source_code, name_node);
238-
239236
idx += 1;
240237

241-
if let Ok(import) = Import::try_new(parsed.full_text, parsed.alias, None, false) {
242-
return Some(import);
238+
if let Some(parsed) = parse_field_child_node(source_code, name_node) {
239+
if let Ok(import) = Import::try_new(parsed.full_text, parsed.alias, None, false) {
240+
return Some(import);
241+
}
243242
}
244243
}
245244
None
@@ -254,7 +253,8 @@ fn parse_import_from_statement<'text>(
254253
debug_assert_eq!(node.kind(), "import_from_statement");
255254
let module_name_node = node.child_by_field_name("module_name").expect(FIELD_EXISTS);
256255
let is_relative = module_name_node.kind() == "relative_import";
257-
let parsed_module = parse_field_child_node(source_code, module_name_node);
256+
let parsed_module = parse_field_child_node(source_code, module_name_node)
257+
.ok_or_else(|| format!("invalid node type `{}`", module_name_node.kind()))?;
258258
// Python syntax invariant: the module of a "from" import can't have an alias.
259259
debug_assert!(parsed_module.alias.is_none());
260260
// tree-sitter grammar invariant: a valid wildcard import must end in a `wildcard_import` node.
@@ -269,13 +269,13 @@ fn parse_import_from_statement<'text>(
269269
let field_children = node.children_by_field_name("name", &mut cursor);
270270
let entities = field_children
271271
.into_iter()
272-
.map(|child| {
273-
let parsed = parse_field_child_node(source_code, child);
272+
.filter_map(|child| {
273+
let parsed = parse_field_child_node(source_code, child)?;
274274
// tree-sitter grammar invariant: these children represent entities, never a module.
275-
Entity {
275+
Some(Entity {
276276
name: parsed.full_text,
277277
alias: parsed.alias,
278-
}
278+
})
279279
})
280280
.collect::<Vec<_>>();
281281
ImportEntities::Specific(entities)
@@ -298,13 +298,13 @@ fn parse_future_import_statement<'text>(
298298
let field_children = node.children_by_field_name("name", &mut cursor);
299299
let entities = field_children
300300
.into_iter()
301-
.map(|child| {
302-
let parsed = parse_field_child_node(source_code, child);
301+
.filter_map(|child| {
302+
let parsed = parse_field_child_node(source_code, child)?;
303303
// tree-sitter grammar invariant: these children represent entities, never a module.
304-
Entity {
304+
Some(Entity {
305305
name: parsed.full_text,
306306
alias: parsed.alias,
307-
}
307+
})
308308
})
309309
.collect::<Vec<_>>();
310310
Import::try_new(
@@ -318,42 +318,37 @@ fn parse_future_import_statement<'text>(
318318
}
319319

320320
/// Constructs a [`MaybeAliased`] from the provided `node`. This is intended to be used on
321-
/// the `name` and `module_name` field children.
322-
///
323-
/// # Panics
324-
/// Panics if the provided node isn't a:
325-
/// * `dotted_name`
326-
/// * `aliased_import`
327-
/// * `relative_import`
321+
/// the `name` and `module_name` field children. Returns `None` if the node isn't a
322+
/// `relative_import`, `dotted_name`, or an `aliased_import`.
328323
fn parse_field_child_node<'text>(
329324
source_code: &'text str,
330325
node: tree_sitter::Node,
331-
) -> MaybeAliased<'text> {
326+
) -> Option<MaybeAliased<'text>> {
332327
match node.kind() {
333328
"relative_import" => {
334329
// (relative_import (import_prefix) (dotted_name))
335330
for i in 0..node.child_count() {
336331
let child = node.child(i).expect("i should be in-bounds");
337332
if child.kind() == "dotted_name" {
338-
return MaybeAliased {
333+
return Some(MaybeAliased {
339334
full_text: ts_node_text(source_code, child),
340335
alias: None,
341-
};
336+
});
342337
}
343338
}
344339
// Otherwise, this `relative_import` only contains an `import_prefix` node,
345340
// so return the entire node's text (which will consist of one or more "."):
346-
MaybeAliased {
341+
Some(MaybeAliased {
347342
full_text: ts_node_text(source_code, node),
348343
alias: None,
349-
}
344+
})
350345
}
351346
"dotted_name" => {
352347
// (dotted_name (identifier)+)
353-
MaybeAliased {
348+
Some(MaybeAliased {
354349
full_text: ts_node_text(source_code, node),
355350
alias: None,
356-
}
351+
})
357352
}
358353
"aliased_import" => {
359354
// (aliased_import name: (dotted_name) alias: (identifier))
@@ -363,18 +358,20 @@ fn parse_field_child_node<'text>(
363358
let alias_node = node.child_by_field_name("alias").expect(FIELD_EXISTS);
364359
debug_assert_eq!(alias_node.kind(), "identifier");
365360
let alias = ts_node_text(source_code, alias_node);
366-
MaybeAliased {
361+
Some(MaybeAliased {
367362
full_text,
368363
alias: Some(alias),
369-
}
364+
})
370365
}
371-
other => panic!("invalid node type `{other}`"),
366+
_ => None,
372367
}
373368
}
374369

375370
#[cfg(test)]
376371
mod tests {
377-
use super::{parse_imports, Entity, Import, ImportEntities};
372+
use super::{parse_imports, parse_imports_with_tree, Entity, Import, ImportEntities};
373+
use crate::analysis::tree_sitter::get_tree;
374+
use crate::model::common::Language;
378375

379376
/// A shorthand to build an [`Entity`] without an alias.
380377
pub fn ent(name: &str) -> Entity<'_> {
@@ -477,6 +474,30 @@ mod tests {
477474
}
478475
}
479476
}
477+
478+
#[test]
479+
fn parse_imports_error_node() {
480+
let code = "import foo, + bar";
481+
let tree = get_tree(code, &Language::Python).unwrap();
482+
assert!(tree.root_node().has_error());
483+
let imports = parse_imports_with_tree(code, &tree);
484+
assert_eq!(
485+
imports,
486+
vec![
487+
Import::try_new("foo", None, None, false).unwrap(),
488+
Import::try_new("bar", None, None, false).unwrap(),
489+
]
490+
);
491+
}
492+
493+
#[test]
494+
fn parse_imports_all_error_nodes() {
495+
let code = "import +, +";
496+
let tree = get_tree(code, &Language::Python).unwrap();
497+
assert!(tree.root_node().has_error());
498+
let imports = parse_imports_with_tree(code, &tree);
499+
assert!(imports.is_empty());
500+
}
480501
}
481502

482503
/// mod for documenting (intentionally) "incorrect" parsing behavior.

0 commit comments

Comments
 (0)