Live data from Hacker News

How Go mitigates supply chain attacks

go.dev

251–260 of 265 posts

Re: How Go mitigates supply chain attacks

#251
post #244

Earlier quoted context omitted.

> Because errors are just return values in golang, and it's not possible to write a linter to guarantee that they're handled That's true of all types, though. Nothing specific to errors applies there. Spend enough time in Go and that's going to bite you, even when the error interface is nowhere to be found. You've still not made clear what's special about errors that makes the concern about errors, not all types, mor…

> You've still not made clear what's special about errors that makes the concern about errors, not all types, more than theoretical. The way errors are handled in golang, its possible to ignore them accidentally. This doesn't happen with other types because you don't keep constantly overwriting the same variable over and over (immutability is generally good, another thing that golang lacks), increasing the likelihood…

> you don't keep constantly overwriting the same variable over and over

Except when you do...

> Whats worse something like this

Yes, this could be catastrophic.

        dir, file := path.Split(fileToKeep)
        dir2, file2 := path.Split(fileToDelete)
        if file2 == "x" {
            removeFile(file)
        }
What does it have to do with errors?

> golang errors are basically strings

That's not true at all. The error interface asks that you provide a string representation of your error when called upon (i.e. the Error() method), but you don't want it to be a string underneath. Further, you don't even need to use the error interface. Not all Go functions, even in the standard library, return errors declared using the error interface.

> Errors are special because they need to carry certain state about the program when an error happened (e.g. stack trace).

A stack trace is useful when you have an exceptional circumstance, but Go's exceptions (panic/recover) already provides that for exceptions. We are, however, talking about errors, not exceptions. Why would you ever need a stack trace when the network is down, for example? There is nothing unusual about the network being down. It's just another happy state along your happy path and logically dealing with it is no different than branching on the user inputting 1 vs. 2. Should languages also have special constructs for dealing with that? Of course not.

> I've experienced the issues that come out of golang's overly simplistic design.

No doubt. I have made clear that Go could improve here. None of those improvements are specific to errors, though. The idea that errors are special just doesn't jive. Every problem you encounter with errors will also be encountered with other types sooner or later.

> languages like Rust have it much better than golang.

Rust is a good example of how errors aren't special, though. The constructs you may use for errors in Rust are built upon lower level features that help with state management generally. Mentioning Rust here is a is a bit strange seeing as how it contradicts the entire premise you're trying to push. The features of Rust that can help you with errors can also help you with problems of other types. Rust is a good example of how good design can work to solve the problem generally, not just for errors like you want to do.

To really get to the meat of this, your entire comment sums up to say that Go could do more to help you with state management. We already established exactly that in previous comments. Where do you think errors specifically begin to come into play?

Re: How Go mitigates supply chain attacks

#252
post #245

Earlier quoted context omitted.

No, it's got nothing to do with checked exceptions. It's from not wanting all the error-handling noise between the two steps of your happy-path logic. And at least in extant Java code and many Java programmers I interview, it's not considered an anti-pattern (but I agree it should be).

It's quite trivial to write a generic function that handles a particular exception else returns a default value, which addresses the above scenario. final var x = getOrDefault(C::foo, Something.class, quux()); return x.bar(); And now with pattern matching in Java, it's trivial to write something similar to Rust's/Scala's `Result `/`Try ` types and be explicit about all exceptions. I haven't compiled the following, bu…

Neither of those is equivalent to what I wrote (in different ways!), and the first one doesn't even work because of type erasure - you can't write generic functions over exception types. You're also focused way too much on the specific lines I wrote and not the general pattern that leads to exception over-handling.

> trivial... trivial

Rethink your use of this word.

Anyway, alternative proposals don't really matter - I could also offer patterns for Go which make accidentally ignoring error values more difficult. We're talking about actually extant code, and nobody writes Java (or Go) like that outside of forum arguments.

Re: How Go mitigates supply chain attacks

#253
post #244

Earlier quoted context omitted.

> You've still not made clear what's special about errors that makes the concern about errors, not all types, more than theoretical. The way errors are handled in golang, its possible to ignore them accidentally. This doesn't happen with other types because you don't keep constantly overwriting the same variable over and over (immutability is generally good, another thing that golang lacks), increasing the likelihood…

> you don't keep constantly overwriting the same variable over and over Except when you do... > Whats worse something like this Yes, this could be catastrophic. dir, file := path.Split(fileToKeep) dir2, file2 := path.Split(fileToDelete) if file2 == "x" { removeFile(file) } What does it have to do with errors? > golang errors are basically strings That's not true at all. The error interface asks that you provide a str…

> What does it have to do with errors?

The fact that the former scenario is much more likelier to happen in golang due to its error handling. The former can be caught using unit tests, and should stick out more in languages like Java with scoped try-with-resources blocks that limit the scope of lifetimed variables (another bad design in golang where defers are function scope, not local scope limited, introducing unnecessary runtime costs, while also being less useful in practice).

At the same time, you don't want to be writing unit tests for every simple scenario. Some people who use dynamic languages argue that you just type assert everything in unit tests and you should be good. Obviously, that's not how things work out in practice. Similarly here, because of the way errors are handled in golang, the same pattern is everywhere in the program, and it is extremely tedious to write the same unit tests over and over everywhere, while making sure to reach a certain level of coverage.

> Not all Go functions, even in the standard library, return errors declared using the error interface.

Do they just return integers then like C?

> Why would you ever need a stack trace when the network is down, for example?

Because it's still important to know where you are in the program when the network broke, in order to make sure that recovery happened correctly for example. Seeing a "network is down" error in the logs is useful, but more so is knowing exactly what I was doing (what if I wasn't expecting to access the network in a particular path? etc.)

> Every problem you encounter with errors will also be encountered with other types sooner or later.

Practicality matters, otherwise C is all we'd ever need, and we wouldn't be having constant CVE's etc.

> Mentioning Rust here is a is a bit strange seeing as how it contradicts the entire premise you're trying to push.

Not really. Rust errors compose nicely, and you're explicitly forced to handle them (unlike golang's), and are much harder to accidentally swallow or ignore compared to golang. This scenario is repeated many times when you compare golang to other better designed languages. The language constantly takes the easy way out so to speak, making the compiler implementation simpler, while pushing complexity onto the user. Furthermore, as I pointed out, there's nothing preventing us from using the same approach in Java, now that it has sealed types and pattern matching.

That is not possible in golang due to its lack of support of all those constructs. Now with generics, some people are starting to use some similar approaches, but they fall flat on their face because (1) golang doesn't have those constructs as I mentioned, but also (2) the way generics have been implemented in golang (similar to the rest of the language) are simplistic (you can't have types on member functions), leading to further abominations and verbose code.

Java's checked exceptions are not much different actually. They still require you to handle the error either by try/catch or by declaring that the method throws the same exception, or a superclass of it.

I also remembered another shortcoming of golang error handling that I've seen several times in real code bases, being forced to check both the error and the other return value to make sure that things are working. Yes, a properly written program shouldn't need to do that, but reality doesn't care. And what's ironic is that golang was supposed to be designed to support "programming in the large" (another unverified claim that is contradicted by reality). The fact that it opens these doors is indicative of the mentality that went into designing it.

Re: How Go mitigates supply chain attacks

#254
post #81

The elephant in the room here is NPM, and I think the obvious problem there is the culture. I have a tiny app I've been playing with using create-react-app. There are over 800 directories in node_modules. That absolutely dwarfs the number of any other language I've used. Even in a medium sized rails app, you likely have some awareness of what every dependency is. It's just impossible with npm. One thought I've had to…

npm may be an elephant, but why is it in a room talking about go.mod?

Why not drag APT and the Debian unstable repository in as well? i think install-debian-os also has around 800 dependencies. Debian unstable is vulnerable to supply chain attacks by package maintainers, and Debian stable suffers from lack of volunteers backporting and verifying security patches. Yet, if their numbers increase, the small chance increases a cabal of them attacks the supply chain by pushing and vouching for a fake patch that introduces malicious code ...

The elephant in the room is not some package manager unrelated to the topic. The elephant is trust.

GO argues to mitigate attacks by verifying and locking all dependencies yourself. Which, does not scale.

Debian argues with a proof of work, carefully curating what they publish, and who can publish to fast tracks like the security repo. In theory these people could go insane or be hacked in the same way node module devs go insane or get hacked. But curated publishing mitigates some of the risk.

As far as i know there is only one "third party" GO module repository (actually its generic, supporting both go.mod, npm, and many more) that has a multi-stage system of curation. It promises to integrate lots of tools, global cooperation, configurable policies, an AI and a team of specialists to help with curation. It is also proprietary, stupidly expensive and may not fully deliver on those promises.

Re: How Go mitigates supply chain attacks

#255

Earlier quoted context omitted.

A few years of Golang under my belt and I still hate it. Russ Cox and co. seem incredibly arrogant to me in that they can ignore decades of PL research only to reinvent a bizarre way of achieving what other languages do in a more standard way (package management, error handling), or just adopt that standard super late (generics). Go has some great qualities and you can make great software with it no doubt. But I find…

What is your favorite PL?

C# probably. Though I enjoy Typescript and Python as well.

C# has generics, exceptions, is the birthplace of async/await, has LINQ, an unmatched standard library, a great build system and package management system, is open source, cross platform, fast..

It doesn't compile to a single native binary unlike golang which is a bummer. But 95% of my software ships as a container so this isn't too big of deal for me. The MS-provided base images are really good too.

Re: How Go mitigates supply chain attacks

#256
post #164

Earlier quoted context omitted.

> Far easier to work with and understand when you dont need to perform a massive disruptive ceremony to handle exceptions. I've been working full time in java for the past 4 years and basically no one handles exceptions because its so cumbersome and bad to read. Not so in Go. Go does it better. No Go doesn't do it better, Go just doesn't give you the choice, from a convention perspective. You have a discipline issue…

> You have a discipline issue with Java, it is not an issue in the language itself, it's with you and your team. Only if you consider the "language" to be only the language itself, i.e. syntax + semantics. When people refer to a "programming language", they almost always mean the whole package, i.e. the language itself, the community, package ecosystem, tooling, conventions, etc. If there is widespread usage of an in…

> Only if you consider the "language" to be only the language itself, i.e. syntax + semantics. When people refer to a "programming language", they almost always mean the whole package, i.e. the language itself, the community, package ecosystem, tooling, conventions, etc.

You're making a lot of assumptions based on absolutely no factual data, only your alleged experience. I can dismiss all your arguments easily as anecdotal.

As I said earlier Go suffers from having a bad exception system itself (panics/recover) and on the top of that go makes it a breeze to ignore errors:

    _ := YieldError()
You're basically claiming that teams are magically more disciplined just because they use Go. That's of course complete bullshit. An undisciplined team will ignore errors and exceptions the same way. There is nothing in Go that compels anybody to handle errors, just like there is nothing in Java that compels anybody to ignore exceptions.

Go errors are purely a convention, Go doesn't have anything in its syntax that forces error as value, it's just what the std library uses. So when talking about Go errors, they are purely talking about a convention, with absolutely no syntax to compel people to follow or enforce that convention. It requires thus even more discipline. Do you really believe your undisciplined programmers that ignored Java exceptions because it's verbose to deal with them are going to magically adopt an even more verbose paradigm? Of course not.

Re: How Go mitigates supply chain attacks

#257
post #245

Earlier quoted context omitted.

It's quite trivial to write a generic function that handles a particular exception else returns a default value, which addresses the above scenario. final var x = getOrDefault(C::foo, Something.class, quux()); return x.bar(); And now with pattern matching in Java, it's trivial to write something similar to Rust's/Scala's `Result `/`Try ` types and be explicit about all exceptions. I haven't compiled the following, bu…

Neither of those is equivalent to what I wrote (in different ways!), and the first one doesn't even work because of type erasure - you can't write generic functions over exception types. You're also focused way too much on the specific lines I wrote and not the general pattern that leads to exception over-handling. > trivial... trivial Rethink your use of this word. Anyway, alternative proposals don't really matter -…

> and the first one doesn't even work because of type erasure - you can't write generic functions over exception types.

It sure does work :-)

    @FunctionalInterface
    interface ThrowingSupplier {
        R get() throws E;
    }

    static  T getOrDefault(ThrowingSupplier f, Class exceptionType, T def) throws E {
        try {
            return f.get();
        } catch (Exception e) {
            if (exceptionType.isInstance(e)) {
                return def;
            }
            throw e;
        }
    }

> and nobody writes Java (or Go) like that outside of forum arguments.

https://www.javadoc.io/doc/io.vavr/vavr/0.9.3/io/vavr/contro...

And now that Java has sealed types and pattern matching, it might become more popular. Kotlin already has them (even without the exhaustive pattern matching that Java has): https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-result/

Re: How Go mitigates supply chain attacks

#258
post #257

Earlier quoted context omitted.

Neither of those is equivalent to what I wrote (in different ways!), and the first one doesn't even work because of type erasure - you can't write generic functions over exception types. You're also focused way too much on the specific lines I wrote and not the general pattern that leads to exception over-handling. > trivial... trivial Rethink your use of this word. Anyway, alternative proposals don't really matter -…

> and the first one doesn't even work because of type erasure - you can't write generic functions over exception types. It sure does work :-) @FunctionalInterface interface ThrowingSupplier { R get() throws E; } static T getOrDefault(ThrowingSupplier f, Class exceptionType, T def) throws E { try { return f.get(); } catch (Exception e) { if (exceptionType.isInstance(e)) { return def; } throw e; } } > and nobody writes…

> It sure does work :-)

That you've marked the function `throws E` is an admission it doesn't really work; that's the one exception type it definitely doesn't throw. (Default handling is also still different than the starting point.)

Please just stop trying to show off how much you know when I'm trying to discuss how programs in general are actually implemented. You're very clever, but part of why you keep getting it slightly wrong is because probably you don't even write error handling like this day to day.

Re: How Go mitigates supply chain attacks

#259
post #253

Earlier quoted context omitted.

> you don't keep constantly overwriting the same variable over and over Except when you do... > Whats worse something like this Yes, this could be catastrophic. dir, file := path.Split(fileToKeep) dir2, file2 := path.Split(fileToDelete) if file2 == "x" { removeFile(file) } What does it have to do with errors? > golang errors are basically strings That's not true at all. The error interface asks that you provide a str…

> What does it have to do with errors? The fact that the former scenario is much more likelier to happen in golang due to its error handling. The former can be caught using unit tests, and should stick out more in languages like Java with scoped try-with-resources blocks that limit the scope of lifetimed variables (another bad design in golang where defers are function scope, not local scope limited, introducing unne…

> At the same time, you don't want to be writing unit tests for every simple scenario.

Yup, exactly. In a perfect world you would, but the world ain't perfect. Sooner or later you're accidentally deleting the wrong thing because you didn't test it/didn't test it correctly.

And for what reason? A language can guard against that kind of mistake quite well.

So, that still leaves us wondering why you don't find it advantageous for a programming language to be able to deal with these kinds of problems unless the problem is related to an error? If you have a solid foundation the problem with errors you are trying to show would disappear. Go only has problems with errors because it also has problems generally.

> Practicality matters, otherwise C is all we'd ever need, and we wouldn't be having constant CVE's etc.

Agreed. Which leaves it to be insane to only want to fix the problems with errors when everything wrong with errors is also wrong with every type. If you fix the error problem properly you've automatically fixed it for all types. Why on earth are you suggesting that you'd purposefully make the fix harder just to ensure that it only fixes errors?

> Rust errors compose nicely, and you're explicitly forced to handle them (unlike golang's), and are much harder to accidentally swallow or ignore compared to golang.

This isn't a feature of specialized error handling, but Rust's overall design towards helping with general state management. Rust alleviates the same problems for all types. Rust yet again shows us that errors aren't special and don't matter. Provide a good foundation for state management and managing error state becomes easy by virtue of just being yet another state. You are able to cleanly deal with errors in Rust because it took care to get the problem right generally, not because it took care to worry about errors at the expense of everything else.

Go could, like Rust, do more to help with general state management. It would be insanity to try and only do that for errors, though.

Re: How Go mitigates supply chain attacks

#260
post #257

Earlier quoted context omitted.

> and the first one doesn't even work because of type erasure - you can't write generic functions over exception types. It sure does work :-) @FunctionalInterface interface ThrowingSupplier { R get() throws E; } static T getOrDefault(ThrowingSupplier f, Class exceptionType, T def) throws E { try { return f.get(); } catch (Exception e) { if (exceptionType.isInstance(e)) { return def; } throw e; } } > and nobody writes…

> It sure does work :-) That you've marked the function `throws E` is an admission it doesn't really work; that's the one exception type it definitely doesn't throw. (Default handling is also still different than the starting point.) Please just stop trying to show off how much you know when I'm trying to discuss how programs in general are actually implemented . You're very clever, but part of why you keep getting i…

> That you've marked the function `throws E` is an admission it doesn't really work

Correct, I didn't spend a lot of time refining it. I suppose the only way to make it work is to remove `throws E` from the signature and make it `throw new RuntimeException(e)`, and be similar to C#/Kotlin/Scala.

My last JVM job used Scala, and we wrote things that are similar to

    val x = foo() match {
      case Success(res) => res
      case Error(e) => return quux() // Returns from function scope
   }
Post reply on HN