Anunciando Dart 2.15

Concurrencia rápida, constructor tear-offs, enums mejorados, y más

Hoy lanzamos la versión 2.15 del SDK de Dart, que incluye concurrencia rápida con worker isolates, una nueva característica del lenguaje de tear-off de constructores, soporte mejorado de enums en la librería dart:core, nuevas características para publicadores de paquetes y más.

Banner de la versión Dart 2.15 destacando concurrencia, tear-offs de constructores y herramientas pub.

Concurrencia rápida con worker isolates

#

Casi todos los dispositivos modernos tienen CPUs con múltiples núcleos, capaces de ejecutar múltiples tareas en paralelo. Para la mayoría de los programas de Dart, cómo se usan estos núcleos es transparente para ti como desarrollador: el sistema de runtime de Dart por defecto ejecuta todo tu código de Dart en un solo núcleo, pero luego usa núcleos adicionales para ejecutar tareas a nivel de sistemas como entrada/salida asíncrona, como escribir un archivo o hacer una llamada de red.

Pero tu código de Dart mismo puede necesitar ejecutarse concurrentemente. Por ejemplo, puedes tener una animación continua y una tarea de larga duración como analizar un archivo JSON grande. Si la tarea adicional tarda demasiado, eso podría causar tartamudeo o retraso en la UI. Al mover esas tareas adicionales a un núcleo separado, la animación puede continuar ejecutándose en el hilo principal de ejecución, sin interrupciones.

El modelo de concurrencia de Dart se basa en isolates — unidades independientes de ejecución que están aisladas entre sí — para prevenir una gran clase de bugs de programación de concurrencia relacionados con la memoria compartida, como condiciones de carrera como las carreras de datos. Dart previene estos bugs al no permitir que ningún objeto mutable se comparta entre isolates, y en su lugar usa un modelo donde los isolates intercambian estado mediante paso de mensajes. En Dart 2.15 hemos hecho una serie de mejoras sustanciales a los isolates.

Empezamos rediseñando y reimplementando cómo funcionan los isolates, introduciendo un nuevo concepto: grupos de isolates. Los isolates en un grupo de isolates comparten varias estructuras de datos internas que representan el programa en ejecución*.* Esto hace que los isolates individuales del grupo sean mucho más económicos. Ahora es más de 100 veces más rápido iniciar un isolate adicional en un grupo de isolates existente ya que no necesitamos inicializar las estructuras del programa, y esos isolates generados consumen entre 10 y 100 veces menos memoria.

Mientras que los grupos de isolates aún previenen el acceso compartido a objetos mutables entre isolates, el grupo se implementa con un heap compartido, lo que desbloquea capacidades adicionales. Podemos pasar objetos de un isolate a otro, lo que puede usarse para worker isolates que realizan una tarea que retorna un gran bloque de memoria. Un ejemplo es un isolate worker que hace una llamada de red para obtener datos, analiza esos datos en un gran grafo de objetos JSON, y luego retorna ese grafo JSON al isolate principal. Antes de Dart 2.15, ese resultado necesitaba ser copiado profundamente, lo que por sí mismo podía causar problemas de fluidez en la UI si la copia tardaba más que el presupuesto de frame.

En 2.15 el isolate worker puede llamar a Isolate.exit(), pasando su resultado como argumento. El runtime de Dart entonces transfiere la memoria que contiene el resultado del isolate worker al isolate principal sin copiarla, y el isolate principal puede recibir el resultado en tiempo constante. Hemos actualizado la compute() función utilitaria en Flutter 2.8 para aprovechar Isolate.exit(). Si ya estás usando compute(), entonces obtendrás estas mejoras de rendimiento automáticamente después de actualizar a Flutter 2.8.

Finalmente, hemos rediseñado cómo está implementado el mecanismo de paso de mensajes entre isolates, haciendo que el paso de mensajes de tamaño pequeño a mediano sea aproximadamente 8 veces más rápido. El envío es significativamente más rápido, y la recepción de mensajes casi siempre se realiza en tiempo constante. También hemos ampliado los tipos de objetos que los isolates pueden enviarse entre sí, añadiendo soporte para tipos de función, closures y objetos stacktrace. Para más detalles, consulta la documentación del API para SendPort.send().

Para aprender más sobre cómo usar isolates, consulta la nueva documentación de Concurrency in Dart que añadimos para 2.15. También tenemos varios ejemplos de código que puedes revisar.

Nueva característica del lenguaje: Tear-offs de constructores

#

En Dart puedes crear un objeto función, que apunta a una función en otro objeto, usando el nombre de la función. En el siguiente ejemplo, la segunda línea del método main() ilustra esta sintaxis cuando asigna g a m.greet:

class Greeter {
  final String name;
  Greeter(this.name);

  void greet(String who) {
    print('$name says: Hello $who!');
  }
}

void main() {
  final m = Greeter('Michael');
  final g = m.greet; // g holds a function pointer to m.greet.
  g('Leaf'); // Invokes and prints "Michael says: Hello Leaf!"
}

Estos punteros de función — también conocidos como tear-offs de función — aparecen frecuentemente al usar las librerías core de Dart. Aquí hay un ejemplo de cómo llamar a foreach() en un iterable pasándole un puntero de función:

final m = Greeter('Michael');

['Lasse', 'Bob', 'Erik'].forEach(m.greet);

// Prints "Michael says: Hello Lasse!", "Michael says: Hello Bob!",
// "Michael says: Hello Erik!"

Históricamente no hemos soportado crear tear-offs desde un constructor (issue del lenguaje #216). Eso es molesto porque en muchos casos — por ejemplo, al construir UIs de Flutter — un tear-off de constructor es lo que necesitas. A partir de Dart 2.15, esta sintaxis ya está soportada. Aquí hay un ejemplo de cómo construir un widget Column que contiene tres widgets Text, llamando a .map() y pasándole un tear-off al constructor de Text.

class FruitWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
        children: ['Apple', 'Orange'].map(Text.new).toList());
  }
}

Text.new se refiere al constructor por defecto de la clase Text. También puedes referirte a un constructor nombrado — por ejemplo, .map(Text.rich).

#

Mientras implementábamos los tear-offs de constructores, aprovechamos la oportunidad para corregir algunas inconsistencias en nuestro soporte existente para punteros de función. Ahora puedes especializar un método genérico para crear un método no genérico:

T id<T>(T value) => value;
var intId = id<int>; // New in 2.15.
int Function(int) intId = id; // Pre-2.15 workaround.

Incluso puedes especializar un objeto de función genérico para crear un objeto de función no genérico:

const fo = id; // Tear off `id`, creating a function object.
const c1 = fo<int>; // New in 2.15; error before.

Por último, limpiamos los literales de tipos que involucran genéricos:

var y = List; // Already supported.
var z = List<int>; // New in 2.15.
var z = typeOf<List<int>>(); // Pre-2.15 workaround.

Enums mejorados en la librería dart:core

#

Hemos hecho varias adiciones de conveniencia a los APIs de enum en la librería dart:core (issue del lenguaje #1511). Ahora puedes obtener el valor String para cada valor de enum usando .name:

enum MyEnum {
  one, two, three
}

void main() {
  print(MyEnum.one.name);  // Prints "one".
}

También puedes buscar un valor de enum por nombre:

print(MyEnum.values.byName('two') == MyEnum.two);  // Prints "true".

Finalmente, puedes obtener un map de todos los pares nombre-valor:

final map = MyEnum.values.asNameMap();
print(map['three'] == MyEnum.three);  // Prints "true".

Para un ejemplo del uso de estos nuevos APIs, consulta este PR de Flutter.

Punteros comprimidos

#

Dart 2.15 añade soporte para punteros comprimidos, una técnica donde un SDK de 64 bits puede usar una representación de punteros más eficiente en espacio si solo necesita soportarse un espacio de direcciones de 32 bits (hasta 4 GB de memoria). Los punteros comprimidos resultan en una reducción significativa de memoria; en nuestras pruebas internas con la app GPay, vimos una reducción de aproximadamente el 10% del tamaño del heap de Dart.

Dado que los punteros comprimidos implican no poder direccionar ninguna RAM disponible por encima de 4 GB, la característica está detrás de una opción de configuración en el SDK de Dart que solo puede ser activada por los integradores del SDK de Dart cuando el SDK se construye. La versión 2.8 del SDK de Flutter ha activado esta configuración para las compilaciones de Android, y el equipo de Flutter está considerando también activarla para iOS en una futura versión.

DevTools de Dart incluido en el SDK de Dart

#

El conjunto de DevTools de herramientas de depuración y rendimiento anteriormente no estaba en el SDK de Dart; tenías que descargarlo por separado. A partir de Dart 2.15, ahora obtienes DevTools al descargar el SDK de Dart, sin necesidad de pasos adicionales de instalación. Para más información sobre el uso de DevTools con apps de Dart de línea de comandos, consulta la documentación de DevTools.

Nuevas características de pub para publicadores de paquetes

#

El SDK de Dart 2.15 también tiene dos nuevas características en el comando de desarrollo dart pub y en el repositorio de paquetes pub.dev.

Primero, hay una nueva característica de seguridad para los publicadores de paquetes. El objetivo es detectar cuando un publicador publica accidentalmente secretos — por ejemplo credenciales de Cloud o CI — dentro de paquetes pub. Nos inspiramos para añadir esta detección de fugas después de aprender que dentro de los repositorios de GitHub, miles de secretos se filan cada día.

La detección de fugas se ejecuta como parte de la validación previa a la publicación en el comando dart pub publish. Si detecta un posible secreto en los archivos a punto de publicarse, el comando publish termina sin publicar, e imprime una salida como esta:

Publishing my_package 1.0.0 to [https://pub.dartlang.org](https://pub.dartlang.org):
Package validation found the following errors:
* line 1, column 1 of lib/key.pem: Potential leak of Private Key detected.

1 │ ┌ - - -BEGIN PRIVATE KEY - - -
2 │ │ H0M6xpM2q+53wmsN/eYLdgtjgBd3DBmHtPilCkiFICXyaA8z9LkJ
3 │ └ - - -END PRIVATE KEY - - -

* line 2, column 23 of lib/my_package.dart: Potential leak of Google OAuth Refresh Token detected.

2 │ final refreshToken = "1//042ys8uoFwZrkCgYIARAAGAQSNwF-L9IrXmFYE-sfKefSpoCnyqEcsHX97Y90KY-p8TPYPPnY2IPgRXdy0QeVw7URuF5u9oUeIF0";

En raras ocasiones esta detección podría tener falsos positivos, marcando posibles fugas para contenido o archivos que de hecho tienes intención de publicar. En esos casos, puedes añadir los archivos a una lista de permitidos.

Segundo, hemos añadido otra característica para publicadores que soporta la retracción de una versión de paquete que ya ha sido publicada. Cuando se publica una versión defectuosa de un paquete, normalmente recomendamos publicar una nueva versión con un incremento menor que corrija el problema no intencional. En casos raros — por ejemplo cuando aún no tienes dicha corrección, o cuando publicaste accidentalmente una nueva versión mayor pero tenías intención de publicar una nueva versión menor — puedes usar la nueva característica de retracción de paquetes como último recurso. Esta funcionalidad está disponible en la interfaz de administración en pub.dev:

La interfaz de administración de pub.dev mostrando la opción para retraer una versión de paquete.

Cuando se retrae una versión de paquete, el cliente pub ya no resuelve a esa versión en pub get o pub upgrade. Si algunos desarrolladores ya han resuelto a la versión retraída (y está por tanto en su archivo pubspec.lock), verán una advertencia la próxima vez que ejecuten pub:

$ dart pub get
Resolving dependencies…
mypkg 0.0.181-buggy (retracted, 0.0.182-fixed available)
Got dependencies!

Análisis de seguridad para detectar caracteres Unicode bidireccionales (CVE-2021–22567)

#

Recientemente se descubrió una vulnerabilidad general de lenguajes de programación que involucra caracteres Unicode bidireccionales (CVE-2021–42574). Esta vulnerabilidad afecta a la mayoría de los lenguajes de programación modernos que soportan Unicode. El siguiente código fuente de Dart ilustra el problema:

main() {
  final accessLevel = 'user';

  if (accessLevel == 'user‮ .⁦// Check if admin⁩ ⁦') {
    print('You are a regular user.');
  } else {
    print('You are an admin.');
  }
}

Probablemente pensarías que este programa imprime You are a regular user., pero de hecho podría imprimir You are an admin.! Este exploit es posible usando una cadena que contiene caracteres Unicode bidireccionales. Estos son caracteres que cambian la dirección del texto de izquierda-a-derecha a derecha-a-izquierda y viceversa, todo dentro de una sola línea. Con caracteres bidireccionales, el texto puede renderizarse en pantalla de manera bastante diferente al contenido real del texto. Puedes ver un ejemplo de esto en este gist de código de GitHub.

Las mitigaciones contra esta vulnerabilidad incluyen el uso de herramientas (editores, herramientas de revisión de código, etc.) que detectan caracteres Unicode bidireccionales, de modo que un desarrollador pueda ser informado de ellos y aceptar conscientemente su uso. El visor de archivos gist de GitHub enlazado arriba es un ejemplo de una herramienta que revela estos caracteres.

Dart 2.15 introduce una mitigación adicional (aviso de seguridad de Dart CVE-2021–22567): el analyzer de Dart ahora escanea en busca de caracteres Unicode bidireccionales, y marca cualquier uso de ellos:

$ dart analyze
Analyzing cvetest...                   2.6s

info • bin/cvetest.dart:4:27 • The Unicode code point 'U+202E'
       changes the appearance of text from how it's interpreted
       by the compiler. Try removing the code point or using the
       Unicode escape sequence '\u202E'. •
       text_direction_code_point_in_literal

Recomendamos reemplazar estos caracteres por secuencias de escape Unicode, para que sean visibles en cualquier editor o visor de texto. Alternativamente, si tienes un uso legítimo de estos caracteres, puedes desactivar la advertencia añadiendo una anulación en la línea anterior al uso:

// ignore: text_direction_code_point_in_literal

Vulnerabilidad de credenciales de pub.dev al usar servidores pub de terceros (CVE-2021–22568)

#

También estamos publicando un segundo aviso de seguridad de Dart relacionado con pub.dev: CVE-2021–22568. Este aviso está dirigido a los publicadores de paquetes que pueden haber publicado paquetes en servidores de paquetes pub de terceros, como servidores de paquetes privados o internos de una empresa. Los desarrolladores que publican solo en el repositorio público pub.dev (la configuración estándar) no están afectados por esta vulnerabilidad.

Si has publicado en un repositorio de terceros, la vulnerabilidad consiste en que el token de acceso temporal OAuth2 (de una hora) presentado para autenticarse en ese repositorio de terceros puede ser usado indebidamente para autenticarse en el repositorio público pub.dev. Así, un servidor pub de terceros malicioso podría usar un token de acceso para suplantarte en pub.dev y publicar paquetes allí. Si has publicado un paquete en un repositorio de paquetes de terceros que no es de confianza, considera hacer una auditoría de toda la actividad de tu cuenta en el repositorio público de paquetes pub.dev. Puedes usar el registro de actividad de pub.dev para este propósito.

Comentarios finales

#

Esperamos que disfrutes las nuevas características de Dart 2.15, disponibles hoy. Este es nuestro último lanzamiento del año, y nos gustaría aprovechar la oportunidad para expresar nuestro agradecimiento al maravilloso ecosistema de Dart. Gracias por todos los excelentes comentarios, por tu continuo apoyo como lo demuestra nuestro crecimiento sostenido, y por extender nuestro ecosistema con miles de paquetes publicados en pub.dev durante el último año. Estamos ansiosos por volver a ello el próximo año, y tenemos muchas cosas emocionantes planeadas para 2022. Hasta entonces, ¡felices fiestas!

Más de Dart