C++: Handle casts to void in IR

Casts to `void` did not have a semantic conversion type in the AST, so they also weren't getting generated correctly in the IR. I've added a `VoidConversion` class to the AST, along with tests. I've also added IR translation for such conversions, using a new `ConvertToVoid` opcode. I'm not sure if it's really necessary to generate an instruction to represent this, but it may be useful for detecting values that are explicitly unused (e.g. return value from a call).

I added two new sanity queries for the IR to detect the following:
- IR blocks with no successors, which usually indicates bad IR translation
- Phi instruction without an operand for one of the predecessor blocks.

These sanity queries found another subtle IR translation bug. If an expression that is normally translated as a condition (e.g. `&&`, `||`, or parens in certain contexts) has a constant value, we were not creating a `TranslatedExpr` for the expression at all. I changed it to always treat a constant condition as a non-condition expression.
This commit is contained in:
Dave Bartolomeo
2018-08-14 17:45:37 -07:00
parent bea298fcab
commit f4a060099b
18 changed files with 362 additions and 2 deletions

View File

@@ -292,6 +292,20 @@ class BoolConversion extends Cast {
}
}
/**
* A conversion to `void`.
*/
class VoidConversion extends Cast {
VoidConversion() {
conversionkinds(this, 0) and
getType().getUnspecifiedType() instanceof VoidType
}
override string getSemanticConversionString() {
result = "conversion to void"
}
}
/**
* A conversion between two pointers or glvalues related by inheritance. The
* base class will always be either a direct base class of the derived class,

View File

@@ -67,6 +67,31 @@ module InstructionSanity {
not tag instanceof UnmodeledUseOperand
}
/**
* Holds if `Phi` instruction `instr` has fewer than two operands.
*/
query predicate missingPhiOperands(PhiInstruction instr, int predIndex, Location predLoc) {
exists(IRBlock pred |
pred = instr.getBlock().getAPredecessor() and
predLoc = pred.getLocation() and
predIndex = pred.getDisplayIndex() and
not exists(PhiOperand operand |
exists(instr.getOperand(operand)) and
operand.getPredecessorBlock() = pred
)
)
}
/**
* Holds if an instruction, other than `ExitFunction`, has no successors.
*/
query predicate instructionWithoutSuccessor(Instruction instr) {
not exists(instr.getASuccessor()) and
not instr instanceof ExitFunctionInstruction and
// Phi instructions aren't linked into the instruction-level flow graph.
not instr instanceof PhiInstruction
}
query predicate operandAcrossFunctions(
Instruction op, Instruction operand, OperandTag tag
) {

View File

@@ -33,6 +33,7 @@ private newtype TOpcode =
TPointerSub() or
TPointerDiff() or
TConvert() or
TConvertToVoid() or
TConvertToBase() or
TConvertToVirtualBase() or
TConvertToDerived() or
@@ -125,6 +126,7 @@ module Opcode {
class PointerSub extends PointerOffsetOpcode, TPointerSub { override final string toString() { result = "PointerSub" } }
class PointerDiff extends PointerArithmeticOpcode, TPointerDiff { override final string toString() { result = "PointerDiff" } }
class Convert extends UnaryOpcode, TConvert { override final string toString() { result = "Convert" } }
class ConvertToVoid extends UnaryOpcode, TConvertToVoid { override final string toString() { result = "ConvertToVoid" } }
class ConvertToBase extends UnaryOpcode, TConvertToBase { override final string toString() { result = "ConvertToBase" } }
class ConvertToVirtualBase extends UnaryOpcode, TConvertToVirtualBase { override final string toString() { result = "ConvertToVirtualBase" } }
class ConvertToDerived extends UnaryOpcode, TConvertToDerived { override final string toString() { result = "ConvertToDerived" } }

View File

@@ -89,7 +89,8 @@ private predicate ignoreElement(Element element) {
* a value.
*/
private predicate isNativeCondition(Expr expr) {
expr instanceof BinaryLogicalOperation
expr instanceof BinaryLogicalOperation and
not expr.isConstant()
}
/**
@@ -101,7 +102,8 @@ private predicate isFlexibleCondition(Expr expr) {
expr instanceof ParenthesisExpr or
expr instanceof NotExpr
) and
usedAsCondition(expr)
usedAsCondition(expr) and
not expr.isConstant()
}
/**

View File

@@ -1126,6 +1126,19 @@ abstract class TranslatedSingleInstructionConversion extends TranslatedConversio
abstract Opcode getOpcode();
}
/**
* The translation of an explicit cast to `void`.
*/
class TranslatedVoidConversion extends TranslatedSingleInstructionConversion {
TranslatedVoidConversion() {
conv instanceof VoidConversion
}
override Opcode getOpcode() {
result instanceof Opcode::ConvertToVoid
}
}
/**
* Represents the translation of a conversion expression that generates a
* `Convert` instruction.

View File

@@ -67,6 +67,31 @@ module InstructionSanity {
not tag instanceof UnmodeledUseOperand
}
/**
* Holds if `Phi` instruction `instr` has fewer than two operands.
*/
query predicate missingPhiOperand(PhiInstruction instr, int predIndex, Location predLoc) {
exists(IRBlock pred |
pred = instr.getBlock().getAPredecessor() and
predLoc = pred.getLocation() and
predIndex = pred.getDisplayIndex() and
not exists(PhiOperand operand |
exists(instr.getOperand(operand)) and
operand.getPredecessorBlock() = pred
)
)
}
/**
* Holds if an instruction, other than `ExitFunction`, has no successors.
*/
query predicate instructionWithoutSuccessor(Instruction instr) {
not exists(instr.getASuccessor()) and
not instr instanceof ExitFunctionInstruction and
// Phi instructions aren't linked into the instruction-level flow graph.
not instr instanceof PhiInstruction
}
query predicate operandAcrossFunctions(
Instruction op, Instruction operand, OperandTag tag
) {

View File

@@ -67,6 +67,31 @@ module InstructionSanity {
not tag instanceof UnmodeledUseOperand
}
/**
* Holds if `Phi` instruction `instr` has fewer than two operands.
*/
query predicate missingPhiOperands(PhiInstruction instr, int predIndex, Location predLoc) {
exists(IRBlock pred |
pred = instr.getBlock().getAPredecessor() and
predLoc = pred.getLocation() and
predIndex = pred.getDisplayIndex() and
not exists(PhiOperand operand |
exists(instr.getOperand(operand)) and
operand.getPredecessorBlock() = pred
)
)
}
/**
* Holds if an instruction, other than `ExitFunction`, has no successors.
*/
query predicate instructionWithoutSuccessor(Instruction instr) {
not exists(instr.getASuccessor()) and
not instr instanceof ExitFunctionInstruction and
// Phi instructions aren't linked into the instruction-level flow graph.
not instr instanceof PhiInstruction
}
query predicate operandAcrossFunctions(
Instruction op, Instruction operand, OperandTag tag
) {

View File

@@ -235,3 +235,16 @@ void FuncPtrConversions(int(*pfn)(int), void* p) {
p = (void*)pfn;
pfn = (int(*)(int))p;
}
int Func();
void ConversionsToVoid() {
int x;
(void)x;
static_cast<void>(x);
(void)Func();
static_cast<void>(Func());
(void)1;
static_cast<void>(1);
}

View File

@@ -143,3 +143,9 @@
| conversions.cpp:231:28:231:63 | dynamic_cast<PolymorphicDerived>... | dynamic_cast | lval | PolymorphicDerived | PolymorphicBase |
| conversions.cpp:235:7:235:16 | (void *)... | pointer conversion | prval | void * | ..(*)(..) |
| conversions.cpp:236:9:236:22 | (..(*)(..))... | pointer conversion | prval | ..(*)(..) | void * |
| conversions.cpp:243:3:243:9 | (void)... | conversion to void | prval | void | int |
| conversions.cpp:244:3:244:22 | static_cast<void>... | conversion to void | prval | void | int |
| conversions.cpp:245:3:245:14 | (void)... | conversion to void | prval | void | int |
| conversions.cpp:246:3:246:27 | static_cast<void>... | conversion to void | prval | void | int |
| conversions.cpp:247:3:247:9 | (void)... | conversion to void | prval | void | int |
| conversions.cpp:248:3:248:22 | static_cast<void>... | conversion to void | prval | void | int |

View File

@@ -1,4 +1,6 @@
missingOperand
unexpectedOperand
duplicateOperand
missingPhiOperand
instructionWithoutSuccessor
operandAcrossFunctions

View File

@@ -1,4 +1,6 @@
missingOperand
unexpectedOperand
duplicateOperand
missingPhiOperands
instructionWithoutSuccessor
operandAcrossFunctions

View File

@@ -5900,3 +5900,60 @@ ir.cpp:
# 897| Type = __va_list_tag[1]
# 897| ValueCategory = lvalue
# 898| 8: return ...
# 900| CastToVoid(int) -> void
# 900| params:
# 900| 0: x
# 900| Type = int
# 900| body: { ... }
# 901| 0: ExprStmt
# 901| 0: (void)...
# 901| Conversion = conversion to void
# 901| Type = void
# 901| ValueCategory = prvalue
# 901| expr: x
# 901| Type = int
# 901| ValueCategory = lvalue
# 902| 1: return ...
# 904| ConstantConditions(int) -> void
# 904| params:
# 904| 0: x
# 904| Type = int
# 904| body: { ... }
# 905| 0: declaration
# 905| 0: definition of a
# 905| Type = bool
# 905| init: initializer for a
# 905| expr: ... && ...
# 905| Type = bool
# 905| Value = 1
# 905| ValueCategory = prvalue
# 905| 0: 1
# 905| Type = bool
# 905| Value = 1
# 905| ValueCategory = prvalue
# 905| 1: 1
# 905| Type = bool
# 905| Value = 1
# 905| ValueCategory = prvalue
# 906| 1: declaration
# 906| 0: definition of b
# 906| Type = int
# 906| init: initializer for b
# 906| expr: ... ? ... : ...
# 906| Type = int
# 906| ValueCategory = prvalue
# 906| 0: (...)
# 906| Type = bool
# 906| Value = 1
# 906| ValueCategory = prvalue
# 906| expr: 1
# 906| Type = bool
# 906| Value = 1
# 906| ValueCategory = prvalue
# 906| 1: x
# 906| Type = int
# 906| ValueCategory = prvalue(load)
# 906| 2: x
# 906| Type = int
# 906| ValueCategory = prvalue(load)
# 907| 2: return ...

View File

@@ -1,4 +1,6 @@
missingOperand
unexpectedOperand
duplicateOperand
missingPhiOperands
instructionWithoutSuccessor
operandAcrossFunctions

View File

@@ -3846,3 +3846,57 @@ ir.cpp:
# 888| v0_40(void) = ReturnVoid :
# 888| v0_41(void) = UnmodeledUse : mu*
# 888| v0_42(void) = ExitFunction :
# 900| CastToVoid(int) -> void
# 900| Block 0
# 900| v0_0(void) = EnterFunction :
# 900| mu0_1(unknown) = UnmodeledDefinition :
# 900| r0_2(int) = InitializeParameter[x] :
# 900| r0_3(glval<int>) = VariableAddress[x] :
# 900| mu0_4(int) = Store : r0_3, r0_2
# 901| r0_5(glval<int>) = VariableAddress[x] :
# 901| v0_6(void) = ConvertToVoid : r0_5
# 902| v0_7(void) = NoOp :
# 900| v0_8(void) = ReturnVoid :
# 900| v0_9(void) = UnmodeledUse : mu*
# 900| v0_10(void) = ExitFunction :
# 904| ConstantConditions(int) -> void
# 904| Block 0
# 904| v0_0(void) = EnterFunction :
# 904| mu0_1(unknown) = UnmodeledDefinition :
# 904| r0_2(int) = InitializeParameter[x] :
# 904| r0_3(glval<int>) = VariableAddress[x] :
# 904| m0_4(int) = Store : r0_3, r0_2
# 905| r0_5(glval<bool>) = VariableAddress[a] :
# 905| r0_6(bool) = Constant[1] :
# 905| m0_7(bool) = Store : r0_5, r0_6
# 906| r0_8(glval<int>) = VariableAddress[b] :
# 906| r0_9(bool) = Constant[1] :
# 906| v0_10(void) = ConditionalBranch : r0_9
#-----| False -> Block 3
#-----| True -> Block 2
# 906| Block 1
# 906| m1_0(int) = Phi : from 2:m2_3, from 3:m3_3
# 906| r1_1(glval<int>) = VariableAddress[#temp906:11] :
# 906| r1_2(int) = Load : r1_1, m1_0
# 906| m1_3(int) = Store : r0_8, r1_2
# 907| v1_4(void) = NoOp :
# 904| v1_5(void) = ReturnVoid :
# 904| v1_6(void) = UnmodeledUse : mu*
# 904| v1_7(void) = ExitFunction :
# 906| Block 2
# 906| r2_0(glval<int>) = VariableAddress[x] :
# 906| r2_1(int) = Load : r2_0, m0_4
# 906| r2_2(glval<int>) = VariableAddress[#temp906:11] :
# 906| m2_3(int) = Store : r2_2, r2_1
#-----| Goto -> Block 1
# 906| Block 3
# 906| r3_0(glval<int>) = VariableAddress[x] :
# 906| r3_1(int) = Load : r3_0, m0_4
# 906| r3_2(glval<int>) = VariableAddress[#temp906:11] :
# 906| m3_3(int) = Store : r3_2, r3_1
#-----| Goto -> Block 1

View File

@@ -896,3 +896,12 @@ void VarArgUsage(int x, ...) {
__builtin_va_end(args);
__builtin_va_end(args2);
}
void CastToVoid(int x) {
(void)x;
}
void ConstantConditions(int x) {
bool a = true && true;
int b = (true) ? x : x;
}

View File

@@ -3826,3 +3826,56 @@ ir.cpp:
# 888| v0_40(void) = ReturnVoid :
# 888| v0_41(void) = UnmodeledUse : mu*
# 888| v0_42(void) = ExitFunction :
# 900| CastToVoid(int) -> void
# 900| Block 0
# 900| v0_0(void) = EnterFunction :
# 900| mu0_1(unknown) = UnmodeledDefinition :
# 900| r0_2(int) = InitializeParameter[x] :
# 900| r0_3(glval<int>) = VariableAddress[x] :
# 900| mu0_4(int) = Store : r0_3, r0_2
# 901| r0_5(glval<int>) = VariableAddress[x] :
# 901| v0_6(void) = ConvertToVoid : r0_5
# 902| v0_7(void) = NoOp :
# 900| v0_8(void) = ReturnVoid :
# 900| v0_9(void) = UnmodeledUse : mu*
# 900| v0_10(void) = ExitFunction :
# 904| ConstantConditions(int) -> void
# 904| Block 0
# 904| v0_0(void) = EnterFunction :
# 904| mu0_1(unknown) = UnmodeledDefinition :
# 904| r0_2(int) = InitializeParameter[x] :
# 904| r0_3(glval<int>) = VariableAddress[x] :
# 904| mu0_4(int) = Store : r0_3, r0_2
# 905| r0_5(glval<bool>) = VariableAddress[a] :
# 905| r0_6(bool) = Constant[1] :
# 905| mu0_7(bool) = Store : r0_5, r0_6
# 906| r0_8(glval<int>) = VariableAddress[b] :
# 906| r0_9(bool) = Constant[1] :
# 906| v0_10(void) = ConditionalBranch : r0_9
#-----| False -> Block 3
#-----| True -> Block 2
# 906| Block 1
# 906| r1_0(glval<int>) = VariableAddress[#temp906:11] :
# 906| r1_1(int) = Load : r1_0, mu0_1
# 906| mu1_2(int) = Store : r0_8, r1_1
# 907| v1_3(void) = NoOp :
# 904| v1_4(void) = ReturnVoid :
# 904| v1_5(void) = UnmodeledUse : mu*
# 904| v1_6(void) = ExitFunction :
# 906| Block 2
# 906| r2_0(glval<int>) = VariableAddress[x] :
# 906| r2_1(int) = Load : r2_0, mu0_1
# 906| r2_2(glval<int>) = VariableAddress[#temp906:11] :
# 906| mu2_3(int) = Store : r2_2, r2_1
#-----| Goto -> Block 1
# 906| Block 3
# 906| r3_0(glval<int>) = VariableAddress[x] :
# 906| r3_1(int) = Load : r3_0, mu0_1
# 906| r3_2(glval<int>) = VariableAddress[#temp906:11] :
# 906| mu3_3(int) = Store : r3_2, r3_1
#-----| Goto -> Block 1

View File

@@ -12,12 +12,14 @@
| IR: CallMin | 1 |
| IR: CallNestedTemplateFunc | 1 |
| IR: CallViaFuncPtr | 1 |
| IR: CastToVoid | 1 |
| IR: Comma | 1 |
| IR: CompoundAssignment | 1 |
| IR: ConditionValues | 13 |
| IR: Conditional | 4 |
| IR: Conditional_LValue | 4 |
| IR: Conditional_Void | 4 |
| IR: ConstantConditions | 4 |
| IR: Constants | 1 |
| IR: Continue | 6 |
| IR: DeclareObject | 1 |

View File

@@ -3846,3 +3846,57 @@ ir.cpp:
# 888| v0_40(void) = ReturnVoid :
# 888| v0_41(void) = UnmodeledUse : mu*
# 888| v0_42(void) = ExitFunction :
# 900| CastToVoid(int) -> void
# 900| Block 0
# 900| v0_0(void) = EnterFunction :
# 900| mu0_1(unknown) = UnmodeledDefinition :
# 900| r0_2(int) = InitializeParameter[x] :
# 900| r0_3(glval<int>) = VariableAddress[x] :
# 900| mu0_4(int) = Store : r0_3, r0_2
# 901| r0_5(glval<int>) = VariableAddress[x] :
# 901| v0_6(void) = ConvertToVoid : r0_5
# 902| v0_7(void) = NoOp :
# 900| v0_8(void) = ReturnVoid :
# 900| v0_9(void) = UnmodeledUse : mu*
# 900| v0_10(void) = ExitFunction :
# 904| ConstantConditions(int) -> void
# 904| Block 0
# 904| v0_0(void) = EnterFunction :
# 904| mu0_1(unknown) = UnmodeledDefinition :
# 904| r0_2(int) = InitializeParameter[x] :
# 904| r0_3(glval<int>) = VariableAddress[x] :
# 904| m0_4(int) = Store : r0_3, r0_2
# 905| r0_5(glval<bool>) = VariableAddress[a] :
# 905| r0_6(bool) = Constant[1] :
# 905| m0_7(bool) = Store : r0_5, r0_6
# 906| r0_8(glval<int>) = VariableAddress[b] :
# 906| r0_9(bool) = Constant[1] :
# 906| v0_10(void) = ConditionalBranch : r0_9
#-----| False -> Block 3
#-----| True -> Block 2
# 906| Block 1
# 906| m1_0(int) = Phi : from 2:m2_3, from 3:m3_3
# 906| r1_1(glval<int>) = VariableAddress[#temp906:11] :
# 906| r1_2(int) = Load : r1_1, m1_0
# 906| m1_3(int) = Store : r0_8, r1_2
# 907| v1_4(void) = NoOp :
# 904| v1_5(void) = ReturnVoid :
# 904| v1_6(void) = UnmodeledUse : mu*
# 904| v1_7(void) = ExitFunction :
# 906| Block 2
# 906| r2_0(glval<int>) = VariableAddress[x] :
# 906| r2_1(int) = Load : r2_0, m0_4
# 906| r2_2(glval<int>) = VariableAddress[#temp906:11] :
# 906| m2_3(int) = Store : r2_2, r2_1
#-----| Goto -> Block 1
# 906| Block 3
# 906| r3_0(glval<int>) = VariableAddress[x] :
# 906| r3_1(int) = Load : r3_0, m0_4
# 906| r3_2(glval<int>) = VariableAddress[#temp906:11] :
# 906| m3_3(int) = Store : r3_2, r3_1
#-----| Goto -> Block 1