青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

loop_in_codes

低調(diào)做技術(shù)__歡迎移步我的獨(dú)立博客 codemaro.com 微博 kevinlynx

使用Clang實(shí)現(xiàn)C語言編程規(guī)范檢查

概述

Clang是LLVM編譯器工具集的前端部分,也就是涵蓋詞法分析、語法語義分析的部分。而LLVM是Apple在Mac OS上用于替代GCC工具集的編譯器軟件集合。Clang支持類C語言的語言,例如C、C++、Objective C。Clang的與眾不同在于其模塊化的設(shè)計,使其不僅實(shí)現(xiàn)編譯器前端部分,并且包裝成庫的形式提供給上層應(yīng)用。使用Clang可以做諸如語法高亮、語法檢查、編程規(guī)范檢查方面的工作,當(dāng)然也可以作為你自己的編譯器前端。

編程規(guī)范一般包含編碼格式和語義規(guī)范兩部分。編碼格式用于約定代碼的排版、符號命名等;而語義規(guī)范則用于約定諸如類型匹配、表達(dá)式復(fù)雜度等,例如不允許對常數(shù)做邏輯運(yùn)算、檢查變量使用前是否被賦值等。本文描述的主要是基于語義方面的檢查,其經(jīng)驗(yàn)來自于最近做的一個檢查工具,該工具實(shí)現(xiàn)了超過130條的規(guī)范。這份規(guī)范部分規(guī)則來自于MISRA C

編程模式

編譯器前端部分主要是輸出代碼對應(yīng)的抽象語法樹(AST)。Clang提供給上層的接口也主要是圍繞語法樹來做操作。通過google一些Clang的資料,你可能會如我當(dāng)初一樣對該如何正確地使用Clang心存疑惑。我最后使用的方式是基于RecursiveASTVisitor。這是一種類似回調(diào)的使用機(jī)制,通過提供特定語法樹節(jié)點(diǎn)的接口,Clang在遍歷語法樹的時候,在遇到該節(jié)點(diǎn)時,就會調(diào)用到上層代碼。不能說這是最好的方式,但起碼它可以工作。基于RecursiveASTVisitor使用Clang,程序主體框架大致為:

// 編寫你感興趣的語法樹節(jié)點(diǎn)訪問接口,例如該例子中提供了函數(shù)調(diào)用語句和goto語句的節(jié)點(diǎn)訪問接口
class MyASTVisitor : public RecursiveASTVisitor<MyASTVisitor> {
public:
    bool VisitCallExpr(CallExpr *expr);

    bool VisitGotoStmt(GotoStmt *stmt);
    ...
};

class MyASTConsumer : public ASTConsumer {
public:
    virtual bool HandleTopLevelDecl(DeclGroupRef DR) {
        for (DeclGroupRef::iterator b = DR.begin(), e = DR.end(); b != e; ++b) {
            Visitor.TraverseDecl(*b);
        }
        return true;
    } 
    
private:
    MyASTVisitor Visitor;
};

int main(int argc, char **argv) {
    CompilerInstance inst;
    Rewriter writer;
    inst.createFileManager();
    inst.createSourceManager(inst.getFileManager());
    inst.createPreprocessor();
    inst.createASTContext();
    writer.setSourceMgr(inst.getSourceManager(), inst.getLangOpts());
    ... // 其他初始化CompilerInstance的代碼
  
    const FileEntry *fileIn = fileMgr.getFile(argv[1]);
    sourceMgr.createMainFileID(fileIn);
    inst.getDiagnosticClient().BeginSourceFile(inst.getLangOpts(), &inst.getPreprocessor());
    MyASTConsumer consumer(writer);
    ParseAST(inst.getPreprocessor(), &consumer, inst.getASTContext());
    inst.getDiagnosticClient().EndSourceFile();
    return 0;
}

以上代碼中,ParseAST為Clang開始分析代碼的主入口,其中提供了一個ASTConsumer。每次分析到一個頂層定義時(Top level decl)就會回調(diào)MyASTConsumer::HandleTopLevelDecl,該函數(shù)的實(shí)現(xiàn)里調(diào)用MyASTVisitor開始遞歸訪問該節(jié)點(diǎn)。這里的decl實(shí)際上包含定義。

這里使用Clang的方式來源于Basic source-to-source transformation with Clang

語法樹

Clang中視所有代碼單元為語句(statement),Clang中使用類Stmt來代表statement。Clang構(gòu)造出來的語法樹,其節(jié)點(diǎn)類型就是Stmt。針對不同類型的語句,Clang有對應(yīng)的Stmt子類,例如GotoStmt。Clang中的表達(dá)式也被視為語句,Clang使用Expr類來表示表達(dá)式,而Expr本身就派生于Stmt

每個語法樹節(jié)點(diǎn)都會有一個子節(jié)點(diǎn)列表,在Clang中一般可以使用如下語句遍歷一個節(jié)點(diǎn)的子節(jié)點(diǎn):

for (Stmt::child_iterator it = stmt->child_begin(); it != stmt->child_end(); ++it) {
    Stmt *child = *it;
}

但遺憾的是,無法從一個語法樹節(jié)點(diǎn)獲取其父節(jié)點(diǎn),這將給我們的規(guī)范檢測工具的實(shí)現(xiàn)帶來一些麻煩。

TraverseXXXStmt

在自己實(shí)現(xiàn)的Visitor中(例如MyASTVisitor),除了可以提供VisitXXXStmt系列接口去訪問某類型的語法樹節(jié)點(diǎn)外,還可以提供TraverseXXXStmt系列接口。Traverse系列的接口包裝對應(yīng)的Visit接口,即他們的關(guān)系大致為:

bool TraverseGotoStmt(GotoStmt *s) {
    VisitGotoStmt(s);
    return true;
}

例如對于GotoStmt節(jié)點(diǎn)而言,Clang會先調(diào)用TraverseGotoStmt,在TraverseGotoStmt的實(shí)現(xiàn)中才會調(diào)用VisitGotoStmt。利用Traverse和Visit之間的調(diào)用關(guān)系,我們可以解決一些因?yàn)椴荒茉L問某節(jié)點(diǎn)父節(jié)點(diǎn)而出現(xiàn)的問題。例如,我們需要限制逗號表達(dá)式的使用,在任何地方一旦檢測到逗號表達(dá)式的出現(xiàn),都給予警告,除非這個逗號表達(dá)式出現(xiàn)在for語句中,例如:

a = (a = 1, b = 2); /* 違反規(guī)范,非法 */
for (a = 1, b = 2; a < 2; ++a) /* 合法 */

逗號表達(dá)式對應(yīng)的訪問接口為VisitBinComma,所以我們只需要提供該接口的實(shí)現(xiàn)即可:

class MyASTVisitor : public RecursiveASTVisitor<MyASTVisitor> {
public:
    ...
    bool VisitBinComma(BinaryOperator *stmt) {
        /* 報告錯誤 */
        return true;
    }
    ...
};

(注:BinaryOperator用于表示二目運(yùn)算表達(dá)式,例如a + b,逗號表達(dá)式也是二目表達(dá)式)

但在循環(huán)中出現(xiàn)的逗號表達(dá)式也會調(diào)用到VisitBinComma。為了有效區(qū)分該逗號表達(dá)式是否出現(xiàn)在for語句中,我們可以期望獲取該逗號表達(dá)式的父節(jié)點(diǎn),并檢查該父節(jié)點(diǎn)是否為for語句。但Clang并沒有提供這樣的能力,我想很大一部分原因在于臆測語法樹(抽象語法樹)節(jié)點(diǎn)的組織結(jié)構(gòu)(父節(jié)點(diǎn)、兄弟節(jié)點(diǎn))本身就不是一個確定的事。

這里的解決辦法是通過提供TraverseForStmt,以在進(jìn)入for語句前得到一個標(biāo)識:

class MyASTVisitor : public RecursiveASTVisitor<MyASTVisitor> {
public:
    ...
    // 這個函數(shù)的實(shí)現(xiàn)可以參考RecursiveASTVisitor的默認(rèn)實(shí)現(xiàn),我們唯一要做的就是在for語句的頭那設(shè)定一個標(biāo)志m_inForLine
    bool TraverseForStmt(ForStmt *s) {
        if (!WalkUpFromForStmt(s))
            return false;
        m_inForLine = true;
        for (Stmt::child_range range = s->children(); range; ++range) {
            if (*range == s->getBody())
                m_inForLine = false;
            TraverseStmt(*range);
        }
        return true;
    }

    bool VisitBinComma(BinaryOperator *stmt) {
        if (!m_inForLine) {
            /* 報告錯誤 */
        }
        return true;
    }
    ...
};

(注:嚴(yán)格來說,我們必須檢查逗號表達(dá)式是出現(xiàn)在for語句的頭中,而不包括for語句循環(huán)體)

類型信息

對于表達(dá)式(Expr)而言,都有一個類型信息。Clang直接用于表示類型的類是QualType,實(shí)際上這個類只是一個接口包裝。這些類型信息可以用于很多類型相關(guān)的編程規(guī)范檢查。例如不允許定義超過2級的指針(例如int ***p):

bool MyASTVisitor::VisitVarDecl(VarDecl *decl) { // 當(dāng)發(fā)現(xiàn)變量定義時該接口被調(diào)用
    QualType t = decl->getType(); // 取得該變量的類型
    int pdepth = 0;
    // check pointer level
    for ( ; t->isPointerType(); t = t->getPointeeType()) { // 如果是指針類型就獲取其指向類型(PointeeType)
        ++pdepth;
    }
    if (pdepth >= 3)
        /* 報告錯誤 */
}

可以直接調(diào)用Expr::getType接口,用于獲取指定表達(dá)式最終的類型,基于此我們可以檢查復(fù)雜表達(dá)式中的類型轉(zhuǎn)換,例如:

float f = 2.0f;
double d = 1.0;
f = d * f; /* 檢查此表達(dá)式 */

對以上表達(dá)式的檢查有很多方法,你可以實(shí)現(xiàn)MyASTVisitor::VisitBinaryOperator(只要是二目運(yùn)算符都會調(diào)用),或者M(jìn)yASTVisitor::VisitBinAssign(賦值運(yùn)算=調(diào)用)。無論哪種方式,我們都可以提供一個遞歸檢查兩個表達(dá)式類型是否相同的接口:

bool HasDiffType(BinaryOperator *stmt) {
    Expr *lhs = stmt->getLHS()->IgnoreImpCasts(); // 忽略隱式轉(zhuǎn)換
    Expr *rhs = stmt->getRHS()->IgnoreImpCasts();
    if (lhs->getType() == rhs->getType())) {
        if (isa<BinaryOperator>(lhs) && HasDiffType(cast<BinaryOperator>(lhs)))
            return true;
        if (isa<BinaryOperator>(rhs) && HasDiffType(cast<BinaryOperator>(rhs)))
            return true;
        return false;
    }
    return true;
}

(注:此函數(shù)只是簡單實(shí)現(xiàn),未考慮類型修飾符之類的問題)

該函數(shù)獲得二目運(yùn)算表達(dá)式的兩個子表達(dá)式,然后遞歸檢測這兩個表達(dá)式的類型是否相同。

Expr類提供了更多方便的類型相關(guān)的接口,例如判定該表達(dá)式是否為常數(shù),是否是布爾表達(dá)式,甚至在某些情況下可以直接計算得到值。例如我們可以檢查明顯的死循環(huán):

while (1) { }

可以使用:

ASTContext &context = inst.GetASTContext();
bool result;
// 假設(shè)stmt為WhileStmt
if (stmt->getCond()->EvaluateAsBooleanCondition(result, context)) {
    if (result) 
        /* 死循環(huán) */

符號表

符號表這個概念比較廣義,這里我僅指的是用于保存類型和變量信息的表。Clang中沒有顯示的符號表數(shù)據(jù)結(jié)構(gòu),但每一個定義都有一個DeclContextDeclContext用于描述一個定義的上下文環(huán)境。有一個特殊的DeclContext被稱為translation unit decl,其實(shí)也就是全局環(huán)境。利用這個translation unit decl,我們可以獲取一些全局符號,例如全局變量、全局類型:

// 獲取全局作用域里指定名字的符號列表
DeclContext::lookup_result GetGlobalDecl(const std::string &name) {
    ASTContext &context = CompilerInst::getSingleton().GetASTContext();
    DeclContext *tcxt = context.getTranslationUnitDecl();
    IdentifierInfo &id = context.Idents.get(name);
    return tcxt->lookup(DeclarationName(&id));
}

// 可以根據(jù)GetGlobalDecl的返回結(jié)果,檢查該列表里是否有特定的定義,例如函數(shù)定義、類型定義等
bool HasSpecDecl(DeclContext::lookup_result ret, Decl::Kind kind) {
    for (size_t i = 0; i < ret.size(); ++i) {
        NamedDecl *decl = ret[i];
        if (decl->getKind() == kind) {
            return true;
        }
    }
    return false;
}

有了以上兩個函數(shù),我們要檢測全局作用域里是否有名為”var”的變量定義,就可以:

HasSpecDecl(GetGlobalDecl("var"), Decl::Var);

每一個Decl都有對應(yīng)的DeclContext,要檢查相同作用域是否包含相同名字的符號,其處理方式和全局的方式有點(diǎn)不一樣:

// 檢查在ctx中是否有與decl同名的符號定義
bool HasSymbolInContext(const NamedDecl *decl, const DeclContext *ctx) {
    for (DeclContext::decl_iterator it = ctx->decls_begin(); it != ctx->decls_end(); ++it) {
        Decl *d = *it;
        if (d != decl && isa<NamedDecl>(d) && 
            cast<NamedDecl>(d)->getNameAsString() == decl->getNameAsString())
            return true;
    }
    return false;
}

bool HasSymbolInContext(const NamedDecl *decl) {
    return HasSymbolInContext(decl, decl->getDeclContext());
}

可以看出,這里檢查相同作用域的方式是遍歷上下文環(huán)境中的所有符號,但對于全局作用域卻是直接查找。對于DeclContext的詳細(xì)信息我也不甚明了,只能算湊合使用。實(shí)際上,這里使用“作用域”一詞并不準(zhǔn)確,在C語言中的作用域概念,和這里的context概念在Clang中并非等同。

如果要檢查嵌套作用域里不能定義相同名字的變量,例如:

int var;
{
    int var;
}

通過Clang現(xiàn)有的API是無法實(shí)現(xiàn)的。因?yàn)镃lang給上層的語法樹結(jié)構(gòu)中,并不包含作用域信息(在Clang的實(shí)現(xiàn)中,用于語義分析的類Sema實(shí)際上有作用域的處理)。當(dāng)然,為了實(shí)現(xiàn)這個檢測,我們可以手動構(gòu)建作用域信息(通過TraverseCompoundStmt)。

宏的處理屬于預(yù)處理階段,并不涵蓋在語法分析階段,所以通過Clang的語法樹相關(guān)接口是無法處理的。跟宏相關(guān)的接口,都是通過Clang的Preprocessor相關(guān)接口。Clang為此提供了相應(yīng)的處理機(jī)制,上層需要往Preprocessor對象中添加回調(diào)對象,例如:

class MyPPCallback : public PPCallbacks {
public:
    // 處理#include
    virtual void InclusionDirective(SourceLocation HashLoc, const Token &IncludeTok,
        StringRef FileName, bool IsAngled, CharSourceRange FilenameRange,
        const FileEntry *File, StringRef SearchPath, StringRef RelativePath, const Module *Imported) {
    }

    // 處理#define
    virtual void MacroDefined(const Token &MacroNameTok, const MacroInfo *MI) {
    }

    virtual void MacroUndefined(const Token &MacroNameTok, const MacroInfo *MI) {
    } 
}

inst.getPreprocessor().addPPCallbacks(new MyPPCallback());

即,通過實(shí)現(xiàn)PPCallbacks中對應(yīng)的接口,就可以獲得處理宏的通知。

Clang使用MacroInfo去表示一個宏。MacroInfo將宏體以一堆token來保存,例如我們要檢測宏體中使用###的情況,則只能遍歷這些tokens:

// 分別記錄#和##在宏體中使用的數(shù)量
int hash = 0, hashhash = 0;
for (MacroInfo::tokens_iterator it = MI->tokens_begin(); it != MI->tokens_end(); ++it) {
    const Token &token = *it;
    hash += (token.getKind() == tok::hash ? 1 : 0);
    hashhash += (token.getKind() == tok::hashhash ? 1 : 0);
}

其他

在我們所支持的編程規(guī)范中,有些規(guī)范是難以支持的,因此我使用了一些蹩腳的方式來實(shí)現(xiàn)。

手工解析

在針對函數(shù)的參數(shù)定義方面,我們支持的規(guī)范要求不能定義參數(shù)為空的函數(shù),如果該函數(shù)沒有參數(shù),則必須以void顯示標(biāo)識,例如:

int func(); /* 非法 */
int func(void); /* 合法 */

對于Clang而言,函數(shù)定義(或聲明)使用的是FunctionDecl,而Clang記錄的信息僅包括該函數(shù)是否有參數(shù),參數(shù)個數(shù)是多少,并不記錄當(dāng)其參數(shù)個數(shù)為0時是否使用void來聲明(記錄下來沒多大意義)。解決這個問題的辦法,可以通過SourceLocation獲取到對應(yīng)源代碼中的文本內(nèi)容,然后對此文本內(nèi)容做手工分析即可。

(注:SourceLocation是Clang中用于表示源代碼位置的類,包括行號和列號,所有Stmt都會包含此信息)

通過SourceLocation獲取對應(yīng)源碼的內(nèi)容:

std::pair<FileID, unsigned> locInfo = SM.getDecomposedLoc(loc);
bool invalidTemp = false;
llvm::StringRef file = SM.getBufferData(locInfo.first, &invalidTemp);
if (invalidTemp)
    return false;
// tokenBegin即為loc對應(yīng)源碼內(nèi)容的起始點(diǎn)
const char *tokenBegin = file.data() + locInfo.second;

要手工分析這些內(nèi)容實(shí)際上還是有點(diǎn)繁雜,為此我們可以直接使用Clang中詞法分析相關(guān)的組件來完成這件事:

Lexer *lexer = new Lexer(SM.getLocForStartOfFile(locInfo.first), opts, file.begin(), tokenBegin, file.end());
Token tok;
lexer->Lex(tok); // 取得第一個tok,反復(fù)調(diào)用可以獲取一段token流

Diagnostic

Clang中用Diagnostic來進(jìn)行編譯錯誤的提示。每一個編譯錯誤(警告、建議等)都會有一段文字描述,這些文字描述為了支持多國語言,使用了一種ID的表示方法。總之,對于一個特定的編譯錯誤提示而言,其diagnostic ID是固定的。

在我們的規(guī)范中,有些規(guī)范檢測的代碼在Clang中會直接編譯出錯,例如函數(shù)調(diào)用傳遞的參數(shù)個數(shù)不等于函數(shù)定義時的形參個數(shù)。當(dāng)Clang編譯出錯時,其語法樹實(shí)際上是不完善的。解決此問題的最簡單辦法,就是通過diagnostic實(shí)現(xiàn)。也就是說,我是通過將我們的特定規(guī)范映射到特定的diagnostic,當(dāng)發(fā)生這個特定的編譯錯誤時,就可以認(rèn)定該規(guī)范實(shí)際上被檢測到。對于簡單的情況而言,這樣的手段還算奏效。

// `TextDiagnosticPrinter`可以將錯誤信息打印在控制臺上,為了調(diào)試方便我從它派生而來
class MyDiagnosticConsumer : public TextDiagnosticPrinter {
public:
    // 當(dāng)一個錯誤發(fā)生時,會調(diào)用此函數(shù),我會在這個函數(shù)里通過Info.getID()取得Diagnostic ID,然后對應(yīng)地取出規(guī)范ID
    virtual void HandleDiagnostic(DiagnosticsEngine::Level DiagLevel,
        const Diagnostic &Info) {
        TextDiagnosticPrinter::HandleDiagnostic(DiagLevel, Info);
        // 例如檢查三字母詞(trigraph)的使用
        if (Info.getID() == 816)
            /* 報告使用了三字母詞 */
    }
};

// 初始化時需傳入自己定義的diagnostic
inst.createDiagnostics(0, NULL, new MyDiagnosticConsumer(&inst.getDiagnosticOpts()));

該例子代碼演示了對三字母詞(wiki trigraph)使用限制的規(guī)范檢測。

全文完。

posted on 2013-02-12 21:53 Kevin Lynx 閱讀(10264) 評論(0)  編輯 收藏 引用 所屬分類: c/c++

青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            精品白丝av| 欧美激情1区| 亚洲激情精品| 欧美成人一区二区| 老司机aⅴ在线精品导航| 久久久噜噜噜久久人人看| 久久激情五月丁香伊人| 久久久久久亚洲精品杨幂换脸| 久久久久久久999| 欧美成人免费小视频| 欧美大片国产精品| 亚洲毛片一区| 欧美永久精品| 女人香蕉久久**毛片精品| 欧美日韩一区二区三区视频 | 欧美一区二区视频97| 玖玖在线精品| 欧美四级电影网站| 国内精品久久久久伊人av| 最新亚洲激情| 欧美一区二区视频观看视频| 蜜桃久久av| 中国成人在线视频| 欧美成人精品一区二区| 国产免费亚洲高清| 亚洲人成网站777色婷婷| 亚洲性感激情| 亚洲成色精品| 亚洲天堂av高清| 久久久久女教师免费一区| 亚洲精品欧美激情| 久久亚洲欧洲| 国产精品午夜春色av| 亚洲黄色片网站| 久久精品亚洲精品| 一本高清dvd不卡在线观看| 久久久蜜桃一区二区人| 国产精品视频一二| 一区二区三区四区五区在线| 欧美成人精品一区二区| 午夜亚洲伦理| 国产精品久久国产三级国电话系列| 18成人免费观看视频| 久久久91精品| 亚洲一区二区少妇| 欧美日韩一二三区| 亚洲美女区一区| 你懂的国产精品| 久久精品人人做人人爽电影蜜月| 国产精品普通话对白| 一区二区欧美激情| 亚洲国产日韩欧美一区二区三区| 久久久久在线观看| 国产一区二区三区高清播放| 欧美一区二区三区视频免费| 一区二区不卡在线视频 午夜欧美不卡在 | 久久夜色精品一区| 午夜精品婷婷| 国产伦精品一区二区三区四区免费| 一本久久综合亚洲鲁鲁| 亚洲人成人一区二区三区| 欧美jizzhd精品欧美巨大免费| 在线观看亚洲视频| 欧美电影在线免费观看网站| 老司机免费视频一区二区三区| 亚洲盗摄视频| 91久久精品网| 国产精品99免费看 | 亚洲激情网址| 日韩午夜在线观看视频| 欧美成人国产va精品日本一级| 久久婷婷蜜乳一本欲蜜臀| 在线精品高清中文字幕| 欧美成人一区二免费视频软件| 久久这里有精品15一区二区三区| 亚洲第一精品夜夜躁人人爽| 亚洲欧洲视频在线| 国产精品久久福利| 久久免费视频一区| 久久琪琪电影院| 亚洲伦理一区| 亚洲一区二区三区久久| 国内精品国产成人| 欧美激情一区二区在线| 欧美精品三级日韩久久| 亚洲欧美成人一区二区三区| 欧美一区二区三区四区夜夜大片 | 亚洲国产毛片完整版| 亚洲人成网站777色婷婷| 欧美日韩综合一区| 久久精品91久久久久久再现| 久久久久成人精品| 夜夜精品视频| 欧美一区二区在线视频| 亚洲精品久久久久久一区二区| 一区二区三区|亚洲午夜| 国产一二精品视频| 亚洲日本国产| 激情丁香综合| 日韩网站在线观看| 激情久久中文字幕| 一区二区不卡在线视频 午夜欧美不卡在| 国产精品视频男人的天堂| 欧美α欧美αv大片| 国产精品v欧美精品v日本精品动漫 | 欧美呦呦网站| 正在播放亚洲一区| 久久久久综合网| 午夜久久久久| 欧美日韩国产一区二区| 免费欧美日韩| 国内精品久久久| 亚洲一区免费在线观看| 一本久久知道综合久久| 久久综合给合久久狠狠色| 久久aⅴ乱码一区二区三区| 欧美片在线观看| 欧美国产大片| 在线成人亚洲| 久久激情久久| 久久国产精品亚洲77777| 国产精品99一区二区| 亚洲日本精品国产第一区| 亚洲国产精品成人精品| 久久精品99无色码中文字幕| 欧美一区三区二区在线观看| 国产精品扒开腿做爽爽爽软件| 亚洲黑丝一区二区| 欧美极品在线播放| 免费视频一区| 在线成人黄色| 久久午夜激情| 你懂的国产精品永久在线| 激情五月***国产精品| 欧美一区二区在线免费播放| 久久精品国亚洲| 狠狠色丁香久久综合频道 | 亚洲成色999久久网站| 精品动漫3d一区二区三区| 欧美一区二区三区久久精品| 久久精品国产第一区二区三区| 国产精品日日摸夜夜摸av| 亚洲综合日本| 久久精品国产精品亚洲| 韩国av一区| 免费黄网站欧美| 亚洲欧洲日韩女同| 亚洲网站在线观看| 国产精品久久久久久影视| 亚洲摸下面视频| 久久先锋资源| 亚洲精品在线观| 国产精品国产a| 欧美亚洲一级| 欧美国产日韩a欧美在线观看| 亚洲日本欧美日韩高观看| 欧美日韩另类字幕中文| 亚洲视频免费观看| 久久人人97超碰国产公开结果| 尤物在线观看一区| 欧美精品亚洲一区二区在线播放| 一二美女精品欧洲| 久久精品视频免费| 亚洲国产婷婷| 国产精品入口尤物| 久久青青草综合| 亚洲人成网站色ww在线| 午夜精品影院| 亚洲国产精品一区二区三区| 欧美日韩免费观看一区二区三区 | 最新精品在线| 国产精品视频成人| 美女视频网站黄色亚洲| 亚洲精品午夜精品| 久久国产精品99国产| 亚洲国产日韩欧美综合久久 | 亚洲另类在线一区| 国产日产高清欧美一区二区三区| 理论片一区二区在线| 亚洲深夜av| 亚洲国产成人tv| 久久av资源网| 一本久久综合| 亚洲高清视频在线| 国产日韩精品一区观看| 欧美日韩国产综合网| 久久久久久久综合日本| 亚洲一区二区三区中文字幕| 亚洲国产精品悠悠久久琪琪| 久久精品官网| 亚洲欧美99| 亚洲少妇诱惑| 欧美三级黄美女| 老司机午夜精品| 校园春色国产精品| 亚洲午夜激情在线| 日韩午夜av| 亚洲欧洲视频在线| 亚洲国产mv| 亚洲国产老妈|