Условно изменить операторы C в исходном файле C

У меня есть C-файл, в котором мы перемещаем инфраструктуру логирования. Так

if ( logging_level >= LEVEL_FINE )
  printf("Value at %p is %d\n", p, i);

становится

do_log2(LEVEL_FINE, "Value at %p is %d\n", _ptr(p), _num(i));

do_log2 означает log с 2 аргументами.

Поэтому для этого мне нужна инфраструктура разбора и модификации C.

Какой инструмент я могу использовать для достижения этой цели наиболее легко?

Примечание: printf также может появиться в файле как:

if ( logging_level >= LEVEL_FINE )
{
  printf("Value at %p is %d\n", 
  p, 
  i);
}

(с отступом и в блоке). Так что это будет трудно сделать из простого разбора текста на Perl.

РЕДАКТИРОВАТЬ: Это мой последний Perl-код, который делает то, что я хочу

#!/usr/bin/perl -W 
$source=<<'END';
include etc
if ( logging_level >= LEVEL_DEBUG )
{
  printf("1:Value at %p is %d\n",
  p(1),
  i(2));
}
hello();
if ( logging_level >= LEVEL_FINE )
{
  printf("2:Value  is %d\n", i);
  printf("3:Value  is %d\n", i);
}
if ( logging_level >= LEVEL_FINE )
{
  printf("2:Value  is %d\n"
     "and other info", i);
}

other();
if(logging_level>=LEVEL_INFO){printf("4:Value at %p is %d %d\n",p(x),i,j);}
if(logging_level>=LEVEL_FINE) printf("5:Just sayin\"\n");
printf("not logging statement\n").
END

while( $source =~ m/\G(.*?\n)\s* if \s* \( \s* logging_level \s* >= \s* ([A-Z_0-9]+) \s* \) \s*(\{?)/sgxc )
{
    my $othercode = $1;
    my $loglevel=$2;
    my $inblock = $3;
    print("$othercode");

    while($source =~ m/\G\s*printf \( ([^;]*) \) \;/sgxc )
    {
    my $insideprint = $1;
    unless ($insideprint =~ /((\"([^\"\\]|\\.)*\")(\s*(\"([^\"\\]|\\.)*\"))*)/g) #fixing stackoverflow quote problem "
    {
        die "First arg not string literal";
    }
    my $formatstr = $1;
    my $remain = substr($insideprint, pos($insideprint));
    $remain =~ tr/\n \t//d;
    my @args = split(",", $remain);
    shift @args;

    my $numargs = @args;

    print "do_log${numargs}($loglevel, $formatstr";
    for (my $i=0; $i < $numargs; $i++)
    {
        unless ($formatstr =~ /%([a-z]+)/g)
        {
        die "Not enough format for args : $formatstr, args = ", join(",", @args), "\n";
        }
        my $lastchar = substr($1, length($1) -1);
        my $wrapper = "";
        if ($lastchar eq "u" || $lastchar eq  "d")
        { $wrapper = "_numeric";}
        elsif($lastchar eq "p"){ $wrapper = "_ptr";}
        elsif($lastchar eq "s"){ $wrapper = "_str";}
        else { die "Unknown format char %$lastchar in $formatstr"; }

        print ", ${wrapper}($args[$i])";
    }
    print ");";
    last unless ($inblock);
    }
# eat trailing }
    if ($inblock)
    {
    if ($source =~ m/\G \s* \} /sgxc)
    {
    }
    else
    {
    }
    }
}
#whatever is left 
print substr($source, pos($source));

вывод:

include etc
do_log2(LEVEL_DEBUG, "1:Value at %p is %d\n", _ptr(p(1)), _numeric(i(2)));
hello();
do_log1(LEVEL_FINE, "2:Value  is %d\n", _numeric(i));
do_log1(LEVEL_FINE, "3:Value  is %d\n", _numeric(i));
do_log1(LEVEL_FINE, "2:Value  is %d\n"
         "and other info", _numeric(i));

other();
do_log3(LEVEL_INFO, "4:Value at %p is %d %d\n", _ptr(p(x)), _numeric(i), _numeric(j));
do_log0(LEVEL_FINE, "5:Just sayin\"\n");
printf("not logging statement\n").

Woohoo! Теперь, чтобы применить к фактическому исходному коду.

13.10.2009 04:05:18
Я не думаю, что это такая сложная проблема для Perl. Но вам бы пришлось заплатить мне деньги, чтобы написать решение для вас.
user181548 13.10.2009 04:21:05
6 ОТВЕТОВ

Что вам нужно, так это система преобразования программ, которая может анализировать C и выполнять преобразования в сущности кода (например, в соответствующих структурах данных компилятора), а не в тексте (так что это не смущает разметка текста и т. Д.) , (Трансформация программы является обобщением рефакторинга).

DMS Software Реинжиниринг Toolkit является такой системой преобразования программы, и она имеет C парсер , который был применен для очень больших систем C.

С DMS ваше изменение может быть записано как:

domain C; -- work with C language syntax

rule change_logging(exp: p, exp: i, s: literal_string, c:literal_integer): stmt -> stmt
  "if ( logging_level >= \l )
      printf(\s, \p, \i);"
  ->  
  "do_log2(\l, \s, _ptr(\p), _num(\i));".

\ K - это либо мета-кавычки («в С нужно заключать в кавычки внутри правила!»), Либо метавариативные переменные (\ p \ i \ s) соответствующего синтаксического типа.

На практике каждый пишет набор взаимодействующих правил преобразования, чтобы выполнить более сложную задачу (у вас, вероятно, также есть случаи log1 и log3).

Шаблон преобразуется, как разобранный код C, в эквивалентные структуры данных компилятора, а затем сопоставляется со структурами данных компилятора для кода C, поэтому форматирование текста не имеет значения. Когда совпадение найдено, оно заменяется структурами данных компилятора для правой части правила (2nd ->). После того, как все преобразования были применены, результирующие структуры данных компилятора используются для регенерации измененного текста, применяя противоположность синтаксического анализа: prettyprinting. Вуаля, ваше изменение сделано.

Есть некоторые сложности с макросами и директивами препроцессора, но они еще хуже, если вам придется делать это с методами взлома строк, которые часто реализуются в Perl.

Существуют также сложности, связанные с обоснованием побочных эффектов, достижением определений, значений указателей и т. Д .; DMS обеспечивает поддержку для решения всех этих проблем.

4
16.10.2009 08:11:15
Есть ли такая система с открытым исходным кодом?
Sid Datta 13.10.2009 05:27:19
Самая близкая вещь - TXL и / или Stratego. Хорошей новостью является то, что за ними стоит та же концепция. Более сложная новость заключается в том, что у них нет надежных синтаксических анализаторов C, связанных с ними. Это не простые системы для сборки. ДМС заняла 15 лет.
Ira Baxter 13.10.2009 05:31:00

Преимущества С99 и __VA_ARGS__!

Насколько жестко у вас есть два примера макетов? Точнее говоря, есть ли у вас когда-нибудь другая активность (например, цикл) внутри if (logging_level...)условий с фигурными скобками? Или несколько printf()заявлений под контролем одного if?

Если у вас мало творчества в том, как использовался код отладки (ab), то вы можете сделать это с помощью специального сценария Perl - не красиво, но это одноразовое изменение (хотя, вероятно, оно будет выполнено) на многих файлах).

Работа с украшением параметров, как в _ptr(p)и _num(i)добавляет еще один уровень сложности. Вам нужно было бы проанализировать строковый литерал (полагая, что никому не хватило фантазии использовать что-либо кроме строкового литерала), чтобы понять, какими должны быть типы аргументов.

В целом, не тривиальное упражнение, особенно если разработчики были изобретательны. Я хотел бы написать сценарий, который обрабатывает 90% или более случаев, а затем работать с исключениями по мере их обнаружения.

1
13.10.2009 04:28:41

Вам не нужно считать аргументы:

#include <stdio.h> 
#include <stdarg.h> 

void do_log( int level, char *format, ... ){
  va_list ap;
  va_start( ap, format );
  printf( "level: %i ", level ); vprintf( format, ap ); puts("");
  va_end(ap);
}

int main(){
  do_log( 1, "zero" );
  do_log( 2, "one: %i", 1 );
  do_log( 3, "one: %i two: %i", 1, 2 );
}

Я хотел бы сделать код переписать с Perl. Я не понимаю, почему это сложно.

РЕДАКТИРОВАТЬ: я написал некоторый Perl-код, чтобы переписать фрагменты кода регистрации:

#!/usr/bin/perl -W 

$source=<<'END';
if ( logging_level >= LEVEL_FINE ).
{
  printf("1:Value at %p is %d\n",.
  p(1),
  i(2));
}

if(logging_level>=LEVEL_FINE){printf("2:Value at %p is %d\n",p(x),i,j);}
END

$res = '';
while( $source =~ /\G(.*?)if\s*\(\s*logging_level\s*>=\s*([A-Z_]+)\s*\)\s*{\s*printf\s*(\(((?:[^()]+|(?3))+)\))\s*;\s*}/sg ){
  $lastpos = pos($source); $res .= $1; $l=$2; $p=$4; $p =~ s/[\r\n\s]+//g;
  $c = $p =~ tr/,/,/;
  $res .= "do_log$c($l,$p);";
}
print $res, substr($source,$lastpos);

Результат:

do_log2(LEVEL_FINE,"1:Valueat%pis%d\n",p(1),i(2));

do_log3(LEVEL_FINE,"2:Valueat%pis%d\n",p(x),i,j);

Я добавляю простой подсчет аргументов в код. Надеюсь помочь.

3
13.10.2009 07:41:07
инфраструктура логирования (do_log * и т. д.) установлена ​​в камне, хе-хе, я не могу это реализовать. Мне не разрешают даже написать упрощающую обертку. Большая старая система, строгие правила.
Sid Datta 13.10.2009 05:16:21
Мне все еще приходится обрабатывать многострочные строки, концепцию _ptr, _num и т. Д., Но ваш код - хорошая отправная точка. Спасибо! Теперь позвольте мне посмотреть, смогу ли я расшифровать этот RE: P
Sid Datta 13.10.2009 19:40:10

Сколько раз logging_levelупоминается? Процесс называется рефакторингом. Если изменение тривиально, в вашем любимом редакторе можно использовать хорошее регулярное выражение. Но часто код имеет много вариаций на одну и ту же тему. В этом случае все они могут быть найдены через logging_level. Вы можете отказаться от них, скрыв значение logging_levelдля кода (так что вы получите предупреждение компилятора, но оно все равно будет работать). Или используйте редактор вроде source-insight, который может показать вам все ссылки за один раз.

Некоторые примеры вариаций (которые трудно найти в скрипте):

if ( logging_level >= LEVEL_FINE )
  printf("Value at %p is %d\n", p, i);

if ( logging_level >= LEVEL_FINE ) {
  calculated_value = i*2/3;
  printf("Value at %p is %f\n", p, calculated_value);
}

(обратите внимание на скобки и рассчитанные переменные).

Для каждого файла со старой конструкцией вы можете выполнить поиск и заменить:

поиск: if \(\s*logging_level\s*>=\s*(LEVEL_[a-zA-Z]+) заменить на: do_log2(\1,)

Можно включить printf, но только если ваш редактор поддерживает многострочные шаблоны.

0
13.10.2009 14:26:31

Возможно, вы захотите проверить coccinelle , который также используется хакерами ядра Linux для выполнения широко автоматизированных крупномасштабных преобразований кода с использованием семантического исправления.

Надеюсь это поможет

1
30.11.2009 19:03:21

Решение Coccinelle будет:

@@
выражение р, я;
@@

-if (logging_level> = LEVEL_FINE)
- printf («Значение при% p равно% d \ n», p, i);
+ do_log2 (LEVEL_FINE, «Значение в% p равно% d \ n», _ptr (p), _num (i));

Не существует общего способа решения упомянутой выше проблемывычисленного значения, но было бы возможно найти код, который имеет эту проблему, следующим образом:

@@
выражение р, я;
@@

* if (logging_level> = LEVEL_FINE)
   {...
* printf («Значение при% p равно% d \ n», p, i);
   ...}

Результат будет выглядеть как разница, но минусы в столбце 0 предназначены для обозначения интересующих элементов, а не элементов для удаления.

2
6.12.2009 07:32:34
Спасибо, я надеюсь, что Coccinelle предлагает некоторые возможности сценариев, так что подсчет аргументов и т. Д. Также может быть решен. Я решил свою проблему с Perl, но все равно спасибо :)
Sid Datta 11.12.2009 19:26:29