Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dereference may produce 'java.lang.NullPointerException' [duplicate]

Following snippet produces a lint warning in Android Studio.

    Bundle extras = getIntent().getExtras();
    if (extras != null && extras.getString(REDIRECT_KEY) != null) {
        switch (extras.getString(REDIRECT_KEY)) { ... 

extras.getString(REDIRECT_KEY) produces the warning

enter image description here

Dereference of 'extras.getString(REDIRECT_KEY)' may produce 'java.lang.NullPointerException'

But I don't see any scenario where this could happen. Is it a bug in lint checks, that it simply does not recognize my null-checks in the if before? Or do I miss something?

EDIT: changing the code to following, did remove the warning

if(getIntent() != null && getIntent().getStringExtra(REDIRECT_KEY) != null){
        switch (getIntent().getStringExtra(REDIRECT_KEY)){
            ...
        }
    }

BUT this is only gone because of the way, this lint check works (at least I guess). If I show more infos to this check, it says at one point

Variables, method parameters and return values marked as @Nullable or @NotNull are treated as nullable (or not-null, respectively) and used during the analysis to check nullability contracts

Looking at the source code of Bundle and of Intent shows:

/**
 * Retrieve extended data from the intent.
 *
 * @param name The name of the desired item.
 *
 * @return the value of an item that previously added with putExtra()
 * or null if no String value was found.
 *
 * @see #putExtra(String, String)
 */
public String getStringExtra(String name) {
    return mExtras == null ? null : mExtras.getString(name);
}

And BaseBundle

/**
 * Returns the value associated with the given key, or null if
 * no mapping of the desired type exists for the given key or a null
 * value is explicitly associated with the key.
 *
 * @param key a String, or null
 * @return a String value, or null
 */
@Nullable
public String getString(@Nullable String key) {
    unparcel();
    final Object o = mMap.get(key);
    try {
        return (String) o;
    } catch (ClassCastException e) {
        typeWarning(key, o, "String", e);
        return null;
    }
}

As you can see, BaseBundle sets its return values to @Nullable, while Intent doesn't. So using getStringExtra only removes the symoptoms, not the cause. I still guess this is caused by an insufficient lint check, instead of wrong coding on my side. Or does anyone still see a scenario, where a Null pointer can be thrown?

like image 456
AlbAtNf Avatar asked Feb 24 '16 11:02

AlbAtNf


People also ask

How do I fix Java Lang NullPointerException?

In Java, the java. lang. NullPointerException is thrown when a reference variable is accessed (or de-referenced) and is not pointing to any object. This error can be resolved by using a try-catch block or an if-else condition to check if a reference variable is null before dereferencing it.

How do I stop null dereference in Java?

How to avoid the NullPointerException? To avoid the NullPointerException, we must ensure that all the objects are initialized properly, before you use them. When we declare a reference variable, we must verify that object is not null, before we request a method or a field from the objects.

What is Dereferencing possible null pointer?

A NULL pointer dereference occurs when the application dereferences a pointer that it expects to be valid, but is NULL, typically causing a crash or exit. NULL pointer dereference issues can occur through a number of flaws, including race conditions, and simple programming omissions.

Why do we get NullPointerException in Java?

Some of the common reasons for NullPointerException in java programs are: Invoking a method on an object instance but at runtime the object is null. Accessing variables of an object instance that is null at runtime. Throwing null in the program.


1 Answers

Take a look at this example taken from here

class Argument {

    public final static int TOMAYTO = 0;
    public final static int TOMAHTO = 1;

    static void argue() {

        int say = TOMAYTO;

        while (true) {

            switch (say) {

            case TOMAYTO:

                say = TOMAHTO;
                break;

            case TOMAHTO:

                say = TOMAYTO;
                break;
            }
        }
    }
}

The bytecodes generated by javac for the argue() method are shown below:

   0 iconst_0 // Push constant 0 (TOMAYTO)
   1 istore_0 // Pop into local var 0: int say = TOMAYTO;
   2 iload_0 // Push key for switch from local var 0
                          // Perform switch statement: switch (say) {...
                          // Low case value is 0, high case value is 1
                          // Default branch offset will goto 2
   3 tableswitch 0 to 1: default=2
            0: 24 // case 0 (TOMAYTO): goto 24
            1: 29 // case 1 (TOMAHTO): goto 29

                          // Note that the next instruction starts at address 24,
                          // which means that the tableswitch took up 21 bytes
  24 iconst_1 // Push constant 1 (TOMAHTO)
  25 istore_0 // Pop into local var 0: say = TOMAHTO
  26 goto 2 // Branch unconditionally to 2, top of while loop
  29 iconst_0 // Push constant 1 (TOMAYTO)
  30 istore_0 // Pop into local var 0: say = TOMAYTO
  31 goto 2 // Branch unconditionally to 2, top of while loop

As you can see for switch statement with String data type a tableswitch is done, and for each case the value passed to switch is compared with the case's value, so what this means is that in your case extras.getString can be called multiple times without your previous if checks being called, and as such since extras is a bundle there is a chance it might get dereferenced and cause a nullpointer exception.

It's always a good practice to create a local variable instead of doing multiple calls of the method, you can have a look at this presentation by Jake Wharton to understand why.

like image 198
Bhargav Avatar answered Sep 29 '22 10:09

Bhargav