#rust
The Ferrocene Language Specification is here!
Наконец-то опубликован первый черновик спеки для Rust
The Ferrocene Language Specification is here!
Наконец-то опубликован первый черновик спеки для Rust
👍8❤1
#prog #go
В стандартной библиотеке Go в пакете net/http есть тип Header, который, как следует из документации, является newtype-ом над
> ReverseProxy is an HTTP Handler that takes an incoming request and sends it to another server, proxying the response back to the client.
До 30 апреля 2020 года там также было следующее:
> ReverseProxy automatically sets the client IP as the value of the X-Forwarded-For header. If an X-Forwarded-For header already exists, the client IP is appended to the existing values.
Это поведение не для всех было удобным: "In our situation, the proxy should be transparent and invisible to the servers, so we'd like the proxy not to add any headers to the HTTP requests he forwards. Unfortunately, there is no option to disable the addition of the X-Forwarded-For header." В качестве решения предлагалось либо добавить явную опцию для подавления этого поведения, включённую по умолчанию, либо добавить хук, который бы редактировал заголовки непосредственно перед отправкой ответа.
К этому разработчики Go, как ни странно, прислушались, и в итоге исправили — правда, по своему, не реализовав ни тот, ни другой предложенный вариант. В изменении эксплуатировался тот факт, что слайс в Go является ссылочным типом (даром, что эта терминология в документации Go не используется): слайс может быть
> ReverseProxy by default sets the client IP as the value of the X-Forwarded-For header. If an X-Forwarded-For header already exists, the client IP is appended to the existing values. As a special case, if the header exists in the Request.Header map but has a nil value (such as when set by the Director func), the X-Forwarded-For header is not modified.
Это не очень изящное решение, хотя бы потому, что неконсистентно — в большинстве других мест пустые слайсы и
...Или нет. Метод ReverseProxy.ServeHTTP безусловно вызывает Request.Clone, внутри которого происходит вызов Header.Clone — ручной реализации глубокого копирования
В исправлении в начало тела этого цикла добавили
Для сравнения, у аналогичного по функционалу HeaderMap из крейта http, не смотря на более сложное устройство (и Амос объяснял, почему более сложное), вся реализация глубокого копирования сводится к #[derive(Clone)].
В стандартной библиотеке Go в пакете net/http есть тип Header, который, как следует из документации, является newtype-ом над
map[string][]string
. Значения этого типа хранят соответствие между заголовками в HTTP-запросе и связанной с ними информацией. Header
используется в net/http.Request, а тот, в свою очередь — в net/http/httputil.ReverseProxy. Как сказано в документации:> ReverseProxy is an HTTP Handler that takes an incoming request and sends it to another server, proxying the response back to the client.
До 30 апреля 2020 года там также было следующее:
> ReverseProxy automatically sets the client IP as the value of the X-Forwarded-For header. If an X-Forwarded-For header already exists, the client IP is appended to the existing values.
Это поведение не для всех было удобным: "In our situation, the proxy should be transparent and invisible to the servers, so we'd like the proxy not to add any headers to the HTTP requests he forwards. Unfortunately, there is no option to disable the addition of the X-Forwarded-For header." В качестве решения предлагалось либо добавить явную опцию для подавления этого поведения, включённую по умолчанию, либо добавить хук, который бы редактировал заголовки непосредственно перед отправкой ответа.
К этому разработчики Go, как ни странно, прислушались, и в итоге исправили — правда, по своему, не реализовав ни тот, ни другой предложенный вариант. В изменении эксплуатировался тот факт, что слайс в Go является ссылочным типом (даром, что эта терминология в документации Go не используется): слайс может быть
nil
, и это не то же самое, что и пустой слайс. После исправления новая документация ReverseProxy
гласила следующее:> ReverseProxy by default sets the client IP as the value of the X-Forwarded-For header. If an X-Forwarded-For header already exists, the client IP is appended to the existing values. As a special case, if the header exists in the Request.Header map but has a nil value (such as when set by the Director func), the X-Forwarded-For header is not modified.
Это не очень изящное решение, хотя бы потому, что неконсистентно — в большинстве других мест пустые слайсы и
nil
-слайсы ведут себя одинаково — и потому что заставляет пользователя ReverseProxy
вручную удалять заголовок. Но оно, по крайней мере, работало....Или нет. Метод ReverseProxy.ServeHTTP безусловно вызывает Request.Clone, внутри которого происходит вызов Header.Clone — ручной реализации глубокого копирования
Header
. Фрагмент кода оттуда:sv := make([]string, nv) // shared backing array for headers' values
h2 := make(Header, len(h))
for k, vv := range h {
n := copy(sv, vv)
h2[k] = sv[:n:n]
sv = sv[n:]
}
return h2
Встроенная функция copy ведёт себя одинаково и в том случае, когда vv
— пустой слайс, и в том случае, когда vv
— nil-слайс, и возвращает в обоих случаях 0. В следующей строчке мы видим безусловное присваивание h2[k] = sv[:n:n]
. Каким бы ни было значение n
, нарезка слайса никогда не возвращает nil-слайс. А это значит, что в возвращённой копии Header
nil-слайс будет замещён на пустой слайс, что и приводит к ошибочному поведению.В исправлении в начало тела этого цикла добавили
if
, который для случая, когда vv
является nil, присваивает соответствующему ключу в h2
значение nil
и переходит на следующую итерацию. Рядом с ним оставили комментарий: "Preserve nil values. ReverseProxy distinguishes between nil and zero-length header values".Для сравнения, у аналогичного по функционалу HeaderMap из крейта http, не смотря на более сложное устройство (и Амос объяснял, почему более сложное), вся реализация глубокого копирования сводится к #[derive(Clone)].
GitHub
net/http/httputil: do not add to empty X-Forwarded-For header in ReverseProxy · Issue #38079 · golang/go
What version of Go are you using (go version)? $ go version go version go1.13.8 linux/386 Does this issue reproduce with the latest release? Yes What operating system and processor architecture are...
❤1
Forwarded from Segment@tion fault
This media is not supported in your browser
VIEW IN TELEGRAM
.
😁16👍2👎1
Это, если что, анкета из частной клиники в Иркутске. И это не они сами придумали, а областной Минздрав.
twitter.com/TonyPepperoni/status/1554363027297902593
twitter.com/TonyPepperoni/status/1554363027297902593
🤡15🤮8🔥2🥴2
#prog #go #article
Даже странно, что я не публиковал это раньше.
50 Shades of Go: Traps, Gotchas, and Common Mistakes for New Golang Devs (зеркало, старый домен сдох) (перевод: 50 оттенков Go: ловушки, подводные камни и распространённые ошибки новичков)
Даже странно, что я не публиковал это раньше.
Devs ♥ Security
50 Shades of Go: Traps, Gotchas, and Common Mistakes for New Golang Devs
Go is a simple and fun language, but, like any other language, it has a few gotchas... Many of those gotchas are not entirely Go's fault. Some of these mistakes are natural traps if you are coming from another language....
Ну почему, почему в Visual Studio Code нельзя просто так взять и открыть один и тот же файл в двух вкладках в одной группе аааааааааааааа
#бомбёжкипост
#бомбёжкипост
GitHub
Allow to open the same file in multiple editors of the same group · Issue #41289 · microsoft/vscode
I wish I could open the same file in multiple tabs. Basically, I want the same functionality as cmd+2 gives me, but without creating a split. In another editor that I use, my workflow consists of t...
🤬1
Решил посмотреть, как же всё-таки работает reqwest
@
> The WSAStartup function must be the first Windows Sockets function called by an application or DLL. <...> The application or DLL can only issue further Windows Sockets functions after successfully calling WSAStartup.
Какой поганый дизайн. Мне больно.
@
> The WSAStartup function must be the first Windows Sockets function called by an application or DLL. <...> The application or DLL can only issue further Windows Sockets functions after successfully calling WSAStartup.
Какой поганый дизайн. Мне больно.
Docs
WSAStartup function (winsock.h) - Win32 apps
The WSAStartup function (winsock.h) initiates use of the Winsock DLL by a process.
Блог*
Решил посмотреть, как же всё-таки работает reqwest @ > The WSAStartup function must be the first Windows Sockets function called by an application or DLL. <...> The application or DLL can only issue further Windows Sockets functions after successfully calling…
Короче, фан-факт (если я, конечно, нигде ничего не напутал): если вы запускаете на Windows программу, которая использует reqwest, то до того, как будет исполнен первый запрос и даже до того, как будет забинден первый сокет для исполнения этого запроса, программа забиндит на 127.0.0.1 порт под номером 34254.
А теперь о том, почему так.
И нет, я так и не понял, почему именно этот порт.
UPD: в комментариях подсказывают, что именно такой порт используется в примере в документации на
А теперь о том, почему так.
reqwest
зависит от hyper, hyper
зависит от socket2, а socket2
ради максимальной гибкости не использует сетевые функции из std (ну, почти), а дёргает сисколы напрямую. Как было сказано выше, перед тем, как что-то делать с сокетами на Windows, надо сначала вызвать инициализирующую функцию, которая подгрузит нужную DLL (конкретно ws2_32.dll). Естественно, её нужно вызвать лишь один раз, да ещё и достаточно аккуратно, чтобы не потянуть с собой зависимость на ws2_32.dll, когда в программе вообще ничего сетевого не вызывается. std уже это делает. Поэтому, по всей видимости, для того, чтобы не дублировать код, а также для того, чтобы не нарушать работу программ, также использующих сокеты из std, конструктор сокета из socket2
на Windows в конечном счёте (во всяком случае, в версии 0.4.4) однократно вызывает std::net::UdpSocket::bind("127.0.0.1:34254").И нет, я так и не понял, почему именно этот порт.
UPD: в комментариях подсказывают, что именно такой порт используется в примере в документации на
std::net::UdpSocket
.docs.rs
socket2 - Rust
Utilities for creating and using sockets.
😱7
Forwarded from Врен о Японии для туриста
Дорога пилигримов Кумано в префектуре Вакаяма проложена меж гигантских кедров и без людей выглядит весьма сказочно. Конкретно этот сегмент находится близ водопадов Нати.
👍6🤩3😱1
#prog #c #cpp #моё
Среди некоторых людей (особенно среди тех, кто давно пишет на языке программирования с именем из одной буквы) бытует мнение, что в низкоуровневом ЯП не нужны ни обобщения, ни вывод типов. Это, конечно же, неправда, и я продемонстрирую на конкретных примерах, что они вполне себе полезны даже вбайтоё перекладывании байтиков.
Начнём с дженериков. Для манипуляций с памятью C предлагает memset, memcpy, memmove и malloc. Так как обобщённых типов в C нету, вместо конкретных типов эти функции оперируют указателями на
Один из вариантов такой ошибки — считать
V568:
Wolfenstein 3D:
V531:
XUIFramework:
WinMerge:
Miranda NG:
Среди некоторых людей (особенно среди тех, кто давно пишет на языке программирования с именем из одной буквы) бытует мнение, что в низкоуровневом ЯП не нужны ни обобщения, ни вывод типов. Это, конечно же, неправда, и я продемонстрирую на конкретных примерах, что они вполне себе полезны даже в
Начнём с дженериков. Для манипуляций с памятью C предлагает memset, memcpy, memmove и malloc. Так как обобщённых типов в C нету, вместо конкретных типов эти функции оперируют указателями на
void
. memset
, memcpy
и memmove
третьим аргументом и malloc
первым принимают размер куска памяти, которым они оперируют. Из-за фактически нетипизированных указателей этот размер выражен не в единицах объектов, а в char
-ах. Для того, чтобы правильно их использовать, нужно неизбежно использовать sizeof
— и тут есть возможность внести ошибку!Один из вариантов такой ошибки — считать
sizeof
от указателя, а не от того, на что этот указатель указывает. Примеры возьму из ошибок, найденных в реальных проектах PVS-Studio:V568:
Wolfenstein 3D:
void CG_RegisterItemVisuals( int itemNum ) {
....
itemInfo_t *itemInfo;
....
memset( itemInfo, 0, sizeof( &itemInfo ) );
....
}
Miranda IM:void ExtraImage_SetAllExtraIcons(HWND hwndList,HANDLE hContact)
{
....
char *(ImgIndex[64]);
....
memset(&ImgIndex,0,sizeof(&ImgIndex));
....
}
Energy Checker SDK:int plh_read_pl_folder(PPLH_PL_FOLDER_INFO pconfig) {
....
WIN32_FIND_DATA file_data;
....
memset(
&file_data,
0,
sizeof(&file_data)
);
....
}
Другой вариант такой ошибки (больше распространённый в случае с malloc) — это неправильно подсчитать само количество, не в форме sizeof(type) * (expr_count)
. Примеры:V531:
XUIFramework:
CPPString CPPHtmlDrawer::GetStringFromDll(....)
{
....
TCHAR szTemp[256];
DWORD dwLen = ::LoadString(hInstDll, dwID,
szTemp, (sizeof(szTemp) * sizeof(TCHAR)));
....
}
GPCS4:struct GnmCmdPSShader
{
....
uint32_t reserved[27];
};
int PS4API sceGnmSetPsShader350(....)
{
....
memset(param->reserved, 0, sizeof(param->reserved) * sizeof(uint32_t));
return SCE_OK;
}
V635:WinMerge:
intV641:
iconvert_new (LPCTSTR source, LPTSTR *destination,
int source_coding, int destination_coding,
bool alphabet_only)
{
LPTSTR dest = (LPTSTR) malloc (_tcslen (source) + 1 + 10);
....
}
Miranda NG:
INT_PTR CALLBACK DlgProcThemeOptions(....)
{
....
str = (TCHAR *)malloc(MAX_PATH+1);
....
}
Все эти ошибки объединяет то, что количество оперируемой памяти можно вывести из фактического типа указателя — до того, как он будет скастован до void*
. Сохранив тип указателя, мы можем сделать более вменяемый интерфейс! И качестве иллюстрации я даже буду использовать C++ вместо Rust — как язык, наиболее близкий к C и при этом позволяющий писать обобщённый код:#include <cstddef>
#include <cstring>
#include <cstdlib>
namespace typed {
template <typename T>
T* memset_n(T *dest, int ch, std::size_t count) {
return static_cast<T*>(
std::memset(dest, ch, count * sizeof(T))
);
}
template <typename T>
T* memset(T *dest, int ch) {
return memset_n(dest, ch, 1);
}
/* опущены определения memcpy{, _n}, memmove, realloc и calloc{, _n} */
template<typename T>
T* malloc_n(std::size_t count) {
return static_cast<T*>(std::malloc(count * sizeof(T)));
}
template<typename T>
T* malloc() {
return malloc_n<T>(1);
}
} // namespace typed
PVS-Studio
Примеры ошибок, обнаруженных с помощью диагностики V568
V568. It is suspicious that the argument of sizeof() operator is the expression.
👍6👎1😁1
Между прочим, с
malloc
связана некоторая неэффективность: так как функция не знает, объекты какого типа она реально выделяет и, соответственно, какое выравнивание нужно для возвращаемого указателя, она вынуждена возвращать указатель с максимально возможным выравниванием. В результате выделение памяти поодиночке для значений типа с меньшим выравниванием приводит к бо́льшему потреблению памяти, чем, вообще говоря, нужно. Если мы задействуем aligned_alloc в реализации typed::malloc
, то, возможно, мы сможем более плотно упаковать выделенную память:template<typename T>
T* malloc_n(std::size_t count) {
return static_cast<T*>(
aligned_alloc(alignof(T), count * sizeof(T))
);
}
Ещё одна неэффективность, связанная со стиранием типов, связана с тем, что аллокатор вынужден где-то хранить метаинформацию о количестве выделенной памяти. В большинстве современных аллокаторов есть отдельные пулы памяти для обработки запросов выделения конкретных небольших размеров памяти. Использование free
с void*
-аргументом вынуждает аллокатор просматривать адреса всех выделенных блоков, чтобы определить, какой именно блок нужно освобождать. Использование типизированных версий функций для выделения и освобождения памяти позволяет, во-первых, не хранить метаинформацию о размере выделенной памяти под единичные экземпляры типа — эту информацию и так можно получить через sizeof
, а во-вторых, используя тот же sizeof
, не просматривать все пулы блоков, а сразу выбрать нужный, зная размер типа (вот вам, кстати, и ответ на вопрос, почему в C++ вкупе с new
и delete
есть new[]
и delete[]
). К сожалению, эти выгоды — не то, что можно легко продемонстрировать.🔥3👍1👎1
А теперь — о выводе типов. В ситуациях, когда важно экономить память, часто используются битовые маски. Эти битовые маски бывают разных типов и, соответственно, разных размеров. Часто эти маски конструируются при помощи сдвига единицы. И тут есть подвох: если не указывать конкретный тип, числовые литералы в C (да и в C++) имеют тип
V784:
Perl 5:
Кто-то, конечно, может возразить, что для предотвращения подобной ошибки достаточно явно при помощи суффиксов приписать тип литералу. У меня есть два довода против этого аргумента. Во-первых, это — ручная работа, которую можно поручить автоматическому инструменту (компилятору) — а чем больше работы мы можем свалить на компилятор, тем меньше шанс ошибки из-за человеческого фактора. Во-вторых, подобный код получается хрупким: в случае, если тип битовой маски по каким-то причинам меняется, программисту придётся искать и исправлять типы связанных вычислений и литералов руками — и компилятор тут никак не поможет.
P. S.: а ещё вывод типов, как ни странно, может помочь в чём-то схожей ситуацией в C#: там из-за типа литерала по умолчанию можно случайно сломать логику сравнения, а также получить enum с одинаковыми значениями. В C, что характерно, аналогичный код на платформе с 32-битным
P. P. S.: как всегда, весь код в гисте.
P. P. P. S.: телега почему-то ломает сообщение при попытке вставить ссылку в уже написанное сообщение, так что вот ссылки на V635 и V641
int
. Если требуемая маска имеет больший размер, чем int
, то сдвиг единицы на слишком большой размер может привести к тому, что верхние биты будут ошибочно выкинуты — даже не смотря на то, что они могли бы остаться в маске, если бы она изначально имела достаточно большой тип. И если вам кажется, что это слишком нереалистичная ситуация, то вам примеры из реальных проектов:V784:
Perl 5:
void ZLIB_INTERNAL inflate_fast(z_streamp strm, unsigned start)
{
....
unsigned long hold; /* local strm->hold */
unsigned bits; /* local strm->bits */
....
hold &= (1U << bits) - 1;
....
}
LLVM/Clang:unsigned getStubAlignment() override {
....
}
Expected<unsigned>
RuntimeDyldImpl::emitSection(const ObjectFile &Obj,
const SectionRef &Section,
bool IsCode) {
....
uint64_t DataSize = Section.getSize();
....
if (StubBufSize > 0)
DataSize &= ~(getStubAlignment() - 1);
....
}
Как тут может помочь вывод типов? В языке с выводом типов числовые литералы могут иметь выводимый тип, а не фиксированный тип (Rust, Haskell). В этом случае компилятор может посмотреть, значению какого типа присваивается результат вычисления битовой маски, и вывести соответствующий для литерала — или вывести ошибку, если типы разные (технически, конечно, ничто не мешает компилятору приписать литералу fallback-тип и вставить преобразование, но мне неизвестны ЯП, в которых одновременно были бы вывод типов и неявные арифметические конверсии).Кто-то, конечно, может возразить, что для предотвращения подобной ошибки достаточно явно при помощи суффиксов приписать тип литералу. У меня есть два довода против этого аргумента. Во-первых, это — ручная работа, которую можно поручить автоматическому инструменту (компилятору) — а чем больше работы мы можем свалить на компилятор, тем меньше шанс ошибки из-за человеческого фактора. Во-вторых, подобный код получается хрупким: в случае, если тип битовой маски по каким-то причинам меняется, программисту придётся искать и исправлять типы связанных вычислений и литералов руками — и компилятор тут никак не поможет.
P. S.: а ещё вывод типов, как ни странно, может помочь в чём-то схожей ситуацией в C#: там из-за типа литерала по умолчанию можно случайно сломать логику сравнения, а также получить enum с одинаковыми значениями. В C, что характерно, аналогичный код на платформе с 32-битным
int
при компиляции выдаёт ошибку.P. P. S.: как всегда, весь код в гисте.
P. P. P. S.: телега почему-то ломает сообщение при попытке вставить ссылку в уже написанное сообщение, так что вот ссылки на V635 и V641
PVS-Studio
Примеры ошибок, обнаруженных с помощью диагностики V784
V784. The size of the bit mask is less than the size of the first operand. This will cause the loss of higher bits.
👍3🥰1
Ну типично, да.
Антон: пишет маленькую заметку
Маленькая заметка: расползается на несколько сообщений
Антон: пишет маленькую заметку
Маленькая заметка: расползается на несколько сообщений
👍11