-
Notifications
You must be signed in to change notification settings - Fork 15.4k
[MC/DC] Enable nested expressions #125413
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: users/chapuni/mcdc/nest/nest-base
Are you sure you want to change the base?
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -228,45 +228,46 @@ struct MapRegionCounters : public RecursiveASTVisitor<MapRegionCounters> { | |
| /// The stacks are also used to find error cases and notify the user. A | ||
| /// standard logical operator nest for a boolean expression could be in a form | ||
| /// similar to this: "x = a && b && c && (d || f)" | ||
| unsigned NumCond = 0; | ||
| bool SplitNestedLogicalOp = false; | ||
| SmallVector<const Stmt *, 16> NonLogOpStack; | ||
| SmallVector<const BinaryOperator *, 16> LogOpStack; | ||
| struct DecisionState { | ||
| llvm::DenseSet<const Stmt *> Leaves; // Not BinOp | ||
| const Expr *DecisionExpr; // Root | ||
| bool Split; | ||
|
|
||
| DecisionState() = delete; | ||
| DecisionState(const Expr *E, bool Split = false) | ||
| : DecisionExpr(E), Split(Split) {} | ||
| }; | ||
|
|
||
| SmallVector<DecisionState, 1> DecisionStack; | ||
|
|
||
| // Hook: dataTraverseStmtPre() is invoked prior to visiting an AST Stmt node. | ||
| bool dataTraverseStmtPre(Stmt *S) { | ||
| /// If MC/DC is not enabled, MCDCMaxCond will be set to 0. Do nothing. | ||
| if (MCDCMaxCond == 0) | ||
| return true; | ||
|
|
||
| /// At the top of the logical operator nest, reset the number of conditions, | ||
| /// also forget previously seen split nesting cases. | ||
| if (LogOpStack.empty()) { | ||
| NumCond = 0; | ||
| SplitNestedLogicalOp = false; | ||
| } | ||
|
|
||
| if (const Expr *E = dyn_cast<Expr>(S)) { | ||
| const BinaryOperator *BinOp = dyn_cast<BinaryOperator>(E->IgnoreParens()); | ||
| if (BinOp && BinOp->isLogicalOp()) { | ||
| /// Check for "split-nested" logical operators. This happens when a new | ||
| /// boolean expression logical-op nest is encountered within an existing | ||
| /// boolean expression, separated by a non-logical operator. For | ||
| /// example, in "x = (a && b && c && foo(d && f))", the "d && f" case | ||
| /// starts a new boolean expression that is separated from the other | ||
| /// conditions by the operator foo(). Split-nested cases are not | ||
| /// supported by MC/DC. | ||
| SplitNestedLogicalOp = SplitNestedLogicalOp || !NonLogOpStack.empty(); | ||
|
|
||
| LogOpStack.push_back(BinOp); | ||
| /// Mark "in splitting" when a leaf is met. | ||
| if (!DecisionStack.empty()) { | ||
| auto &StackTop = DecisionStack.back(); | ||
| if (!StackTop.Split) { | ||
| if (StackTop.Leaves.contains(S)) { | ||
| assert(!StackTop.Split); | ||
| StackTop.Split = true; | ||
| } | ||
| return true; | ||
| } | ||
|
|
||
| // Split | ||
| assert(StackTop.Split); | ||
| assert(!StackTop.Leaves.contains(S)); | ||
| } | ||
|
|
||
| /// Keep track of non-logical operators. These are OK as long as we don't | ||
| /// encounter a new logical operator after seeing one. | ||
| if (!LogOpStack.empty()) | ||
| NonLogOpStack.push_back(S); | ||
| if (const auto *E = dyn_cast<Expr>(S)) { | ||
| if (const auto *BinOp = | ||
| dyn_cast<BinaryOperator>(CodeGenFunction::stripCond(E)); | ||
| BinOp && BinOp->isLogicalOp()) | ||
| DecisionStack.emplace_back(E); | ||
| } | ||
|
|
||
| return true; | ||
| } | ||
|
|
@@ -275,49 +276,57 @@ struct MapRegionCounters : public RecursiveASTVisitor<MapRegionCounters> { | |
| // an AST Stmt node. MC/DC will use it to to signal when the top of a | ||
| // logical operation (boolean expression) nest is encountered. | ||
| bool dataTraverseStmtPost(Stmt *S) { | ||
| /// If MC/DC is not enabled, MCDCMaxCond will be set to 0. Do nothing. | ||
| if (MCDCMaxCond == 0) | ||
| if (DecisionStack.empty()) | ||
| return true; | ||
|
|
||
| if (const Expr *E = dyn_cast<Expr>(S)) { | ||
| const BinaryOperator *BinOp = dyn_cast<BinaryOperator>(E->IgnoreParens()); | ||
| if (BinOp && BinOp->isLogicalOp()) { | ||
| assert(LogOpStack.back() == BinOp); | ||
| LogOpStack.pop_back(); | ||
|
|
||
| /// At the top of logical operator nest: | ||
| if (LogOpStack.empty()) { | ||
| /// Was the "split-nested" logical operator case encountered? | ||
| if (SplitNestedLogicalOp) { | ||
| unsigned DiagID = Diag.getCustomDiagID( | ||
| DiagnosticsEngine::Warning, | ||
| "unsupported MC/DC boolean expression; " | ||
| "contains an operation with a nested boolean expression. " | ||
| "Expression will not be covered"); | ||
| Diag.Report(S->getBeginLoc(), DiagID); | ||
| return true; | ||
| } | ||
|
|
||
| /// Was the maximum number of conditions encountered? | ||
| if (NumCond > MCDCMaxCond) { | ||
| unsigned DiagID = Diag.getCustomDiagID( | ||
| DiagnosticsEngine::Warning, | ||
| "unsupported MC/DC boolean expression; " | ||
| "number of conditions (%0) exceeds max (%1). " | ||
| "Expression will not be covered"); | ||
| Diag.Report(S->getBeginLoc(), DiagID) << NumCond << MCDCMaxCond; | ||
| return true; | ||
| } | ||
|
|
||
| // Otherwise, allocate the Decision. | ||
| MCDCState.DecisionByStmt[BinOp].ID = MCDCState.DecisionByStmt.size(); | ||
| } | ||
| return true; | ||
| /// If MC/DC is not enabled, MCDCMaxCond will be set to 0. Do nothing. | ||
| assert(MCDCMaxCond > 0); | ||
|
|
||
| auto &StackTop = DecisionStack.back(); | ||
|
|
||
| if (StackTop.DecisionExpr != S) { | ||
| if (StackTop.Leaves.contains(S)) { | ||
| assert(StackTop.Split); | ||
| StackTop.Split = false; | ||
| } | ||
|
|
||
| return true; | ||
| } | ||
|
|
||
| /// Allocate the entry (with Valid=false) | ||
| auto &DecisionEntry = | ||
| MCDCState | ||
| .DecisionByStmt[CodeGenFunction::stripCond(StackTop.DecisionExpr)]; | ||
|
|
||
| /// Was the "split-nested" logical operator case encountered? | ||
| if (false && DecisionStack.size() > 1) { | ||
|
||
| unsigned DiagID = Diag.getCustomDiagID( | ||
| DiagnosticsEngine::Warning, | ||
| "unsupported MC/DC boolean expression; " | ||
| "contains an operation with a nested boolean expression. " | ||
| "Expression will not be covered"); | ||
| Diag.Report(S->getBeginLoc(), DiagID); | ||
| DecisionStack.pop_back(); | ||
| return true; | ||
| } | ||
|
|
||
| /// Was the maximum number of conditions encountered? | ||
| auto NumCond = StackTop.Leaves.size(); | ||
| if (NumCond > MCDCMaxCond) { | ||
| unsigned DiagID = | ||
| Diag.getCustomDiagID(DiagnosticsEngine::Warning, | ||
| "unsupported MC/DC boolean expression; " | ||
| "number of conditions (%0) exceeds max (%1). " | ||
| "Expression will not be covered"); | ||
| Diag.Report(S->getBeginLoc(), DiagID) << NumCond << MCDCMaxCond; | ||
| DecisionStack.pop_back(); | ||
| return true; | ||
| } | ||
|
|
||
| if (!LogOpStack.empty()) | ||
| NonLogOpStack.pop_back(); | ||
| // The Decision is validated. | ||
| DecisionEntry.ID = MCDCState.DecisionByStmt.size() - 1; | ||
|
|
||
| DecisionStack.pop_back(); | ||
|
|
||
| return true; | ||
| } | ||
|
|
@@ -329,14 +338,17 @@ struct MapRegionCounters : public RecursiveASTVisitor<MapRegionCounters> { | |
| /// order to use MC/DC, count the number of total LHS and RHS conditions. | ||
| bool VisitBinaryOperator(BinaryOperator *S) { | ||
| if (S->isLogicalOp()) { | ||
| if (CodeGenFunction::isInstrumentedCondition(S->getLHS())) | ||
| NumCond++; | ||
| if (CodeGenFunction::isInstrumentedCondition(S->getLHS())) { | ||
| if (!DecisionStack.empty()) | ||
| DecisionStack.back().Leaves.insert(S->getLHS()); | ||
| } | ||
|
|
||
| if (CodeGenFunction::isInstrumentedCondition(S->getRHS())) { | ||
| if (ProfileVersion >= llvm::IndexedInstrProf::Version7) | ||
| CounterMap[S->getRHS()] = NextCounter++; | ||
|
|
||
| NumCond++; | ||
| if (!DecisionStack.empty()) | ||
| DecisionStack.back().Leaves.insert(S->getRHS()); | ||
| } | ||
| } | ||
| return Base::VisitBinaryOperator(S); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,9 +1,9 @@ | ||
| // RUN: %clang_cc1 -emit-llvm-only -fprofile-instrument=clang -fcoverage-mcdc -Werror -Wno-unused-value %s -verify | ||
| // RUN: %clang_cc1 -emit-llvm-only -fprofile-instrument=clang -fcoverage-mcdc -Werror -Wno-unused-value %s -verify -fmcdc-max-conditions=2 | ||
|
|
||
| int foo(int x); | ||
|
|
||
| int main(void) { | ||
| int a, b, c; | ||
| a && foo( b && c ); // expected-warning{{unsupported MC/DC boolean expression; contains an operation with a nested boolean expression. Expression will not be covered}} | ||
| a && foo( a && b && c ); // expected-warning{{unsupported MC/DC boolean expression; number of conditions (3) exceeds max (2). Expression will not be covered}} | ||
| return 0; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you document what splitting means in the code?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.