Skip to content
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

[analyzer] Fix zext assertion failure in loop unrolling #121203

Merged
merged 7 commits into from
Dec 28, 2024

Conversation

shenjunjiekoda
Copy link
Contributor

@shenjunjiekoda shenjunjiekoda commented Dec 27, 2024

The current implementation of APInt extension in the code can trigger an assertion failure when the zext function is called with a target width smaller than the current bit width. For example:

if (InitNum.getBitWidth() != BoundNum.getBitWidth()) {
    InitNum = InitNum.zext(BoundNum.getBitWidth());
    BoundNum = BoundNum.zext(InitNum.getBitWidth());
}

This logic does not guarantee that the zext target width is always greater than or equal to the current bit width, leading to potential crashes.

Expected Behavior:

  • Ensure InitNum and BoundNum are extended to the maximum of their respective widths.
  • Prevent assertion failures by enforcing correct zext usage.

Fixes #121201

@llvmbot llvmbot added clang Clang issues not falling into any other category clang:static analyzer labels Dec 27, 2024
@llvmbot
Copy link
Member

llvmbot commented Dec 27, 2024

@llvm/pr-subscribers-clang

Author: JOSTAR (shenjunjiekoda)

Changes

The current implementation of APInt extension in the code can trigger an assertion failure when the zext function is called with a target width smaller than the current bit width. For example:

if (InitNum.getBitWidth() != BoundNum.getBitWidth()) {
    InitNum = InitNum.zext(BoundNum.getBitWidth());
    BoundNum = BoundNum.zext(InitNum.getBitWidth());
}

This logic does not guarantee that the zext target width is always greater than or equal to the current bit width, leading to potential crashes.

Expected Behavior:

  • Ensure InitNum and BoundNum are extended to the maximum of their respective widths.
  • Prevent assertion failures by enforcing correct zext usage.

Depend on ##121201


Full diff: https://github.com/llvm/llvm-project/pull/121203.diff

1 Files Affected:

  • (modified) clang/lib/StaticAnalyzer/Core/LoopUnrolling.cpp (+6-4)
diff --git a/clang/lib/StaticAnalyzer/Core/LoopUnrolling.cpp b/clang/lib/StaticAnalyzer/Core/LoopUnrolling.cpp
index 96f5d7c44baf89..e3b27e22712b58 100644
--- a/clang/lib/StaticAnalyzer/Core/LoopUnrolling.cpp
+++ b/clang/lib/StaticAnalyzer/Core/LoopUnrolling.cpp
@@ -283,10 +283,12 @@ static bool shouldCompletelyUnroll(const Stmt *LoopStmt, ASTContext &ASTCtx,
   llvm::APInt InitNum =
       Matches[0].getNodeAs<IntegerLiteral>("initNum")->getValue();
   auto CondOp = Matches[0].getNodeAs<BinaryOperator>("conditionOperator");
-  if (InitNum.getBitWidth() != BoundNum.getBitWidth()) {
-    InitNum = InitNum.zext(BoundNum.getBitWidth());
-    BoundNum = BoundNum.zext(InitNum.getBitWidth());
-  }
+  unsigned MaxWidth = std::max(InitNum.getBitWidth(), BoundNum.getBitWidth());
+
+  if (InitNum.getBitWidth() != MaxWidth)
+      InitNum = InitNum.zext(MaxWidth);
+  if (BoundNum.getBitWidth() != MaxWidth)
+      BoundNum = BoundNum.zext(MaxWidth);
 
   if (CondOp->getOpcode() == BO_GE || CondOp->getOpcode() == BO_LE)
     maxStep = (BoundNum - InitNum + 1).abs().getZExtValue();

@llvmbot
Copy link
Member

llvmbot commented Dec 27, 2024

@llvm/pr-subscribers-clang-static-analyzer-1

Author: JOSTAR (shenjunjiekoda)

Changes

The current implementation of APInt extension in the code can trigger an assertion failure when the zext function is called with a target width smaller than the current bit width. For example:

if (InitNum.getBitWidth() != BoundNum.getBitWidth()) {
    InitNum = InitNum.zext(BoundNum.getBitWidth());
    BoundNum = BoundNum.zext(InitNum.getBitWidth());
}

This logic does not guarantee that the zext target width is always greater than or equal to the current bit width, leading to potential crashes.

Expected Behavior:

  • Ensure InitNum and BoundNum are extended to the maximum of their respective widths.
  • Prevent assertion failures by enforcing correct zext usage.

Depend on ##121201


Full diff: https://github.com/llvm/llvm-project/pull/121203.diff

1 Files Affected:

  • (modified) clang/lib/StaticAnalyzer/Core/LoopUnrolling.cpp (+6-4)
diff --git a/clang/lib/StaticAnalyzer/Core/LoopUnrolling.cpp b/clang/lib/StaticAnalyzer/Core/LoopUnrolling.cpp
index 96f5d7c44baf89..e3b27e22712b58 100644
--- a/clang/lib/StaticAnalyzer/Core/LoopUnrolling.cpp
+++ b/clang/lib/StaticAnalyzer/Core/LoopUnrolling.cpp
@@ -283,10 +283,12 @@ static bool shouldCompletelyUnroll(const Stmt *LoopStmt, ASTContext &ASTCtx,
   llvm::APInt InitNum =
       Matches[0].getNodeAs<IntegerLiteral>("initNum")->getValue();
   auto CondOp = Matches[0].getNodeAs<BinaryOperator>("conditionOperator");
-  if (InitNum.getBitWidth() != BoundNum.getBitWidth()) {
-    InitNum = InitNum.zext(BoundNum.getBitWidth());
-    BoundNum = BoundNum.zext(InitNum.getBitWidth());
-  }
+  unsigned MaxWidth = std::max(InitNum.getBitWidth(), BoundNum.getBitWidth());
+
+  if (InitNum.getBitWidth() != MaxWidth)
+      InitNum = InitNum.zext(MaxWidth);
+  if (BoundNum.getBitWidth() != MaxWidth)
+      BoundNum = BoundNum.zext(MaxWidth);
 
   if (CondOp->getOpcode() == BO_GE || CondOp->getOpcode() == BO_LE)
     maxStep = (BoundNum - InitNum + 1).abs().getZExtValue();

Copy link

github-actions bot commented Dec 27, 2024

✅ With the latest revision this PR passed the C/C++ code formatter.

@steakhal
Copy link
Contributor

Hey, could you add a test for this PR that would crash on main, but wouldn't with this patch?

@steakhal steakhal self-requested a review December 27, 2024 11:08
@steakhal
Copy link
Contributor

steakhal commented Dec 27, 2024

Could you please add a RUN line to the test so that the test actually gets invoked by the check-clang-analysis build target, thus get tested in CI, and a // no-crash comment at the line where previously crashed during interpretation?
I'd also prefer a clang-formated test file if possible.
Are you sure the test case is minimal and couldn't be minimized further now that you know why it crashes?

@shenjunjiekoda
Copy link
Contributor Author

shenjunjiekoda commented Dec 27, 2024

Could you please add a RUN line to the test so that the test actually gets invoked by the check-clang-analysis build target, thus get tested in CI, and a // no-crash comment at the line where previously crashed during interpretation? I'd also prefer a clang-formated test file if possible. Are you sure the test case is minimal and couldn't be minimized further now that you know why it crashes?

The crash occurred due to a failed assertion in the zext method of APInt. The zext function requires the following condition to be met:

// Zero extend to a new width.
APInt APInt::zext(unsigned width) const {
  assert(width >= BitWidth && "Invalid APInt ZeroExtend request");
  // ...
}

However, the original logic in clang/lib/StaticAnalyzer/Core/LoopUnrolling.cpp used an inequality check (!=) to determine if the widths were mismatched. This could lead to a scenario where one of the zext calls in the if block triggers the assertion failure internally:

static bool shouldCompletelyUnroll(const Stmt *LoopStmt, ASTContext &ASTCtx,
                                   ExplodedNode *Pred, unsigned &maxStep) {

  // ...
  if (InitNum.getBitWidth() != BoundNum.getBitWidth()) {
    InitNum = InitNum.zext(BoundNum.getBitWidth());
    BoundNum = BoundNum.zext(InitNum.getBitWidth());
  }

For the test case, I used the cvise tool to simplify the test/std-test.cc file from the libfmt repo while ensuring it remained free of compilation errors. This test case appears to be the minimal version that cvise could produce.

Copy link
Contributor

@steakhal steakhal left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the nice reproducer!
The test looks a bit verbose to my taste, but it's okay as-is.
I had some deeper thoughts of the fix inline to settle before we could merge this.

Thanks again for working on this issue!

Copy link
Contributor

@steakhal steakhal left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks wonderful now.
I had recommended one simplification, but other than that we can merge this.

Thanks again!

clang/lib/StaticAnalyzer/Core/LoopUnrolling.cpp Outdated Show resolved Hide resolved
@shenjunjiekoda
Copy link
Contributor Author

Looks wonderful now. I had recommended one simplification, but other than that we can merge this.

Thanks again!

Thank you for your patient and careful response!

@steakhal steakhal merged commit 8e965d8 into llvm:main Dec 28, 2024
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:static analyzer clang Clang issues not falling into any other category
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[analyzer] loop unrolling crash
3 participants