Violación interesante del sistema de tipos en C # (en contexto seguro)

La violación del sistema de tipos de la que hablo es cuando emula uniones.

¿Por qué se permite en un contexto seguro?

  • Le permite manejar tipos de valor, como tipo de valor diferente, como lograría con:

    float f = 2.5f;
    int binaryIdenticalInt = * ((int *) & f);

  • Le permite hacer referencia a un objeto de tipo X, ya que era de tipo Y, AUN CUANDO NO HAY RELACIÓN ENTRE ESOS TIPOS.

  • Le permite crear tipos que el CLR ni siquiera cargará (si al menos uno de los campos superpuestos es un tipo de referencia, y al menos uno de ellos es valor / tipo puntero, el CLR rechazaría cargarlo). Espero que el comstackdor de C # sea más exigente con lo que permite, que el CLR. no menos.

Entonces, ¿por qué el comstackdor permite esas cosas en un contexto seguro en primer lugar? ¿Por qué no requiere un contexto inseguro para los tipos con diseño explícito, que tienen campos superpuestos?

Espero que alguien por ahí tenga una respuesta, porque tiene que ser interesante.

Ejemplos (todos comstackdos sin bloque inseguro, ni siquiera interruptor inseguro):

[StructLayout(LayoutKind.Explicit)] struct ValueUnion { [FieldOffset(0)] public int i; [FieldOffset(0)] public float f; } [StructLayout(LayoutKind.Explicit)] struct ReferenceUnion { [FieldOffset(0)] public string str; [FieldOffset(0)] public Stream stream; } [StructLayout(LayoutKind.Explicit)] struct CLRWontLoad { [FieldOffset(0)] public string str; [FieldOffset(0)] public IntPtr ptr; } 

Déjame contestar algunas preguntas ligeramente diferentes.

¿El uso del atributo de diseño explícito permite violaciones de seguridad de tipo sin usar la palabra clave unsafe ?

Sí. Para eso está.

¿Qué conocimiento del atributo FieldOffset tiene el comstackdor?

El comstackdor verifica que el atributo FieldOffset no esté en campos estáticos / const y que sea consistente con el atributo StructLayout . No comprueba nada más sobre la validez del atributo.

¿Por qué el comstackdor de C # no detecta un uso potencialmente peligroso del atributo y requiere la palabra clave unsafe ?

Esa es una característica. Para ser utilizado por usted, una característica debe ser pensada, diseñada, especificada, implementada, probada, documentada y enviada a los clientes. Esta característica fue pensada. No fue diseñado, especificado, implementado, probado, documentado o enviado, y por lo tanto, no existe tal característica.

Esa es una respuesta insatisfactoria.

Mi consejo es que no haga preguntas “por qué” en StackOverflow. Las preguntas de “por qué” son vagas y, por lo tanto, a menudo se obtienen respuestas insatisfactorias. Las preguntas “por qué no” son aún más difíciles de responder satisfactoriamente porque tienen la presuposición de que es necesario que haya una buena razón para que el mundo no sea una forma en que no lo es.

OK, déjame reformular. Si esta característica fue lanzada al equipo de diseño del comstackdor, ¿qué ventajas y desventajas podrían considerar al decidir si continuar con la característica?

  • Pro: el código se puede usar claramente de manera insegura, por lo que debe requerir la palabra clave unsafe para permitirlo.

  • En contra: la palabra clave unsafe está ahí para evitar el uso accidental de características peligrosamente inseguras, característica que cuatro décadas de C nos ha demostrado que incluso los progtwigdores expertos cometen errores difíciles de encontrar. No hay manera de usar accidentalmente el atributo de diseño de estructura. Se puede suponer que alguien que usa este atributo sabe exactamente lo que está haciendo y tiene una buena razón para lo que está haciendo.

En mi opinión, los contras superan a los pros aquí. La característica sería un beneficio muy pequeño por un costo moderado.

Recuerde, cada característica implementada significa otra característica en la lista no implementada, porque el presupuesto es finito. No querría hacer esta característica débil y tengo que cortar una característica que realmente benefició a los desarrolladores.

¿El equipo ha considerado esta característica y la ha rechazado por estos motivos?

Sí. Ver

http://connect.microsoft.com/VisualStudio/feedback/details/357828/using-explicit-struct-layout-causes-compiler-to-produce-unverifiable-code

Esto inevitablemente termina como una explicación circular. Le permite utilizar los sindicatos para violar la seguridad de tipos porque para eso se usan los sindicatos. No puede omitir la función porque no puede escribir pinvoke decente sin ella. El winapi, en particular, está lleno de sindicatos. Y Pinvoke no es inseguro, simplemente no es verificable. C # es un lenguaje bastante pragmático, si quieres disparar tu pierna, tienes derecho a hacerlo.

De lo contrario no es un agujero de seguridad. Lo que realmente le importa al CLR es el código verificable . La palabra clave insegura de C # simplemente se superpone, el código C # sin la palabra clave no se puede verificar automáticamente. Con pinvoke siendo el ejemplo más obvio. El código fuente de CLR lo explica bastante bien en un comentario. Desde clr / src / vm / class.cpp, MethodTableBuilder :: HandleExplicitLayout () funciona:

 // go through each field and look for invalid layout // (note that we are more permissive than what Ecma allows. We only disallow the minimum set necessary to // close security holes.) // // This is what we implment: // // 1. Verify that every OREF is on a valid alignment // 2. Verify that OREFs only overlap with other OREFs. // 3. If an OREF does overlap with another OREF, the class is marked unverifiable. // 4. If an overlap of any kind occurs, the class will be marked NotTightlyPacked (affects ValueType.Equals()). // 

“OREF” significa “Referencia de objeto”. Es la regla número 3 la que pone a Kibosh a escribir código verdaderamente inseguro, el código no verificable solo puede ejecutarse con plena confianza. La regla número 2 rechaza tu tercer ejemplo. Ese tiene cooties fundamentales porque el GC no puede adivinar si la referencia del objeto es válida, por lo que no puede mantener el objeto vivo.

Hay más código que se ocupa del diseño explícito; por ejemplo, también calcula la confianza mínima en función de la confianza de los tipos de campo participantes. Puedes echar un vistazo a la distribución SSCLI20.

Oh, ahora has agregado ejemplos.

Los comstackdores lo permiten porque este código es seguro. Casi todo lo que hace con un código inseguro puede hacerse con un código de seguridad. Es por eso que casi nadie usa código inseguro.

C # también le permite serializar un tipo y deserializarlo en el otro. Seguro no significa sin errores.