pub struct OpaquePredicatePass<T: Target>(PhantomData<T>);Expand description
Opaque predicate detection and removal pass.
Detects conditional expressions that always evaluate to the same value (always true or always false) and simplifies branches, comparisons, and phi nodes accordingly. Handles self-comparison, identity operations, number-theoretic predicates, null checks, range-based predicates, and nested predicate chains.
Generic over Target; the inner PhantomData lets the same struct
host all analysis methods without holding runtime state.
Tuple Fields§
§0: PhantomData<T>Implementations§
Source§impl<T: Target> OpaquePredicatePass<T>
impl<T: Target> OpaquePredicatePass<T>
Sourceconst MAX_PREDICATE_DEPTH: usize = 16
const MAX_PREDICATE_DEPTH: usize = 16
Maximum recursion depth for nested predicate analysis.
Each level corresponds to one SSA instruction defining a comparison result that feeds into another comparison. 16 levels handles deeply nested opaque predicates from advanced obfuscators (e.g., PureLogs multi-level chains) while preventing stack overflow on pathological inputs.
Sourcefn analyze_predicate_with_cache(
op: &SsaOp<T>,
cache: &DefinitionCache<T>,
depth: usize,
) -> PredicateResult
fn analyze_predicate_with_cache( op: &SsaOp<T>, cache: &DefinitionCache<T>, depth: usize, ) -> PredicateResult
Analyzes a predicate operation with full context, dispatching by SsaOp kind.
Pattern-matching cascade:
- Self-comparison (
Ceq/Clt/Cgtwhereleft == right): immediate result. - Equality analysis (
Ceq): delegates toanalyze_equalityfor XOR==0, SUB==0, MUL*0==0, AND&0==0, number-theoretic, constant, null, and nested patterns. - Less-than / greater-than (
Clt/Cgt): delegates to range-based and constant analysis. - Zero-producing ops (
Xor/Subwithleft==right): returnsUnknownsince the result only becomes meaningful when used in a comparison (handled at that level). - Remainder / Multiplication / And: delegates to specialized analyzers.
Supports recursion up to MAX_PREDICATE_DEPTH for nested
predicates (e.g., ceq(ceq(x, x), 1)).
§Arguments
op- The SSA operation to analyze (typically a comparison or arithmetic op).cache- Pre-built definition cache for efficient variable resolution.depth- Current recursion depth (0 at the top level).
§Returns
PredicateResult::AlwaysTrue or AlwaysFalse if the
predicate can be statically determined, Unknown otherwise.
Sourcefn analyze_equality(
left: SsaVarId,
right: SsaVarId,
cache: &DefinitionCache<T>,
depth: usize,
) -> PredicateResult
fn analyze_equality( left: SsaVarId, right: SsaVarId, cache: &DefinitionCache<T>, depth: usize, ) -> PredicateResult
Analyzes an equality comparison (Ceq) for opaque predicate patterns.
Checks the following patterns (each with symmetric left/right variants):
(x ^ x) == 0– XOR self-cancellation, always true.(x - x) == 0– subtraction self-cancellation, always true.(x * 0) == 0or(0 * x) == 0– zero-producing multiplication, always true.(x & 0) == 0or(0 & x) == 0– zero-producing AND, always true.- Number-theoretic:
(x*(x+1)) % 2 == 0and factored forms, always true. - Constant equality: both sides are constants with known values.
- Null checks: non-null variable (from
NewObjetc.) compared to null, always false. - Nested analysis fallback: if the left operand is itself a predicate (comparison), recursively analyzes it. If the result is known and compared to 1, returns that result; if compared to 0, returns the negation.
§Arguments
left- Left operand of theCeq.right- Right operand of theCeq.cache- Definition cache for resolving variable definitions.depth- Current recursion depth for nested predicate analysis.
§Returns
PredicateResult::AlwaysTrue or AlwaysFalse if the
equality can be statically determined, Unknown otherwise.
Sourcefn analyze_less_than(
left: SsaVarId,
right: SsaVarId,
unsigned: bool,
cache: &DefinitionCache<T>,
_depth: usize,
) -> PredicateResult
fn analyze_less_than( left: SsaVarId, right: SsaVarId, unsigned: bool, cache: &DefinitionCache<T>, _depth: usize, ) -> PredicateResult
Analyzes a less-than comparison (Clt) for opaque predicate patterns.
Checks in order:
- Constant comparison: both operands are constants, evaluate directly (signed or unsigned).
- Range-based: if both operands have known
ValueRanges, checks whetherleft.max < right.min(always true) orleft.min >= right.max(always false). - Left range vs. constant right: uses
ValueRange::always_less_than. - Unsigned bounds:
x <.un 0is always false (no unsigned value is less than zero). - Non-negative check: if
leftis known non-negative (e.g.,ArrayLength), thenleft < 0is always false.
§Arguments
left- Left operand of the comparison.right- Right operand of the comparison.unsigned- Whether this is an unsigned comparison (clt.un).cache- Definition cache for resolving variable definitions and ranges._depth- Unused (less-than analysis is non-recursive).
§Returns
PredicateResult::AlwaysTrue or AlwaysFalse if the
less-than comparison can be statically determined,
Unknown otherwise.
Sourcefn analyze_greater_than(
left: SsaVarId,
right: SsaVarId,
unsigned: bool,
cache: &DefinitionCache<T>,
_depth: usize,
) -> PredicateResult
fn analyze_greater_than( left: SsaVarId, right: SsaVarId, unsigned: bool, cache: &DefinitionCache<T>, _depth: usize, ) -> PredicateResult
Analyzes a greater-than comparison (Cgt) for opaque predicate patterns.
Checks in order:
- Constant comparison: both operands are constants, evaluate directly (signed or unsigned).
- Range-based: if both operands have known
ValueRanges, checks whetherleft.min > right.max(always true) orleft.max <= right.min(always false). - Left range vs. constant right: uses
ValueRange::always_greater_than. - Unsigned bounds:
0 >.un xis always false (zero is never greater than any unsigned value). - Non-negative vs. negative: if
leftis known non-negative andrightis a negative constant, returns always true.
§Arguments
left- Left operand of the comparison.right- Right operand of the comparison.unsigned- Whether this is an unsigned comparison (cgt.un).cache- Definition cache for resolving variable definitions and ranges._depth- Unused (greater-than analysis is non-recursive).
§Returns
PredicateResult::AlwaysTrue or AlwaysFalse if the
greater-than comparison can be statically determined,
Unknown otherwise.
Sourcefn analyze_remainder(
_left: SsaVarId,
right: SsaVarId,
cache: &DefinitionCache<T>,
_depth: usize,
) -> PredicateResult
fn analyze_remainder( _left: SsaVarId, right: SsaVarId, cache: &DefinitionCache<T>, _depth: usize, ) -> PredicateResult
Analyzes a remainder operation (Rem) for x % 1 which always produces zero.
Returns Unknown rather than AlwaysTrue/AlwaysFalse because the zero result
is only meaningful when subsequently compared (handled by the equality analysis).
§Arguments
_left- Left operand of the remainder (unused; any value mod 1 is zero).right- Right operand of the remainder (checked for constant 1).cache- Definition cache for resolving variable definitions._depth- Unused (remainder analysis is non-recursive).
§Returns
Always returns PredicateResult::Unknown because the zero result is only
meaningful when used in a subsequent comparison.
Sourcefn analyze_multiplication(
left: SsaVarId,
right: SsaVarId,
cache: &DefinitionCache<T>,
_depth: usize,
) -> PredicateResult
fn analyze_multiplication( left: SsaVarId, right: SsaVarId, cache: &DefinitionCache<T>, _depth: usize, ) -> PredicateResult
Analyzes a multiplication (Mul) for zero-producing patterns (x * 0 or 0 * x).
Returns Unknown because the zero result is only meaningful when subsequently
compared (handled by the equality analysis at the comparison level).
§Arguments
left- Left operand of the multiplication.right- Right operand of the multiplication.cache- Definition cache for resolving variable definitions._depth- Unused (multiplication analysis is non-recursive).
§Returns
Always returns PredicateResult::Unknown because the zero result is only
meaningful when used in a subsequent comparison.
Sourcefn analyze_and(
left: SsaVarId,
right: SsaVarId,
cache: &DefinitionCache<T>,
_depth: usize,
) -> PredicateResult
fn analyze_and( left: SsaVarId, right: SsaVarId, cache: &DefinitionCache<T>, _depth: usize, ) -> PredicateResult
Analyzes a bitwise AND (And) for zero-producing patterns (x & 0 or 0 & x).
Returns Unknown because the zero result is only meaningful when subsequently
compared (handled by the equality analysis at the comparison level).
§Arguments
left- Left operand of the AND.right- Right operand of the AND.cache- Definition cache for resolving variable definitions._depth- Unused (AND analysis is non-recursive).
§Returns
Always returns PredicateResult::Unknown because the zero result is only
meaningful when used in a subsequent comparison.
Sourcefn is_zero_constant(op: &SsaOp<T>) -> bool
fn is_zero_constant(op: &SsaOp<T>) -> bool
Checks if an operation produces a constant zero.
Sourcefn is_one_constant(op: &SsaOp<T>) -> bool
fn is_one_constant(op: &SsaOp<T>) -> bool
Checks if an operation produces a constant one.
Sourcefn is_null_constant(op: &SsaOp<T>) -> bool
fn is_null_constant(op: &SsaOp<T>) -> bool
Checks if an operation produces a null constant.
Sourcefn is_minus_one_constant(op: &SsaOp<T>) -> bool
fn is_minus_one_constant(op: &SsaOp<T>) -> bool
Checks if an operation produces a constant -1.
Sourcefn is_zero_producing_mul(
op: Option<&SsaOp<T>>,
cache: &DefinitionCache<T>,
) -> bool
fn is_zero_producing_mul( op: Option<&SsaOp<T>>, cache: &DefinitionCache<T>, ) -> bool
Returns true if the operation is a Mul where either operand is a constant zero.
§Arguments
op- The operation to check, orNoneif the variable has no definition.cache- Definition cache for resolving the multiplication operands.
§Returns
true if op is a Mul with at least one constant-zero operand, false otherwise.
Sourcefn is_zero_producing_and(
op: Option<&SsaOp<T>>,
cache: &DefinitionCache<T>,
) -> bool
fn is_zero_producing_and( op: Option<&SsaOp<T>>, cache: &DefinitionCache<T>, ) -> bool
Returns true if the operation is an And where either operand is a constant zero.
§Arguments
op- The operation to check, orNoneif the variable has no definition.cache- Definition cache for resolving the AND operands.
§Returns
true if op is an And with at least one constant-zero operand, false otherwise.
Sourcefn is_always_even_expression(
op: Option<&SsaOp<T>>,
cache: &DefinitionCache<T>,
) -> bool
fn is_always_even_expression( op: Option<&SsaOp<T>>, cache: &DefinitionCache<T>, ) -> bool
Checks if an operation is an expression modulo 2 that always evaluates to 0.
Detects number-theoretic opaque predicates based on the mathematical property that the product of two consecutive integers is always even:
(x * (x + 1)) % 2— direct consecutive product(x * (x - 1)) % 2— reversed consecutive product(x * x - x) % 2— factored form: x^2-x = x(x-1)(x * x + x) % 2— factored form: x^2+x = x(x+1)
§Arguments
op- The operation to check, orNoneif the variable has no definition.cache- Definition cache for resolving operand definitions.
§Returns
true if the expression is a Rem by 2 whose dividend is always even, false otherwise.
Sourcefn is_self_square(
square_var: SsaVarId,
other: SsaVarId,
cache: &DefinitionCache<T>,
) -> bool
fn is_self_square( square_var: SsaVarId, other: SsaVarId, cache: &DefinitionCache<T>, ) -> bool
Checks if square_var is defined as other * other (i.e., other^2).
§Arguments
square_var- The variable suspected to be a square.other- The variable that should appear as both operands of the multiplication.cache- Definition cache for resolving the definition ofsquare_var.
§Returns
true if square_var is defined as Mul { left: other, right: other }, false otherwise.
Sourcefn is_consecutive_pair(
a: SsaVarId,
b: SsaVarId,
cache: &DefinitionCache<T>,
) -> bool
fn is_consecutive_pair( a: SsaVarId, b: SsaVarId, cache: &DefinitionCache<T>, ) -> bool
Checks if two variables form a consecutive integer pair (n and n+1).
Performs three symmetric checks:
b = a + 1(either operand order of theAdd).a = b + 1(symmetric:ais the incremented one).b = a - (-1)(subtraction of -1 is equivalent to adding 1).
§Arguments
a- First variable of the potential consecutive pair.b- Second variable of the potential consecutive pair.cache- Definition cache for resolving variable definitions.
§Returns
true if one variable is defined as the other plus one, false otherwise.
Sourcefn analyze_branch(
condition: SsaVarId,
cache: &DefinitionCache<T>,
) -> PredicateResult
fn analyze_branch( condition: SsaVarId, cache: &DefinitionCache<T>, ) -> PredicateResult
Analyzes a branch condition variable to determine if it is an opaque predicate.
Follows Copy chains iteratively with a BitSet-based cycle detector to handle
SSA copies from phi nodes or obfuscated control flow. At each step:
- If the variable has no definition in
DefUseIndexbut is phi-defined, checks range info for a constant-zero equivalence (all-zero phi = always false). - Delegates to
analyze_predicate_with_cachefor comparison operations. - For
Copyops, advances to the source variable and continues the loop. - For all other operations, falls back to
analyze_branch_opwhich checks direct truthiness of arithmetic and constant operations.
§Arguments
condition- The SSA variable used as the branch condition.cache- Definition cache for resolving the condition’s definition chain.
§Returns
PredicateResult::AlwaysTrue if the branch always takes the true path,
AlwaysFalse if it always takes the false path,
Unknown if the condition cannot be statically resolved.
Sourcefn analyze_branch_op(
cond_op: &SsaOp<T>,
cache: &DefinitionCache<T>,
) -> PredicateResult
fn analyze_branch_op( cond_op: &SsaOp<T>, cache: &DefinitionCache<T>, ) -> PredicateResult
Analyzes a non-Copy, non-comparison operation for direct truthiness in a branch.
Called after the Copy chain has been resolved by analyze_branch.
Checks:
x ^ x= 0 (always false inbrtrue).x - x= 0 (always false).x & 0orx * 0= 0 (always false).x | -1= all-bits-set (always true).Const: zero/null/false is always false; non-zero numeric ortrueis always true.
§Arguments
cond_op- The resolved operation producing the branch condition value.cache- Definition cache for resolving operands of the condition operation.
§Returns
PredicateResult::AlwaysTrue if the operation always produces a non-zero value,
AlwaysFalse if it always produces zero,
Unknown if the truthiness cannot be determined.
Sourcefn analyze_comparison_simplification(
op: &SsaOp<T>,
cache: &DefinitionCache<T>,
) -> Option<ComparisonSimplification<T>>
fn analyze_comparison_simplification( op: &SsaOp<T>, cache: &DefinitionCache<T>, ) -> Option<ComparisonSimplification<T>>
Analyzes a comparison operation for algebraic simplification opportunities.
This checks for patterns like:
(x - y) == 0→x == y(x - y) < 0→x < y(x - y) > 0→x > y(x ^ y) == 0→x == y(cmp) == 1→cmp
§Arguments
op- The SSA comparison operation to analyze (Ceq,Clt, orCgt).cache- Definition cache for resolving operand definitions.
§Returns
Some(ComparisonSimplification) if the comparison can be algebraically simplified,
None if no simplification applies or the operation is not a comparison.
Sourcefn is_zero_var(var: SsaVarId, cache: &DefinitionCache<T>) -> bool
fn is_zero_var(var: SsaVarId, cache: &DefinitionCache<T>) -> bool
Checks if a variable is defined as a constant zero.
Sourcefn is_one_var(var: SsaVarId, cache: &DefinitionCache<T>) -> bool
fn is_one_var(var: SsaVarId, cache: &DefinitionCache<T>) -> bool
Checks if a variable is defined as a constant with value 1.
Sourcefn analyze_ceq_simplification(
dest: SsaVarId,
left: SsaVarId,
right: SsaVarId,
cache: &DefinitionCache<T>,
) -> Option<ComparisonSimplification<T>>
fn analyze_ceq_simplification( dest: SsaVarId, left: SsaVarId, right: SsaVarId, cache: &DefinitionCache<T>, ) -> Option<ComparisonSimplification<T>>
Analyzes a Ceq operation for algebraic simplification.
Detects three patterns:
(x - y) == 0simplifies tox == y(subtraction-zero, skip self-subtraction).(x ^ y) == 0simplifies tox == y(XOR-zero, skip self-XOR).(cmp) == 1simplifies toCopy(cmp)when the other operand is aCeq/Clt/Cgtresult, since CIL comparisons already produce 0 or 1.
§Arguments
dest- Destination variable of theCeq(preserved in the simplified op).left- Left operand of theCeq.right- Right operand of theCeq.cache- Definition cache for resolving operand definitions.
§Returns
Some(ComparisonSimplification) if a simplification pattern matches, None otherwise.
Sourcefn analyze_clt_simplification(
dest: SsaVarId,
left: SsaVarId,
right: SsaVarId,
unsigned: bool,
cache: &DefinitionCache<T>,
) -> Option<ComparisonSimplification<T>>
fn analyze_clt_simplification( dest: SsaVarId, left: SsaVarId, right: SsaVarId, unsigned: bool, cache: &DefinitionCache<T>, ) -> Option<ComparisonSimplification<T>>
Analyzes a Clt operation for algebraic simplification.
Detects (x - y) < 0 and simplifies to x < y (signed only; unsigned subtraction
has different overflow semantics). Self-subtraction is skipped since it is handled
as a constant predicate (AlwaysFalse).
§Arguments
dest- Destination variable of theClt(preserved in the simplified op).left- Left operand of theClt.right- Right operand of theClt.unsigned- Whether this is an unsigned comparison; iftrue, no simplification is attempted.cache- Definition cache for resolving operand definitions.
§Returns
Some(ComparisonSimplification::SimplerOp) if (x - y) < 0 is detected, None otherwise.
Sourcefn analyze_cgt_simplification(
dest: SsaVarId,
left: SsaVarId,
right: SsaVarId,
unsigned: bool,
cache: &DefinitionCache<T>,
) -> Option<ComparisonSimplification<T>>
fn analyze_cgt_simplification( dest: SsaVarId, left: SsaVarId, right: SsaVarId, unsigned: bool, cache: &DefinitionCache<T>, ) -> Option<ComparisonSimplification<T>>
Analyzes a Cgt operation for algebraic simplification.
Detects (x - y) > 0 and simplifies to x > y (signed only; unsigned subtraction
has different overflow semantics). Self-subtraction is skipped since it is handled
as a constant predicate (AlwaysFalse).
§Arguments
dest- Destination variable of theCgt(preserved in the simplified op).left- Left operand of theCgt.right- Right operand of theCgt.unsigned- Whether this is an unsigned comparison; iftrue, no simplification is attempted.cache- Definition cache for resolving operand definitions.
§Returns
Some(ComparisonSimplification::SimplerOp) if (x - y) > 0 is detected, None otherwise.
Sourcefn evaluate_with_tracked(
ssa: &SsaFunction<T>,
condition: SsaVarId,
block_idx: usize,
ptr_size: PointerSize,
) -> PredicateResult
fn evaluate_with_tracked( ssa: &SsaFunction<T>, condition: SsaVarId, block_idx: usize, ptr_size: PointerSize, ) -> PredicateResult
Fallback evaluator for branch conditions that pattern matching cannot resolve.
Creates an SsaEvaluator and runs a forward pass over all blocks from 0 through
block_idx, accumulating concrete values via dataflow propagation. If the condition
variable resolves to a constant after evaluation, returns AlwaysTrue (non-zero) or
AlwaysFalse (zero).
This catches predicates that require multi-step constant propagation across blocks (e.g., a value computed in block 0 that flows through assignments to block 5’s branch).
§Arguments
ssa- The SSA function to evaluate.condition- The branch condition variable to resolve.block_idx- The block containing the branch (evaluation covers blocks 0..=block_idx).ptr_size- Pointer size for the evaluator (affects address arithmetic).
§Returns
PredicateResult::AlwaysTrue or AlwaysFalse if the
evaluator resolves the condition to a constant. Unknown if
the value depends on runtime inputs, loops, or unresolvable phi operands.
Sourcefn analyze_phi_constants(
ssa: &SsaFunction<T>,
) -> BTreeMap<SsaVarId, ConstValue<T>>
fn analyze_phi_constants( ssa: &SsaFunction<T>, ) -> BTreeMap<SsaVarId, ConstValue<T>>
Detects phi nodes where every operand resolves to the same constant value.
Iterates over all phi nodes in all blocks. For each phi, looks up each operand’s
defining operation: if all are Const with identical values, records the mapping
from the phi result variable to that constant. These entries are later used to
replace the phi with a Const instruction and to resolve branch conditions that
depend on phi-defined variables.
§Arguments
ssa- The SSA function whose phi nodes are analyzed.
§Returns
A map from phi result variable to the constant value that all operands agree on. Empty if no phi nodes have all-constant, all-identical operands.
Source§impl<T: Target> OpaquePredicatePass<T>
impl<T: Target> OpaquePredicatePass<T>
Sourcepub fn run<L>(
&self,
ssa: &mut SsaFunction<T>,
method: &T::MethodRef,
events: &L,
ptr_size: PointerSize,
) -> boolwhere
L: EventListener<T> + ?Sized,
pub fn run<L>(
&self,
ssa: &mut SsaFunction<T>,
method: &T::MethodRef,
events: &L,
ptr_size: PointerSize,
) -> boolwhere
L: EventListener<T> + ?Sized,
Per-method entry point. See run for the free-function shape that
host wrappers usually call.
Trait Implementations§
Source§impl<T: Target> Default for OpaquePredicatePass<T>
impl<T: Target> Default for OpaquePredicatePass<T>
Source§impl<T, H> SsaPass<T, H> for OpaquePredicatePass<T>
impl<T, H> SsaPass<T, H> for OpaquePredicatePass<T>
Source§fn description(&self) -> &'static str
fn description(&self) -> &'static str
Source§fn run_on_method(
&self,
ssa: &mut SsaFunction<T>,
method: &T::MethodRef,
host: &H,
) -> Result<bool>
fn run_on_method( &self, ssa: &mut SsaFunction<T>, method: &T::MethodRef, host: &H, ) -> Result<bool>
Source§fn should_run(&self, _method: &T::MethodRef, _host: &H) -> bool
fn should_run(&self, _method: &T::MethodRef, _host: &H) -> bool
Source§fn run_global(&self, _host: &H) -> Result<bool>
fn run_global(&self, _host: &H) -> Result<bool>
Source§fn is_global(&self) -> bool
fn is_global(&self) -> bool
Source§fn initialize(&mut self, _host: &H) -> Result<()>
fn initialize(&mut self, _host: &H) -> Result<()>
Source§fn finalize(&mut self, _host: &H) -> Result<()>
fn finalize(&mut self, _host: &H) -> Result<()>
Source§fn modification_scope(&self) -> ModificationScope
fn modification_scope(&self) -> ModificationScope
Source§fn provides(&self) -> &[T::Capability]
fn provides(&self) -> &[T::Capability]
Source§fn requires(&self) -> &[T::Capability]
fn requires(&self) -> &[T::Capability]
Source§fn reads_peer_ssa(&self) -> bool
fn reads_peer_ssa(&self) -> bool
run_on_method. Read moreSource§fn requires_full_scan(&self) -> bool
fn requires_full_scan(&self) -> bool
Source§fn fallback_layer(&self) -> usize
fn fallback_layer(&self) -> usize
Auto Trait Implementations§
impl<T> Freeze for OpaquePredicatePass<T>
impl<T> RefUnwindSafe for OpaquePredicatePass<T>where
T: RefUnwindSafe,
impl<T> Send for OpaquePredicatePass<T>where
T: Send,
impl<T> Sync for OpaquePredicatePass<T>where
T: Sync,
impl<T> Unpin for OpaquePredicatePass<T>where
T: Unpin,
impl<T> UnsafeUnpin for OpaquePredicatePass<T>
impl<T> UnwindSafe for OpaquePredicatePass<T>where
T: UnwindSafe,
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more