aboutsummaryrefslogtreecommitdiffstats
path: root/dashboard/app
diff options
context:
space:
mode:
Diffstat (limited to 'dashboard/app')
-rw-r--r--dashboard/app/ai.go87
-rw-r--r--dashboard/app/ai_test.go57
-rw-r--r--dashboard/app/entities_datastore.go13
-rw-r--r--dashboard/app/label.go21
-rw-r--r--dashboard/app/reporting_email.go2
-rw-r--r--dashboard/app/tree.go4
6 files changed, 173 insertions, 11 deletions
diff --git a/dashboard/app/ai.go b/dashboard/app/ai.go
index 4ea52bd7e..29bfc38d1 100644
--- a/dashboard/app/ai.go
+++ b/dashboard/app/ai.go
@@ -4,6 +4,7 @@
package main
import (
+ "bytes"
"context"
"encoding/json"
"fmt"
@@ -118,7 +119,7 @@ func handleAIJobPage(ctx context.Context, w http.ResponseWriter, r *http.Request
default:
job.Correct = spanner.NullBool{}
}
- if err := aidb.UpdateJob(ctx, job); err != nil {
+ if err := aiJobUpdate(ctx, job); err != nil {
return err
}
}
@@ -284,10 +285,92 @@ func apiAIJobDone(ctx context.Context, req *dashapi.AIJobDoneReq) (any, error) {
if len(req.Results) != 0 {
job.Results = spanner.NullJSON{Value: req.Results, Valid: true}
}
- err = aidb.UpdateJob(ctx, job)
+ err = aiJobUpdate(ctx, job)
return nil, err
}
+func aiJobUpdate(ctx context.Context, job *aidb.Job) error {
+ if err := aidb.UpdateJob(ctx, job); err != nil {
+ return err
+ }
+ if !job.BugID.Valid || !job.Finished.Valid || job.Error != "" {
+ return nil
+ }
+ bug, err := loadBug(ctx, job.BugID.StringVal)
+ if err != nil {
+ return err
+ }
+ labelType, labelValue, labelAdd, err := aiBugLabel(job)
+ if err != nil || labelType == EmptyLabel {
+ return err
+ }
+ label := BugLabel{
+ Label: labelType,
+ Value: labelValue,
+ Link: job.ID,
+ }
+ labelSet := makeLabelSet(ctx, bug)
+ return updateSingleBug(ctx, bug.key(ctx), func(bug *Bug) error {
+ if bug.HasUserLabel(labelType) {
+ return nil
+ }
+ if labelAdd {
+ return bug.SetLabels(labelSet, []BugLabel{label})
+ }
+ bug.UnsetLabels(labelType)
+ return nil
+ })
+}
+
+func aiBugLabel(job *aidb.Job) (typ BugLabelType, value string, set bool, err0 error) {
+ switch job.Type {
+ case ai.WorkflowAssessmentKCSAN:
+ // For now we require a manual correctness check,
+ // later we may apply some labels w/o the manual check.
+ if !job.Correct.Valid {
+ return
+ }
+ if !job.Correct.Bool {
+ return RaceLabel, "", false, nil
+ }
+ res, err := castJobResults[ai.AssessmentKCSANOutputs](job)
+ if err != nil {
+ err0 = err
+ return
+ }
+ if !res.Confident {
+ return
+ }
+ if res.Benign {
+ return RaceLabel, BenignRace, true, nil
+ }
+ return RaceLabel, HarmfulRace, true, nil
+ }
+ return
+}
+
+func castJobResults[T any](job *aidb.Job) (T, error) {
+ var res T
+ raw, ok := job.Results.Value.(map[string]any)
+ if !ok || !job.Results.Valid {
+ return res, fmt.Errorf("finished job %v %v does not have results", job.Type, job.ID)
+ }
+ // Database may store older versions of the output structs.
+ // It's not possible to automatically handle all possible changes to the structs.
+ // For now we just parse in some way. Later when we start changing output structs,
+ // we may need to reconsider and use more careful parsing.
+ data, err := json.Marshal(raw)
+ if err != nil {
+ return res, err
+ }
+ dec := json.NewDecoder(bytes.NewReader(data))
+ dec.DisallowUnknownFields()
+ if err := dec.Decode(&res); err != nil {
+ return res, fmt.Errorf("failed to unmarshal %T: %w", res, err)
+ }
+ return res, nil
+}
+
func apiAITrajectoryLog(ctx context.Context, req *dashapi.AITrajectoryReq) (any, error) {
err := aidb.StoreTrajectorySpan(ctx, req.JobID, req.Span)
return nil, err
diff --git a/dashboard/app/ai_test.go b/dashboard/app/ai_test.go
index 86ede7901..bd3935b18 100644
--- a/dashboard/app/ai_test.go
+++ b/dashboard/app/ai_test.go
@@ -4,10 +4,12 @@
package main
import (
+ "fmt"
"testing"
"time"
"github.com/google/syzkaller/dashboard/dashapi"
+ "github.com/google/syzkaller/pkg/aflow/ai"
"github.com/google/syzkaller/pkg/aflow/trajectory"
"github.com/google/syzkaller/prog"
"github.com/stretchr/testify/require"
@@ -198,3 +200,58 @@ func TestAIJob(t *testing.T) {
},
}))
}
+
+func TestAIAssessmentKCSAN(t *testing.T) {
+ c := NewSpannerCtx(t)
+ defer c.Close()
+
+ build := testBuild(1)
+ c.aiClient.UploadBuild(build)
+ crash := testCrash(build, 1)
+ crash.Title = "KCSAN: data-race in foo / bar"
+ c.aiClient.ReportCrash(crash)
+ extID := c.aiClient.pollEmailExtID()
+
+ resp, err := c.aiClient.AIJobPoll(&dashapi.AIJobPollReq{
+ CodeRevision: prog.GitRevision,
+ LLMModel: "smarty",
+ Workflows: []dashapi.AIWorkflow{
+ {Type: ai.WorkflowAssessmentKCSAN, Name: string(ai.WorkflowAssessmentKCSAN)},
+ },
+ })
+ require.NoError(t, err)
+ require.Equal(t, resp.Workflow, string(ai.WorkflowAssessmentKCSAN))
+
+ _, err = c.GET(fmt.Sprintf("/ai_job?id=%v", resp.ID))
+ require.NoError(t, err)
+
+ // Since the job is not completed, setting correctness must fail.
+ _, err = c.GET(fmt.Sprintf("/ai_job?id=%v&correct=%v", resp.ID, aiCorrectnessCorrect))
+ require.Error(t, err)
+
+ require.NoError(t, c.aiClient.AIJobDone(&dashapi.AIJobDoneReq{
+ ID: resp.ID,
+ Results: map[string]any{
+ "Confident": true,
+ "Benign": true,
+ "Explanation": "I don't care about races.",
+ },
+ }))
+
+ // Now setting correctness must not fail.
+ _, err = c.GET(fmt.Sprintf("/ai_job?id=%v&correct=%v", resp.ID, aiCorrectnessCorrect))
+ require.NoError(t, err)
+
+ bug, _, _ := c.loadBug(extID)
+ labels := bug.LabelValues(RaceLabel)
+ require.Len(t, labels, 1)
+ require.Equal(t, labels[0].Value, BenignRace)
+
+ // Re-mark the result as incorrect, this should remove the label.
+ _, err = c.GET(fmt.Sprintf("/ai_job?id=%v&correct=%v", resp.ID, aiCorrectnessIncorrect))
+ require.NoError(t, err)
+
+ bug, _, _ = c.loadBug(extID)
+ labels = bug.LabelValues(RaceLabel)
+ require.Len(t, labels, 0)
+}
diff --git a/dashboard/app/entities_datastore.go b/dashboard/app/entities_datastore.go
index 370cf3ceb..5a204d5d2 100644
--- a/dashboard/app/entities_datastore.go
+++ b/dashboard/app/entities_datastore.go
@@ -168,7 +168,7 @@ type BugLabel struct {
// The email of the user who manually set this subsystem tag.
// If empty, the label was set automatically.
SetBy string
- // Link to the message.
+ // Link to the message, or AI job ID for automatic labels.
Link string
}
@@ -193,7 +193,7 @@ func (bug *Bug) SetAutoSubsystems(c context.Context, list []*subsystem.Subsystem
for _, item := range list {
objects = append(objects, BugLabel{Label: SubsystemLabel, Value: item.Name})
}
- bug.SetLabels(makeLabelSet(c, bug.Namespace), objects)
+ bug.SetLabels(makeLabelSet(c, bug), objects)
}
func updateSingleBug(c context.Context, bugKey *db.Key, transform func(*Bug) error) error {
@@ -915,6 +915,15 @@ func bugKeyHash(c context.Context, ns, title string, seq int64) string {
return hash.String([]byte(fmt.Sprintf("%v-%v-%v-%v", getNsConfig(c, ns).Key, ns, title, seq)))
}
+func loadBug(c context.Context, bugHash string) (*Bug, error) {
+ bug := new(Bug)
+ bugKey := db.NewKey(c, "Bug", bugHash, 0, nil)
+ if err := db.Get(c, bugKey, bug); err != nil {
+ return nil, fmt.Errorf("failed to load bug by hash %q: %w", bugHash, err)
+ }
+ return bug, nil
+}
+
func loadSimilarBugs(c context.Context, bug *Bug) ([]*Bug, error) {
domain := getNsConfig(c, bug.Namespace).SimilarityDomain
dedup := make(map[string]bool)
diff --git a/dashboard/app/label.go b/dashboard/app/label.go
index 807951f48..de874cf50 100644
--- a/dashboard/app/label.go
+++ b/dashboard/app/label.go
@@ -8,6 +8,8 @@ import (
"fmt"
"sort"
"strings"
+
+ "github.com/google/syzkaller/pkg/report/crash"
)
const (
@@ -17,6 +19,7 @@ const (
NoRemindersLabel BugLabelType = "no-reminders"
OriginLabel BugLabelType = "origin"
MissingBackportLabel BugLabelType = "missing-backport"
+ RaceLabel BugLabelType = "race"
)
type BugPrio string
@@ -27,11 +30,16 @@ const (
HighPrioBug BugPrio = "high"
)
+const (
+ BenignRace = "benign"
+ HarmfulRace = "harmful"
+)
+
type oneOf []string
type subsetOf []string
type trueFalse struct{}
-func makeLabelSet(c context.Context, ns string) *labelSet {
+func makeLabelSet(c context.Context, bug *Bug) *labelSet {
ret := map[BugLabelType]any{
PriorityLabel: oneOf([]string{
string(LowPrioBug),
@@ -41,7 +49,12 @@ func makeLabelSet(c context.Context, ns string) *labelSet {
NoRemindersLabel: trueFalse{},
MissingBackportLabel: trueFalse{},
}
- service := getNsConfig(c, ns).Subsystems.Service
+ typ := crash.TitleToType(bug.Title)
+ if typ == crash.KCSANDataRace {
+ ret[RaceLabel] = oneOf([]string{BenignRace, HarmfulRace})
+ }
+ cfg := getNsConfig(c, bug.Namespace)
+ service := cfg.Subsystems.Service
if service != nil {
names := []string{}
for _, item := range service.List() {
@@ -51,7 +64,7 @@ func makeLabelSet(c context.Context, ns string) *labelSet {
}
originLabels := []string{}
- for _, repo := range getNsConfig(c, ns).Repos {
+ for _, repo := range cfg.Repos {
if repo.LabelIntroduced != "" {
originLabels = append(originLabels, repo.LabelIntroduced)
}
@@ -66,7 +79,7 @@ func makeLabelSet(c context.Context, ns string) *labelSet {
return &labelSet{
c: c,
- ns: ns,
+ ns: bug.Namespace,
labels: ret,
}
}
diff --git a/dashboard/app/reporting_email.go b/dashboard/app/reporting_email.go
index 236a95857..301da9ccc 100644
--- a/dashboard/app/reporting_email.go
+++ b/dashboard/app/reporting_email.go
@@ -973,7 +973,7 @@ Please contact the bot's maintainers.`
func handleSetCommand(c context.Context, bug *Bug, msg *email.Email,
command *email.SingleCommand) string {
- labelSet := makeLabelSet(c, bug.Namespace)
+ labelSet := makeLabelSet(c, bug)
match := setCmdRe.FindStringSubmatch(command.Args)
if match == nil {
diff --git a/dashboard/app/tree.go b/dashboard/app/tree.go
index b6261e289..2103f78a1 100644
--- a/dashboard/app/tree.go
+++ b/dashboard/app/tree.go
@@ -223,7 +223,7 @@ func (ctx *bugTreeContext) setOriginLabels() pollTreeJobResult {
for _, label := range allLabels {
labels = append(labels, BugLabel{Label: OriginLabel, Value: label})
}
- ctx.bug.SetLabels(makeLabelSet(ctx.c, ctx.bug.Namespace), labels)
+ ctx.bug.SetLabels(makeLabelSet(ctx.c, ctx.bug), labels)
return pollResultSkip{}
}
@@ -330,7 +330,7 @@ func (ctx *bugTreeContext) missingBackports() pollTreeJobResult {
}
ctx.bug.UnsetLabels(MissingBackportLabel)
if resultDone.Crashed {
- ctx.bug.SetLabels(makeLabelSet(ctx.c, ctx.bug.Namespace), []BugLabel{
+ ctx.bug.SetLabels(makeLabelSet(ctx.c, ctx.bug), []BugLabel{
{Label: MissingBackportLabel},
})
}