Java assertions - $assertionsDisabled vs $assertionsEnabled
Asked Answered
A

3

13

Assertions in java compile down to a private synthetic static boolean added to a test - the proposal is nicely documented here:

JSR Assertion Proposal

In it, we create

final private static boolean $assertionsEnabled = ClassLoader.desiredAssertionStatus(className);

and then

assert(X)
becomes
if ($assertionsEnabled && !x) { throw }

Which makes perfect sense ;)

However, I've noticed that what I actually get is

public void test1(String s) {
    assert (!s.equals("Fred"));
    System.out.println(s);
}

becomes

static final /* synthetic */ boolean $assertionsDisabled;

public void test1(String s) {
    if ((!(AssertTest.$assertionsDisabled)) && (s.equals("Fred"))) {
        throw new AssertionError();
    }
    System.out.println(s);
}

static {
    AssertTest.$assertionsDisabled = !(AssertTest.class.desiredAssertionStatus());
}

I can't find any documentation as to why they went with a NEGATIVE test, rather than a positive test - i.e. the original proposal captured assertionsENABLED, now we use assertionsDISABLED.

The only thing I can think of is that this would possibly (POSSIBLY!) generate better branch prediction, but that seems like a pretty lame guess to me - the Java philosophy is (almost) always to make the bytecode simple, and let the JIT sort out optimisations.

( note that this isn't a question about how assertions work - I know that! :) )

( As an aside, it's quite interesting to see that this leads to incorrect tutorials! 6.2.1 of this tutorial, which someone quoted in response to a previous SO question on assertions gets the sense of the test wrong! :)

Any ideas?

Atrice answered 31/5, 2013 at 9:28 Comment(0)
D
20

This is actually done for a reason, not merely for something like a more compact bytecode or faster condition execution. If you look at the Java Language Specification, §14.10, you will see the following note:

An assert statement that is executed before its class or interface has completed initialization is enabled.

There's also an example that contains an initialization loop:

class Bar {
    static {
        Baz.testAsserts(); 
        // Will execute before Baz is initialized!
    }
}
class Baz extends Bar {
    static void testAsserts() {
        boolean enabled = false;
        assert  enabled = true;
        System.out.println("Asserts " + 
               (enabled ? "enabled" : "disabled"));
    }
}

As Bar is a superclass for Baz, it must be initialized before Baz initialization. However, its initializer executes an assertion statement in the context of Baz class that is not initialized yet, so there was no chance to set the $assertionsDisabled field. In this case, the field has its default value, and everything works according to the spec: assertion is executed. Had we have an $assertionsEnabled field, the assertions for an uninitialized class would not be executed, so it would violate the specification.

Dew answered 2/12, 2019 at 4:59 Comment(0)
U
2

The boolean is actually implemented with an integer. There is a common believe that comparison with zero is quicker, but I don't see any reason to use disable instead of enabled.

IMHO, as false is the default for boolean, I try to chose a flag which has a default value of false In this case $assertionsEnabled would make more sense.

Under answered 31/5, 2013 at 10:9 Comment(1)
Indeed. Which makes using $assertionsDisabled all the more confusing a decision ;) Also (fairly obviously), this variable is constant folded out of existence in JITted code anyway. (verified with -XX:+PrintAssembly)Atrice
A
2

Although it looks like there's redundant work being done when you look at the decompiled java source - you can't rely on this - you need to look at the byte code level.

Have a look at the bytecode both the eclipse compiler and oracle javac produce:

                    #0: getstatic Test.$assertionsDisabled
                    #3: ifne #23
                    (assertion code) #6: aload_1
                    (assertion code) #7: ldc "Fred"
                    (assertion code) #9: invokevirtual String.equals(Object)
                    (assertion code) #12: ifeq #23
                    (assertion code) #15: new AssertionError
                    (assertion code) #18: dup
                    (assertion code) #19: invokespecial AssertionError.<init>()
                    (assertion code) #22: athrow
                    #23: getstatic System.out (PrintStream)
                    #26: aload_1
                    #27: invokevirtual PrintStream.println(String)
                    #30: return

Please note byte code #3 - it doesn't need to invert the Test.$assertionsDisabled value, it only needs to perform a single negative test (i.e. it doesn't make any difference if it's a negative test or a positive test at the byte code level)

In summary, it's being implemented efficiently and without any redundant work being performed.

Aker answered 2/9, 2016 at 2:22 Comment(1)
This is not the reason. ifeq with $assertionsEnabled would work just as fine.Dew

© 2022 - 2024 — McMap. All rights reserved.