Использование библиотеки анализа кода OpenC++: модификация, улучшение, исправление ошибок Автор: Андрей Карпов Дата: 12.01.2008 Аннотация Данная статья представляет интерес для разработчиков, использующих или планирующих использовать библиотеку OpenC++ (OpenCxx). Автор рассказывает о своем опыте улучшения библиотеки OpenC++ и модификации библиотеки для решения специализированных задач. Введение В форумах часто можно услышать, что синтаксических анализаторов ("парсеров") языка Си++ в мире огромное количество. В том числе и бесплатных. Или, что можно взять, например, YACC и легко реализовать свой анализатор. Не верьте, все не так просто [1 , 2]. Особенно, если вспомнить, что разобрать синтаксис - это меньше половины дела. Необходимо реализовать структуры для хранения дерева программы и семантических таблиц, содержащих информацию о различных объектах и областях их действия. Особенно это важно при разработке специализированных приложений, связанных с обработкой и статическим анализом Си++ кода. Для их реализации необходимо сохранение полного дерева программы, что могут предоставить не многие библиотеки. Одной из них является открытая библиотека OpenC++ (OpenCxx) [3 ], о которой мы и поговорим в этой статье. Хочется помочь разработчикам в освоении библиотеки OpenC++ и поделиться опытом ее модернизации и использования некоторых недочетов. Статья представляет собой сборник советов, каждый из которых посвящен исправлению какого-то дефекта или реализации усовершенствования. Статья основывается на воспоминаниях об изменениях, которые были осуществлены в библиотеке VivaCore [4 ], основанной на базе OpenC++. Конечно, здесь отражена только малая часть этих изменений. Вспомнить и описать их все будет непростой задачей. Например, описание добавления в библиотеку OpenC++ поддержки языка Си займет много места. Но Вы всегда можете обратиться к исходным текстам библиотеки VivaCore и получить много интересной информации. Последнее, о чем хочется сказать, что библиотека OpenC++, к сожалению, на данный момент устарела и нуждается в серьезной доработке для поддержания современного стандарта языка Си++. Поэтому, если Вы, например, собираетесь реализовать современный компилятор, то Вам лучше обратить внимание на GCC или посмотреть в сторону коммерческих библиотек [5 , 6 ]. Но OpenC++ и сейчас остается хорошим и удобным инструментом для многих разработчиков в области систем специализированной обработки и модификации программного кода. C использованием OpenC++ разработаны многие интересные решения. Например: среда
39
Embed
Использование библиотеки анализа кода OpenC++: модификация, улучшение, исправление ошибок
Данная статья представляет интерес для разработчиков, использующих или планирующих использовать библиотеку OpenC++ (OpenCxx). Автор рассказывает о своем опыте улучшения библиотеки OpenC++ и модификации библиотеки для решения специализированных задач.
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Использование библиотеки анализа
кода OpenC++: модификация,
улучшение, исправление ошибок
Автор: Андрей Карпов
Дата: 12.01.2008
Аннотация Данная статья представляет интерес для разработчиков, использующих или планирующих
использовать библиотеку OpenC++ (OpenCxx). Автор рассказывает о своем опыте улучшения
библиотеки OpenC++ и модификации библиотеки для решения специализированных задач.
Введение В форумах часто можно услышать, что синтаксических анализаторов ("парсеров") языка Си++ в
мире огромное количество. В том числе и бесплатных. Или, что можно взять, например, YACC и
легко реализовать свой анализатор. Не верьте, все не так просто [1, 2]. Особенно, если вспомнить,
что разобрать синтаксис - это меньше половины дела. Необходимо реализовать структуры для
хранения дерева программы и семантических таблиц, содержащих информацию о различных
объектах и областях их действия. Особенно это важно при разработке специализированных
приложений, связанных с обработкой и статическим анализом Си++ кода. Для их реализации
необходимо сохранение полного дерева программы, что могут предоставить не многие
библиотеки. Одной из них является открытая библиотека OpenC++ (OpenCxx) [3], о которой мы и
поговорим в этой статье.
Хочется помочь разработчикам в освоении библиотеки OpenC++ и поделиться опытом ее
модернизации и использования некоторых недочетов. Статья представляет собой сборник
советов, каждый из которых посвящен исправлению какого-то дефекта или реализации
усовершенствования.
Статья основывается на воспоминаниях об изменениях, которые были осуществлены в
библиотеке VivaCore [4], основанной на базе OpenC++. Конечно, здесь отражена только малая
часть этих изменений. Вспомнить и описать их все будет непростой задачей. Например, описание
добавления в библиотеку OpenC++ поддержки языка Си займет много места. Но Вы всегда можете
обратиться к исходным текстам библиотеки VivaCore и получить много интересной информации.
Последнее, о чем хочется сказать, что библиотека OpenC++, к сожалению, на данный момент
устарела и нуждается в серьезной доработке для поддержания современного стандарта языка
Си++. Поэтому, если Вы, например, собираетесь реализовать современный компилятор, то Вам
лучше обратить внимание на GCC или посмотреть в сторону коммерческих библиотек [5, 6]. Но
OpenC++ и сейчас остается хорошим и удобным инструментом для многих разработчиков в
области систем специализированной обработки и модификации программного кода. C
использованием OpenC++ разработаны многие интересные решения. Например: среда
исполнения OpenTS [7] для языка программирования T++ (разработка Института программных
систем РАН), статический анализатор кода Viva64 [8] или инструмент Synopsis для подготовки
документации по исходному коду [9].
Цель данной статьи - показать на примерах, как можно модифицировать и улучшить код
библиотеки OpenC++. Для этого в статье описано 15 модификаций библиотеки, связанных с
исправлением ошибок или добавлением новой функциональности. Все они не только позволяют
сделать библиотеку OpenC++ лучше, но и дают возможность глубже изучить принципы ее работы.
Давайте познакомимся с ними.
1. Пропуск ключевых слов среды разработки, не влияющих на
обработку программы Разрабатывая анализатор кода под конкретную среду разработки, Вы наверняка столкнетесь с ее
специфическими языковыми конструкциями. Часто эти конструкции являются указаниями для
конкретного компилятора и могут не представлять для Вас никакого практического интереса. Но
такие конструкции не могут быть обработаны библиотекой OpenC++, так как не являются частью
языка Си++. В этом случае одним из простых способов игнорировать их является добавление
ключевых слов в таблицу rw_table table с ключом Ignore. Пример:
static rw_table table[] = {
...
{ "__ptr32", Ignore},
{ "__ptr64", Ignore},
{ "__unaligned", Ignore},
...
};
При добавлении следует учитывать, что слова в таблице rw_table table должны быть расположены
в алфавитном порядке. Будьте аккуратны!
2. Добавление новой лексемы Если Вы хотите добавить ключевое слово, которое следует обрабатывать, то Вам необходимо
создать новую лексему ("токен"). Рассмотрим пример добавления ключевого слова "__w64". В
начале создайте идентификатор новой лексемы (смотрите файл token-name.h), например, так:
enum {
Identifier = 258,
Constant = 262,
...
W64 = 346, // New token name
...
};
Модернизируйте таблицу "table" в файле lex.cc:
static rw_table table[] = {
...
{ "__w64", W64 },
...
};
Наш следующий шаг - это создание класса для новой лексемы, который мы назовем LeafW64:
namespace Opencxx
{
class LeafW64 : public LeafReserved {
public:
LeafW64(Token& t) : LeafReserved(t) {}
LeafW64(char* str, ptrdiff_t len) :
LeafReserved(str, len) {}
ptrdiff_t What() { return W64; }
};
}
Для создания объекта нам понадобится модифицировать функцию optIntegralTypeOrClassSpec():
...
case UNSIGNED :
flag = 'U';
kw = new (GC) LeafUNSIGNED(tk);
break;
case W64 : // NEW!
flag = 'W';
kw = new (GC) LeafW64(tk);
break;
...
Обратите внимание, что поскольку мы решили отнести "__w64" к типам данных, то нам
понадобился символ 'W' для кодирования этого типа. Более подробно с механизмом
кодирования типов можно познакомиться в файле Encoding.cc.
Вводя новый тип, мы должны помнить о необходимости модернизации таких функций, как
например Parser::isTypeSpecifier().
И, наконец, последний важный момент - это модификация функции Encoding::MakePtree:
В приведенном коде используются некоторые вспомогательные функции, отсутствующие в этой
статье. Но вы можете найти их в библиотеке VivaCore.
12. Поддержка объявлений в классах функций вида T (min)() { } Иногда при программировании приходится использовать обходные пути для достижения
результата. Например, широко известный макрос "max" часто приносит сложности при
объявлении в классе метода вида "T max() {return m;}". В этом случае прибегают к хитростям и
объявляют метод так: "T (max)() {return m;}". К сожалению, OpenC++ не понимает такие
объявления внутри классов. Для исправления этого недочета следует модернизировать функцию
Parser::isConstructorDecl() следующим образом:
bool Parser::isConstructorDecl()
{
if(lex->LookAhead(0) != '(')
return false;
else{
// Support: T (min)() { }
if (lex->LookAhead(1) == Identifier &&
lex->LookAhead(2) == ')' &&
lex->LookAhead(3) == '(')
return false;
ptrdiff_t t = lex->LookAhead(1);
if(t == '*' || t == '&' || t == '(')
return false; // declarator
else if(t == CONST || t == VOLATILE)
return true; // constructor or d eclarator
else if(isPtrToMember(1))
return false; // declarator (::*)
else
return true; // maybe constructo r
}
}
13. Обработка конструкций "using" и "namespace" внутри функций Библиотека OpenC++ не "знает", что внутри функций можно использовать конструкции "using" и
"namespace". Но это легко исправить, модернизируя функцию Parser::rStatement():
bool Parser::rStatement(Ptree*& st)
{
...
case USING :
return rUsing(st);
case NAMESPACE :
if (lex->LookAhead(2) == '=')
return rNamespaceAlias(st);
return rExprStatement(st);
...
}
14. Делаем "this" указателем Как известно, "this" является указателем. В OpenC++ это не так. Поэтому стоит исправить функцию
Walker::TypeofThis(), чтобы исправить ошибку определения типа.
Замените код:
void Walker::TypeofThis(Ptree*, TypeInfo& t)
{
t.Set(env->LookupThis());
}
на:
void Walker::TypeofThis(Ptree*, TypeInfo& t)
{
t.Set(env->LookupThis());
t.Reference();
}
15. Оптимизация функции LineNumber() Мы уже упоминали о функции Program::LineNumber(), говоря, что она возвращает имена файлов в
разных форматах. И затем предложили функцию FixFileName() для исправления этой ситуации. Но
у функции LineNumber() есть еще один недостаток, связанный с медленной скоростью ее работы.
Поэтому мы предлагаем оптимизированный вариант функции LineNumber():
/*
LineNumber() returns the line number of the line
pointed to by PTR.
*/
size_t Program::LineNumber(const char* ptr,
const char*& filename,
ptrdiff_t& filename_leng th,
const char *&beginLinePt r) const
{
beginLinePtr = NULL;
ptrdiff_t n;
size_t len;
size_t name;
ptrdiff_t nline = 0;
size_t pos = ptr - buf;
size_t startPos = pos;
if(pos > size){
// error?
assert(false);
filename = defaultname.c_str();
filename_length = defaultname.length();
beginLinePtr = buf;
return 0;
}
ptrdiff_t line_number = -1;
filename_length = 0;
while(pos > 0){
if (pos == oldLineNumberPos) {
line_number = oldLineNumber + nline;
assert(!oldFileName.empty());
filename = oldFileName.c_str();
filename_length = oldFileName.length();
assert(oldBeginLinePtr != NULL);
if (beginLinePtr == NULL)
beginLinePtr = oldBeginLinePtr;
oldBeginLinePtr = beginLinePtr;
oldLineNumber = line_number;
oldLineNumberPos = startPos;
return line_number;
}
switch(buf[--pos]) {
case '\n' :
if (beginLinePtr == NULL)
beginLinePtr = &(buf[pos]) + 1;
++nline;
break;
case '#' :
len = 0;
n = ReadLineDirective(pos, -1, name, len) ;
if(n >= 0){ // unless #pr agma
if(line_number < 0) {
line_number = n + nline;
}
if(len > 0 && filename_length == 0){
filename = (char*)Read(name);
filename_length = len;
}
}
if(line_number >= 0 && filename_length > 0) {
oldLineNumberPos = pos;
oldBeginLinePtr = beginLinePtr;
oldLineNumber = line_number;
oldFileName = std::string(filename,
filename_leng th);
return line_number;
}
break;
}
}
if(filename_length == 0){
filename = defaultname.c_str();
filename_length = defaultname.length();
oldFileName = std::string(filename,
filename_length);
}
if (line_number < 0) {
line_number = nline + 1;
if (beginLinePtr == NULL)
beginLinePtr = buf;
oldBeginLinePtr = beginLinePtr;
oldLineNumber = line_number;
oldLineNumberPos = startPos;
}
return line_number;
}
16. Исправление ошибки при анализе директивы "#line" В некоторых случаях функция Program::ReadLineDirective() дает сбой, принимая за директиву
"#line" посторонний текст. Исправленный вариант функции выглядит следующим образом:
ptrdiff_t Program::ReadLineDirective(size_t i,
ptrdiff_t line_number,
size_t& filename, size_t& filename_length) const
{
char c;
do{
c = Ref(++i);
} while(is_blank(c));
#if defined(_MSC_VER) || defined(IRIX_CC)
if(i + 5 <= GetSize() &&
strncmp(Read(i), "line ", 5) == 0) {
i += 4;
do{
c = Ref(++i);
}while(is_blank(c));
} else {
return -1;
}
#endif
if(is_digit(c)){ /* # <line> <file> */
unsigned num = c - '0';
for(;;){
c = Ref(++i);
if(is_digit(c))
num = num * 10 + c - '0';
else
break;
}
/* line_number'll be incremented soon */
line_number = num - 1;
if(is_blank(c)){
do{
c = Ref(++i);
}while(is_blank(c));
if(c == '"'){
size_t fname_start = i;
do{
c = Ref(++i);
} while(c != '"');
if(i > fname_start + 2){
filename = fname_start;
filename_length = i - fname_sta rt + 1;
}
}
}
}
return line_number;
}
Заключение Конечно, в этой статье описана малая часть возможных улучшений. Но хочется надеяться, что они
будут полезны разработчикам при использовании библиотеки OpenC++ и станут примерами,
демонстрирующими как можно специализировать библиотеку для своих задач.
Еще раз хочется напомнить, что показанные в этой статье и многие другие улучшения можно
найти в коде библиотеки VivaCore. Для многих задач библиотека VivaCore может оказаться
удобнее, чем OpenC++.
Если у Вас возникли вопросы, Вы хотите что-то добавить или прокомментировать, то наша
команда Viva64.com [10] всегда рада и открыта к общению. Мы готовы обсудить возникшие
вопросы, дать рекомендации и помочь в использовании библиотеки OpenC++ или VivaCore.
Пишите нам!
Библиографический список 1. Зуев Е.А. Редкая профессия. PC Magazine/Russian Edition. Спецвыпуск N 5(75), 1997.
http://www.viva64.com/go.php?url=43
2. Эллис М., Строуструп Б. Справочное руководство по языку программирования C++ с
комментариями: Пер. с англ.- М.: Мир, 1992 - 445 с., илл. ISBN 5-03-002868-4.