Skip to content

Conversation

@visz11
Copy link
Collaborator

@visz11 visz11 commented Nov 21, 2025

CodeAnt-AI Description

Select the best non-fallthrough branch when both sides exit

What Changed

  • ruleBlockIfNoExit now compares both successor blocks and collapses the branch with lower nesting, keeping deeper no-exit branches intact
  • When depths tie, the decompiler avoids folding simple return-only branches by preferring the opposite path, improving accuracy when both sides exit
  • Block depth and size information now drives this heuristic so collapses reflect the visible nesting of the original code

Impact

✅ Fewer incorrect conditional collapses
✅ More accurate early-return detection
✅ Clearer nested control flow after decompilation

💡 Usage Guide

Checking Your Pull Request

Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.

Talking to CodeAnt AI

Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:

@codeant-ai ask: Your question here

This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.

Example

@codeant-ai ask: Can you suggest a safer alternative to storing this secret?

Preserve Org Learnings with CodeAnt

You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:

@codeant-ai: Your feedback here

This helps CodeAnt AI learn and adapt to your team's coding style and standards.

Example

@codeant-ai: Do not flag unused imports.

Retrigger review

Ask CodeAnt AI to review the PR again, by typing:

@codeant-ai: review

Check Your Repository Health

To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.

@codeant-ai
Copy link

codeant-ai bot commented Nov 21, 2025

CodeAnt AI is reviewing your PR.


Thanks for using CodeAnt! 🎉

We're free for open-source projects. if you're enjoying it, help us grow by sharing.

Share on X ·
Reddit ·
LinkedIn

@visz11
Copy link
Collaborator Author

visz11 commented Nov 21, 2025

@refacto-visz

@gemini-code-assist
Copy link

Summary of Changes

Hello @visz11, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly refines the decompiler's handling of conditional control flow, specifically when both branches of an 'if' statement lead to a non-exiting state (e.g., both return or throw). By introducing new metrics like block depth and detailed PcodeOp analysis, the decompiler can now apply a more sophisticated heuristic to select the 'best' branch for collapsing, leading to more accurate and readable decompiled output for such scenarios.

Highlights

  • Enhanced Branch Selection Logic: Introduced a new selectBestNoExit function within CollapseStructure to intelligently choose between two 'no exit' branches in conditional blocks. This improves the decompiler's ability to structure complex control flows.
  • Block Depth Calculation: Added getBlockDepth virtual methods across various FlowBlock types (BlockGraph, BlockList, BlockCondition, BlockSwitch) and a getInnerBlockDepth method to BlockGraph to quantify the nesting level of code blocks.
  • PcodeOp Analysis: Added BlockBasic::getOpSize() to retrieve the number of PcodeOps in a basic block and PcodeOp::isStandardReturn() to identify standard return operations, both of which are used in the new branch selection heuristics.
  • Refined ruleBlockIfNoExit: The ruleBlockIfNoExit function now leverages the new selectBestNoExit logic, prioritizing branches based on lowest block depth, preference for non-return blocks, and then single-operation return blocks, to make a more informed decision when both conditional branches are non-exiting.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@codeant-ai codeant-ai bot added the size:L This PR changes 100-499 lines, ignoring generated files label Nov 21, 2025
@refacto-visz
Copy link

refacto-visz bot commented Nov 21, 2025

feat: Improve branch selection for if-else statements with dual returns

TL;DR: This change introduces a smarter heuristic for structuring if-else statements where both branches terminate program flow (e.g., via return), improving the readability of the decompiled output.


Refacto PR Summary

This pull request refactors the control-flow structuring rule ruleBlockIfNoExit. Previously, when an if condition had two branches that both terminated execution (a "no exit" path), the decompiler's choice of which branch to form the if body was arbitrary. This could lead to less readable code, such as placing a large, complex block inside the if body while a simple return; is relegated to the else.

To address this, a new heuristic has been implemented to "better" select the branch. This is achieved by introducing a concept of "block depth" to measure the complexity of control-flow nesting. The new logic prefers to collapse the branch with the shallower block depth into the if body. Tie-breaking rules are also added, which favor non-return blocks or blocks containing only a single return instruction. This results in more natural and readable C code, typically by handling simple exit cases first.

Change Highlights

Click to expand
  • Ghidra/Features/Decompiler/src/decompile/cpp/blockaction.cc: Implemented selectBestNoExit, a new function containing the core heuristic for choosing between two "no exit" branches based on block depth and other properties. The ruleBlockIfNoExit rule was updated to use this new selection logic.
  • Ghidra/Features/Decompiler/src/decompile/cpp/block.hh: Added the new virtual function getBlockDepth() to the FlowBlock class hierarchy, providing the interface for measuring structural nesting.
  • Ghidra/Features/Decompiler/src/decompile/cpp/block.cc: Provided concrete implementations for getBlockDepth() across various block types (BlockGraph, BlockList, BlockSwitch, etc.), enabling the recursive calculation of nesting depth.
  • Ghidra/Features/Decompiler/src/decompile/cpp/op.hh: Introduced the isStandardReturn() helper method on PcodeOp to distinguish a standard return from other flow-breaking operations like calls to noreturn functions.

Sequence Diagram

sequenceDiagram
    participant CS as CollapseStructure
    participant Rule as ruleBlockIfNoExit
    participant Select as selectBestNoExit
    participant B0 as Branch 0
    participant B1 as Branch 1

    CS->>Rule: Analyze conditional block `bl`
    Rule->>Rule: Verify `bl` has 2 "no exit" branches
    Note over Rule: Both branches lead to a return or throw.
    Rule->>Select: selectBestNoExit(branch_0, branch_1)
    
    Select->>B0: getBlockDepth()
    B0-->>Select: depth0
    Select->>B1: getBlockDepth()
    B1-->>Select: depth1

    alt depth0 < depth1
        Select-->>Rule: Return index 0
    else depth1 < depth0
        Select-->>Rule: Return index 1
    else Depths are equal
        Select->>Select: Apply tie-breaking rules (e.g., check for simple returns)
        Select-->>Rule: Return best index
    end

    Rule->>CS: Restructure graph with chosen branch in `if` body
Loading

Testing Guide

Click to expand

To verify this change, you can test its effect on a function where an if-else has returns in both branches, and one branch is clearly more complex than the other.

  1. Create a test case: Compile the following C code with optimizations disabled (-O0) to preserve the control flow structure.
    int test_function(int a, int b) {
        if (a > 100) {
            return 42; // Simple branch
        } else {
            // Complex branch
            if (b < 0) {
                return -1;
            }
            for (int i = 0; i < b; ++i) {
                if (i % 2 == 0) {
                    a += i;
                }
            }
            return a;
        }
    }
  2. Decompile Before Patch: Load the binary into a version of Ghidra without this patch. Decompile test_function. The output might be structured with the complex block first:
    if (a <= 100) {
      // ... complex for-loop logic ...
      return a;
    }
    else {
      return 42;
    }
  3. Decompile After Patch: Apply the patch, rebuild the decompiler, and decompile the same function again.
  4. Verify Result: The new output should be more readable, placing the simpler case first by inverting the condition:
    if (a > 100) {
      return 42;
    }
    // ... complex for-loop logic ...
    return a;

@coderabbitai
Copy link

coderabbitai bot commented Nov 21, 2025

Warning

Rate limit exceeded

@visz11 has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 9 minutes and 50 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 2abfd4e and 5f927f3.

📒 Files selected for processing (5)
  • Ghidra/Features/Decompiler/src/decompile/cpp/block.cc (5 hunks)
  • Ghidra/Features/Decompiler/src/decompile/cpp/block.hh (6 hunks)
  • Ghidra/Features/Decompiler/src/decompile/cpp/blockaction.cc (2 hunks)
  • Ghidra/Features/Decompiler/src/decompile/cpp/blockaction.hh (1 hunks)
  • Ghidra/Features/Decompiler/src/decompile/cpp/op.hh (1 hunks)
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch clone-ruleBlockIfNoExit-select-best-noexit

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@refacto-visz
Copy link

refacto-visz bot commented Nov 21, 2025

Refacto is reviewing this PR. Please wait for the review comments to be posted.

@codeant-ai
Copy link

codeant-ai bot commented Nov 21, 2025

CodeAnt AI finished reviewing your PR.

@refacto-visz
Copy link

refacto-visz bot commented Nov 21, 2025

Code Review: Decompiler Heuristic Refinements

👍 Well Done
Improved Structuring Heuristic

Adding block depth analysis to select the no-exit branch is a great improvement for code structuring.

Improved Code Encapsulation

Adding helpers like isStandardReturn and getBlockDepth improves readability and maintainability by encapsulating complex logic.

📁 Selected files for review (5)
  • Ghidra/Features/Decompiler/src/decompile/cpp/block.cc
  • Ghidra/Features/Decompiler/src/decompile/cpp/block.hh
  • Ghidra/Features/Decompiler/src/decompile/cpp/blockaction.cc
  • Ghidra/Features/Decompiler/src/decompile/cpp/blockaction.hh
  • Ghidra/Features/Decompiler/src/decompile/cpp/op.hh
🎯 Custom Instructions
✅ Applied Instructions
Organization Guidelines
  • Use feature flags for new functionality and include a clear rollback plan.
  • Very critical to consider apply pagination to queries
  • Follow the company security checklist:
    • No hard-coded secrets or credentials.
    • Validate all external inputs.
    • Use parameterized queries for DB access.

Scope: All files

📝 Additional Comments
Ghidra/Features/Decompiler/src/decompile/cpp/blockaction.cc (1)
Duplicated Logic in Heuristic

The logic to check for a simple, single-operation return block is duplicated for clause0 and clause1. This violates the DRY (Don't Repeat Yourself) principle, making the code harder to read and maintain. Extracting this check into a separate helper function would improve clarity and prevent potential bugs from inconsistent updates in the future.

Standards:

  • Maintainability-Quality-Code-Duplication
  • Clean-Code-DRY

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a more sophisticated heuristic for selecting a branch in if statements where both branches are no-exit. This is achieved by calculating and comparing the block depth of the branches. The changes include adding getBlockDepth() methods to various FlowBlock subclasses and a new getOpSize() method.

The overall logic is sound and improves the decompiler's output. My review includes a few suggestions to improve code readability, style consistency, and const-correctness. Notably, getOpSize() should be a const method, which is a crucial change for correctness and good design. I've also suggested using modern C++ features like range-based for loops and nullptr to make the code cleaner.

list<PcodeOp *>::const_iterator beginOp(void) const { return op.begin(); } ///< Return an iterator to the beginning of the PcodeOps
list<PcodeOp *>::const_iterator endOp(void) const { return op.end(); } ///< Return an iterator to the end of the PcodeOps
bool emptyOp(void) const { return op.empty(); } ///< Return \b true if \b block contains no operations
int4 getOpSize(void); ///< Number of PcodeOps contained in \b this block

Choose a reason for hiding this comment

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

high

The getOpSize method does not modify the state of the BlockBasic object. It should be declared as const to enforce this and allow it to be called on const objects. This improves const-correctness and can prevent potential issues elsewhere.

  int4 getOpSize(void) const;                                       ///< Number of PcodeOps contained in \b this block

return hasOnlyMarkers();
}

int4 BlockBasic::getOpSize(void)

Choose a reason for hiding this comment

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

high

The implementation of getOpSize should be marked as const to match the declaration in the header file. This method does not modify the object's state.

int4 BlockBasic::getOpSize(void) const

Comment on lines +1326 to +1338
int4 BlockGraph::getInnerBlockDepth(void)

{
int4 depth;
int4 maxDepth = 0;
for(int4 i=0;i<list.size();++i){
depth = list[i]->getBlockDepth();
if(depth>maxDepth){
maxDepth=depth;
}
}
return maxDepth;
}

Choose a reason for hiding this comment

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

medium

The implementation of this loop can be made more concise and readable by using a range-based for loop. Also, the indentation for maxDepth=depth; seems to be off (using a tab instead of spaces), which affects code consistency. Declaring depth inside the loop scope is also better practice.

int4 BlockGraph::getInnerBlockDepth(void)
{
  int4 maxDepth = 0;
  for (const FlowBlock *block : list) {
    const int4 depth = block->getBlockDepth();
    if (depth > maxDepth) {
      maxDepth = depth;
    }
  }
  return maxDepth;
}

Comment on lines +3222 to +3232
int4 BlockSwitch::getBlockDepth(void){
int4 i;
int4 maxDepth=0;
for(i=0;i<caseblocks.size();++i){
int4 depth=caseblocks[i].block->getBlockDepth();
if(depth>maxDepth){
maxDepth=depth;
}
}
return maxDepth+2; // +1 for switch block and +1 for case/default block
}

Choose a reason for hiding this comment

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

medium

This function's implementation can be improved for readability and consistency.

  • The opening brace { is on the same line as the function signature, which is inconsistent with the surrounding code style.
  • A range-based for loop would be more modern and readable than a C-style for loop.
  • The indentation for maxDepth=depth; appears to be incorrect (using a tab).
  • The variable i is declared at the top of the function but only used in the loop.
int4 BlockSwitch::getBlockDepth(void)
{
  int4 maxDepth = 0;
  for (const auto &caseblock : caseblocks) {
    const int4 depth = caseblock.block->getBlockDepth();
    if (depth > maxDepth) {
      maxDepth = depth;
    }
  }
  return maxDepth + 2; // +1 for switch block and +1 for case/default block
}

Comment on lines +1514 to +1515
bool isRet0 = clause0->lastOp()!=(PcodeOp *)0 && clause0->lastOp()->isStandardReturn();
bool isRet1 = clause1->lastOp()!=(PcodeOp *)0 && clause1->lastOp()->isStandardReturn();

Choose a reason for hiding this comment

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

medium

For better code clarity and to adhere to modern C++ standards, it's recommended to use nullptr instead of the C-style (PcodeOp *)0 for null pointer checks.

  bool isRet0 = clause0->lastOp() != nullptr && clause0->lastOp()->isStandardReturn();
  bool isRet1 = clause1->lastOp() != nullptr && clause1->lastOp()->isStandardReturn();

Comment on lines +1521 to 1529
FlowBlock* fb;
if(clause0->getType()==FlowBlock::t_copy){
fb = ((BlockCopy*)clause0)->subBlock(0);
if(fb->getType()==FlowBlock::t_basic && ((BlockBasic*)fb)->getOpSize()==1) return 0;
}
if(clause1->getType()==FlowBlock::t_copy){
fb = ((BlockCopy*)clause1)->subBlock(0);
if(fb->getType()==FlowBlock::t_basic && ((BlockBasic*)fb)->getOpSize()==1) return 1;
}

Choose a reason for hiding this comment

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

medium

This block of code can be improved for safety and clarity:

  1. Use static_cast instead of C-style casts. This is safer and more explicit about the cast's intent.
  2. The fb variable is declared once and reused. It's better practice to declare variables in the narrowest scope possible.

Additionally, BlockBasic::getOpSize() should be a const method, as it doesn't modify the object's state. I've added separate comments for this.

    if(clause0->getType()==FlowBlock::t_copy){
      FlowBlock* fb = static_cast<BlockCopy*>(clause0)->subBlock(0);
      if(fb->getType()==FlowBlock::t_basic && static_cast<BlockBasic*>(fb)->getOpSize()==1) return 0;
    }
    if(clause1->getType()==FlowBlock::t_copy){
      FlowBlock* fb = static_cast<BlockCopy*>(clause1)->subBlock(0);
      if(fb->getType()==FlowBlock::t_basic && static_cast<BlockBasic*>(fb)->getOpSize()==1) return 1;
    }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L This PR changes 100-499 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants