Skip to content

Performance Cliff and Excessive Memory Usage at --unwind 6 #8600

Open
@zhoulaifu

Description

@zhoulaifu

CBMC Bug Report: Performance Cliff and Excessive Memory Usage at --unwind 6

Performance Degradation and Memory Explosion in CBMC 6.4.1 on macOS ARM64 with --unwind 6 When Verifying cJSON.c.

Please advise if this is a known bug, and suggest patches or workarounds. I’m happy to provide more logs, test cases, or assist in debugging.

Description

I am experiencing a severe performance issue with CBMC 6.4.1 when increasing the --unwind value from 5 to 6 while verifying a C program that uses the cJSON library (cJSON.c). For --unwind ≤ 5, CBMC completes execution within seconds with reasonable memory usage. However, at --unwind 6, CBMC runs for over 10 hours without producing a result, and memory usage exceeds 100GB, which is unexpected and appears to indicate a state space explosion or bug.

Steps to Reproduce

  1. Environment:

    • OS: macOS (Apple M3)
    • CBMC Version: 6.4.1
  2. Project Structure:

    • Source files:
      • main.c: Contains a harness function provided below.
      • lib/cJSON/cJSON.c: The cJSON JSON parsing library (version 1.7.99, linked from https://github.com/DaveGamble/cJSON/blob/master/cJSON.c). The file also contains manually inserted assertions.
      • lib/cJSON/cJSON_Utils.c: Utilities for cJSON.
      • lib/cJSON/cJSON.h and lib/cJSON/cJSON_Utils.h: Corresponding headers (assumed to be included).
  3. Harness and Command:

    • Harness Code (in main.c):

      #include <assert.h>
      #include <string.h>
      
      #define MAX_INPUT_SIZE 512
      
      // Forward declaration
      char *process_command(const char *command);
      
      void harness() {
          char buffer[3][MAX_INPUT_SIZE]; // Process up to 3 commands
          for (int i = 0; i < 3; i++) {
              __CPROVER_assume(buffer[i][MAX_INPUT_SIZE - 1] == '\0');
              __CPROVER_assume(strncmp(buffer[i], "ADD", 3) == 0 || strncmp(buffer[i], "MOD", 3) == 0 || 
                               strncmp(buffer[i], "GET id", 6) == 0 || strncmp(buffer[i], "EXIT", 4) == 0);
              char *response = process_command(buffer[i]);
              if (response) {
                  assert(response[strlen(response)] == '\0');
                  free(response);
              }
          }
      }
    • Command for Low Unwind (Works Fine):

      cbmc -Ilib/cJSON main.c lib/cJSON/cJSON.c lib/cJSON/cJSON_Utils.c --function harness --trace --no-standard-checks --no-built-in-assertions --unwind 5
      • Completes in seconds with normal memory usage.
    • Command for High Unwind (Fails):

      cbmc -Ilib/cJSON app/main.c lib/cJSON/cJSON.c lib/cJSON/cJSON_Utils.c --function harness --trace --no-standard-checks --no-built-in-assertions --unwind 6
      • Runs for >10 hours without completing and consumes >100GB of memory.
  4. Verbose Output (With --verbosity 9):

    • When running with --unwind 6 and --verbosity 9, the following lines seem to repeat indefinitely, suggesting a potentially infinite loop or excessive unrolling:
      Unwinding loop cJSON_Delete.0 iteration 5 file lib/cJSON/cJSON.c line 260 function cJSON_Delete thread 0
      Unwinding recursion cJSON_Delete iteration 5
      Unwinding recursion cJSON_Delete iteration 6
      Not unwinding recursion cJSON_Delete iteration 7
      Unwinding loop cJSON_Delete.0 iteration 1 file lib/cJSON/cJSON.c line 260 function cJSON_Delete thread 0
      Not unwinding recursion cJSON_Delete iteration 7
      Unwinding loop cJSON_Delete.0 iteration 2 file lib/cJSON/cJSON.c line 260 function cJSON_Delete thread 0
      Not unwinding recursion cJSON_Delete iteration 7
      [And so on...]
      
    • For a single-command harness (processing one process_command call), the output is concise and fast:
      Unwinding recursion cJSON_Delete iteration 1
      Unwinding loop cJSON_Delete.0 iteration 1 file lib/cJSON/cJSON.c line 260 function cJSON_Delete thread 0
      Unwinding loop cJSON_Delete.0 iteration 1 file lib/cJSON/cJSON.c line 260 function cJSON_Delete thread 0
      ....
      
      This completes quickly with low memory usage.

Expected Behavior

  • I expected runtime and memory usage to increase gradually with each increment of --unwind (e.g., from 5 to 6), not exponentially. For --unwind 6, CBMC should complete within minutes (or at most hours) with reasonable memory usage (e.g., <10GB), not >10 hours and >100GB.

Actual Behavior

  • At --unwind 6, CBMC hangs for >10 hours, consumes >100GB of memory, and appears stuck in excessive unrolling of cJSON_Delete’s loop and recursion in lib/cJSON/cJSON.c (line 260).

Additional Context

  • Assertions in cJSON.c:

    • I have manually injected three assertions in cJSON.c to test for vulnerabilities.
  • Workarounds Tried:

    • Constrained buffer[i] to specific commands (ADD, MOD, GET id, EXIT) using __CPROVER_assume, but the issue persists.
    • Used a single-command harness, which works fast but doesn’t test the multi-command sequence needed for potential bugs.

Impact

This performance cliff prevents effective verification of multi-command sequences.

Possible Cause

  • The cJSON_Delete function (line 260 in cJSON.c) contains a while loop and recursive calls (cJSON_Delete(item->child)), which CBMC unrolls excessively at --unwind 6. This likely causes a state space explosion.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions