C++: Use IR alias analysis for field flow#4142
Conversation
…loading it afterwards
…dataflow read and store steps.
jbj
left a comment
There was a problem hiding this comment.
I was hoping for a much less disruptive PR where offsets were only used to fix up cases where the field can't be determined by SSA.
| private import semmle.code.cpp.ir.IR | ||
| private import DataFlowDispatch | ||
| private import semmle.code.cpp.ir.implementation.aliased_ssa.internal.AliasedSSA | ||
| private import semmle.code.cpp.ir.internal.IntegerConstant |
There was a problem hiding this comment.
I don't think we can merge this before we find a way to avoid implementation and internal imports here.
There was a problem hiding this comment.
I agree. I'll have a chat with @dbartol about this later today.
cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowPrivate.qll
Outdated
Show resolved
Hide resolved
cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowPrivate.qll
Outdated
Show resolved
Hide resolved
…Interval predicate to NonPhiMemoryOperands.
…dds a Class column to the FieldContent branch so FieldContent has a pretty toString implementation again.
|
I've added a new iteration of this PR now. Here's an overview:
I've started a new CPP-difference here: https://jenkins.internal.semmle.com/job/Changes/job/CPP-Differences/1379/ |
Why do we no longer catch lambda-captured variables? I thought they were represented as |
Oh, you're right. They are! I thought that was the reason we caught https://github.com/github/codeql/blob/main/cpp/ql/test/library-tests/dataflow/dataflow-tests/lambdas.cpp#L46 before I added the |
…the shared dataflow library. This is the tuple counts on ChakraCore before the fix:
(5539s) Tuple counts for DataFlowPrivate::readStep#fff:
3208924 ~0% {2} r1 = SCAN Operand::NonPhiMemoryOperand::getAnyDef_dispred#3#ff AS I OUTPUT I.<1>, I.<0>
3208924 ~2% {2} r2 = JOIN r1 WITH DataFlowUtil::TInstructionNode#ff AS R ON FIRST 1 OUTPUT r1.<1>, R.<1>
751306 ~8% {2} r3 = JOIN r2 WITH Instruction::CopyInstruction::getSourceValueOperand_dispred#3#ff_10#join_rhs AS R ON FIRST 1 OUTPUT R.<1>, r2.<1>
751306 ~0% {3} r4 = JOIN r3 WITH DataFlowUtil::TInstructionNode#ff AS R ON FIRST 1 OUTPUT r3.<0>, r3.<1>, R.<1>
751306 ~0% {4} r5 = JOIN r4 WITH Instruction::CopyInstruction::getSourceValueOperand_dispred#3#ff AS R ON FIRST 1 OUTPUT R.<1>, r4.<1>, r4.<0>, r4.<2>
751306 ~2% {4} r6 = JOIN r5 WITH Operand::NonPhiMemoryOperand::getAnyDef_dispred#3#ff AS R ON FIRST 1 OUTPUT R.<1>, r5.<1>, r5.<2>, r5.<3>
209341 ~0% {4} r7 = JOIN r6 WITH Instruction::Instruction::getResultType_dispred#fb AS R ON FIRST 1 OUTPUT R.<1>, r6.<1>, r6.<2>, r6.<3>
7115323 ~1% {6} r8 = JOIN r7 WITH DataFlowPrivate::FieldContent#class#ffff_1023#join_rhs AS R ON FIRST 1 OUTPUT R.<2>, R.<3>, r7.<1>, r7.<2>, r7.<3>, R.<1>
81341188413 ~0% {5} r9 = JOIN r8 WITH SSAConstruction::Cached::getUsedInterval#fff_120#join_rhs AS R ON FIRST 2 OUTPUT r8.<3>, R.<2>, r8.<2>, r8.<4>, r8.<5>
137684 ~7% {3} r10 = JOIN r9 WITH Instruction::CopyInstruction::getSourceValueOperand_dispred#3#ff AS R ON FIRST 2 OUTPUT r9.<2>, r9.<4>, r9.<3>
751306 ~0% {3} r11 = JOIN r4 WITH Instruction::LoadInstruction::getSourceAddress_dispred#ff AS R ON FIRST 1 OUTPUT R.<1>, r4.<1>, r4.<2>
94306 ~0% {3} r12 = JOIN r11 WITH Instruction::FieldInstruction::getField_dispred#3#ff AS R ON FIRST 1 OUTPUT R.<1>, r11.<1>, r11.<2>
152363 ~6% {3} r13 = JOIN r12 WITH DataFlowPrivate::FieldContent::getField_dispred#ff_10#join_rhs AS R ON FIRST 1 OUTPUT r12.<1>, R.<1>, r12.<2>
290047 ~22% {3} r14 = r10 \/ r13
return r14
and after:
(851s) Tuple counts for DataFlowPrivate::readStep#fff:
3208924 ~0% {2} r1 = SCAN Operand::NonPhiMemoryOperand::getAnyDef_dispred#3#ff AS I OUTPUT I.<1>, I.<0>
3208924 ~2% {2} r2 = JOIN r1 WITH DataFlowUtil::TInstructionNode#ff AS R ON FIRST 1 OUTPUT r1.<1>, R.<1>
751306 ~8% {2} r3 = JOIN r2 WITH Instruction::CopyInstruction::getSourceValueOperand_dispred#3#ff_10#join_rhs AS R ON FIRST 1 OUTPUT R.<1>, r2.<1>
751306 ~0% {3} r4 = JOIN r3 WITH DataFlowUtil::TInstructionNode#ff AS R ON FIRST 1 OUTPUT r3.<0>, r3.<1>, R.<1>
751306 ~0% {4} r5 = JOIN r4 WITH Instruction::CopyInstruction::getSourceValueOperand_dispred#3#ff AS R ON FIRST 1 OUTPUT r4.<0>, r4.<1>, r4.<2>, R.<1>
751306 ~0% {5} r6 = JOIN r5 WITH Instruction::CopyInstruction::getSourceValueOperand_dispred#3#ff AS R ON FIRST 1 OUTPUT R.<1>, r5.<1>, r5.<0>, r5.<2>, r5.<3>
751306 ~0% {5} r7 = JOIN r6 WITH Operand::NonPhiMemoryOperand::getAnyDef_dispred#3#ff AS R ON FIRST 1 OUTPUT R.<1>, r6.<1>, r6.<2>, r6.<3>, r6.<4>
209341 ~1% {5} r8 = JOIN r7 WITH Instruction::Instruction::getResultType_dispred#fb AS R ON FIRST 1 OUTPUT R.<1>, r7.<1>, r7.<2>, r7.<3>, r7.<4>
7115323 ~0% {7} r9 = JOIN r8 WITH DataFlowPrivate::FieldContent#class#ffff_1023#join_rhs AS R ON FIRST 1 OUTPUT r8.<4>, r8.<1>, r8.<2>, r8.<3>, R.<1>, R.<2>, R.<3>
7116087 ~0% {9} r10 = JOIN r9 WITH SSAConstruction::Cached::getUsedInterval#fff@staged_ext AS R ON FIRST 1 OUTPUT r9.<1>, r9.<2>, r9.<3>, r9.<0>, r9.<4>, r9.<5>, r9.<6>, R.<1>, R.<2>
449879 ~4% {9} r11 = SELECT r10 ON r10.<5> <= r10.<7>
193804 ~1% {9} r12 = SELECT r11 ON r11.<5> >= r11.<7>
154980 ~0% {9} r13 = SELECT r12 ON r12.<6> <= r12.<8>
137684 ~0% {9} r14 = SELECT r13 ON r13.<6> >= r13.<8>
137684 ~7% {3} r15 = SCAN r14 OUTPUT r14.<0>, r14.<4>, r14.<2>
751306 ~0% {3} r16 = JOIN r4 WITH Instruction::LoadInstruction::getSourceAddress_dispred#ff AS R ON FIRST 1 OUTPUT R.<1>, r4.<1>, r4.<2>
94306 ~0% {3} r17 = JOIN r16 WITH Instruction::FieldInstruction::getField_dispred#3#ff AS R ON FIRST 1 OUTPUT R.<1>, r16.<1>, r16.<2>
152363 ~6% {3} r18 = JOIN r17 WITH DataFlowPrivate::FieldContent::getField_dispred#ff_10#join_rhs AS R ON FIRST 1 OUTPUT r17.<1>, R.<1>, r17.<2>
290047 ~22% {3} r19 = r15 \/ r18
return r19
6412503 to
5546830
Compare
Oops! I understand why we're losing that result (along with the other new ones) now. The flow isn't through a lambda-captured variable, but rather through a reference parameter that gets assigned in the body of the lambda. It's the same reason we lose the result in 3cca74e#diff-17a3dcef69a5018db3bc4790a32f25b0: The assignment was recognized as a |
|
I've started a (hopefully final) CPP-difference that should show that performance is fine now. The internal PR should also be up-to-date. It would be great if you could comment on 1e13a39 and 91a2309, @dbartol. |
5e5d64a to
c679516
Compare
|
Oops. I was too trigger happy with my rebase. Sorry! My previous join order fix didn't preserve the behavior, so I've rebased to the correct solution now. It turned out that the changes in the internal tests were caused by multiple Sorry for the annoyingly long git conversation this has caused 😢. Anyway, assuming the CPP-differences comes back without performance regressions this should be good to go. |
The CPP-difference showed no performance regressions! 🎉 |
| override predicate hasLocationInfo(string path, int sl, int sc, int el, int ec) { | ||
| f.getLocation().hasLocationInfo(path, sl, sc, el, ec) | ||
| } | ||
| Field getField() { result = getField(c, startBit, endBit) } |
There was a problem hiding this comment.
I suggest renaming these two getField predicates to getAField so it's clear to callers there might be multiple results.
There was a problem hiding this comment.
Good point. I've done this in 9c1837e. I also changed the qldoc to reflect that there might be multiple results.
|
|
||
| sink(x); // clean [FALSE POSITIVE] | ||
| sink(y); // tainted | ||
| sink(y); // tainted [NOT DETECTED by IR] |
There was a problem hiding this comment.
Do we have an internal issue for recovering these cases of lost flow?
There was a problem hiding this comment.
I don't think we do. I think that's a great idea to have, though!
Note that this wasn't lost due to this PR. On main we don't catch this flow using the IR, and I just updated the comment to reflect that.
2889821 to
9c1837e
Compare
dbartol
left a comment
There was a problem hiding this comment.
The cleaned-up IR changes look good. LGTM.
|
There's a conflict in a test output file. |
|
Note that the only check that's not passing is one that's broken on all PRs. (So this PR should be safe to merge.) |
Fixes https://github.com/github/codeql-c-analysis-team/issues/95.
Prior to this PR we wouldn't catch the following flow:
because the
FieldAddressInstructionwas "far away" from the actualLoadInstruction, so we wouldn't recognize that the load generated by*pxloaded from the field addressx. This meant that it wasn't picked up as areadStepin IR dataflow.However, the IR already computed that this is a load that totally overlaps with the definition
S s_copy = s, and so it is reasonably easy to hook into the IR internals and grab this information.