Kotlin的空安全真的安全嗎?

CHJ發表於2019-07-16

故事從一個Koltin專案新功能的除錯過程說起,應用丟擲了一個異常,堆疊資訊(區域性)如下:

[http-nio-8080-exec-2] ERROR c.c.p.f.i.c.c.e.GlobalExceptionHandler - java.lang.IllegalArgumentException: Parameter specified as non-null is null: method ${className}.<init>, parameter ${fieldName}
複製程式碼

是一個常見的空安全異常,向空安全的變數賦了Null 值。但值變數是也是來自空安全變數,為什麼會出現這樣的情況呢?接下來逐步分析。

Koltin 的空安全

Kotlin的在類成員變數的空安全是在編譯級別實現的,即在編譯成class檔案的時候在類的建構函式新增了空值檢查

// 第一個引數是建構函式傳入的變數值,第二個引數是變數名
Intrinsics.checkParameterIsNotNull(name, "name"); 
複製程式碼

這個檢查方法會在傳入Null值時丟擲java.lang.IllegalArgumentException異常,以此保證類的成員屬性是空安全。

Gson的反序列化

Gson的反序列化的主流程邏輯集中在ReflectiveTypeAdapterFactory.BoundField.read()方法中

@Override public T read(JsonReader in) throws IOException {
      if (in.peek() == JsonToken.NULL) {
        in.nextNull();
        return null;
      }

      T instance = constructor.construct();

      try {
        in.beginObject();
        while (in.hasNext()) {
          String name = in.nextName();
          BoundField field = boundFields.get(name);
          if (field == null || !field.deserialized) {
            in.skipValue();
          } else {
            field.read(in, instance);
          }
        }
      } catch (IllegalStateException e) {
        throw new JsonSyntaxException(e);
      } catch (IllegalAccessException e) {
        throw new AssertionError(e);
      }
      in.endObject();
      return instance;
}
複製程式碼

流程十分簡單:

  • 檢查Json的輸入流,為空則返回Null
  • 例項化泛型引數型別的物件例項instance
  • 解析Json字串給instance屬性賦值

最重要的是第二步,即物件的例項化過程constructor.construct()constructor是一個什麼物件呢?

constructor是一個封裝了物件構造方法的物件,它的生成邏輯如下:

public <T> ObjectConstructor<T> get(TypeToken<T> typeToken) {
    final Type type = typeToken.getType();
    final Class<? super T> rawType = typeToken.getRawType();

    // first try an instance creator

    @SuppressWarnings("unchecked") // types must agree
    final InstanceCreator<T> typeCreator = (InstanceCreator<T>) instanceCreators.get(type);
    if (typeCreator != null) {
      return new ObjectConstructor<T>() {
        @Override public T construct() {
          return typeCreator.createInstance(type);
        }
      };
    }

    // Next try raw type match for instance creators
    @SuppressWarnings("unchecked") // types must agree
    final InstanceCreator<T> rawTypeCreator =
        (InstanceCreator<T>) instanceCreators.get(rawType);
    if (rawTypeCreator != null) {
      return new ObjectConstructor<T>() {
        @Override public T construct() {
          return rawTypeCreator.createInstance(type);
        }
      };
    }

    ObjectConstructor<T> defaultConstructor = newDefaultConstructor(rawType);
    if (defaultConstructor != null) {
      return defaultConstructor;
    }

    ObjectConstructor<T> defaultImplementation = newDefaultImplementationConstructor(type, rawType);
    if (defaultImplementation != null) {
      return defaultImplementation;
    }

    // finally try unsafe
    return newUnsafeAllocator(type, rawType);
  }
複製程式碼
  • 如果註冊過 InstanceCreator ,則返回註冊的 InstanceCreator
  • 如果類有無參建構函式,則返回撥用無參建構函式的 InstanceCreator
  • 如果是集合類,則返回 newDefaultImplementationConstructor生成的 InstanceCreator
  • 否則交給 UnsafeAllocator

這次異常的分支條件呼叫的是UnsafeAllocator,其原始碼就不進行具體解析,其工作原理就是包裝了sun.misc.Unsafe的方法來完成物件的例項化,這個sun.misc.Unsafe就是這次異常的“病根”。

類的例項化 & sun.misc.Unsafe

Java/Kotlin中,物件的建立方式比較常見的是以下幾種:

  • new語句, 比如 MyClass demo = new MyClass()
  • Class物件的newInstance()方法,比如MyClass demo = MyClass.class.newInstance() (前提就是必須提供無參的建構函式)
  • 利用Constructor物件來建立物件

方式雖多,但殊途同歸,在JVM層面,其對應都是三條重要指令,以java.lang.StringBuilder為例

NEW java/lang/StringBuilder
DUP
INVOKESPECIAL java/lang/StringBuilder.<init> ()V
複製程式碼

其對應的邏輯分表別是

  • 分配物件所需記憶體並返回記憶體地址壓入棧頂
  • 複製一份上述記憶體地址並在壓入棧頂
  • 執行建立物件的<init>方法

可以看到物件的建立過程不是原子性的,所以還存在一種特殊途徑來建立物件,即sun.misc.Unsafe類。
它建立物件的方法和放射類似,但它不會執行INVOKESPECIAL指令,建立的物件的所有成員變數都處於空值狀態。

那麼回到應用的異常情況,在執行時Gson通過反序列化生成了一個與型別宣告不符的“半成品”物件,這個異常值的傳遞至一個空安全欄位時,就受到了Koltin的檢查導致異常發生。

總結

Kotlin的空安全確實給開發人員帶了極大的便利,但也帶來了隱患,即可能會給開發人員帶來虛假的安全感:Kotlin終究還是在基於JVM的靜態語言,面對類似sun.misc.Unsafe等類似的底層操作,空檢查可以被輕易突破,且這種非法物件的存在是渾然不知的,但是卻給我們的應用帶來真切的Bug,在面對空安全屬性上,各位同學還是需要多留個心眼,防範這些“非法移民”。

相關文章