Условно изменить операторы C в исходном файле C - PullRequest
2 голосов
/ 13 октября 2009

У меня есть 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 означает журнал с 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! Теперь применить к фактическому исходному коду.

Ответы [ 6 ]

4 голосов
/ 13 октября 2009

Вам нужна Система преобразования программ , которая может анализировать C и выполнять преобразования по сути кода (например, по соответствующим структурам данных компилятора), а не по тексту (так что это не так). t перепутано с разметкой текста и т. д.). (Преобразование программы является обобщением рефакторинга).

DMS Software Reengineering 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 являются либо мета-кавычками ("в C должно быть заключено в кавычки правила!) или мета-переменные (\ p \ i \ s) соответствующего синтаксического типа.

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

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

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

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

3 голосов
/ 13 октября 2009

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

#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);

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

2 голосов
/ 06 декабря 2009

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

@@
expression p,i;
@@

-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));

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

@@
expression p,i;
@@

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

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

1 голос
/ 30 ноября 2009

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

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

1 голос
/ 13 октября 2009

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

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

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

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

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

0 голосов
/ 13 октября 2009

Сколько раз на 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, но только если ваш редактор поддерживает многострочные шаблоны.

...