基于LLVM开发Clang插件进行代码风格检查

这个很多人都做过,文章也挺多的,我也是参考别人文章的,不过直到真正实现还是踩了许多坑,所以记录下来,或许对其他人有帮助。其实LLVM和Clang我还没有好好研究过,之前大部分都是用Swift开发,代码风格检查都是用的Swiftlint,所以这次选择OC的代码检查作为开始,通过实践找找感觉和兴趣,之后再一点一点精进。

下载源码

代开终端

sudo mkdir llvm
sudo chown `whoami` llvm
cd llvm
export LLVM_HOME=`pwd`

git clone -b release_60 https://github.com/llvm-mirror/llvm.git llvm  
git clone -b release_60 https://github.com/llvm-mirror/clang.git llvm/tools/clang  
git clone -b release_60 https://github.com/llvm-mirror/clang-tools-extra.git llvm/tools/clang/tools/extra  
git clone -b release_60 https://github.com/llvm-mirror/compiler-rt.git llvm/projects/compiler-rt

下载源码会比较慢,另外网上有其他文章,有的文章比较老,版本也比较老,建议用最新的release_60

安装cmake

如果没有安装cmake需要安装一下,后面需要用。

brew update
brew install cmake

开始编写插件

cd到llvm/llvm/tools/clang/examples

1.打开这个目录下的CMakeLists.txt文件,然后添加add_subdirectory(CodeChecker)

Screen Shot 2019-05-20 at 10.40.00 PM.png

2.在当前目录创建新的文件夹CodeChecker,并cd到CodeChecker

mkdir CodeChecker
cd CodeChecker

3.在新建的CodeChecker目录下创建三个文件

touch  CMakeLists.txt
touch  CodeChecker.cpp
touch  CodeChecker.exports
Screen Shot 2019-05-20 at 10.59.22 PM.png

在新创建的CMakeLists.txt中添加

if( NOT MSVC ) # MSVC mangles symbols differently
  if( NOT LLVM_REQUIRES_RTTI )
    if( NOT LLVM_REQUIRES_EH )
      set(LLVM_EXPORTED_SYMBOL_FILE ${CMAKE_CURRENT_SOURCE_DIR}/CodeChecker.exports)
    endif()
  endif()
endif()

add_llvm_loadable_module(CodeChecker CodeChecker.cpp)

if(LLVM_ENABLE_PLUGINS AND (WIN32 OR CYGWIN))
  target_link_libraries(CodeChecker ${cmake_2_8_12_PRIVATE}
    clangAST
    clangBasic
    clangFrontend
    LLVMSupport
    )
endif()

CodeChecker.cpp文件中加入

#include <iostream>
#include <stdio.h>
#include <string>
#include <fstream>
#include <sstream>
#include <algorithm>
#include <functional>
#include <vector>
#include "clang/Frontend/FrontendPluginRegistry.h"
#include "clang/Rewrite/Core/Rewriter.h"
#include "clang/AST/AST.h"
#include "clang/AST/ASTConsumer.h"
#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Sema/Sema.h"

using namespace clang;
using namespace std;

namespace
{
    static vector<string> split(const string &s, char delim)
    {
        vector<string> elems;
        stringstream ss;
        ss.str(s);
        string item;
        while (getline(ss, item, delim)) {
            elems.push_back(item);
        }
        return elems;
    }

    class CodeVisitor : public RecursiveASTVisitor<CodeVisitor>
    {
    private:
        CompilerInstance &Instance;
        ASTContext *Context;

    public:

        void setASTContext (ASTContext &context)
        {
            this -> Context = &context;
        }

    private:

        /**
         判断是否为用户源码

         @param decl 声明
         @return true 为用户源码,false 非用户源码
         */
        bool isUserSourceCode (Decl *decl)
        {
            string filename = Instance.getSourceManager().getFilename(decl->getSourceRange().getBegin()).str();

            if (filename.empty())
                return false;

            //非XCode中的源码都认为是用户源码
            if(filename.find("/Applications/Xcode.app/") == 0)
                return false;

            return true;
        }

        /**
         检测类名是否存在小写开头

         @param decl 类声明
         */
        void checkClassNameForLowercaseName(ObjCInterfaceDecl *decl)
        {
            StringRef className = decl -> getName();

            //类名称必须以大写字母开头
            char c = className[0];
            if (isLowercase(c))
            {
                //修正提示
                std::string tempName = className;
                tempName[0] = toUppercase(c);
                StringRef replacement(tempName);
                SourceLocation nameStart = decl->getLocation();
                SourceLocation nameEnd = nameStart.getLocWithOffset(className.size() - 1);
                FixItHint fixItHint = FixItHint::CreateReplacement(SourceRange(nameStart, nameEnd), replacement);

                //报告警告
                DiagnosticsEngine &D = Instance.getDiagnostics();
                int diagID = D.getCustomDiagID(DiagnosticsEngine::Error, "Class name should not start with lowercase letter");
                SourceLocation location = decl->getLocation();
                D.Report(location, diagID).AddFixItHint(fixItHint);
            }
        }

        /**
         检测类名是否包含下划线

         @param decl 类声明
         */
        void checkClassNameForUnderscoreInName(ObjCInterfaceDecl *decl)
        {
            StringRef className = decl -> getName();

            //类名不能包含下划线
            size_t underscorePos = className.find('_');
            if (underscorePos != StringRef::npos)
            {
                //修正提示
                std::string tempName = className;
                std::string::iterator end_pos = std::remove(tempName.begin(), tempName.end(), '_');
                tempName.erase(end_pos, tempName.end());
                StringRef replacement(tempName);
                SourceLocation nameStart = decl->getLocation();
                SourceLocation nameEnd = nameStart.getLocWithOffset(className.size() - 1);
                FixItHint fixItHint = FixItHint::CreateReplacement(SourceRange(nameStart, nameEnd), replacement);

                //报告错误
                DiagnosticsEngine &diagEngine = Instance.getDiagnostics();
                unsigned diagID = diagEngine.getCustomDiagID(DiagnosticsEngine::Error, "Class name with `_` forbidden");
                SourceLocation location = decl->getLocation().getLocWithOffset(underscorePos);
                diagEngine.Report(location, diagID).AddFixItHint(fixItHint);
            }
        }


        /**
         检测方法名是否存在大写开头

         @param decl 方法声明
         */
        void checkMethodNameForUppercaseName(ObjCMethodDecl *decl)
        {
            //检查名称的每部分,都不允许以大写字母开头
            Selector sel = decl -> getSelector();
            int selectorPartCount = decl -> getNumSelectorLocs();
            for (int i = 0; i < selectorPartCount; i++)
            {
                StringRef selName = sel.getNameForSlot(i);
                char c = selName[0];
                if (isUppercase(c))
                {
                    //修正提示
                    std::string tempName = selName;
                    tempName[0] = toLowercase(c);
                    StringRef replacement(tempName);
                    SourceLocation nameStart = decl -> getSelectorLoc(i);
                    SourceLocation nameEnd = nameStart.getLocWithOffset(selName.size() - 1);
                    FixItHint fixItHint = FixItHint::CreateReplacement(SourceRange(nameStart, nameEnd), replacement);

                    //报告警告
                    DiagnosticsEngine &D = Instance.getDiagnostics();
                    int diagID = D.getCustomDiagID(DiagnosticsEngine::Error, "Selector name should not start with uppercase letter");
                    SourceLocation location = decl->getLocation();
                    D.Report(location, diagID).AddFixItHint(fixItHint);
                }
            }
        }

        /**
         检测方法中定义的参数名称是否存在大写开头

         @param decl 方法声明
         */
        void checkMethodParamsNameForUppercaseName(ObjCMethodDecl *decl)
        {
            for (ObjCMethodDecl::param_iterator it = decl -> param_begin(); it != decl -> param_end(); it++)
            {
                ParmVarDecl *parmVarDecl = *it;
                StringRef name = parmVarDecl -> getName();
                char c = name[0];
                if (isUppercase(c))
                {
                    //修正提示
                    std::string tempName = name;
                    tempName[0] = toLowercase(c);
                    StringRef replacement(tempName);
                    SourceLocation nameStart = parmVarDecl -> getLocation();
                    SourceLocation nameEnd = nameStart.getLocWithOffset(name.size() - 1);
                    FixItHint fixItHint = FixItHint::CreateReplacement(SourceRange(nameStart, nameEnd), replacement);

                    //报告警告
                    DiagnosticsEngine &D = Instance.getDiagnostics();
                    int diagID = D.getCustomDiagID(DiagnosticsEngine::Warning, "Selector's param name should not start with uppercase letter");
                    SourceLocation location = decl->getLocation();
                    D.Report(location, diagID).AddFixItHint(fixItHint);
                }
            }
        }

        /**
         检测方法实现是否超过500行代码

         @param decl 方法声明
         */
        void checkMethodBodyForOver500Lines(ObjCMethodDecl *decl)
        {
            if (decl -> hasBody())
            {
                //存在方法体
                Stmt *methodBody = decl -> getBody();

                string srcCode;
                srcCode.assign(Instance.getSourceManager().getCharacterData(methodBody->getSourceRange().getBegin()),
                               methodBody->getSourceRange().getEnd().getRawEncoding() - methodBody->getSourceRange().getBegin().getRawEncoding() + 1);
                vector<string> lines = split(srcCode, '\n');
                if(lines.size() > 500)
                {
                    DiagnosticsEngine &D = Instance.getDiagnostics();
                    unsigned diagID = D.getCustomDiagID(DiagnosticsEngine::Warning, "Single method should not have body over 500 lines");
                    D.Report(decl -> getSourceRange().getBegin(), diagID);
                }
            }
        }

        /**
         检测属性名是否存在大写开头

         @param decl 属性声明
         */
        void checkPropertyNameForUppercaseName(ObjCPropertyDecl *decl)
        {
            bool checkUppercaseNameIndex = 0;

            StringRef name = decl -> getName();

            if (name.find('_') == 0)
            {
                //表示以下划线开头
                checkUppercaseNameIndex = 1;
            }

            //名称必须以小写字母开头
            char c = name[checkUppercaseNameIndex];
            if (isUppercase(c))
            {
                //修正提示
                std::string tempName = name;
                tempName[checkUppercaseNameIndex] = toLowercase(c);
                StringRef replacement(tempName);
                SourceLocation nameStart = decl->getLocation();
                SourceLocation nameEnd = nameStart.getLocWithOffset(name.size() - 1);
                FixItHint fixItHint = FixItHint::CreateReplacement(SourceRange(nameStart, nameEnd), replacement);

                //报告错误
                DiagnosticsEngine &D = Instance.getDiagnostics();
                int diagID = D.getCustomDiagID(DiagnosticsEngine::Error, "Property name should not start with uppercase letter");
                SourceLocation location = decl->getLocation();
                D.Report(location, diagID).AddFixItHint(fixItHint);
            }
        }

        /**
         检测属性名是否包含下划线

         @param decl 属性声明
         */
        void checkPropertyNameForUnderscoreInName(ObjCPropertyDecl *decl)
        {
            StringRef name = decl -> getName();

            if (name.size() == 1)
            {
                //不需要检测
                return;
            }

            //类名不能包含下划线
            size_t underscorePos = name.find('_', 1);
            if (underscorePos != StringRef::npos)
            {
                //修正提示
                std::string tempName = name;
                std::string::iterator end_pos = std::remove(tempName.begin() + 1, tempName.end(), '_');
                tempName.erase(end_pos, tempName.end());
                StringRef replacement(tempName);
                SourceLocation nameStart = decl->getLocation();
                SourceLocation nameEnd = nameStart.getLocWithOffset(name.size() - 1);
                FixItHint fixItHint = FixItHint::CreateReplacement(SourceRange(nameStart, nameEnd), replacement);

                //报告错误
                DiagnosticsEngine &diagEngine = Instance.getDiagnostics();
                unsigned diagID = diagEngine.getCustomDiagID(DiagnosticsEngine::Error, "Property name with `_` forbidden");
                SourceLocation location = decl->getLocation().getLocWithOffset(underscorePos);
                diagEngine.Report(location, diagID).AddFixItHint(fixItHint);
            }
        }


        /**
         检测委托属性是否有使用weak修饰

         @param decl 属性声明
         */
        void checkDelegatePropertyForUsageWeak (ObjCPropertyDecl *decl)
        {
            QualType type = decl -> getType();
            StringRef typeStr = type.getAsString();

            //Delegate
            if(typeStr.find("<") != string::npos && typeStr.find(">") != string::npos)
            {
                ObjCPropertyDecl::PropertyAttributeKind attrKind = decl -> getPropertyAttributes();

                string typeSrcCode;
                typeSrcCode.assign(Instance.getSourceManager().getCharacterData(decl -> getSourceRange().getBegin()),
                                   decl -> getSourceRange().getEnd().getRawEncoding() - decl -> getSourceRange().getBegin().getRawEncoding());

                if(!(attrKind & ObjCPropertyDecl::OBJC_PR_weak))
                {
                    DiagnosticsEngine &diagEngine = Instance.getDiagnostics();
                    unsigned diagID = diagEngine.getCustomDiagID(DiagnosticsEngine::Warning, "Delegate should be declared as weak.");
                    diagEngine.Report(decl -> getLocation(), diagID);
                }
            }
        }


        /**
         检测常量名称是否存在小写开头

         @param decl 常量声明
         */
        void checkConstantNameForLowercaseName (VarDecl *decl)
        {
            StringRef className = decl -> getName();

            //类名称必须以大写字母开头
            char c = className[0];
            if (isLowercase(c))
            {
                //修正提示
                std::string tempName = className;
                tempName[0] = toUppercase(c);
                StringRef replacement(tempName);
                SourceLocation nameStart = decl->getLocation();
                SourceLocation nameEnd = nameStart.getLocWithOffset(className.size() - 1);
                FixItHint fixItHint = FixItHint::CreateReplacement(SourceRange(nameStart, nameEnd), replacement);

                //报告警告
                DiagnosticsEngine &D = Instance.getDiagnostics();
                int diagID = D.getCustomDiagID(DiagnosticsEngine::Warning, "Constant name should not start with lowercase letter");
                SourceLocation location = decl->getLocation();
                D.Report(location, diagID).AddFixItHint(fixItHint);
            }
        }


        /**
         检测变量名称是否存在大写开头

         @param decl 变量声明
         */
        void checkVarNameForUppercaseName (VarDecl *decl)
        {
            StringRef className = decl -> getName();

            //类名称必须以大写字母开头
            char c = className[0];
            if (isUppercase(c))
            {
                //修正提示
                std::string tempName = className;
                tempName[0] = toLowercase(c);
                StringRef replacement(tempName);
                SourceLocation nameStart = decl->getLocation();
                SourceLocation nameEnd = nameStart.getLocWithOffset(className.size() - 1);
                FixItHint fixItHint = FixItHint::CreateReplacement(SourceRange(nameStart, nameEnd), replacement);

                //报告警告
                DiagnosticsEngine &D = Instance.getDiagnostics();
                int diagID = D.getCustomDiagID(DiagnosticsEngine::Warning, "Variable name should not start with uppercase letter");
                SourceLocation location = decl->getLocation();
                D.Report(location, diagID).AddFixItHint(fixItHint);
            }
        }


        /**
         检测变量名称

         @param decl 变量声明
         */
        void checkVarName(VarDecl *decl)
        {
            if (decl -> isStaticLocal())
            {
                //静态变量

                if (decl -> getType().isConstant(*this -> Context))
                {
                    //常量
                    checkConstantNameForLowercaseName(decl);
                }
                else
                {
                    //非常量
                    checkVarNameForUppercaseName(decl);
                }

            }
            else if (decl -> isLocalVarDecl())
            {
                //本地变量
                if (decl -> getType().isConstant(*this -> Context))
                {
                    //常量
                    checkConstantNameForLowercaseName(decl);
                }
                else
                {
                    //非常量
                    checkVarNameForUppercaseName(decl);
                }
            }
            else if (decl -> isFileVarDecl())
            {
                //文件定义变量
                if (decl -> getType().isConstant(*this -> Context))
                {
                    //常量
                    checkConstantNameForLowercaseName(decl);
                }
                else
                {
                    //非常量
                    checkVarNameForUppercaseName(decl);
                }
            }
        }

    public:

        CodeVisitor (CompilerInstance &Instance)
        :Instance(Instance)
        {

        }

        /**
         观察ObjC的类声明

         @param declaration 声明对象
         @return 返回
         */
        bool VisitObjCInterfaceDecl(ObjCInterfaceDecl *declaration)
        {
            if (isUserSourceCode(declaration))
            {
                checkClassNameForLowercaseName(declaration);
                checkClassNameForUnderscoreInName(declaration);
            }

            return true;
        }


        /**
         观察类方法声明

         @param declaration 声明对象
         @return 返回
         */
        bool VisitObjCMethodDecl(ObjCMethodDecl *declaration)
        {
            if (isUserSourceCode(declaration))
            {
                checkMethodNameForUppercaseName(declaration);
                checkMethodParamsNameForUppercaseName(declaration);
                checkMethodBodyForOver500Lines(declaration);
            }

            return true;
        }


        /**
         观察类属性声明

         @param declaration 声明对象
         @return 返回
         */
        bool VisitObjCPropertyDecl(ObjCPropertyDecl *declaration)
        {
            if (isUserSourceCode(declaration))
            {
                checkPropertyNameForUppercaseName(declaration);
                checkPropertyNameForUnderscoreInName(declaration);
                checkDelegatePropertyForUsageWeak(declaration);
            }

            return true;
        }

        /**
         观察变量声明

         @param declaration 声明对象
         @return 返回
         */
        bool VisitVarDecl(VarDecl *declaration)
        {
            if (isUserSourceCode(declaration))
            {
                checkVarName(declaration);
            }

            return true;
        }

        /**
         观察枚举常量声明

         @param declaration 声明对象
         @return 返回
         */
        //        bool VisitEnumConstantDecl (EnumConstantDecl *declaration)
        //        {
        //            return true;
        //        }
    };

    class CodeConsumer : public ASTConsumer
    {
        CompilerInstance &Instance;
        std::set<std::string> ParsedTemplates;
    public:
        CodeConsumer(CompilerInstance &Instance,
                     std::set<std::string> ParsedTemplates)
        : Instance(Instance), ParsedTemplates(ParsedTemplates), visitor(Instance)
        {

        }

        bool HandleTopLevelDecl(DeclGroupRef DG) override
        {
            return true;
        }

        void HandleTranslationUnit(ASTContext& context) override
        {
            visitor.setASTContext(context);
            visitor.TraverseDecl(context.getTranslationUnitDecl());
        }

    private:
        CodeVisitor visitor;
    };

    class CodeASTAction : public PluginASTAction
    {
        std::set<std::string> ParsedTemplates;
    protected:
        std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI,
                                                       llvm::StringRef) override
        {
            return llvm::make_unique<CodeConsumer>(CI, ParsedTemplates);
        }

        bool ParseArgs(const CompilerInstance &CI,
                       const std::vector<std::string> &args) override
        {
            //            DiagnosticsEngine &D = CI.getDiagnostics();
            //            D.Report(D.getCustomDiagID(DiagnosticsEngine::Error,
            //                                       "My plugin Started..."));

            return true;
        }
    };
}

static clang::FrontendPluginRegistry::Add<CodeASTAction>
X("CodeChecker", "Code Checker");

使用cmake编译源代码

1.cd到llvm,注意不是llvm/llvm,执行

mkdir llvm_build && cd llvm_build
cmake -G Xcode ../llvm -DCMAKE_BUILD_TYPE:STRING=MinSizeRel

2.然后会在llvm_build文件目录中看到LLVM.xcodeproj,用xcode打开,选择Automatically Create Schemes

Screen Shot 2019-05-20 at 11.01.35 PM.png

3.编译 clang,CodeChecker,libclang

Screen Shot 2019-05-20 at 11.07.04 PM.png

Screen Shot 2019-05-20 at 11.04.24 PM.png

Screen Shot 2019-05-20 at 11.04.49 PM.png

4.在llvm_build/Debug/lib目录下可以找到我们的插件,如下图:

Screen Shot 2019-05-20 at 11.09.41 PM.png

Xcode集成Plugin

创建需要加载插件的项目,在Build Settings栏目中的OTHER_CFLAGS添加上如下内容:

-Xclang -load -Xclang (.dylib)动态库路径 -Xclang -add-plugin -Xclang 插件名字

我把插件拷贝的桌面了,所以我的是:

-Xclang -load -Xclang /Users/roy.cao/Desktop/CodeChecker.dylib -Xclang -add-plugin -Xclang CodeChecker

Screen Shot 2019-05-20 at 11.45.39 PM.png

然后你build项目,可能有unable to load plugin '/Users/roy.cao/Desktop/CodeChecker.dylib'的error,这是由于Clang插件需要对应的Clang版本来加载,如果版本不一致会导致编译错误。

Screen Shot 2019-05-20 at 11.28.02 PM.png

在Build Settings栏目中新增两项用户定义的设置,分别为CC和CXX
CC对应的是自己编译的clang的绝对路径,CXX对应的是自己编译的clang++的绝对路径

Screen Shot 2019-05-20 at 11.31.47 PM.png
/Users/roy.cao/llvm/llvm_build/Debug/bin/clang
/Users/roy.cao/llvm/llvm_build/Debug/bin/clang++

再build,会有以下错误:

Screen Shot 2019-05-20 at 11.37.39 PM.png

在Build Settings栏目中搜索index,将Enable Index-Wihle-Building Functionality的Default改为NO.


Screen Shot 2019-05-20 at 11.39.40 PM.png

再build可能会出现一大堆系统库的 symbol not found 错误

Screen Shot 2019-05-20 at 11.41.49 PM.png

这个时候需要在刚刚OTHER_CFLAGS的

-Xclang -load -Xclang /Users/roy.cao/Desktop/CodeChecker.dylib -Xclang -add-plugin -Xclang CodeChecker

后面再加上-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator12.0.sdk

注意iPhoneSimulator12.0.sdk,每个人的可能不同,需要到目录下看看自己的版本。
那么完整的就是下图这样:

Screen Shot 2019-05-20 at 11.54.47 PM.png

再build就能做代码风格检查啦,庆祝一下吧~~~~


Screen Shot 2019-05-20 at 11.52.35 PM.png

参考文章:
LLVM & Clang 入门
使用Xcode开发iOS语法检查的Clang插件
Clang 之旅--使用 Xcode 开发 Clang 插件

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容