什么是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 javafrom Parameter pwhere not exists(p.getAnAccess())select p
参考:https://lgtm.com/query/2098670762/
class类语法,常用来简化使逻辑更清晰
QL中的类表示逻辑属性:当值满足该属性时,它是该类的成员。这意味着一个值可以在许多类中 - 一个成员可能在多个类之中。
import javaclass OneTwoThree extends int {OneTwoThree() { /* (1) */this = 1 or this = 2 or this = 3}}from OneTwoThree ottselect 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 ottselect 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 javafrom Type twhere t instanceof Classselect t
使用cast测试,例如:
import javafrom Type twhere t.(Class).getASupertype().hasName("List")select t
使用exists将该变量设置为具有所需类型的另一个变量,例如:
import javafrom Type twhere exists(Class c |c = tand c.getASupertype().hasName("List"))select t
将变量传递给期望所需类型变量的谓词,例如:
import javapredicate derivedFromList(Class c) {c.getASupertype().hasName("List")}from Type twhere derivedFromList(t)select t
查找@SuppressWarnings附加到构造函数的所有注释,并返回注释本身和其value元素的值
import javafrom Constructor c, Annotation ann, AnnotationType anntpwhere ann = c.getAnAnnotation() andanntp = ann.getType() andanntp.hasQualifiedName("java.lang", "SuppressWarnings")select ann, ann.getValue("value")
Finds comments containing the word “TODO”
import javafrom JavadocText cwhere 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() orresult = this.getAnAncestor().getAParent()}}相当于person.getAParent*()
例如,如果您使用有序聚合,选择最老的村民就会变得更简单。
select max(Person p | | p order by p.getAge())
发现反序列化漏洞
import javafrom MethodAccess call, Method readobjectwherecall.getMethod() = readobject andreadobject.hasName("readObject") andreadobject.getDeclaringType().hasQualifiedName("java.io", "ObjectInputStream")select call
业务场景举例
从用户输入获取数据,并且查找流出到反序列化的案例
import javaimport semmle.code.java.dataflow.DataFlowimport semmle.code.java.dataflow.FlowSourcesimport semmle.code.java.dataflow.TaintTrackingimport UnsafeDeserializationfrom RemoteUserInput source, UnsafeDeserializationSink sinkwhere source.flowsTo(sink)select source, sink
从用户输入获取数据,并且查找流出到XSS的案例
import javaimport semmle.code.java.dataflow.FlowSourcesimport semmle.code.java.security.XSSfrom XssSink sink, RemoteUserInput sourcewhere source.flowsTo(sink)select sink, "Cross-site scripting vulnerability due to $@.",source, "user-provided value"
if冗余的语句执行代码的基本搜索
import javafrom IfStmt ifstmt, Block blockwhere ifstmt.getThen() = blockand block.getNumStmt() = 0select ifstmt, "This 'if' statement is redundant."
where ifstmt.getThen() = block andblock.getNumStmt() = 0 andnot exists(ifstmt.getElse())
查询查找程序中所有类型的变量int
import javafrom Variable v, PrimitiveType ptwhere pt = v.getType() andpt.hasName("int")select v
exists引入一个临时变量c,是string类型,仅当t.getHaircolor()满足条件c至少一个时返回真.
from Person twhere exists(string c | t.getHairColor() = c)select t
查找所有名称与其编译单元名称不同的顶级类型
import javafrom TopLevelType tlwhere tl.getName() != tl.getCompilationUnit().getName()select tl
查找所有直接扩展的嵌套类Object``
import javafrom NestedClass ncwhere nc.getASupertype() instanceof TypeObjectselect nc
查找所有参数化的实例java.util.Map
import javafrom GenericInterface map, ParameterizedType ptwhere map.hasQualifiedName("java.util", "Map") andpt.getSourceDeclaration() = mapselect pt
查找所有类型绑定的类型变量Number
import javafrom TypeVariable tv, TypeBound tbwhere tb = tv.getATypeBound() andtb.getType().hasQualifiedName("java.lang", "Number")select tv
查找其父项为return语句的所有表达式
import javafrom Expr ewhere e.getParent() instanceof ReturnStmtselect e
查找注解
import javafrom Constructor c, Annotation ann, AnnotationType anntpwhere ann = c.getAnAnnotation() andanntp = ann.getType() andanntp.hasQualifiedName("java.lang", "SuppressWarnings")select ann, ann.getValue("value")
引入一个QL类,用于表示警告列表@SuppressWarnings中的字符串deprecated出现的所有注释以进行压缩
import javaclass SuppressDeprecationWarningAnnotation extends Annotation {SuppressDeprecationWarningAnnotation() {this.getType().hasQualifiedName("java.lang", "SuppressWarnings") andthis.getAValue().(Literal).getLiteral().regexpMatch(".*deprecation.*")}}// Insert the class definitions from abovefrom Call callwhere call.getCallee() instanceof DeprecatedMethodand not call.getCaller() instanceof DeprecatedMethodand not call.getCaller().getAnAnnotation() instanceof SuppressDeprecationWarningAnnotationselect call, "This call invokes a deprecated method."
Finds methods that override com.example.Class.baseMethod
import javafrom Method override, Method basewhere base.hasName("baseMethod")and base.getDeclaringType().hasQualifiedName("com.example", "Class")and override.overrides+(base)select override
查找不被其他任何方法调用的方法
import javafrom Callable calleewhere not exists(Callable caller | caller.polyCalls(callee)) andcallee.getCompilationUnit().fromSource()select callee, "Not called."
确定变量的最具体类型
import javafrom Expr e, Callable cwherec.getDeclaringType().hasQualifiedName("my.namespace.name", "MyClass")and c.getName() = "c"and e.getEnclosingCallable() = cselect e, e.getAQlClass()
识别类似文件的简单查询
import javaimport external.CodeDuplicationfrom File f, File other, int percentwhere 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)orcp(@case) and not exists(Expr e | e.getParent() = this))
各种类限定方法的优劣?
折叠谓词的查询优化如何理解?
!=和not(=)之间的区别
a() != b()not(a() = b())
这是一个向量计算,上面两种并不是等价的。
第一个表达式表示存在一对不同的值(不等式的每一边)。
第二个表达式并不是说有一对相同的值- 也就是说,所有的值对都是不同的
