>
>
Использование библиотеки анализа кода O…

Андрей Карпов
Статей: 674

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

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

Введение

В форумах часто можно услышать, что синтаксических анализаторов ("парсеров") языка Си++ в мире огромное количество. В том числе и бесплатных. Или, что можно взять, например, YACC и легко реализовать свой анализатор. Не верьте, все не так просто. Особенно, если вспомнить, что разобрать синтаксис – это меньше половины дела. Необходимо реализовать структуры для хранения дерева программы и семантических таблиц, содержащих информацию о различных объектах и областях их действия. Особенно это важно при разработке специализированных приложений, связанных с обработкой и статическим анализом Си++ кода. Для их реализации необходимо сохранение полного дерева программы, что могут предоставить не многие библиотеки. Одной из них является открытая библиотека OpenC++ (OpenCxx), о которой мы и поговорим в этой статье.

Хочется помочь разработчикам в освоении библиотеки OpenC++ и поделиться опытом ее модернизации и использования некоторых недочетов. Статья представляет собой сборник советов, каждый из которых посвящен исправлению какого-то дефекта или реализации усовершенствования.

Статья основывается на воспоминаниях об изменениях, которые были осуществлены в библиотеке VivaCore, основанной на базе OpenC++. Конечно, здесь отражена только малая часть этих изменений. Вспомнить и описать их все будет непростой задачей. Например, описание добавления в библиотеку OpenC++ поддержки языка Си займет много места. Но Вы всегда можете обратиться к исходным текстам библиотеки VivaCore и получить много интересной информации.

Последнее, о чем хочется сказать, что библиотека OpenC++, к сожалению, на данный момент устарела и нуждается в серьезной доработке для поддержания современного стандарта языка Си++. Поэтому, если Вы, например, собираетесь реализовать современный компилятор, то Вам лучше обратить внимание на GCC или посмотреть в сторону коммерческих библиотек. Но OpenC++ и сейчас остается хорошим и удобным инструментом для многих разработчиков в области систем специализированной обработки и модификации программного кода. C использованием OpenC++ разработаны многие интересные решения. Например: среда исполнения OpenTS для языка программирования T++ (разработка Института программных систем РАН), статический анализатор кода Viva64 или инструмент Synopsis для подготовки документации по исходному коду.

Цель данной статьи - показать на примерах, как можно модифицировать и улучшить код библиотеки 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:

Ptree* Encoding::MakePtree(unsigned char*& encoded, Ptree* decl)
{
    ...
        case 'W' :
            typespec = PtreeUtil::Snoc(typespec, w64_t);
            break;
    ...
}

Естественно, это только пример, и добавление других лексем может потребовать гораздо больше работы. Хороший способ корректно добавить новую лексему - взять близкую к ней по смыслу, а затем найти и изучить все места в библиотеке OpenC++ где она используется.

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

Мы уже рассмотрели способ, как пропустить единичные ключевые слова, не имеющие для нашей программы смысловой нагрузки, но которые мешают разбору кода. К сожалению, иногда дело обстоит сложнее. Возьмем в качестве демонстрации такие конструкции как __pragma и __noop, которые можно встретить в заголовочных файлах VisualC++:

__forceinline DWORD HEAP_MAKE_TAG_FLAGS (
     DWORD TagBase, DWORD Tag )
{
    __pragma(warning(push)) __pragma(warning(disable : 4548)) do
{__noop(TagBase);} while((0,0) __pragma(warning(pop)) );
    return ((DWORD)((TagBase) + ((Tag) << 18)));
}

Посмотреть описание конструкций __pragma и __noop можно в MSDN. Для нашей программы важно следующее: а) они нам не интересны; б) имеют параметры; в) мешают анализу кода.

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

static void InitializeOtherKeywords(bool recognizeOccExtensions)
{
  ...
  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;
        }
    ...
}

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_variant.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::Instance();
  string inputFileName;
  cfg.GetInputFileName(inputFileName);
  if (oldInputFileName == inputFileName)
    return fileDirectory;
  oldInputFileName = inputFileName;
  filesystem::path inputFileNamePath(inputFileName,
    filesystem::native);
  fileDirectory = inputFileNamePath.branch_path().string();
  if (fileDirectory.empty()) {
    TCHAR curDir[MAX_PATH];
    if (GetCurrentDirectory(MAX_PATH, curDir) != 0) {
      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::native);
  fileName = tmpPath.string();
  algorithm::replace_all(fileName, "/", "\\");
  to_lower(fileName);
  if (fileName.length() < 2) {
    assert(false);
    FileNamesMap.insert(make_pair(oldFileName, fileName));
    return;
  }
 
  if (fileName[0] == '.' && fileName[1] != '.') {
    const string &dir = GetInputFileDirectory();
    if (!dir.empty())
      fileName.replace(0, 1, dir);
    FileNamesMap.insert(make_pair(oldFileName, fileName));
    return;
  }
  if (isalpha(fileName[0]) && fileName[1] == ':' ) {
    FileNamesMap.insert(make_pair(oldFileName, fileName));
    return;
  }
  const string &dir = GetInputFileDirectory();
  if (dir.empty())
    fileName.insert(0, ".\\");
  else {
    fileName.insert(0, "\\");
    fileName.insert(0, dir);
  }
  FileNamesMap.insert(make_pair(oldFileName, fileName));
}

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');; 
}  
SimpleType GetTypeBySufix(  *from, size_t len) 
{   
  assert(from != NULL);    
  (len == 0)      
  ST_INT;   
  assert(!isdigit(*from));     
  suffix_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 = ; ; : assert();          
       ST_UNKNOWN;     
    }   
  }    
  assert(suffix_8 + suffix_16 + 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 GetHexLiteral(  *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 GetDecLiteral(  *from, size_t len, &retValue) 
{   
  assert(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')
    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;
}
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];
  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);
    return false;
  }
  retValue = c;
  return true;
}
//'A', '\t', L'A', '\xFE'
static bool GetCharLiteral(const char *from,
                           size_t len,
                           unsigned __int64 &retValue) {
  if (len >= 3) {
    if (from[0] == '\'' && from[len - 1] == '\'') {
      unsigned char c = from[1];
      if (c == '\\') {
        verify(GetEscape(from + 2, len - 3, retValue));
      } 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, retValue));
      } else {
        retValue = c;
      }
      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;
      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) {
  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;
  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;
      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)
      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 len,
                         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<ptrdiff_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 len,
                         unsigned __int64 &retValue) {
  assert(len >= 2);
  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<ptrdiff_t>(len));
  return GetTypeBySufix(p, newLen);
}
SimpleType GetDecLiteral(const char *from, size_t len,
                         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<ptrdiff_t>(len));
  return GetTypeBySufix(from, newLen);
}
SimpleType GetLiteralType(const char *from, size_t len,
                          unsigned __int64 &retValue) {
  if (from == NULL || len == 0)
    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;
    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 {
              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;
}
static bool isOperatorInTemplateArg(ptrdiff_t t) {
  return t == AssignOp || t == EqualOp || t == LogOrOp ||
         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;
            
            К сожалению, теперь неправильно обрабатывается
            приведенное ниже выражение, но таких случаев
            меньше, чем поправленных.
            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;
                }
            }
            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;
    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 recovery
        }
        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;
    
    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;
        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;
    }
    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);
  }
}

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, newEmptyContainer);
      lex->ReplaceTokens(pos + 0, pos + 1, newEmptyContainer);
      lex->Restore(before_rDeclarators - 2);
      bool res = rDeclaration(statement);
      return res;
    }
  }
}

В приведенном коде используются некоторые вспомогательные функции, отсутствующие в этой статье. Но вы можете найти их в библиотеке 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 declarator
        else if(isPtrToMember(1))
            return false;       // declarator (::*)
        else
            return true;        // maybe constructor
    }
}

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_length,
                           const char *&beginLinePtr) 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 #pragma
              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_length);
            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_start + 1;
                }
            }
        }
    }
    return line_number;
}

Заключение

Конечно, в этой статье описана малая часть возможных улучшений. Но хочется надеяться, что они будут полезны разработчикам при использовании библиотеки OpenC++ и станут примерами, демонстрирующими как можно специализировать библиотеку для своих задач.

Еще раз хочется напомнить, что показанные в этой статье и многие другие улучшения можно найти в коде библиотеки VivaCore. Для многих задач библиотека VivaCore может оказаться удобнее, чем OpenC++.

Если у Вас возникли вопросы, Вы хотите что-то добавить или прокомментировать, то наша команда Viva64.com всегда рада и открыта к общению. Мы готовы обсудить возникшие вопросы, дать рекомендации и помочь в использовании библиотеки OpenC++ или VivaCore. Пишите нам!

Библиографический список