[Lldb-commits] [lldb] [lldb] Clamp out-of-range shift amounts in Scalar (PR #217027)

Yao Qi via lldb-commits lldb-commits at lists.llvm.org
Fri Aug 21 13:58:44 PDT 2026


https://github.com/qiyao updated https://github.com/llvm/llvm-project/pull/217027

>From 07989f8a3f704f34f4518152f5e41e883199d724 Mon Sep 17 00:00:00 2001
From: Yao Qi <yao_qi at apple.com>
Date: Wed, 24 Jun 2026 11:24:58 +0100
Subject: [PATCH 1/3] [lldb] Clamp out-of-range shift amounts in Scalar

`Scalar::operator>>=` and `Scalar::ExtractBitfield` pass their shift
amount straight to `APSInt::operator>>=(unsigned)`, which asserts when the
amount exceeds the operand's bit width:

```
Assertion failed: (ShiftAmt <= BitWidth && "Invalid shift amount"),
function ashrInPlace, file APInt.h, line 842.
    #7  llvm::APInt::ashrInPlace(unsigned int)
    #8  lldb_private::Scalar::ExtractBitfield(unsigned int, unsigned int)
    #9  lldb_private::DWARFExpression::Evaluate(...)
   #11  LLVMFuzzerTestOneInput
```

The DWARF expression evaluator reaches both from untrusted operands
(`DW_OP_shra` shift counts and `DW_OP_bit_piece` bit offsets), so
`lldb-dwarf-expression-fuzzer` aborts the process on a shift wider than the
value.  A shift amount at or beyond the width is well-defined as a sign or
zero fill, so clamp it rather than reject it.  `operator>>=` uses
`getLimitedValue`, which also avoids the `getZExtValue` assertion when the
amount itself needs more than 64 bits.

`operator<<=` and `ShiftRightLogical` (`DW_OP_shl` and `DW_OP_shr`) already
pass an `APInt` amount, and those `APInt` overloads clamp internally the same
way, so they need no change.
---
 lldb/source/Utility/Scalar.cpp                | 13 ++++--
 .../Expression/DWARFExpressionTest.cpp        | 22 ++++++++++
 lldb/unittests/Utility/ScalarTest.cpp         | 43 +++++++++++++++++++
 3 files changed, 74 insertions(+), 4 deletions(-)

diff --git a/lldb/source/Utility/Scalar.cpp b/lldb/source/Utility/Scalar.cpp
index f2c18cdd896da..440fef9d564a6 100644
--- a/lldb/source/Utility/Scalar.cpp
+++ b/lldb/source/Utility/Scalar.cpp
@@ -18,6 +18,7 @@
 #include "llvm/ADT/SmallString.h"
 #include "llvm/ADT/StringExtras.h"
 
+#include <algorithm>
 #include <cinttypes>
 #include <cstdio>
 
@@ -471,10 +472,12 @@ bool Scalar::ShiftRightLogical(const Scalar &rhs) {
 }
 
 Scalar &Scalar::operator>>=(const Scalar &rhs) {
-  if (m_type == e_int && rhs.m_type == e_int)
-    m_integer >>= rhs.m_integer.getZExtValue();
-  else
+  if (m_type == e_int && rhs.m_type == e_int) {
+    // Shifting past the width is a sign or zero fill; APSInt asserts on it.
+    m_integer >>= rhs.m_integer.getLimitedValue(m_integer.getBitWidth());
+  } else {
     m_type = e_void;
+  }
   return *this;
 }
 
@@ -820,7 +823,9 @@ bool Scalar::ExtractBitfield(uint32_t bit_size, uint32_t bit_offset) {
     break;
 
   case Scalar::e_int:
-    m_integer >>= bit_offset;
+    // bit_offset is externally supplied and may exceed the width, which the
+    // APSInt shift asserts on.
+    m_integer >>= std::min(bit_offset, m_integer.getBitWidth());
     m_integer = m_integer.extOrTrunc(bit_size).extOrTrunc(8 * GetByteSize());
     return true;
   }
diff --git a/lldb/unittests/Expression/DWARFExpressionTest.cpp b/lldb/unittests/Expression/DWARFExpressionTest.cpp
index c2752e16672f7..656b3726eb2bb 100644
--- a/lldb/unittests/Expression/DWARFExpressionTest.cpp
+++ b/lldb/unittests/Expression/DWARFExpressionTest.cpp
@@ -802,6 +802,13 @@ TEST(DWARFExpression, DW_OP_piece) {
       ExpectHostAddress(expected_host_buffer));
 }
 
+TEST(DWARFExpression, DW_OP_bit_piece) {
+  // Extract the high 16 bits of a 32-bit value.
+  EXPECT_THAT_EXPECTED(Evaluate({DW_OP_const4u, 0x44, 0x33, 0x22, 0x11,
+                                 DW_OP_bit_piece, 16, 16}),
+                       ExpectScalar(0x1122));
+}
+
 TEST(DWARFExpression, DW_OP_implicit_value) {
   unsigned char bytes = 4;
 
@@ -1096,6 +1103,14 @@ TEST(DWARFExpression, DW_OP_shl_overflow_count) {
   SUCCEED();
 }
 
+TEST(DWARFExpression, DW_OP_shra_overflow_count) {
+  // Shift count exceeding scalar bit width must not crash. The arithmetic
+  // shift sign-fills, so -1 stays -1.
+  EXPECT_THAT_EXPECTED(Evaluate({DW_OP_const1s, static_cast<uint8_t>(-1),
+                                 DW_OP_const1u, 99, DW_OP_shra}),
+                       ExpectScalar(static_cast<int32_t>(-1)));
+}
+
 TEST(DWARFExpression, DW_OP_push_object_address_no_object) {
   // Without an object_address_ptr, must fail cleanly.
   EXPECT_THAT_EXPECTED(Evaluate({DW_OP_push_object_address}), llvm::Failed());
@@ -1152,6 +1167,13 @@ TEST(DWARFExpression, DW_OP_bit_piece_overflow) {
       ExpectScalar(5));
 }
 
+TEST(DWARFExpression, DW_OP_bit_piece_offset_overflow) {
+  // A bit offset beyond the scalar width must not crash; shifting the whole
+  // value out leaves zero. ULEB128(1000) = {0xE8, 0x07}.
+  EXPECT_THAT_EXPECTED(Evaluate({DW_OP_lit1, DW_OP_bit_piece, 32, 0xE8, 0x07}),
+                       ExpectScalar(0));
+}
+
 TEST(DWARFExpression, DW_OP_bit_piece_empty_stack) {
   EXPECT_THAT_EXPECTED(Evaluate({DW_OP_bit_piece, 0x08, 0x00}), llvm::Failed());
 }
diff --git a/lldb/unittests/Utility/ScalarTest.cpp b/lldb/unittests/Utility/ScalarTest.cpp
index 869a5809e6d14..671659859cdc8 100644
--- a/lldb/unittests/Utility/ScalarTest.cpp
+++ b/lldb/unittests/Utility/ScalarTest.cpp
@@ -131,6 +131,29 @@ TEST(ScalarTest, RightShiftOperator) {
   ASSERT_EQ(e >> c, e_scalar >> c_scalar);
 }
 
+TEST(ScalarTest, RightShiftOutOfRange) {
+  // A shift of exactly the width is a sign fill for a signed value. This is
+  // in range for the APSInt shift, which asserts only past the width.
+  Scalar a(static_cast<int32_t>(-1));
+  a >>= Scalar(static_cast<uint32_t>(32));
+  EXPECT_EQ(a, Scalar(static_cast<int32_t>(-1)));
+
+  // A shift past the width fills the same way.
+  Scalar b(static_cast<int32_t>(0x12345678));
+  b >>= Scalar(static_cast<uint32_t>(1000));
+  EXPECT_EQ(b, Scalar(static_cast<int32_t>(0)));
+
+  // An unsigned value is zero filled instead.
+  Scalar c(static_cast<uint32_t>(0xFFFFFFFF));
+  c >>= Scalar(static_cast<uint32_t>(33));
+  EXPECT_EQ(c, Scalar(static_cast<uint32_t>(0)));
+
+  // The shift amount itself can need more than 64 bits to represent.
+  Scalar d(static_cast<int32_t>(-1));
+  d >>= Scalar(APInt::getOneBitSet(128, 70));
+  EXPECT_EQ(d, Scalar(static_cast<int32_t>(-1)));
+}
+
 TEST(ScalarTest, GetBytes) {
   uint8_t Storage[256];
   int a = 0x01020304;
@@ -293,6 +316,26 @@ TEST(ScalarTest, ExtractBitfield) {
   EXPECT_EQ(u_scalar, b2);
 }
 
+TEST(ScalarTest, ExtractBitfieldOutOfRange) {
+  uint32_t len = sizeof(int32_t) * 8;
+
+  // A bit offset of exactly the width is a sign fill for a signed value. This
+  // is in range for the APSInt shift, which asserts only past the width.
+  Scalar s_scalar(static_cast<int32_t>(-1));
+  ASSERT_TRUE(s_scalar.ExtractBitfield(len, len));
+  EXPECT_EQ(s_scalar, Scalar(static_cast<int32_t>(-1)));
+
+  // A bit offset past the width fills the same way.
+  Scalar s_far_scalar(static_cast<int32_t>(-1));
+  ASSERT_TRUE(s_far_scalar.ExtractBitfield(len, 1000));
+  EXPECT_EQ(s_far_scalar, Scalar(static_cast<int32_t>(-1)));
+
+  // An unsigned value is zero filled instead.
+  Scalar u_scalar(static_cast<uint32_t>(0xFFFFFFFF));
+  ASSERT_TRUE(u_scalar.ExtractBitfield(len, 1000));
+  EXPECT_EQ(u_scalar, Scalar(static_cast<uint32_t>(0)));
+}
+
 template <typename T> static std::string ScalarGetValue(T value) {
   StreamString stream;
   Scalar(value).GetValue(stream, false);

>From 06272027398a0c34e467078d1a9caed6d8c4eaca Mon Sep 17 00:00:00 2001
From: Yao Qi <qiyaoltc at gmail.com>
Date: Fri, 21 Aug 2026 21:58:12 +0100
Subject: [PATCH 2/3] Update lldb/source/Utility/Scalar.cpp

Co-authored-by: Jonas Devlieghere <jonas at devlieghere.com>
---
 lldb/source/Utility/Scalar.cpp | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/lldb/source/Utility/Scalar.cpp b/lldb/source/Utility/Scalar.cpp
index 440fef9d564a6..a72ff2d2bebf7 100644
--- a/lldb/source/Utility/Scalar.cpp
+++ b/lldb/source/Utility/Scalar.cpp
@@ -823,8 +823,7 @@ bool Scalar::ExtractBitfield(uint32_t bit_size, uint32_t bit_offset) {
     break;
 
   case Scalar::e_int:
-    // bit_offset is externally supplied and may exceed the width, which the
-    // APSInt shift asserts on.
+    // Avoid APSInt assertion when exceeding the width.
     m_integer >>= std::min(bit_offset, m_integer.getBitWidth());
     m_integer = m_integer.extOrTrunc(bit_size).extOrTrunc(8 * GetByteSize());
     return true;

>From 4daf60becff3be88e37165599999d44a44ffed8f Mon Sep 17 00:00:00 2001
From: Yao Qi <qiyaoltc at gmail.com>
Date: Fri, 21 Aug 2026 21:58:32 +0100
Subject: [PATCH 3/3] Update lldb/source/Utility/Scalar.cpp

Co-authored-by: Jonas Devlieghere <jonas at devlieghere.com>
---
 lldb/source/Utility/Scalar.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lldb/source/Utility/Scalar.cpp b/lldb/source/Utility/Scalar.cpp
index a72ff2d2bebf7..d332c5f7fa06c 100644
--- a/lldb/source/Utility/Scalar.cpp
+++ b/lldb/source/Utility/Scalar.cpp
@@ -473,7 +473,7 @@ bool Scalar::ShiftRightLogical(const Scalar &rhs) {
 
 Scalar &Scalar::operator>>=(const Scalar &rhs) {
   if (m_type == e_int && rhs.m_type == e_int) {
-    // Shifting past the width is a sign or zero fill; APSInt asserts on it.
+    // Avoid APSInt assertion when exceeding the width is a sign or zero fill.
     m_integer >>= rhs.m_integer.getLimitedValue(m_integer.getBitWidth());
   } else {
     m_type = e_void;



More information about the lldb-commits mailing list