Skip to content

Match rules on AST instead of strings #447

Closed

Description

@lucic71

Today rules are matched by comparing printed strings. This does not work for NTTPs which have to be masked (#435). String printing also does not work for all AST shapes (llvm/llvm-project#217883, llvm/llvm-project#218420).

To fix this, do the matching at AST level. Take the following example:

std::vector<int> v;
v.push_back(3);

template <typename T1> void f1(std::vector<T1> &a0, const T1& a1) {
  return a0.push_back(a1);
}
  1. Extract the usage arguments ([v, 3]) and its declaration (std::vector<int>::push_back(const int&))
  2. Apply the extracted arguments to the rule: f1(v, 3) which instantiates to f1<int>
  3. Take the declaration that f1<int>'s return statement resolves to and compare it to the one from step 1 (std::vector<int>::push_back(const int&))

The entire matching is done on the AST. So there is no need for representing rules as strings. ir_src.json, cpp-rule-preprocessor and ToString disappear.


To implement this, rules are included in the translated TU. Only the rules that parse are kept. If the TU never included <map> then there are no rules for map. This is faster than the string matching approach.

Some rules take a type in a non-deduced context (a template parameter to the left of a ::, such as typename std::vector<T1>::iterator). Clang deduction does not work for them. Such rules are rewritten to take the iterator type itself (It) and compute T1 from it:

template <typename It,
          typename T1 = typename std::iterator_traits<It>::value_type,
          typename = typename std::enable_if<
            std::is_same<It, typename std::vector<T1>::iterator>::value>::type>
typename std::vector<T1>::reference f2(It it) {
  return it.operator*();
}

How this works for f2(v.begin()):

  1. It is deduced as __gnu_cxx::__normal_iterator<int *, std::vector<int>> on libstdc++ and std::__wrap_iter<int *> on libc++
  2. T1 becomes int

At this step we got the mapping T1 = int but this is not enough. __normal_iterator or __wrap_iter are generic iterators, they are used for vector, list, string, etc. The final step is:
3. Choose this rule only if it's the iterator for vector: enable_if<is_same<It, vector::iterator>>


Most of this work will go into the new ast_matcher which replaces string_matcher. ir_src.json, cpp-rule-preprocessor and ToString will be deleted. C specific rules will need to live in src.c instead of src.cpp. Also rules will need to be guarded by the standard version they were introduced in.

Activity

  1. added a commit that references this issue on Oct 4, 2026
  2. nunoplopes commented on Oct 4, 2026

    @nunoplopes
    Contributor

    Parsing c++ files is slow. I don't think we want to get rid of cpp-rule-preprocessor. Parsing thousands of files and headers is going to take a minute easily.

  3. lucic71 commented on Oct 5, 2026

    @lucic71
    ContributorAuthor

    Parsing c++ files is slow. I don't think we want to get rid of cpp-rule-preprocessor. Parsing thousands of files and headers is going to take a minute easily.

    The thing is that we don't parse STL headers with this approach. Rules that fail to parse in the current TU are not relevant, for example if rules for map fail to parse that's because the TU never included map.

    This approach is really nice because it means no intermediate representation of rules and no hints for instantiating the rules in cpp-rule-preprocessor. I plan to spend some time thinking how can I make the proposed solution faster.

    Parsing thousands of files and headers is going to take a minute easily.

    As I said in #451 it's 0.05 seconds for 1K rules and 0.5 seconds for 10K rules.

  4. lucic71 commented on Oct 5, 2026

    @lucic71
    ContributorAuthor

    Parsing c++ files is slow. I don't think we want to get rid of cpp-rule-preprocessor. Parsing thousands of files and headers is going to take a minute easily.

    The thing is that we don't parse STL headers with this approach. Rules that fail to parse in the current TU are not relevant, for example if rules for map fail to parse that's because the TU never included map.

    This approach is really nice because it means no intermediate representation of rules and no hints for instantiating the rules in cpp-rule-preprocessor. I plan to spend some time thinking how can I make the proposed solution faster.

    Parsing thousands of files and headers is going to take a minute easily.

    As I said in #451 it's 0.05 seconds for 1K rules and 0.5 seconds for 10K rules.

    I found a cheap way to implement this: instead of parsing all rules, scan the C++ program to only include the rules that it uses. I did a test with 100K dummy rules loaded for running the unit tests:

    • with selective loading it takes on average 20ms per invocation to load the rules
    • with full loading it takes on average 5s

    With full loading of the current string representation (ir_src.json) it takes on average 5s

  5. added a commit that references this issue on Oct 14, 2026
    e65e265
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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