Announcement

Collapse
No announcement yet.

FreeType 2.10.4 Rushed Out As Emergency Security Release

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

  • #21
    Originally posted by HadrienG View Post
    Oh, I forgot another problem with C++'s const design which caused me some pain a while ago. The fact that a const object with an inner pointer-to-mutable can still change the value targeted by that pointer from a const method.
    ...
    Perfect example for my earlier claim, where I said that "good" reasons for const_cast usually turn out to be bad interface design.

    In general, when this happens, if the outer object can tolerate changes to the inner object in other contexts, then why can't it here? And if the inner object must be const in other contexts too, then why wasn't it declared const instead of only the pointer member pointing to it? Classic differerence between "constant pointer" and "pointer to a constant object". And even if the designer can, somehow, by some rare exception provide a reasonable explanation to this design discrepancy, const-casting is still usually the wrong solution. The right solution in these cases is to make the inner object available only over an interface function, providing const and non-const overloads if necessary, returning the correct pointer type in each case.

    Originally posted by HadrienG View Post
    So they had to choose between not using const_cast, but needing to be careful when writing const methods because the compiler is not checking const-correctness as intended, or using const_cast during initialization for the benefit of later on having proper compiler checking that const methods are not mutating accessible state from other objects.
    In your fellow's case however, if I understand correctly, the problem was slightly different than in my previous paragraph. Basically the object needed to be const, but they couldn't make it const due to some initialization logic. Well, this turns out to have an even simpler solution: complex initialization of a const object has a known and wide-spread idiom in C++, which is using lambda functions. To me it sounds like your colleague simply didn't know how to use the language (or the code was written in the pre-C++11 era). (I will add to the SO answer linked above that you'd normally write the lambda declaration, invokation on the same "line", not storing the lambda separately.)

    EDIT: I do agree that Rust is on the safer side by default, but it also mean being more restrictive. And when you need more flexibility, you end up going into unsafe contexts and stuff, where you do lose many of the compiler's guarantees in the end after all. Also, with Rust, given its newcomer nature, there is no deprecated compatibility crux that needs to be avoided in modern code. Yet this a bad excuse for writing bad code today in C++. Old programmers need to learn to use the new stuff in the language, and new programmers will learn to use it the right way from the get-go.
    Last edited by ultimA; 20 October 2020, 05:11 PM.

    Comment


    • #22
      Originally posted by uid313 View Post

      I didn't know about these features in C++.
      No, this is different than const, I meant when you declare a variable and you pass it into a function then that function now owns the variable and can modify it, but you cannot modify it outside of that function since it no longer owns the variable.

      Also I heard it is very easy to shoot yourself in the foot with C++, that it is very easy to do bad things. Linus even ranted about C++ calling it a horrible language, and refuses to let in C++ into the Linux kernel.

      Also all of these nice features you mentioned were tucked on to C++ later like a patch, while Rust was built and designed explicitly with those features from the start.
      No, this is exactly const. Again, bad interface design. If the callee function does not modify the object, then why doesn't it take it as const? And if in addition, it doesn't want to take ownership, why doesn't it take a reference? Taking a const reference as the function parameter would have been the correct thing to do in this case.

      Yes, of course you can "shoot yourself in the foot". All I'm saying, if you do things correctly (C++ provides all the necessary tools to do that), your callers / library users will not be able to shoot themselves in the foot, unless they willingly do stupid things that shouldn't even be taught today anymore. But if you do willingly stupid things, shooting yourself in the foot is possible even in Rust (see unsafe context and all...)

      Comment


      • #23
        I think I'm going to start off a few threads with "it should be written in Rust" and see the thread turn into chaos

        Comment


        • #24
          Originally posted by Ironmask View Post
          I think I'm going to start off a few threads with "it should be written in Rust" and see the thread turn into chaos
          No need: every thread mentioning a CVE *already* gets an almost-immediate "should be written in Rust" post.
          (and it's usually by someone who can't even program in the first place!)

          Comment


          • #25
            Originally posted by ultimA View Post
            > Oh, I forgot another problem with C++'s const design which caused me some pain a while ago. The fact that a const object with an inner pointer-to-mutable can still change the value targeted by that pointer from a const method.

            Perfect example for my earlier claim, where I said that "good" reasons for const_cast usually turn out to be bad interface design.

            In general, when this happens, if the outer object can tolerate changes to the inner object in other contexts, then why can't it here? And if the inner object must be const in other contexts too, then why wasn't it declared const instead of only the pointer member pointing to it? Classic differerence between "constant pointer" and "pointer to a constant object". And even if the designer can, somehow, by some rare exception provide a reasonable explanation to this design discrepancy, const-casting is still usually the wrong solution. The right solution in these cases is to make the inner object available only over an interface function, providing const and non-const overloads if necessary, returning the correct pointer type in each case.
            I reached the same conclusion at the time. But since we had that pattern in multiple areas of the codebase, and I was worried about enforcing use of the proper const/non-const pointer accessor method with a readily available non-const member, I tried a slightly more elaborate version of your suggestion.

            What I did was to use a pointer wrapper, akin to the propagate_const that from memory was proposed as an addition to the C++ standard a while ago, which behaves like T* when accessed from a non-const context and like a const T* when accessed from a const context. In the end, however, that pattern was rejected as too intrusive. The basic problem was that people were not happy about exposing things like std::vector<propagate_const<T*>>& in APIs, and the dirty hack of casting that into std::vector<T*>& for "saving apparences" was rightfully rejected as too dirty.

            From this experience, I concluded that "clean" const-correctness with C++ pointers is effectively impossible without sacrificing API ergonomics (easy access to members, no weird types in APIs...), and in that sense pointers are another wart in C++'s const-correctness story.

            > So they had to choose between not using const_cast, but needing to be careful when writing const methods because the compiler is not checking const-correctness as intended, or using const_cast during initialization for the benefit of later on having proper compiler checking that const methods are not mutating accessible state from other objects.

            In your fellow's case however, if I understand correctly, the problem was slightly different than in my previous paragraph. Basically the object needed to be const, but they couldn't make it const due to some initialization logic. Well, this turns out to have an even simpler solution: complex initialization of a const object has a known and wide-spread idiom in C++, which is using lambda functions. To me it sounds like your colleague simply didn't know how to use the language (or the code was written in the pre-C++11 era). (I will add to the SO answer linked above that you'd normally write the lambda declaration, invokation on the same "line", not storing the lambda separately.)
            If I understand your proposal correctly, I think it wouldn't have applied in that case.

            The problem is that we have a two pass object graph construction process :
            • In a first pass, we iteratively set up the graph by constructing objects and setting up links between them. At that time, a number of functions work on sub-graphs without a knowledge of the "big picture", and other functions assemble those sub-graphs.
            • In a second pass, we traverse the final object graph to annotate each object with metadata that could only be computed with knowledge of the full object graph.
            • Only after that, can the graph be considered finalized. It has to remain const after that point, since any change after that would invalidate the metadata that was written during the second initialization pass.


            From my understanding, the lambda solution doesn't work in that case, because it assumes that you know all the data that needs to be written into the object at the time where said object is constructed. This is not the case here, as individual objects are annotated with information that will only be available once all objects have been constructed.

            EDIT: I do agree that Rust is on the safer side by default, but it also mean being more restrictive. And when you need more flexibility, you end up going into unsafe contexts and stuff, where you do lose many of the compiler's guarantees in the end after all. Also, with Rust, given its newcomer nature, there is no deprecated compatibility crux that needs to be avoided in modern code. Yet this a bad excuse for writing bad code today in C++. Old programmers need to learn to use the new stuff in the language, and new programmers will learn to use it the right way from the get-go.
            All of this is true. The bet that Rust makes, which in my experience actually tends to work out pretty well in practice, is that after the initial "weird language" adjustment period, people will rarely need the flexible/unsafe subset of the language in their everyday code. Or, in other words, that safe Rust is usually (but not always) good enough.

            This means that unsafe can be almost absent from even relatively elaborate codebases (you can find out there security-sensitive stuff like parsers, file compression/decompression libraries, crypto libraries... which are completely written in safe Rust and use no unsafe dependencies besides the language standard library and the underlying OS kernel).

            And in codebases that do need unsafe constructs (say, hardware drivers), it means that the unsafety is, if done properly, fine-grained and readily encapsulated in "safe abstractions". This is exactly the strategy that modern C++ also tries to go for, but it's much easier to do in a language that has 1/no legacy far west code/advice/documentation that breaks all the newer coding rules, 2/a developer base which is trained to consider safety as important and 3/static safety analysis which ensures that unsafe constructs are easily auditable by simply grepping for use of the "unsafe" keyword in the project and its dependencies.

            With that, you can usually enjoy the comfort of not worrying about type/memory/thread-safety matters, and save brain bandwidth for the things that you actually care about in your code, and for the occasional unsafe block that you do need to write or audit. I think that's a good compromise.
            Last edited by HadrienG; 21 October 2020, 04:19 AM.

            Comment


            • #26
              Originally posted by uid313 View Post
              Maybe FreeType or parts of it should be rewritten in Rust to avoid these buffer overflow problems.
              Oh and then someone like M$ would buy their overly centralized crates repo - and put backdoors and telemetry by default. Problem solved! At expense of few dozens of far harder problems though. That's what stupid humans get when their wishes are finally granted.

              Comment

              Working...
              X