Skip to content

Commit aaa3b36

Browse files
authored
Merge pull request #21929 from owen-mc/go/no-ret-functions
Go: Recognize more non-returning logging functions
2 parents 9618e9b + 9dbe9ad commit aaa3b36

19 files changed

Lines changed: 732 additions & 535 deletions

File tree

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
category: minorAnalysis
3+
---
4+
* More logging functions are now recognized as not returning or panicking.

go/ql/lib/semmle/go/Concepts.qll

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -413,17 +413,13 @@ private class ExternalLoggerCall extends LoggerCall::Range, DataFlow::CallNode {
413413
}
414414
}
415415

416-
/**
417-
* A call to an interface that looks like a logger. It is common to use a
418-
* locally-defined interface for logging to make it easy to changing logging
419-
* library.
420-
*/
421-
private class HeuristicLoggerCall extends LoggerCall::Range, DataFlow::CallNode {
422-
HeuristicLoggerCall() {
423-
exists(Method m, string tp, string logFunctionPrefix, string name |
424-
m = this.getTarget() and
425-
m.hasQualifiedName(_, tp, name) and
426-
m.getReceiverBaseType().getUnderlyingType() instanceof InterfaceType
416+
private class HeuristicLoggerFunction extends Method {
417+
string logFunctionPrefix;
418+
419+
HeuristicLoggerFunction() {
420+
exists(string tp, string name |
421+
this.hasQualifiedName(_, tp, name) and
422+
this.getReceiverBaseType().getUnderlyingType() instanceof InterfaceType
427423
|
428424
tp.regexpMatch(".*[lL]ogger") and
429425
logFunctionPrefix =
@@ -435,6 +431,19 @@ private class HeuristicLoggerCall extends LoggerCall::Range, DataFlow::CallNode
435431
)
436432
}
437433

434+
override predicate mayReturnNormally() { logFunctionPrefix != "Fatal" }
435+
436+
override predicate mustPanic() { logFunctionPrefix = "Panic" }
437+
}
438+
439+
/**
440+
* A call to an interface that looks like a logger. It is common to use a
441+
* locally-defined interface for logging to make it easy to change logging
442+
* library.
443+
*/
444+
private class HeuristicLoggerCall extends LoggerCall::Range, DataFlow::CallNode {
445+
HeuristicLoggerCall() { this.getTarget() instanceof HeuristicLoggerFunction }
446+
438447
override DataFlow::Node getAMessageComponent() { result = this.getASyntacticArgument() }
439448
}
440449

go/ql/lib/semmle/go/frameworks/Glog.qll

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,37 @@ import go
1212
* forks.
1313
*/
1414
module Glog {
15+
/** Gets a package name for `glog` or `klog` (which is a fork). */
16+
string packagePath() {
17+
result =
18+
package([
19+
"github.com/golang/glog", "gopkg.in/glog", "k8s.io/klog", "github.com/barakmich/glog"
20+
], "")
21+
}
22+
1523
private class GlogFunction extends Function {
1624
int firstPrintedArg;
25+
string format;
26+
string level;
1727

1828
GlogFunction() {
19-
exists(string pkg, string fn, string level |
20-
pkg = package(["github.com/golang/glog", "gopkg.in/glog", "k8s.io/klog"], "") and
29+
exists(string pkg, string context, int nContextArgs, string depth, int nDepthArgs, string fn |
30+
pkg = packagePath() and
2131
level = ["Error", "Exit", "Fatal", "Info", "Warning"] and
2232
(
23-
fn = level + ["", "f", "ln"] and firstPrintedArg = 0
33+
context = "" and nContextArgs = 0
34+
or
35+
context = "Context" and nContextArgs = 1
36+
) and
37+
(
38+
depth = "" and nDepthArgs = 0
2439
or
25-
fn = level + "Depth" and firstPrintedArg = 1
40+
depth = "Depth" and nDepthArgs = 1
41+
) and
42+
format = ["", "f", "ln"] and
43+
(
44+
fn = level + context + depth + format and
45+
firstPrintedArg = nContextArgs + nDepthArgs
2646
)
2747
|
2848
this.hasQualifiedName(pkg, fn)
@@ -35,10 +55,15 @@ module Glog {
3555
* Gets the index of the first argument that may be output, including a format string if one is present.
3656
*/
3757
int getFirstPrintedArg() { result = firstPrintedArg }
58+
59+
/** Holds if this function takes a format string. */
60+
predicate formatter() { format = "f" }
61+
62+
override predicate mayReturnNormally() { level != "Fatal" and level != "Exit" }
3863
}
3964

4065
private class StringFormatter extends StringOps::Formatting::Range instanceof GlogFunction {
41-
StringFormatter() { this.getName().matches("%f") }
66+
StringFormatter() { this.formatter() }
4267

4368
override int getFormatStringIndex() { result = super.getFirstPrintedArg() }
4469
}

go/ql/lib/semmle/go/frameworks/Logrus.qll

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,12 @@ module Logrus {
2828
this.(Method).hasQualifiedName(packagePath(), ["Entry", "Logger"], name)
2929
)
3030
}
31+
32+
override predicate mayReturnNormally() {
33+
not exists(string level, string suffix | level = ["Fatal", "Panic"] |
34+
this.getName() = level + suffix
35+
)
36+
}
3137
}
3238

3339
private class StringFormatters extends StringOps::Formatting::Range instanceof LogFunction {

go/ql/lib/semmle/go/frameworks/Zap.qll

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ module Zap {
4747
}
4848

4949
/** A Zap logging function which always panics. */
50-
private class FatalLogMethod extends Method {
50+
private class FatalLogMethod extends ZapFunction {
5151
FatalLogMethod() {
5252
this.hasQualifiedName(packagePath(), "Logger", "Fatal")
5353
or
@@ -58,7 +58,7 @@ module Zap {
5858
}
5959

6060
/** A Zap logging function which always panics. */
61-
private class MustPanicLogMethod extends Method {
61+
private class MustPanicLogMethod extends ZapFunction {
6262
MustPanicLogMethod() {
6363
this.hasQualifiedName(packagePath(), "Logger", "Panic")
6464
or

go/ql/lib/semmle/go/frameworks/stdlib/Log.qll

Lines changed: 21 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -29,18 +29,37 @@ module Log {
2929
}
3030

3131
private class LogFormatter extends StringOps::Formatting::Range instanceof LogFunction {
32-
LogFormatter() { this.getName() = ["Fatalf", "Panicf", "Printf"] }
32+
LogFormatter() { this.getName() = ["Fatalf", "Panicf", "Printf", "Panic", "Panicf", "Panicln"] }
3333

3434
override int getFormatStringIndex() { result = 0 }
3535
}
3636

3737
/** A fatal log function, which calls `os.Exit`. */
3838
private class FatalLogFunction extends Function {
39-
FatalLogFunction() { this.hasQualifiedName("log", ["Fatal", "Fatalf", "Fatalln"]) }
39+
FatalLogFunction() {
40+
exists(string fn | fn = ["Fatal", "Fatalf", "Fatalln"] |
41+
this.hasQualifiedName("log", fn)
42+
or
43+
this.(Method).hasQualifiedName("log", "Logger", fn)
44+
)
45+
}
4046

4147
override predicate mayReturnNormally() { none() }
4248
}
4349

50+
/** A log function which must panic. */
51+
private class PanicLogFunction extends Function {
52+
PanicLogFunction() {
53+
exists(string fn | fn = ["Panic", "Panicf", "Panicln"] |
54+
this.hasQualifiedName("log", fn)
55+
or
56+
this.(Method).hasQualifiedName("log", "Logger", fn)
57+
)
58+
}
59+
60+
override predicate mustPanic() { any() }
61+
}
62+
4463
// These models are not implemented using Models-as-Data because they represent reverse flow.
4564
private class FunctionModels extends TaintTracking::FunctionModel {
4665
FunctionInput inp;
@@ -63,30 +82,6 @@ module Log {
6382
FunctionOutput outp;
6483

6584
MethodModels() {
66-
// signature: func (*Logger) Fatal(v ...interface{})
67-
this.hasQualifiedName("log", "Logger", "Fatal") and
68-
(inp.isParameter(_) and outp.isReceiver())
69-
or
70-
// signature: func (*Logger) Fatalf(format string, v ...interface{})
71-
this.hasQualifiedName("log", "Logger", "Fatalf") and
72-
(inp.isParameter(_) and outp.isReceiver())
73-
or
74-
// signature: func (*Logger) Fatalln(v ...interface{})
75-
this.hasQualifiedName("log", "Logger", "Fatalln") and
76-
(inp.isParameter(_) and outp.isReceiver())
77-
or
78-
// signature: func (*Logger) Panic(v ...interface{})
79-
this.hasQualifiedName("log", "Logger", "Panic") and
80-
(inp.isParameter(_) and outp.isReceiver())
81-
or
82-
// signature: func (*Logger) Panicf(format string, v ...interface{})
83-
this.hasQualifiedName("log", "Logger", "Panicf") and
84-
(inp.isParameter(_) and outp.isReceiver())
85-
or
86-
// signature: func (*Logger) Panicln(v ...interface{})
87-
this.hasQualifiedName("log", "Logger", "Panicln") and
88-
(inp.isParameter(_) and outp.isReceiver())
89-
or
9085
// signature: func (*Logger) Print(v ...interface{})
9186
this.hasQualifiedName("log", "Logger", "Print") and
9287
(inp.isParameter(_) and outp.isReceiver())

0 commit comments

Comments
 (0)