Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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
55 changes: 53 additions & 2 deletions cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/SsaImpl.qll
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,9 @@ private module SourceVariables {
NormalSourceVariable() { this = TNormalSourceVariable(base, ind) }

final override string toString() {
result = repeatStars(this.getIndirection()) + base.toString()
if ind = 0
then result = "&" + base.toString()
else result = repeatStars(this.getIndirection() - 1) + base.toString()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mixing direct field access ind with this.getIndirection() becomes very confusing to read. Same issue below.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in d93de54

}
}

Expand All @@ -157,7 +159,9 @@ private module SourceVariables {
}

final override string toString() {
result = repeatStars(this.getIndirection()) + base.toString() + " [before crement]"
if ind = 0
then result = "&" + base.toString() + " [before crement]"
else result = repeatStars(this.getIndirection() - 1) + base.toString() + " [before crement]"
}

/**
Expand Down Expand Up @@ -1353,6 +1357,53 @@ class PhiNode extends Definition instanceof SsaImpl::PhiNode {
final predicate hasInputFromBlock(Definition input, IRBlock bb) {
phiHasInputFromBlock(this, input, bb)
}

override int getIndirection() { result = this.getSourceVariable().getIndirection() }

override predicate isCertain() {
// If this phi node is part of a phi cycle of phi nodes the least
// fixed-point semantics of datalog means we don't get the right answer.
// So we perform an SCC reduction to simulate greated fixed-point semantics.
Comment thread
MathiasVP marked this conversation as resolved.
Outdated
getCycle(this).isCertain()
or
// If there is no cycle we get the right semantics through traditional
// recursion.
not exists(getCycle(this)) and
forex(Definition inp | inp = this.getAnInput() | inp.isCertain())
}

final override Declaration getFunction() {
result = SsaImpl::PhiNode.super.getBasicBlock().getEnclosingFunction()
}
}

private PhiNode getAnInput(PhiNode phi) { result = phi.getAnInput() }

private predicate definitionCycle(PhiNode phi) { getAnInput+(phi) = phi }

private predicate hasAnInput(PhiNode phi1, PhiNode phi2) {
definitionCycle(phi1) and
definitionCycle(phi2) and
getAnInput(phi1) = phi2
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is wrong. You're attempting to define SCC-internal edges here, but if two SCCs are adjacent then this will accidentally merge them. You need this instead:

private predicate sccEdge(PhiNode phi1, PhiNode phi2) {
  getAnInput(phi1) = phi2 and getAnInput+(phi2) = phi1
}

Copy link
Copy Markdown
Contributor Author

@MathiasVP MathiasVP May 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, yes I can see how these two definitions aren't equivalent and that yours captures what I intended to capture. Now I'm just trying to construct an example where the two definitions would cause a change in a test 🤔

If you have one at hand I'd love to know. Otherwise I'll spend some time trying to construct one, and if all else fail I'll just replace the code with your suggestion as it's obviously the right one

Copy link
Copy Markdown
Contributor

@aschackmull aschackmull May 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something like this perhaps:

while (..) { // phi_1
  if (..) x = ..
  // phi_2
}
while (..) { // phi_3
  if (..) x = ..
  // phi_4
}

Here phi_1 + phi_2 ought to form one SCC and phi_3 + phi_4 another, and your code would conflate them. I think if you make the assignment in the first loop certain, and the one in the second uncertain then you should end up with an observable difference.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That indeed did the trick. f77d426 adds the test and f5113b1 fixes it using your definition. Thanks!


private module PhiCycleEquivalence = QlBuiltins::EquivalenceRelation<PhiNode, hasAnInput/2>;

private PhiCycle getCycle(PhiNode phi) { result.getAPhiNode() = phi }

private class PhiCycle extends PhiCycleEquivalence::EquivalenceClass {
PhiNode getAPhiNode() { PhiCycleEquivalence::getEquivalenceClass(result) = this }

predicate hasPhiNode(PhiNode phi) { this.getAPhiNode() = phi }

string toString() { result = strictconcat(this.getAPhiNode().toString(), ", ") }

predicate isCertain() {
// A phi cycle is certain if all of the inputs into the phi cycle is certain.
Comment thread
MathiasVP marked this conversation as resolved.
forex(PhiNode phi | phi = this.getAPhiNode() |
forall(PhiNode inp | phi.getAnInput() = inp and not this.hasPhiNode(inp) | inp.isCertain())
)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Factor out the range to make the recursion simpler. Add a member predicate

pragma[nomagic]
Definition getAnInput() {
  result = this.getAPhiNode().getAnInput() and not this.hasPhiNode(result)
}

and use this to define the recursive forall:

forex(Definition inp | this.getAnInput() | inp.isCertain())

Note also that I've changed the type of inp from PhiNode to Definition - I believe that was also wrong in your version.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in c6ce13a

}
}

/** An static single assignment (SSA) definition. */
Expand Down
88 changes: 61 additions & 27 deletions cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/SsaImplCommon.qll
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ abstract class Indirection extends Type {
*
* `certain` is `true` if this write is guaranteed to write to the address.
Comment thread
MathiasVP marked this conversation as resolved.
*/
predicate isAdditionalWrite(Node0Impl value, Operand address, boolean certain) { none() }
predicate isAdditionalWrite(Node0Impl value, Operand address, Certainty certain) { none() }

/**
* Gets the base type of this indirection, after specifiers have been deeply
Expand Down Expand Up @@ -198,11 +198,11 @@ private module IteratorIndirections {
baseType = super.getValueType()
}

override predicate isAdditionalWrite(Node0Impl value, Operand address, boolean certain) {
override predicate isAdditionalWrite(Node0Impl value, Operand address, Certainty certain) {
exists(CallInstruction call | call.getArgumentOperand(0) = value.asOperand() |
this = call.getStaticCallTarget().(Function).getClassAndName("operator=") and
address = call.getThisArgumentOperand() and
certain = false
certain instanceof AlwaysUncertain
)
}

Expand Down Expand Up @@ -271,30 +271,62 @@ predicate isDereference(Instruction deref, Operand address, boolean additional)
additional = false
}

predicate isWrite(Node0Impl value, Operand address, boolean certain) {
private newtype TCertainty =
TCertainWhenAddressIsCertain() or
TAlwaysCertain() or
TAlwaysUncertain()

abstract private class Certainty extends TCertainty {
abstract predicate isCertain(boolean addressIsCertain);

abstract string toString();
}

private class CertainWhenAddressIsCertain extends Certainty, TCertainWhenAddressIsCertain {
override predicate isCertain(boolean addressIsCertain) { addressIsCertain = true }

override string toString() { result = "CertainWhenAddressIsCertain" }
}

private class AlwaysCertain extends Certainty, TAlwaysCertain {
override predicate isCertain(boolean addressIsCertain) {
addressIsCertain = true or addressIsCertain = false
}

override string toString() { result = "AlwaysCertain" }
}

private class AlwaysUncertain extends Certainty, TAlwaysUncertain {
override predicate isCertain(boolean addressIsCertain) { none() }

override string toString() { result = "AlwaysUncertain" }
}

predicate isWrite(Node0Impl value, Operand address, Certainty certain) {
any(Indirection ind).isAdditionalWrite(value, address, certain)
or
certain = true and
(
exists(StoreInstruction store |
value.asInstruction() = store and
address = store.getDestinationAddressOperand()
)
or
exists(InitializeParameterInstruction init |
value.asInstruction() = init and
address = init.getAnOperand()
)
or
exists(InitializeDynamicAllocationInstruction init |
value.asInstruction() = init and
address = init.getAllocationAddressOperand()
)
or
exists(UninitializedInstruction uninitialized |
value.asInstruction() = uninitialized and
address = uninitialized.getAnOperand()
)
exists(StoreInstruction store |
value.asInstruction() = store and
address = store.getDestinationAddressOperand() and
certain instanceof CertainWhenAddressIsCertain
)
or
exists(InitializeParameterInstruction init |
value.asInstruction() = init and
address = init.getAnOperand() and
certain instanceof AlwaysCertain
)
or
exists(InitializeDynamicAllocationInstruction init |
value.asInstruction() = init and
address = init.getAllocationAddressOperand() and
certain instanceof AlwaysCertain
)
or
exists(UninitializedInstruction uninitialized |
value.asInstruction() = uninitialized and
address = uninitialized.getAnOperand() and
certain instanceof AlwaysCertain
)
}

Expand Down Expand Up @@ -718,16 +750,18 @@ private module Cached {
int indirectionIndex
) {
exists(
boolean writeIsCertain, boolean addressIsCertain, int ind0, CppType type, int lower, int upper
Certainty writeIsCertain, boolean addressIsCertain, int ind0, CppType type, int lower,
int upper
|
isWrite(value, address, writeIsCertain) and
isDefImpl(address, base, ind0, addressIsCertain) and
certain = writeIsCertain.booleanAnd(addressIsCertain) and
type = getLanguageType(address) and
upper = countIndirectionsForCppType(type) and
ind = ind0 + [lower .. upper] and
indirectionIndex = ind - (ind0 + lower) and
lower = getMinIndirectionsForType(any(Type t | type.hasUnspecifiedType(t, _)))
|
if writeIsCertain.isCertain(addressIsCertain) then certain = true else certain = false
)
}

Expand Down
64 changes: 64 additions & 0 deletions cpp/ql/test/library-tests/dataflow/certain/test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
void use(...);

void test1() {
int x = 0; // $ certain="SSA def(&x)" certain="SSA def(x)"
use(x);

x = 1; // $ certain="SSA def(x)"
use(x);

int* p = &x; // $ certain="SSA def(&p)" certain="SSA def(p)" certain="SSA def(*p)"
use(p);

*p = 2; // $ certain="SSA def(*p)"
use(p);

p = nullptr; // $ certain="SSA def(p)" certain="SSA def(*p)"
use(p);

*p = 2; // $ uncertain="SSA def(*p)"
use(p);
}

void test2(bool b) { // $ certain="SSA def(&b)" certain="SSA def(b)"
{
int x; // $ certain="SSA def(&x)"
if(b) {
x = 0; // $ certain="SSA def(x)"
} else {
x = 1; // $ certain="SSA def(x)"
}
use(x); // $ certain="SSA phi(x)"
}

{
int x; // $ certain="SSA def(&x)" certain="SSA def(x)"
if(b) {
x = 0; // $ certain="SSA def(x)"
} else {

}
use(x); // $ certain="SSA phi(x)"
}

{
int x; // $ certain="SSA def(&x)" certain="SSA def(x)"
int* p = &x; // $ certain="SSA def(&p)" certain="SSA def(p)" certain="SSA def(*p)"
if(b) {
*p = 0; // $ certain="SSA def(*p)"
} else {
*(p + 1) = 1; // $ uncertain="SSA def(*p)"
}
use(p); // $ uncertain="SSA phi(*p)"
}

}

void test3(bool b) { // $ certain="SSA def(&b)" certain="SSA def(b)"
for(int i = 0; i < 10;) { // $ certain="SSA def(&i)" certain="SSA def(i)" certain="SSA phi(i)"
if(b) {
++i; // $ certain="SSA def(i)"
}
use(i); // $ certain="SSA phi(i)"
}
}
Empty file.
22 changes: 22 additions & 0 deletions cpp/ql/test/library-tests/dataflow/certain/test.ql
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import cpp
import utils.test.InlineExpectationsTest
import semmle.code.cpp.dataflow.new.DataFlow::DataFlow

bindingset[s]
string quote(string s) { if s.matches("% %") then result = "\"" + s + "\"" else result = s }

module AsDefinitionTest implements TestSig {
string getARelevantTag() { result = ["certain", "uncertain"] }

predicate hasActualResult(Location location, string element, string tag, string value) {
exists(Ssa::Definition d |
location = d.getLocation() and
element = d.toString() and
value = quote(d.toString())
|
if d.isCertain() then tag = "certain" else tag = "uncertain"
)
}
}

import MakeTest<AsDefinitionTest>
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ postWithInFlow
| test.cpp:1153:5:1153:6 | * ... [post update] | PostUpdateNode should not be the target of local flow. |
| test.cpp:1165:5:1165:6 | * ... [post update] | PostUpdateNode should not be the target of local flow. |
| test.cpp:1195:5:1195:6 | * ... [post update] | PostUpdateNode should not be the target of local flow. |
| test.cpp:1337:5:1337:13 | access to array [post update] | PostUpdateNode should not be the target of local flow. |
viableImplInCallContextTooLarge
uniqueParameterNodeAtPosition
uniqueParameterNodePosition
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,52 +65,52 @@
| test.cpp:8:8:8:9 | t1 | test.cpp:9:8:9:9 | t1 |
| test.cpp:9:8:9:9 | t1 | test.cpp:11:7:11:8 | t1 |
| test.cpp:9:8:9:9 | t1 | test.cpp:11:7:11:8 | t1 |
| test.cpp:10:8:10:9 | t2 | test.cpp:11:7:11:8 | [input] SSA phi read(t2) |
| test.cpp:10:8:10:9 | t2 | test.cpp:11:7:11:8 | [input] SSA phi(*t2) |
| test.cpp:10:8:10:9 | t2 | test.cpp:11:7:11:8 | [input] SSA phi read(&t2) |
| test.cpp:10:8:10:9 | t2 | test.cpp:11:7:11:8 | [input] SSA phi(t2) |
| test.cpp:10:8:10:9 | t2 | test.cpp:13:10:13:11 | t2 |
| test.cpp:11:7:11:8 | [input] SSA phi read(t2) | test.cpp:15:8:15:9 | t2 |
| test.cpp:11:7:11:8 | [input] SSA phi(*t2) | test.cpp:15:8:15:9 | t2 |
| test.cpp:11:7:11:8 | [input] SSA phi read(&t2) | test.cpp:15:8:15:9 | t2 |
| test.cpp:11:7:11:8 | [input] SSA phi(t2) | test.cpp:15:8:15:9 | t2 |
| test.cpp:11:7:11:8 | t1 | test.cpp:21:8:21:9 | t1 |
| test.cpp:12:5:12:10 | ... = ... | test.cpp:13:10:13:11 | t2 |
| test.cpp:12:10:12:10 | 0 | test.cpp:12:5:12:10 | ... = ... |
| test.cpp:13:10:13:11 | t2 | test.cpp:15:8:15:9 | t2 |
| test.cpp:13:10:13:11 | t2 | test.cpp:15:8:15:9 | t2 |
| test.cpp:15:8:15:9 | t2 | test.cpp:23:15:23:16 | [input] SSA phi read(*t2) |
| test.cpp:15:8:15:9 | t2 | test.cpp:23:15:23:16 | [input] SSA phi read(&t2) |
| test.cpp:15:8:15:9 | t2 | test.cpp:23:15:23:16 | [input] SSA phi read(t2) |
| test.cpp:17:3:17:8 | ... = ... | test.cpp:21:8:21:9 | t1 |
| test.cpp:17:8:17:8 | 0 | test.cpp:17:3:17:8 | ... = ... |
| test.cpp:21:8:21:9 | t1 | test.cpp:23:19:23:19 | SSA phi read(t1) |
| test.cpp:21:8:21:9 | t1 | test.cpp:23:19:23:19 | SSA phi(*t1) |
| test.cpp:21:8:21:9 | t1 | test.cpp:23:19:23:19 | SSA phi read(&t1) |
| test.cpp:21:8:21:9 | t1 | test.cpp:23:19:23:19 | SSA phi(t1) |
| test.cpp:23:15:23:16 | 0 | test.cpp:23:15:23:16 | 0 |
| test.cpp:23:15:23:16 | 0 | test.cpp:23:19:23:19 | SSA phi(*i) |
| test.cpp:23:15:23:16 | [input] SSA phi read(*t2) | test.cpp:23:19:23:19 | SSA phi read(*t2) |
| test.cpp:23:15:23:16 | 0 | test.cpp:23:19:23:19 | SSA phi(i) |
| test.cpp:23:15:23:16 | [input] SSA phi read(&t2) | test.cpp:23:19:23:19 | SSA phi read(&t2) |
| test.cpp:23:15:23:16 | [input] SSA phi read(t2) | test.cpp:23:19:23:19 | SSA phi read(t2) |
| test.cpp:23:19:23:19 | SSA phi read(*t2) | test.cpp:24:10:24:11 | t2 |
| test.cpp:23:19:23:19 | SSA phi read(i) | test.cpp:23:19:23:19 | i |
| test.cpp:23:19:23:19 | SSA phi read(t1) | test.cpp:23:23:23:24 | t1 |
| test.cpp:23:19:23:19 | SSA phi read(&i) | test.cpp:23:19:23:19 | i |
| test.cpp:23:19:23:19 | SSA phi read(&t1) | test.cpp:23:23:23:24 | t1 |
| test.cpp:23:19:23:19 | SSA phi read(&t2) | test.cpp:24:10:24:11 | t2 |
| test.cpp:23:19:23:19 | SSA phi read(t2) | test.cpp:24:10:24:11 | t2 |
| test.cpp:23:19:23:19 | SSA phi(*i) | test.cpp:23:19:23:19 | i |
| test.cpp:23:19:23:19 | SSA phi(*t1) | test.cpp:23:23:23:24 | t1 |
| test.cpp:23:19:23:19 | SSA phi(i) | test.cpp:23:19:23:19 | i |
| test.cpp:23:19:23:19 | SSA phi(t1) | test.cpp:23:23:23:24 | t1 |
| test.cpp:23:19:23:19 | i | test.cpp:23:27:23:27 | i |
| test.cpp:23:19:23:19 | i | test.cpp:23:27:23:27 | i |
| test.cpp:23:23:23:24 | t1 | test.cpp:23:27:23:29 | [input] SSA phi read(t1) |
| test.cpp:23:23:23:24 | t1 | test.cpp:23:27:23:29 | [input] SSA phi read(&t1) |
| test.cpp:23:23:23:24 | t1 | test.cpp:26:8:26:9 | t1 |
| test.cpp:23:23:23:24 | t1 | test.cpp:26:8:26:9 | t1 |
| test.cpp:23:27:23:27 | *i | test.cpp:23:27:23:27 | *i |
| test.cpp:23:27:23:27 | *i | test.cpp:23:27:23:27 | i |
| test.cpp:23:27:23:27 | i | test.cpp:23:27:23:27 | i |
| test.cpp:23:27:23:27 | i | test.cpp:23:27:23:27 | i |
| test.cpp:23:27:23:27 | i | test.cpp:23:27:23:29 | [input] SSA phi read(i) |
| test.cpp:23:27:23:27 | i | test.cpp:23:27:23:29 | [input] SSA phi read(&i) |
| test.cpp:23:27:23:29 | ... ++ | test.cpp:23:27:23:29 | ... ++ |
| test.cpp:23:27:23:29 | ... ++ | test.cpp:23:27:23:29 | [input] SSA phi(*i) |
| test.cpp:23:27:23:29 | [input] SSA phi read(*t2) | test.cpp:23:19:23:19 | SSA phi read(*t2) |
| test.cpp:23:27:23:29 | [input] SSA phi read(i) | test.cpp:23:19:23:19 | SSA phi read(i) |
| test.cpp:23:27:23:29 | [input] SSA phi read(t1) | test.cpp:23:19:23:19 | SSA phi read(t1) |
| test.cpp:23:27:23:29 | ... ++ | test.cpp:23:27:23:29 | [input] SSA phi(i) |
| test.cpp:23:27:23:29 | [input] SSA phi read(&i) | test.cpp:23:19:23:19 | SSA phi read(&i) |
| test.cpp:23:27:23:29 | [input] SSA phi read(&t1) | test.cpp:23:19:23:19 | SSA phi read(&t1) |
| test.cpp:23:27:23:29 | [input] SSA phi read(&t2) | test.cpp:23:19:23:19 | SSA phi read(&t2) |
| test.cpp:23:27:23:29 | [input] SSA phi read(t2) | test.cpp:23:19:23:19 | SSA phi read(t2) |
| test.cpp:23:27:23:29 | [input] SSA phi(*i) | test.cpp:23:19:23:19 | SSA phi(*i) |
| test.cpp:23:27:23:29 | [input] SSA phi(*t1) | test.cpp:23:19:23:19 | SSA phi(*t1) |
| test.cpp:24:5:24:11 | ... = ... | test.cpp:23:27:23:29 | [input] SSA phi(*t1) |
| test.cpp:24:10:24:11 | t2 | test.cpp:23:27:23:29 | [input] SSA phi read(*t2) |
| test.cpp:23:27:23:29 | [input] SSA phi(i) | test.cpp:23:19:23:19 | SSA phi(i) |
| test.cpp:23:27:23:29 | [input] SSA phi(t1) | test.cpp:23:19:23:19 | SSA phi(t1) |
| test.cpp:24:5:24:11 | ... = ... | test.cpp:23:27:23:29 | [input] SSA phi(t1) |
| test.cpp:24:10:24:11 | t2 | test.cpp:23:27:23:29 | [input] SSA phi read(&t2) |
| test.cpp:24:10:24:11 | t2 | test.cpp:23:27:23:29 | [input] SSA phi read(t2) |
| test.cpp:24:10:24:11 | t2 | test.cpp:24:5:24:11 | ... = ... |
| test.cpp:382:48:382:54 | source1 | test.cpp:384:16:384:23 | *& ... |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ astFlow
| test.cpp:1312:7:1312:12 | call to source | test.cpp:1313:8:1313:24 | ... ? ... : ... |
| test.cpp:1312:7:1312:12 | call to source | test.cpp:1314:8:1314:8 | x |
| test.cpp:1329:11:1329:16 | call to source | test.cpp:1330:10:1330:10 | i |
| test.cpp:1335:10:1335:15 | buffer | test.cpp:1336:10:1336:18 | access to array |
| true_upon_entry.cpp:17:11:17:16 | call to source | true_upon_entry.cpp:21:8:21:8 | x |
| true_upon_entry.cpp:27:9:27:14 | call to source | true_upon_entry.cpp:29:8:29:8 | x |
| true_upon_entry.cpp:33:11:33:16 | call to source | true_upon_entry.cpp:39:8:39:8 | x |
Expand Down
8 changes: 8 additions & 0 deletions cpp/ql/test/library-tests/dataflow/dataflow-tests/test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1329,3 +1329,11 @@ void nsdmi_test() {
nsdmi y(source());
sink(y.i); // $ ir ast
}

void certain_def_uninitialized_instruction_test() {
for(int i = 0; i < 10; i++) {
char buffer[10];
sink(buffer[0]); // $ SPURIOUS: ast
buffer[0] = source();
}
}
Loading