什么是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
声明控制。
from /* ... variable declarations ... */
where /* ... logical formulas ... */
select /* ... expressions ... */
返回字符串“lgtm”的长度
select "lgtm".length()
该where子句通过将参数限制p
为未访问的参数来查找未使用的参数。
import java
from Parameter p
where not exists(p.getAnAccess())
select p
参考:https://lgtm.com/query/2098670762/
class类语法,常用来简化使逻辑更清晰
QL中的类表示逻辑属性:当值满足该属性时,它是该类的成员。这意味着一个值可以在许多类中 - 一个成员可能在多个类之中。
import java
class OneTwoThree extends int {
OneTwoThree() { /* (1) */
this = 1 or this = 2 or this = 3
}
}
from OneTwoThree ott
select ott
类的特征谓词(Predicates):可以理解成函数
谓词southern(p)
采用单个参数p
并检查是否p
满足属性p.getLocation() = "south"
。返回的通常是boolean.可以用结果定义谓词。在这种情况下,关键字predicate
被替换为结果的类型。
class OneTwoThree extends int {
OneTwoThree() { /* characteristic predicate (1) */
this = 1 or this = 2 or this = 3
}
string stringify() { /* member predicate (2) */
result = "One, two or three: " + this.toString()
}
predicate even() { /* member predicate (3) */
this = 2
}
}
/** ... class OneTwoThree as above ... */
from OneTwoThree ott
select ott.stringify().length()
子类和继承类(Subtyping and inheritance of classes)
在QL中,定义一个不扩展任何其他类型的类是非法的,尽管它足以扩展一个基本类型。
子类就是设置包含 - 表示子类的集合是表示每个超类的集合的子集。
class OneTwo extends OneTwoThree {
OneTwo() {
this = 1 or this = 2
}
}
重写类(Overriding classes)
class OneTwoThree extends int {
OneTwoThree() {
this = 1 or this = 2 or this = 3
}
string stringify() {
result = "One, two or three: " + this.toString()
}
}
class OneTwo extends OneTwoThree {
OneTwo() {
this = 1 or this = 2
}
string stringify() {
result = "One or two: " + this.toString()
}
}
多继承(Multiple inheritance)
QL classes are allowed to extend more than one type. (集合结果)
class Two extends OneTwo, TwoThree {
string stringify() { result = "Two: " + this.toString() }
}
这种多次应用相同操作(在这种情况下parentOf()
)的递归在QL中非常常见,并且被称为操作的传递闭包。有两个特殊符号+
,*
在使用传递闭包时非常有用:
parentOf+(p)
将parentOf()
谓词应用于p
一次或多次。这相当于ancestorOf(p)
。parentOf*(p)
在适用parentOf()
谓语p
零次或多次,所以它会返回一个祖先p
或p
本身。
试着用这个新的符号来定义一个谓词,relativeOf()
并用它来列出国王的所有活着的亲属。
譬如:
以下是定义的一种方法relativeOf()
:
Person relativeOf(Person p) {
parentOf*(result) = parentOf*(p)
}
类型约束方法(按类型缩小范围)
使用instanceof
测试,例如:
import java
from Type t
where t instanceof Class
select t
使用cast测试,例如:
import java
from Type t
where t.(Class).getASupertype().hasName("List")
select t
使用exists将该变量设置为具有所需类型的另一个变量,例如:
import java
from Type t
where exists(Class c |
c = t
and c.getASupertype().hasName("List")
)
select t
将变量传递给期望所需类型变量的谓词,例如:
import java
predicate derivedFromList(Class c) {
c.getASupertype().hasName("List")
}
from Type t
where derivedFromList(t)
select t
查找@SuppressWarnings
附加到构造函数的所有注释,并返回注释本身和其value
元素的值
import java
from Constructor c, Annotation ann, AnnotationType anntp
where ann = c.getAnAnnotation() and
anntp = ann.getType() and
anntp.hasQualifiedName("java.lang", "SuppressWarnings")
select ann, ann.getValue("value")
Finds comments containing the word “TODO”
import java
from JavadocText c
where c.getText().regexpMatch("(?si).*\\bTODO\\b.*")
select c
标准语法及API
我们可以MainMethod
从标准QL类派生一个子类,该类Method
包含那些称为“main”的Java函数
class MainMethod extends Method {
MainMethod() {
hasName("main")
}
}
让cp(C)
表示类的特性谓词C
,它是明确表示:
cp(MainMethod) = cp(Method) and hasName("main")
抽象类
它case
在switch
声明中对两种不同类型进行建模:case e
具有表达式e 的形式的常量情况,以及不具有表达式e的默认情况default
。
abstract class SwitchCase extends Stmt {
}
/** A constant case of a switch statement. */
class ConstCase extends SwitchCase, @case {
ConstCase() { exists(Expr e | e.getParent() = this) }
...
}
/** A default case of a switch statement. */
class DefaultCase extends SwitchCase, @case {
DefaultCase() { not exists(Expr e | e.getParent() = this) }
...
}
递归的传递闭包
例如,我们可以通过书写来计算给定人的一组亲属 person.getAParent().getAChild() - 也就是说,我们将手中任何人的祖先的任何后代。
class Person extends @person {
/* ... */
Person getAnAncestor() {
result = this.getAParent() or
result = this.getAnAncestor().getAParent()
}
}
相当于person.getAParent*()
例如,如果您使用有序聚合,选择最老的村民就会变得更简单。
select max(Person p | | p order by p.getAge())
发现反序列化漏洞
import java
from MethodAccess call, Method readobject
where
call.getMethod() = readobject and
readobject.hasName("readObject") and
readobject.getDeclaringType().hasQualifiedName("java.io", "ObjectInputStream")
select call
业务场景举例
从用户输入获取数据,并且查找流出到反序列化的案例
import java
import semmle.code.java.dataflow.DataFlow
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.dataflow.TaintTracking
import UnsafeDeserialization
from RemoteUserInput source, UnsafeDeserializationSink sink
where source.flowsTo(sink)
select source, sink
从用户输入获取数据,并且查找流出到XSS的案例
import java
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.security.XSS
from XssSink sink, RemoteUserInput source
where source.flowsTo(sink)
select sink, "Cross-site scripting vulnerability due to $@.",
source, "user-provided value"
if
冗余的语句执行代码的基本搜索
import java
from IfStmt ifstmt, Block block
where ifstmt.getThen() = block
and block.getNumStmt() = 0
select ifstmt, "This 'if' statement is redundant."
where ifstmt.getThen() = block and
block.getNumStmt() = 0 and
not exists(ifstmt.getElse())
查询查找程序中所有类型的变量int
import java
from Variable v, PrimitiveType pt
where pt = v.getType() and
pt.hasName("int")
select v
exists引入一个临时变量c,是string类型,仅当t.getHaircolor()满足条件c至少一个时返回真.
from Person t
where exists(string c | t.getHairColor() = c)
select t
查找所有名称与其编译单元名称不同的顶级类型
import java
from TopLevelType tl
where tl.getName() != tl.getCompilationUnit().getName()
select tl
查找所有直接扩展的嵌套类Object``
import java
from NestedClass nc
where nc.getASupertype() instanceof TypeObject
select nc
查找所有参数化的实例java.util.Map
import java
from GenericInterface map, ParameterizedType pt
where map.hasQualifiedName("java.util", "Map") and
pt.getSourceDeclaration() = map
select pt
查找所有类型绑定的类型变量Number
import java
from TypeVariable tv, TypeBound tb
where tb = tv.getATypeBound() and
tb.getType().hasQualifiedName("java.lang", "Number")
select tv
查找其父项为return
语句的所有表达式
import java
from Expr e
where e.getParent() instanceof ReturnStmt
select e
查找注解
import java
from Constructor c, Annotation ann, AnnotationType anntp
where ann = c.getAnAnnotation() and
anntp = ann.getType() and
anntp.hasQualifiedName("java.lang", "SuppressWarnings")
select ann, ann.getValue("value")
引入一个QL类,用于表示警告列表@SuppressWarnings
中的字符串deprecated
出现的所有注释以进行压缩
import java
class SuppressDeprecationWarningAnnotation extends Annotation {
SuppressDeprecationWarningAnnotation() {
this.getType().hasQualifiedName("java.lang", "SuppressWarnings") and
this.getAValue().(Literal).getLiteral().regexpMatch(".*deprecation.*")
}
}
// Insert the class definitions from above
from Call call
where call.getCallee() instanceof DeprecatedMethod
and not call.getCaller() instanceof DeprecatedMethod
and not call.getCaller().getAnAnnotation() instanceof SuppressDeprecationWarningAnnotation
select call, "This call invokes a deprecated method."
Finds methods that override com.example.Class.baseMethod
import java
from Method override, Method base
where base.hasName("baseMethod")
and base.getDeclaringType().hasQualifiedName("com.example", "Class")
and override.overrides+(base)
select override
查找不被其他任何方法调用的方法
import java
from Callable callee
where not exists(Callable caller | caller.polyCalls(callee)) and
callee.getCompilationUnit().fromSource()
select callee, "Not called."
确定变量的最具体类型
import java
from Expr e, Callable c
where
c.getDeclaringType().hasQualifiedName("my.namespace.name", "MyClass")
and c.getName() = "c"
and e.getEnclosingCallable() = c
select e, e.getAQlClass()
识别类似文件的简单查询
import java
import external.CodeDuplication
from File f, File other, int percent
where similarFiles(f, other, percent)
select f, "This file is similar to $@.", other, other.getBaseName()
常见问题
什么是特征谓词?
cp(SwitchCase) = cp(Stmt) and (
cp(@case) and exists(Expr e | e.getParent() = this)
or
cp(@case) and not exists(Expr e | e.getParent() = this)
)
各种类限定方法的优劣?
折叠谓词的查询优化如何理解?
!=和not(=)之间的区别
a() != b()
not(a() = b())
这是一个向量计算,上面两种并不是等价的。
第一个表达式表示存在一对不同的值(不等式的每一边)。
第二个表达式并不是说有一对相同的值- 也就是说,所有的值对都是不同的