39
Использование библиотеки анализа кода 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++ разработаны многие интересные решения. Например: среда

Использование библиотеки анализа кода OpenC++: модификация, улучшение, исправление ошибок

Embed Size (px)

DESCRIPTION

Данная статья представляет интерес для разработчиков, использующих или планирующих использовать библиотеку OpenC++ (OpenCxx). Автор рассказывает о своем опыте улучшения библиотеки OpenC++ и модификации библиотеки для решения специализированных задач.

Citation preview

Page 1: Использование библиотеки анализа кода OpenC++: модификация, улучшение, исправление ошибок

Использование библиотеки анализа

кода 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++ разработаны многие интересные решения. Например: среда

Page 2: Использование библиотеки анализа кода 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

Page 3: Использование библиотеки анализа кода OpenC++: модификация, улучшение, исправление ошибок

...

};

Модернизируйте таблицу "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;

...

Page 4: Использование библиотеки анализа кода OpenC++: модификация, улучшение, исправление ошибок

Обратите внимание, что поскольку мы решили отнести "__w64" к типам данных, то нам

понадобился символ 'W' для кодирования этого типа. Более подробно с механизмом

кодирования типов можно познакомиться в файле Encoding.cc.

Вводя новый тип, мы должны помнить о необходимости модернизации таких функций, как

например Parser::isTypeSpecifier().

И, наконец, последний важный момент - это модификация функции Encoding::MakePtree:

Ptree* Encoding::MakePtree(unsigned char*& encoded, Ptree* decl)

{

...

case 'W' :

typespec = PtreeUtil::Snoc(typespec, w6 4_t);

break;

...

}

Естественно, это только пример, и добавление других лексем может потребовать гораздо больше

работы. Хороший способ корректно добавить новую лексему - взять близкую к ней по смыслу, а

затем найти и изучить все места в библиотеке OpenC++ где она используется.

3. Пропуск сложных ключевых конструкций среды разработки, не

влияющих на обработку программы Мы уже рассмотрели способ, как пропустить единичные ключевые слова, не имеющие для нашей

программы смысловой нагрузки, но которые мешают разбору кода. К сожалению, иногда дело

обстоит сложнее. Возьмем в качестве демонстрации такие конструкции как __pragma и __noop,

которые можно встретить в заголовочных файлах VisualC++:

__forceinline DWORD HEAP_MAKE_TAG_FLAGS (

DWORD TagBase, DWORD Tag )

{

__pragma(warning(push)) __pragma(warning(disabl e : 4548)) do

{__noop(TagBase);} while((0,0) __pragma(warning(pop )) );

return ((DWORD)((TagBase) + ((Tag) << 18)));

}

Посмотреть описание конструкций __pragma и __noop можно в MSDN. Для нашей программы

важно следующее: а) они нам не интересны; б) имеют параметры; в) мешают анализу кода.

Page 5: Использование библиотеки анализа кода OpenC++: модификация, улучшение, исправление ошибок

Вначале добавим новые лексемы, как было рассказано ранее, но теперь для этой цели

воспользуемся функцией InitializeOtherKeywords():

static void InitializeOtherKeywords(bool recognizeO ccExtensions)

{

...

verify(Lex::RecordKeyword("__pragma", MSPRAGMA));

verify(Lex::RecordKeyword("__noop", MS__NOOP));

...

}

Решение заключается в модификации функции Lex::ReadToken таким образом, что, когда мы

встречаем лексему DECLSPEC или MSPRAGMA, мы пропускаем ее. А затем пропускаем все

лексемы, относящиеся к параметрам __pragma и __noop. Для пропуска всех лишних лексем мы

используем функцию SkipDeclspecToken() таким образом, как показано далее.

ptrdiff_t Lex::ReadToken(char*& ptr, ptrdiff_t& len )

{

...

else if(t == DECLSPEC){

SkipDeclspecToken();

continue;

}

else if(t == MSPRAGMA) { // NEW

SkipDeclspecToken();

continue;

}

else if(t == MS__NOOP) { //NEW

SkipDeclspecToken();

continue;

}

...

}

Page 6: Использование библиотеки анализа кода OpenC++: модификация, улучшение, исправление ошибок

4. Функция раскрытия полных путей к файлам В задачах анализа исходного кода большое количество функциональности связано с созданием

диагностических сообщений, а также навигацией по исходным файлам. Неудобство заключается в

том, что имена файлов, возвращаемые такими функциями как Program::LineNumber(), могут быть

представлены в разнообразном виде. Вот несколько примеров:

C:\\Program Files\\MSVS 8\\VC\\atlmfc\\include\\afx .h

.\\drawing.cpp

c:\\src\\wxwindows-2.4.2\\samples\\drawing\\wx/defs .h

Boost\\boost-1_33_1\\boost/variant/recursive_varian t.hpp

..\\FieldEdit2\\Src\\amsEdit.cpp

..\\..\\..\\src\\base\\ftbase.c

Путь может быть полным или относительным. Могут использоваться различные разделители. Все

это делает использование таких путей неудобным для обработки или для вывода в

информационных сообщениях. Поэтому мы предлагаем реализацию функции FixFileName(),

приводящую пути к единообразному полному виду. Используемая вспомогательная функция

GetInputFileDirectory() должна возвращать путь к каталогу, в котором находится обрабатываемый

файл:

const string &GetInputFileDirectory() {

static string oldInputFileName;

static string fileDirectory;

string dir;

VivaConfiguration &cfg = VivaConfiguration::Insta nce();

string inputFileName;

cfg.GetInputFileName(inputFileName);

if (oldInputFileName == inputFileName)

return fileDirectory;

oldInputFileName = inputFileName;

filesystem::path inputFileNamePath(inputFileName, filesystem::native);

fileDirectory = inputFileNamePath.branch_path().s tring();

if (fileDirectory.empty()) {

TCHAR curDir[MAX_PATH];

if (GetCurrentDirectory(MAX_PATH, curDir) != 0) {

Page 7: Использование библиотеки анализа кода OpenC++: модификация, улучшение, исправление ошибок

fileDirectory = curDir;

} else {

assert(false);

}

}

algorithm::replace_all(fileDirectory, "/", "\\");

to_lower(fileDirectory);

return fileDirectory;

}

typedef map<string, string> StrStrMap;

typedef StrStrMap::iterator StrStrMapIt;

void FixFileName(string &fileName) {

static StrStrMap FileNamesMap;

StrStrMapIt it = FileNamesMap.find(fileName);

if (it != FileNamesMap.end()) {

fileName = it->second;

return;

}

string oldFileName = fileName;

algorithm::replace_all(fileName, "/", "\\");

algorithm::replace_all(fileName, "\\\\", "\\");

filesystem::path tmpPath(fileName, filesystem::na tive);

fileName = tmpPath.string();

algorithm::replace_all(fileName, "/", "\\");

to_lower(fileName);

if (fileName.length() < 2) {

assert(false);

FileNamesMap.insert(make_pair(oldFileName, file Name));

return;

}

Page 8: Использование библиотеки анализа кода OpenC++: модификация, улучшение, исправление ошибок

if (fileName[0] == '.' && fileName[1] != '.') {

const string &dir = GetInputFileDirectory();

if (!dir.empty())

fileName.replace(0, 1, dir);

FileNamesMap.insert(make_pair(oldFileName, file Name));

return;

}

if (isalpha(fileName[0]) && fileName[1] == ':' ) {

FileNamesMap.insert(make_pair(oldFileName, file Name));

return;

}

const string &dir = GetInputFileDirectory();

if (dir.empty())

fileName.insert(0, ".\\");

else {

fileName.insert(0, "\\");

fileName.insert(0, dir);

}

FileNamesMap.insert(make_pair(oldFileName, fileNa me));

}

5. Получение значения числовых литералов В системах построения документации по коду полезной может оказаться функция получения

значения числового литерала. Например, с ее помощью можно узнать и использовать тот факт,

что аргумент функции "void foo(a = 99)" равен 99.

Предлагаемая функция GetLiteralType() позволяет получить тип литерала и его значение, если он

целочисленный. Функция GetLiteralType() создана для получения наиболее часто необходимой

информации и не поддерживает редко используемые виды записи. Но если Вам, например, будет

необходимо поддержать UCNs или получать значения типа double, то вы можете самостоятельно

расширить функциональность приведенных ниже функций:

", 5) == 0) { retValue = 0; ; } ; } IsHexLiteral( *from, size_t len) { (len < 3) ; (from[0 ] != '0') ; (from[1] != 'x' && from[1] != 'X') ; ; } S impleType

Page 9: Использование библиотеки анализа кода OpenC++: модификация, улучшение, исправление ошибок

GetTypeBySufix( *from, size_t len) { assert(from != NULL); (len == 0) ST_INT; assert(!isdigit(*from)); s uffix_8 = ; suffix_16 = ; suffix_32 = ; suffix_64 = ; suffix_i = ; suffix_l = ; suffix_u = ; (len != 0) { - -len; c = *from++; (c) { '8': suffix_8 = ; ; '1': (len == 0 || *from++ != '6') { assert(); ST_UNKNOWN; } --len; suffix _16 = ; ; '3': (len == 0 || *from++ != '2') { assert(); ST_UNKNOWN; } --len; suffix _32 = ; ; '6': (len == 0 || *from++ != '4') { assert(); ST_UNKNOWN; } --len; suffix _64 = ; ; 'I': 'i': suffix_i = ; ; 'U': 'u': suffix_u = ; ; 'L': 'l': suffix_l = ; ; : ass ert(); ST_UNKNOWN; } } assert(suffix_8 + suffix_1 6 + suffix_32 + suffix_64 <= 1); (suffix_8 || suffix_16) ST_LESS_INT; (suffix_32) { (suffix_u) ST_UINT; ST_INT; } (suffix_64) { (suffix_u) ST_UINT64; ST_INT64; } (suffix_l) { (suffix_u) ST_ULONG; ST_LONG; } (suffix_u) ST_UINT; assert (suffix_i); ST_INT; } SimpleType Ge tHexLiteral( *from, size_t len, &retValue) { assert(len >= 3); *p = from + 2; (!GetHex(p, len, retValue)) { ST_UNKNOWN; } ptrdiff_t newLen = len - (p - from); assert(newLen >= 0 && newLen < <ptrdiff_t> (len)); GetTypeBySufix(p, newLen); } IsOctLiteral( *from , size_t len) { (len < 2) ; (from[0] != '0') ; ; } SimpleType GetOctLiteral( *from, size_t len, &retValue) { assert(len >= 2); *p = from + 1; (!GetOct(p, len, retValue)) { ST_UNKNOWN; } ptrdiff_t newLen = len - (p - from); assert(newLen >= 0 && newLen < <ptrdiff_t> (len)); GetTypeBySufix(p, newLen); } SimpleType GetDecLite ral( *from, size_t len, &retValue) { asse rt(len >= 1); *limit = from + len; n = 0; (from < limit) { c = *from; (c < '0' || c > '9') ; from++; n = n * 10 + (c - '0'); } ptrdiff_t newLen = limit - from; (newLen == <ptrdiff_t>(len)) ST_UNKNOWN; retValue = n; assert(newLen >= 0 && newLen < <ptrdiff_t>(len)); GetTypeBySufix(from, newLen); } SimpleType GetLiteralType( *from, size_t len, &retValue) { (from == NULL || len == 0) ST_ UNKNOWN; retValue = 1; (from == NULL || len == 0) ST_UNKNOWN; (GetCharLiteral(from, len, retValue)) ST_LESS_ INT; (GetStringLiteral(from, len)) ST_POINTER; (GetBoolLiteral(from, len, retValue)) ST_LESS_ INT; (IsRealLiteral(from, len)) GetRealLiteral(from , len); (IsHexLiteral(from, len)) GetHexLiteral(from, len, retValue); (IsOctLiteral(from, len)) GetOctLiteral(from, len, retValue); GetDecLiteral(from, len, retValue); }

unsigned __int64 GetHexValue(unsigned char c) {

if (c >= '0' && c <= '9')

Page 10: Использование библиотеки анализа кода OpenC++: модификация, улучшение, исправление ошибок

return c - '0';

if (c >= 'a' && c <= 'f')

return c - 'a' + 0x0a;

if (c >= 'A' && c <= 'F')

return c - 'A' + 0x0a;

assert(false);

return 0;

}

bool GetHex(const char *&from, size_t len,

unsigned __int64 &retValue) {

unsigned __int64 c, n = 0, overflow = 0;

int digits_found = 0;

const char *limit = from + len;

while (from < limit)

{

c = *from;

if (!isxdigit(c))

break;

from++;

overflow |= n ^ (n << 4 >> 4);

n = (n << 4) + GetHexValue(c);

digits_found = 1;

}

if (!digits_found)

return false;

if (overflow) {

assert(false);

}

retValue = n;

return true;

Page 11: Использование библиотеки анализа кода OpenC++: модификация, улучшение, исправление ошибок

}

bool GetOct(const char *&from, size_t len,

unsigned __int64 &retValue) {

unsigned __int64 c, n = 0;

bool overflow = false;

const char *limit = from + len;

while (from < limit)

{

c = *from;

if (c < '0' || c > '7')

break;

from++;

overflow |= static_cast<bool>(n ^ (n << 3 >> 3) );

n = (n << 3) + c - '0';

}

retValue = n;

return true;

}

#define HOST_CHARSET_ASCII

bool GetEscape(const char *from, size_t len,

unsigned __int64 &retValue) {

/* Values of \a \b \e \f \n \r \t \v respectively . */

// HOST_CHARSET_ASCII

static const char charconsts[] =

{ 7, 8, 27, 12, 10, 13, 9, 11 };

// HOST_CHARSET_EBCDIC

//static const uchar charconsts[] =

{ 47, 22, 39, 12, 21, 13, 5, 11 };

unsigned char c;

c = from[0];

Page 12: Использование библиотеки анализа кода OpenC++: модификация, улучшение, исправление ошибок

switch (c)

{

/* UCNs, hex escapes, and octal escapes

are processed separately. */

case 'u': case 'U':

// convert_ucn - not supported. Return: 65535.

retValue = 0xFFFFui64;

return true;

case 'x': {

const char *p = from + 1;

return GetHex(p, len, retValue);

}

case '0': case '1': case '2': case '3':

case '4': case '5': case '6': case '7': {

const char *p = from + 1;

return GetOct(p, len, retValue);

}

case '\\': case '\'': case '"': case '?':

break;

case 'a': c = charconsts[0]; break;

case 'b': c = charconsts[1]; break;

case 'f': c = charconsts[3]; break;

case 'n': c = charconsts[4]; break;

case 'r': c = charconsts[5]; break;

case 't': c = charconsts[6]; break;

case 'v': c = charconsts[7]; break;

case 'e': case 'E': c = charconsts[2]; break;

default:

assert(false);

Page 13: Использование библиотеки анализа кода OpenC++: модификация, улучшение, исправление ошибок

return false;

}

retValue = c;

return true;

}

//'A', '\t', L'A', '\xFE'

static bool GetCharLiteral(const char *from,

size_t len,

unsigned __int64 &retVal ue) {

if (len >= 3) {

if (from[0] == '\'' && from[len - 1] == '\'') {

unsigned char c = from[1];

if (c == '\\') {

verify(GetEscape(from + 2, len - 3, retValu e));

} else {

retValue = c;

}

return true;

}

}

if (len >= 4) {

if (from[0] == 'L' &&

from[1] == '\'' &&

from[len - 1] == '\'') {

unsigned char c = from[2];

if (c == '\\') {

verify(GetEscape(from + 3, len - 4, retValu e));

} else {

retValue = c;

}

Page 14: Использование библиотеки анализа кода OpenC++: модификация, улучшение, исправление ошибок

return true;

}

}

return false;

}

// "string"

static bool GetStringLiteral(const char *from, size _t len) {

if (len >= 2) {

if (from[0] == '"' && from[len - 1] == '"')

return true;

}

if (len >= 3) {

if (from[0] == 'L' &&

from[1] == '"' &&

from[len - 1] == '"')

return true;

}

return false;

}

bool IsRealLiteral(const char *from, size_t len) {

if (len < 2)

return false;

bool isReal = false;

bool digitFound = false;

for (size_t i = 0; i != len; ++i) {

unsigned char c = from[i];

switch(c) {

case 'x': return false;

case 'X': return false;

case 'f': isReal = true; break;

Page 15: Использование библиотеки анализа кода OpenC++: модификация, улучшение, исправление ошибок

case 'F': isReal = true; break;

case '.': isReal = true; break;

case 'e': isReal = true; break;

case 'E': isReal = true; break;

case 'l': break;

case '-': break;

case '+': break;

case 'L': break;

default:

if (!isdigit(c))

return false;

digitFound = true;

}

}

return isReal && digitFound;

}

SimpleType GetRealLiteral(const char *from, size_t len) {

assert(len > 1);

unsigned char rc1 = from[len - 1];

if (is_digit(rc1) || rc1 == '.' ||

rc1 == 'l' || rc1 == 'L' ||

rc1 == 'e' || rc1 == 'E')

return ST_DOUBLE;

if (rc1 == 'f' || rc1 == 'F')

return ST_FLOAT;

assert(false);

return ST_UNKNOWN;

}

bool GetBoolLiteral(const char *from, size_t len,

unsigned __int64 &retValue) {

Page 16: Использование библиотеки анализа кода OpenC++: модификация, улучшение, исправление ошибок

if (len == 4 && strncmp(from, "true", 4) == 0) {

retValue = 1;

return true;

}

if (len == 5 && strncmp(from, "false", 5) == 0) {

retValue = 0;

return true;

}

return false;

}

bool IsHexLiteral(const char *from, size_t len) {

if (len < 3)

return false;

if (from[0] != '0')

return false;

if (from[1] != 'x' && from[1] != 'X')

return false;

return true;

}

SimpleType GetTypeBySufix(const char *from, size_t len) {

assert(from != NULL);

if (len == 0)

return ST_INT;

assert(!isdigit(*from));

bool suffix_8 = false;

bool suffix_16 = false;

bool suffix_32 = false;

bool suffix_64 = false;

bool suffix_i = false;

bool suffix_l = false;

Page 17: Использование библиотеки анализа кода OpenC++: модификация, улучшение, исправление ошибок

bool suffix_u = false;

while (len != 0) {

--len;

const char c = *from++;

switch(c) {

case '8': suffix_8 = true; break;

case '1':

if (len == 0 || *from++ != '6') {

assert(false);

return ST_UNKNOWN;

}

--len;

suffix_16 = true;

break;

case '3':

if (len == 0 || *from++ != '2') {

assert(false);

return ST_UNKNOWN;

}

--len;

suffix_32 = true;

break;

case '6':

if (len == 0 || *from++ != '4') {

assert(false);

return ST_UNKNOWN;

}

--len;

suffix_64 = true;

break;

Page 18: Использование библиотеки анализа кода OpenC++: модификация, улучшение, исправление ошибок

case 'I':

case 'i': suffix_i = true; break;

case 'U':

case 'u': suffix_u = true; break;

case 'L':

case 'l': suffix_l = true; break;

default:

assert(false);

return ST_UNKNOWN;

}

}

assert(suffix_8 + suffix_16 + suffix_32 + suffix_ 64 <= 1);

if (suffix_8 || suffix_16)

return ST_LESS_INT;

if (suffix_32) {

if (suffix_u)

return ST_UINT;

else

return ST_INT;

}

if (suffix_64) {

if (suffix_u)

return ST_UINT64;

else

return ST_INT64;

}

if (suffix_l) {

if (suffix_u)

Page 19: Использование библиотеки анализа кода OpenC++: модификация, улучшение, исправление ошибок

return ST_ULONG;

else

return ST_LONG;

}

if (suffix_u)

return ST_UINT;

assert(suffix_i);

return ST_INT;

}

SimpleType GetHexLiteral(const char *from, size_t l en,

unsigned __int64 &retValue ) {

assert(len >= 3);

const char *p = from + 2;

if (!GetHex(p, len, retValue)) {

return ST_UNKNOWN;

}

ptrdiff_t newLen = len - (p - from);

assert(newLen >= 0 && newLen < static_cast<ptrdif f_t>(len));

return GetTypeBySufix(p, newLen);

}

bool IsOctLiteral(const char *from, size_t len) {

if (len < 2)

return false;

if (from[0] != '0')

return false;

return true;

}

SimpleType GetOctLiteral(const char *from, size_t l en,

unsigned __int64 &retValue ) {

assert(len >= 2);

Page 20: Использование библиотеки анализа кода OpenC++: модификация, улучшение, исправление ошибок

const char *p = from + 1;

if (!GetOct(p, len, retValue)) {

return ST_UNKNOWN;

}

ptrdiff_t newLen = len - (p - from);

assert(newLen >= 0 && newLen < static_cast<ptrdif f_t>(len));

return GetTypeBySufix(p, newLen);

}

SimpleType GetDecLiteral(const char *from, size_t l en,

unsigned __int64 &retValue ) {

assert(len >= 1);

const char *limit = from + len;

unsigned __int64 n = 0;

while (from < limit) {

const char c = *from;

if (c < '0' || c > '9')

break;

from++;

n = n * 10 + (c - '0');

}

ptrdiff_t newLen = limit - from;

if (newLen == static_cast<ptrdiff_t>(len))

return ST_UNKNOWN;

retValue = n;

assert(newLen >= 0 && newLen < static_cast<ptrdif f_t>(len));

return GetTypeBySufix(from, newLen);

}

SimpleType GetLiteralType(const char *from, size_t len,

unsigned __int64 &retValu e) {

if (from == NULL || len == 0)

Page 21: Использование библиотеки анализа кода OpenC++: модификация, улучшение, исправление ошибок

return ST_UNKNOWN;

retValue = 1;

if (from == NULL || len == 0)

return ST_UNKNOWN;

if (GetCharLiteral(from, len, retValue))

return ST_LESS_INT;

if (GetStringLiteral(from, len))

return ST_POINTER;

if (GetBoolLiteral(from, len, retValue))

return ST_LESS_INT;

if (IsRealLiteral(from, len))

return GetRealLiteral(from, len);

if (IsHexLiteral(from, len))

return GetHexLiteral(from, len, retValue);

if (IsOctLiteral(from, len))

return GetOctLiteral(from, len, retValue);

return GetDecLiteral(from, len, retValue);

}

6. Исправление функции обработки строковых литералов Мы предлагаем модифицировать функцию Lex::ReadStrConst() так, как показано ниже. Это

позволит исправить две ошибки, связанные с обработкой разделенных строковых литералов.

Первая ошибка возникает при обработке строк вида:

const char *name = "Viva\

Core";

Вторая - при обработке:

const wchar_t *str = L"begin"L"end".

Исправленный вариант функции:

bool Lex::ReadStrConst(size_t top, bool isWcharStr)

{

char c;

Page 22: Использование библиотеки анализа кода OpenC++: модификация, улучшение, исправление ошибок

for(;;){

c = file->Get();

if(c == '\\'){

c = file->Get();

// Support: "\"

if (c == '\r') {

c = file->Get();

if (c != '\n')

return false;

} else if(c == '\0')

return false;

}

else if(c == '"</str>'){

size_t pos = file->GetCurPos() + 1;

ptrdiff_t nline = 0;

do{

c = file->Get();

if(c == '\n')

++nline;

} while(is_blank(c) || c == '\n');

if (isWcharStr && c == 'L') {

//Support: L"123" L"456" L "789".

c = file->Get();

if(c == '"')

/* line_number += nline; */ ;

else{

file->Unget();

return false;

}

} else {

Page 23: Использование библиотеки анализа кода OpenC++: модификация, улучшение, исправление ошибок

if(c == '"')

/* line_number += nline; */ ;

else{

token_len = ptrdiff_t(pos - top);

file->Rewind(pos);

return true;

}

}

}

else if(c == '\n' || c == '\0')

return false;

}

}

7. Частичное исправление обработки выражений вида "bool r = a < 1

|| b > (int) 2;" В библиотеке OpenC++ существует ошибка, связанная с обработкой некоторых выражений,

которые ошибочно воспринимаются как шаблоны. Например, в строке "bool r = a < 1 || b > (int) 2;"

переменная "a" будет принята за имя шаблона, а дальше, дальше начнутся беды с синтаксическим

анализом... Полноценное исправление данной ошибки требует существенных изменений и на

данный момент не реализовано. Мы предлагаем промежуточное решение, исключающее

основную часть ошибок. Ниже приведены функции, которые нужно добавить или

модифицировать:

bool VivaParser::MaybeTypeNameOrClassTemplate(Token &token) {

if (m_env == NULL) {

return true;

}

const char *ptr = token.GetPtr();

ptrdiff_t len = token.GetLen();

Bind *bind;

bool isType = m_env->LookupType(ptr, len, bind);

return isType;

}

Page 24: Использование библиотеки анализа кода OpenC++: модификация, улучшение, исправление ошибок

static bool isOperatorInTemplateArg(ptrdiff_t t) {

return t == AssignOp || t == EqualOp || t == LogO rOp ||

t == LogAndOp || t == IncOp || t == RelOp;

}

/*

template.args : '<' any* '>'

template.args must be followed by '(' or '::'

*/

bool Parser::isTemplateArgs()

{

ptrdiff_t i = 0;

ptrdiff_t t = lex->LookAhead(i++);

if(t == '<'){

ptrdiff_t n = 1;

while(n > 0){

ptrdiff_t u = lex->LookAhead(i++);

/*

TODO. :(

Fixing: bool r = a < 1 || b > (int) 2;

Исправим не все случаи, но все равно станет лучше.

Метод правки. Если нашли идентификатор рядом

с оператором, то это явно не шаблон, так как

внутри скобок может быть только тип или константное

выражение.

Пример, который все равно не работает:

r = a < fooi() || 1 > (int) b;

К сожалению, теперь неправильно обрабатывается

приведенное ниже выражение, но таких случаев

меньше, чем поправленных.

Page 25: Использование библиотеки анализа кода OpenC++: модификация, улучшение, исправление ошибок

template <int z>

unsigned TFoo(unsigned a) {

return a + z;

}

enum EEnum { EE1, EE2 };

b = TFoo < EE1 && EE2 > (2);

*/

ptrdiff_t next = lex->LookAhead(i);

if (u == Identifier &&

isOperatorInTemplateArg(next))

return false;

if (isOperatorInTemplateArg(u) &&

next == Identifier)

return false;

if(u == '<')

++n;

else if(u == '>')

--n;

else if(u == '('){

ptrdiff_t m = 1;

while(m > 0){

ptrdiff_t v = lex->LookAhead(i+ +);

if(v == '(')

++m;

else if(v == ')')

--m;

else if(v == '\0' || v == ';' | | v == '}')

return false;

}

Page 26: Использование библиотеки анализа кода OpenC++: модификация, улучшение, исправление ошибок

}

else if(u == '\0' || u == ';' || u == ' }')

return false;

}

t = lex->LookAhead(i);

return bool(t == Scope || t == '(');

}

return false;

}

8. Улучшенная обработка ошибок К сожалению, реализованный в OpenC++ механизм обработки ошибок иногда приводит к

аварийному завершению программы. Проблемным местом в OpenC++ является код, аналогичный

этому:

if(!rDefinition(def)){

if(!SyntaxError())

return false;

SkipTo('}');

lex->GetToken(cp); // WARNING: crash in the same case.

body = PtreeUtil::List(new Leaf(op), 0, new Leaf( cp));

return true;

}

Следует уделить внимание местам, где происходит обработка ошибок, и скорректировать, как это

показано на примере функций Parser::rLinkageBody() и Parser::SyntaxError(). Общий смысл

исправлений заключается в том, что после возникновения ошибки не следует сразу извлекать

следующую лексему, используя GetToken(), а нужно вначале проверить ее наличие, используя

функцию CanLookAhead():

bool Parser::rLinkageBody(Ptree*& body)

{

Token op, cp;

Ptree* def;

if(lex->GetToken(op) != '{')

return false;

Page 27: Использование библиотеки анализа кода OpenC++: модификация, улучшение, исправление ошибок

body = 0;

while(lex->LookAhead(0) != '}'){

if(!rDefinition(def)){

if(!SyntaxError())

return false; // too many errors

if (lex->CanLookAhead(1)) {

SkipTo('}');

lex->GetToken(cp);

if (!lex->CanLookAhead(0))

return false;

} else {

return false;

}

body =

PtreeUtil::List(new (GC) Leaf(op), 0,

new (GC) Leaf(cp));

return true; // error re covery

}

body = PtreeUtil::Snoc(body, def);

}

lex->GetToken(cp);

body = new (GC)

PtreeBrace(new (GC) Leaf(op), body, new (GC) Leaf(cp));

return true;

}

bool Parser::SyntaxError()

{

syntaxErrors_ = true;

Token t, t2;

Page 28: Использование библиотеки анализа кода OpenC++: модификация, улучшение, исправление ошибок

if (lex->CanLookAhead(0)) {

lex->LookAhead(0, t);

} else {

lex->LookAhead(-1, t);

}

if (lex->CanLookAhead(1)) {

lex->LookAhead(1, t2);

} else {

t2 = t;

}

SourceLocation location(GetSourceLocation(*this , t.ptr));

string token(t2.ptr, t2.len);

errorLog_.Report(ParseErrorMsg(location, token) );

return true;

}

9. Обновление функции rTemplateDecl2 Не вдаваясь в детали, предлагаем Вам заменить функцию rTemplateDecl2() на предложенный

вариант. Это исключит некоторые ошибки при работе с шаблонными классами:

bool Parser::rTemplateDecl2(Ptree*& decl,

TemplateDeclKind &kind)

{

Token tk;

Ptree *args = 0;

if(lex->GetToken(tk) != TEMPLATE)

return false;

if(lex->LookAhead(0) != '<') {

if (lex->LookAhead(0) == CLASS) {

// template instantiation

decl = 0;

Page 29: Использование библиотеки анализа кода OpenC++: модификация, улучшение, исправление ошибок

kind = tdk_instantiation;

return true; // ignore TEMPLATE

}

decl = new (GC)

PtreeTemplateDecl(new (GC) LeafReserved(tk) );

} else {

decl = new (GC)

PtreeTemplateDecl(new (GC) LeafReserved(tk) );

if(lex->GetToken(tk) != '<')

return false;

decl = PtreeUtil::Snoc(decl, new (GC) Leaf(tk ));

if(!rTempArgList(args))

return false;

if(lex->GetToken(tk) != '>')

return false;

}

decl =

PtreeUtil::Nconc(decl,

PtreeUtil::List(args, new (GC) Leaf(tk)));

// ignore nested TEMPLATE

while (lex->LookAhead(0) == TEMPLATE) {

lex->GetToken(tk);

if(lex->LookAhead(0) != '<')

break;

lex->GetToken(tk);

if(!rTempArgList(args))

return false;

if(lex->GetToken(tk) != '>')

return false;

}

Page 30: Использование библиотеки анализа кода OpenC++: модификация, улучшение, исправление ошибок

if (args == 0)

// template < > declaration

kind = tdk_specialization;

else

// template < ... > declaration

kind = tdk_decl;

return true;

}

10. Определение позиции Ptree в тексте программы В некоторых случаях бывает необходимо знать, где в тексте программы расположен код, из

которого был построен определенный объект Ptree.

Ниже предлагается функция, возвращающая адрес начала и конца области памяти с текстом

программы, из которой был создан указанный Ptree.

void GetPtreePos(const Ptree *p, const char *&begin ,

const char *&end) {

if (p == NULL)

return;

if (p->IsLeaf()) {

const char *pos = p->GetLeafPosition();

if (begin == NULL) {

begin = pos;

} else {

begin = min(begin, pos);

}

end = max(end, pos);

}

else {

GetPtreePos(p->Car(), begin, end);

GetPtreePos(p->Cdr(), begin, end);

}

Page 31: Использование библиотеки анализа кода OpenC++: модификация, улучшение, исправление ошибок

}

11. Поддержка объявлений вида const A (a) Библиотека OpenС++ не поддерживает объявление переменных вида "const A (a)". Для

исправления этого недочета необходимо внутри функции Parser::rOtherDeclaration изменить

участок кода:

if(!rDeclarators(decl, type_encode, false))

return false;

Вместо него следует использовать:

if(!rDeclarators(decl, type_encode, false)) {

// Support: const A (a);

Lex::TokenIndex after_rDeclarators = lex->Save();

lex->Restore(before_rDeclarators);

if (lex->CanLookAhead(3) && lex->CanLookAhead(-2) ) {

ptrdiff_t c_2 = lex->LookAhead(-2);

ptrdiff_t c_1 = lex->LookAhead(-1);

ptrdiff_t c0 = lex->LookAhead(0);

ptrdiff_t c1 = lex->LookAhead(1);

ptrdiff_t c2 = lex->LookAhead(2);

ptrdiff_t c3 = lex->LookAhead(3);

if (c_2 == CONST && c_1 == Identifier &&

c0 == '(' && c1 == Identifier && c2 == ')' &&

(c3 == ';' || c3 == '='))

{

Lex::TokenContainer newEmptyContainer;

ptrdiff_t pos = before_rDeclarators;

lex->ReplaceTokens(pos + 2, pos + 3, newEmpty Container);

lex->ReplaceTokens(pos + 0, pos + 1, newEmpty Container);

lex->Restore(before_rDeclarators - 2);

bool res = rDeclaration(statement);

return res;

Page 32: Использование библиотеки анализа кода OpenC++: модификация, улучшение, исправление ошибок

}

}

}

В приведенном коде используются некоторые вспомогательные функции, отсутствующие в этой

статье. Но вы можете найти их в библиотеке 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

}

Page 33: Использование библиотеки анализа кода OpenC++: модификация, улучшение, исправление ошибок

}

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

}

Page 34: Использование библиотеки анализа кода OpenC++: модификация, улучшение, исправление ошибок

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;

Page 35: Использование библиотеки анализа кода OpenC++: модификация, улучшение, исправление ошибок

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;

Page 36: Использование библиотеки анализа кода OpenC++: модификация, улучшение, исправление ошибок

}

}

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;

}

Page 37: Использование библиотеки анализа кода OpenC++: модификация, улучшение, исправление ошибок

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;

Page 38: Использование библиотеки анализа кода OpenC++: модификация, улучшение, исправление ошибок

}

/* 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.

Пишите нам!

Page 39: Использование библиотеки анализа кода OpenC++: модификация, улучшение, исправление ошибок

Библиографический список 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.

3. OpenC++ library. http://www.viva64.com/go.php?url=16

4. Андрей Карпов, Евгений Рыжков. Сущность библиотеки анализа кода VivaCore.

http://www.viva64.com/art-2-1-696420215.html

5. Semantic Designs site. http://www.viva64.com/go.php?url=19

6. Сайт компании Интерстрон. http://www.viva64.com/go.php?url=42

7. What is OpenTS? http://www.viva64.com/go.php?url=17

8. Евгений Рыжков. Viva64: что это и для кого? http://viva64.com/art-1-1-2081052208.html

9. Synopsis: A Source-code Introspection Tool. http://www.viva64.com/go.php?url=18

10. Сайт ООО "СиПроВер". http://www.Viva64.com