Обновить
8K+
5

Программист

-5
Рейтинг
9
Подписчики
Отправить сообщение
Маленькие файлы =)

Зависит от языка программирования. Вы бы еще сравнили 100 кб ассемблерного кода и 100 кб С++.


В два раза медленней — это не на порядок.

Написано же — минимум. И я не делал оптимизаций специальных для грамматики, процесса работы и прочего.
По памяти гарантированно на порядок лучше, так как вы же сами писали, что требует 1 ГБ ОЗУ для 100 кб вашего кода. Сравните это с 128 мб. Причем я еще и выгружать данные на диск в процессе анализа могу :D В том числе в процессе резолва — маппинга типов на исходный код.


Поддерживается ли у вас межпроцедурный и межфайловый taint анализ?

Модель R для того и разрабатывалась, вернее это минимум, что должно быть. Больше говорить не стану. Межпроцедурным и межфайловым анализом как раз 5 лет и занимался, так что с этим проблем точно не будет =) Цель покрупнее идет, именно поэтому все что отвечает за SAST должно быть "идеальным". Ресурсы нужны для других целей.

А 130Кб — уж извините, но ни о чем.

Зависит от языка. Для PLSQL — действительно ни о чем, но в случае минифицированного javascript — это очень много. В распакованном виде будет 2 мб исходников с очень нетривиальной логикой внутри.
Видимо я неясно выразился.


А можно ссылку на этот файл для начала? Для парсинга JavaScript мы сейчас используем Esprima.NET — порт оригинального парсера Esprima на JavaScript. Но, боюсь, это не то, что вас интересует, т.к. работает он побыстрее ANTLR, правда не обладает такой же гибкостью, поддерживает не все синтаксические конструкции.

Нет смысла сравнивать тогда универсальные решения и специализированные. Написать под конкретный язык можно и без ANTLR.

а на сколько оно там памяти потребляет и как быстро бегает.

Парсер в 8 потоков все файлы меньше 10 кб парсит при 128 мб хипа, можно ужать до 64 мб, работать будет, но кроме парсера есть еще штуки, которые память по умолчанию щадить не могут.
Если речь про большие файлы, то при однопоточном анализе, минифицированный вендор.js на 100-130 кб можно запихнуть в те же рамки, просто сборщик мусора будет работать больше.
По быстродействию скажу, что текущие универсальные решения (не специализированные, как например в браузере), будут работать минимум в 2 раза медленнее, чем rscan, во всяком случае уж точно быстрее Positive Tech =) Сложно быть медленее…
В целом планирую, что 90% проектов будут taint-анализироваться с хипом в 1 гб, этого должно быть достаточно, да и разработчику будет удобнее пользоваться непосредственно в процессе разработки, и видеть проблемные места. Насколько мне известно, уж 4 гб ОЗУ у разработчиков как правило есть, значит и для SAST место найдется. Для всяких мелкосервисов и онлайн-магазов самое то!


А почему вас интересуют такие вопросы? Спиён? =)
Все же лучше задавать вопросы по теме статьи, мы же не rscan в целом обсуждаем сейчас, а конкретно оптимизацию ANTLR. Есть ли у вас по этой теме вопросы?

Все же java есть, в статье удалял код из грамматик, потому что по какой-то причине хабра-парсер превращает всю статью в код.

Добавил в статью код, вообще можно вот тут посмотреть: лексер
Предлагаю самостоятельно разобраться, почему эта свертка будет работать быстрее, чем правило, написанное на ANTLR, а так же почему то, что вы предлагаете не будет работать.


Можете вкратце рассказать, что из себя представляет ваш SAST, какие уязвимости
находит, и почему он идеальный?

Без холиваров.


Да и грамматика упростилась — не пришлось бы в каждом блоке прописывать код для создания конкретного узла.

Грамматика не станет проще, вы пытаетесь смотреть на частности, не увидев всю картину, причем даже в том участке проекта, который был раскрыт на гитхабе — уже можно увидеть причину, почему строится собственное дерево.
AST — это абстрактное синтаксное дерево подходящее для любого, у чего есть синтаксис. Языки разработки — более конкретное множество, поэтому те операции, что создаются в обход ANTLR — имеют более строгие ограничения, чем обычный AST. При этом весь вывод уже имеет многочисленные маркировки и преобразования, из-за которых потом обработка будет проходить быстрее.
Если вы посмотрите, как например выглядит декларация переменной, то увидите, что будет что-то такое:


LANG at /ex.js[1:0-1:49]
    Language: JavaScript
    Children: 
        RAW_DECL_LOCAL at /ex.js[1:12-1:48]
            Children: 
                NAME at /ex.js[1:12-1:33]
                    Children: 
                        UNRESOLVED_ID at /ex.js[1:12-1:33]
                            Name: regexUnnecessaryIndex
                MODIFIERS at /ex.js[1:12-1:48]
                    Modifiers: const 
                INIT at /ex.js[1:36-1:48]

В будущем работать именно с такими структурами будет куда как проще, так же как и юнит-тестировать саму грамматику и всякие штуки для обработки такого сырого документа.


Я предлагаю вам все же посмотреть проект и убедится, что то, что вы сейчас говорите не позволит экономить память. Попробуйте закоментировать вот эту строку, найти файл на 130+ кб и запустить анализ с -Xmx128m. Чудеса — правда? Памяти почему-то потребляется не столько же.


PS можно свертки (такие же как в статье, а не то, что вы написали в виде правила) реализовать и с листенерами/визиторами, просто будете ловить напрямую токен FoldBlock и обрабатывать его другим способом. Но это долго и много мороки, сложно тестировать, вариант с кодом непосредственно в грамматике гораздо лучше.


Вопрос вам: сколько времени нужно вашему анализатору Positive Tech, что бы получить AST для tsserver.js? Можно приблизительно. Достаточно измерить время между началом работы начального правила и его окончания.

речь не про "падать", а про игнорирование файла.


Искать хоть "что-то" в том, что не может работать по определению — бессмысленная работа и демонстрация первичных половых признаков будет не так уж глупо выглядеть.


особенно если используется C++.

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

Не увидел чего-то сложного перейдя по вашей ссылке.
То что понять что есть что на уровне лексера не выйдет, да и у парсера будут проблемы — да.
Но зачем идти по извилистой земляной дорожке в поле, если рядом есть шоссе?


Советую попробовать другие варианты работы с исходными кодами. Особенно это касается AST, он вам не нужен. UST — ближе, но все еще не то.


А есть ли у вас какой-нибудь опыт по обработке препроцессорных директив?

Писал, правда 5 дней было потрачено на него и, как говорил Костя, пару багов исправил, уже после моего увольнения. Соответствие спецификации 2011 года — 100%, но есть там нюансы со склейкой токенов, видимо на них баги и были.
Есть небольшое расхождение в части переноса строк, в GNU они уходят, в спеке говорится, что должны оставаться. Я про макросы и параметры.


Есть два способа — делать сложные стейтменты (когда у стейтмента может быть предок), либо же результаты макро-обработки мэппить на координаты исходного токена (или токенов). Что бы избежать наложения адресов советую добавить virtual — тогда каждый новый токен будет иметь свой адрес при любых раскладах. Указывать будет на точку применения макроса.


Вообще это отдельная тема и обсуждать ее тут не вижу смысла.

Кстати, по поводу очистки ATN. Сам создатель оптимизированного ANTLR C# рантайма рекомендует переинициализировать ATN, если нужно снизить потребление памяти.

Основное потребление памяти из-за необходимости парсера делать слишком долгие lookahead, именно из-за него получаются хипы на 4 ГБ забитые под завязку. С ATN память будет медленно течь, особую проблему быстродействия не доставляет.


Были реальные исходники, где файлы внезапно обрывались, из-за этого SLL переключался на медленный LL, что приводило к тормозам. Ну и да — есть требование в идеале парсить код с любыми ошибками, включая синтаксические.

вы все равно в этом коде ничего не сможете получить, зачем тогда парсить?
В случае синтаксических ошибок в SLL режиме лучше делать сводку о причинах и просить пользователя отправить сообщение разработчику.
В LL режиме нет смысла, разве что вы пишете IDE и неплохо было бы давать адекватную подсветку каждый раз.

Разве нельзя инициализировать парсер так?

В Java можно это сделать, но проще пропатчить генерируемый класс, чем везде выставлять такую штуку, тем более что в C# достаточно 2 аргументов, а в Java их надо 4. Впрочем попробую, может оно и в правду лучше работать будет.


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

Летом отчет по вебгоату присылали. Версию я не помню, осилить HASP ваш не смог :D


А как парсить файлы, в которых есть синтаксические ошибки и их нужно максимально корректно обрабатывать? Т.е. либо добавлять токен, либо удалять токен, либо пропускать последовательность до синхронизирующего токена (типа точки с запятой). Я не пробовал как работает SLL со стандартной стратегией обработки ошибок.

А зачем парсить файлы с синтаксическими ошибками?
Если это какая-то мелкая ошибка, то тем более фолдинг — лучшее решение, потому что потеряется только фрагмент, хотя достаточно требовать, что бы код компилировался родной утилитой, тогда проблем не будет.
SLL хорошо работает на компилируемых файлах. Мусор отправлять на анализ тоже такое себе дело.
Или у вас парсер позволяет в txt файле java-код или какой-нибудь еще?

Каким образом, просто обнулять ссылки медленно?

Неправильно выразился, парсер, если ему отдавать сразу все работает очень медленно.
А заменить в джаве final static нельзя (на самом деле можно, но это будет слабоумие и отвага).


Как это работает, как парсится. Можно поподробней?

  override def nextToken(): Token = {
    if (hasEnqueuedTokens) dequeueToken
    else super.nextToken() match {
      case x: RScanToken if x.getType == foldOpen => buildFoldToken(x)
      case x: Token => x
    }
  }

buildFoldToken рекурсивно собирает все токены начиная с { и по }, создает для них токен с адресом от { по }, внутри имеет список токенов, которые он поглотил, включая такие же фолды.
А уже в ANTLR идет так:


functionBody[boolean yield, boolean await]
    :   LBRACE stmtList[$yield, $await, true] RBRACE
    |   FoldBlock
    ;

В итоге и обычный режим работает, и оптимизация тоже пашет. Парсить токены внутри FoldBlock можно когда потребуется. А главное, что lookahead убивается на корню, потому что он не нужен для случаев, когда альтернативы быть не может, но ANTLR честно их ищет.


А какие языки? :)

Java.


В чем посредственность на конкретных примерах? Было бы интересно узнать, как это можно избежать. Ну и для этого они и открытые, чтобы сообщество могло их дорабатывать.

Они не SLL. Запускаешь эти грамматики на тестовом множестве (2000+ файлов), так половина минимум падает с ошибками.

Увы, но предложенное вами решение крайне медленное. Сброс кеша в джаве таким же способом не удастся реализовать, особенно, если есть многопоточный анализ. Возможно в C# как-то по другому реализованы статики.
Дано: грамматика JavaScript 2019, на вход посылаем файл в 127 кб минифицированного кода — 38 секунд на обработку, 12% gc max. Без xmx4g запускать бессысленно.
Включаем упаковку блоков в токен (создается токен, в котором есть список токенов) — 12 секунд xmx64m дает 5% gc max, при xmx128m 1% gc max, парсим блоки в многопоточном режиме на 8 ядрах — 4 секунды. ATN создается на каждый новый инстанс парсера.


Поэтапная обработка — наше все

Если под поэтапностью вы понимаете разбор одного и того же файла разными парсерами, то да, но использовать AST, создаваемый ANTLR все равно не рекомендую, слишком много лишней информации.


parser.getInterpreter().setPredictionMode(PredictionMode.LL);

Java, CSharp, Python, Php, JavaScript, TypeScript, C++, C, и многие другие языки достаточно просты, что бы сделать грамматику, которая будет работать без синтаксических ошибок в режиме SLL.
Вы что-то не так делаете в вашем парсере, что требуется включать такую фичу.


Впрочем я уже видел работу анализатора Positive Tech :D Воздержусь от критики.


Так сделано, например, в грамматике JavaScript.

Открытые грамматики реализованы крайне посредственно. Иногда можно встретить хорошее решение, но в общем случае лучше написать самому сразу SLL, чем давится ALL кактусом.


Для каждого пункта было бы неплохо добавить разъяснение и примеры кода как не надо и как надо (или наоборот, т.к. советы вредные). Особенно про ATN и SLL.

Будет отдельная статья, где объясню всю кухню работы с ANTLR4 на Java, данная статья была больше для того, что бы понять, есть ли интерес у людей к такой тематике.

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


  override def nextToken(): Token =
    if (hasEnqueuedTokens) dequeueToken
    else if (templateMode) nextTokenTemplate
    else nextTokenNonTemplate

  private def nextTokenNonTemplate: Token = {
    val token = _input.LA(1) match {
      case '`' => parseTemplateLiteral
      case _ => super.nextToken()
    }
    if (token.getChannel == Token.DEFAULT_CHANNEL)
      lastToken = token
    token
  }

  private def nextTokenTemplate: Token =
    if (!insideTemplate) {
      if (_input.LA(1) != '`')
        throw new IllegalStateException("Template string must start with '`' character")
      insideTemplate = true
      val token = createToken(_input.index(), 1, templateLiteralDelimiter, Token.HIDDEN_CHANNEL)
      templateStartToken()
      token
    } else if (_input.index() == closePosition) {
      if (_input.LA(1) != '}')
        throw new IllegalStateException("Close position is not at '}' character")
      val token = createToken(_input.index(), 1, templateLiteralDelimiter, Token.HIDDEN_CHANNEL)
      templateMiddleOrStopToken()
      token
    } else nextTokenNonTemplate

Это кусок кода для анализа TypeScript/JavaScript, гораздо выгоднее проанализировать собственными руками последовательность символов и сделать токен с шаблонной строкой, чем нагружать лексер, тем более, что в случае вложенности будут проблемы с анализом выражений. Для правила startTemplate токены TemplateLiteral* создавались не лексером, а руками. Код у правил удалил, потому что при просмотре превращается в кашу.


//////////////////////////////////////// Main Entry Points 
startTypeScript
    :   typeScriptStmtList EOF
    |   EOF
    ;

//////////////////////////////////////// Secondary Entry Points 
startTemplate
    :  
        TemplateLiteralStart
        // опционально, потому что TemplateLiteralStart может содержать окончание
        (
            expression[true,_yield,await] 
            (
                TemplateLiteralMiddle                
                expression[true,_yield,await] 
            )*
            TemplateLiteralStop
        )?
        EOF        
    ;

  private def parseTemplateLiteral: Token = {
    val start = _input.index()
    createToken(start, parseTemplateString(), templateLiteral)
  }

parseTemplateString — находит индекс последнего символа шаблонной строки с учетом вложенности.
Уже после этот литерал парсится отдельно. Например вот так:


    final def parse(): RLangOp = {
      val result = parseInitial()
      enqueue(result.folds ++ result.templates)
      while (jobs.nonEmpty) {
        val _j = Seq.empty ++ jobs
        jobs.clear()
        parseItems(_j)
      }
      result.op.asInstanceOf[RLangOp]
    }

Внутри parseItems все folds, templates так же добавляются в очередь jobs
Полагаю, что с комментариями можно такую же штуку провернуть.
Должно быть что-то в таком духе у вас:


methodWithMeta:
  MethodMeta?
  methodDecl[$MethodMeta]

Если поиграетесь с режимами работы лексера (mode — его можно устанавливать до выполнения), то можно все в одном уместить, и с парсером так же удастся сделать.


Правила можно легко вызывать вот таким образом:


  final def invokeRule[T](ruleName: String): T = resultOfRule[T](ruleName)

  private def resultOfRule[T](ruleName: String): T =
    resultField[T](getClass.getDeclaredMethod(ruleName).invoke(this))

  private def resultField[T](value: AnyRef): T =
    value.getClass.getDeclaredField("result").get(value).asInstanceOf[T]

Делаешь токен с новым типом, сохраняешь в него список токенов, регистрируешь каким-то образом токен, что бы можно было узнать о нем. Далее в местах, где есть блок, добавляешь альтернативу с новым типом токена. После анализа файла создаешь парсеры для сохраненных токенов и разбираешь их, пока не останется в регистре элементов. Лексер работает только один раз.
И вуаля, вместо 4 ГБ хипа хватает 128 мб.

В 2020 бы заниматься разработкой поиска уязвимостей по регулярным выражениям…
Еще в 2012 году такое было.

Когда тебя каждые пять минут гоняют от одной задачи к другой — не поднимите. Вот вообще никак. Кроме истерзанных нервов ничего не будет. Да еще и при увольнении выскажут много чего интересного.

Однако в защиту товарища хотелось бы упомянуть, что обычно у нас капиталисты говорят о каких-то рисках и прочее, однако вот оборотной стороной (прибылью) никогда не делятся с работниками.
Не про безответственное отношение сотрудника, а про любителей каждую проблему выдавать за срочную и исключительно по вине сотрудника (неоплачиваемую).
Но опять же, лично мое мнение, что возможность вызвать сотрудника в случае ЧП в любое время — это реально важно. Факторы бывают разные. Невозможно все предусмотреть на всех уровнях.

Вы уже сами описали, что смешали работу нескольких специальностей.
Я же описывал именно "возможность" напрягать мозги в одном направлении в течении долгого времени. По опыту — максимум возможностей — 8 часов, и то после тренировок. И кстати результаты совпадают с общественной практикой, люди вообще больше 8 часов в день не могут продуктивно работать над чем-то в течении всего года. Замечу, речь именно о среднесуточной нагрузке в течении года, а не "две недели 60 часов отработал, а потом неделю отдыхал".


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


аналитика, менеджера, программиста, тестировщика и DevOps.
При наличие более узкой области работы — возможно и получалось бы больше погружаться, хотя и тогда от постоянного изучения никуда не денешься, а вот со стороны заказчика это выглядит как конструктор собрать из уже готовых деталей — и на что там еще можно время потратить как не на отлынивание от работы!

Значит вы пока не работали над проектами, где один человек уже не в силах справляться со всеми этими функциями.
Но в целом — да. У нас так хаят "неквалифицированную рабочую силу", что забывают насколько "заказчики" бывают недалеки и неквалифицированны вообще во всех сферах кроме "заплатить меньше, получить больше".

Это решаемо, но в целом — да. Возможность хотя бы экстренно выйти на связь в случае ЧП — очень важна.


Либо же просто не работать в таких областях, где такое может произойти.

Тоже посчитал — 3-4 часа в день именно на написание кода.

Зависит от тренировки. Если начинал с самого детства, то лет за 10 можно и 6-8 часов в день выдерживать. Зависит еще сильно от общего физического состояния.
В 14 лет с трудом выжимал из себя 15-40 минут непрерывной концентрации на коде — поэтому везде ходил с блокнотом и карандашом — писал всякую хрень. К 30 годам 6 часов непрерывной умственной нагрузки — даже усталости нет.


Однако при приближении к 8 часам есть снижение всех когнитивных навыков, т.е. вообще всех, падает реакция, глаза болят и висок ноет (левый). По 7-8 часов в день больше 14 дней подряд не выдерживаю, нужен отдых. Силой никто не гонит, просто "хочется еще".


Если так весь год работать, то нужен отпуск примерно 48 дней.
Субъективно, согласен — но пища для размышлений есть.


Поняв что надо сделать — надо понять как это сделать

Так ведь уже сказали, что декомпозиция осуществлена. Все остальные операции — уже очень легкие и не требуют существенного труда.


Концентрироваться на активной интеллектуальной задаче более 3-4 часов в день — значит накапливать усталость.

Описал выше. Тренировка и общая физическая подготовка. Стоит обратить внимание на ваше питание. Почитайте про то, как маленькие улучшения коммулятивно дают прирост в разы.


Я сейчас стараюсь разбивать свой рабочий день на две части по 3 часа

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


У многих же заказчиков, полное впечатление, представление о программировании как о копании траншеи — от забора до заката. Причем сразу — сел и давай кодить…

"Многие" даже траншеи не копали. На прошлой работе начальник вполне легко мог отвлечь посреди задачи, или вовсе что-то обсуждать.
Это не проблема кодинга, а проблема воспитания людей и коммуникации между ними. Вы же не сможете в деталях объяснить быт и труд шахтера? Каждый человек живет в своем мире, просто у кого-то он размером с горошину — представления о других людях соответственные.

Информация

В рейтинге
Не участвует
Откуда
Москва, Москва и Московская обл., Россия
Зарегистрирован
Активность