什么是lgtm.com(lgtm厉害在哪里)

  • lgtm.com是一个将代码转化为快照关系数据库平台(可以理解类SQL语句上进行扩展UDF),它可以运行一系列优化查询分析,从而发现语法错误、安全漏洞。lgtm.com处理软件开发项目的内容,其源代码存储在公共Git存储库中,该存储库托管在Bitbucket Cloud、GitHub、GitLab。目前已经包含数千个开源项目,并且可以自主添加若干代码。

  • 目前lgtm.com支持以下语言:

    • C and C++ (currently in beta testing)

    • Java

    • JavaScript/TypeScript

    • Python

  • 从官网可以看到,目前lgtm被google、microsoft、nasa等使用。

  • lgtm.com支持Eclipse、Idea插件拓展。

典型简单用例

基本语法描述

查询结果中包含的信息由select声明控制。

  1. from /* ... variable declarations ... */
  2. where /* ... logical formulas ... */
  3. select /* ... expressions ... */

返回字符串“lgtm”的长度

  1. select "lgtm".length()

该where子句通过将参数限制p为未访问的参数来查找未使用的参数。

  1. import java
  2. from Parameter p
  3. where not exists(p.getAnAccess())
  4. select p

参考:https://lgtm.com/query/2098670762/

class类语法,常用来简化使逻辑更清晰

QL中的类表示逻辑属性:当值满足该属性时,它是该类的成员。这意味着一个值可以在许多类中 - 一个成员可能在多个类之中。

  1. import java
  2. class OneTwoThree extends int {
  3. OneTwoThree() { /* (1) */
  4. this = 1 or this = 2 or this = 3
  5. }
  6. }
  7. from OneTwoThree ott
  8. select ott

类的特征谓词(Predicates):可以理解成函数

谓词southern(p)采用单个参数p并检查是否p满足属性p.getLocation() = "south"。返回的通常是boolean.可以用结果定义谓词。在这种情况下,关键字predicate被替换为结果的类型。

  1. class OneTwoThree extends int {
  2. OneTwoThree() { /* characteristic predicate (1) */
  3. this = 1 or this = 2 or this = 3
  4. }
  5. string stringify() { /* member predicate (2) */
  6. result = "One, two or three: " + this.toString()
  7. }
  8. predicate even() { /* member predicate (3) */
  9. this = 2
  10. }
  11. }
  12. /** ... class OneTwoThree as above ... */
  13. from OneTwoThree ott
  14. select ott.stringify().length()

子类和继承类(Subtyping and inheritance of classes)

在QL中,定义一个不扩展任何其他类型的类是非法的,尽管它足以扩展一个基本类型。
子类就是设置包含 - 表示子类的集合是表示每个超类的集合的子集。

  1. class OneTwo extends OneTwoThree {
  2. OneTwo() {
  3. this = 1 or this = 2
  4. }
  5. }

重写类(Overriding classes)

  1. class OneTwoThree extends int {
  2. OneTwoThree() {
  3. this = 1 or this = 2 or this = 3
  4. }
  5. string stringify() {
  6. result = "One, two or three: " + this.toString()
  7. }
  8. }
  9. class OneTwo extends OneTwoThree {
  10. OneTwo() {
  11. this = 1 or this = 2
  12. }
  13. string stringify() {
  14. result = "One or two: " + this.toString()
  15. }
  16. }

多继承(Multiple inheritance)

QL classes are allowed to extend more than one type. (集合结果)

  1. class Two extends OneTwo, TwoThree {
  2. string stringify() { result = "Two: " + this.toString() }
  3. }

这种多次应用相同操作(在这种情况下parentOf())的递归在QL中非常常见,并且被称为操作的传递闭包。有两个特殊符号+*在使用传递闭包时非常有用:

  • parentOf+(p)parentOf()谓词应用于p一次或多次。这相当于ancestorOf(p)

  • parentOf*(p)在适用parentOf()谓语p零次或多次,所以它会返回一个祖先pp本身。

试着用这个新的符号来定义一个谓词,relativeOf()并用它来列出国王的所有活着的亲属。

譬如:

以下是定义的一种方法relativeOf()

  1. Person relativeOf(Person p) {
  2. parentOf*(result) = parentOf*(p)
  3. }

类型约束方法(按类型缩小范围)

使用instanceof测试,例如:

  1. import java
  2. from Type t
  3. where t instanceof Class
  4. select t

使用cast测试,例如:

  1. import java
  2. from Type t
  3. where t.(Class).getASupertype().hasName("List")
  4. select t

使用exists将该变量设置为具有所需类型的另一个变量,例如:

  1. import java
  2. from Type t
  3. where exists(Class c |
  4. c = t
  5. and c.getASupertype().hasName("List")
  6. )
  7. select t

将变量传递给期望所需类型变量的谓词,例如:

  1. import java
  2. predicate derivedFromList(Class c) {
  3. c.getASupertype().hasName("List")
  4. }
  5. from Type t
  6. where derivedFromList(t)
  7. select t

查找@SuppressWarnings附加到构造函数的所有注释,并返回注释本身和其value元素的值

  1. import java
  2. from Constructor c, Annotation ann, AnnotationType anntp
  3. where ann = c.getAnAnnotation() and
  4. anntp = ann.getType() and
  5. anntp.hasQualifiedName("java.lang", "SuppressWarnings")
  6. select ann, ann.getValue("value")

Finds comments containing the word “TODO”

  1. import java
  2. from JavadocText c
  3. where c.getText().regexpMatch("(?si).*\\bTODO\\b.*")
  4. select c

标准语法及API

我们可以MainMethod从标准QL类派生一个子类,该类Method包含那些称为“main”的Java函数

  1. class MainMethod extends Method {
  2. MainMethod() {
  3. hasName("main")
  4. }
  5. }

cp(C)表示类的特性谓词C,它是明确表示:

  1. cp(MainMethod) = cp(Method) and hasName("main")

抽象类

caseswitch声明中对两种不同类型进行建模:case e具有表达式e 的形式的常量情况,以及不具有表达式e的默认情况default

  1. abstract class SwitchCase extends Stmt {
  2. }
  3. /** A constant case of a switch statement. */
  4. class ConstCase extends SwitchCase, @case {
  5. ConstCase() { exists(Expr e | e.getParent() = this) }
  6. ...
  7. }
  8. /** A default case of a switch statement. */
  9. class DefaultCase extends SwitchCase, @case {
  10. DefaultCase() { not exists(Expr e | e.getParent() = this) }
  11. ...
  12. }

递归的传递闭包

例如,我们可以通过书写来计算给定人的一组亲属 person.getAParent().getAChild() - 也就是说,我们将手中任何人的祖先的任何后代。

  1. class Person extends @person {
  2. /* ... */
  3. Person getAnAncestor() {
  4. result = this.getAParent() or
  5. result = this.getAnAncestor().getAParent()
  6. }
  7. }
  8. 相当于person.getAParent*()

例如,如果您使用有序聚合,选择最老的村民就会变得更简单。

  1. select max(Person p | | p order by p.getAge())

发现反序列化漏洞

  1. import java
  2. from MethodAccess call, Method readobject
  3. where
  4. call.getMethod() = readobject and
  5. readobject.hasName("readObject") and
  6. readobject.getDeclaringType().hasQualifiedName("java.io", "ObjectInputStream")
  7. select call

业务场景举例

从用户输入获取数据,并且查找流出到反序列化的案例

  1. import java
  2. import semmle.code.java.dataflow.DataFlow
  3. import semmle.code.java.dataflow.FlowSources
  4. import semmle.code.java.dataflow.TaintTracking
  5. import UnsafeDeserialization
  6. from RemoteUserInput source, UnsafeDeserializationSink sink
  7. where source.flowsTo(sink)
  8. select source, sink

从用户输入获取数据,并且查找流出到XSS的案例

  1. import java
  2. import semmle.code.java.dataflow.FlowSources
  3. import semmle.code.java.security.XSS
  4. from XssSink sink, RemoteUserInput source
  5. where source.flowsTo(sink)
  6. select sink, "Cross-site scripting vulnerability due to $@.",
  7. source, "user-provided value"

if冗余的语句执行代码的基本搜索

  1. import java
  2. from IfStmt ifstmt, Block block
  3. where ifstmt.getThen() = block
  4. and block.getNumStmt() = 0
  5. select ifstmt, "This 'if' statement is redundant."
  1. where ifstmt.getThen() = block and
  2. block.getNumStmt() = 0 and
  3. not exists(ifstmt.getElse())

查询查找程序中所有类型的变量int

  1. import java
  2. from Variable v, PrimitiveType pt
  3. where pt = v.getType() and
  4. pt.hasName("int")
  5. select v

exists引入一个临时变量c,是string类型,仅当t.getHaircolor()满足条件c至少一个时返回真.

  1. from Person t
  2. where exists(string c | t.getHairColor() = c)
  3. select t

查找所有名称与其编译单元名称不同的顶级类型

  1. import java
  2. from TopLevelType tl
  3. where tl.getName() != tl.getCompilationUnit().getName()
  4. select tl

查找所有直接扩展的嵌套类Object``

  1. import java
  2. from NestedClass nc
  3. where nc.getASupertype() instanceof TypeObject
  4. select nc

查找所有参数化的实例java.util.Map

  1. import java
  2. from GenericInterface map, ParameterizedType pt
  3. where map.hasQualifiedName("java.util", "Map") and
  4. pt.getSourceDeclaration() = map
  5. select pt

查找所有类型绑定的类型变量Number

  1. import java
  2. from TypeVariable tv, TypeBound tb
  3. where tb = tv.getATypeBound() and
  4. tb.getType().hasQualifiedName("java.lang", "Number")
  5. select tv

查找其父项为return语句的所有表达式

  1. import java
  2. from Expr e
  3. where e.getParent() instanceof ReturnStmt
  4. select e

查找注解

  1. import java
  2. from Constructor c, Annotation ann, AnnotationType anntp
  3. where ann = c.getAnAnnotation() and
  4. anntp = ann.getType() and
  5. anntp.hasQualifiedName("java.lang", "SuppressWarnings")
  6. select ann, ann.getValue("value")

引入一个QL类,用于表示警告列表@SuppressWarnings中的字符串deprecated出现的所有注释以进行压缩

  1. import java
  2. class SuppressDeprecationWarningAnnotation extends Annotation {
  3. SuppressDeprecationWarningAnnotation() {
  4. this.getType().hasQualifiedName("java.lang", "SuppressWarnings") and
  5. this.getAValue().(Literal).getLiteral().regexpMatch(".*deprecation.*")
  6. }
  7. }
  8. // Insert the class definitions from above
  9. from Call call
  10. where call.getCallee() instanceof DeprecatedMethod
  11. and not call.getCaller() instanceof DeprecatedMethod
  12. and not call.getCaller().getAnAnnotation() instanceof SuppressDeprecationWarningAnnotation
  13. select call, "This call invokes a deprecated method."

Finds methods that override com.example.Class.baseMethod

  1. import java
  2. from Method override, Method base
  3. where base.hasName("baseMethod")
  4. and base.getDeclaringType().hasQualifiedName("com.example", "Class")
  5. and override.overrides+(base)
  6. select override

查找不被其他任何方法调用的方法

  1. import java
  2. from Callable callee
  3. where not exists(Callable caller | caller.polyCalls(callee)) and
  4. callee.getCompilationUnit().fromSource()
  5. select callee, "Not called."

确定变量的最具体类型

  1. import java
  2. from Expr e, Callable c
  3. where
  4. c.getDeclaringType().hasQualifiedName("my.namespace.name", "MyClass")
  5. and c.getName() = "c"
  6. and e.getEnclosingCallable() = c
  7. select e, e.getAQlClass()

识别类似文件的简单查询

  1. import java
  2. import external.CodeDuplication
  3. from File f, File other, int percent
  4. where similarFiles(f, other, percent)
  5. select f, "This file is similar to $@.", other, other.getBaseName()

常见问题

什么是特征谓词?

  1. cp(SwitchCase) = cp(Stmt) and (
  2. cp(@case) and exists(Expr e | e.getParent() = this)
  3. or
  4. cp(@case) and not exists(Expr e | e.getParent() = this)
  5. )

各种类限定方法的优劣?

折叠谓词的查询优化如何理解?

!=和not(=)之间的区别

  1. a() != b()

  2. not(a() = b())

  • 这是一个向量计算,上面两种并不是等价的。

  • 第一个表达式表示存在一对不同的值(不等式的每一边)。

  • 第二个表达式并不是说有一对相同的值- 也就是说,所有的值对都是不同的

QL中的单调聚集体

QL的运行过程

一共有哪些基本表(即最基本的可以from哪些地方)

JavaDoc的说明

类型和类层次结构

发现正则

什么是传递闭包

参考资料