Skip to content

C

This page curates a list of example ast-grep rules to check and to rewrite C code.

Match Function Call in C

Description

One of the common questions of ast-grep is to match function calls in C.

A plain pattern like test($A) will not work. This is because tree-sitter-c parse the code snippet into macro_type_specifier, see the pattern output.

To avoid this ambiguity, ast-grep lets us write a contextual pattern, which is a pattern inside a larger code snippet. We can use context to write a pattern like this: test($A);. Then, we can use the selector call_expression to match only function calls.

YAML

yaml
id: match-function-call
language: c
rule:
  pattern:
    context: $M($$$);
    selector: call_expression

Example

c
#define test(x) (2*x)
int a = test(2);
int main(){
    int b = test(2);
}

Caveat

Note, tree-sitter-c parses code differently when it receives code fragment. For example,

  • test(a) is parsed as macro_type_specifier
  • test(a); is parsed as expression_statement -> call_expression
  • int b = test(a) is parsed as declaration -> init_declarator -> call_expression

The behavior is controlled by how the tree-sitter parser is written. And tree-sitter-c behaves differently from tree-sitter-cpp.

Please file issues on tree-sitter-c repo if you want to change the behavior. ast-grep will respect changes and decision from upstream authors.

Rewrite Method to Function Call Has Fix

Description

In C, there is no built-in support for object-oriented programming, but some programmers use structs and function pointers to simulate classes and methods. However, this style can have some drawbacks, such as:

  • extra memory allocation and deallocation for the struct and the function pointer.
  • indirection overhead when calling the function pointer.

A possible alternative is to use a plain function call with the struct pointer as the first argument.

YAML

yaml
id: method_receiver
language: c
rule:
  pattern: $R.$METHOD($$$ARGS)
transform:
  MAYBE_COMMA:
    replace:
      source: $$$ARGS
      replace: '^.+'
      by: ', '
fix:
  $METHOD(&$R$MAYBE_COMMA$$$ARGS)

Example

c
void test_func() {
    some_struct->field.method();
    some_struct->field.other_method(1, 2, 3);
}

Diff

c
void test_func() {
    some_struct->field.method();
    method(&some_struct->field);
    some_struct->field.other_method(1, 2, 3);
    other_method(&some_struct->field, 1, 2, 3);
}

Contributed by

Surma, adapted from the original tweet

Rewrite Check to Yoda Condition Has Fix

Description

In programming jargon, a Yoda condition is a style that places the constant portion of the expression on the left side of the conditional statement. It is used to prevent assignment errors that may occur in languages like C.

YAML

yaml
id: may-the-force-be-with-you
language: c
rule:
  pattern: $A == $B                 # Find equality comparison
  inside:                           # inside an if_statement
    kind: parenthesized_expression
    inside: {kind: if_statement}
constraints:                        # with the constraint that
  B: { kind: number_literal }       # right side is a number
fix: $B == $A

The rule targets an equality comparison, denoted by the pattern $A == $B. This comparison must occur inside an if_statement. Additionally, there’s a constraint that the right side of the comparison, $B, must be a number_literal like 42.

Example

c
if (myNumber == 42) { /* ... */}
if (notMatch == another) { /* ... */}
if (notMatch) { /* ... */}

Diff

c
if (myNumber == 42) { /* ... */}
if (42 == myNumber) { /* ... */}
if (notMatch == another) { /* ... */}
if (notMatch) { /* ... */}

Contributed by

Inspired by this thread

Made with ❤️ with Rust