Merge pull request #20156 from MathiasVP/value-numbering-for-noop-casts

C++: Value numbering for casts that only modify specifiers
This commit is contained in:
Mathias Vorreiter Pedersen
2025-08-11 10:33:58 +02:00
committed by GitHub
7 changed files with 121 additions and 1 deletions

View File

@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* The global value numbering library (`semmle.code.cpp.valuenumbering.GlobalValueNumbering` and `semmle.code.cpp.ir.ValueNumbering`) has been improved so more expressions are assigned the same value number.

View File

@@ -43,6 +43,23 @@ newtype TValueNumber =
} or } or
TUniqueValueNumber(IRFunction irFunc, Instruction instr) { uniqueValueNumber(instr, irFunc) } TUniqueValueNumber(IRFunction irFunc, Instruction instr) { uniqueValueNumber(instr, irFunc) }
/**
* A `ConvertInstruction` which converts data of type `T` to data of type `U`
* where `T` and `U` only differ in specifiers. For example, if `T` is `int`
* and `U` is `const T` this is a conversion from a non-const integer to a
* const integer.
*
* Generally, the value number of a converted value is different from the value
* number of an unconverted value, but conversions which only modify specifiers
* leave the resulting value bitwise identical to the old value.
*/
class TypePreservingConvertInstruction extends ConvertInstruction {
TypePreservingConvertInstruction() {
pragma[only_bind_out](this.getResultType().getUnspecifiedType()) =
pragma[only_bind_out](this.getUnary().getResultType().getUnspecifiedType())
}
}
/** /**
* A `CopyInstruction` whose source operand's value is congruent to the definition of that source * A `CopyInstruction` whose source operand's value is congruent to the definition of that source
* operand. * operand.
@@ -216,6 +233,7 @@ private predicate unaryValueNumber(
not instr instanceof InheritanceConversionInstruction and not instr instanceof InheritanceConversionInstruction and
not instr instanceof CopyInstruction and not instr instanceof CopyInstruction and
not instr instanceof FieldAddressInstruction and not instr instanceof FieldAddressInstruction and
not instr instanceof TypePreservingConvertInstruction and
instr.getOpcode() = opcode and instr.getOpcode() = opcode and
tvalueNumber(instr.getUnary()) = operand tvalueNumber(instr.getUnary()) = operand
} }
@@ -351,6 +369,10 @@ private TValueNumber nonUniqueValueNumber(Instruction instr) {
or or
// The value number of a copy is just the value number of its source value. // The value number of a copy is just the value number of its source value.
result = tvalueNumber(instr.(CongruentCopyInstruction).getSourceValue()) result = tvalueNumber(instr.(CongruentCopyInstruction).getSourceValue())
or
// The value number of a type-preserving conversion is just the value
// number of the unconverted value.
result = tvalueNumber(instr.(TypePreservingConvertInstruction).getUnary())
) )
) )
} }

View File

@@ -43,6 +43,23 @@ newtype TValueNumber =
} or } or
TUniqueValueNumber(IRFunction irFunc, Instruction instr) { uniqueValueNumber(instr, irFunc) } TUniqueValueNumber(IRFunction irFunc, Instruction instr) { uniqueValueNumber(instr, irFunc) }
/**
* A `ConvertInstruction` which converts data of type `T` to data of type `U`
* where `T` and `U` only differ in specifiers. For example, if `T` is `int`
* and `U` is `const T` this is a conversion from a non-const integer to a
* const integer.
*
* Generally, the value number of a converted value is different from the value
* number of an unconverted value, but conversions which only modify specifiers
* leave the resulting value bitwise identical to the old value.
*/
class TypePreservingConvertInstruction extends ConvertInstruction {
TypePreservingConvertInstruction() {
pragma[only_bind_out](this.getResultType().getUnspecifiedType()) =
pragma[only_bind_out](this.getUnary().getResultType().getUnspecifiedType())
}
}
/** /**
* A `CopyInstruction` whose source operand's value is congruent to the definition of that source * A `CopyInstruction` whose source operand's value is congruent to the definition of that source
* operand. * operand.
@@ -216,6 +233,7 @@ private predicate unaryValueNumber(
not instr instanceof InheritanceConversionInstruction and not instr instanceof InheritanceConversionInstruction and
not instr instanceof CopyInstruction and not instr instanceof CopyInstruction and
not instr instanceof FieldAddressInstruction and not instr instanceof FieldAddressInstruction and
not instr instanceof TypePreservingConvertInstruction and
instr.getOpcode() = opcode and instr.getOpcode() = opcode and
tvalueNumber(instr.getUnary()) = operand tvalueNumber(instr.getUnary()) = operand
} }
@@ -351,6 +369,10 @@ private TValueNumber nonUniqueValueNumber(Instruction instr) {
or or
// The value number of a copy is just the value number of its source value. // The value number of a copy is just the value number of its source value.
result = tvalueNumber(instr.(CongruentCopyInstruction).getSourceValue()) result = tvalueNumber(instr.(CongruentCopyInstruction).getSourceValue())
or
// The value number of a type-preserving conversion is just the value
// number of the unconverted value.
result = tvalueNumber(instr.(TypePreservingConvertInstruction).getUnary())
) )
) )
} }

View File

@@ -43,6 +43,23 @@ newtype TValueNumber =
} or } or
TUniqueValueNumber(IRFunction irFunc, Instruction instr) { uniqueValueNumber(instr, irFunc) } TUniqueValueNumber(IRFunction irFunc, Instruction instr) { uniqueValueNumber(instr, irFunc) }
/**
* A `ConvertInstruction` which converts data of type `T` to data of type `U`
* where `T` and `U` only differ in specifiers. For example, if `T` is `int`
* and `U` is `const T` this is a conversion from a non-const integer to a
* const integer.
*
* Generally, the value number of a converted value is different from the value
* number of an unconverted value, but conversions which only modify specifiers
* leave the resulting value bitwise identical to the old value.
*/
class TypePreservingConvertInstruction extends ConvertInstruction {
TypePreservingConvertInstruction() {
pragma[only_bind_out](this.getResultType().getUnspecifiedType()) =
pragma[only_bind_out](this.getUnary().getResultType().getUnspecifiedType())
}
}
/** /**
* A `CopyInstruction` whose source operand's value is congruent to the definition of that source * A `CopyInstruction` whose source operand's value is congruent to the definition of that source
* operand. * operand.
@@ -216,6 +233,7 @@ private predicate unaryValueNumber(
not instr instanceof InheritanceConversionInstruction and not instr instanceof InheritanceConversionInstruction and
not instr instanceof CopyInstruction and not instr instanceof CopyInstruction and
not instr instanceof FieldAddressInstruction and not instr instanceof FieldAddressInstruction and
not instr instanceof TypePreservingConvertInstruction and
instr.getOpcode() = opcode and instr.getOpcode() = opcode and
tvalueNumber(instr.getUnary()) = operand tvalueNumber(instr.getUnary()) = operand
} }
@@ -351,6 +369,10 @@ private TValueNumber nonUniqueValueNumber(Instruction instr) {
or or
// The value number of a copy is just the value number of its source value. // The value number of a copy is just the value number of its source value.
result = tvalueNumber(instr.(CongruentCopyInstruction).getSourceValue()) result = tvalueNumber(instr.(CongruentCopyInstruction).getSourceValue())
or
// The value number of a type-preserving conversion is just the value
// number of the unconverted value.
result = tvalueNumber(instr.(TypePreservingConvertInstruction).getUnary())
) )
) )
} }

View File

@@ -1145,3 +1145,43 @@ test.cpp:
# 152| v152_7(void) = ReturnVoid : # 152| v152_7(void) = ReturnVoid :
# 152| v152_8(void) = AliasedUse : ~m156_7 # 152| v152_8(void) = AliasedUse : ~m156_7
# 152| v152_9(void) = ExitFunction : # 152| v152_9(void) = ExitFunction :
# 166| void test_constMemberFunction()
# 166| Block 0
# 166| v166_1(void) = EnterFunction :
# 166| m166_2(unknown) = AliasedDefinition :
# 166| valnum = unique
# 166| m166_3(unknown) = InitializeNonLocal :
# 166| valnum = unique
# 166| m166_4(unknown) = Chi : total:m166_2, partial:m166_3
# 166| valnum = unique
# 167| r167_1(glval<StructWithConstMemberFunction>) = VariableAddress[s] :
# 167| valnum = r167_1, r168_2, r169_1, r169_2
# 167| m167_2(StructWithConstMemberFunction) = Uninitialized[s] : &:r167_1
# 167| valnum = m167_2, m168_4, r168_3
# 167| m167_3(unknown) = Chi : total:m166_4, partial:m167_2
# 167| valnum = unique
# 168| r168_1(glval<StructWithConstMemberFunction>) = VariableAddress[s2] :
# 168| valnum = unique
# 168| r168_2(glval<StructWithConstMemberFunction>) = VariableAddress[s] :
# 168| valnum = r167_1, r168_2, r169_1, r169_2
# 168| r168_3(StructWithConstMemberFunction) = Load[s] : &:r168_2, m167_2
# 168| valnum = m167_2, m168_4, r168_3
# 168| m168_4(StructWithConstMemberFunction) = Store[s2] : &:r168_1, r168_3
# 168| valnum = m167_2, m168_4, r168_3
# 169| r169_1(glval<StructWithConstMemberFunction>) = VariableAddress[s] :
# 169| valnum = r167_1, r168_2, r169_1, r169_2
# 169| r169_2(glval<StructWithConstMemberFunction>) = Convert : r169_1
# 169| valnum = r167_1, r168_2, r169_1, r169_2
# 169| r169_3(glval<unknown>) = FunctionAddress[constMemberFunction] :
# 169| valnum = unique
# 169| v169_4(void) = Call[constMemberFunction] : func:r169_3, this:r169_2
# 169| m169_5(unknown) = ^CallSideEffect : ~m167_3
# 169| valnum = unique
# 169| m169_6(unknown) = Chi : total:m167_3, partial:m169_5
# 169| valnum = unique
# 169| v169_7(void) = ^IndirectReadSideEffect[-1] : &:r169_2, ~m169_6
# 170| v170_1(void) = NoOp :
# 166| v166_5(void) = ReturnVoid :
# 166| v166_6(void) = AliasedUse : ~m169_6
# 166| v166_7(void) = ExitFunction :

View File

@@ -156,4 +156,15 @@ void test_read_global_different(int n) {
global_a->y = n; global_a->y = n;
int d = global_a->x; int d = global_a->x;
}
struct StructWithConstMemberFunction {
int x;
void constMemberFunction() const;
};
void test_constMemberFunction() {
StructWithConstMemberFunction s;
StructWithConstMemberFunction s2 = s;
s.constMemberFunction();
} }

View File

@@ -13,7 +13,6 @@
| test.cpp:49:2:49:9 | call to strcpy_s | Potentially unsafe call to strcpy_s; second argument should be size of destination. | | test.cpp:49:2:49:9 | call to strcpy_s | Potentially unsafe call to strcpy_s; second argument should be size of destination. |
| test.cpp:62:3:62:9 | call to strncpy | Potentially unsafe call to strncpy; third argument should be size of destination. | | test.cpp:62:3:62:9 | call to strncpy | Potentially unsafe call to strncpy; third argument should be size of destination. |
| test.cpp:65:3:65:9 | call to strncpy | Potentially unsafe call to strncpy; third argument should be size of destination. | | test.cpp:65:3:65:9 | call to strncpy | Potentially unsafe call to strncpy; third argument should be size of destination. |
| test.cpp:70:2:70:8 | call to strncpy | Potentially unsafe call to strncpy; third argument should be size of destination. |
| test.cpp:81:3:81:9 | call to strncpy | Potentially unsafe call to strncpy; third argument should be size of destination. | | test.cpp:81:3:81:9 | call to strncpy | Potentially unsafe call to strncpy; third argument should be size of destination. |
| test.cpp:84:3:84:9 | call to strncpy | Potentially unsafe call to strncpy; third argument should be size of destination. | | test.cpp:84:3:84:9 | call to strncpy | Potentially unsafe call to strncpy; third argument should be size of destination. |
| test.cpp:105:2:105:10 | call to wcsxfrm_l | Potentially unsafe call to wcsxfrm_l; third argument should be size of destination. | | test.cpp:105:2:105:10 | call to wcsxfrm_l | Potentially unsafe call to wcsxfrm_l; third argument should be size of destination. |