1.83K subscribers
3.24K photos
127 videos
15 files
3.52K links
Блог со звёздочкой.

Много репостов, немножко программирования.

Небольшое прикольное комьюнити: @decltype_chat_ptr_t
Автор: @insert_reference_here
Download Telegram
Блог*
Что делает @insert_reference_here, когда у него освобождается свободное время в выходные? Ну разумеется решает заковыристую задачку с лайфтаймами с работы.
Что делает @insert_reference_here, когда у него освобождается свободное время в выходные? Ну разумеется решает чужую заковыристую задачку с macro_rules!.
Forwarded from Reactor Live
This media is not supported in your browser
VIEW IN TELEGRAM
joy/ #interestingasfuck
Извлечение электричества из катушки Теслы с помощью шприца
#prog #rust

Хозяйке на заметку

Как известно, в macro_rules! можно использовать спецификатор повтора ? в форме $(some complex pattern)?. Вопрос: что делать, если опицональный захват нужно развернуть в Some($захват), если он есть, и в None, если его нету? Очень просто: достаточно делегировать эту задачу очень простому макросу make_option!:

macro_rules! make_option {
() => { ::core::option::Option::None };
($($anything:tt)*) => { ::core::option::Option::Some($($anything)*) };
}

Разумеется, это будет работать без сбоев только в том случае, если захват не может соответствовать пустой последовательности лексем.
#itsec #article

И да, извините, что в этот раз не прочитал статью целиком (пока). Одним из уязвимых мест оказался парсер изображений. Сложный парсинг, который запускается на недоверенных данных и потому может стать вектором хакерской атаки — просто идеальный юзкейс для Rust.
Извините, что ночью, но исследователи утверждают, что Pegasus стабильно взламывает самую последнюю версию айфона (14.6). Без всяких кликов по ссылкам, просто посылают жертве «заряженную смску» (сообщение в iMessage) и всё, атакующий владеет телефоном.

Речь идет об использовании уязвимости нулевого дня, о которой пока не знает Apple. Благодаря этому, атакующие могут даже не сохранять взлом в долгосрочной памяти, так что после перезагрузки телефона почти все следы атаки стираются — будет нужно, взломают ещё раз. Это усложняет исследование.

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

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

Если вы интересны спецслужбам — выключите iMessage в настройках айфона.
Блог*
#prog Переписал по работе одну утилиту для анализа логов. Раньше для разбора строк использовались регулярные выражения, а я заменил на наколенный лексер. В результате утилита, которая почти 23 гигабайта перемалывает за чуть больше, чем за 5 минут, стала на…
#prog #rust #parsing #моё

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

Начнём с определения центральной структуры для разбора:

#[derive(Clone)]
struct Lexer<'a> {
s: &'a str,
}

impl<'a> Lexer<'a> {
fn of(s: &'a str) -> Self {
Self { s }
}
}

Да, всё верно, это буквально обёртка над строковым слайсом. Все последующие методы будут принимать мутабельную ссылку на Lexer и будут возвращать Option<Something>, где Some означает, что нечто удалось распарсить, а None будет обозначать ошибку. При этом все эти методы будут следовать одному правилу: если что-то распарсить не удалось, то Lexer остаётся ровно в том же состоянии, что был до вызова.

Начнём с метода, который будет обозначать конец разбора — он пригодится нам, чтобы удостовериться, что мы не оставили никакого мусора:

fn end(&mut self) -> Option<()> {
if self.s.is_empty() {
Some(())
} else {
None
}
}

Следующий метод не очень полезен сам по себе, но он потребуется для других методов. Он просто будет сдвигать позицию, с которой производится парсинг:

fn shift(&mut self, pos: usize) {
self.s = &self.s[pos..];
}

Что ж, теперь напишем самый простой метод, который действительно что-то парсит, а именно — конкретную переданную строку:

fn literal(&mut self, literal: &str) -> Option<()> {
self.s = self.s.strip_prefix(literal)?;
Some(())
}

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

fn before_literal(&mut self, literal: &str) -> Option<&'a str> {
let pos = self.s.find(literal)?;
let ret = &self.s[..pos];
self.shift(pos + literal.len());
Some(ret)
}

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

fn number<Num: std::str::FromStr>(&mut self) -> Option<Num> {
let pos = self
.s
.as_bytes()
.iter()
.position(|ch| !ch.is_ascii_digit())
.unwrap_or(self.s.len());
let ret = self.s[..pos].parse().ok()?;
self.shift(pos);
Some(ret)
}

Clippy тут, к сожалению, ругается на вызов .unwrap_or(self.s.len())... Точнее, ругался раньше, теперь это исправили. Отлично!

Казалось бы, что можно сделать с таким набором методов? Ну, этого уже достаточно, чтобы распарсить IPv4-адрес:

#[derive(PartialEq, Debug)]
struct Ip4Addr([u8; 4]);

fn parse_ip(s: &str) -> Option<Ip4Addr> {
let mut p = Lexer::of(s);
let a = p.number()?;
p.literal(".")?;
let b = p.number()?;
p.literal(".")?;
let c = p.number()?;
p.literal(".")?;
let d = p.number()?;
p.end()?;
Some(Ip4Addr([a, b, c, d]))
}

fn main() {
assert_eq!(parse_ip("12.32.200.21"), Some(Ip4Addr([12, 32, 200, 21])));
}

Обратите внимание, благодаря выводу типов нам не пришлось уточнять тип для p.number().

Но далеко не все форматы являются столь же простыми и линейными. Добавим чуточку больше возможностей, а именно — возможность распарсить что-то опционально. В отличие от предыдущих методов, тут None не будет фатальной ошибкой:

fn optional<T, F: FnOnce(&mut Self) -> Option<T>>(&mut self, f: F) -> Option<T> {
let backtrack = self.clone();
let ret = f(self);
if ret.is_none() {
*self = backtrack;
}
ret
}

В принципе, теперь мы можем распарсить IP4-адрес с опциональной маской подсети, но я покажу кое-что более интересное: парсинг из задачи Advent of Code 2020.
Частью решения является разбор строк вида:

bright white bags contain 1 shiny gold bag.
vibrant plum bags contain 5 faded blue bags, 6 dotted black bags.
faded blue bags contain no other bags.

Напишем функцию для разбора одной подобной строки:

fn parse_bags_with_amount(line: &str) -> Option<(&str, Vec<(&str, usize)>)> {
let mut p = Lexer::of(line);
// ...

Повторяющимся элементом каждой строки является подстрока "bags contain". Извлечём то, что находится до этой строки — это и будет цветом сумки, про содержимое которой идёт речь:

    let outer_color = p.before_literal(" bags contain ")?;

А вот теперь возможны варианты. Если далее идёт "no other bags.", то на этом можно разбор заканчивать и возвращать пустой список:

    if p.optional(|p| {
p.literal("no other bags.")?;
p.end()
})
.is_some()
{
return Some((outer_color, Vec::new()));
}

А вот теперь нужно распарсить список значений, разделённых запятыми. К сожалению, я не придумал ничего лучшего, чем сделать безусловный цикл, который на каждой итерации будет парсить очередную порцию, а в конце итерации пытаться парсить запятую и завершать цикл, если это не удалось:

    let mut inner_colors = Vec::new();
loop {
// ...
if p.optional(|p| p.literal(", ")).is_none() {
break;
}
}

Тут бы пригодился цикл с постусловием, которого в Rust, увы, нету :/

Теперь нам нужно извлечь число сумок, их цвет и само слово bag, учтя при это, что оно может быть во множественном числе:

        // ...
let amount = p.number()?;
let inner_color = p.before_literal(" bag")?.trim_start_matches(' ');
p.optional(|p| p.literal("s"));
inner_colors.push((inner_color, amount));
// ...

Строка заканчивается точкой:

    p.literal(".")?;
p.end()?;

Если выполнение дошло до этой точки, значит, парсинг успешен:

Some((outer_color, inner_colors))

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

fn main() {
let input = "\
bright white bags contain 1 shiny gold bag.
vibrant plum bags contain 5 faded blue bags, 6 dotted black bags.
faded blue bags contain no other bags.";
let parsed = input
.lines()
.map(parse_bags_with_amount)
.map(Option::unwrap)
.collect::<Vec<_>>();
assert_eq!(
parsed,
[
("bright white", vec![("shiny gold", 1)]),
("vibrant plum", vec![("faded blue", 5), ("dotted black", 6)]),
("faded blue", vec![]),
],
);
}

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

UPD (16/10/2022): добавил метод для парсинга числа со знаком
The internet is for porn cats, так что держите котика
Которепости і підписюни (2)
Уведомление по почте в час ночи: "Ваш MR [который уже пару месяцев пилится и совсем недавно пришёл в состояние, пригодное для аппрува] теперь нельзя смержить из-за конфликтов".

Кто, блин, пушает код в рабочую репу в такое время?

#трудовыебудни
Forwarded from This Software Dev Does(n’t)? Exist
Фраза дня (не с работы): А/Б тестирование уровня Аушвиц/Бухенвальд

#shitposting
Ливкодинг - это вроде виджеинга, только картинка создаётся кодом. Программа пишется прямо по ходу выступления и исходник виден на экране.

Вчера впервые поливкодил на местном рейве. Думал меня хватит на полтора часа, но продержался часов шесть, пока не посветлело. Беруши помогли :-)

Забавно, что никто не отличает замысел от багов. Можно писать, что попало. А все думают, что так и было задумано. Лишь бы не черный экран.
Forwarded from Cosy Code
Посылку в сортировочном центре сортировали-сортировали, да не высортировали