mirror of
https://github.com/safedep/vet.git
synced 2025-12-10 12:07:30 -06:00
feat: Building code graph Refactor to support import processing Handle relative import name fixup Add docs for code analysis framework Update docs to include additional examples feat: Function call graph Update code graph to link function decl and calls Include call node in function calls feat: Flatten vulnerabilities in CSV reporter refactor: Maintain separation of concerns for code analysis framework refactor: Separate storage entities in its own package feat: Add callback support in code graph builder docs: Fix code analysis framework docs Signed-off-by: abhisek <abhisek.datta@gmail.com>
200 lines
5.2 KiB
Go
200 lines
5.2 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/safedep/dry/utils"
|
|
"github.com/safedep/vet/internal/ui"
|
|
"github.com/safedep/vet/pkg/code"
|
|
"github.com/safedep/vet/pkg/code/languages"
|
|
"github.com/safedep/vet/pkg/common/logger"
|
|
"github.com/safedep/vet/pkg/storage/graph"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
var (
|
|
codeAppDirectories = []string{}
|
|
codeImportDirectories = []string{}
|
|
codeGraphDatabase string
|
|
codeLanguage string
|
|
)
|
|
|
|
func newCodeCommand() *cobra.Command {
|
|
cmd := &cobra.Command{
|
|
Use: "code",
|
|
Short: "[EXPERIMENTAL] Perform code analysis with insights data",
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
return nil
|
|
},
|
|
}
|
|
|
|
cmd.Flags().StringArrayVarP(&codeAppDirectories, "src", "", []string{}, "Source code root directory to analyze")
|
|
cmd.Flags().StringArrayVarP(&codeImportDirectories, "imports", "", []string{}, "Language specific directory to find imported source")
|
|
cmd.Flags().StringVarP(&codeGraphDatabase, "db", "", "", "Path to the database")
|
|
cmd.Flags().StringVarP(&codeLanguage, "lang", "", "python", "Language of the source code")
|
|
|
|
err := cmd.MarkFlagRequired("db")
|
|
if err != nil {
|
|
logger.Errorf("Failed to mark flag as required: %v", err)
|
|
}
|
|
|
|
cmd.AddCommand(newCodeCreateDatabaseCommand())
|
|
cmd.AddCommand(newCodeImportReachabilityCommand())
|
|
|
|
return cmd
|
|
}
|
|
|
|
func newCodeCreateDatabaseCommand() *cobra.Command {
|
|
cmd := &cobra.Command{
|
|
Use: "create-db",
|
|
Short: "Analyse code and create a database for further analysis",
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
startCreateDatabase()
|
|
return nil
|
|
},
|
|
}
|
|
|
|
return cmd
|
|
}
|
|
|
|
func newCodeImportReachabilityCommand() *cobra.Command {
|
|
cmd := &cobra.Command{
|
|
Use: "import-reachability",
|
|
Short: "Analyse import reachability",
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
startImportReachability()
|
|
return nil
|
|
},
|
|
}
|
|
|
|
return cmd
|
|
}
|
|
|
|
func startCreateDatabase() {
|
|
failOnError("code-create-db", internalStartCreateDatabase())
|
|
}
|
|
|
|
func startImportReachability() {
|
|
failOnError("code-import-reachability-analysis", internalStartImportReachability())
|
|
}
|
|
|
|
func internalStartImportReachability() error {
|
|
codePrintExperimentalWarning()
|
|
|
|
if utils.IsEmptyString(codeGraphDatabase) {
|
|
return fmt.Errorf("no database path provided")
|
|
}
|
|
|
|
// TODO: We need a code graph loader to load the code graph from the database
|
|
// before invoking analysis modules
|
|
|
|
return nil
|
|
}
|
|
|
|
func internalStartCreateDatabase() error {
|
|
codePrintExperimentalWarning()
|
|
logger.Debugf("Starting code analysis")
|
|
|
|
if len(codeAppDirectories) == 0 {
|
|
return fmt.Errorf("no source code directory provided")
|
|
}
|
|
|
|
if len(codeImportDirectories) == 0 {
|
|
return fmt.Errorf("no import directory provided")
|
|
}
|
|
|
|
if utils.IsEmptyString(codeGraphDatabase) {
|
|
return fmt.Errorf("no database path provided")
|
|
}
|
|
|
|
codeRepoCfg := code.FileSystemSourceRepositoryConfig{
|
|
SourcePaths: codeAppDirectories,
|
|
ImportPaths: codeImportDirectories,
|
|
}
|
|
|
|
codeRepo, err := code.NewFileSystemSourceRepository(codeRepoCfg)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create source repository: %w", err)
|
|
}
|
|
|
|
codeLang, err := codeGetLanguage()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create source language: %w", err)
|
|
}
|
|
|
|
codeRepo.ConfigureForLanguage(codeLang)
|
|
|
|
graph, err := graph.NewPropertyGraph(&graph.LocalPropertyGraphConfig{
|
|
Name: "code-analysis",
|
|
DatabasePath: codeGraphDatabase,
|
|
})
|
|
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create graph database: %w", err)
|
|
}
|
|
|
|
builderConfig := code.CodeGraphBuilderConfig{
|
|
RecursiveImport: true,
|
|
}
|
|
|
|
builder, err := code.NewCodeGraphBuilder(builderConfig, codeRepo, codeLang, graph)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create code graph builder: %w", err)
|
|
}
|
|
|
|
redirectLogToFile(logFile)
|
|
|
|
var fileProcessedTracker any
|
|
var importsProcessedTracker any
|
|
var functionsProcessedTracker any
|
|
|
|
builder.RegisterEventHandler("ui-callback",
|
|
func(event code.CodeGraphBuilderEvent, metrics code.CodeGraphBuilderMetrics) error {
|
|
switch event.Kind {
|
|
case code.CodeGraphBuilderEventFileQueued:
|
|
ui.IncrementTrackerTotal(fileProcessedTracker, 1)
|
|
case code.CodeGraphBuilderEventFileProcessed:
|
|
ui.IncrementProgress(fileProcessedTracker, 1)
|
|
}
|
|
|
|
ui.UpdateValue(importsProcessedTracker, int64(metrics.ImportsCount))
|
|
ui.UpdateValue(functionsProcessedTracker, int64(metrics.FunctionsCount))
|
|
|
|
return nil
|
|
})
|
|
|
|
ui.StartProgressWriter()
|
|
|
|
fileProcessedTracker = ui.TrackProgress("Processing source files", 0)
|
|
importsProcessedTracker = ui.TrackProgress("Processing imports", 0)
|
|
functionsProcessedTracker = ui.TrackProgress("Processing functions", 0)
|
|
|
|
err = builder.Build()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to build code graph: %w", err)
|
|
}
|
|
|
|
ui.MarkTrackerAsDone(fileProcessedTracker)
|
|
ui.MarkTrackerAsDone(importsProcessedTracker)
|
|
ui.MarkTrackerAsDone(functionsProcessedTracker)
|
|
ui.StopProgressWriter()
|
|
|
|
logger.Debugf("Code analysis completed")
|
|
return nil
|
|
}
|
|
|
|
func codePrintExperimentalWarning() {
|
|
ui.PrintWarning("Code analysis is experimental and may have breaking change")
|
|
}
|
|
|
|
func codeGetLanguage() (code.SourceLanguage, error) {
|
|
lang := strings.ToLower(codeLanguage)
|
|
switch lang {
|
|
case "python":
|
|
return languages.NewPythonSourceLanguage()
|
|
default:
|
|
return nil, fmt.Errorf("unsupported language: %s", codeLanguage)
|
|
}
|
|
}
|