Verificaci贸n del c贸digo de Telegram Open Network por el analizador PVS-Studio

Imagen 1

Telegram Open Network (TON) es una plataforma de los creadores del Telegram messenger, que, adem谩s de blockchain, contiene un gran conjunto de servicios. Los desarrolladores publicaron recientemente un c贸digo de plataforma escrito en C ++ y lo publicaron en GitHub. Quer铆amos ver el proyecto antes de su lanzamiento oficial.

Introduccion


Telegram Open Network es un conjunto de varios servicios. Por ejemplo, el proyecto tiene su propio sistema de pago basado en la criptomoneda Gram, tambi茅n hay una m谩quina virtual TON VM: los contratos inteligentes funcionan en ella. Hay un servicio de mensajer铆a: TON Messages. El proyecto en s铆 tiene como objetivo combatir la censura de Internet.

El proyecto utiliza el sistema de ensamblaje CMake, por lo que no hubo problemas especiales con el ensamblaje y la verificaci贸n. El c贸digo est谩 escrito en C ++ 14 y tiene 210 mil l铆neas:

Cuadro 5

El proyecto es peque帽o y de alta calidad, por lo que no hubo tantos errores, pero son dignos de atenci贸n y correcci贸n.

C贸digo de retorno


static int process_workchain_shard_hashes(....) { .... if (f == 1) { if ((shard.shard & 1) || cs.size_ext() != 0x20000) { return false; // <= } .... int r = process_workchain_shard_hashes(....); if (r < 0) { return r; } .... return cb.store_bool_bool(true) && cb.store_ref_bool(std::move(left)) && cb.store_ref_bool(std::move(right)) && cb.finalize_to(branch) ? r : -1; .... } 

Advertencia de PVS-Studio: V601 El valor 'falso' se convierte impl铆citamente en el tipo entero. mc-config.cpp 884

Parece que en este punto los desarrolladores alteraron el estado de error devuelto. Aparentemente, la funci贸n deber铆a devolver un valor negativo en caso de falla, y no verdadero / falso. Al menos, es el retorno de -1 lo que se puede observar en el siguiente c贸digo.

Comparar una variable consigo misma


 class LastBlock : public td::actor::Actor { .... ton::ZeroStateIdExt zero_state_id_; .... }; void LastBlock::update_zero_state(ton::ZeroStateIdExt zero_state_id) { .... if (zero_state_id_ == zero_state_id_) { return; } LOG(FATAL) << ....; } 

Advertencia de PVS-Studio: V501 Hay subexpresiones id茅nticas a la izquierda y a la derecha del operador '==': zero_state_id_ == zero_state_id_ LastBlock.cpp 66

El c贸digo TON se escribe utilizando un est谩ndar de codificaci贸n en el que los miembros de la clase se nombran con un gui贸n bajo al final. Sin embargo, dicha ortograf铆a, como en este caso, puede pasar desapercibida y provocar un error. El par谩metro que entra en esta funci贸n tiene un nombre similar a un miembro de la clase, por lo que son f谩ciles de confundir. Lo m谩s probable es que fuera 茅l quien participara en la comparaci贸n.

Macro insegura


 namespace td { namespace detail { [[noreturn]] void process_check_error(const char *message, const char *file, int line); } // namespace detail } #define CHECK(condition) \ if (!(condition)) { \ ::td::detail::process_check_error(#condition, __FILE__, __LINE__); \ } void BlockDb::get_block_handle(BlockIdExt id, ....) { if (!id.is_valid()) { promise.set_error(....); return; } CHECK(id.is_valid()); // <= .... } 

Advertencia de PVS-Studio: V581 Las expresiones condicionales de las declaraciones 'if' ubicadas una junto a la otra son id茅nticas. Verifique las l铆neas: 80, 84. blockdb.cpp 84

La condici贸n dentro de la macro CHECK nunca se cumplir谩, porque su verificaci贸n ya estaba dentro del if anterior.

Hay otro error: la macro CHECK no es segura, porque la condici贸n en su interior no est谩 envuelta en una construcci贸n do {...} while (0) . Esto es para evitar colisiones con otras condiciones en otra cosa . En otras palabras, este c贸digo no funcionar谩 como se esperaba:

 if (X) CHECK(condition) else foo(); 

Validaci贸n de variable de signo


 class Slice { .... char operator[](size_t i) const; .... }; td::Result<int> CellSerializationInfo::get_bits(td::Slice cell) const { .... int last = cell[data_offset + data_len - 1]; if (!last || last == 0x80) { // <= return td::Status::Error("overlong encoding"); } .... } 

Advertencia de PVS-Studio: V560 Una parte de la expresi贸n condicional siempre es falsa: 煤ltimo == 0x80. boc.cpp 78

La segunda parte de la condici贸n nunca se cumplir谩, porque escriba char en este caso tiene un signo. Al asignar un valor a una variable de tipo int , el signo se expandir谩, por lo tanto, el rango de valores de la variable permanecer谩 en el l铆mite [-128, 127] y no en [0, 256].

Vale la pena se帽alar que el tipo char no siempre se firmar谩; este comportamiento depende de la plataforma y el compilador. Entonces, esta condici贸n a煤n se puede cumplir te贸ricamente cuando se ensambla en otra plataforma.

Bit negativo Bit Bit Shift


 template <class Tr> bool AnyIntView<Tr>::export_bits_any(....) const { .... int mask = (-0x100 >> offs) & 0xff; .... } 

Advertencia de PVS-Studio: V610 Comportamiento no especificado. Verifique el operador de turno '>>'. El operando izquierdo '-0x100' es negativo. bigint.hpp 1925

La operaci贸n de desplazar bit a bit un n煤mero negativo hacia la derecha es un comportamiento no especificado: no se sabe c贸mo se comportar谩 el signo en este caso: 驴se expandir谩 o se llenar谩 con ceros?

Comprobar nulo despu茅s de nuevo


 CellBuilder* CellBuilder::make_copy() const { CellBuilder* c = new CellBuilder(); if (!c) { // <= throw CellWriteError(); } .... } 

Advertencia de PVS-Studio: V668 No tiene sentido probar el puntero 'c' contra nulo, ya que la memoria se asign贸 utilizando el operador 'nuevo'. La excepci贸n se generar谩 en caso de error de asignaci贸n de memoria. CellBuilder.cpp 531

Todo est谩 claro en la advertencia del analizador: en caso de que la asignaci贸n de memoria no sea exitosa, se generar谩 una excepci贸n y no se devolver谩 un puntero nulo. La verificaci贸n no tiene sentido.

Volver a comprobar


 int main(int argc, char* const argv[]) { .... if (!no_env) { const char* path = std::getenv("FIFTPATH"); if (path) { parse_include_path_set(path ? path : "/usr/lib/fift", source_include_path); } } .... } 

Advertencia de PVS-Studio: V547 La expresi贸n 'ruta' siempre es verdadera. fift-main.cpp 136

Este c贸digo fue tomado en una de las utilidades internas. El operador ternario en este caso es superfluo: la condici贸n que est谩 registrada ya existe dentro del if anterior. Lo m谩s probable es que olvidaron eliminar el operador ternario cuando quer铆an abandonar las rutas est谩ndar (al menos no se dijo nada sobre ellas en el mensaje de ayuda).

Variable no utilizada


 bool Op::set_var_info_except(const VarDescrList& new_var_info, const std::vector<var_idx_t>& var_list) { if (!var_list.size()) { return set_var_info(new_var_info); } VarDescrList tmp_info{new_var_info}; tmp_info -= var_list; return set_var_info(new_var_info); // <= } 

Advertencia de PVS-Studio: V1001 La variable 'tmp_info' se asigna pero no se usa al final de la funci贸n. analyzer.cpp 140

Aparentemente, en la 煤ltima l铆nea de esta funci贸n, se plane贸 usar la variable tmp_info . Aqu铆 est谩 el c贸digo para la misma funci贸n, pero con otros especificadores de par谩metros:

 bool Op::set_var_info_except(VarDescrList&& new_var_info, const std::vector<var_idx_t>& var_list) { if (var_list.size()) { new_var_info -= var_list; // <= } return set_var_info(std::move(new_var_info)); } 

Mas o menos?


 int compute_compare(const VarDescr& x, const VarDescr& y, int mode) { switch (mode) { case 1: // > return x.always_greater(y) ? 1 : (x.always_leq(y) ? 2 : 3); case 2: // = return x.always_equal(y) ? 1 : (x.always_neq(y) ? 2 : 3); case 3: // >= return x.always_geq(y) ? 1 : (x.always_less(y) ? 2 : 3); case 4: // < return x.always_less(y) ? 1 : (x.always_geq(y) ? 2 : 3); case 5: // <> return x.always_neq(y) ? 1 : (x.always_equal(y) ? 2 : 3); case 6: // >= return x.always_geq(y) ? 1 : (x.always_less(y) ? 2 : 3); case 7: // <=> return x.always_less(y) ? 1 : (x.always_equal(y) ? 2 : (x.always_greater(y) ? 4 : (x.always_leq(y) ? 3 : (x.always_geq(y) ? 6 : (x.always_neq(y) ? 5 : 7))))); default: return 7; } } 

Advertencia de PVS-Studio: V1037 Dos o m谩s ramificaciones de casos realizan las mismas acciones. L铆neas de verificaci贸n: 639, 645 builtins.cpp 639

Los lectores atentos pueden haber notado que falta la operaci贸n <= en este c贸digo. Y de hecho, deber铆a ser el n煤mero 6. Esto se puede encontrar en dos lugares. El primero es la inicializaci贸n:

 AsmOp compile_cmp_int(std::vector<VarDescr>& res, std::vector<VarDescr>& args, int mode) { .... if (x.is_int_const() && y.is_int_const()) { r.set_const(compute_compare(x.int_const, y.int_const, mode)); x.unused(); y.unused(); return push_const(r.int_const); } int v = compute_compare(x, y, mode); .... } void define_builtins() { .... define_builtin_func("_==_", arith_bin_op, std::bind(compile_cmp_int, _1, _2, 2)); define_builtin_func("_!=_", arith_bin_op, std::bind(compile_cmp_int, _1, _2, 5)); define_builtin_func("_<_", arith_bin_op, std::bind(compile_cmp_int, _1, _2, 4)); define_builtin_func("_>_", arith_bin_op, std::bind(compile_cmp_int, _1, _2, 1)); define_builtin_func("_<=_", arith_bin_op, std::bind(compile_cmp_int, _1, _2, 6)); define_builtin_func("_>=_", arith_bin_op, std::bind(compile_cmp_int, _1, _2, 3)); define_builtin_func("_<=>_", arith_bin_op, std::bind(compile_cmp_int, _1, _2, 7)); .... } 

En la funci贸n define_builtins , puede ver la llamada compile_cmp_int para <= con el par谩metro de modo igual a 6.

Bueno, la segunda es la misma funci贸n compile_cmp_int , que tiene los nombres de las operaciones:

 AsmOp compile_cmp_int(std::vector<VarDescr>& res, std::vector<VarDescr>& args, int mode) { .... static const char* cmp_names[] = {"", "GREATER", "EQUAL", "GEQ", "LESS", "NEQ", "LEQ", "CMP"}; .... return exec_op(cmp_names[mode], 2); } 

Debajo del 铆ndice 6 est谩 la palabra LEQ , que significa Menos o Igual.

Otro hermoso error relacionado con la clase de errores en las funciones de comparaci贸n .

Otros


 #define VM_LOG_IMPL(st, mask) \ LOG_IMPL_FULL(get_log_interface(st), ...., VERBOSITY_NAME(DEBUG), \ (get_log_mask(st) & mask) != 0, "") // <= 

Advertencia de PVS-Studio: V1003 La macro 'VM_LOG_IMPL' es una expresi贸n peligrosa. El par谩metro 'm谩scara' debe estar rodeado de par茅ntesis. log.h 23

La macro VM_LOG_IMPL no es segura. Su segundo par谩metro no est谩 rodeado de par茅ntesis; esto puede provocar efectos secundarios si se pasa una expresi贸n compleja a la condici贸n. Si la m谩scara es solo una constante, entonces no habr谩 problemas con este c贸digo, pero nada nos impide pasar algo m谩s a la macro.

En conclusi贸n


El c贸digo TON result贸 no ser tan grande y no contiene muchos errores. Por lo cual, por supuesto, vale la pena rendir homenaje a los programadores del equipo de Telegram. Sin embargo, incluso ellos no son inmunes a los errores. El analizador de c贸digo es una herramienta poderosa que puede encontrar lugares peligrosos en las primeras etapas, incluso en bases de c贸digo de alta calidad, por lo que no se debe descuidar su uso. El an谩lisis est谩tico no es una herramienta para las comprobaciones 煤nicas, sino que forma parte del proceso de desarrollo: " Incruste el an谩lisis est谩tico en el proceso y no busque errores ".



Si desea compartir este art铆culo con una audiencia de habla inglesa, utilice el enlace a la traducci贸n: Sergey Larin. Comprobaci贸n de Telegram Open Network con PVS-Studio .

Source: https://habr.com/ru/post/469919/


All Articles