Skip to content

Rust: Support blanket implementations #20133

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 27 additions & 4 deletions rust/ql/lib/codeql/rust/internal/PathResolution.qll
Original file line number Diff line number Diff line change
Expand Up @@ -933,15 +933,38 @@ class TypeParamItemNode extends TypeItemNode instanceof TypeParam {
}

pragma[nomagic]
Path getABoundPath() {
exists(TypeBoundList tbl | result = tbl.getABound().getTypeRepr().(PathTypeRepr).getPath() |
tbl = super.getTypeBoundList()
TypeBound getBound(int index) {
result = super.getTypeBoundList().getBound(index)
or
exists(int offset |
offset = super.getTypeBoundList().getNumberOfBounds()
or
tbl = this.getAWherePred().getTypeBoundList()
not super.hasTypeBoundList() and
offset = 0
|
result = this.getAWherePred().getTypeBoundList().getBound(index - offset)
)
}

pragma[nomagic]
Path getBoundPath(int index) {
result = this.getBound(index).getTypeRepr().(PathTypeRepr).getPath()
}

Path getABoundPath() { result = this.getBoundPath(_) }

pragma[nomagic]
ItemNode resolveBound(int index) {
result =
rank[index + 1](int i, ItemNode item |
item = resolvePath(this.getBoundPath(i))
|
item order by i
)
}

private predicate noFirst2() { not exists(this.resolveBound(0)) and exists(this.resolveBound(1)) }

Check warning

Code scanning / CodeQL

Dead code Warning

This code is never used, and it's not publicly exported.

ItemNode resolveABound() { result = resolvePath(this.getABoundPath()) }

/**
Expand Down
138 changes: 138 additions & 0 deletions rust/ql/lib/codeql/rust/internal/TypeInference.qll
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,13 @@ private module Input2 implements InputSig2 {
constraint = object.getTrait()
)
}

private predicate testConditionSatisfiesConstraint(
TypeAbstraction abs, TypeMention condition, TypeMention constraint
) {
conditionSatisfiesConstraint(abs, condition, constraint) and
constraint.resolveType().(TraitType).getTrait().getName().getText() = "TryFuture"
}
}

private module M2 = Make2<Input2>;
Expand Down Expand Up @@ -1512,6 +1519,10 @@ private predicate methodCandidateTrait(Type type, Trait trait, string name, int
methodCandidate(type, name, arity, impl)
}

/**
* Holds if `mc` has `rootType` as the root type of the reciever and the target
* method is named `name` and has arity `arity`
*/
pragma[nomagic]
private predicate isMethodCall(MethodCall mc, Type rootType, string name, int arity) {
rootType = mc.getTypeAt(TypePath::nil()) and
Expand Down Expand Up @@ -1710,6 +1721,131 @@ private predicate methodCallHasImplCandidate(MethodCall mc, Impl impl) {
else any()
}

private module BlanketImplementation {
/**
* Holds if `impl` is a blanket implementation, that is, an implementation of a
* trait for a type parameter.
*/
private TypeParamItemNode getBlanketImplementationTypeParam(Impl impl) {
result = impl.(ImplItemNode).resolveSelfTy() and
result = impl.getGenericParamList().getAGenericParam() and
not exists(impl.getAttributeMacroExpansion())
}

predicate isBlanketImplementation(Impl impl) { exists(getBlanketImplementationTypeParam(impl)) }

predicate duplicatedImpl(Impl impl1, Impl impl2) {
isBlanketImplementation(impl1) and
isBlanketImplementation(impl2) and
impl1 != impl2 and
getBlanketImplementationTypeParam(impl1).toString() =
getBlanketImplementationTypeParam(impl2).toString() and
impl1.getTrait().toString() = impl2.getTrait().toString() and
impl1.getLocation().getFile() != impl2.getLocation().getFile() and
impl1.getLocation().getFile().getBaseName() = impl2.getLocation().getFile().getBaseName()
}

Impl getCanonicalImpl(Impl impl) {
isBlanketImplementation(impl) and
result =
max(Impl impl0 |
duplicatedImpl(impl, impl0) or impl = impl0
|
impl0 order by impl0.getLocation().getFile().getAbsolutePath()
)
}

predicate isCanonicalBlanketImplementation(Impl impl) { impl = getCanonicalImpl(impl) }

/**
* Holds if `impl` is a blanket implementation for a type parameter and the type
* parameter must implement `trait`.
*/
private predicate blanketImplementationTraitBound(Impl impl, Trait t) {
t =
rank[1](Trait trait, int i |
trait = getBlanketImplementationTypeParam(impl).resolveBound(i) and
// Exclude traits that are "trivial" in the sense that they are known to
// not narrow things down very much.
not trait.getName().getText() =
[
"Sized", "Clone", "Fn", "FnOnce", "FnMut",
// The auto traits
"Send", "Sync", "Unpin", "UnwindSafe", "RefUnwindSafe"
]
|
trait order by i
)
}

private predicate blanketImplementationWithoutBound(Impl impl) {
not exists(impl.getAttributeMacroExpansion()) and
exists(TypeParam gp |
gp = impl.getGenericParamList().getAGenericParam() and
Comment on lines +1779 to +1784

Check warning

Code scanning / CodeQL

Selecting minimum element using `rank[1]` Warning

Using rank1 is an anti-pattern, use min(..) instead.
gp = impl.(ImplItemNode).resolveSelfTy() and
not exists(gp.(TypeParamItemNode).getABoundPath())
)

Check warning

Code scanning / CodeQL

Dead code Warning

This code is never used, and it's not publicly exported.
}

private predicate test(Impl impl, ItemNode resolved) {
exists(TypeParamItemNode tp |
tp = getBlanketImplementationTypeParam(impl) and
not blanketImplementationTraitBound(impl, _) and
not blanketImplementationWithoutBound(impl) and
resolved = resolvePath(tp.getBoundPath(0))
)

Check warning

Code scanning / CodeQL

Dead code Warning

This code is never used, and it's not publicly exported.
}

private predicate blanketImplementationMethod(
Impl impl, Trait trait, string name, int arity, Function f
) {
isCanonicalBlanketImplementation(impl) and
blanketImplementationTraitBound(impl, trait) and
f.getParamList().hasSelfParam() and
arity = f.getParamList().getNumberOfParams() and
// Make this stronger and document
(
f = impl.(ImplItemNode).getAssocItem(name)
or
f = impl.(ImplItemNode).resolveTraitTy().getAssocItem(name) and
not f = impl.(ImplItemNode).getAssocItem(name)
) and
not getBlanketImplementationTypeParam(impl).resolveABound().(TraitItemNode).getASuccessor(name) =
f
}

predicate methodCallMatchesBlanketImpl(MethodCall mc, Type t, Impl impl, Trait trait, Function f) {
// Only check method calls where we have ruled out inherent method targets.
// Ideally we would also check if non-blanket method targets have been ruled
// out.
methodCallHasNoInherentTarget(mc) and
exists(string name, int arity |
isMethodCall(mc, t, name, arity) and
blanketImplementationMethod(impl, trait, name, arity, f)
)
}

module SatisfiesConstraintInput implements SatisfiesConstraintInputSig<MethodCall> {
pragma[nomagic]
predicate relevantConstraint(MethodCall mc, Type constraint) {
methodCallMatchesBlanketImpl(mc, _, _, constraint.(TraitType).getTrait(), _)
}

predicate useUniversalConditions() { none() }
}

predicate getBlanketImpl(MethodCall mc, Type t, Impl impl, Trait trait, Function f) {
SatisfiesConstraint<MethodCall, SatisfiesConstraintInput>::satisfiesConstraintType(mc,

Check warning

Code scanning / CodeQL

Dead code Warning

This code is never used, and it's not publicly exported.
TTrait(trait), _, _) and
methodCallMatchesBlanketImpl(mc, t, impl, trait, f)
}

pragma[nomagic]

Check warning

Code scanning / CodeQL

Predicates starting with "get" or "as" should return a value Warning

This predicate starts with 'get' but does not return a value.
Function getMethodFromBlanketImpl(MethodCall mc) {
BlanketImplementation::getBlanketImpl(mc, _, _, _, result)
}
}

/** Gets a method from an `impl` block that matches the method call `mc`. */
pragma[nomagic]
private Function getMethodFromImpl(MethodCall mc) {
Expand Down Expand Up @@ -1745,6 +1881,8 @@ private Function resolveMethodCallTarget(MethodCall mc) {
// The method comes from an `impl` block targeting the type of the receiver.
result = getMethodFromImpl(mc)
or
result = BlanketImplementation::getMethodFromBlanketImpl(mc)
or
// The type of the receiver is a type parameter and the method comes from a
// trait bound on the type parameter.
result = getTypeParameterMethod(mc.getTypeAt(TypePath::nil()), mc.getMethodName())
Expand Down
155 changes: 155 additions & 0 deletions rust/ql/test/library-tests/type-inference/blanket_impl.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
// Tests for method resolution targeting blanket trait implementations

mod basic_blanket_impl {
#[derive(Debug, Copy, Clone)]
struct S1;

trait Clone1 {
fn clone1(&self) -> Self;
}

trait Duplicatable {
fn duplicate(&self) -> Self
where
Self: Sized;
}

impl Clone1 for S1 {
// S1::clone1
fn clone1(&self) -> Self {
*self // $ target=deref
}
}

// Blanket implementation for all types that implement Display and Clone
impl<T: Clone1> Duplicatable for T {
// Clone1duplicate
fn duplicate(&self) -> Self {
self.clone1() // $ target=clone1
}
}

pub fn test_basic_blanket() {
let x = S1.clone1(); // $ target=S1::clone1
println!("{x:?}");
let y = S1.duplicate(); // $ MISSING: target=Clone1duplicate
println!("{y:?}");
}
}

mod extension_trait_blanket_impl {
// 1. Elements a trait that is implemented for a type parameter
// 2. An extension trait
// 3. A blanket implementation of the extension trait for a type parameter

trait Flag {
fn read_flag(&self) -> bool;
}

trait TryFlag {
fn try_read_flag(&self) -> Option<bool>;
}

impl<Fl> TryFlag for Fl
where
Fl: Flag,
{
fn try_read_flag(&self) -> Option<bool> {
Some(self.read_flag()) // $ target=read_flag
}
}

trait TryFlagExt: TryFlag {
// TryFlagExt::try_read_flag_twice
fn try_read_flag_twice(&self) -> Option<bool> {
self.try_read_flag() // $ target=try_read_flag
}
}

impl<T: TryFlag> TryFlagExt for T {}

trait AnotherTryFlag {
// AnotherTryFlag::try_read_flag_twice
fn try_read_flag_twice(&self) -> Option<bool>;
}

struct MyTryFlag {
flag: bool,
}

impl TryFlag for MyTryFlag {
// MyTryFlag::try_read_flag
fn try_read_flag(&self) -> Option<bool> {
Some(self.flag) // $ fieldof=MyTryFlag
}
}

struct MyFlag {
flag: bool,
}

impl Flag for MyFlag {
// MyFlag::read_flag
fn read_flag(&self) -> bool {
self.flag // $ fieldof=MyFlag
}
}

struct MyOtherFlag {
flag: bool,
}

impl AnotherTryFlag for MyOtherFlag {
// MyOtherFlag::try_read_flag_twice
fn try_read_flag_twice(&self) -> Option<bool> {
Some(self.flag) // $ fieldof=MyOtherFlag
}
}

fn test() {
let my_try_flag = MyTryFlag { flag: true };
let result = my_try_flag.try_read_flag_twice(); // $ MISSING: target=TryFlagExt::try_read_flag_twice

let my_flag = MyFlag { flag: true };
// Here `TryFlagExt::try_read_flag_twice` is since there is a blanket
// implementaton of `TryFlag` for `Flag`.
let result = my_flag.try_read_flag_twice(); // $ MISSING: target=TryFlagExt::try_read_flag_twice

let my_other_flag = MyOtherFlag { flag: true };
// Here `TryFlagExt::try_read_flag_twice` is _not_ a target since
// `MyOtherFlag` does not implement `TryFlag`.
let result = my_other_flag.try_read_flag_twice(); // $ target=MyOtherFlag::try_read_flag_twice
}
}

mod replicate {
use std::{
fs::{create_dir, create_dir_all, read_dir, remove_dir_all},
io,
path::{Path, PathBuf},
};

pub type Result<T> = std::result::Result<T, io::Error>;

#[rustfmt::skip]
pub fn create<P>(path: P, erase: bool) -> Result<()>
where
P: AsRef<Path>,
{
if erase && path.as_ref().exists() // $ target=as_ref MISSING: target=exists
{
remove(&path)?; // $ target=remove
}
Ok(create_dir(&path)?) // $ target=create_dir
}

#[rustfmt::skip]
pub fn remove<P: AsRef<Path>>(path: P) -> Result<()> {
if path.as_ref().exists() // $ target=as_ref target=exists
{
Ok(remove_dir_all(path)?) // $ target=remove_dir_all
} else {
Ok(())
}
}
}
3 changes: 3 additions & 0 deletions rust/ql/test/library-tests/type-inference/main.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
#![feature(box_patterns)]

mod blanket_impl;

mod field_access {
#[derive(Debug)]
struct S;
Expand Down
Loading
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy