Skip to content

Use explicit width for finite width parameters #687

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

Merged
merged 5 commits into from
Sep 25, 2024
Merged

Conversation

elazarg
Copy link
Collaborator

@elazarg elazarg commented Sep 25, 2024

Summary by CodeRabbit

  • New Features

    • Enhanced interval handling with support for 64-bit integer evaluations.
    • Introduced new template methods in the z_number class for improved type casting and fitting checks.
  • Bug Fixes

    • Improved precision and correctness in interval arithmetic and relational checks.
  • Refactor

    • Streamlined the bound_t and interval_t classes by simplifying method signatures and enhancing type safety.
    • Updated the z_number class to utilize templates for integral type handling, improving readability and usability.
    • Standardized type checking and casting methods across various components for consistency.

Copy link

coderabbitai bot commented Sep 25, 2024

Walkthrough

The changes across multiple files focus on enhancing type handling, particularly through the introduction of template methods for type checking and casting. Key modifications include updates to arithmetic operations, with a shift from 32-bit to 64-bit representations for better precision. The z_number class has been improved for integral type casting and fitting checks, while various functions have been standardized to use a more generic template-based approach.

Changes

Files Change Summary
src/crab/array_domain.cpp Standardized type checking and casting methods across functions to use template-based approaches.
src/crab/ebpf_domain.cpp Updated interval and constraint handling for signed and unsigned integers to support 64-bit operations.
src/crab/interval.cpp Modified arithmetic operations; improved handling of zero cases and streamlined logic for bitwise operations.
src/crab/split_dbm.cpp Changed type checking and casting methods to a more generic template-based approach.
src/crab/var_factory.cpp Updated casting method in cell_var to use template-based casting.
src/crab_utils/bignums.hpp Enhanced z_number class with template methods for fitting checks and casting; improved code readability.
src/crab_verifier.cpp Updated type checking and casting in get_max_loop_count method to use template-based methods.
src/test/conformance_check.cpp Modified type checking and casting methods in the main function.

Possibly related PRs

  • Use template for truncation #685: The changes in this PR involve implementing a template for truncation, which aligns with the main PR's updates to truncation methods for handling 64-bit intervals, enhancing the precision of numeric handling.

🐰 In the land of code, where changes bloom,
A hop of logic chased away the gloom.
With 64 bits, our numbers now dance,
Templates and bounds, they take their chance.
So here’s to the tweaks, both clever and bright,
A rabbit’s delight in the coding night! 🐇✨


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 11

📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE

📥 Commits

Files that changed from the base of the PR and between 4ee1d77 and fecfa5e.

📒 Files selected for processing (4)
  • src/crab/ebpf_domain.cpp (19 hunks)
  • src/crab/interval.cpp (9 hunks)
  • src/crab/interval.hpp (9 hunks)
  • src/crab_utils/bignums.hpp (6 hunks)
🔇 Additional comments not posted (37)
src/crab/interval.cpp (8)

32-33: LGTM: Consistent type usage

The change from z_bound to bound_t improves type consistency within the interval_t class. This modification aligns with the overall goal of using explicit width for finite width parameters.


37-38: LGTM: Consistent type usage continued

The replacement of z_bound with bound_t continues in this segment, maintaining consistency with the previous changes. This modification further enhances the type consistency within the interval_t class.


50-50: LGTM: Improved syntax for bound determination

The use of std::min and std::max with initializer lists is a good improvement. It simplifies the syntax, makes the code more concise, and leverages modern C++ features. This change enhances readability while maintaining the same functionality.


79-80: LGTM: Consistent type usage in SDiv method

The replacement of z_bound with bound_t in the SDiv method maintains consistency with the changes made in other parts of the class. This continued effort to use explicit width for finite width parameters enhances the overall type consistency of the interval_t class.

Also applies to: 84-85


97-97: LGTM: Consistent improvement in bound determination

The use of std::min and std::max with initializer lists in the SDiv method is consistent with the changes made in the operator/ method. This modification continues to simplify the syntax, enhance readability, and leverage modern C++ features while maintaining the same functionality.


215-216: LGTM: Consistent type usage in URem method

The replacement of z_bound with bound_t in the URem method maintains consistency with the changes made throughout the interval_t class. This continued effort to use explicit width for finite width parameters further enhances the overall type consistency of the class.

Also applies to: 220-221


269-269: LGTM: Simplified minimum value determination

The use of std::min instead of a ternary operator is a good improvement. It simplifies the syntax, makes the code more concise, and is consistent with the use of std::min in other parts of the class. This change enhances readability while maintaining the same functionality.


Line range hint 1-424: Overall assessment: Consistent improvements in type usage and syntax

The changes in this file consistently replace z_bound with bound_t, improving type consistency throughout the interval_t class. Additionally, the use of std::min and std::max with initializer lists simplifies the syntax for bound determination in several methods. These modifications align well with the PR objective of using explicit width for finite width parameters, enhance code consistency, and leverage modern C++ features. The changes maintain the existing functionality while improving code readability and maintainability.

src/crab_utils/bignums.hpp (5)

38-38: No action needed for access specifier placement.

The public: access specifier is correctly placed, ensuring proper access control in the class.


86-90: Well-implemented generic fits<T>() method.

The addition of the fits<T>() template method enhances the flexibility and reusability of the code by allowing checks for any integral type T. Good use of C++20 concepts with std::integral and the [[nodiscard]] attribute to prevent unintentional ignoring of the return value.


94-109: Refactored fits_*() methods improve maintainability.

The methods fits_sint32(), fits_uint32(), fits_sint64(), and fits_uint64() now leverage the generic fits<T>() template method. This change reduces code duplication and enhances maintainability.


212-215: Extended truncation to support 8-bit and 16-bit signed widths.

Adding cases for 8-bit and 16-bit widths in truncate_to_signed_finite_width() enhances functionality by allowing truncation to smaller signed types, increasing flexibility.


226-229: Extended truncation to support 8-bit and 16-bit unsigned widths.

The inclusion of 8-bit and 16-bit cases in truncate_to_unsigned_finite_width() improves the method's utility by supporting a wider range of unsigned truncation widths.

src/crab/interval.hpp (4)

139-140: Ensure correct handling of negative bounds when casting to uint64_t

Casting negative numbers to uint64_t in unsigned division may lead to unexpected large positive values due to modulo arithmetic. In the UDiv method, please verify that casting _n and x._n to uint64_t when they are negative is intentional and that the bound calculations handle negative values correctly.


154-155: Ensure correct handling of negative bounds when casting to uint64_t in UMod

Similar to the UDiv method, casting negative numbers to uint64_t in the UMod method may result in unexpected values. Please confirm that this casting is intentional and that the modulo operation behaves correctly with negative bounds.


402-402: Verify compatibility with C++ standards for std::min and std::max with initializer lists

The use of std::min and std::max with an initializer list in the expression std::min({ll, lu, ul, uu}) requires C++11 or higher. Ensure that the project is compiled with a compatible C++ standard (C++11 or later) to support this syntax.


655-655: Confirm noexcept usage in to_string function

The noexcept specifier has been added to the to_string function:

std::string to_string(const crab::interval_t& interval) noexcept;

Ensure that all operations within the to_string implementation are exception-safe and do not throw, to adhere to the noexcept guarantee.

src/crab/ebpf_domain.cpp (20)

134-136: Correct Handling of Nonnegative 64-bit Intervals

The modification at line 134 changes the condition to explicitly use crab::interval_t::nonnegative(64) instead of the implicit 32-bit default. This ensures that when dealing with 64-bit signed equalities, the nonnegative interval is accurately represented.


209-210: Truncating Intervals to 32-bit Signed Integers

At lines 209-210, the code truncates intervals to 32-bit signed integers using truncate_to_sint(32) when they exceed the 32-bit signed range. This ensures proper handling of intervals that could cause overflow in 32-bit operations.


218-219: Correct Splitting of Left Interval into Positive and Negative Parts

Lines 218-219 correctly split left_interval into left_interval_positive and left_interval_negative based on the 64-bit nonnegative and negative intervals. This allows for proper handling of signed intervals in subsequent computations.


225-227: Handling of Signed Intervals with Unsigned Representations

In lines 225-227, when the signed interval is top but the unsigned interval is precise, the code splits the interval into positive and negative parts accordingly. This ensures accurate analysis even when the signed interval lacks precision.


236-237: Ensuring Intervals Fit Within 64-bit Signed Integers

Lines 236-237 truncate any intervals that do not fit within 64-bit signed integers using truncate_to_sint(64). This prevents potential overflow issues and maintains consistency in interval representations.


255-256: Adjustments for 32-bit and 64-bit Unsigned Intervals

At lines 255-256 and 261-262 in get_unsigned_intervals, the code checks whether the intervals fit within 32-bit nonnegative ranges and sets is64 accordingly. Intervals not fitting within 32-bit are truncated to 32-bit unsigned integers. This logic ensures proper handling of unsigned intervals based on their bit-width.

Also applies to: 261-262


270-271: Splitting Left Interval into Low and High Unsigned Parts

Lines 270-271 appropriately split left_interval into left_interval_low and left_interval_high for 64-bit unsigned integers. This allows for accurate comparison and analysis of unsigned intervals across their full range.


277-278: Handling Precise Signed Intervals Represented as Unsigned

At lines 277-278, when the unsigned interval is top but the signed interval is precise, the code creates unsigned intervals from the signed bounds. This ensures that the analysis does not lose precision when dealing with values that can only be accurately represented in one form.


286-287: Truncating Intervals to 64-bit Unsigned Integers

Lines 286-287 ensure that all intervals are truncated to fit within 64-bit unsigned integers using truncate_to_uint(64). This maintains consistency and prevents potential overflows in unsigned arithmetic.


300-305: Improved Comparison Logic in assume_signed_64bit_lt

Lines 300-305 enhance the logic for signed less-than comparisons in 64-bit intervals. The code correctly handles cases when the right interval is negative or nonnegative, ensuring accurate constraints are applied based on the sign and range of the intervals.


323-332: Refinement of 32-bit Signed Less-Than Comparisons

At lines 323-332 and 340-341, the code refines the handling of 32-bit signed less-than comparisons in assume_signed_32bit_lt. The updated conditions account for the signedness and bounds of the intervals, improving the correctness of the analysis.

Also applies to: 340-341


358-369: Enhancements in Signed Greater-Than Comparisons for 64-bit

Lines 358-369 in assume_signed_64bit_gt improve the handling of signed greater-than comparisons for 64-bit intervals, ensuring that both positive and negative ranges are correctly considered and that appropriate constraints are applied.


389-407: Improvements in Signed Greater-Than Comparisons for 32-bit

Lines 389-407 refine the logic in assume_signed_32bit_gt for 32-bit signed greater-than comparisons. The updated code ensures accurate handling of different interval ranges and applies correct relational constraints.


Line range hint 491-519: Enhanced Unsigned Less-Than Comparisons in 64-bit Context

Lines 491-519 and 523 improve the logic in assume_unsigned_64bit_lt for unsigned less-than comparisons. The code correctly handles various cases, including intervals representable as both signed and unsigned values, ensuring proper constraint application.

Also applies to: 523-523


540-551: Refined Unsigned Less-Than Comparisons for 32-bit

At lines 540-551, the code in assume_unsigned_32bit_lt is updated to more accurately handle 32-bit unsigned less-than comparisons, considering the signedness and bounds of the intervals involved.


569-583: Improvements in Unsigned Greater-Than Comparisons for 64-bit

Lines 569-583 in assume_unsigned_64bit_gt enhance the handling of unsigned greater-than comparisons for 64-bit intervals, ensuring that constraints reflect the correct relationships between intervals in different ranges.


601-606: Refinement of Unsigned Greater-Than Comparisons for 32-bit

Lines 601-606 update assume_unsigned_32bit_gt to handle 32-bit unsigned greater-than comparisons more accurately, taking into account different interval representations and ensuring correct constraint applications.


Line range hint 633-654: Handling of Unsigned Not-Equal and Comparison Operations

Lines 633-654 in assume_unsigned_cst_interval address the logic for handling unsigned not-equal, less-than, and greater-than comparisons. The code ensures that proper relational constraints are derived based on whether the intervals are singleton values and the specific comparison operation.


738-741: Correct Handling of 64-bit Equality Comparisons

Lines 738-741 in assume_cst_reg correctly handle equality comparisons for 64-bit values, ensuring that both signed and unsigned intervals are considered when applying constraints.


203-204: Potential Confusion in is64 Flag Assignment

At lines 203-204, the condition checks if both left_interval and right_interval are within 32-bit nonnegative or negative ranges, and then sets is64 = true. This seems counterintuitive, as one might expect is64 to remain false when both intervals fit within 32-bit. Please verify that setting is64 = true in this case is intentional and aligns with the desired behavior.

You can run the following script to review all occurrences of is64 assignments and their contexts:

This can help ensure that the logic for handling 32-bit and 64-bit intervals is implemented as intended.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 10

📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE

📥 Commits

Files that changed from the base of the PR and between fecfa5e and b7a7b2b.

📒 Files selected for processing (2)
  • src/crab/interval.hpp (9 hunks)
  • src/crab_utils/bignums.hpp (8 hunks)
🧰 Additional context used
📓 Learnings (1)
src/crab/interval.hpp (1)
Learnt from: elazarg
PR: vbpf/ebpf-verifier#687
File: src/crab/interval.hpp:256-262
Timestamp: 2024-09-25T11:00:29.008Z
Learning: When comparing two unsigned integral types of the same size using '>', the comparison behaves correctly without needing special handling.
🔇 Additional comments not posted (7)
src/crab_utils/bignums.hpp (3)

106-111: Refactor improves code maintainability via templated fits<T>()

The introduction of the templated fits<T>() method enhances code reuse and reduces duplication in the fits_*() methods. This change improves maintainability and eases future expansions to support additional integral types.


29-35: 🛠️ Refactor suggestion

Consider integrating truncate_to_coderabbit with existing truncate_to<T>()

The new truncate_to_coderabbit function for unsigned integral types appears to duplicate the functionality of the existing truncate_to<T>() template function. To avoid redundancy and improve maintainability, consider integrating this implementation into truncate_to<T>() or replacing the existing function if the new implementation addresses previous issues.

⛔ Skipped due to learnings
Learnt from: elazarg
PR: vbpf/ebpf-verifier#685
File: src/crab_utils/bignums.hpp:22-27
Timestamp: 2024-09-24T10:52:52.162Z
Learning: In the `truncate_to` function, suggestions that result in the original code after inlining variables are unnecessary refactorings.

149-162: Verify correctness of casting logic in cast_to<U>()

The cast_to<U>() function for unsigned integral types includes logic to handle cases where the value fits into the signed counterpart S. Please ensure that this casting logic correctly handles all edge cases, particularly when casting negative values or values that exceed the limits of U.

To confirm the correctness, you could check the behavior with test cases covering boundary values:

✅ Verification successful

Casting Logic in cast_to<U>() Verified Correct

The cast_to<U>() function appropriately handles edge cases by verifying that values fit into the target unsigned type or its signed counterpart before casting. All usages in the codebase include necessary checks to prevent invalid casts of negative or oversized values.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Identify usages of `cast_to<U>()` and ensure they are correctly handling edge cases.

# Test: Search for calls to `cast_to<U>()` in the codebase.
rg --type cpp 'cast_to<.*>()'

Length of output: 523


Script:

#!/bin/bash
# Description: Extract surrounding lines of each `cast_to<U>()` usage to analyze handling of edge cases.

# Extract lines with `cast_to<U>()` along with 3 lines before and after for context.
rg --type cpp -C 3 'cast_to<.*>()' src/crab_utils/bignums.hpp src/crab/interval.hpp

Length of output: 1747

src/crab/interval.hpp (4)

256-262: Ensure correct initialization in templated constructor

The templated constructor for interval_t may not correctly initialize the interval when lb > ub. Currently, it sets _lb to 0 and _ub to -1, representing a bottom interval. Ensure that this approach aligns with the representation of an empty interval in your implementation.

Verify that setting _lb to 0 and _ub to -1 correctly represents a bottom interval within the codebase. You might want to check if other parts of the code interpret this representation as intended.


484-492: Consistent handling of invalid widths in truncate_to_sint

In the truncate_to_sint method, the default case in the switch statement calls CRAB_ERROR("Invalid width ", width);. Verify that CRAB_ERROR appropriately handles this error scenario (e.g., throws an exception or terminates the program) and that the function does not continue execution after this call.

Ensure that CRAB_ERROR is properly defined to handle errors and consider documenting its behavior for clarity.


496-504: Consistent handling of invalid widths in truncate_to_uint

Similar to truncate_to_sint, in the truncate_to_uint method, confirm that the default case and CRAB_ERROR usage correctly handle invalid width values.

Ensure consistent error handling across both methods and that CRAB_ERROR prevents further execution when an invalid width is provided.


651-651: Use of noexcept specifier in to_string function

The addition of the noexcept specifier to the to_string function is appropriate if the function is guaranteed not to throw exceptions. Ensure that all code within the function adheres to this guarantee.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE

📥 Commits

Files that changed from the base of the PR and between b7a7b2b and e9c4b17.

📒 Files selected for processing (1)
  • src/crab/interval.hpp (9 hunks)
🧰 Additional context used
📓 Learnings (1)
src/crab/interval.hpp (1)
Learnt from: elazarg
PR: vbpf/ebpf-verifier#687
File: src/crab/interval.hpp:256-262
Timestamp: 2024-09-25T11:00:29.008Z
Learning: When comparing two unsigned integral types of the same size using '>', the comparison behaves correctly without needing special handling.
🔇 Additional comments not posted (13)
src/crab/interval.hpp (13)

Line range hint 23-28: Constructor Implementation Looks Good

The implementation of the bound_t constructor correctly initializes the object and ensures proper handling of infinite bounds.


139-140: Potential Incorrect Handling of Negative Numbers in UDiv


154-155: Potential Incorrect Handling of Negative Numbers in UMod


256-262: Correct Implementation of Templated interval_t Constructor

The templated constructor for interval_t correctly initializes the interval and handles cases where lb > ub by setting the interval to bottom.


319-319: Correct Use of std::min and std::max in Union Operation

The use of std::min and std::max correctly computes the bounds for the interval union in the operator| method.


327-327: Correct Use of std::max and std::min in Intersection Operation

The use of std::max and std::min correctly computes the bounds for the interval intersection in the operator& method.


402-402: Efficient Computation of Interval Bounds in Multiplication

The use of std::min and std::max with initializer lists efficiently computes the bounds after multiplication in the operator* method.


486-492: Updated truncate_to_sint Function Correctly Uses Width Parameter

The truncate_to_sint function now accepts an integer width and correctly handles truncation based on the provided width.


498-504: Updated truncate_to_uint Function Correctly Uses Width Parameter

The truncate_to_uint function now accepts an integer width and correctly handles truncation based on the provided width.


508-524: Template Function truncate_to Correctly Handles Truncation

The templated truncate_to function accurately truncates the interval to the specified type, ensuring correct representation or returning the full range of the type if necessary.


530-536: Updated signed_int Function for Specified Widths

The signed_int static function now accepts an integer width and correctly returns the full interval for the specified signed integer type.


543-551: Updated unsigned_int Function for Specified Widths

The unsigned_int static function now accepts an integer width and correctly returns the full interval for the specified unsigned integer type.


653-653: Addition of noexcept Specifier to to_string Function

Marking the to_string function with noexcept indicates that it does not throw exceptions, which can improve reliability and allow for certain compiler optimizations.

@coveralls
Copy link

coveralls commented Sep 25, 2024

Coverage Status

coverage: 90.367% (-0.04%) from 90.41%
when pulling b1e7d5e on interval-explicit-width
into 4ee1d77 on main.

Signed-off-by: Elazar Gershuni <[email protected]>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 14

🧹 Outside diff range comments (3)
src/crab/interval.cpp (3)

Line range hint 56-93: LGTM! Consistent improvements with operator/.

The changes to the SDiv method mirror the improvements made to operator/, enhancing readability and robustness.

For consistency with the operator/ method, consider adding a similar comment explaining the rationale for division by 0:

 } else {
     // The eBPF ISA defines division by 0 as resulting in 0.
+    // This is a specific behavior of eBPF, different from traditional arithmetic.
     return interval_t(number_t(0));
 }

Line range hint 101-139: LGTM! Consistent improvements with operator/ and SDiv.

The changes to the UDiv method align with the improvements made to operator/ and SDiv, enhancing readability and robustness.

For consistency with the operator/ and SDiv methods, consider adding a similar comment explaining the rationale for division by 0:

 } else {
     // The eBPF ISA defines division by 0 as resulting in 0.
+    // This is a specific behavior of eBPF, different from traditional arithmetic.
     return interval_t(number_t(0));
 }

Line range hint 148-182: LGTM! Improved handling of edge cases.

The changes to the SRem method have improved its readability and robustness, particularly in handling edge cases like modulo by zero.

Consider adding a comment to explain the behavior when the divisor contains zero:

 if (x.contains(0)) {
     // The divisor contains 0.
+    // In this case, we split the interval and handle non-zero cases separately.
+    // We also include the original dividend as a possible result for x = 0.
     interval_t l(x._lb, bound_t(-1));
     interval_t u(bound_t(1), x._ub);
     return SRem(l) | SRem(u) | *this;
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE

📥 Commits

Files that changed from the base of the PR and between e9c4b17 and 281e418.

📒 Files selected for processing (4)
  • src/crab/ebpf_domain.cpp (20 hunks)
  • src/crab/interval.cpp (6 hunks)
  • src/crab/interval.hpp (10 hunks)
  • src/crab_utils/bignums.hpp (8 hunks)
🧰 Additional context used
📓 Learnings (1)
src/crab/interval.hpp (3)
Learnt from: elazarg
PR: vbpf/ebpf-verifier#687
File: src/crab/interval.hpp:568-569
Timestamp: 2024-09-25T13:22:39.531Z
Learning: In this project, 'svalue' and 'uvalue' are standard terms and should be used as is.
Learnt from: elazarg
PR: vbpf/ebpf-verifier#687
File: src/crab/interval.hpp:412-417
Timestamp: 2024-09-25T13:23:50.545Z
Learning: In the `interval_t` class, the `size()` method may return infinite size when the interval has infinite bounds, and this is acceptable behavior.
Learnt from: elazarg
PR: vbpf/ebpf-verifier#687
File: src/crab/interval.hpp:256-262
Timestamp: 2024-09-25T11:00:29.008Z
Learning: When comparing two unsigned integral types of the same size using '>', the comparison behaves correctly without needing special handling.
🔇 Additional comments not posted (56)
src/crab/interval.cpp (3)

268-283: LGTM! Minor readability improvements.

The changes to the Or method have improved its readability through the use of const auto for singleton checks.


289-295: LGTM! Minor readability improvements.

The changes to the Xor method have improved its readability through the use of const auto for singleton checks.


Line range hint 1-367: Overall, excellent improvements to readability and robustness.

The changes in this file consistently enhance the readability and robustness of the interval_t class operations. Key improvements include:

  1. Consistent use of const auto for singleton checks across all methods.
  2. Improved handling of edge cases, particularly for division and modulo operations.
  3. More explicit and easier-to-understand logic for operations involving zero.
  4. Use of std::min and std::max with initializer lists for more concise and less error-prone code.
  5. Enhanced precision for bitwise operations, especially for 32-bit scenarios.

These changes contribute to a more maintainable and reliable codebase. The minor suggestions provided in the review comments, if implemented, will further improve code clarity and consistency.

src/crab_utils/bignums.hpp (2)

232-235: Add support for 8-bit and 16-bit signed truncation

In the truncate_to_signed_finite_width(int finite_width) function, cases for 8-bit and 16-bit widths have been added. This enhancement allows truncation to smaller signed widths, which can be useful for certain applications.


246-249: Add support for 8-bit and 16-bit unsigned truncation

Similarly, in the truncate_to_unsigned_finite_width(int finite_width) function, cases for 8-bit and 16-bit widths have been added. This addition provides flexibility for truncating to smaller unsigned widths.

src/crab/interval.hpp (15)

23-23: Acknowledged parameter modification

The use of const qualifiers on parameters passed by value, such as const bool is_infinite, is acceptable and indicates that the parameter is not mutated within the function.


85-94: Correct handling of finite and infinite bounds in operator+

The updated implementation of operator+ in the bound_t class properly handles combinations of finite and infinite bounds, including error handling for undefined operations like -oo + +oo.


253-259: Template constructor for interval_t enhances flexibility

The addition of the templated constructor allows interval_t to be constructed with any integral type T, improving the class's usability. The constructor correctly handles cases where lb > ub by setting the interval to bottom.


316-316: Efficient union operation using std::min and std::max

The use of std::min and std::max with bound_t objects in the operator| method enhances code readability and ensures correct computation of interval bounds during the union operation.


324-324: Accurate intersection operation in operator&

The operator& method correctly computes the intersection of intervals by using std::max for lower bounds and std::min for upper bounds.


399-399: Optimized multiplication result calculation

The consolidation of possible products using std::min and std::max over an initializer list in the operator* method efficiently determines the resulting interval bounds after multiplication.


504-520: Proper handling of optional finite sizes in truncate_to

The truncate_to template method correctly checks for a finite size before proceeding, ensuring that optional values are safely dereferenced. This prevents potential runtime errors due to undefined optional values.


480-501: Refactored truncation methods enhance clarity

Replacing bool is64 with int width in truncate_to_sint and truncate_to_uint methods improves code clarity and flexibility, allowing for explicit width specifications.


526-532: Updated signed_int method with explicit widths

The signed_int static method now accepts an int width parameter, aligning it with modern coding practices and improving the explicitness of width specifications for signed intervals.


539-547: Consistent width handling in unsigned_int method

The unsigned_int method follows the same pattern as signed_int, using an int width parameter to specify the bit width of the unsigned interval, enhancing consistency across the codebase.


549-560: Clarified nonnegative interval construction

The nonnegative method correctly generates intervals representing non-negative numbers within a specified width, using an int width parameter for explicitness.


562-571: Correct implementation of negative method

The negative method provides intervals representing negative numbers within the given width. The method and comments accurately reflect its functionality.


585-589: Addition of full<T> template method for complete intervals

The full<T> method offers a convenient way to create an interval covering the entire range of a given integral type T, enhancing the utility of the interval_t class.


590-595: Introduction of high<T> method for upper interval ranges

The high<T> method generates intervals representing high ranges in unsigned integral types, specifically from INT_MAX + 1 to UINT_MAX, which is useful for certain unsigned computations.


649-649: Added noexcept specifier to to_string function

The addition of the noexcept specifier to the to_string function indicates that the function is guaranteed not to throw exceptions, which can improve performance and reliability.

src/crab/ebpf_domain.cpp (36)

134-134: Specified explicit 64-bit width in interval comparison

By adding 64 to crab::interval_t::nonnegative(64), the code now explicitly handles 64-bit intervals, enhancing accuracy in the comparison operation.


203-204: Used explicit 32-bit widths in signed interval checks

The inclusion of 32 in interval_t::nonnegative(32) and interval_t::negative(32) ensures correct handling of 32-bit signed intervals during comparisons.


209-210: Truncated intervals to 32-bit signed integers

By checking *interval <= interval_t::signed_int(32) and truncating with truncate_to_sint(32), the code accurately manages 32-bit signed integer intervals, preventing potential overflow issues.


218-219: Intersected intervals using explicit 64-bit nonnegative interval

Using interval_t::nonnegative(64) for intersection ensures that the positive intervals are correctly bounded within 64 bits.


225-227: Handled upper bounds with explicit 64-bit truncation

Truncating the upper bound to 64 bits using truncate_to_signed_finite_width(64) in line 227 ensures that interval calculations remain within the correct finite-width range.


230-231: Initialized intervals with explicit 64-bit widths

Setting left_interval_positive to interval_t::nonnegative(64) and left_interval_negative to interval_t::negative(64) explicitly defines the intervals within 64-bit bounds.


236-237: Ensured intervals are within 64-bit signed integer range

By truncating intervals exceeding interval_t::signed_int(64) to 64 bits using truncate_to_sint(64), the code maintains accurate interval representations.


255-256: Used explicit 32-bit widths in unsigned interval checks

The replacement of implicit widths with interval_t::nonnegative(32) and interval_t::unsigned_high(32) ensures correct handling of 32-bit unsigned intervals.


261-262: Truncated intervals to 32-bit unsigned integers

By truncating intervals not within interval_t::unsigned_int(32) using truncate_to_uint(32), the code properly manages 32-bit unsigned integer ranges.


270-271: Split unsigned intervals using explicit 64-bit bounds

Initializing left_interval_low with interval_t::nonnegative(64) ensures accurate splitting of unsigned intervals for 64-bit analysis.


286-287: Truncated intervals to 64-bit unsigned integers when necessary

Ensuring that intervals exceeding interval_t::unsigned_int(64) are truncated using truncate_to_uint(64) maintains correct interval bounds.


300-305: Handled 64-bit negative intervals in signed less-than comparison

By explicitly checking for interval_t::negative(64) and using 64-bit bounds, the code accurately manages comparisons involving negative intervals.


323-330: Managed 32-bit negative intervals in signed less-than comparison

Including explicit 32-bit width checks with interval_t::negative(32) and interval_t::nonnegative(32) ensures correct behavior in 32-bit signed comparisons.


340-341: Handled intervals within 32-bit signed integer range

By verifying that intervals are within interval_t::signed_int(32), the code accurately processes 32-bit signed integers in comparisons.


358-360: Managed 64-bit nonnegative intervals in signed greater-than comparison

Using interval_t::nonnegative(64) and truncating upper bounds with truncate_to_sint(64) ensures precise handling of positive intervals in 64-bit comparisons.


368-369: Handled 64-bit negative intervals in signed greater-than comparison

By explicitly checking for interval_t::negative(64), the code accurately processes negative intervals in 64-bit signed greater-than comparisons.


389-391: Managed 32-bit nonnegative intervals in signed greater-than comparison

Including checks for interval_t::nonnegative(32) and truncating with truncate_to_sint(32), the code correctly handles positive intervals in 32-bit comparisons.


399-400: Handled 32-bit negative intervals in signed greater-than comparison

Using explicit checks for interval_t::negative(32), the code ensures accurate processing of negative intervals in 32-bit signed comparisons.


406-407: Verified intervals within 32-bit signed integer range

By confirming intervals are within interval_t::signed_int(32), the code properly manages 32-bit signed integer comparisons.


491-492: Truncated low interval bounds to 64-bit unsigned integers

Using truncate_to_uint(64) on left_interval_low before accessing the lower bound ensures accurate interval calculations in unsigned comparisons.


502-503: Truncated high interval bounds to 64-bit unsigned integers

By truncating left_interval_high with truncate_to_uint(64), the code correctly processes high intervals in 64-bit unsigned comparisons.


513-515: Handled 64-bit signed intervals in unsigned less-than comparison

Checking right_interval <= interval_t::signed_int(64) and truncating upper bounds with truncate_to_uint(64) ensures proper handling of signed intervals as unsigned.


519-523: Addressed edge cases in unsigned 64-bit interval comparisons

The conditions handle scenarios where left_interval_low is bottom and when intervals equal interval_t::unsigned_int(64), ensuring correctness in edge cases.


540-541: Managed 32-bit nonnegative intervals in unsigned less-than comparison

By checking if intervals are within interval_t::nonnegative(32), the code accurately handles 32-bit unsigned comparisons.


545-546: Handled 32-bit negative intervals in unsigned less-than comparison

Including checks for interval_t::negative(32) allows for correct processing of negative intervals in unsigned comparisons, which may represent high unsigned values due to wrapping.


550-551: Ensured intervals are within 32-bit unsigned integer range

By verifying intervals are within interval_t::unsigned_int(32), the code properly manages 32-bit unsigned integer comparisons.


569-572: Computed bounds for unsigned greater-than comparison

By evaluating right_interval.lb(), and truncating bounds with truncate_to_uint(64), the code calculates accurate intervals for comparison.


578-578: Handled low interval out-of-range scenarios

The condition checks if llub is less than rlb, ensuring correct handling when the low interval is out of range in unsigned comparisons.


601-601: Managed 32-bit unsigned high intervals in greater-than comparison

By checking right_interval <= interval_t::unsigned_high(32), the code accurately processes high unsigned intervals in 32-bit comparisons.


605-606: Ensured intervals are within 32-bit unsigned integer range

Verifying that both left_uvalue and right_uvalue are within interval_t::unsigned_int(32) ensures correct handling in unsigned greater-than comparisons.


633-633: Compared intervals with explicit finite widths

Using left_interval.truncate_to_uint(is64 ? 64 : 32) ensures that the comparison operates on intervals truncated to the correct finite width based on is64.


738-738: Evaluated source interval with explicit variable

Retrieving src_interval using inv.eval_interval(src_svalue) explicitly handles the source variable in interval calculations.


2060-2060: Checked interval containment with context descriptors

By assessing whether interval contains desc->data, desc->meta, or desc->end, the code determines if pointers may be touched, enhancing safety checks.


2736-2736: Determined finite width based on is64 flag

Setting finite_width to 64 or 32 depending on bin.is64 ensures that subsequent operations consider the correct bit width.


2753-2753: Handled unsigned overflow with explicit finite width

Using bin.is64 ? 64 : 32 in overflow_unsigned accurately addresses potential overflows in unsigned arithmetic based on the operand size.


3024-3025: Optimized conditional check with explicit finite width

By using interval_t::unsigned_int(bin.is64 ? 64 : 32), the code clearly defines the interval range for the evaluation, enhancing readability.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 10

🧹 Outside diff range comments (2)
src/crab/interval.cpp (1)

Line range hint 148-181: Clarify behavior when divisor is zero in SRem method

In the SRem (signed remainder) method, when the divisor is zero, the function returns the dividend interval unchanged. This behavior might not be immediately clear to readers.

Add a comment to explain this specific case:

 if (*divisor == 0) {
     return interval_t(*dividend);
+    // In eBPF, modulo by zero results in the dividend unchanged.
 }
src/crab/ebpf_domain.cpp (1)

Line range hint 738-744: Potential inefficiency in equality comparison

The code checks if src_interval is not a singleton and if it is less than or equal to a non-negative 64-bit interval before adding constraints. This may be unnecessarily restrictive and could lead to inefficiencies in constraint handling.

Consider simplifying the condition or reevaluating the necessity of the check to improve performance.

Apply this diff to streamline the condition:

-if (!src_interval.is_singleton() && (src_interval <= crab::interval_t::nonnegative(64))) {
+if (src_interval <= crab::interval_t::nonnegative(64)) {
    return {eq(dst_svalue, src_svalue), eq(dst_uvalue, src_uvalue), eq(dst_svalue, dst_uvalue)};
} else {
    return {eq(dst_svalue, src_svalue), eq(dst_uvalue, src_uvalue)};
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE

📥 Commits

Files that changed from the base of the PR and between 281e418 and b1e7d5e.

📒 Files selected for processing (8)
  • src/crab/array_domain.cpp (4 hunks)
  • src/crab/ebpf_domain.cpp (33 hunks)
  • src/crab/interval.cpp (6 hunks)
  • src/crab/split_dbm.cpp (2 hunks)
  • src/crab/var_factory.cpp (1 hunks)
  • src/crab_utils/bignums.hpp (5 hunks)
  • src/crab_verifier.cpp (1 hunks)
  • src/test/conformance_check.cpp (1 hunks)
🧰 Additional context used
📓 Learnings (1)
src/crab/ebpf_domain.cpp (1)
Learnt from: elazarg
PR: vbpf/ebpf-verifier#687
File: src/crab/interval.hpp:256-262
Timestamp: 2024-09-25T11:00:29.008Z
Learning: When comparing two unsigned integral types of the same size using '>', the comparison behaves correctly without needing special handling.
🔇 Additional comments not posted (24)
src/test/conformance_check.cpp (1)

84-85: Approve: Improved type safety with explicit width parameters

The changes in these lines enhance type safety and code clarity by using templated functions with explicit type parameters:

  1. fits_cast_to<int64_t>() instead of fits_cast_to_int64()
  2. cast_to<uint64_t>() instead of cast_to_uint64()

These modifications align with modern C++ practices and make the code more maintainable. They also contribute to the PR's objective of using explicit width for finite width parameters.

src/crab/var_factory.cpp (1)

179-179: LGTM! Verify the behavior of the new casting method.

The change from offset.cast_to_uint64() to offset.cast_to<uint64_t>() looks good. This template-based approach might provide better type safety or flexibility.

To ensure the change doesn't introduce any unexpected behavior, please run the following verification:

src/crab_verifier.cpp (1)

51-52: Approve: Improved type safety and consistency

The changes to get_max_loop_count method enhance type safety and consistency:

  1. Replacing fits_sint32() with fits<int32_t>() and cast_to_sint32() with cast_to<int32_t>() makes the target type explicit, improving code clarity.
  2. This change likely aligns with a broader effort to use template-based type checking and casting throughout the codebase, promoting consistency.
  3. The functionality remains the same, ensuring backward compatibility.

These updates contribute to better maintainability and readability of the code.

src/crab/array_domain.cpp (5)

586-587: Improved type checking for better safety and readability

The change from fits_sint32() to fits<int32_t>() is a good improvement. It uses a more standard C++ template approach for type checking, which enhances type safety and makes the code more readable. This change aligns well with modern C++ practices.


604-605: Consistent improvement in type checking

This change is consistent with the previous modification, replacing fits_sint32() with fits<int32_t>(). This consistency across the codebase enhances maintainability and reinforces the use of modern C++ practices. Good job on maintaining a uniform approach to type checking.


619-620: Enhanced type casting for improved flexibility

The change from cast_to_uint64() to cast_to<uint64_t>() is a positive improvement. This generic template approach for type casting enhances the flexibility of the code, allowing for easier adaptation to different types if needed in the future. It also maintains consistency with the other changes in this file, contributing to a more uniform and maintainable codebase.


754-755: Consistent type checking improvement throughout the file

The change from fits_uint32() to fits<uint32_t>() is consistent with the previous modifications in this file. This uniform approach to type checking enhances the overall code quality by improving type safety and maintainability. It's commendable to see this consistency maintained throughout the entire file, as it makes the code easier to understand and modify in the future.


Line range hint 586-755: Overall improvement in type handling and code consistency

The changes made throughout this file represent a significant improvement in type handling and code consistency. By replacing custom functions like fits_sint32(), fits_uint32(), and cast_to_uint64() with more standard C++ template approaches (fits<int32_t>(), fits<uint32_t>(), and cast_to<uint64_t>()), the code now benefits from:

  1. Enhanced type safety
  2. Improved readability
  3. Better maintainability
  4. Increased flexibility for future modifications
  5. Alignment with modern C++ practices

These consistent changes across the file demonstrate a thoughtful approach to code improvement. They make the codebase more robust and easier to work with in the long term. Great job on implementing these enhancements!

src/crab/split_dbm.cpp (2)

50-50: Improved type checking with template-based approach

The change from n.fits_sint64() to n.fits<int64_t>() is a good improvement. It introduces a more generic, template-based approach for type checking, which enhances type safety and flexibility. This change will make it easier to adapt the code to different integer types in the future if needed.


1034-1034: Consistent improvement in type casting

The change from k.cast_to_uint64() to k.cast_to<uint64_t>() is a positive improvement. It introduces a template-based casting method, which is more generic and aligns with modern C++ practices. This change enhances type safety and flexibility, making it consistent with the earlier modification in the file. It will make the code more adaptable to potential future changes in data types.

src/crab_utils/bignums.hpp (4)

106-109: Great addition of templated fits<T>() method

The introduction of the templated fits<T>() method enhances the code by providing a generic way to check if the internal value _n fits into any integral type T, reducing code redundancy and improving maintainability.


120-128: Implementation of cast_to<U>() improves type safety

The new templated cast_to<U>() method for unsigned integral types provides a type-safe way of casting the internal value _n to type U, with appropriate fit checks to ensure correctness before casting. This enhances the robustness of the casting operations.


131-141: Enhanced casting with cast_to<S>() method

The templated cast_to<S>() method for signed integral types allows safe casting of the internal value _n to type S, with necessary checks in place to prevent overflow or undefined behavior. This addition improves the flexibility and safety of the class.


170-173: Unified truncation using truncate_to<T>()

The addition of the templated truncate_to<T>() method provides a unified approach to truncating the internal value _n to any integral type T. This generalization simplifies the code and eliminates the need for multiple specialized truncation functions.

src/crab/ebpf_domain.cpp (10)

106-108: Proper check before casting to int64_t

The code correctly checks whether dst_n is initialized and whether dst_n.value() fits into an int64_t before casting, preventing potential overflows or undefined behavior.


Line range hint 134-138: Correct handling of equality constraints for signed and unsigned values

The code appropriately adds eq(left_svalue, left_uvalue) when right_interval is non-negative and not a singleton, ensuring consistency between signed and unsigned representations.


236-237: Ensure intervals are correctly truncated to 64-bit signed integers

The code checks if the intervals exceed a 64-bit signed integer range and truncates them accordingly. This prevents potential overflow issues during subsequent computations.


261-262: Proper truncation of intervals to 32-bit unsigned integers

The code correctly truncates intervals to 32-bit unsigned integers when they exceed the 32-bit range, ensuring accurate interval analysis for 32-bit computations.


1517-1518: Check for fits_cast_to<int64_t> before casting

The code correctly checks if n->fits_cast_to<int64_t>() before casting, preventing potential overflows or undefined behavior when performing the cast.


1666-1668: Proper handling of helper function IDs

The code ensures that the helper function ID fits into a 32-bit integer before proceeding, which is essential for compatibility with the eBPF helper function ID specifications.


2060-2062: Efficient computation of may_touch_ptr

The variable may_touch_ptr aggregates the results of interval.contains(desc->data), desc->meta, and desc->end, determining if the interval may affect pointer values.


2481-2483: Handling dynamic helper function IDs

The code attempts to process dynamic helper function IDs where the ID is stored in a register. It checks if the ID fits into a 32-bit integer before processing.


2699-2700: Correct masking of shift amount to prevent undefined behavior

The code masks imm with (finite_width - 1) to ensure that the shift amount is within the valid range, preventing undefined behavior due to shifting by an amount equal to or greater than the bit-width.


2959-2960: Duplicate comment identified

A previous review comment already addressed the issue regarding the potential need to truncate to uint64. Ensure that the previous comment has been considered before making further changes.

@@ -47,7 +47,7 @@ SplitDBM::vert_id SplitDBM::get_vert(variable_t v) {
**/
SafeInt64DefaultParams::Weight SafeInt64DefaultParams::convert_NtoW(const z_number& n, bool& overflow) {
overflow = false;
if (!n.fits_sint64()) {
if (!n.fits<int64_t>()) {
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Summary: Improved type handling with template-based approaches

The two changes in this file consistently improve type checking and casting operations by introducing template-based approaches. These modifications enhance type safety, flexibility, and align the code with modern C++ practices. The changes appear to be part of a larger effort to modernize the codebase and improve its maintainability.

To fully leverage these improvements:

  1. Consider reviewing other parts of the codebase for similar type checking and casting operations that could benefit from this template-based approach.
  2. Ensure that the new template-based methods are properly documented, especially if they are part of a public API.
  3. If not already done, update any relevant unit tests to cover these new template-based methods.

Also applies to: 1034-1034

Comment on lines +189 to +223
}
if (const auto n = x.singleton()) {
// Divisor is a singleton:
// the linear interval solver can perform many divisions where
// the divisor is a singleton interval. We optimize for this case.
if (std::optional<number_t> n = x.singleton()) {
number_t c = *n;
if (c > number_t{0}) {
return interval_t(_lb.UMod(bound_t{c}), _ub.UMod(bound_t{c}));
} else {
// The eBPF ISA defines modulo 0 as being unchanged.
return *this;
}
number_t c = *n;
if (c > number_t{0}) {
return interval_t(_lb.URem(bound_t{c}), _ub.URem(bound_t{c}));
}
// Divisor is not a singleton
using z_interval = interval_t;
if (x[0]) {
// The divisor contains 0.
z_interval l(x._lb, z_bound(-1));
z_interval u(z_bound(1), x._ub);
return (URem(l) | URem(u) | *this);
} else if (operator[](0)) {
// The dividend contains 0.
z_interval l(_lb, z_bound(-1));
z_interval u(z_bound(1), _ub);
return (l.URem(x) | u.URem(x) | *this);
// The eBPF ISA defines modulo 0 as being unchanged.
return *this;
}
if (x.contains(0)) {
// The divisor contains 0.
interval_t l(x._lb, bound_t(-1));
interval_t u(bound_t(1), x._ub);
return URem(l) | URem(u) | *this;
} else if (contains(0)) {
// The dividend contains 0.
interval_t l(_lb, bound_t(-1));
interval_t u(bound_t(1), _ub);
return l.URem(x) | u.URem(x) | *this;
} else {
// Neither the dividend nor the divisor contains 0
if (x._lb.is_infinite() || x._ub.is_infinite()) {
// Divisor is infinite. A "negative" dividend could result in anything except
// a value between the upper bound and 0, so set to top. A "positive" dividend
// could result in anything between 0 and the dividend - 1.
return (_ub < number_t{0}) ? top() : ((*this - interval_t(number_t{1})) | interval_t(number_t(0)));
} else if (_ub.is_finite() && (_ub.number()->cast_to<uint64_t>() < x._lb.number()->cast_to<uint64_t>())) {
// Dividend lower than divisor, so the dividend is the remainder.
return *this;
} else {
// Neither the dividend nor the divisor contains 0
if (x._lb.is_infinite() || x._ub.is_infinite()) {
// Divisor is infinite. A "negative" dividend could result in anything except
// a value between the upper bound and 0, so set to top. A "positive" dividend
// could result in anything between 0 and the dividend - 1.
return (_ub < number_t{0}) ? top() : ((*this - interval_t(number_t{1})) | interval_t(number_t(0)));
} else if (_ub.is_finite() && (_ub.number()->cast_to_uint64() < x._lb.number()->cast_to_uint64())) {
// Dividend lower than divisor, so the dividend is the remainder.
return *this;
} else {
number_t max_divisor{x._ub.number()->cast_to_uint64()};
return interval_t(number_t{0}, max_divisor - 1);
}
number_t max_divisor{x._ub.number()->cast_to<uint64_t>()};
return interval_t(number_t{0}, max_divisor - 1);
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Refactor common modulo logic to enhance code reuse

The SRem and URem methods have overlapping logic, particularly in handling singleton divisors and zero checks. To improve code clarity and reduce duplication, consider refactoring the shared logic into a helper function.

This change promotes reusability and aligns with best practices.

Comment on lines +232 to +261
}
assert(is_top() || (lb() >= number_t{0}));
assert(x.is_top() || (x.lb() >= number_t{0}));

if (left_op && right_op) {
return interval_t((*left_op) & (*right_op));
} else if (right_op && right_op.value().fits_uint32() && right_op.value().cast_to_uint32() == UINT32_MAX) {
// Handle bitwise-AND with UINT32_MAX, which we do for 32-bit operations.
if (auto width = finite_size()) {
number_t lb32_n = (uint32_t)lb().number()->cast_to_uint64();
number_t ub32_n = (uint32_t)ub().number()->cast_to_uint64();
number_t width32_n = (uint32_t)width->cast_to_uint64();
if ((width->cast_to_uint64() <= UINT32_MAX) && (lb32_n < ub32_n) && (lb32_n + width32_n == ub32_n)) {
return interval_t{lb32_n, ub32_n};
}
if (const auto left = singleton()) {
if (const auto right = x.singleton()) {
return interval_t(*left & *right);
}
}
if (x == interval_t{std::numeric_limits<uint32_t>::max()}) {
// Handle bitwise-AND with UINT32_MAX, which we do for 32-bit operations.
if (const auto width = finite_size()) {
const number_t lb32_n = lb().number()->truncate_to<uint32_t>();
const number_t ub32_n = ub().number()->truncate_to<uint32_t>();
if (width->fits<uint32_t>() && lb32_n < ub32_n && lb32_n + width->truncate_to<uint32_t>() == ub32_n) {
return interval_t{lb32_n, ub32_n};
}

// Return the full range of all 32-bit numbers. Use unsigned bounds to
// avoid setting other bits via sign extension.
return interval_t{number_t{0}, number_t{UINT32_MAX}};
} else if (!is_top() && !x.is_top()) {
return interval_t(number_t{0}, bound_t::min(ub(), x.ub()));
} else if (!x.is_top()) {
return interval_t(number_t{0}, x.ub());
} else if (!is_top()) {
return interval_t(number_t{0}, ub());
} else {
return top();
}
return full<uint32_t>();
}
if (x.contains(std::numeric_limits<uint64_t>::max())) {
return truncate_to<uint64_t>();
} else if (!is_top() && !x.is_top()) {
return interval_t(number_t{0}, std::min(ub(), x.ub()));
} else if (!x.is_top()) {
return interval_t(number_t{0}, x.ub());
} else if (!is_top()) {
return interval_t(number_t{0}, ub());
} else {
return top();
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Optimize And operation with known masks

In the And method, when dealing with masks like UINT32_MAX and UINT64_MAX, there's special handling to improve precision for 32-bit and 64-bit operations. To further optimize performance, consider leveraging bitwise properties or creating specialized methods for common masks.

This could enhance efficiency when performing bitwise operations with standard masks.

Comment on lines +301 to +367
const number_t k = *shift;
if (k < number_t{0}) {
// CRAB_ERROR("lshr shift operand cannot be negative");
return top();
}
// Some crazy linux drivers generate shl instructions with
// huge shifts. We limit the number of times the loop is run
// to avoid wasting too much time on it.
if (k <= 128) {
number_t factor = 1;
for (int i = 0; k > i; i++) {
factor *= 2;
}
return this->operator*(interval_t{factor});
}
return top();
}
return top();
}

interval_t interval_t::AShr(const interval_t& x) const {
if (is_bottom() || x.is_bottom()) {
return bottom();
} else {
if (std::optional<number_t> shift = x.singleton()) {
number_t k = *shift;
if (k < number_t{0}) {
// CRAB_ERROR("ashr shift operand cannot be negative");
return top();
}
// Some crazy linux drivers generate ashr instructions with
// huge shifts. We limit the number of times the loop is run
// to avoid wasting too much time on it.
if (k <= 128) {
number_t factor = 1;
for (int i = 0; k > i; i++) {
factor *= 2;
}
return this->operator/(interval_t{factor});
}
if (const auto shift = x.singleton()) {
const number_t k = *shift;
if (k < number_t{0}) {
// CRAB_ERROR("ashr shift operand cannot be negative");
return top();
}
// Some crazy linux drivers generate ashr instructions with
// huge shifts. We limit the number of times the loop is run
// to avoid wasting too much time on it.
if (k <= 128) {
number_t factor = 1;
for (int i = 0; k > i; i++) {
factor *= 2;
}
return this->operator/(interval_t{factor});
}
return top();
}
return top();
}

interval_t interval_t::LShr(const interval_t& x) const {
if (is_bottom() || x.is_bottom()) {
return bottom();
} else {
if (std::optional<number_t> shift = x.singleton()) {
number_t k = *shift;
if (k < number_t{0}) {
// CRAB_ERROR("lshr shift operand cannot be negative");
return top();
}
// Some crazy linux drivers generate lshr instructions with
// huge shifts. We limit the number of times the loop is run
// to avoid wasting too much time on it.
if (k <= 128) {
if (lb() >= number_t{0} && ub().is_finite() && shift) {
number_t lb = *this->lb().number();
number_t ub = *this->ub().number();
return interval_t(lb >> k, ub >> k);
}
}
if (const auto shift = x.singleton()) {
const number_t k = *shift;
if (k < number_t{0}) {
// CRAB_ERROR("lshr shift operand cannot be negative");
return top();
}
// Some crazy linux drivers generate lshr instructions with
// huge shifts. We limit the number of times the loop is run
// to avoid wasting too much time on it.
if (k <= 128) {
if (lb() >= number_t{0} && ub().is_finite() && shift) {
const number_t lb = *this->lb().number();
const number_t ub = *this->ub().number();
return interval_t(lb >> k, ub >> k);
}
}
return top();
}
return top();
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Ensure consistent limit checks in shift operations

The Shl, AShr, and LShr methods enforce a limit on the shift amount (k <= 128) to prevent excessive computation. However, the comments and implementation details vary slightly between these methods.

For consistency, align the comments and structure across all shift methods:

-// Some crazy linux drivers generate shl instructions with
-// huge shifts.  We limit the number of times the loop is run
-// to avoid wasting too much time on it.
+// Limit the shift amount to prevent excessive computation
 if (k <= 128) {
     // Existing logic
 } else {
     return top();
 }

Apply similar changes to AShr and LShr methods to maintain uniformity.

Committable suggestion was skipped due to low confidence.

Comment on lines +11 to +49
}
if (const auto n = x.singleton()) {
// Divisor is a singleton:
// the linear interval solver can perform many divisions where
// the divisor is a singleton interval. We optimize for this case.
if (std::optional<number_t> n = x.singleton()) {
number_t c = *n;
if (c == number_t{1}) {
return *this;
} else if (c > number_t{0}) {
return interval_t(_lb / bound_t{c}, _ub / bound_t{c});
} else if (c < number_t{0}) {
return interval_t(_ub / bound_t{c}, _lb / bound_t{c});
} else {
// The eBPF ISA defines division by 0 as resulting in 0.
return interval_t(number_t(0));
}
}
// Divisor is not a singleton
using z_interval = interval_t;
if (x[0]) {
// The divisor contains 0.
z_interval l(x._lb, z_bound(-1));
z_interval u(z_bound(1), x._ub);
return (operator/(l) | operator/(u) | z_interval(number_t(0)));
} else if (operator[](0)) {
// The dividend contains 0.
z_interval l(_lb, z_bound(-1));
z_interval u(z_bound(1), _ub);
return ((l / x) | (u / x) | z_interval(number_t(0)));
number_t c = *n;
if (c == number_t{1}) {
return *this;
} else if (c > number_t{0}) {
return interval_t(_lb / bound_t{c}, _ub / bound_t{c});
} else if (c < number_t{0}) {
return interval_t(_ub / bound_t{c}, _lb / bound_t{c});
} else {
// Neither the dividend nor the divisor contains 0
z_interval a =
(_ub < number_t{0})
? (*this + ((x._ub < number_t{0}) ? (x + z_interval(number_t(1))) : (z_interval(number_t(1)) - x)))
: *this;
bound_t ll = a._lb / x._lb;
bound_t lu = a._lb / x._ub;
bound_t ul = a._ub / x._lb;
bound_t uu = a._ub / x._ub;
return interval_t(bound_t::min(ll, lu, ul, uu), bound_t::max(ll, lu, ul, uu));
// The eBPF ISA defines division by 0 as resulting in 0.
return interval_t(number_t(0));
}
}
if (x.contains(0)) {
// The divisor contains 0.
interval_t l(x._lb, bound_t(-1));
interval_t u(bound_t(1), x._ub);
return (operator/(l) | operator/(u) | interval_t(number_t(0)));
} else if (contains(0)) {
// The dividend contains 0.
interval_t l(_lb, bound_t(-1));
interval_t u(bound_t(1), _ub);
return ((l / x) | (u / x) | interval_t(number_t(0)));
} else {
// Neither the dividend nor the divisor contains 0
interval_t a =
(_ub < number_t{0})
? (*this + ((x._ub < number_t{0}) ? (x + interval_t(number_t(1))) : (interval_t(number_t(1)) - x)))
: *this;
bound_t ll = a._lb / x._lb;
bound_t lu = a._lb / x._ub;
bound_t ul = a._ub / x._lb;
bound_t uu = a._ub / x._ub;
return interval_t(std::min({ll, lu, ul, uu}), std::max({ll, lu, ul, uu}));
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Refactor common division logic to improve maintainability

The operator/, SDiv, and UDiv methods share similar logic for handling division cases, including singleton divisors and zero checks. To adhere to the DRY (Don't Repeat Yourself) principle and enhance code maintainability, consider refactoring the shared logic into a common helper function.

You can create a private helper function like divide_interval to encapsulate the common logic:

+// Helper function for division operations
+interval_t interval_t::divide_interval(const interval_t& x, bool is_signed) const {
+    // Implement shared division logic here
+}

 interval_t interval_t::operator/(const interval_t& x) const {
-    // Existing division logic
+    return divide_interval(x, /*is_signed=*/true);
 }

 interval_t interval_t::SDiv(const interval_t& x) const {
-    // Existing signed division logic
+    return divide_interval(x, /*is_signed=*/true);
 }

 interval_t interval_t::UDiv(const interval_t& x) const {
-    // Existing unsigned division logic
+    return divide_interval(x, /*is_signed=*/false);
 }

Committable suggestion was skipped due to low confidence.

Comment on lines +111 to 113
if (!src_n || !src_n->fits_cast_to<int64_t>()) {
return {};
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Potential issue when casting negative values to uint64_t

After ensuring src_n.value() fits into an int64_t, the code casts it to uint64_t. If src_n.value() is negative, casting it to uint64_t may lead to unintended large positive values due to two's complement representation.

Consider checking if src_n.value() is non-negative before casting to uint64_t, or adjust the logic to handle negative values appropriately.

Also applies to: 114-117

Comment on lines +491 to +492
auto lllb = left_interval_low.truncate_to_uint(64).lb();
if ((right_interval <= interval_t::nonnegative(64)) && (strict ? (lllb >= rub) : (lllb > rub))) {
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Potential use of uninitialized variable lllb

The variable lllb is assigned using left_interval_low.truncate_to_uint(64).lb(), but there is no check to ensure that this lower bound is not infinite or undefined, which may lead to unexpected behavior.

Add a check to ensure lllb.number() is valid before using it in comparisons.

Apply this diff to address the issue:

 auto lllb = left_interval_low.truncate_to_uint(64).lb();
+if (!lllb.number()) {
+    // Handle the case where the lower bound number is null
+    return {};
+}
 if ((right_interval <= interval_t::nonnegative(64)) && (strict ? (lllb >= rub) : (lllb > rub))) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
auto lllb = left_interval_low.truncate_to_uint(64).lb();
if ((right_interval <= interval_t::nonnegative(64)) && (strict ? (lllb >= rub) : (lllb > rub))) {
auto lllb = left_interval_low.truncate_to_uint(64).lb();
if (!lllb.number()) {
// Handle the case where the lower bound number is null
return {};
}
if ((right_interval <= interval_t::nonnegative(64)) && (strict ? (lllb >= rub) : (lllb > rub))) {

Comment on lines +151 to 153
int64_t lb = inv.eval_interval(left_svalue).lb().number()->cast_to<int64_t>();

// Use the high 32-bits from the left lower bound and the low 32-bits from the right singleton.
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Possible null pointer dereference of inv.eval_interval(left_svalue).lb().number()

The code assumes inv.eval_interval(left_svalue).lb().number() is not nullptr. If lb().number() is nullptr (e.g., if the lower bound is infinite), dereferencing it will cause a null pointer exception.

Add a check to ensure inv.eval_interval(left_svalue).lb().number() is not nullptr before dereferencing.

Apply this diff to address the issue:

 int64_t lb = inv.eval_interval(left_svalue).lb().number()->cast_to<int64_t>();
+if (!inv.eval_interval(left_svalue).lb().number()) {
+    // Handle the case where the lower bound number is null
+    return {};
+}

Committable suggestion was skipped due to low confidence.

Comment on lines +165 to 167
int64_t ub = inv.eval_interval(left_svalue).ub().number()->cast_to<int64_t>();

// Use the high 32-bits from the left upper bound and the low 32-bits from the right singleton.
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Possible null pointer dereference of inv.eval_interval(left_svalue).ub().number()

Similar to the lower bound, assuming inv.eval_interval(left_svalue).ub().number() is not nullptr may lead to a null pointer exception if the upper bound is infinite.

Add a check to ensure inv.eval_interval(left_svalue).ub().number() is not nullptr before dereferencing.

Apply this diff to address the issue:

 int64_t ub = inv.eval_interval(left_svalue).ub().number()->cast_to<int64_t>();
+if (!inv.eval_interval(left_svalue).ub().number()) {
+    // Handle the case where the upper bound number is null
+    return {};
+}

Committable suggestion was skipped due to low confidence.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants