From e51cb478af4f8a8bca580592a7fb800a87ee83c4 Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Mon, 12 May 2025 19:43:19 +0100 Subject: [PATCH 1/7] C++: Expose 'MemoryLocation0'. --- .../cpp/ir/implementation/aliased_ssa/internal/AliasedSSA.qll | 2 +- .../cpp/ir/implementation/unaliased_ssa/internal/SimpleSSA.qll | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/cpp/ql/lib/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasedSSA.qll b/cpp/ql/lib/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasedSSA.qll index acb17006fefb..b5937815dab5 100644 --- a/cpp/ql/lib/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasedSSA.qll +++ b/cpp/ql/lib/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasedSSA.qll @@ -235,7 +235,7 @@ private newtype TMemoryLocation = * * Some of these memory locations will be filtered out for performance reasons before being passed to SSA construction. */ -abstract private class MemoryLocation0 extends TMemoryLocation { +abstract class MemoryLocation0 extends TMemoryLocation { final string toString() { if this.isMayAccess() then result = "?" + this.toStringInternal() diff --git a/cpp/ql/lib/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/SimpleSSA.qll b/cpp/ql/lib/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/SimpleSSA.qll index 8bee2bf86a77..b3922bb2bd45 100644 --- a/cpp/ql/lib/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/SimpleSSA.qll +++ b/cpp/ql/lib/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/SimpleSSA.qll @@ -73,6 +73,8 @@ class MemoryLocation extends TMemoryLocation { final predicate canReuseSsa() { canReuseSsaForVariable(var) } } +class MemoryLocation0 = MemoryLocation; + predicate canReuseSsaForOldResult(Instruction instr) { none() } abstract class VariableGroup extends Unit { From f1b4e05579f3cc497058902839b773395844af7d Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Mon, 12 May 2025 19:45:19 +0100 Subject: [PATCH 2/7] C++: Expose 'isBusyDef'. --- .../ir/implementation/aliased_ssa/internal/AliasedSSA.qll | 2 +- .../ir/implementation/unaliased_ssa/internal/SimpleSSA.qll | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/cpp/ql/lib/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasedSSA.qll b/cpp/ql/lib/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasedSSA.qll index b5937815dab5..522cd393081e 100644 --- a/cpp/ql/lib/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasedSSA.qll +++ b/cpp/ql/lib/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasedSSA.qll @@ -874,7 +874,7 @@ private int numberOfOverlappingUses(MemoryLocation0 def) { * Holds if `def` is a busy definition. That is, it has a large number of * overlapping uses. */ -private predicate isBusyDef(MemoryLocation0 def) { numberOfOverlappingUses(def) > 1024 } +predicate isBusyDef(MemoryLocation0 def) { numberOfOverlappingUses(def) > 1024 } /** Holds if `use` is a use that overlaps with a busy definition. */ private predicate useOverlapWithBusyDef(MemoryLocation0 use) { diff --git a/cpp/ql/lib/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/SimpleSSA.qll b/cpp/ql/lib/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/SimpleSSA.qll index b3922bb2bd45..39283d07b54c 100644 --- a/cpp/ql/lib/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/SimpleSSA.qll +++ b/cpp/ql/lib/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/SimpleSSA.qll @@ -143,3 +143,9 @@ int getStartBitOffset(MemoryLocation location) { none() } /** Gets the end bit offset of a `MemoryLocation`, if any. */ int getEndBitOffset(MemoryLocation location) { none() } + +/** + * Holds if `def` is a busy definition. That is, it has a large number of + * overlapping uses. + */ +predicate isBusyDef(MemoryLocation def) { none() } From 510df38da2476708271777dc17ad5bd52068f14c Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Mon, 12 May 2025 19:46:50 +0100 Subject: [PATCH 3/7] C++: Add an 'hasIncompleteSsa' predicate to check whether a function has correctly modelled SSA information. --- .../cpp/ir/implementation/aliased_ssa/IRFunction.qll | 8 ++++++++ .../aliased_ssa/internal/SSAConstruction.qll | 12 ++++++++++++ .../code/cpp/ir/implementation/raw/IRFunction.qll | 8 ++++++++ .../implementation/raw/internal/IRConstruction.qll | 2 ++ .../ir/implementation/unaliased_ssa/IRFunction.qll | 8 ++++++++ .../unaliased_ssa/internal/SSAConstruction.qll | 12 ++++++++++++ 6 files changed, 50 insertions(+) diff --git a/cpp/ql/lib/semmle/code/cpp/ir/implementation/aliased_ssa/IRFunction.qll b/cpp/ql/lib/semmle/code/cpp/ir/implementation/aliased_ssa/IRFunction.qll index 354ba41e3d1b..7fa08f57a006 100644 --- a/cpp/ql/lib/semmle/code/cpp/ir/implementation/aliased_ssa/IRFunction.qll +++ b/cpp/ql/lib/semmle/code/cpp/ir/implementation/aliased_ssa/IRFunction.qll @@ -58,4 +58,12 @@ class IRFunction extends IRFunctionBase { * Gets all blocks in this function. */ final IRBlock getABlock() { result.getEnclosingIRFunction() = this } + + /** + * Holds if this function may have incomplete def-use information. + * + * Def-use information may be omitted for a function when it is too expensive + * to compute. + */ + final predicate hasIncompleteSsa() { Construction::hasIncompleteSsa(this) } } diff --git a/cpp/ql/lib/semmle/code/cpp/ir/implementation/aliased_ssa/internal/SSAConstruction.qll b/cpp/ql/lib/semmle/code/cpp/ir/implementation/aliased_ssa/internal/SSAConstruction.qll index e0a6594e7400..d7df2d40abee 100644 --- a/cpp/ql/lib/semmle/code/cpp/ir/implementation/aliased_ssa/internal/SSAConstruction.qll +++ b/cpp/ql/lib/semmle/code/cpp/ir/implementation/aliased_ssa/internal/SSAConstruction.qll @@ -1319,6 +1319,18 @@ predicate canReuseSsaForMemoryResult(Instruction instruction) { // We don't support reusing SSA for any location that could create a `Chi` instruction. } +/** + * Holds if the def-use information for `f` may have been omitted because it + * was too expensive to compute. This happens if one of the memory allocations + * in `f` is a busy definition (i.e., it has many different overlapping uses). + */ +predicate hasIncompleteSsa(IRFunction f) { + exists(Alias::MemoryLocation0 defLocation | + Alias::isBusyDef(defLocation) and + defLocation.getIRFunction() = f + ) +} + /** * Expose some of the internal predicates to PrintSSA.qll. We do this by publicly importing those modules in the * `DebugSsa` module, which is then imported by PrintSSA. diff --git a/cpp/ql/lib/semmle/code/cpp/ir/implementation/raw/IRFunction.qll b/cpp/ql/lib/semmle/code/cpp/ir/implementation/raw/IRFunction.qll index 354ba41e3d1b..7fa08f57a006 100644 --- a/cpp/ql/lib/semmle/code/cpp/ir/implementation/raw/IRFunction.qll +++ b/cpp/ql/lib/semmle/code/cpp/ir/implementation/raw/IRFunction.qll @@ -58,4 +58,12 @@ class IRFunction extends IRFunctionBase { * Gets all blocks in this function. */ final IRBlock getABlock() { result.getEnclosingIRFunction() = this } + + /** + * Holds if this function may have incomplete def-use information. + * + * Def-use information may be omitted for a function when it is too expensive + * to compute. + */ + final predicate hasIncompleteSsa() { Construction::hasIncompleteSsa(this) } } diff --git a/cpp/ql/lib/semmle/code/cpp/ir/implementation/raw/internal/IRConstruction.qll b/cpp/ql/lib/semmle/code/cpp/ir/implementation/raw/internal/IRConstruction.qll index 5e2461ba8b7c..594e37b668d6 100644 --- a/cpp/ql/lib/semmle/code/cpp/ir/implementation/raw/internal/IRConstruction.qll +++ b/cpp/ql/lib/semmle/code/cpp/ir/implementation/raw/internal/IRConstruction.qll @@ -220,6 +220,8 @@ Instruction getMemoryOperandDefinition( none() } +predicate hasIncompleteSsa(IRFunction f) { none() } + /** * Holds if the operand totally overlaps with its definition and consumes the * bit range `[startBitOffset, endBitOffset)`. diff --git a/cpp/ql/lib/semmle/code/cpp/ir/implementation/unaliased_ssa/IRFunction.qll b/cpp/ql/lib/semmle/code/cpp/ir/implementation/unaliased_ssa/IRFunction.qll index 354ba41e3d1b..7fa08f57a006 100644 --- a/cpp/ql/lib/semmle/code/cpp/ir/implementation/unaliased_ssa/IRFunction.qll +++ b/cpp/ql/lib/semmle/code/cpp/ir/implementation/unaliased_ssa/IRFunction.qll @@ -58,4 +58,12 @@ class IRFunction extends IRFunctionBase { * Gets all blocks in this function. */ final IRBlock getABlock() { result.getEnclosingIRFunction() = this } + + /** + * Holds if this function may have incomplete def-use information. + * + * Def-use information may be omitted for a function when it is too expensive + * to compute. + */ + final predicate hasIncompleteSsa() { Construction::hasIncompleteSsa(this) } } diff --git a/cpp/ql/lib/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/SSAConstruction.qll b/cpp/ql/lib/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/SSAConstruction.qll index e0a6594e7400..d7df2d40abee 100644 --- a/cpp/ql/lib/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/SSAConstruction.qll +++ b/cpp/ql/lib/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/SSAConstruction.qll @@ -1319,6 +1319,18 @@ predicate canReuseSsaForMemoryResult(Instruction instruction) { // We don't support reusing SSA for any location that could create a `Chi` instruction. } +/** + * Holds if the def-use information for `f` may have been omitted because it + * was too expensive to compute. This happens if one of the memory allocations + * in `f` is a busy definition (i.e., it has many different overlapping uses). + */ +predicate hasIncompleteSsa(IRFunction f) { + exists(Alias::MemoryLocation0 defLocation | + Alias::isBusyDef(defLocation) and + defLocation.getIRFunction() = f + ) +} + /** * Expose some of the internal predicates to PrintSSA.qll. We do this by publicly importing those modules in the * `DebugSsa` module, which is then imported by PrintSSA. From 9d2eb3d9b83bba10b6f8c690513c8f0f8fe197a8 Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Mon, 12 May 2025 19:47:47 +0100 Subject: [PATCH 4/7] C++: Filter out instructions with incomplete SSA in range analysis. --- .../new/internal/semantic/SemanticExprSpecific.qll | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/cpp/ql/lib/semmle/code/cpp/rangeanalysis/new/internal/semantic/SemanticExprSpecific.qll b/cpp/ql/lib/semmle/code/cpp/rangeanalysis/new/internal/semantic/SemanticExprSpecific.qll index 1b36ae2efc5e..1b83ae959d2c 100644 --- a/cpp/ql/lib/semmle/code/cpp/rangeanalysis/new/internal/semantic/SemanticExprSpecific.qll +++ b/cpp/ql/lib/semmle/code/cpp/rangeanalysis/new/internal/semantic/SemanticExprSpecific.qll @@ -112,7 +112,14 @@ module SemanticExprConfig { } /** Holds if no range analysis should be performed on the phi edges in `f`. */ - private predicate excludeFunction(Cpp::Function f) { count(f.getEntryPoint()) > 1 } + private predicate excludeFunction(Cpp::Function f) { + count(f.getEntryPoint()) > 1 + or + exists(IR::IRFunction irFunction | + irFunction.getFunction() = f and + irFunction.hasIncompleteSsa() + ) + } SemType getUnknownExprType(Expr expr) { result = getSemanticType(expr.getResultIRType()) } From c3c18bdbd288a39fba09dcacda44d6cb4a8ced2a Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Tue, 13 May 2025 11:28:25 +0100 Subject: [PATCH 5/7] C++: Add change note. --- .../change-notes/2025-05-13-range-analysis-infinite-loop.md | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 cpp/ql/lib/change-notes/2025-05-13-range-analysis-infinite-loop.md diff --git a/cpp/ql/lib/change-notes/2025-05-13-range-analysis-infinite-loop.md b/cpp/ql/lib/change-notes/2025-05-13-range-analysis-infinite-loop.md new file mode 100644 index 000000000000..7452e024d53f --- /dev/null +++ b/cpp/ql/lib/change-notes/2025-05-13-range-analysis-infinite-loop.md @@ -0,0 +1,4 @@ +--- +category: fix +--- +* Fixed an infinite loop in `semmle.code.cpp.rangeanalysis.new.RangeAnalysis` when computing ranges in very large and complex function bodies. \ No newline at end of file From 0836f0b413126532825744034cf5e46a80604da6 Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Tue, 13 May 2025 13:41:15 +0100 Subject: [PATCH 6/7] C++: Cache and fix join order in 'hasIncompleteSsa'. --- .../aliased_ssa/internal/SSAConstruction.qll | 26 ++++++++++--------- .../internal/SSAConstruction.qll | 26 ++++++++++--------- 2 files changed, 28 insertions(+), 24 deletions(-) diff --git a/cpp/ql/lib/semmle/code/cpp/ir/implementation/aliased_ssa/internal/SSAConstruction.qll b/cpp/ql/lib/semmle/code/cpp/ir/implementation/aliased_ssa/internal/SSAConstruction.qll index d7df2d40abee..1a1eb6a1876f 100644 --- a/cpp/ql/lib/semmle/code/cpp/ir/implementation/aliased_ssa/internal/SSAConstruction.qll +++ b/cpp/ql/lib/semmle/code/cpp/ir/implementation/aliased_ssa/internal/SSAConstruction.qll @@ -731,6 +731,20 @@ private module Cached { or instruction = getChi(result.(UninitializedGroupInstruction)) } + + /** + * Holds if the def-use information for `f` may have been omitted because it + * was too expensive to compute. This happens if one of the memory allocations + * in `f` is a busy definition (i.e., it has many different overlapping uses). + */ + pragma[nomagic] + cached + predicate hasIncompleteSsa(IRFunction f) { + exists(Alias::MemoryLocation0 defLocation | + Alias::isBusyDef(pragma[only_bind_into](defLocation)) and + defLocation.getIRFunction() = f + ) + } } private Instruction getNewInstruction(OldInstruction instr) { getOldInstruction(result) = instr } @@ -1319,18 +1333,6 @@ predicate canReuseSsaForMemoryResult(Instruction instruction) { // We don't support reusing SSA for any location that could create a `Chi` instruction. } -/** - * Holds if the def-use information for `f` may have been omitted because it - * was too expensive to compute. This happens if one of the memory allocations - * in `f` is a busy definition (i.e., it has many different overlapping uses). - */ -predicate hasIncompleteSsa(IRFunction f) { - exists(Alias::MemoryLocation0 defLocation | - Alias::isBusyDef(defLocation) and - defLocation.getIRFunction() = f - ) -} - /** * Expose some of the internal predicates to PrintSSA.qll. We do this by publicly importing those modules in the * `DebugSsa` module, which is then imported by PrintSSA. diff --git a/cpp/ql/lib/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/SSAConstruction.qll b/cpp/ql/lib/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/SSAConstruction.qll index d7df2d40abee..1a1eb6a1876f 100644 --- a/cpp/ql/lib/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/SSAConstruction.qll +++ b/cpp/ql/lib/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/SSAConstruction.qll @@ -731,6 +731,20 @@ private module Cached { or instruction = getChi(result.(UninitializedGroupInstruction)) } + + /** + * Holds if the def-use information for `f` may have been omitted because it + * was too expensive to compute. This happens if one of the memory allocations + * in `f` is a busy definition (i.e., it has many different overlapping uses). + */ + pragma[nomagic] + cached + predicate hasIncompleteSsa(IRFunction f) { + exists(Alias::MemoryLocation0 defLocation | + Alias::isBusyDef(pragma[only_bind_into](defLocation)) and + defLocation.getIRFunction() = f + ) + } } private Instruction getNewInstruction(OldInstruction instr) { getOldInstruction(result) = instr } @@ -1319,18 +1333,6 @@ predicate canReuseSsaForMemoryResult(Instruction instruction) { // We don't support reusing SSA for any location that could create a `Chi` instruction. } -/** - * Holds if the def-use information for `f` may have been omitted because it - * was too expensive to compute. This happens if one of the memory allocations - * in `f` is a busy definition (i.e., it has many different overlapping uses). - */ -predicate hasIncompleteSsa(IRFunction f) { - exists(Alias::MemoryLocation0 defLocation | - Alias::isBusyDef(defLocation) and - defLocation.getIRFunction() = f - ) -} - /** * Expose some of the internal predicates to PrintSSA.qll. We do this by publicly importing those modules in the * `DebugSsa` module, which is then imported by PrintSSA. From f255fc2fd593dcd08e21062829384688690b297c Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Tue, 13 May 2025 14:21:28 +0100 Subject: [PATCH 7/7] C++: Drive-by join order fix. Before: ``` Evaluated relational algebra for predicate SsaInternals::getDefImpl/1#1ed4f567@65628fbv with tuple counts: 4935102 ~5% {4} r1 = SCAN `SsaInternals::SsaImpl::Definition.definesAt/3#dispred#7eea4c8f` OUTPUT In.2, In.3, In.0, In.1 104274503 ~1% {3} | JOIN WITH `SsaInternals::DefImpl.hasIndexInBlock/2#dispred#30a6c29f_120#join_rhs` ON FIRST 2 OUTPUT Rhs.2, Lhs.3, Lhs.2 4921319 ~2% {2} | JOIN WITH `SsaInternals::DefImpl.getSourceVariable/0#dispred#72437659` ON FIRST 2 OUTPUT Lhs.2, Lhs.0 return r1 ``` After: ``` Evaluated relational algebra for predicate SsaInternals::SsaImpl::Definition.definesAt/3#dispred#7eea4c8f_1230#join_rhs@b280fb5h with tuple counts: 4935102 ~3% {4} r1 = SCAN `SsaInternals::SsaImpl::Definition.definesAt/3#dispred#7eea4c8f` OUTPUT In.1, In.2, In.3, In.0 return r1 Evaluated relational algebra for predicate SsaInternals::DefImpl.hasIndexInBlock/3#dispred#31d295aa_1230#join_rhs@2be655s4 with tuple counts: 5634706 ~1% {4} r1 = SCAN `SsaInternals::DefImpl.hasIndexInBlock/3#dispred#31d295aa` OUTPUT In.1, In.2, In.3, In.0 return r1 Evaluated relational algebra for predicate SsaInternals::getDefImpl/1#1ed4f567@8afa36uu with tuple counts: 4921319 ~2% {2} r1 = JOIN `SsaInternals::SsaImpl::Definition.definesAt/3#dispred#7eea4c8f_1230#join_rhs` WITH `SsaInternals::DefImpl.hasIndexInBlock/3#dispred#31d295aa_1230#join_rhs` ON FIRST 3 OUTPUT Lhs.3, Rhs.3 return r1 ``` --- .../code/cpp/ir/dataflow/internal/DataFlowPrivate.qll | 4 ++-- .../code/cpp/ir/dataflow/internal/SsaInternals.qll | 9 +++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowPrivate.qll b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowPrivate.qll index 39975d8883c4..e517a75edf97 100644 --- a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowPrivate.qll +++ b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowPrivate.qll @@ -1567,7 +1567,7 @@ private int countNumberOfBranchesUsingParameter(SwitchInstruction switch, Parame | exists(Ssa::UseImpl use | use.hasIndexInBlock(useblock, _, sv)) or - exists(Ssa::DefImpl def | def.hasIndexInBlock(useblock, _, sv)) + exists(Ssa::DefImpl def | def.hasIndexInBlock(sv, useblock, _)) ) ) ) @@ -1814,7 +1814,7 @@ module IteratorFlow { */ private predicate isIteratorWrite(Instruction write, Operand address) { exists(Ssa::DefImpl writeDef, IRBlock bb, int i | - writeDef.hasIndexInBlock(bb, i, _) and + writeDef.hasIndexInBlock(_, bb, i) and bb.getInstruction(i) = write and address = writeDef.getAddressOperand() ) diff --git a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/SsaInternals.qll b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/SsaInternals.qll index 51829f13df51..bea6b68d5119 100644 --- a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/SsaInternals.qll +++ b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/SsaInternals.qll @@ -191,7 +191,7 @@ abstract class DefImpl extends TDefImpl { * Holds if this definition (or use) has index `index` in block `block`, * and is a definition (or use) of the variable `sv` */ - final predicate hasIndexInBlock(IRBlock block, int index, SourceVariable sv) { + final predicate hasIndexInBlock(SourceVariable sv, IRBlock block, int index) { this.hasIndexInBlock(block, index) and sv = this.getSourceVariable() } @@ -891,12 +891,12 @@ private module SsaInput implements SsaImplCommon::InputSig { predicate variableWrite(BasicBlock bb, int i, SourceVariable v, boolean certain) { DataFlowImplCommon::forceCachingInSameStage() and ( - exists(DefImpl def | def.hasIndexInBlock(bb, i, v) | + exists(DefImpl def | def.hasIndexInBlock(v, bb, i) | if def.isCertain() then certain = true else certain = false ) or exists(GlobalDefImpl global | - global.hasIndexInBlock(bb, i, v) and + global.hasIndexInBlock(v, bb, i) and certain = true ) ) @@ -934,10 +934,11 @@ module SsaCached { } /** Gets the `DefImpl` corresponding to `def`. */ +pragma[nomagic] private DefImpl getDefImpl(SsaImpl::Definition def) { exists(SourceVariable sv, IRBlock bb, int i | def.definesAt(sv, bb, i) and - result.hasIndexInBlock(bb, i, sv) + result.hasIndexInBlock(sv, bb, i) ) }