Skip to content

Commit 1428811

Browse files
author
Dave Bartolomeo
committed
C++: IR translation for binary conditional operator
IR generation was not handling the special two-operand flavor of the `?:` operator that GCC supports as an extension. The extractor doesn't quite give us enough information to do this correctly (see github/codeql-c-extractor-team#67), but we can get pretty close. About half of the code could be shared between the two-operand and three-operand flavors. The main differences for the two-operand flavor are: 1. The "then" operand isn't a child of the `ConditionalExpr`. Instead, we just reuse the original value of the "condition" operand, skipping any implicit cast to `bool` (see comment for rationale). 2. For the three-operand flavor, we generate the condition as control flow rather than the computation of a `bool` value, to avoid creating unnecessarily complicated branching. For the two-operand version, we just compute the value, since we have to reuse that value in the "then" branch anyway. I've added IR tests for these new cases. I've also updated the expectations for `SignAnalysis.ql` based on the fix. @rdmarsh2, can you please double-check that these diffs look correct? I believe they do, but you're the range/sign analysis expert.
1 parent 875daae commit 1428811

File tree

5 files changed

+655
-36
lines changed

5 files changed

+655
-36
lines changed

cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/TranslatedElement.qll

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,11 @@ private predicate usedAsCondition(Expr expr) {
200200
or
201201
exists(IfStmt ifStmt | ifStmt.getCondition().getFullyConverted() = expr)
202202
or
203-
exists(ConditionalExpr condExpr | condExpr.getCondition().getFullyConverted() = expr)
203+
exists(ConditionalExpr condExpr |
204+
// The two-operand form of `ConditionalExpr` treats its condition as a value, since it needs to
205+
// be reused as a value if the condition is true.
206+
condExpr.getCondition().getFullyConverted() = expr and not condExpr.isTwoOperand()
207+
)
204208
or
205209
exists(NotExpr notExpr |
206210
notExpr.getOperand().getFullyConverted() = expr and

cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/TranslatedExpr.qll

Lines changed: 130 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1758,19 +1758,14 @@ class TranslatedDestructorFieldDestruction extends TranslatedNonConstantExpr, St
17581758
private TranslatedExpr getDestructorCall() { result = getTranslatedExpr(expr.getExpr()) }
17591759
}
17601760

1761-
class TranslatedConditionalExpr extends TranslatedNonConstantExpr, ConditionContext {
1761+
/**
1762+
* The IR translation of the `?:` operator. This class has the portions of the implementation that
1763+
* are shared between the standard three-operand form (`a ? b : c`) and the GCC-extension
1764+
* two-operand form (`a ?: c`).
1765+
*/
1766+
abstract class TranslatedConditionalExpr extends TranslatedNonConstantExpr {
17621767
override ConditionalExpr expr;
17631768

1764-
final override TranslatedElement getChild(int id) {
1765-
id = 0 and result = getCondition()
1766-
or
1767-
id = 1 and result = getThen()
1768-
or
1769-
id = 2 and result = getElse()
1770-
}
1771-
1772-
override Instruction getFirstInstruction() { result = getCondition().getFirstInstruction() }
1773-
17741769
override predicate hasInstruction(Opcode opcode, InstructionTag tag, CppType resultType) {
17751770
not resultIsVoid() and
17761771
(
@@ -1866,13 +1861,13 @@ class TranslatedConditionalExpr extends TranslatedNonConstantExpr, ConditionCont
18661861
)
18671862
}
18681863

1869-
override predicate hasTempVariable(TempVariableTag tag, CppType type) {
1864+
final override predicate hasTempVariable(TempVariableTag tag, CppType type) {
18701865
not resultIsVoid() and
18711866
tag = ConditionValueTempVar() and
18721867
type = getResultType()
18731868
}
18741869

1875-
override IRVariable getInstructionVariable(InstructionTag tag) {
1870+
final override IRVariable getInstructionVariable(InstructionTag tag) {
18761871
not resultIsVoid() and
18771872
(
18781873
tag = ConditionValueTrueTempAddressTag() or
@@ -1882,25 +1877,75 @@ class TranslatedConditionalExpr extends TranslatedNonConstantExpr, ConditionCont
18821877
result = getTempVariable(ConditionValueTempVar())
18831878
}
18841879

1885-
override Instruction getResult() {
1880+
final override Instruction getResult() {
18861881
not resultIsVoid() and
18871882
result = getInstruction(ConditionValueResultLoadTag())
18881883
}
18891884

18901885
override Instruction getChildSuccessor(TranslatedElement child) {
1886+
child = getElse() and
1887+
if elseIsVoid()
1888+
then result = getParent().getChildSuccessor(this)
1889+
else result = getInstruction(ConditionValueFalseTempAddressTag())
1890+
}
1891+
1892+
/**
1893+
* Gets the `TranslatedExpr` for the "then" result. Note that nothing in the base implementation
1894+
* of this class assumes that `getThen()` is disjoint from `getCondition()`.
1895+
*/
1896+
abstract TranslatedExpr getThen();
1897+
1898+
/**
1899+
* Gets the `TranslatedExpr` for the "else" result.
1900+
*/
1901+
final TranslatedExpr getElse() { result = getTranslatedExpr(expr.getElse().getFullyConverted()) }
1902+
1903+
final predicate thenIsVoid() {
1904+
getThen().getResultType().getIRType() instanceof IRVoidType
1905+
or
1906+
// A `ThrowExpr.getType()` incorrectly returns the type of exception being
1907+
// thrown, rather than `void`. Handle that case here.
1908+
expr.getThen() instanceof ThrowExpr
1909+
}
1910+
1911+
private predicate elseIsVoid() {
1912+
getElse().getResultType().getIRType() instanceof IRVoidType
1913+
or
1914+
// A `ThrowExpr.getType()` incorrectly returns the type of exception being
1915+
// thrown, rather than `void`. Handle that case here.
1916+
expr.getElse() instanceof ThrowExpr
1917+
}
1918+
1919+
private predicate resultIsVoid() { getResultType().getIRType() instanceof IRVoidType }
1920+
}
1921+
1922+
/**
1923+
* The IR translation of the ternary conditional operator (`a ? b : c`).
1924+
* For this version, we expand the condition as a `TranslatedCondition`, rather than a
1925+
* `TranslatedExpr`, to simplify the control flow in the presence of short-ciruit logical operators.
1926+
*/
1927+
class TranslatedTernaryConditionalExpr extends TranslatedConditionalExpr, ConditionContext {
1928+
TranslatedTernaryConditionalExpr() { not expr.isTwoOperand() }
1929+
1930+
final override TranslatedElement getChild(int id) {
1931+
id = 0 and result = getCondition()
1932+
or
1933+
id = 1 and result = getThen()
1934+
or
1935+
id = 2 and result = getElse()
1936+
}
1937+
1938+
override Instruction getFirstInstruction() { result = getCondition().getFirstInstruction() }
1939+
1940+
override Instruction getChildSuccessor(TranslatedElement child) {
1941+
result = TranslatedConditionalExpr.super.getChildSuccessor(child)
1942+
or
18911943
(
18921944
child = getThen() and
18931945
if thenIsVoid()
18941946
then result = getParent().getChildSuccessor(this)
18951947
else result = getInstruction(ConditionValueTrueTempAddressTag())
18961948
)
1897-
or
1898-
(
1899-
child = getElse() and
1900-
if elseIsVoid()
1901-
then result = getParent().getChildSuccessor(this)
1902-
else result = getInstruction(ConditionValueFalseTempAddressTag())
1903-
)
19041949
}
19051950

19061951
override Instruction getChildTrueSuccessor(TranslatedCondition child) {
@@ -1917,31 +1962,81 @@ class TranslatedConditionalExpr extends TranslatedNonConstantExpr, ConditionCont
19171962
result = getTranslatedCondition(expr.getCondition().getFullyConverted())
19181963
}
19191964

1920-
private TranslatedExpr getThen() {
1965+
final override TranslatedExpr getThen() {
19211966
result = getTranslatedExpr(expr.getThen().getFullyConverted())
19221967
}
1968+
}
1969+
1970+
/**
1971+
* The IR translation of a two-operand conditional operator (`a ?: b`). This is a GCC language
1972+
* extension.
1973+
* This version of the conditional expression returns its first operand (the condition) if that
1974+
* condition is non-zero. Since we'll be reusing the value of the condition, we'll compute that
1975+
* value directly before branching, even if that value was a short-circuit logical expression.
1976+
*/
1977+
class TranslatedBinaryConditionalExpr extends TranslatedConditionalExpr {
1978+
TranslatedBinaryConditionalExpr() { expr.isTwoOperand() }
19231979

1924-
private TranslatedExpr getElse() {
1925-
result = getTranslatedExpr(expr.getElse().getFullyConverted())
1980+
final override TranslatedElement getChild(int id) {
1981+
// We only truly have two children, because our "condition" and "then" are the same as far as
1982+
// the extractor is concerned.
1983+
id = 0 and result = getCondition()
1984+
or
1985+
id = 1 and result = getElse()
19261986
}
19271987

1928-
private predicate thenIsVoid() {
1929-
getThen().getResultType().getIRType() instanceof IRVoidType
1988+
override Instruction getFirstInstruction() { result = getCondition().getFirstInstruction() }
1989+
1990+
override predicate hasInstruction(Opcode opcode, InstructionTag tag, CppType resultType) {
1991+
super.hasInstruction(opcode, tag, resultType)
19301992
or
1931-
// A `ThrowExpr.getType()` incorrectly returns the type of exception being
1932-
// thrown, rather than `void`. Handle that case here.
1933-
expr.getThen() instanceof ThrowExpr
1993+
// For the binary variant, we create our own conditional branch.
1994+
tag = ValueConditionConditionalBranchTag() and
1995+
opcode instanceof Opcode::ConditionalBranch and
1996+
resultType = getVoidType()
19341997
}
19351998

1936-
private predicate elseIsVoid() {
1937-
getElse().getResultType().getIRType() instanceof IRVoidType
1999+
override Instruction getInstructionSuccessor(InstructionTag tag, EdgeKind kind) {
2000+
result = super.getInstructionSuccessor(tag, kind)
19382001
or
1939-
// A `ThrowExpr.getType()` incorrectly returns the type of exception being
1940-
// thrown, rather than `void`. Handle that case here.
1941-
expr.getElse() instanceof ThrowExpr
2002+
tag = ValueConditionConditionalBranchTag() and
2003+
(
2004+
kind instanceof TrueEdge and
2005+
result = getInstruction(ConditionValueTrueTempAddressTag())
2006+
or
2007+
kind instanceof FalseEdge and
2008+
result = getElse().getFirstInstruction()
2009+
)
19422010
}
19432011

1944-
private predicate resultIsVoid() { getResultType().getIRType() instanceof IRVoidType }
2012+
override Instruction getInstructionOperand(InstructionTag tag, OperandTag operandTag) {
2013+
result = super.getInstructionOperand(tag, operandTag)
2014+
or
2015+
tag = ValueConditionConditionalBranchTag() and
2016+
operandTag instanceof ConditionOperandTag and
2017+
result = getCondition().getResult()
2018+
}
2019+
2020+
override Instruction getChildSuccessor(TranslatedElement child) {
2021+
result = super.getChildSuccessor(child)
2022+
or
2023+
child = getCondition() and result = getInstruction(ValueConditionConditionalBranchTag())
2024+
}
2025+
2026+
private TranslatedExpr getCondition() {
2027+
result = getTranslatedExpr(expr.getCondition().getFullyConverted())
2028+
}
2029+
2030+
final override TranslatedExpr getThen() {
2031+
// The extractor returns the exact same expression for `ConditionalExpr::getCondition()` and
2032+
// `ConditionalExpr::getThen()`, even though the condition may have been converted to `bool`,
2033+
// and the "then" may have been converted to the result type. We'll strip the top-level implicit
2034+
// conversions from this, to skip any conversion to `bool`. We don't have enough information to
2035+
// know how to convert the result to the destination type, especially in the class pointer case,
2036+
// so we'll still sometimes wind up with one operand as the wrong type. This is better than
2037+
// always converting the "then" operand to `bool`, which is almost always the wrong type.
2038+
result = getTranslatedExpr(expr.getThen().getExplicitlyConverted())
2039+
}
19452040
}
19462041

19472042
/**

0 commit comments

Comments
 (0)