Rust: Implement path resolution in QL

This commit is contained in:
Tom Hvitved
2025-01-15 11:03:36 +01:00
parent a0af4c9a84
commit 0aee2e6fb2
14 changed files with 972 additions and 46 deletions

View File

@@ -73,6 +73,14 @@ query predicate multiplePositions(Element parent, int pos1, int pos2, string acc
pos1 != pos2
}
private import codeql.rust.elements.internal.PathResolution
/** Holds if `p` may resolve to multiple items. */
query predicate multiplePathResolutions(Path p, Item i) {
i = resolvePath(p) and
strictcount(resolvePath(p)) > 1
}
/**
* Gets counts of abstract syntax tree inconsistencies of each type.
*/
@@ -98,4 +106,7 @@ int getAstInconsistencyCounts(string type) {
or
type = "Multiple positions" and
result = count(Element e | multiplePositions(_, _, _, _, e) | e)
or
type = "Multiple path resolutions" and
result = count(Path p | multiplePathResolutions(p, _) | p)
}

View File

@@ -16,6 +16,7 @@ module Impl {
private import codeql.rust.elements.internal.MethodCallExprImpl::Impl
private import codeql.rust.elements.internal.CallExprImpl::Impl
private import codeql.rust.elements.internal.PathExprImpl::Impl
private import codeql.rust.elements.internal.PathResolution
pragma[nomagic]
Resolvable getCallResolvable(CallExprBase call) {
@@ -33,6 +34,10 @@ module Impl {
* Gets the target callable of this call, if a unique such target can
* be statically resolved.
*/
Callable getStaticTarget() { getCallResolvable(this).resolvesAsItem(result) }
Callable getStaticTarget() {
getCallResolvable(this).resolvesAsItem(result)
or
result = resolvePath(this.(CallExpr).getFunction().(PathExpr).getPath())
}
}
}

View File

@@ -0,0 +1,464 @@
/** Provides functionality for resolving paths. */
private import rust
private import codeql.rust.elements.internal.generated.ParentChild
/**
* An item that may be referred to by a path, and which is a node in
* the _item graph_.
*
* The item graph is a labeled directed graph, where an edge
* `item1 --name--> item2` means that `item2` is available inside the
* scope of `item1` under the name `name`. For example, if we have
*
* ```rust
* mod m1 {
* mod m2 { }
* }
* ```
*
* then there is an edge `m1 --m2--> m1::m2`.
*
* Source files are also considered nodes in the item graph, and for
* each source file `f` there is an edge `f --name--> item` when `f`
* declares `item` with the name `name`.
*
* For imports like
*
* ```rust
* mod m1 {
* mod m2;
* use m2::foo;
* }
* ```
*
* we first generate an edge `m1::m2 --name--> f::item`, where `item` is
* any item (named `name`) inside the imported source file `f`. Using this
* edge, `m2::foo` can resolve to `f::foo`, which results in the edge
* `m1::use m2 --foo--> f::foo`. Lastly, all edges out of `use` nodes are
* lifted to predecessors in the graph, so we get an edge `m1 --foo--> f::foo`.
*
*
* References:
* - https://doc.rust-lang.org/reference/items/modules.html
* - https://doc.rust-lang.org/reference/names/scopes.html
* - https://doc.rust-lang.org/reference/paths.html
* - https://doc.rust-lang.org/reference/visibility-and-privacy.html
*/
abstract class ItemNode extends AstNode {
/** Gets the (original) name of this item. */
abstract string getName();
/** Gets the visibility of this item, if any. */
abstract Visibility getVisibility();
/** Holds if this item is declared as `pub`. */
bindingset[this]
pragma[inline_late]
predicate isPublic() { exists(this.getVisibility()) }
/** Gets an element that has this item as immediately enlcosing item. */
pragma[nomagic]
Element getADescendant() {
getImmediateParent(result) = this
or
exists(Element mid |
mid = this.getADescendant() and
getImmediateParent(result) = mid and
not mid instanceof ItemNode
)
}
/** Gets the immediately enclosing item of this item, if any. */
pragma[nomagic]
ItemNode getImmediateParent() { this = result.getADescendant() }
/** Gets the immediately enclosing module (or source file) of this item. */
pragma[nomagic]
ModuleLikeNode getImmediateParentModule() { this = result.getAnItemInScope() }
/** Gets a successor named `name` of this item, if any. */
pragma[nomagic]
ItemNode getASuccessor(string name) {
sourceFileEdge(this, name, result)
or
this = result.getImmediateParent() and
name = result.getName()
or
fileImportEdge(this, name, result)
or
useImportEdge(this, name, result)
or
// items made available through `use` are available to nodes that contain the `use`
exists(UseItemNode use |
use = this.getASuccessor(_) and
result = use.getASuccessor(name)
)
or
// items made available through macro calls are available to nodes that contain the macro call
exists(MacroCallItemNode call |
call = this.getASuccessor(_) and
result = call.getASuccessor(name)
)
or
name = "super" and
if this instanceof Module
then result = this.getImmediateParentModule()
else result = this.getImmediateParentModule().getImmediateParentModule()
or
name = "self" and
if this instanceof Module then result = this else result = this.getImmediateParentModule()
or
name = "Self" and
this = result.(ImplOrTraitItemNode).getAnItemInSelfScope()
or
name = "crate" and
result.(SourceFileItemNode).getFile() = this.getFile()
}
}
/** A module or a source file. */
abstract private class ModuleLikeNode extends ItemNode {
/** Gets an item that may refer directly to items defined in this module. */
pragma[nomagic]
ItemNode getAnItemInScope() {
result.getImmediateParent() = this
or
exists(ItemNode mid |
mid = this.getAnItemInScope() and
result.getImmediateParent() = mid and
not mid instanceof ModuleLikeNode
)
}
}
private class SourceFileItemNode extends ModuleLikeNode, SourceFile {
override string getName() { result = "(source file)" }
override Visibility getVisibility() { none() }
}
private class ConstItemNode extends ItemNode instanceof Const {
override string getName() { result = Const.super.getName().getText() }
override Visibility getVisibility() { result = Const.super.getVisibility() }
}
private class EnumItemNode extends ItemNode instanceof Enum {
override string getName() { result = Enum.super.getName().getText() }
override Visibility getVisibility() { result = Enum.super.getVisibility() }
}
private class VariantItemNode extends ItemNode instanceof Variant {
override string getName() { result = Variant.super.getName().getText() }
override Visibility getVisibility() { result = Variant.super.getVisibility() }
}
private class FunctionItemNode extends ItemNode instanceof Function {
override string getName() { result = Function.super.getName().getText() }
override Visibility getVisibility() { result = Function.super.getVisibility() }
}
abstract private class ImplOrTraitItemNode extends ItemNode {
/** Gets an item that may refer to this node using `Self`. */
pragma[nomagic]
ItemNode getAnItemInSelfScope() {
result.getImmediateParent() = this
or
exists(ItemNode mid |
mid = this.getAnItemInSelfScope() and
result.getImmediateParent() = mid and
not mid instanceof ImplOrTraitItemNode
)
}
}
private class ImplItemNode extends ImplOrTraitItemNode instanceof Impl {
override string getName() { result = "(impl)" }
override Visibility getVisibility() { none() }
}
private class MacroCallItemNode extends ItemNode instanceof MacroCall {
override string getName() { result = "(macro call)" }
override Visibility getVisibility() { none() }
}
private class ModuleItemNode extends ModuleLikeNode instanceof Module {
override string getName() { result = Module.super.getName().getText() }
override Visibility getVisibility() { result = Module.super.getVisibility() }
}
private class StructItemNode extends ItemNode instanceof Struct {
override string getName() { result = Struct.super.getName().getText() }
override Visibility getVisibility() { result = Struct.super.getVisibility() }
}
private class TraitItemNode extends ImplOrTraitItemNode instanceof Trait {
override string getName() { result = Trait.super.getName().getText() }
override Visibility getVisibility() { result = Trait.super.getVisibility() }
}
private class UnionItemNode extends ItemNode instanceof Union {
override string getName() { result = Union.super.getName().getText() }
override Visibility getVisibility() { result = Union.super.getVisibility() }
}
private class UseItemNode extends ItemNode instanceof Use {
override string getName() { result = "(use)" }
override Visibility getVisibility() { none() }
}
private class BlockExprItemNode extends ItemNode instanceof BlockExpr {
override string getName() { result = "(block expr)" }
override Visibility getVisibility() { none() }
}
private predicate sourceFileEdge(SourceFile f, string name, ItemNode item) {
item = f.getAnItem() and
name = item.getName()
}
/** Holds if `f` is available as `mod name;` inside `folder`. */
private predicate fileModule(SourceFile f, string name, Folder folder) {
exists(File file | file = f.getFile() |
file.getBaseName() = name + ".rs" and
folder = file.getParentContainer()
or
exists(Folder encl |
file.getBaseName() = "mod.rs" and
encl = file.getParentContainer() and
name = encl.getBaseName() and
folder = encl.getParentContainer()
)
)
}
/**
* Holds if `m` is a `mod name;` module declaration happening in a file named
* `fileName.rs`, inside the folder `parent`.
*/
private predicate modImport(Module m, string fileName, string name, Folder parent) {
exists(File f |
f = m.getFile() and
not m.hasItemList() and
// TODO: handle
// ```
// #[path = "foo.rs"]
// mod bar;
// ```
not m.getAnAttr().getMeta().getPath().getPart().getNameRef().getText() = "path" and
name = m.getName().getText() and
parent = f.getParentContainer() and
fileName = f.getStem()
)
}
/** Holds if `m` is a `mod name;` item importing file `f`. */
private predicate fileImport(Module m, SourceFile f) {
exists(string fileName, string name, Folder parent | modImport(m, fileName, name, parent) |
// sibling import
fileModule(f, name, parent)
or
// child import
fileModule(f, name, parent.getFolder(fileName))
)
}
/**
* Holds if `mod` is a `mod name;` item targeting a file resulting in `item` being
* in scope under the name `name`.
*/
private predicate fileImportEdge(Module mod, string name, ItemNode item) {
item.isPublic() and
exists(SourceFile f |
fileImport(mod, f) and
sourceFileEdge(f, name, item)
)
}
pragma[nomagic]
private predicate useTreeIsGlobImport(UseTree use) {
// TODO: the extractor should provide this information
use.getLocation() != use.getPath().getLocation() and
not use.hasUseTreeList() and
not use.hasRename()
}
private predicate useTreeDeclares(UseTree tree, string name) {
not useTreeIsGlobImport(tree) and
not exists(tree.getUseTreeList()) and
(
name = tree.getRename().getName().getText() and
name != "_"
or
not tree.hasRename() and
name = tree.getPath().getPart().getNameRef().getText()
)
or
exists(UseTree mid |
useTreeDeclares(mid, name) and
mid = tree.getUseTreeList().getAUseTree()
)
}
/**
* Holds if `item` explicitly declares a sub item named `name`. This includes
* items declared by `use` statements, except for glob imports.
*/
pragma[nomagic]
private predicate declares(ItemNode item, string name) {
exists(ItemNode child | child.getImmediateParent() = item |
child.getName() = name
or
useTreeDeclares(child.(Use).getUseTree(), name)
)
or
exists(MacroCallItemNode call |
declares(call, name) and
call.getImmediateParent() = item
)
}
private class RelevantPath extends Path {
RelevantPath() { not this = any(VariableAccess va).(PathExpr).getPath() }
pragma[nomagic]
predicate isRoot(string name) {
not exists(this.getQualifier()) and
not this = any(UseTreeList list).getAUseTree().getPath() and
name = this.getPart().getNameRef().getText()
}
}
/**
* Holds if the root path `root` references an item named `name`, and `name`
* may be looked up inside enclosing item `encl`.
*/
pragma[nomagic]
private predicate rootPathLookup(RelevantPath root, string name, ItemNode encl) {
exists(ItemNode encl0 |
// lookup in the immediately enclosing item
root.isRoot(name) and
encl0.getADescendant() = root
or
// lookup in an outer scope, but only if the item is not declared in inner scope
exists(ItemNode mid |
rootPathLookup(root, name, mid) and
not declares(mid, name)
|
// nested modules do not have unqualified access to items from outer modules,
// except for items declared at top-level in the source file
if mid instanceof Module
then encl0.(SourceFileItemNode) = mid.getImmediateParent+()
else encl0 = mid.getImmediateParent()
)
|
// functions in `impl` blocks need to use explicit `Self::` to access other
// functions in the `impl` block
if encl0 instanceof ImplOrTraitItemNode then encl = encl0.getImmediateParent() else encl = encl0
)
}
/** Gets the item that `path` resolves to, if any. */
cached
ItemNode resolvePath(RelevantPath path) {
exists(ItemNode encl, string name |
rootPathLookup(path, name, encl) and
result = encl.getASuccessor(name)
)
or
exists(ItemNode q, string name |
q = resolvePathQualifier(path, name) and
result = q.getASuccessor(name)
)
or
result = resolveUseTreeListItem(_, _, path)
}
pragma[nomagic]
private ItemNode resolvePathQualifier(RelevantPath path, string name) {
result = resolvePath(path.getQualifier()) and
name = path.getPart().getNameRef().getText()
}
private predicate isUseTreeSubPath(UseTree tree, RelevantPath path) {
path = tree.getPath()
or
exists(RelevantPath mid |
isUseTreeSubPath(tree, mid) and
path = mid.getQualifier()
)
}
pragma[nomagic]
private predicate isUseTreeSubPathUnqualified(UseTree tree, RelevantPath path, string name) {
isUseTreeSubPath(tree, path) and
not exists(path.getQualifier()) and
name = path.getPart().getNameRef().getText()
}
pragma[nomagic]
private ItemNode resolveUseTreeListItem(Use use, UseTree tree, RelevantPath path) {
exists(UseTree midTree, ItemNode mid, string name |
mid = resolveUseTreeListItem(use, midTree) and
tree = midTree.getUseTreeList().getAUseTree() and
isUseTreeSubPathUnqualified(tree, path, pragma[only_bind_into](name)) and
result = mid.getASuccessor(pragma[only_bind_into](name))
)
or
exists(ItemNode q, string name |
q = resolveUseTreeListItemQualifier(use, tree, path, name) and
result = q.getASuccessor(name)
)
}
pragma[nomagic]
private ItemNode resolveUseTreeListItemQualifier(
Use use, UseTree tree, RelevantPath path, string name
) {
result = resolveUseTreeListItem(use, tree, path.getQualifier()) and
name = path.getPart().getNameRef().getText()
}
pragma[nomagic]
private ItemNode resolveUseTreeListItem(Use use, UseTree tree) {
tree = use.getUseTree() and
result = resolvePath(tree.getPath())
or
result = resolveUseTreeListItem(use, tree, tree.getPath())
}
/** Holds if `use` imports `item` as `name`. */
pragma[nomagic]
private predicate useImportEdge(Use use, string name, ItemNode item) {
exists(UseTree tree, ItemNode used |
used = resolveUseTreeListItem(use, tree) and
not exists(tree.getUseTreeList()) and
if useTreeIsGlobImport(tree)
then
exists(ItemNode encl |
encl.getADescendant() = use and
item = used.getASuccessor(name) and
// glob imports can be shadowed
not declares(encl, name)
)
else item = used
|
not tree.hasRename() and
name = item.getName()
or
name = tree.getRename().getName().getText() and
name != "_"
)
}

View File

@@ -0,0 +1,3 @@
multiplePathResolutions
| main.rs:118:9:118:9 | f | main.rs:104:5:106:5 | fn f |
| main.rs:118:9:118:9 | f | main.rs:110:5:112:5 | fn f |

View File

@@ -0,0 +1,201 @@
mod my; // I1
use my::*; // $ item=I1
use my::nested::nested1::nested2::*; // $ item=I3
mod my2; // I14
use my2::*; // $ item=I14
use my2::nested2::nested3::nested4::{f, g}; // $ item=I11 item=I12 item=I13
mod m1 {
fn f() {
println!("main.rs::m1::f");
} // I16
pub mod m2 {
fn f() {
println!("main.rs::m1::m2::f");
} // I18
pub fn g() {
println!("main.rs::m1::m2::g");
f(); // $ item=I18
super::f(); // $ item=I16
} // I19
pub mod m3 {
use super::f; // $ item=I18
pub fn h() {
println!("main.rs::m1::m2::m3::h");
f(); // $ item=I18
} // I21
} // I20
} // I17
} // I15
mod m4 {
use super::m1::m2::g; // $ item=I19
pub fn i() {
println!("main.rs::m4::i");
g(); // $ item=I19
} // I23
} // I22
struct Foo {} // I24
fn h() {
println!("main.rs::h");
struct Foo {} // I26
fn f() {
use m1::m2::g; // $ item=I19
g(); // $ item=I19
struct Foo {} // I28
println!("main.rs::h::f");
let _ = Foo {}; // $ item=I28
} // I27
let _ = Foo {}; // $ item=I26
f(); // $ item=I27
self::i(); // $ item=I29
} // I25
fn i() {
println!("main.rs::i");
let _ = Foo {}; // $ item=I24
{
struct Foo {
x: i32,
} // I30
let _ = Foo { x: 0 }; // $ item=I30
}
} // I29
use my2::nested2 as my2_nested2_alias; // $ item=I8
use my2_nested2_alias::nested3::{nested4::f as f_alias, nested4::g as g_alias, nested4::*}; // $ item=I10 item=I12 item=I13 item=I11
macro_rules! fn_in_macro {
($e:expr) => {
fn f_defined_in_macro() {
$e
}
};
}
fn j() {
println!("main.rs::j");
fn_in_macro!(println!("main.rs::j::f"));
f_defined_in_macro(); // $ item=f_defined_in_macro
} // I31
mod m5 {
pub fn f() {
println!("main.rs::m5::f");
} // I33
} // I32
mod m6 {
fn f() {
println!("main.rs::m6::f");
} // I35
pub fn g() {
println!("main.rs::m6::g");
// this import shadows the definition `I35`, which we don't currently handle
use super::m5::*; // $ item=I32
f(); // $ item=I33 $ SPURIOUS: item=I35
} // I36
} // I34
mod m7 {
pub enum MyEnum {
A(i32), // I42
B { x: i32 }, // I43
C, // I44
} // I41
#[rustfmt::skip]
pub fn f() -> MyEnum // $ item=I41
{
println!("main.rs::m7::f");
let _ = MyEnum::A(0); // $ item=I42
let _ = MyEnum::B { x: 0 }; // $ item=I43
MyEnum::C // $ item=I44
} // I45
} // I40
mod m8 {
trait MyTrait {
fn f(&self); // I48
fn g(&self) {
println!("main.rs::m8::MyTrait::g");
f(); // $ item=I51
Self::f(self); // $ item=I48
} // I49
} // I47
struct MyStruct {} // I50
fn f() {
println!("main.rs::m8::f");
} // I51
#[rustfmt::skip]
impl MyTrait for MyStruct { // $ item=I47 item=I50
fn f(&self) {
println!("main.rs::m8::<MyStruct as MyTrait>::f");
f(); // $ item=I51
Self::g(self); // $ item=I54
} // I53
fn g(&self) {
println!("main.rs::m8::<MyStruct as MyTrait>::g");
} // I54
} // I52
#[rustfmt::skip]
pub fn g() {
let x = MyStruct {}; // $ item=I50
MyTrait::f(&x); // $ item=I48
<MyStruct as // $ item=I50
MyTrait // $ MISSING: item=I47
> // $ MISSING: item=52
::f(&x); // $ MISSING: item=I53
let x = MyStruct {}; // $ item=I50
x.f(); // $ MISSING: item=I53
let x = MyStruct {}; // $ item=I50
x.g(); // $ MISSING: item=I54
} // I55
} // I46
fn main() {
my::nested::nested1::nested2::f(); // $ item=I4
my::f(); // $ item=I38
nested2::nested3::nested4::f(); // $ item=I12
f(); // $ item=I12
g(); // $ item=I13
crate::h(); // $ item=I25
m1::m2::g(); // $ item=I19
m1::m2::m3::h(); // $ item=I21
m4::i(); // $ item=I23
h(); // $ item=I25
f_alias(); // $ item=I12
g_alias(); // $ item=I13
j(); // $ item=I31
m6::g(); // $ item=I36
m7::f(); // $ item=I45
m8::g(); // $ item=I55
}

View File

@@ -0,0 +1,137 @@
testFailures
mod
| lib.rs:1:1:1:7 | mod my |
| main.rs:1:1:1:7 | mod my |
| main.rs:7:1:7:8 | mod my2 |
| main.rs:13:1:37:1 | mod m1 |
| main.rs:18:5:36:5 | mod m2 |
| main.rs:29:9:35:9 | mod m3 |
| main.rs:39:1:46:1 | mod m4 |
| main.rs:103:1:107:1 | mod m5 |
| main.rs:109:1:120:1 | mod m6 |
| main.rs:122:1:137:1 | mod m7 |
| main.rs:139:1:182:1 | mod m8 |
| my2/mod.rs:1:1:1:16 | mod nested2 |
| my2/nested2.rs:1:1:11:1 | mod nested3 |
| my2/nested2.rs:2:5:10:5 | mod nested4 |
| my.rs:1:1:1:15 | mod nested |
| my/nested.rs:1:1:17:1 | mod nested1 |
| my/nested.rs:2:5:11:5 | mod nested2 |
resolvePath
| main.rs:3:5:3:6 | my | main.rs:1:1:1:7 | mod my |
| main.rs:5:5:5:6 | my | main.rs:1:1:1:7 | mod my |
| main.rs:5:5:5:14 | ...::nested | my.rs:1:1:1:15 | mod nested |
| main.rs:5:5:5:23 | ...::nested1 | my/nested.rs:1:1:17:1 | mod nested1 |
| main.rs:5:5:5:32 | ...::nested2 | my/nested.rs:2:5:11:5 | mod nested2 |
| main.rs:9:5:9:7 | my2 | main.rs:7:1:7:8 | mod my2 |
| main.rs:11:5:11:7 | my2 | main.rs:7:1:7:8 | mod my2 |
| main.rs:11:5:11:16 | ...::nested2 | my2/mod.rs:1:1:1:16 | mod nested2 |
| main.rs:11:5:11:25 | ...::nested3 | my2/nested2.rs:1:1:11:1 | mod nested3 |
| main.rs:11:5:11:34 | ...::nested4 | my2/nested2.rs:2:5:10:5 | mod nested4 |
| main.rs:11:38:11:38 | f | my2/nested2.rs:3:9:5:9 | fn f |
| main.rs:11:41:11:41 | g | my2/nested2.rs:7:9:9:9 | fn g |
| main.rs:25:13:25:13 | f | main.rs:19:9:21:9 | fn f |
| main.rs:26:13:26:17 | super | main.rs:13:1:37:1 | mod m1 |
| main.rs:26:13:26:20 | ...::f | main.rs:14:5:16:5 | fn f |
| main.rs:30:17:30:21 | super | main.rs:18:5:36:5 | mod m2 |
| main.rs:30:17:30:24 | ...::f | main.rs:19:9:21:9 | fn f |
| main.rs:33:17:33:17 | f | main.rs:19:9:21:9 | fn f |
| main.rs:40:9:40:13 | super | main.rs:1:1:201:2 | SourceFile |
| main.rs:40:9:40:17 | ...::m1 | main.rs:13:1:37:1 | mod m1 |
| main.rs:40:9:40:21 | ...::m2 | main.rs:18:5:36:5 | mod m2 |
| main.rs:40:9:40:24 | ...::g | main.rs:23:9:27:9 | fn g |
| main.rs:44:9:44:9 | g | main.rs:23:9:27:9 | fn g |
| main.rs:56:13:56:14 | m1 | main.rs:13:1:37:1 | mod m1 |
| main.rs:56:13:56:18 | ...::m2 | main.rs:18:5:36:5 | mod m2 |
| main.rs:56:13:56:21 | ...::g | main.rs:23:9:27:9 | fn g |
| main.rs:57:9:57:9 | g | main.rs:23:9:27:9 | fn g |
| main.rs:61:17:61:19 | Foo | main.rs:59:9:59:21 | struct Foo |
| main.rs:64:13:64:15 | Foo | main.rs:53:5:53:17 | struct Foo |
| main.rs:66:5:66:5 | f | main.rs:55:5:62:5 | fn f |
| main.rs:68:5:68:8 | self | main.rs:1:1:201:2 | SourceFile |
| main.rs:68:5:68:11 | ...::i | main.rs:71:1:83:1 | fn i |
| main.rs:74:13:74:15 | Foo | main.rs:48:1:48:13 | struct Foo |
| main.rs:81:17:81:19 | Foo | main.rs:77:9:79:9 | struct Foo |
| main.rs:85:5:85:7 | my2 | main.rs:7:1:7:8 | mod my2 |
| main.rs:85:5:85:16 | ...::nested2 | my2/mod.rs:1:1:1:16 | mod nested2 |
| main.rs:87:5:87:21 | my2_nested2_alias | my2/mod.rs:1:1:1:16 | mod nested2 |
| main.rs:87:5:87:30 | ...::nested3 | my2/nested2.rs:1:1:11:1 | mod nested3 |
| main.rs:87:34:87:40 | nested4 | my2/nested2.rs:2:5:10:5 | mod nested4 |
| main.rs:87:34:87:43 | ...::f | my2/nested2.rs:3:9:5:9 | fn f |
| main.rs:87:57:87:63 | nested4 | my2/nested2.rs:2:5:10:5 | mod nested4 |
| main.rs:87:57:87:66 | ...::g | my2/nested2.rs:7:9:9:9 | fn g |
| main.rs:87:80:87:86 | nested4 | my2/nested2.rs:2:5:10:5 | mod nested4 |
| main.rs:100:5:100:22 | f_defined_in_macro | main.rs:99:18:99:42 | fn f_defined_in_macro |
| main.rs:117:13:117:17 | super | main.rs:1:1:201:2 | SourceFile |
| main.rs:117:13:117:21 | ...::m5 | main.rs:103:1:107:1 | mod m5 |
| main.rs:118:9:118:9 | f | main.rs:104:5:106:5 | fn f |
| main.rs:118:9:118:9 | f | main.rs:110:5:112:5 | fn f |
| main.rs:130:19:130:24 | MyEnum | main.rs:123:5:127:5 | enum MyEnum |
| main.rs:133:17:133:22 | MyEnum | main.rs:123:5:127:5 | enum MyEnum |
| main.rs:133:17:133:25 | ...::A | main.rs:124:9:124:14 | A |
| main.rs:134:17:134:22 | MyEnum | main.rs:123:5:127:5 | enum MyEnum |
| main.rs:134:17:134:25 | ...::B | main.rs:124:23:125:20 | B |
| main.rs:135:9:135:14 | MyEnum | main.rs:123:5:127:5 | enum MyEnum |
| main.rs:135:9:135:17 | ...::C | main.rs:125:23:126:9 | C |
| main.rs:145:13:145:13 | f | main.rs:152:5:154:5 | fn f |
| main.rs:146:13:146:16 | Self | main.rs:140:5:148:5 | trait MyTrait |
| main.rs:146:13:146:19 | ...::f | main.rs:141:9:141:20 | fn f |
| main.rs:157:10:157:16 | MyTrait | main.rs:140:5:148:5 | trait MyTrait |
| main.rs:157:22:157:29 | MyStruct | main.rs:150:5:150:22 | struct MyStruct |
| main.rs:160:13:160:13 | f | main.rs:152:5:154:5 | fn f |
| main.rs:161:13:161:16 | Self | main.rs:156:5:167:5 | impl MyTrait for MyStruct { ... } |
| main.rs:161:13:161:19 | ...::g | main.rs:164:9:166:9 | fn g |
| main.rs:171:17:171:24 | MyStruct | main.rs:150:5:150:22 | struct MyStruct |
| main.rs:172:9:172:15 | MyTrait | main.rs:140:5:148:5 | trait MyTrait |
| main.rs:172:9:172:18 | ...::f | main.rs:141:9:141:20 | fn f |
| main.rs:173:10:173:17 | MyStruct | main.rs:150:5:150:22 | struct MyStruct |
| main.rs:173:10:173:17 | MyStruct | main.rs:150:5:150:22 | struct MyStruct |
| main.rs:177:17:177:24 | MyStruct | main.rs:150:5:150:22 | struct MyStruct |
| main.rs:179:17:179:24 | MyStruct | main.rs:150:5:150:22 | struct MyStruct |
| main.rs:185:5:185:6 | my | main.rs:1:1:1:7 | mod my |
| main.rs:185:5:185:14 | ...::nested | my.rs:1:1:1:15 | mod nested |
| main.rs:185:5:185:23 | ...::nested1 | my/nested.rs:1:1:17:1 | mod nested1 |
| main.rs:185:5:185:32 | ...::nested2 | my/nested.rs:2:5:11:5 | mod nested2 |
| main.rs:185:5:185:35 | ...::f | my/nested.rs:3:9:5:9 | fn f |
| main.rs:186:5:186:6 | my | main.rs:1:1:1:7 | mod my |
| main.rs:186:5:186:9 | ...::f | my.rs:5:1:7:1 | fn f |
| main.rs:187:5:187:11 | nested2 | my2/mod.rs:1:1:1:16 | mod nested2 |
| main.rs:187:5:187:20 | ...::nested3 | my2/nested2.rs:1:1:11:1 | mod nested3 |
| main.rs:187:5:187:29 | ...::nested4 | my2/nested2.rs:2:5:10:5 | mod nested4 |
| main.rs:187:5:187:32 | ...::f | my2/nested2.rs:3:9:5:9 | fn f |
| main.rs:188:5:188:5 | f | my2/nested2.rs:3:9:5:9 | fn f |
| main.rs:189:5:189:5 | g | my2/nested2.rs:7:9:9:9 | fn g |
| main.rs:190:5:190:9 | crate | main.rs:1:1:201:2 | SourceFile |
| main.rs:190:5:190:12 | ...::h | main.rs:50:1:69:1 | fn h |
| main.rs:191:5:191:6 | m1 | main.rs:13:1:37:1 | mod m1 |
| main.rs:191:5:191:10 | ...::m2 | main.rs:18:5:36:5 | mod m2 |
| main.rs:191:5:191:13 | ...::g | main.rs:23:9:27:9 | fn g |
| main.rs:192:5:192:6 | m1 | main.rs:13:1:37:1 | mod m1 |
| main.rs:192:5:192:10 | ...::m2 | main.rs:18:5:36:5 | mod m2 |
| main.rs:192:5:192:14 | ...::m3 | main.rs:29:9:35:9 | mod m3 |
| main.rs:192:5:192:17 | ...::h | main.rs:30:27:34:13 | fn h |
| main.rs:193:5:193:6 | m4 | main.rs:39:1:46:1 | mod m4 |
| main.rs:193:5:193:9 | ...::i | main.rs:42:5:45:5 | fn i |
| main.rs:194:5:194:5 | h | main.rs:50:1:69:1 | fn h |
| main.rs:195:5:195:11 | f_alias | my2/nested2.rs:3:9:5:9 | fn f |
| main.rs:196:5:196:11 | g_alias | my2/nested2.rs:7:9:9:9 | fn g |
| main.rs:197:5:197:5 | j | main.rs:97:1:101:1 | fn j |
| main.rs:198:5:198:6 | m6 | main.rs:109:1:120:1 | mod m6 |
| main.rs:198:5:198:9 | ...::g | main.rs:114:5:119:5 | fn g |
| main.rs:199:5:199:6 | m7 | main.rs:122:1:137:1 | mod m7 |
| main.rs:199:5:199:9 | ...::f | main.rs:129:5:136:5 | fn f |
| main.rs:200:5:200:6 | m8 | main.rs:139:1:182:1 | mod m8 |
| main.rs:200:5:200:9 | ...::g | main.rs:169:5:181:5 | fn g |
| 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 |
| my2/mod.rs:5:5:5:29 | ...::nested4 | my2/nested2.rs:2:5:10:5 | mod nested4 |
| my2/mod.rs:5:5:5:32 | ...::f | my2/nested2.rs:3:9:5:9 | fn f |
| my.rs:3:5:3:10 | nested | my.rs:1:1:1:15 | mod nested |
| my.rs:3:5:3:13 | ...::g | my/nested.rs:19:1:22:1 | fn g |
| my.rs:11:5:11:5 | g | my/nested.rs:19:1:22:1 | fn g |
| my/nested.rs:9:13:9:13 | f | my/nested.rs:3:9:5:9 | fn f |
| my/nested.rs:15:9:15:15 | nested2 | my/nested.rs:2:5:11:5 | mod nested2 |
| my/nested.rs:15:9:15:18 | ...::f | my/nested.rs:3:9:5:9 | fn f |
| my/nested.rs:21:5:21:11 | nested1 | my/nested.rs:1:1:17:1 | mod nested1 |
| my/nested.rs:21:5:21:20 | ...::nested2 | my/nested.rs:2:5:11:5 | mod nested2 |
| my/nested.rs:21:5:21:23 | ...::f | my/nested.rs:3:9:5:9 | fn f |

View File

@@ -0,0 +1,49 @@
import rust
import codeql.rust.elements.internal.PathResolution
import utils.test.InlineExpectationsTest
query predicate mod(Module m) { any() }
query predicate resolvePath(Path p, ItemNode i) { i = resolvePath(p) }
module ResolveTest implements TestSig {
string getARelevantTag() { result = "item" }
private predicate itemAt(ItemNode i, string filepath, int line, boolean inMacro) {
i.getLocation().hasLocationInfo(filepath, _, _, line, _) and
if i.isInMacroExpansion() then inMacro = true else inMacro = false
}
private predicate commmentAt(string text, string filepath, int line) {
exists(Comment c |
c.getLocation().hasLocationInfo(filepath, line, _, _, _) and
c.getCommentText() = text
)
}
private predicate item(ItemNode i, string value) {
exists(string filepath, int line, boolean inMacro | itemAt(i, filepath, line, inMacro) |
commmentAt(value, filepath, line) and
inMacro = false
or
(
not commmentAt(_, filepath, line)
or
inMacro = true
) and
value = i.getName()
)
}
predicate hasActualResult(Location location, string element, string tag, string value) {
exists(Path p |
not p = any(Path parent).getQualifier() and
location = p.getLocation() and
element = p.toString() and
item(resolvePath(p), value) and
tag = "item"
)
}
}
import MakeTest<ResolveTest>

View File

@@ -0,0 +1,12 @@
pub mod nested; // I37
use nested::g; // $ item=I7
pub fn f() {
println!("my.rs::f");
} // I38
pub fn h() {
println!("my.rs::h");
g(); // $ item=I7
} // I39

View File

@@ -0,0 +1,22 @@
pub mod nested1 {
pub mod nested2 {
pub fn f() {
println!("nested.rs:nested1::nested2::f");
} // I4
fn g() {
println!("nested.rs:nested1::nested2::g");
f(); // $ item=I4
} // I5
} // I3
fn g() {
println!("nested.rs:nested1::g");
nested2::f(); // $ item=I4
} // I6
} // I1
pub fn g() {
println!("nested.rs::g");
nested1::nested2::f(); // $ item=I4
} // I7

View File

@@ -0,0 +1,6 @@
pub mod nested2; // I8
fn g() {
println!("mod.rs::g");
nested2::nested3::nested4::f(); // $ item=I12
} // I9

View File

@@ -0,0 +1,11 @@
pub mod nested3 {
pub mod nested4 {
pub fn f() {
println!("nested2.rs::nested3::nested4::f");
} // I12
pub fn g() {
println!("nested2.rs::nested3::nested4::g");
} // I13
} // I11
} // I10

View File

@@ -1,6 +1,7 @@
| Multiple children | 0 |
| Multiple locations | 0 |
| Multiple parents | 0 |
| Multiple path resolutions | 0 |
| Multiple positions | 0 |
| Multiple primary QL classes | 0 |
| Multiple toStrings | 0 |

View File

@@ -1,44 +1,49 @@
#select
| test.rs:31:9:31:25 | ...::stdout(...) | test.rs:29:1:29:13 | Attr | test.rs:31:9:31:25 | ...::stdout(...) | Call to ...::stdout(...) in a function with the ctor attribute. |
| test.rs:36:9:36:25 | ...::stdout(...) | test.rs:34:1:34:13 | Attr | test.rs:36:9:36:25 | ...::stdout(...) | Call to ...::stdout(...) in a function with the dtor attribute. |
| test.rs:43:9:43:25 | ...::stdout(...) | test.rs:40:1:40:13 | Attr | test.rs:43:9:43:25 | ...::stdout(...) | Call to ...::stdout(...) in a function with the dtor attribute. |
| test.rs:53:9:53:16 | stdout(...) | test.rs:51:1:51:7 | Attr | test.rs:53:9:53:16 | stdout(...) | Call to stdout(...) in a function with the ctor attribute. |
| test.rs:58:9:58:16 | stderr(...) | test.rs:56:1:56:7 | Attr | test.rs:58:9:58:16 | stderr(...) | Call to stderr(...) in a function with the ctor attribute. |
| test.rs:63:14:63:28 | ...::_print(...) | test.rs:61:1:61:7 | Attr | test.rs:63:14:63:28 | ...::_print(...) | Call to ...::_print(...) in a function with the ctor attribute. |
| test.rs:69:9:69:24 | ...::stdin(...) | test.rs:66:1:66:7 | Attr | test.rs:69:9:69:24 | ...::stdin(...) | Call to ...::stdin(...) in a function with the ctor attribute. |
| test.rs:90:5:90:35 | ...::sleep(...) | test.rs:88:1:88:7 | Attr | test.rs:90:5:90:35 | ...::sleep(...) | Call to ...::sleep(...) in a function with the ctor attribute. |
| test.rs:97:5:97:23 | ...::exit(...) | test.rs:95:1:95:7 | Attr | test.rs:97:5:97:23 | ...::exit(...) | Call to ...::exit(...) in a function with the ctor attribute. |
| test.rs:126:9:126:16 | stderr(...) | test.rs:129:1:129:7 | Attr | test.rs:126:9:126:16 | stderr(...) | Call to stderr(...) in a function with the ctor attribute. |
| test.rs:126:9:126:16 | stderr(...) | test.rs:145:1:145:7 | Attr | test.rs:126:9:126:16 | stderr(...) | Call to stderr(...) in a function with the ctor attribute. |
| test.rs:126:9:126:44 | ... .write_all(...) | test.rs:129:1:129:7 | Attr | test.rs:126:9:126:44 | ... .write_all(...) | Call to ... .write_all(...) in a function with the ctor attribute. |
| test.rs:126:9:126:44 | ... .write_all(...) | test.rs:145:1:145:7 | Attr | test.rs:126:9:126:44 | ... .write_all(...) | Call to ... .write_all(...) in a function with the ctor attribute. |
| test.rs:171:5:171:15 | ...::stdout(...) | test.rs:169:1:169:7 | Attr | test.rs:171:5:171:15 | ...::stdout(...) | Call to ...::stdout(...) in a function with the ctor attribute. |
| test.rs:30:9:30:25 | ...::stdout(...) | test.rs:28:1:28:13 | Attr | test.rs:30:9:30:25 | ...::stdout(...) | Call to ...::stdout(...) in a function with the ctor attribute. |
| test.rs:35:9:35:25 | ...::stdout(...) | test.rs:33:1:33:13 | Attr | test.rs:35:9:35:25 | ...::stdout(...) | Call to ...::stdout(...) in a function with the dtor attribute. |
| test.rs:42:9:42:25 | ...::stdout(...) | test.rs:39:1:39:13 | Attr | test.rs:42:9:42:25 | ...::stdout(...) | Call to ...::stdout(...) in a function with the dtor attribute. |
| test.rs:52:9:52:16 | stdout(...) | test.rs:50:1:50:7 | Attr | test.rs:52:9:52:16 | stdout(...) | Call to stdout(...) in a function with the ctor attribute. |
| test.rs:57:9:57:16 | stderr(...) | test.rs:55:1:55:7 | Attr | test.rs:57:9:57:16 | stderr(...) | Call to stderr(...) in a function with the ctor attribute. |
| test.rs:62:14:62:28 | ...::_print(...) | test.rs:60:1:60:7 | Attr | test.rs:62:14:62:28 | ...::_print(...) | Call to ...::_print(...) in a function with the ctor attribute. |
| test.rs:68:9:68:24 | ...::stdin(...) | test.rs:65:1:65:7 | Attr | test.rs:68:9:68:24 | ...::stdin(...) | Call to ...::stdin(...) in a function with the ctor attribute. |
| test.rs:89:5:89:35 | ...::sleep(...) | test.rs:87:1:87:7 | Attr | test.rs:89:5:89:35 | ...::sleep(...) | Call to ...::sleep(...) in a function with the ctor attribute. |
| test.rs:96:5:96:23 | ...::exit(...) | test.rs:94:1:94:7 | Attr | test.rs:96:5:96:23 | ...::exit(...) | Call to ...::exit(...) in a function with the ctor attribute. |
| test.rs:125:9:125:16 | stderr(...) | test.rs:128:1:128:7 | Attr | test.rs:125:9:125:16 | stderr(...) | Call to stderr(...) in a function with the ctor attribute. |
| test.rs:125:9:125:16 | stderr(...) | test.rs:144:1:144:7 | Attr | test.rs:125:9:125:16 | stderr(...) | Call to stderr(...) in a function with the ctor attribute. |
| test.rs:125:9:125:16 | stderr(...) | test.rs:150:1:150:7 | Attr | test.rs:125:9:125:16 | stderr(...) | Call to stderr(...) in a function with the ctor attribute. |
| test.rs:125:9:125:44 | ... .write_all(...) | test.rs:128:1:128:7 | Attr | test.rs:125:9:125:44 | ... .write_all(...) | Call to ... .write_all(...) in a function with the ctor attribute. |
| test.rs:125:9:125:44 | ... .write_all(...) | test.rs:144:1:144:7 | Attr | test.rs:125:9:125:44 | ... .write_all(...) | Call to ... .write_all(...) in a function with the ctor attribute. |
| test.rs:125:9:125:44 | ... .write_all(...) | test.rs:150:1:150:7 | Attr | test.rs:125:9:125:44 | ... .write_all(...) | Call to ... .write_all(...) in a function with the ctor attribute. |
| test.rs:170:5:170:15 | ...::stdout(...) | test.rs:168:1:168:7 | Attr | test.rs:170:5:170:15 | ...::stdout(...) | Call to ...::stdout(...) in a function with the ctor attribute. |
edges
| test.rs:29:1:29:13 | Attr | test.rs:29:1:32:1 | fn bad1_1 |
| test.rs:29:1:32:1 | fn bad1_1 | test.rs:31:9:31:25 | ...::stdout(...) |
| test.rs:34:1:34:13 | Attr | test.rs:34:1:37:1 | fn bad1_2 |
| test.rs:34:1:37:1 | fn bad1_2 | test.rs:36:9:36:25 | ...::stdout(...) |
| test.rs:39:1:44:1 | fn bad1_3 | test.rs:43:9:43:25 | ...::stdout(...) |
| test.rs:40:1:40:13 | Attr | test.rs:39:1:44:1 | fn bad1_3 |
| test.rs:51:1:51:7 | Attr | test.rs:51:1:54:1 | fn bad2_1 |
| test.rs:51:1:54:1 | fn bad2_1 | test.rs:53:9:53:16 | stdout(...) |
| test.rs:56:1:56:7 | Attr | test.rs:56:1:59:1 | fn bad2_2 |
| test.rs:56:1:59:1 | fn bad2_2 | test.rs:58:9:58:16 | stderr(...) |
| test.rs:61:1:61:7 | Attr | test.rs:61:1:64:1 | fn bad2_3 |
| test.rs:61:1:64:1 | fn bad2_3 | test.rs:63:14:63:28 | ...::_print(...) |
| test.rs:66:1:66:7 | Attr | test.rs:66:1:70:1 | fn bad2_4 |
| test.rs:66:1:70:1 | fn bad2_4 | test.rs:69:9:69:24 | ...::stdin(...) |
| test.rs:88:1:88:7 | Attr | test.rs:88:1:91:1 | fn bad2_7 |
| test.rs:88:1:91:1 | fn bad2_7 | test.rs:90:5:90:35 | ...::sleep(...) |
| test.rs:95:1:95:7 | Attr | test.rs:95:1:98:1 | fn bad2_8 |
| test.rs:95:1:98:1 | fn bad2_8 | test.rs:97:5:97:23 | ...::exit(...) |
| test.rs:125:1:127:1 | fn call_target3_1 | test.rs:126:9:126:16 | stderr(...) |
| test.rs:125:1:127:1 | fn call_target3_1 | test.rs:126:9:126:44 | ... .write_all(...) |
| test.rs:129:1:129:7 | Attr | test.rs:129:1:132:1 | fn bad3_1 |
| test.rs:129:1:132:1 | fn bad3_1 | test.rs:131:5:131:20 | call_target3_1(...) |
| test.rs:131:5:131:20 | call_target3_1(...) | test.rs:125:1:127:1 | fn call_target3_1 |
| test.rs:145:1:145:7 | Attr | test.rs:145:1:149:1 | fn bad3_3 |
| test.rs:145:1:149:1 | fn bad3_3 | test.rs:147:5:147:20 | call_target3_1(...) |
| test.rs:147:5:147:20 | call_target3_1(...) | test.rs:125:1:127:1 | fn call_target3_1 |
| test.rs:169:1:169:7 | Attr | test.rs:169:1:172:1 | fn bad4_1 |
| test.rs:169:1:172:1 | fn bad4_1 | test.rs:171:5:171:15 | ...::stdout(...) |
| test.rs:28:1:28:13 | Attr | test.rs:28:1:31:1 | fn bad1_1 |
| test.rs:28:1:31:1 | fn bad1_1 | test.rs:30:9:30:25 | ...::stdout(...) |
| test.rs:33:1:33:13 | Attr | test.rs:33:1:36:1 | fn bad1_2 |
| test.rs:33:1:36:1 | fn bad1_2 | test.rs:35:9:35:25 | ...::stdout(...) |
| test.rs:38:1:43:1 | fn bad1_3 | test.rs:42:9:42:25 | ...::stdout(...) |
| test.rs:39:1:39:13 | Attr | test.rs:38:1:43:1 | fn bad1_3 |
| test.rs:50:1:50:7 | Attr | test.rs:50:1:53:1 | fn bad2_1 |
| test.rs:50:1:53:1 | fn bad2_1 | test.rs:52:9:52:16 | stdout(...) |
| test.rs:55:1:55:7 | Attr | test.rs:55:1:58:1 | fn bad2_2 |
| test.rs:55:1:58:1 | fn bad2_2 | test.rs:57:9:57:16 | stderr(...) |
| test.rs:60:1:60:7 | Attr | test.rs:60:1:63:1 | fn bad2_3 |
| test.rs:60:1:63:1 | fn bad2_3 | test.rs:62:14:62:28 | ...::_print(...) |
| test.rs:65:1:65:7 | Attr | test.rs:65:1:69:1 | fn bad2_4 |
| test.rs:65:1:69:1 | fn bad2_4 | test.rs:68:9:68:24 | ...::stdin(...) |
| test.rs:87:1:87:7 | Attr | test.rs:87:1:90:1 | fn bad2_7 |
| test.rs:87:1:90:1 | fn bad2_7 | test.rs:89:5:89:35 | ...::sleep(...) |
| test.rs:94:1:94:7 | Attr | test.rs:94:1:97:1 | fn bad2_8 |
| test.rs:94:1:97:1 | fn bad2_8 | test.rs:96:5:96:23 | ...::exit(...) |
| test.rs:124:1:126:1 | fn call_target3_1 | test.rs:125:9:125:16 | stderr(...) |
| test.rs:124:1:126:1 | fn call_target3_1 | test.rs:125:9:125:44 | ... .write_all(...) |
| test.rs:128:1:128:7 | Attr | test.rs:128:1:131:1 | fn bad3_1 |
| test.rs:128:1:131:1 | fn bad3_1 | test.rs:130:5:130:20 | call_target3_1(...) |
| test.rs:130:5:130:20 | call_target3_1(...) | test.rs:124:1:126:1 | fn call_target3_1 |
| test.rs:144:1:144:7 | Attr | test.rs:144:1:148:1 | fn bad3_3 |
| test.rs:144:1:148:1 | fn bad3_3 | test.rs:146:5:146:20 | call_target3_1(...) |
| test.rs:146:5:146:20 | call_target3_1(...) | test.rs:124:1:126:1 | fn call_target3_1 |
| test.rs:150:1:150:7 | Attr | test.rs:150:1:153:1 | fn bad3_4 |
| test.rs:150:1:153:1 | fn bad3_4 | test.rs:152:5:152:12 | bad3_3(...) |
| test.rs:152:5:152:12 | bad3_3(...) | test.rs:144:1:148:1 | fn bad3_3 |
| test.rs:168:1:168:7 | Attr | test.rs:168:1:171:1 | fn bad4_1 |
| test.rs:168:1:171:1 | fn bad4_1 | test.rs:170:5:170:15 | ...::stdout(...) |

View File

@@ -1,4 +1,3 @@
// --- attribute variants ---
use std::io::Write;
@@ -123,7 +122,7 @@ unsafe fn harmless2_11() {
// --- transitive cases ---
fn call_target3_1() {
_ = stderr().write_all(b"Hello, world!"); // $ Alert=source3_1 Alert=source3_3 MISSING: Alert=source3_4
_ = stderr().write_all(b"Hello, world!"); // $ Alert=source3_1 Alert=source3_3 Alert=source3_4
}
#[ctor] // $ Source=source3_1
@@ -148,7 +147,7 @@ fn bad3_3() {
call_target3_2();
}
#[ctor] // $ MISSING: Source=source3_4
#[ctor] // $ Source=source3_4
fn bad3_4() {
bad3_3();
}