/*
 * Copyright (c) 2013-present, Facebook, Inc.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package codetoanalyze.java.infer;

import android.annotation.SuppressLint;
import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.text.TextUtils;
import com.facebook.infer.annotation.Assertions;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.util.HashMap;
import java.util.concurrent.locks.Lock;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;

public class NullPointerExceptions {

  class A {
    int x;

    public void method() {}
  }

  // npe local with field
  public int nullPointerException() {
    A a = null;
    return a.x;
  }

  public A canReturnNullObject(boolean ok) {
    A a = new A();
    if (ok) return a;
    else return null;
  }

  public static void expectNotNullObjectParameter(A a) {
    a.method();
  }

  public static void expectNotNullArrayParameter(A[] array) {
    array.clone();
  }

  // npe with branching, interprocedural
  public int nullPointerExceptionInterProc() {
    A a = canReturnNullObject(false);
    return a.x;
  }

  // npe with exception handling
  public int nullPointerExceptionWithExceptionHandling(boolean ok) {
    A a = null;
    try {
      throw new Exception();
    } catch (Exception e) {
      return a.x;
    }
  }

  class B {
    A a;

    void test() {}
  }

  public static int nullPointerExceptionWithArray() {
    A[] array = new A[] {null};
    A t = array[0];
    return t.x;
  }

  // npe with a chain of fields
  class C {
    B b;
  }

  public int nullPointerExceptionWithAChainOfFields(C c) {
    c.b = new B();
    return c.b.a.x;
  }

  // npe with a null object parameter
  public static void nullPointerExceptionWithNullObjectParameter() {
    expectNotNullObjectParameter(null);
  }

  // npe with a null array parameter
  public static void nullPointerExceptionWithNullArrayParameter() {
    expectNotNullArrayParameter(null);
  }

  public static void nullPointerExceptionFromFaillingResourceConstructor() throws IOException {
    FileInputStream fis = null;
    try {
      fis = new FileInputStream(new File("whatever.txt"));
    } catch (IOException e) {
    } finally {
      fis.close();
    }
  }

  public static void nullPointerExceptionFromFailingFileOutputStreamConstructor()
      throws IOException {
    FileOutputStream fos = null;
    try {
      fos = new FileOutputStream(new File("whatever.txt"));
    } catch (IOException e) {
    } finally {
      fos.close();
    }
  }

  int x;

  public void nullPointerExceptionFromNotKnowingThatThisIsNotNull() {
    if (this == null) {}
    this.x = 4;
  }

  public <T> T id_generics(T o) {
    o.toString();
    return o;
  }

  public A frame(A x) {
    return id_generics(x);
  }

  public void nullPointerExceptionUnlessFrameFails() {
    String s = null;
    Object a = frame(new A());
    if (a instanceof A) {
      s.length();
    }
  }

  class D {
    int x;
  }

  public int preconditionCheckStateTest(D d) {
    Preconditions.checkState(d != null);
    return d.x;
  }

  public void genericMethodSomewhereCheckingForNull(String s) {
    if (s == null) {}
  }

  public void FP_noNullPointerExceptionAfterSkipFunction() {
    String t = new String("Hello!");
    String s = t.toString();
    genericMethodSomewhereCheckingForNull(s);
    s.length();
  }

  String hashmapNPE(HashMap h, Object o) {
    return (h.get(o).toString());
  }

  String NPEhashmapProtectedByContainsKey(HashMap h, Object o) {
    if (h.containsKey(o)) {
      return (h.get(o).toString());
    }
    return "aa";
  }

  int NPEvalueOfFromHashmapBad(HashMap<Integer, Integer> h, int position) {
    return h.get(position);
  }

  Integer NPEvalueOfFromHashmapGood(HashMap<Integer, Integer> h, int position) {
    return h.get(position);
  }

  void nullPointerExceptionInArrayLengthLoop(Object[] arr) {
    for (int i = 0; i < arr.length; i++) {
      Object x = null;
      x.toString();
    }
  }

  Context mContext;
  ContentResolver mContentResolver;

  public void cursorFromContentResolverNPE(String customClause) {
    String[] projection = {"COUNT(*)"};
    String selectionClause = selectionClause = customClause;
    Cursor cursor =
        mContext.getContentResolver().query(null, projection, selectionClause, null, null);
    cursor.close();
  }

  public int cursorQueryShouldNotReturnNull(SQLiteDatabase sqLiteDatabase) {
    Cursor cursor = sqLiteDatabase.query("events", null, null, null, null, null, null);
    try {
      return cursor.getCount();
    } finally {
      cursor.close();
    }
  }

  Object[] arr = new Object[1];

  Object arrayReadShouldNotCauseSymexMemoryError(int i) {
    arr[i].toString();
    return null;
  }

  void nullPointerExceptionCallArrayReadMethod() {
    arr[0] = new Object();
    arrayReadShouldNotCauseSymexMemoryError(0).toString();
  }

  public void sinkWithNeverNullSource() {
    NeverNullSource source = new NeverNullSource();
    T t = source.get();
    t.f();
  }

  public void otherSinkWithNeverNullSource() {
    SomeLibrary source = new SomeLibrary();
    T t = source.get();
    t.f();
  }

  private @Nullable Object mFld;

  void nullableFieldNPE() {
    mFld.toString();
  }

  void guardedNullableFieldDeref() {
    if (mFld != null) mFld.toString();
  }

  void allocNullableFieldDeref() {
    mFld = new Object();
    mFld.toString();
  }

  void nullableParamNPE(@Nullable Object param) {
    param.toString();
  }

  void guardedNullableParamDeref(@Nullable Object param) {
    if (param != null) param.toString();
  }

  void allocNullableParamDeref(@Nullable Object param) {
    param = new Object();
    param.toString();
  }

  native boolean test();

  Object getObj() {
    if (test()) {
      return new Object();
    } else {
      return null;
    }
  }

  Boolean getBool() {
    if (test()) {
      return new Boolean(true);
    } else {
      return null;
    }
  }

  void derefGetterAfterCheckShouldNotCauseNPE() {
    if (getObj() != null) {
      getObj().toString();
    }
  }

  void derefBoxedGetterAfterCheckShouldNotCauseNPE() {
    boolean b = getBool() != null && getBool();
  }

  static void derefNonThisGetterAfterCheckShouldNotCauseNPE() {
    NullPointerExceptions c = new NullPointerExceptions();
    if (c.getObj() != null) {
      c.getObj().toString();
    }
  }

  void badCheckShouldCauseNPE() {
    if (getBool() != null) getObj().toString();
  }

  void nullPointerExceptionArrayLength() {
    Object[] arr = null;
    int i = arr.length;
  }

  class $$Class$Name$With$Dollars {
    void npeWithDollars() {
      String s = null;
      int n = s.length();
    }
  }

  void nullableNonNullStringAfterTextUtilsIsEmptyCheckShouldNotCauseNPE(@Nullable String str) {
    if (!TextUtils.isEmpty(str)) {
      str.length();
    }
  }

  void someNPEAfterResourceLeak() {
    T t = CloseableAsResourceExample.sourceOfNullWithResourceLeak();
    t.f();
  }

  private Object mOkObj = new Object();

  public void nullableParamReassign1(@Nullable Object o) {
    if (o == null) {
      o = mOkObj;
    }
    o.toString();
  }

  public void nullableParamReassign2(@Nullable Object o, Object okObj) {
    if (o == null) {
      o = okObj;
    }
    o.toString();
  }

  private @Nullable Object mNullableField;

  public void nullableFieldReassign1() {
    if (mNullableField == null) {
      mNullableField = mOkObj;
    }
    mNullableField.toString();
  }

  public void nullableFieldReassign2(Object okObj) {
    if (mNullableField == null) {
      mNullableField = okObj;
    }
    mNullableField.toString();
  }

  public void nullableFieldReassign3(Object param) {
    mNullableField = param;
    mNullableField.toString();
  }

  public Object nullableGetter() {
    return mNullableField;
  }

  public void derefNullableGetter() {
    Object o = nullableGetter();
    o.toString();
  }

  public @Nullable Object nullableRet(boolean b) {
    if (b) {
      return null;
    }
    return new Object();
  }

  public void derefNullableRet(boolean b) {
    Object ret = nullableRet(b);
    ret.toString();
  }

  public void derefNullableRetOK(boolean b) {
    Object ret = nullableRet(b);
    if (ret != null) {
      ret.toString();
    }
  }

  public native @Nullable Object undefNullableRet();

  public void derefUndefNullableRet() {
    Object ret = undefNullableRet();
    ret.toString();
  }

  public void derefUndefNullableRetOK() {
    Object ret = undefNullableRet();
    if (ret != null) {
      ret.toString();
    }
  }

  void assumeUndefNullableIdempotentOk() {
    if (undefNullableRet() != null) {
      undefNullableRet().toString();
    }
  }

  public Object undefNullableWrapper() {
    return undefNullableRet();
  }

  public void derefUndefNullableRetWrapper() {
    undefNullableWrapper().toString();
  }

  private int returnsThreeOnlyIfRetNotNull(Object obj) {
    if (obj == null) {
      return 2;
    }
    return 3;
  }

  public void testNullablePrecision() {
    Object ret = undefNullableRet();
    if (returnsThreeOnlyIfRetNotNull(ret) == 3) {
      ret.toString(); // shouldn't warn here
    }
  }

  public @Nullable String testSystemGetPropertyArgument() {
    String s = System.getProperty(null);
    return s;
  }

  public void testSystemGetPropertyReturn() {
    String s = System.getProperty("");
    int n = s.length();
  }

  Object retUndefined() {
    return "".toString(); // toString is a skip function
  }

  Object derefUndefinedCallee() {
    // if retUndefined() is handled incorrectly, we get a symexec_memory_error here
    retUndefined().toString();
    return null;
  }

  void derefNull() {
    // should be NPE, but will not be reported if we handled retUndefined() incorrectly
    derefUndefinedCallee().toString();
  }

  @SuppressLint("NULL_DEREFERENCE")
  void shouldNotReportNPE() {
    Object o = null;
    o.toString();
  }

  void shouldNotReportOnSkippedSource() {
    Object o = SkippedSourceFile.createdBySkippedFile();
    o.toString();
  }

  int nullListFiles(String pathname) {
    File dir = new File(pathname);
    File[] files = dir.listFiles();
    return files.length; // expect possible NullPointerException as files == null is possible
  }

  native Object unknownFunc();

  void nullDerefernceReturnOfSkippedFunctionBad() {
    Object object = unknownFunc();
    if (object == null) {
      object.toString();
    }
  }

  native @Nonnull Object doesNotReturnNull();

  void noNPEWhenCallingSkippedNonnullAnnotatedMethodGood() {
    Object object = doesNotReturnNull();
    if (object == null) {
      object.toString();
    }
  }

  Object callUnknownFunc() {
    return unknownFunc();
  }

  void dontReportOnNullableDirectReassignmentToUnknown(@Nullable Object o) {
    o = unknownFunc();
    o.toString();
  }

  void dontReportOnNullableIndirectReassignmentToUnknown(@Nullable Object o) {
    o = callUnknownFunc();
    o.toString();
  }

  @Nullable
  Object wrapUnknownFuncWithNullable() {
    return unknownFunc();
  }

  void deferenceNullableMethodCallingSkippedMethodBad() {
    wrapUnknownFuncWithNullable().toString();
  }

  String nullTryLock(FileChannel chan) throws IOException {
    FileLock lock = chan.tryLock();
    return lock.toString(); // expect possible NullPointerException as lock == null is possible
  }

  String tryLockThrows(FileChannel chan) {
    try {
      FileLock lock = chan.tryLock();
      return (lock != null ? lock.toString() : "");
    } catch (IOException e) {
      Object o = null;
      return o.toString(); // expect NullPointerException as tryLock can throw
    }
  }

  class L {
    L next;
  }

  Object returnsNullAfterLoopOnList(L l) {
    while (l != null) {
      l = l.next;
    }
    return null;
  }

  void dereferenceAfterLoopOnList(L l) {
    Object obj = returnsNullAfterLoopOnList(l);
    obj.toString();
  }

  void dereferenceAfterUnlock1(Lock l) {
    l.unlock();
    String s = l.toString();
    s = null;
    s.toString(); // Expect NPE here
  }

  void dereferenceAfterUnlock2(Lock l) {
    synchronized (l) {
      String b = null;
    }
    String s = l.toString();
    s = null;
    s.toString(); // Expect NPE here
  }

  void optionalNPE(Optional<Object> o) {
    o.orNull().toString();
  }

  void stringConstantEqualsTrueNotNPE() {
    final String c1 = "Test string!";
    final String c2 = "Test string!";
    String s = null;
    if (c1.equals(c1)) {
      s = "safe";
    }
    s.toString(); // No NPE
    s = null;
    if (c1.equals(c2)) {
      s = "safe";
    }
    s.toString(); // No NPE
  }

  void stringConstantEqualsFalseNotNPE_FP() {
    // This won't actually cause an NPE, but our current model for String.equals
    // returns boolean_undefined for all cases other than String constant
    // equality. Consider handling constant inequality in the future.
    final String c1 = "Test string 1";
    final String c2 = "Test string 2";
    String s = null;
    if (!c1.equals(c2)) {
      s = "safe";
    }
    s.toString(); // No NPE
  }

  String getString2() {
    return "string 2";
  }

  void stringVarEqualsFalseNPE() {
    final String c1 = "Test string 1";
    String c2 = "Test " + getString2();
    String s = null;
    if (!c1.equals(c2)) {
      s.toString(); // NPE
    }
  }

  String assertParameterNotNullableOk(@Nullable Object object) {
    return Assertions.assertNotNull(object).toString();
  }

  interface I {
    @Nullable Object mObject = null;
  }

  class E implements I {

    void dereferenceNullableInterfaceFieldBad() {
      mObject.toString();
    }
  }

  Object getObject() {
    return null;
  }

  void addNullToImmutableListBuilderBad() {
    ImmutableList.Builder<Object> listBuilder = ImmutableList.builder();
    listBuilder.add(getObject());
  }
}
