Skip to content

Commit 624128f

Browse files
Merge pull request #158 from awslabs/cli-improvements
Report construction bug fix + terminal UI improvements
2 parents 437af6d + 18f569d commit 624128f

File tree

19 files changed

+254
-55
lines changed

19 files changed

+254
-55
lines changed

analysis/config/config.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -705,8 +705,8 @@ func (c Config) ExceedsMaxDepth(d int) bool {
705705

706706
// TargetInfo is the information needed to build the target
707707
type TargetInfo struct {
708-
// Files in the target
709-
Files []string
708+
// Patterns in the target
709+
Patterns []string
710710
// Platform of the target
711711
Platform string
712712
// UseProgramTransforms for the target
@@ -724,7 +724,7 @@ func (c Config) GetTargetMap() map[string]TargetInfo {
724724
reflectValueCallInstances = append(reflectValueCallInstances, compileRegexes(r))
725725
}
726726
targets[targetSpec.Name] = TargetInfo{
727-
Files: targetSpec.Files,
727+
Patterns: targetSpec.Files,
728728
Platform: targetSpec.Platform,
729729
UseProgramTransforms: targetSpec.UseProgramTransforms,
730730
ReflectValueCallInstances: reflectValueCallInstances,

analysis/config/logging.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
package config
1616

1717
import (
18+
"fmt"
1819
"io"
1920
"log"
2021
"os"
@@ -263,3 +264,23 @@ func (l *LogGroup) LogsDebug() bool {
263264
func (l *LogGroup) LogsTrace() bool {
264265
return l.Level >= TraceLevel
265266
}
267+
268+
// Infoboxf prints info-level logging in a box.
269+
func (l *LogGroup) Infoboxf(format string, v ...any) {
270+
// Example:
271+
// ╭───────────────────────────────╮
272+
// │ this is the message to print │
273+
// ╰───────────────────────────────╯
274+
contents := fmt.Sprintf(format, v...)
275+
contentsLines := strings.Split(contents, "\n")
276+
n := 0
277+
for _, line := range contentsLines {
278+
n = max(n, len(line))
279+
}
280+
bar := strings.Repeat("─", n+2)
281+
l.Info("╭" + bar + "╮\n")
282+
for _, content := range contentsLines {
283+
l.Info("│ " + content + " │\n")
284+
}
285+
l.Info("╰" + bar + "╯\n")
286+
}

analysis/config/report.go

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"sync"
2121

2222
"github.com/awslabs/ar-go-tools/internal/formatutil"
23+
"github.com/awslabs/ar-go-tools/internal/funcutil"
2324
)
2425

2526
// Errors contains errors generated during the different steps of the analysis, keyed by some string
@@ -84,11 +85,20 @@ func (r *ReportInfo) Merge(other *ReportInfo) {
8485
for sev, count := range other.CountBySeverity {
8586
r.IncrementSevCount(sev, count)
8687
}
87-
for tag, reportGroup := range other.Reports {
88-
if thisReportGroup, ok := r.Reports[tag]; ok {
89-
thisReportGroup.Details = append(thisReportGroup.Details, r.Reports[tag].Details...)
88+
for otherTag, otherReportGroup := range other.Reports {
89+
if thisReportGroup, ok := r.Reports[otherTag]; ok {
90+
newDetails := thisReportGroup.Details
91+
for _, otherDetail := range otherReportGroup.Details {
92+
if !funcutil.Contains(thisReportGroup.Details, otherDetail) {
93+
newDetails = append(newDetails, otherDetail)
94+
}
95+
}
96+
// Note: reports with same tags should have same severity always,
97+
// because the config enforces tag uniqueness, and 1 tag - 1 severity
98+
thisReportGroup.Details = newDetails
99+
r.Reports[otherTag] = thisReportGroup
90100
} else {
91-
r.Reports[tag] = reportGroup
101+
r.Reports[otherTag] = otherReportGroup
92102
}
93103
}
94104
}
@@ -112,7 +122,10 @@ func (r *ReportInfo) addEntry(tag string, entry ReportEntry) {
112122
return
113123
}
114124
group := r.Reports[tag]
115-
newDetails := append(group.Details, entry.ContentFile)
125+
newDetails := group.Details
126+
if !funcutil.Contains(group.Details, entry.ContentFile) {
127+
newDetails = append(group.Details, entry.ContentFile)
128+
}
116129
r.Reports[tag] = ReportGroup{
117130
Tool: entry.Tool,
118131
Severity: entry.Severity,

analysis/config/report_test.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package config
16+
17+
import (
18+
"testing"
19+
20+
"github.com/awslabs/ar-go-tools/internal/funcutil"
21+
)
22+
23+
func TestMerge(t *testing.T) {
24+
report1 := NewReport()
25+
report2 := NewReport()
26+
report1.addEntry("tag1", ReportEntry{
27+
Tool: SyntacticTool,
28+
ContentFile: "example.json",
29+
Severity: High,
30+
})
31+
report2.addEntry("tag1", ReportEntry{
32+
Tool: SyntacticTool,
33+
ContentFile: "ex2.json",
34+
Severity: High,
35+
})
36+
report2.addEntry("tag2", ReportEntry{
37+
Tool: SyntacticTool,
38+
ContentFile: "example3.json",
39+
Severity: Low,
40+
})
41+
report1.Merge(report2)
42+
if !funcutil.Contains(report1.Reports["tag1"].Details, "ex2.json") {
43+
t.Fatalf("report1 should contain report2's ex2.json file after merge in tag1")
44+
}
45+
}

analysis/dataflow/function_summary_graph_nodes.go

Lines changed: 54 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -161,43 +161,83 @@ func NodeSummary(g GraphNode) string {
161161
return ""
162162
}
163163

164+
// NodeTermLabel returns a string to be printed in terminals for quick identification of a dataflow node
165+
// in a trace.
166+
// The returned string prints as a 7-character long string in a terminal.
167+
func NodeTermLabel(g GraphNode) string {
168+
switch g.(type) {
169+
case *ParamNode:
170+
return formatutil.BgCyan("•PARAM ")
171+
case *CallNode:
172+
return formatutil.BgMagenta("• CALL ")
173+
case *CallNodeArg:
174+
return formatutil.BgBlue("• ARG ")
175+
case *ReturnValNode:
176+
return formatutil.BgGreen("• RETV ")
177+
case *ClosureNode:
178+
return formatutil.BgWhite("•CLOSU ")
179+
case *BoundLabelNode:
180+
return formatutil.BgRed("• BNDL ") // red because it usually yields false positives
181+
case *SyntheticNode:
182+
return formatutil.BgLightGreen("• SYNT ")
183+
case *BoundVarNode:
184+
return formatutil.BgLightRed("• BNDV ")
185+
case *FreeVarNode:
186+
return formatutil.BgOrange("•FREEV ")
187+
case *AccessGlobalNode:
188+
return formatutil.BgYellow("• GLOB ")
189+
}
190+
return " ??? "
191+
}
192+
164193
// TermNodeSummary returns a string summary of the node, highlighting with terminal colors
165194
// green indicates a relative index/argument name,
166195
// magenta is used for function names,
167196
// italic is used for types.
168197
func TermNodeSummary(g GraphNode) string {
198+
nodeCode := NodeTermLabel(g)
169199
switch x := g.(type) {
170200
case *ParamNode:
171-
return fmt.Sprintf("Parameter %s (type %s) of %s",
201+
return fmt.Sprintf("%s Parameter %s (type %s) of %s",
202+
nodeCode,
172203
formatutil.Green(x.ssaNode.Name()),
173-
formatutil.Italic(x.Type().String()),
204+
formatutil.Italic(formatutil.FormatLastSplit(x.Type().String(), ".", formatutil.Blue)),
174205
formatutil.Magenta(fmt.Sprintf("%q", x.parent.Parent.Name())))
175206
case *CallNode:
176-
return fmt.Sprintf("Result of call to %s (type %s)",
207+
return fmt.Sprintf("%s Result of call to %s (type %s)",
208+
nodeCode,
177209
formatutil.Magenta(x.Callee().Name()),
178-
formatutil.Italic(x.Type().String()))
210+
formatutil.Italic(formatutil.FormatLastSplit(x.Type().String(), ".", formatutil.Blue)))
179211
case *CallNodeArg:
180-
return fmt.Sprintf("Argument #%s (type %s) in call to %s",
212+
return fmt.Sprintf("%s Argument #%s (type %s) in call to %s",
213+
nodeCode,
181214
formatutil.Green(x.Index()),
182-
formatutil.Italic(x.Type().String()),
215+
formatutil.Italic(formatutil.FormatLastSplit(x.Type().String(), ".", formatutil.Blue)),
183216
formatutil.Magenta(x.ParentNode().Callee().Name()))
184217
case *ReturnValNode:
185-
return fmt.Sprintf("Return value %s (type %s) of %s",
218+
return fmt.Sprintf("%s Return value %s (type %s) of %s",
219+
nodeCode,
186220
formatutil.Green(x.Index()),
187-
formatutil.Italic(x.Type().String()),
221+
formatutil.Italic(formatutil.FormatLastSplit(x.Type().String(), ".", formatutil.Blue)),
188222
formatutil.Magenta(x.ParentName()))
189223
case *ClosureNode:
190-
return "Closure"
224+
return formatutil.BgCyan(nodeCode) + " Closure node (entire function)"
191225
case *BoundLabelNode:
192-
return fmt.Sprintf("Bound label of type %s", formatutil.Italic(x.targetInfo.Type().String()))
226+
return fmt.Sprintf("%s Bound label of type %s",
227+
nodeCode,
228+
formatutil.Italic(
229+
formatutil.FormatLastSplit(x.targetInfo.Type().String(), ".", formatutil.Blue)))
193230
case *SyntheticNode:
194-
return "Synthetic node"
231+
return nodeCode + " Synthetic node"
195232
case *BoundVarNode:
196-
return "Bound variable"
233+
return fmt.Sprintf("%s Bound variable %s", nodeCode, x.String())
197234
case *FreeVarNode:
198-
return fmt.Sprintf("Free variable #%s of %q", formatutil.Green(x.fvPos), x.ssaNode.Parent().String())
235+
return fmt.Sprintf("%s Free variable #%s of %q",
236+
nodeCode,
237+
formatutil.Green(x.fvPos),
238+
x.ssaNode.Parent().String())
199239
case *AccessGlobalNode:
200-
return fmt.Sprintf("Global variable %s", formatutil.Red(x.Global.String()))
240+
return fmt.Sprintf("%s Global variable %s", nodeCode, formatutil.Red(x.Global.String()))
201241
}
202242
return ""
203243
}

analysis/dataflow/inter_procedural.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,7 @@ func (g *InterProceduralFlowGraph) RunVisitorOnEntryPoints(visitor Visitor, spec
319319
summary.ForAllNodes(scanEntryPoints(g, spec, entryPoints))
320320
}
321321

322-
g.AnalyzerState.Logger.Infof("--- # of analysis entry points: %d ---\n", len(entryPoints))
322+
g.AnalyzerState.Logger.Infoboxf(" %d analysis entry points", len(entryPoints))
323323
if g.AnalyzerState.Logger.LogsDebug() {
324324
for _, entryPoint := range entryPoints {
325325
g.AnalyzerState.Logger.Debugf("Entry: %s", entryPoint.Node)

analysis/syntactic/structinit/structinit.go

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,8 @@ func Analyze(state *ptr.State, reqs AnalysisReqs) (AnalysisResult, error) {
110110
funcutil.Map(specs, func(ss config.StructInitSpec) string { return ss.Tag }), ","))
111111
if len(specs) == 0 {
112112
// If there is no specs here, it's like because of the tags being filtered out by structInitSpecs
113-
state.Logger.Infof("No struct-init specs matching configuration; check the tags if you expected a result")
113+
state.Logger.Infof(
114+
"No struct-init specs matching configuration; check the tags if you expected a result")
114115
return AnalysisResult{}, nil
115116
}
116117
res := AnalysisResult{InitInfos: make(map[*types.Named]InitInfo)}
@@ -196,7 +197,8 @@ func newState(spec config.StructInitSpec, st *ptr.State) (*state, error) {
196197
for i := 0; i < structTyp.NumFields(); i++ {
197198
f := structTyp.Field(i)
198199
if fieldSpec.Field == "" {
199-
return nil, fmt.Errorf("field name in fields-set spec should not be empty: %+v", fieldSpec)
200+
return nil,
201+
fmt.Errorf("field name in fields-set spec should not be empty: %+v", fieldSpec)
200202
}
201203
if fieldSpec.Field == f.Name() {
202204
field = f
@@ -205,12 +207,21 @@ func newState(spec config.StructInitSpec, st *ptr.State) (*state, error) {
205207
}
206208

207209
if field == nil {
208-
return nil, fmt.Errorf("failed to find field %v in struct %v from spec: %+v", fieldSpec.Field, structTyp, spec)
210+
return nil,
211+
fmt.Errorf(
212+
"failed to find field %v in struct %v from spec: %+v",
213+
fieldSpec.Field,
214+
structTyp,
215+
spec)
209216
}
210217
if fieldSpec.Value.Const != "" {
211218
c, ok := findNamedConst(st.Program, fieldSpec.Value)
212219
if !ok {
213-
return nil, fmt.Errorf("failed to find a named constant in the program for %v in spec: %+v", fieldSpec.Value, spec)
220+
return nil,
221+
fmt.Errorf(
222+
"failed to find a named constant in the program for %v in spec: %+v",
223+
fieldSpec.Value,
224+
spec)
214225
}
215226

216227
fieldVal[alloc.typ.named][field] = c.Value
@@ -219,7 +230,10 @@ func newState(spec config.StructInitSpec, st *ptr.State) (*state, error) {
219230
if fieldSpec.Value.Method != "" {
220231
f, ok := findMethod(st.Program, fieldSpec.Value)
221232
if !ok {
222-
return nil, fmt.Errorf("failed to find a function in the program for %v in spec: %+v", fieldSpec.Value, spec)
233+
return nil,
234+
fmt.Errorf("failed to find a function in the program for %v in spec: %+v",
235+
fieldSpec.Value,
236+
spec)
223237
}
224238

225239
fieldVal[alloc.typ.named][field] = f

analysis/taint/report.go

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -77,15 +77,24 @@ func reportCoverage(coverage map[string]bool, coverageWriter io.StringWriter) {
7777

7878
// logTaintFlow logs a taint flow on the state's logger.
7979
func logTaintFlow(s *dataflow.State, source dataflow.NodeWithTrace, sink *dataflow.VisitorNode) {
80-
s.Logger.Infof(" 💀 Tainted data flows to sink:")
81-
s.Logger.Infof(" |- Data from %s (%s)", source.Node.Position(s), formatutil.Green(source.Node.String()))
82-
s.Logger.Infof(" |- Reached sink at %s (%s)\n", sink.Node.Position(s), formatutil.Red(sink.Node.String()))
83-
84-
sinkPos := sink.Node.Position(s)
85-
if callArg, isCallArgsink := sink.Node.(*dataflow.CallNodeArg); isCallArgsink {
86-
sinkPos = callArg.ParentNode().Position(s)
80+
s.Logger.Infof(" ┌──────────────────────────────")
81+
s.Logger.Infof(" │ 💀 Tainted data flows to sink:")
82+
s.Logger.Infof(" │ Data from source %s\n", formatutil.Green(source.Node.String()))
83+
s.Logger.Infof(" │ ↳ Position: %s\n", source.Node.Position(s))
84+
s.Logger.Infof(" │ Reached sink %s\n", formatutil.Red(sink.Node.String()))
85+
s.Logger.Infof(" │ ↳ Position: %s\n", sink.Node.Position(s))
86+
if s.Config.ReportPaths {
87+
s.Logger.Infof(" ├──────────────────┄")
88+
} else {
89+
s.Logger.Infof(" └──────────────────┄")
8790
}
91+
8892
if s.Config.ReportPaths {
93+
s.Logger.Infof("%s", formatutil.Blue(" ├ TRACE:"))
94+
sinkPos := sink.Node.Position(s)
95+
if callArg, isCallArgsink := sink.Node.(*dataflow.CallNodeArg); isCallArgsink {
96+
sinkPos = callArg.ParentNode().Position(s)
97+
}
8998
nodes := []*dataflow.VisitorNode{}
9099
cur := sink
91100
for cur != nil {
@@ -97,19 +106,22 @@ func logTaintFlow(s *dataflow.State, source dataflow.NodeWithTrace, sink *datafl
97106
if nodes[i].Status.Kind != dataflow.DefaultTracing {
98107
continue
99108
}
100-
s.Logger.Infof("%s - %s",
101-
formatutil.Blue(" |- TRACE"),
109+
s.Logger.Infof("%s %s",
110+
formatutil.Blue(" ├───┬"),
102111
dataflow.TermNodeSummary(nodes[i].Node))
103112
// - Context [<calling context string>] Pos: <position in source code>
104-
s.Logger.Infof("%s - Context [%s]\n",
105-
" | ",
113+
s.Logger.Infof("%s %s Context [%s]\n",
114+
formatutil.Faint(" │ "),
115+
formatutil.Blue("╞"),
106116
dataflow.FuncNames(nodes[i].Trace, s.Logger.LogsDebug()))
107-
s.Logger.Infof("%s - %s %s\n",
108-
" | ",
117+
s.Logger.Infof("%s %s %s %s\n",
118+
formatutil.Faint(" │ "),
119+
formatutil.Blue("└"),
109120
formatutil.Yellow("At"),
110121
nodes[i].Node.Position(s).String())
111122
}
112-
s.Logger.Infof(" ⎣ ENDS WITH SINK: %s\n", sinkPos.String())
123+
s.Logger.Infof(" │ ENDS WITH SINK: %s\n", sinkPos.String())
124+
s.Logger.Infof(" └──────────────────┄")
113125
s.Logger.Infof(" ")
114126
}
115127
}

cmd/argot/backtrace/backtrace.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ func Run(flags tools.CommonFlags) error {
7777
ApplyRewrites: true,
7878
}
7979

80-
c := config.NewState(cfg, targetName, target.Files, loadOptions)
80+
c := config.NewState(cfg, targetName, target.Patterns, loadOptions)
8181
var actual result.Result[config.State]
8282
if target.UseProgramTransforms && len(target.ReflectValueCallInstances) >= 1 {
8383
c.Logger.Infof("Reflect value call instances specified. Tool supports only 1 for now, will use the first.")
@@ -87,7 +87,7 @@ func Run(flags tools.CommonFlags) error {
8787
} else {
8888
actual = result.Ok(c)
8989
}
90-
c.Logger.Infof("Backtrace analysis of target \"%s\" = %v", targetName, target.Files)
90+
c.Logger.Infof("Backtrace analysis of target \"%s\" = %v", targetName, target.Patterns)
9191
c.Logger.PushContext(formatutil.Faint(targetName))
9292
ptrState := result.Bind(result.Bind(actual, loadprogram.NewState), ptr.NewState) // build pointer analysis info
9393
state, err := result.Bind(ptrState, dataflow.NewState).Value()

0 commit comments

Comments
 (0)