精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

在 Swift 中如何正確傳遞 Unsafe Pointers 參數

開發
在過去一個季度抖音規模化落地 Swift 組件的過程中,我負責的代碼在 CI 運行單測階段暴露了幾個問題,都與 Swift 中的 unsafe pointers 有關。

TL;DR

  • Swift 中對于類型大小為空的變量使用 & 取地址是未定義行為,編譯為目標碼之后的體現為一個根據之前代碼執行結果產生的任意數值。這是一個 feature。
  • Swift 中在多個線程中對同一個變量使用 & 將獲取「寫訪問」,會造成運行時崩潰。
  • Swift 中對 computed property 取地址會取到臨時變量的地址。如果 computed property 是一個鎖,將造成鎖被拷貝到多個線程的執行棧上,造成程序錯誤。

平平無奇但錯誤的代碼

在過去一個季度抖音規模化落地 Swift 組件的過程中,我負責的代碼在 CI 運行單測階段暴露了幾個問題,都與 Swift 中的 unsafe pointers 有關。

第一個是通過 Objective-C 中 associated object 技巧擴展出來的 property 在 release build 后再運行,set 之后只能 get 到 nil;debug build 下則正常:

圖片

范例代碼一

第二個是下列代碼在 release build 后,在多線程環境有可能崩潰在 swift_endAccess 函數中:

@_implementationOnly import Darwin


public class UnfairLock {


    var _lock: os_unfair_lock
    
    public func withLock<R>(perform action: () -> R) -> R {
        os_unfair_lock_lock(&_lock)
        defer {
            os_unfair_lock_unlock(&_lock)
        }
        return action()
    }
    
    public init() {
        _lock = os_unfair_lock()
    }
    
}

范例代碼二

圖片

是不是覺得很奇怪?以上兩段代碼既符合直覺,也沒有編譯錯誤。那么,為什么會產生上述問題呢?

歸因:ObjC 關聯對象訪存出錯

針對范例代碼一里面 ObjC associated object 訪存得到錯誤結果的問題,我們可以進入匯編模式,看看到底 objc_get(set)AssociatedObject 得到的參數是什么。首先打開 Xcode 的 Always Show Disassembly(看完文章后記得關閉哦),在 objc_getAssociatedObject  objc_setAssociatedObject 打下斷點。

圖片

運行后我們可以看到 objc_setAssociatedObject  key 這個參數(arm64 上的 x1 寄存器)的值是 0x04000001ed295c71,這個地址存儲的值是 0x00000001ed295c71。我們預期通過 dis -s 指令對這個地址進行反匯編可以獲得該地址對應的二進制鏡像名稱,然而這里卻提示「反匯編失敗」。這是為什么呢?

圖片

我們可以進一步檢查 x1 寄存器內容的來源。下圖紅線為 x1 寄存器在 MyObject.myProperty.setter (下稱 setter 函數)內的數據流。可以看到:

  1. setter 函數初始棧高為 0x20 (sub sp, sp, #0x20)
  2. x1 為 setter 函數執行棧基準地址 + 0x40 - 0x48 = setter 函數執行棧基準地址 - 0x8。所以對應 setter 函數執行棧上 0x18 偏移的棧變量

圖片

而 setter 函數開頭調用的 Optional<Any> 的拷貝初始化函數 outlined init with copy of Swift.Optional<Any> 的第二個參數 x1 為 setter 函數執行棧基準地址 + 0x40 - 0x60 = setter 函數執行棧基準地址 - 0x20。結合之前獲得的 setter 函數執行棧高 0x20 的信息,所以對應 setter 函數執行棧上 0x0 偏移的棧變量。

于是我們可以知道,setter 函數執行棧基準地址后的所有空間都有可能被 Optional<Any> 的拷貝初始化函數利用到。而實際上這個拷貝初始化函數的第二個參數就是拷貝操作的目標地址。加上上圖中藍線的原點在判別完 x21 的內容(即 objc_setAssociatedObject 接受到的 x1)之后進行了條件跳轉(cbz,即 conditional branching if zero 的縮寫),跳過的內容正是把 Any 橋接到 Objective-C(因為中途有調用 Swift._bridgeAnythingToObjectiveC),所以我們可以大膽猜測:

x21 —— 即 objc_setAssociatedObject 接受到的 x1,實際上是可以判斷 Optional<Any> 為空的信息——而這個并不是我們在源碼中給定的 key——這就是導致我們使用 objc_get(set)AssociatedObject 不正常工作的原因。而我們一開 dis -s 之所以會失敗,是因為我們在嘗試對函數執行棧進行反匯編。

深入:Void 全局變量編譯細節

為了了解更多代碼生成細節,知道為什么生成了這樣的代碼,我們可以使用 Swift 編譯器的 -emit-sil(gen)  -emit-ir(gen) 參數來考察 SIL(原始 SIL)和 IR(原始 IR)的生成結果,看到底是哪一步產生了意外。檢查的順序應該 -emit-silgen, -emit-sil, -emit-irgen, -emit-ir,確保先檢查原始 SIL(SILGen)和原始 IR(IRGen),再檢查優化后的 SIL 和 IR。檢查 SILGen 的命令如下:

// 這里我保存的文件叫 UnsafePointers.swift
// 我的 Xcode 放置的路徑是 /Applications/Xcode-15.0.app
// 大家可以根據自己的情況對以下命令進行修改:
xcrun swift-frontend -c UnsafePointers.swift \
    -enable-objc-interop \
    -target arm64-apple-macos14.0 \
    -sdk /Applications/Xcode-15.0.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk \
    -emit-silgen > UnsafePointers.silgen.sil

上文的 setter 函數在 SILGen 中生成了三個區塊:bb0 是函數入口,在 363 行進行 switch-case 之后跳轉至 bb1  bb2。但不論是 bb1 還是 bb2,最后都會跳轉至 bb3(如下圖藍線所示)。所以我們直接看 bb3 好了。

圖片

所以我們直接折疊 bb1  bb2,然后可以畫出 objc_setAssociatedObject 第二個參數 %32 的數據流。可以看到其最終來自于 %2,而 %2 會對一個全局 Swift 符號取地址。

圖片

而這個符號正是我們定義的 myKey

圖片

因為 global_addr 是 SIL 指令,已經是 SIL 這一層的「原語(最小不可分割語素)」了,所以我們應該進一步查看 IRGen 的結果。

xcrun swift-frontend -c UnsafePointers.swift \
    -enable-objc-interop \
    -target arm64-apple-macos14.0 \
    -sdk /Applications/Xcode-15.0.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk \
    -emit-irgen > UnsafePointers.irgen.ll

在 IRGen 結果中,我們可以直接搜索 MyObject.myProperty.setter 的 Swift 改編符號(如果你也使用 UnsafePointers.swift 這個文件名那么就是 $s14UnsafePointers8MyObjectC10myPropertyypSgvs)。我們可以看到,517 行給 objc_setAssociatedObject 的第二個參數已經變成了 undef

圖片

為了進一步探究 SIL 中的 global_addr 指令為何在 lower 到 IR 之后會得到 undef,我們可以動態調試一下 Swift 編譯器。這里我們使用簡化后的代碼以加速編譯器調試。因為 foo 是全局變量,所以第 4 行的 &foo 仍然會生成 global_addr 指令。

var foo: Void = Void()


func bar() -> UnsafeRawPointer {
    UnsafeRawPointer(&foo)
}

然后我們在 IRGenSILFunction::visitGlobalAddrInst 中打下斷點,編譯上面的源代碼,從 DEBUG CONSOLE 中的 i-> dump() 結果可以看到,此次 GlobalAddrInst 實例 i 的內容為被 & 引用的變量。最后代碼執行進入了 2950 行,這里可以看到針對該全局變量的 ti: TypeInfo&,如果其 isKnownEmpty 返回 true 就不會生成符號,因此地址是 undef。這是一個 feature。

圖片

圖片

而 Swift 編譯器中的 TypeInfo 類型負責記載類型信息對應信息。對于 TypeInfo::isKnownEmpty 而言,簡單來說如果可以在編譯器在編譯時可以確定大小的類型,并且大小為空,即可認為其會返回 true

歸因:多線程下取地址崩潰

要理解文章開頭范例代碼二中所出現的「多線程下對實例變量取地址」而導致的崩潰,更直接的方法是打開 Xcode 的 Thread Sanitizer 后運行程序。我們可以看到,Xcode 幫我們檢測出了「訪問競爭(access race)」。這是因為在 Swift 中對變量使用 & 即意味著需要獲取一個「寫訪問(write access)」,而目前的代碼有多個線程在訪問 UnfairLock.withLock,那么也就有多個線程在嘗試對 UnfairLock._lock 獲取「寫訪問」。這個在 Swift 中不符合運行時 exclusivity enforcement,所以會崩潰。

圖片

圖片

想要修復這個問題,將獲得指針的時機移動至多線程代碼外(即 x.testLock 外)即可。

但是 exclusivity 沖突并不是這個寫法的全部問題,這個寫法還有一個非常隱蔽的問題:& 可能取到的不是變量本身的地址,而是一個臨時分配的變量。要理解這個問題,需要知道 Swift 是如何實現變量取地址的。

深入:Swift 變量取地址實現

在 Swift 中 var 關鍵字定義的變量滿足以下抽象:

  • 一定包含一個 get accessor
  • 可選包含一個 set accessor
  • 可選包含一個存儲容器

然而,上面只是開發者在日常開發中能夠感知到的部分。上述抽象沒有解決的問題是:如果一個變量的存儲容器是可選的,那么我們應該如何獲得這個變量的地址呢?所以編譯器還會為我們自動生成:

  • 一定包含一個 _read accessor
  • 可選包含一個 _modify accessor

其中 _read accessor 是一個對變量產生「讀訪問」,并且拋出一個只讀地址的協程

 _modify accessor 是一個對變量產生「寫訪問」,并且拋出一個可寫地址的協程

而通過上述 _read  _modify accessor,我們就定義了獲取 var 關鍵字變量地址的手段。

「協程」可以理解為不保證棧平衡的函數(或稱「過程」)。協程本是過程的原始形態——過程引入棧平衡是為了實現本地變量,而這個特性在協程中無法實現。但是人們后來發現非平衡的棧可以讓后續執行的代碼沿用之前的棧內存內容,而不用重復在棧上傳參,又或者開辟堆空間傳參,所以人們又開始利用起了「協程」。Swift 引入協程的目的也是做性能優化。 需要注意的是 Swift Concurrency 并不是協程。

而在 Swift 中對變量使用 & 本質上就是獲取 _modify accessor 拋出的「變量可寫地址」。

我們可以從 SILGen 的結果中一窺究竟。

下面是 UnfairLock.withLock(perform:) 在未修改前的 SILGen 結果,紅色的線和方框代表了 &_lock 這句 Swift 源碼在 SIL 層面的數據流。

我們可以看到 &_lock 最初來自于 %6,而 %6 正是對 self (%2) 使用了 #UnfiarLock.lock!modify 這個協程產生的,同時產生的 %7 則是協程產生的非平衡棧的 resumption 函數,由 89 行的 endApply 調用,用以恢復棧平衡。

圖片

而當前實現的 UnfiarLock.lock._modify 則是通過 ref_element_addr 這條 SIL 指令直接拋出了 UnfairLock.lock 在當前 self 中的地址。

圖片

但是,當情況變得復雜一些的時候,這個 _modify accessor 拋出的將會變成一個臨時變量的地址——對,就是這個協程在非平衡棧里面分配的臨時變量。要造成這個結果很簡單:比如,把 os_unfair_lock 打包放入一個叫做 Data 的類型中,然后 var _lock: os_unfair_lock 改成 computed property,從 Data 中訪存 os_unfair_lock

private struct Data {
  var lock = os_unfair_lock()
}


public class UnfairLock {


  private var data = Data()


  internal var _lock: os_unfair_lock {
    get {
      data.lock
    }
    set {
      data.lock = newValue
    }
  }


  public func withLock<R>(perform action: () -> R) -> R {
    os_unfair_lock_lock(&_lock)
    defer {
      os_unfair_lock_unlock(&_lock)
    }
    return action()
  }


  public init() {
    
  }


}

我們可以看到,此時 UnfairLock._lock.modify 的 SILGen 結果中出現了棧分配(683 行),然后分配后的棧地址內又被 store(復制)了 UnfairLock._lock (687 行),隨后棧分配后的地址被 _modify 拋出,爾后在 _modify 的 resumption 函數(bb1  bb2)中,棧地址中的內容又被重新 set 回了 UnfairLock._lock(694 行及 703 行)。

圖片

上面這種行為在多線程場景就是災難性質的——因為每一個線程都有自己獨立的執行棧,而這種行為就是把一個鎖復制到了每一個在競爭這把鎖的線程的執行棧上再加解鎖——最后的結果一定是程序出錯。

深入:理解取地址中的臨時變量

要理解對 var 取地址時可能取到臨時變量的地址,還是需要回到 Swift 對 var 關鍵字定義的變量的抽象:

  • 一定包含一個 get accessor
  • 可選包含一個 set accessor
  • 可選包含一個存儲容器

而編譯器幫助合成 _read 或者 _modify 時,如果變量沒有實際存儲容器,那么也只能通過 get  set 實現:當出現 computed property 時,其 _modify 會先通過 get accessor 創建一個臨時變量,拋出臨時變量地址之后,在 resumption 時再使用 set accessor 寫回這種實現了。

所以,如果開發者書寫如下代碼:

struct Foo {
    var _bar: Int = 0
    var bar: Int {
        get {
            self._bar
        }
        set {
            self._bar = newValue
        }
    }
}


var foo = Foo()


withUnsafeMutablePointer(to: &foo, body: handleIntPtr)


func handleIntPtr(_ ptr: UnsafeMutablePointer<Int>) {
    // ...
}

那么實際上編譯器會幫助合成并生成如下代碼:

struct Foo {
    var bar: Int {
        // ...
        _read { // 編譯器合成代碼
            let tempFoo = 棧分配
            tempFoo = self.bar.getter
            拋出不可變棧地址 tempFoo
        }
        _read.resumption { // 編譯器合成代碼
            棧析構 tempFoo
        }
        _modify { // 編譯器合成代碼
            var tempFoo = 棧分配
            tempFoo = self.bar.getter
            拋出可變棧地址 tempFoo
        }
        _modify.resumption { // 編譯器合成代碼
            self.bar.setter = tempFoo
            棧析構 tempFoo
        }
    }
}


var foo = Foo()


// 棧分配 tempFoo 以及隱式地址到指針轉換
let ptrToTempFoo: UnsafeMutablePointer<Int> = Foo.bar._modify(foo)


// 應用 withUnsafeMutablePointer 的 body 閉包
handleIntPtr(ptrToTempFoo)


// 將臨時變量 set 回去,并完成 tempFoo 棧析構
Foo.bar._modify.resumption(foo)

知道這一點之后,我們也可以嘗試手寫 UnfairLock._lock._modify 的實現,直接拋出 data.lock 的地址,來消除臨時變量:

public class UnfairLock {


  private var data = Data()


  internal var _lock: os_unfair_lock {
    _read {
      yield data.lock
    }
    _modify {
      yield &data.lock
    }
  }
  
  // ...


}

此時我們可以通過 SILGen 的結果看到:UnfairLock._lock.modify 目前會委托到 UnfairLock.data.modify(84 行),然后利用 UnfairLock.data.modify 的結果,然后取出 Data.lock 的地址(85 行),最后拋出(86 行):

圖片

 UnfairLock.data.modify 則是直接拋出了 UnfairLock.data 在當前 self 下的地址(第 61 行)。

圖片

上述技巧繞過了「使用 var 關鍵字變量的 get 和 set」來實現 _modify,同時也產生了一個「副作用」。大家能想到是什么嗎?

最佳實踐及準入建設

在實際的日常開發活動中,我們并不想耗費如此多的心智在如何處理好給 unsafe pointers 傳參上,所以我們需要一套最佳實踐以及自動化準入機制來保證我們的日常開發的執行結果。

上述問題可以歸結為:

  1.  Void 取地址出現無意義數值
  2. 多線程使用 & 對變量取地址后崩潰
  3. 對 computed property 取地址得到的是臨時變量地址

下面我們分情況討論

ObjC 關聯對象訪存出錯

前文「ObjC 關聯對象訪存結果出錯」的本質是:Swift 中對于空大小類型的全局變量取地址編譯到 LLVM IR 后是 undef 的,編譯為目標碼之后的體現為一個根據之前代碼執行結果產生的任意數值。所以「對大小為空的類型的全局變量取地址」本身就是一個未定義行為。這里的最佳實踐就是不允許這樣做。在準入建設方面,我們可以設立靜態分析規則進行檢出。依據公司現有靜態分析設施,我們可以分析:1)標準庫以及2)文件內定義的空大小類型,并且3)有選擇地加入系統庫的類型定義。


多線程下取地址崩潰

前文「多線程下對實例變量取地址發生崩潰」的本質是:Swift 中,在多個線程中對同一個變量獲取「寫訪問」會引發「訪問競爭」,這是 Swift 運行時在開啟 runtime exclusivity enforcement 之后所不允許的。相關最佳實踐應該是將「寫訪問」提出多線程代碼:

比如下列代碼通過 DispatchQueue.concurrentPerform 這個并發執行的接口,在多個線程中通過 & 取地址對同一個變量 counter 獲取了「寫訪問」:

// ?
var counter = AtomicIntStorage() // zero init
DispatchQueue.concurrentPerform(iterations: 10) { _ in
  for _ in 0 ..< 1_000_000 {
    atomicFetchAddInt(&counter, 1)  // Exclusivity violation
  }
}
print(atomicLoadInt(&counter) // ???

我們可以將相關代碼提取出 DispatchQueue.concurrentPerform 的尾閉包:

// ?
var counter = AtomicIntStorage() // zero init
withUnsafeMutablePointer(to: &counter) { pointer in
  DispatchQueue.concurrentPerform(iterations: 10) { _ in
    for _ in 0 ..< 1_000_000 {
      atomicFetchAddInt(pointer, 1) // OK
    }
  }
  print(atomicLoadInt(pointer) // 10_000_000
}

但是上面是發生在極其局部的問題。泛化而言,面對運行時訪問競爭,蘋果使用 SIL 這種檢測能力很強的靜態檢測手段亦無法檢出,我們即可判斷:對于運行時的行為,我們需要運行時的設施進行檢測,所以我們需要研發流程準入同時進行調整:

  1. 研發流程中加入研判是否需要設計多線程測試用例的步驟;如果需要設計,則需要單獨在評審時提供多線程測試用例
  2. 準入調整為要求單元測試覆蓋率 100%
  3. 準入調整為在 CI 單測流水線在 Xcode test plan 中開啟 thread sanitizer,CI 消費 thread sanitizer 檢出結果

圖片

取地址得到臨時變量地址

前文「對變量取地址有可能取到臨時地址」的本質是:Swift 中對 computed property 的默認實現取地址過程依賴 get  set 來分配臨時變量,然后再通過 set 設置回去。其在多線程中產生的后果是:鎖在各個線程的執行過程中被 get 多份至各線程的執行棧上;在普遍的代碼中產生的后果是:取地址的結果不穩定。

所以這里最佳實踐也應該分情況討論:

  • 對于使用鎖的需求而言,蘋果的思路是提供封裝好的鎖,而不是鼓勵使用原始鎖(如 os_unfair_lock 或者 pthread_mutex)。但是蘋果在 iOS 16 才想起這件事,所以這里我們需要自行封裝好沒問題的鎖,并且鼓勵開發者只使用封裝好的鎖。
// 下列代碼是蘋果的封裝
enum MyState {
    case idle
    case loading
    case complete(MyAsset)
    case error(Error)
}
let protectedState = OSAllocatedUnfairLock(initialState: MyState.idle)
func myLoadMethod() {
    protectedState.withLock { state in
        state = .loading
    }
    var (resource, error) = loadMyResources()
    if resource != nil {
        protectedState.withLock { state in
            state = .complete(resource)
        }
    } else {
        protectedState.withLock { state in
            state = .error(error!)
        }
    }
}
  • 對于其他需求,這里最佳實踐應該是:
  • 禁用對 get/set 實現的 computed property 取地址。
  • 如果是對 stored property 取地址,也應該使用左值存儲指針,而不是直接使用右值送參。且 stored property 及存儲指針的左值的生命周期可以涵蓋使用指針代碼的生命周期——比如 stored property 及存儲指針的左值是一個全局變量。
// ?
private var myKey: Int8 = 0
objc_getAssociatedObject(self, &myKey) // &myKey 是一個右值


extension NSObject {
    var myProperty: Any? {
        get {
            objc_getAssociatedObject(self, &myKey)
        }
        set {
            objc_setAssociatedObject(
                self,
                &myKey, 
                newValue,
                .OBJC_ASSOCIATION_RETAIN_NONATOMIC
            )
        }
    }
}
// ?
// 全局變量
private var myKey: Int8 = 0
// 全局變量,myKeyPtr 是一個左值。
private let myKeyPtr = UnsafeRawPointer(withUnsafeMutablePointer(to: &myKey) {$0})
// 通常來說 withUnsafeMutablePointer 獲得的指針值只保證在尾閉包中有效
// 這里利用了 myKey 是全局變量,生命周期貫穿全 app 啟動關閉
// 所以即使 return 了 withUnsafeMutablePointer 中尾閉包的指針值也沒有問題


extension NSObject {
    var myProperty: Any? {
        get {
            objc_getAssociatedObject(self, myKeyPtr)
        }
        set {
            objc_setAssociatedObject(
                self,
                myKeyPtr, 
                newValue,
                .OBJC_ASSOCIATION_RETAIN_NONATOMIC
            )
        }
    }
}

準入方面,我們可以:

  1. 通過靜態檢測對「在 Swift 中使用原始鎖」這種行為進行攔截,并不再鼓勵直接使用原始鎖(如 os_unfair_lock 或者 pthread_mutex);對自己開發水平自信的開發者依然可以使用原始鎖,但是相關代碼需要單獨豁免。
  2. 通過靜態檢測對「Swift 中 & 取地址之后作為右值使用」這種行為進行攔截,攔截后建議修復為使用左值存儲指針值后再使用
  3. 通過靜態檢測對「向 computed property 取地址」這種行為進行攔截。

總結

上述問題的本質、后果總結如下

圖片

上述最佳實踐及處置手段總結如下:

圖片


作者介紹:

  • 李禹龍,2020 年加入字節跳動,來自抖音 iOS 基礎技術團隊,專注 Swift 語言及 UI DSL 框架。
責任編輯:龐桂玉 來源: 字節跳動技術團隊
相關推薦

2024-01-17 06:23:35

SwiftTypeScript定義函數

2025-02-12 10:51:51

2022-06-07 08:31:44

JavaUnsafe

2012-02-21 14:04:15

Java

2017-12-05 08:53:20

Golang參數傳遞

2021-04-16 20:50:16

URL爬蟲參數

2021-04-13 09:20:21

JavaUnsafejava8

2010-01-05 14:49:03

JSON格式

2014-07-04 09:47:24

SwiftSwift開發

2025-07-28 09:10:00

2009-06-09 21:54:26

傳遞參數JavaScript

2014-07-22 09:01:53

SwiftJSON

2022-06-27 09:00:55

SwiftGit Hooks

2024-04-28 11:36:07

LambdaPython函數

2023-11-17 14:10:08

C++函數

2015-09-08 10:16:41

Java參數按值傳遞

2010-01-05 15:30:25

JSONP

2009-12-08 14:31:31

PHP命令行讀取參數

2009-12-15 14:09:39

Ruby創建可參數化類

2010-05-05 17:53:39

Oracle 8i
點贊
收藏

51CTO技術棧公眾號

懂色一区二区三区免费观看| 97国产成人高清在线观看| 天天爽夜夜爽夜夜爽精品视频| 国产精品99久久久久久久| 国产精品人人人人| 日韩综合在线| 亚洲国产精品va在线看黑人 | 国内精品在线视频| 国产一区二区三区免费观看| 91精品成人久久| 五月婷婷欧美激情| 国产精品对白| 91.com在线观看| 国产精品亚洲a| 羞羞电影在线观看www| 欧美激情一区二区三区蜜桃视频| av一区和二区| 一区二区不卡视频在线观看| 国产精品主播| 欧美日韩成人在线播放| 精品成人无码一区二区三区| 欧美视频二区欧美影视| 91国偷自产一区二区使用方法| 中文字幕精品在线播放| 国产九色在线| 91美女片黄在线| 操一操视频一区| 国产又粗又大又爽| 日韩精品久久理论片| 97婷婷大伊香蕉精品视频| 三上悠亚作品在线观看| 欧美日韩精品在线一区| 日韩精品在线视频| youjizz.com日本| 日本亚洲视频| 91精品国产综合久久久久久久久久| 久久9精品区-无套内射无码| 欧美人与性动交α欧美精品济南到| 国产精品系列在线| 欧美激情第一页在线观看| 乱精品一区字幕二区| 久久精品国产精品亚洲综合| 国产精品va在线| 天天操天天摸天天干| 国内自拍一区| 欧美日韩成人精品| 男人的天堂久久久| 欧美一区影院| 欧美精品亚州精品| 青娱乐国产盛宴| 9191国语精品高清在线| 日韩在线视频免费观看| 在线看片中文字幕| 97精品国产福利一区二区三区| 亚洲欧美制服另类日韩| 国产精品探花一区二区在线观看| 国产精品久av福利在线观看| 精品国产第一区二区三区观看体验| www.色.com| 秋霞影院一区| 亚洲电影免费观看高清完整版在线观看| 日本女人黄色片| 欧美国产中文高清| 精品奇米国产一区二区三区| 亚洲成年人av| 无码日韩精品一区二区免费| 国产午夜精品久久久| 欧美无人区码suv| 九九热精品视频在线观看| 亚洲天堂av在线免费观看| 欧美激情aaa| 成人高清电影网站| 久久精品电影网| 欧美日韩偷拍视频| 伊人久久成人| 日韩免费视频在线观看| 亚洲无码精品在线播放| 国产一区二区三区免费在线观看| 国产精品国产三级国产专区53| 欧性猛交ⅹxxx乱大交| 久久免费国产精品| 亚洲午夜在线观看| 日本在线视频中文有码| 欧美三级免费观看| 天堂一区在线观看| 综合激情久久| 亚洲性av网站| 搜索黄色一级片| 亚洲激情国产| 国产精品视频网站| 成人毛片在线精品国产| 久久综合一区二区| 宅男av一区二区三区| www中文字幕在线观看| 在线亚洲一区二区| 五月天丁香社区| 日本高清免费电影一区| 欧美日韩国产成人| 中国黄色一级视频| 成人夜色视频网站在线观看| 四虎影院一区二区三区| caoporn视频在线观看| 在线精品视频免费播放| 逼特逼视频在线观看| 日韩精品欧美| 97人人做人人爱| 国产毛片在线视频| 久久久午夜精品理论片中文字幕| 欧美日韩午夜爽爽| 国产91欧美| 亚洲精品少妇网址| 福利所第一导航| 日韩av不卡一区二区| 国产亚洲欧美一区二区三区| 欧美96在线| 91国偷自产一区二区三区观看 | 久久久av一区| 国产一级片av| 99视频有精品| 三级在线免费观看| 激情久久99| 亚洲人成电影网站| 国产精品500部| 国产精品一区在线| 一区二区不卡在线视频 午夜欧美不卡' | 99综合久久| 亚洲午夜国产成人av电影男同| 久久综合久久鬼| 国产在线视频一区二区| 午夜精品一区二区在线观看| 625成人欧美午夜电影| 精品日韩99亚洲| 国产成人久久久久| 久久国产精品无码网站| 婷婷五月色综合| 网友自拍亚洲| 亚洲欧洲激情在线| 日批视频免费在线观看| 2021国产精品久久精品| 亚洲中文字幕无码中文字| 九九热hot精品视频在线播放| 欧美精品在线免费观看| 在线观看免费视频一区| 国产精品区一区二区三区| 国产成人久久777777| 日本成人7777| 2019最新中文字幕| 视频污在线观看| 午夜精品久久久久久久99樱桃| 91精品人妻一区二区三区蜜桃2 | 欧美成人免费视频| av 一区二区三区| 亚洲九九爱视频| 亚洲精品一二三四| 国产精品观看| 国产欧美亚洲日本| av资源中文在线天堂| 亚洲精品在线91| 亚洲欧美日韩激情| 欧美高清在线一区二区| 亚洲黄色小视频在线观看| 91欧美在线| 亚洲综合中文字幕在线| 激情网站在线| 亚洲精品一区在线观看香蕉| 97人妻精品视频一区| 国产精品久久久久久久久图文区| 久久婷婷中文字幕| 亚洲无吗在线| 欧美日韩一区在线观看视频| 国产精品天堂蜜av在线播放| 久久伊人色综合| 国产 日韩 欧美 综合| 福利精品视频在线| 丁香六月激情综合| 国产成人av电影| 九一国产精品视频| 波多野结衣的一区二区三区| 91久久久精品| 欧美少妇网站| 色视频www在线播放国产成人| 午夜精品小视频| 欧美日韩中文字幕在线| 欧美性生交大片| 成人免费高清视频| 91国产精品视频在线观看| 综合天堂av久久久久久久| 国产精品久久久久久久久婷婷| a日韩av网址| 久久天堂电影网| 亚洲 国产 欧美 日韩| 欧美日韩国产一级片| 91精品国产高潮对白| 国产欧美日韩中文久久| 又黄又爽又色的视频| 午夜在线播放视频欧美| 国产成人三级视频| 伊人久久大香线蕉无限次| 91情侣偷在线精品国产| 国产精欧美一区二区三区蓝颜男同| 久久精品国产亚洲一区二区| 四虎在线观看| 欧美成人猛片aaaaaaa| av片免费观看| 亚州成人在线电影| 久久高清内射无套| 久久久久青草大香线综合精品| 男插女视频网站| 日韩精品一二区| 青青草成人免费在线视频| 欧美大人香蕉在线| 欧美一区1区三区3区公司| 88久久精品| 成人美女av在线直播| 桃花岛tv亚洲品质| 国内精品久久久久伊人av | 自拍偷拍国产亚洲| 国产激情在线免费观看| 成人黄色在线网站| 日本中文字幕在线不卡| 毛片一区二区三区| 亚洲乱码国产一区三区| 一本久道久久综合狠狠爱| 免费的一级黄色片| 天天影视天天精品| 一本一本久久a久久精品综合妖精| 日本国产精品| 国产色综合一区二区三区| 色悠久久久久综合先锋影音下载| 国产欧美va欧美va香蕉在| 超碰这里只有精品| 国产成人精品最新| gay欧美网站| 日韩美女在线看| 在线看片福利| 欧美尤物巨大精品爽| 依依综合在线| 国产精品69久久久久| 国产综合色区在线观看| 国产成人精品日本亚洲| 日韩影片中文字幕| 日本成人激情视频| 日韩一区二区三区在线免费观看 | 亚洲综合色婷婷在线观看| 亚洲精品日韩av| 精品国产三级| 51国产成人精品午夜福中文下载| 九九99久久精品在免费线bt| 亚洲综合色激情五月| 综合中文字幕| 久久精品国产99精品国产亚洲性色| 欧美大胆视频| 欧美在线日韩精品| 欧美日韩一区二区三区视频播放| 日韩欧美一区二区在线观看| 日韩激情在线| 男同互操gay射视频在线看| 欧美精品国产一区二区| 国产欧美精品aaaaaa片| 99国产精品久久久久久久| 水蜜桃色314在线观看| 久久裸体视频| 久久久久久久久久一区二区| 国产一区激情在线| 亚洲精品久久一区二区三区777 | 欧美男男tv网站在线播放| 日本欧美爱爱爱| 久久婷婷五月综合色丁香| 91中文字幕在线观看| jizz性欧美2| 免费av一区二区三区| 精品日韩免费| 无码人妻精品一区二区蜜桃百度| 在线日韩电影| 婷婷激情四射五月天| 国产成人自拍在线| 国产精品无码毛片| 欧美激情中文字幕| 青娱乐免费在线视频| 色综合久久88色综合天天| 国产老女人乱淫免费| 亚洲精品99久久久久| 自拍视频在线网| 97视频在线观看免费| a∨色狠狠一区二区三区| 99视频在线| 欧美老女人另类| 久久久久久av无码免费网站下载| 国产日韩1区| 国产精品999.| 久久九九全国免费| 久久久久久久9999| 91久久精品一区二区二区| 性猛交xxxx乱大交孕妇印度| 亚洲欧美日韩直播| 日本在线观看高清完整版| 国产成人精品a视频一区www| 2023国产精华国产精品| 五月天亚洲综合情| 99在线|亚洲一区二区| 久久精品亚洲天堂| 久久久三级国产网站| 国产无遮挡免费视频| 欧美精品欧美精品系列| 男女污视频在线观看| 欧美激情精品久久久久| 日韩精品一页| 日本午夜一区二区三区| 亚洲精品乱码| 91福利视频免费观看| 国产精品久久久久永久免费观看| 国产一级做a爱片久久毛片a| 日韩三级精品电影久久久| 岛国在线视频免费看| 97在线视频免费看| 三级欧美日韩| 男女h黄动漫啪啪无遮挡软件| 日本欧洲一区二区| 一级性生活大片| 五月婷婷另类国产| 成人午夜视频一区二区播放| 久久国产视频网站| 四虎永久精品在线| 亚洲亚洲精品三区日韩精品在线视频| 毛片一区二区| 亚洲午夜福利在线观看| 亚洲国产日韩一区二区| 国产99视频在线| 久久天天躁日日躁| www一区二区三区| 中文有码久久| 久久99精品国产麻豆婷婷洗澡| 免费看裸体网站| 在线观看一区不卡| 国产裸舞福利在线视频合集| 国产91久久婷婷一区二区| 日韩中出av| 国产精品50p| 99r国产精品| 天堂网中文字幕| 亚洲黄色av女优在线观看| 九九色在线视频| 国产传媒一区二区| 亚洲激情网址| 欧美熟妇精品黑人巨大一二三区| 精品久久久久久久中文字幕| 熟妇人妻中文av无码| 68精品国产免费久久久久久婷婷| 欧美成人一区在线观看| av动漫在线观看| 国产人成一区二区三区影院| 这里只有久久精品视频| 日日狠狠久久偷偷四色综合免费 | 精品国产sm最大网站免费看| 香蕉成人app免费看片| 国产精品亚洲综合| 一区二区三区四区五区在线 | 搡的我好爽在线观看免费视频| 自拍偷拍欧美激情| 欧性猛交ⅹxxx乱大交| 欧美一区二区大胆人体摄影专业网站| 亚洲性视频大全| 国产又猛又黄的视频| 自拍偷拍亚洲激情| 刘玥91精选国产在线观看| 日韩av不卡电影| 91中文字幕精品永久在线| 91人妻一区二区三区| 亚洲第一搞黄网站| 精品欧美不卡一区二区在线观看| 国产精品99免视看9| 婷婷亚洲综合| 男男做爰猛烈叫床爽爽小说| 色中色一区二区| bt在线麻豆视频| 久久国产精品精品国产色婷婷| 日韩精品一二三| 久久久久久久久97| 亚洲欧洲在线免费| 成人影院网站ww555久久精品| 国产aaa免费视频| 欧美激情一区不卡| 免费看国产片在线观看| 国产成人福利网站| 中文无码久久精品| 成人性生交大免费看| 91精品国产综合久久久久久久久久 | av老司机久久| 欧洲成人免费aa| 亚洲一区 二区 三区| 国产熟妇搡bbbb搡bbbb| 91精品国产综合久久久蜜臀图片| 涩涩视频在线| 欧美一级黄色录像片| 久久嫩草精品久久久精品一| 99在线精品视频免费观看20| 国产成人一区二区三区小说| 欧美日韩中文| 老司机福利在线观看| 亚洲第一男人av|