diff --git a/cli/cmd/analyzer_inputs.go b/cli/cmd/analyzer_inputs.go index d9fabfe8b4..2d7da3a483 100644 --- a/cli/cmd/analyzer_inputs.go +++ b/cli/cmd/analyzer_inputs.go @@ -1,33 +1,46 @@ package cmd import ( - "github.com/seqra/opentaint/internal/approximation" + "github.com/seqra/opentaint/internal/models" "github.com/seqra/opentaint/internal/utils/log" + "github.com/spf13/cobra" ) -// addDataflowApproximations resolves the paths to compiled model directories. +func addDataflowModelsFlag(cmd *cobra.Command, paths *[]string) { + cmd.Flags().StringArrayVar(paths, "dataflow-models", nil, + "Dataflow model project, build output, or class directory (repeatable)") + addDeprecatedFlagAlias(cmd.Flags(), "dataflow-models", "dataflow-approximations") +} + +func addPassthroughModelsFlag(cmd *cobra.Command, paths *[]string) { + cmd.Flags().StringArrayVar(paths, "passthrough-models", nil, + "Pass-through model YAML file or directory (repeatable)") + addDeprecatedFlagAlias(cmd.Flags(), "passthrough-models", "passthrough-approximations") +} + +// addDataflowModels resolves the paths to compiled model directories. // It rebuilds a model project when its inputs change. -func addDataflowApproximations(b *AnalyzerBuilder, paths []string, analyzerJarPath string) { +func addDataflowModels(b *AnalyzerBuilder, paths []string, analyzerJarPath string) { if len(paths) == 0 { return } absPaths := make([]string, 0, len(paths)) - for _, approxPath := range paths { - absPaths = append(absPaths, log.AbsPathOrExit(approxPath, "dataflow-approximations")) + for _, modelPath := range paths { + absPaths = append(absPaths, log.AbsPathOrExit(modelPath, "dataflow-models")) } - classDirs, err := approximation.ResolveAll(absPaths, newApproximationBuilder(analyzerJarPath)) + classDirs, err := models.ResolveAll(absPaths, newModelBuilder(analyzerJarPath)) if err != nil { - out.Fatalf("Failed to apply dataflow approximations: %s", err) + out.Fatalf("Failed to apply dataflow models: %s", err) } for _, classDir := range classDirs { b.AddDataflowApproximations(classDir) } } -func addPassthroughApproximations(b *AnalyzerBuilder, paths []string) { +func addPassthroughModels(b *AnalyzerBuilder, paths []string) { for _, passthrough := range paths { - b.AddPassthroughApproximations(log.AbsPathOrExit(passthrough, "passthrough-approximations")) + b.AddPassthroughApproximations(log.AbsPathOrExit(passthrough, "passthrough-models")) } } diff --git a/cli/cmd/command_builder.go b/cli/cmd/command_builder.go index 263c45e58c..428454c7b6 100644 --- a/cli/cmd/command_builder.go +++ b/cli/cmd/command_builder.go @@ -69,6 +69,13 @@ type AnalyzerBuilder struct { debugFactReachabilitySarif bool runRuleTests bool debugRunAnalysisOnSelectedEntryPoints string + // logsFile is the analyzer log of the last command built. + logsFile string +} + +// LogsFile returns the path of the analyzer log that the last command from BuildNativeCommand writes. +func (a *AnalyzerBuilder) LogsFile() string { + return a.logsFile } func (a *AnalyzerBuilder) SetProject(projectPath string) *AnalyzerBuilder { @@ -193,6 +200,7 @@ func (a *AnalyzerBuilder) BuildNativeCommand() []string { return []string{} } tempLogsFile := filepath.Join(tempLogsDir, "analyzer.log") + a.logsFile = tempLogsFile command := []string{ a.maxMemory, diff --git a/cli/cmd/command_builder_test.go b/cli/cmd/command_builder_test.go index 903585a70f..58777087a8 100644 --- a/cli/cmd/command_builder_test.go +++ b/cli/cmd/command_builder_test.go @@ -1,6 +1,8 @@ package cmd import ( + "os" + "path/filepath" "reflect" "testing" ) @@ -63,6 +65,17 @@ func TestAppendVerbosityFlagPreservesExistingFlags(t *testing.T) { } } +// The analyzer log path is reported after an analyzer exception, so it must be the one the command writes. +func TestAnalyzerBuilderReportsItsLogsFile(t *testing.T) { + b := NewAnalyzerBuilder().SetProject("/project/project.yaml").SetOutputDir("/output") + cmd := b.BuildNativeCommand() + t.Cleanup(func() { _ = os.RemoveAll(filepath.Dir(b.LogsFile())) }) + + if filepath.Base(b.LogsFile()) != "analyzer.log" || !containsFlagPair(cmd, "--logs-file", b.LogsFile()) { + t.Fatalf("LogsFile() = %q, command was %v", b.LogsFile(), cmd) + } +} + // containsFlagPair reports whether args contains the adjacent pair `flag value`. func containsFlagPair(args []string, flag, value string) bool { for i := 0; i+1 < len(args); i++ { diff --git a/cli/cmd/compile_approximations_cmd.go b/cli/cmd/compile_approximations_cmd.go deleted file mode 100644 index d9ab54bcaf..0000000000 --- a/cli/cmd/compile_approximations_cmd.go +++ /dev/null @@ -1,65 +0,0 @@ -package cmd - -import ( - "fmt" - - "github.com/seqra/opentaint/internal/approximation" - "github.com/seqra/opentaint/internal/utils/log" - "github.com/spf13/cobra" -) - -var compileApproximationsOutput string - -var compileApproximationsCmd = &cobra.Command{ - Use: "approximations ", - Short: "Compile a dataflow approximation project", - Long: `Compile the dataflow approximation models in against the -dependencies that project pins in its own build file. - -This is the model project, not the project to scan: its build.gradle.kts lists the exact -library versions the models are written against, so the same models compile identically -wherever they are applied. - -Scanning and testing build an approximation project on demand, so running this is only -needed to compile ahead of time or to see compilation errors on their own.`, - Args: cobra.ExactArgs(1), - Run: func(cmd *cobra.Command, args []string) { - projectPath := log.AbsPathOrExit(args[0], "approximation-project") - - if !approximation.IsProject(projectPath) { - out.Fatalf("%s is not an approximation project: it has no build.gradle.kts declaring the "+ - "dependencies its models are written against.\nCreate one with 'opentaint approximation init %s "+ - "--dependency '", projectPath, args[0]) - } - - outputPath := compileApproximationsOutput - if outputPath == "" { - outputPath = approximation.BuildDir(projectPath) - } else { - outputPath = log.AbsPathOrExit(outputPath, "output") - } - - analyzerJarPath, err := ensureAnalyzerAvailable() - if err != nil { - out.Fatalf("Failed to resolve analyzer: %s", err) - } - - if err := approximation.Build(projectPath, outputPath, newApproximationBuilder(analyzerJarPath)); err != nil { - out.Fatalf("Approximation compilation failed: %s", err) - } - - out.Section("Approximation Compile Summary"). - Field("Approximation project", projectPath). - Field("Compiled models written to", approximation.ClassesDir(outputPath)). - Render() - suggest("To apply these models in a scan run", - fmt.Sprintf("opentaint scan --dataflow-approximations %s", args[0])) - }, -} - -func init() { - compileCmd.AddCommand(compileApproximationsCmd) - - compileApproximationsCmd.Flags().StringVarP(&compileApproximationsOutput, "output", "o", "", - "Path to the compiled models (default: /.opentaint/build)") -} diff --git a/cli/cmd/compile_approximations.go b/cli/cmd/compile_models.go similarity index 57% rename from cli/cmd/compile_approximations.go rename to cli/cmd/compile_models.go index 52b08ba73f..f2d6629eb3 100644 --- a/cli/cmd/compile_approximations.go +++ b/cli/cmd/compile_models.go @@ -5,27 +5,27 @@ import ( "os" "path/filepath" - "github.com/seqra/opentaint/internal/approximation" "github.com/seqra/opentaint/internal/autobuilder" "github.com/seqra/opentaint/internal/globals" + "github.com/seqra/opentaint/internal/models" "github.com/seqra/opentaint/internal/output" ) -// approximationBuilder compiles a model project with the autobuilder. +// modelBuilder compiles a model project with the autobuilder. // The model project supplies the compile dependencies. -type approximationBuilder struct { +type modelBuilder struct { analyzerJarPath string apiJar []byte } -func newApproximationBuilder(analyzerJarPath string) approximation.Builder { - return &approximationBuilder{analyzerJarPath: analyzerJarPath} +func newModelBuilder(analyzerJarPath string) models.Builder { + return &modelBuilder{analyzerJarPath: analyzerJarPath} } // Fingerprint identifies the compiler a build was produced with. // The source stamp covers the model project but not the tool that reads it, so without // this an upgraded CLI keeps serving models compiled by the previous one. -func (b *approximationBuilder) Fingerprint() (string, error) { +func (b *modelBuilder) Fingerprint() (string, error) { autobuilder := globals.ArtifactByKind("autobuilder") if autobuilder.Override == "" { return "autobuilder " + autobuilder.Version, nil @@ -40,20 +40,20 @@ func (b *approximationBuilder) Fingerprint() (string, error) { autobuilder.Override, info.Size(), info.ModTime().UnixNano()), nil } -// Prepare updates the approximation API jar in the model project. +// Prepare updates the model API jar in the model project. // The builder reads the jar from the analyzer only once. -func (b *approximationBuilder) Prepare(projectDir string) error { +func (b *modelBuilder) Prepare(projectDir string) error { if b.apiJar == nil { - apiJar, err := approximation.ExtractApiJar(b.analyzerJarPath) + apiJar, err := models.ExtractApiJar(b.analyzerJarPath) if err != nil { return err } b.apiJar = apiJar } - return approximation.WriteApiJar(projectDir, b.apiJar) + return models.WriteApiJar(projectDir, b.apiJar) } -func (b *approximationBuilder) Compile(projectDir, projectModelDir string) error { +func (b *modelBuilder) Compile(projectDir, projectModelDir string) error { autobuilderJarPath, err := ensureAutobuilderAvailable() if err != nil { return fmt.Errorf("failed to resolve autobuilder: %w", err) @@ -61,27 +61,27 @@ func (b *approximationBuilder) Compile(projectDir, projectModelDir string) error javaRunner := newAutobuilderJavaRunner() if _, err := javaRunner.EnsureJava(); err != nil { - return fmt.Errorf("failed to resolve Java for approximation compilation: %w", err) + return fmt.Errorf("failed to resolve Java for model compilation: %w", err) } - logsDir, err := os.MkdirTemp("", "opentaint-approximation-logs-*") + logsDir, err := os.MkdirTemp("", "opentaint-model-logs-*") if err != nil { - return fmt.Errorf("failed to create temp directory for approximation build logs: %w", err) + return fmt.Errorf("failed to create temp directory for model build logs: %w", err) } - logsFile := filepath.Join(logsDir, "approximation-build.log") + logsFile := filepath.Join(logsDir, "model-build.log") // Show the spinner for direct and automatic builds. - output.LogInfof("Compiling approximation project %s", projectDir) + output.LogInfof("Compiling model project %s", projectDir) compile := func() error { return compileProjectWithLogs(projectModelDir, projectDir, autobuilderJarPath, logsFile, true, javaRunner) } - if err := out.RunWithSpinner("Compiling approximation project "+filepath.Base(projectDir), compile); err != nil { + if err := out.RunWithSpinner("Compiling model project "+filepath.Base(projectDir), compile); err != nil { // Show compiler errors when they are available. // Keep the full log if no compiler error is available. if diagnostics := autobuilder.CompilerDiagnostics(logsFile); diagnostics != "" { - return fmt.Errorf("approximation project %s failed to compile:\n%s", projectDir, diagnostics) + return fmt.Errorf("model project %s failed to compile:\n%s", projectDir, diagnostics) } - return fmt.Errorf("failed to compile approximation project %s (build log: %s): %w", projectDir, logsFile, err) + return fmt.Errorf("failed to compile model project %s (build log: %s): %w", projectDir, logsFile, err) } _ = os.RemoveAll(logsDir) diff --git a/cli/cmd/compile_models_cmd.go b/cli/cmd/compile_models_cmd.go new file mode 100644 index 0000000000..fa2a4a613f --- /dev/null +++ b/cli/cmd/compile_models_cmd.go @@ -0,0 +1,66 @@ +package cmd + +import ( + "fmt" + + "github.com/seqra/opentaint/internal/models" + "github.com/seqra/opentaint/internal/utils/log" + "github.com/spf13/cobra" +) + +var compileModelsOutput string + +var compileModelsCmd = &cobra.Command{ + Use: "models ", + Short: "Compile a dataflow model project", + Long: `Compile the dataflow models in against the +dependencies that project pins in its own build file. + +This is the model project, not the project to scan: its build.gradle.kts lists the exact +library versions the models are written against, so the same models compile identically +wherever they are applied. + +Scanning and testing build a model project on demand, so running this is only +needed to compile ahead of time or to see compilation errors on their own.`, + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + projectPath := log.AbsPathOrExit(args[0], "model-project") + + if !models.IsProject(projectPath) { + out.Fatalf("%s is not a model project: it has no build.gradle.kts declaring the "+ + "dependencies its models are written against.\nCreate one with 'opentaint model init %s "+ + "--dependency '", projectPath, args[0]) + } + + outputPath := compileModelsOutput + if outputPath == "" { + outputPath = models.BuildDir(projectPath) + } else { + outputPath = log.AbsPathOrExit(outputPath, "output") + } + + analyzerJarPath, err := ensureAnalyzerAvailable() + if err != nil { + out.Fatalf("Failed to resolve analyzer: %s", err) + } + + if err := models.Build(projectPath, outputPath, newModelBuilder(analyzerJarPath)); err != nil { + out.Fatalf("Model compilation failed: %s", err) + } + + out.Section("Model Compile Summary"). + Field("Model project", projectPath). + Field("Compiled models written to", models.ClassesDir(outputPath)). + Render() + suggest("To apply these models in a scan run", + fmt.Sprintf("opentaint scan --dataflow-models %s", args[0])) + }, +} + +func init() { + compileCmd.AddCommand(compileModelsCmd) + + compileModelsCmd.Flags().StringVarP(&compileModelsOutput, "output", "o", "", + "Path to the compiled models (default: /.opentaint/build)") + compileCmd.AddCommand(deprecatedCommand(compileModelsCmd, "approximations", "opentaint compile models")) +} diff --git a/cli/cmd/deprecated.go b/cli/cmd/deprecated.go new file mode 100644 index 0000000000..130ceacaa1 --- /dev/null +++ b/cli/cmd/deprecated.go @@ -0,0 +1,37 @@ +package cmd + +import ( + "strings" + + "github.com/spf13/cobra" + "github.com/spf13/pflag" +) + +// deprecatedCommand returns a hidden copy of cmd under its old name. Running it prints that the +// name is deprecated, pointing at newPath, and then runs cmd with the same flags. +func deprecatedCommand(cmd *cobra.Command, oldName, newPath string) *cobra.Command { + alias := &cobra.Command{ + Use: oldName + strings.TrimPrefix(cmd.Use, cmd.Name()), + Short: cmd.Short, + Long: cmd.Long, + Args: cmd.Args, + Run: cmd.Run, + Annotations: cmd.Annotations, + Deprecated: "use '" + newPath + "' instead", + } + alias.Flags().AddFlagSet(cmd.Flags()) + return alias +} + +// addDeprecatedFlagAlias registers oldName as a hidden, deprecated name of the flag newName. +// Both names set the same value, so they can be mixed on one command line. +func addDeprecatedFlagAlias(flags *pflag.FlagSet, newName, oldName string) { + flag := flags.Lookup(newName) + flags.AddFlag(&pflag.Flag{ + Name: oldName, + Usage: flag.Usage, + Value: flag.Value, + DefValue: flag.DefValue, + }) + _ = flags.MarkDeprecated(oldName, "use --"+newName+" instead") +} diff --git a/cli/cmd/deprecated_test.go b/cli/cmd/deprecated_test.go new file mode 100644 index 0000000000..959fe6e1b7 --- /dev/null +++ b/cli/cmd/deprecated_test.go @@ -0,0 +1,80 @@ +package cmd + +import ( + "reflect" + "strings" + "testing" + + "github.com/spf13/cobra" +) + +func TestDeprecatedCommandsPointAtTheirNewNames(t *testing.T) { + for _, tc := range []struct { + old []string + newPath string + flag string + }{ + {[]string{"approximation", "init"}, "opentaint model init", "dependency"}, + {[]string{"compile", "approximations"}, "opentaint compile models", "output"}, + {[]string{"test", "approximation", "init"}, "opentaint test model init", "dependency"}, + {[]string{"test", "approximation", "run"}, "opentaint test model run", "dataflow-models"}, + } { + t.Run(strings.Join(tc.old, " "), func(t *testing.T) { + command, rest, err := rootCmd.Find(tc.old) + if err != nil || len(rest) != 0 || command.Name() != tc.old[len(tc.old)-1] { + t.Fatalf("Find(%v) = %s %v %v", tc.old, command.CommandPath(), rest, err) + } + if !strings.Contains(command.Deprecated, tc.newPath) { + t.Errorf("Deprecated = %q, want it to name %q", command.Deprecated, tc.newPath) + } + if command.IsAvailableCommand() { + t.Error("a deprecated command is listed in help") + } + if command.Run == nil || command.Flags().Lookup(tc.flag) == nil { + t.Errorf("the alias does not run the new command with its flags") + } + + current, _, err := rootCmd.Find(strings.Fields(strings.TrimPrefix(tc.newPath, "opentaint "))) + if err != nil || current.Deprecated != "" || !current.IsAvailableCommand() { + t.Errorf("new command %s is missing or hidden: %v", tc.newPath, err) + } + }) + } +} + +func TestDeprecatedFlagAliasesFeedTheNewFlag(t *testing.T) { + var dataflow, passthrough []string + command := &cobra.Command{Use: "probe", Run: func(*cobra.Command, []string) {}} + addDataflowModelsFlag(command, &dataflow) + addPassthroughModelsFlag(command, &passthrough) + + err := command.ParseFlags([]string{ + "--dataflow-models", "a", "--dataflow-approximations", "b", + "--passthrough-approximations", "c", "--passthrough-models", "d", + }) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(dataflow, []string{"a", "b"}) { + t.Errorf("dataflow models = %v, want [a b]", dataflow) + } + if !reflect.DeepEqual(passthrough, []string{"c", "d"}) { + t.Errorf("passthrough models = %v, want [c d]", passthrough) + } + for _, old := range []string{"dataflow-approximations", "passthrough-approximations"} { + flag := command.Flags().Lookup(old) + if !flag.Hidden || !strings.Contains(flag.Deprecated, "-models") { + t.Errorf("--%s: hidden=%v deprecated=%q, want a hidden alias naming the new flag", old, flag.Hidden, flag.Deprecated) + } + } +} + +func TestModelFlagsOnAnalyzerCommands(t *testing.T) { + for _, command := range []*cobra.Command{scanCmd, testRuleRunCmd, testModelRunCmd} { + for _, name := range []string{"dataflow-models", "dataflow-approximations", "passthrough-models", "passthrough-approximations"} { + if command.Flags().Lookup(name) == nil { + t.Errorf("%s has no --%s", command.CommandPath(), name) + } + } + } +} diff --git a/cli/cmd/approximation.go b/cli/cmd/model.go similarity index 54% rename from cli/cmd/approximation.go rename to cli/cmd/model.go index 61d5050c88..371b4ed129 100644 --- a/cli/cmd/approximation.go +++ b/cli/cmd/model.go @@ -3,28 +3,28 @@ package cmd import ( "fmt" - "github.com/seqra/opentaint/internal/approximation" + "github.com/seqra/opentaint/internal/models" "github.com/seqra/opentaint/internal/utils/log" "github.com/spf13/cobra" ) -var initApproximationDeps []string +var initModelDeps []string -var approximationCmd = &cobra.Command{ - Use: "approximation", - Short: "Create dataflow approximation projects", - Long: `A dataflow approximation project holds code-based models of how taint moves through +var modelCmd = &cobra.Command{ + Use: "model", + Short: "Create dataflow model projects", + Long: `A dataflow model project holds code-based models of how taint moves through methods the analyzer cannot see into, together with the exact dependency versions those models are written against. -Compile one with 'opentaint compile approximations', and apply it with the ---dataflow-approximations flag of 'opentaint scan' or 'opentaint test approximation run'.`, +Compile one with 'opentaint compile models', and apply it with the +--dataflow-models flag of 'opentaint scan' or 'opentaint test model run'.`, } -var approximationInitCmd = &cobra.Command{ +var modelInitCmd = &cobra.Command{ Use: "init ", - Short: "Create a dataflow approximation project", - Long: `Create a Gradle project for OpenTaint dataflow approximation models. + Short: "Create a dataflow model project", + Long: `Create a Gradle project for OpenTaint dataflow models. The project includes: - build.gradle.kts with compile-only dependencies, settings.gradle.kts @@ -46,29 +46,37 @@ nothing about the project under analysis, decide what the models compile against // Init replaces the build file but keeps the model sources. // Warn the user because this removes dependency pins that they added manually. - existing := approximation.IsProject(projectPath) + existing := models.IsProject(projectPath) if existing { - out.Warnf("Replacing the build file of the existing approximation project at %s; "+ + out.Warnf("Replacing the build file of the existing model project at %s; "+ "pass every dependency the models need, not only the new ones", projectPath) } - if err := approximation.Scaffold(projectPath, initApproximationDeps, analyzerJarPath); err != nil { - out.Fatalf("Failed to create approximation project: %s", err) + if err := models.Scaffold(projectPath, initModelDeps, analyzerJarPath); err != nil { + out.Fatalf("Failed to create model project: %s", err) } if existing { - fmt.Printf("Approximation project updated at %s\n", projectPath) + fmt.Printf("Model project updated at %s\n", projectPath) } else { - fmt.Printf("Approximation project initialized at %s\n", projectPath) + fmt.Printf("Model project initialized at %s\n", projectPath) } - suggest("To compile the models run", fmt.Sprintf("opentaint compile approximations %s", args[0])) + suggest("To compile the models run", fmt.Sprintf("opentaint compile models %s", args[0])) }, } func init() { - rootCmd.AddCommand(approximationCmd) - approximationCmd.AddCommand(approximationInitCmd) + rootCmd.AddCommand(modelCmd) + modelCmd.AddCommand(modelInitCmd) - approximationInitCmd.Flags().StringArrayVar(&initApproximationDeps, "dependency", nil, + modelInitCmd.Flags().StringArrayVar(&initModelDeps, "dependency", nil, "Compile-only Maven dependency coordinates the models are written against (repeatable)") + + approximationCmd := &cobra.Command{ + Use: "approximation", + Short: modelCmd.Short, + Deprecated: "use 'opentaint model' instead", + } + approximationCmd.AddCommand(deprecatedCommand(modelInitCmd, "init", "opentaint model init")) + rootCmd.AddCommand(approximationCmd) } diff --git a/cli/cmd/scan.go b/cli/cmd/scan.go index 30f51ed2fa..6df354bae9 100644 --- a/cli/cmd/scan.go +++ b/cli/cmd/scan.go @@ -34,8 +34,8 @@ type ScanConfig struct { Recompile bool LogFile string RuleID []string - PassthroughApproximations []string - DataflowApproximations []string + PassthroughModels []string + DataflowModels []string TrackExternalMethods bool IterationLimit int @@ -142,9 +142,9 @@ func addScanFlags(cmd *cobra.Command) { cmd.Flags().StringVar(&scanFlags.ProjectModelPath, "project-model", "", "Path to a pre-compiled project model (skips compilation)") cmd.Flags().StringVar(&scanFlags.LogFile, "log-file", "", "Path to the log file (default: /logs/.log)") - cmd.Flags().StringArrayVar(&scanFlags.PassthroughApproximations, "passthrough-approximations", nil, "Pass-through approximation YAML file or directory (repeatable)") + addPassthroughModelsFlag(cmd, &scanFlags.PassthroughModels) - cmd.Flags().StringArrayVar(&scanFlags.DataflowApproximations, "dataflow-approximations", nil, "Dataflow approximation project, build output, or class directory (repeatable)") + addDataflowModelsFlag(cmd, &scanFlags.DataflowModels) cmd.Flags().BoolVar(&scanFlags.TrackExternalMethods, "track-external-methods", false, "Write external-method coverage files next to the SARIF report") @@ -159,8 +159,8 @@ func currentScanBuilder(cfg ScanConfig, sourcePath string) *utils.OpentaintComma WithRuleset(cfg.Ruleset). WithSemgrepCompatibility(cfg.SemgrepCompatibilitySarif). WithRuleID(cfg.RuleID). - WithPassthroughApproximations(cfg.PassthroughApproximations). - WithDataflowApproximations(cfg.DataflowApproximations). + WithPassthroughModels(cfg.PassthroughModels). + WithDataflowModels(cfg.DataflowModels). WithTrackExternalMethods(cfg.TrackExternalMethods). WithIterationLimit(cfg.IterationLimit) if !isDefaultSeverity(cfg.Severity) { @@ -388,7 +388,7 @@ func runScan(cmd *cobra.Command, cfg ScanConfig) { for _, ruleID := range ruleIDs { nativeBuilder.AddRuleID(ruleID) } - addPassthroughApproximations(nativeBuilder, cfg.PassthroughApproximations) + addPassthroughModels(nativeBuilder, cfg.PassthroughModels) if cfg.TrackExternalMethods { nativeBuilder.SetTrackExternalMethods(true) } @@ -408,7 +408,7 @@ func runScan(cmd *cobra.Command, cfg ScanConfig) { } nativeBuilder.SetJarPath(analyzerJarPath) - addDataflowApproximations(nativeBuilder, cfg.DataflowApproximations, analyzerJarPath) + addDataflowModels(nativeBuilder, cfg.DataflowModels, analyzerJarPath) analyzerJavaRunner := newAnalyzerJavaRunner() if _, err := analyzerJavaRunner.EnsureJava(); err != nil { @@ -467,6 +467,9 @@ func runScan(cmd *cobra.Command, cfg ScanConfig) { var suggestions []output.Suggestion if analyzerFail != nil { suggestions = appendLogSuggestion(suggestions) + if analyzerLog, ok := analyzerLogSuggestion(analyzerFail, nativeBuilder.LogsFile()); ok { + suggestions = append(suggestions, analyzerLog) + } } if report != nil { // Scan does not expose summary's filter/group flags, so pass zero values: diff --git a/cli/cmd/suggest.go b/cli/cmd/suggest.go index 9508a86cb0..bd64b5a54d 100644 --- a/cli/cmd/suggest.go +++ b/cli/cmd/suggest.go @@ -4,6 +4,7 @@ import ( "fmt" "os" + "github.com/seqra/opentaint/internal/analyzer" "github.com/seqra/opentaint/internal/globals" "github.com/seqra/opentaint/internal/output" ) @@ -30,6 +31,19 @@ func logSuggestion() (output.Suggestion, bool) { }, true } +// analyzerLogSuggestion points at the analyzer log after an unhandled analyzer exception +// (exit code 252), whose details are only in that log. The second result is false for other +// outcomes, and when the run has its own log file, which already holds the analyzer output. +func analyzerLogSuggestion(fail *analyzer.Error, logsFile string) (output.Suggestion, bool) { + if fail == nil || fail.ExitCode != analyzer.ExitException || logsFile == "" || globals.LogPath != "" { + return output.Suggestion{}, false + } + return output.Suggestion{ + Description: "For the analyzer exception, check the analyzer log:", + Command: logsFile, + }, true +} + // appendLogSuggestion appends the log-file pointer to s when a log file is // active and returns s unchanged otherwise. It centralizes the "lead with the // log" idiom shared by buildFailSuggestions and the scan summary tail. diff --git a/cli/cmd/suggest_test.go b/cli/cmd/suggest_test.go index 570eb7690b..60b232d26a 100644 --- a/cli/cmd/suggest_test.go +++ b/cli/cmd/suggest_test.go @@ -3,6 +3,7 @@ package cmd import ( "testing" + "github.com/seqra/opentaint/internal/analyzer" "github.com/seqra/opentaint/internal/globals" "github.com/seqra/opentaint/internal/output" ) @@ -60,3 +61,27 @@ func TestBuildFailSuggestions(t *testing.T) { t.Errorf("expected no suggestions, got %+v", got) } } + +func TestAnalyzerLogSuggestion(t *testing.T) { + orig := globals.LogPath + t.Cleanup(func() { globals.LogPath = orig }) + globals.LogPath = "" + + for code, want := range map[int]bool{analyzer.ExitException: true, analyzer.ExitOOM: false, analyzer.ExitTimeout: false, 1: false} { + sug, ok := analyzerLogSuggestion(&analyzer.Error{ExitCode: code}, "/tmp/opentaint-1/analyzer.log") + if ok != want { + t.Errorf("exit %d: suggestion shown = %v, want %v", code, ok, want) + } + if ok && sug.Command != "/tmp/opentaint-1/analyzer.log" { + t.Errorf("exit %d: command = %q, want the analyzer log", code, sug.Command) + } + } + if _, ok := analyzerLogSuggestion(nil, "/tmp/analyzer.log"); ok { + t.Error("a successful run points at the analyzer log") + } + + globals.LogPath = "/tmp/run.log" + if _, ok := analyzerLogSuggestion(&analyzer.Error{ExitCode: analyzer.ExitException}, "/tmp/analyzer.log"); ok { + t.Error("a run with its own log file also points at the analyzer log") + } +} diff --git a/cli/cmd/test.go b/cli/cmd/test.go index a31a3cc73b..2142a7c757 100644 --- a/cli/cmd/test.go +++ b/cli/cmd/test.go @@ -8,8 +8,8 @@ import ( var testCmd = &cobra.Command{ Use: "test", - Short: "Create and run rule and approximation tests", - Long: `Tools for creating test projects, running annotated rule and approximation tests, and debugging rule reachability.`, + Short: "Create and run rule and model tests", + Long: `Tools for creating test projects, running annotated rule and model tests, and debugging rule reachability.`, } var testRuleCmd = &cobra.Command{ @@ -17,14 +17,22 @@ var testRuleCmd = &cobra.Command{ Short: "Create, run, and debug detection-rule tests", } +var testModelCmd = &cobra.Command{ + Use: "model", + Short: "Create and run dataflow model tests", +} + +// testApproximationCmd is the deprecated name of testModelCmd. var testApproximationCmd = &cobra.Command{ - Use: "approximation", - Short: "Create and run dataflow-approximation tests", + Use: "approximation", + Short: testModelCmd.Short, + Deprecated: "use 'opentaint test model' instead", } func init() { rootCmd.AddCommand(testCmd) testCmd.AddCommand(testRuleCmd) + testCmd.AddCommand(testModelCmd) testCmd.AddCommand(testApproximationCmd) } @@ -33,15 +41,16 @@ func testExitCodesHelp(passedLine string) string { 0 ` + passedLine + ` 1 General failure (configuration or infrastructure error) 2 One or more tests failed (false negatives/positives or skipped samples) - 252 Unhandled analyzer exception + 252 Unhandled analyzer exception (the analyzer log path is printed) 253 Out of memory (try increasing --max-memory) 254 Analysis timed out (try increasing --timeout) 255 Project configuration error` } -func addTestRunFlags(cmd *cobra.Command, outputDir *string, timeout *time.Duration, maxMemory *string, dataflow *[]string) { +func addTestRunFlags(cmd *cobra.Command, outputDir *string, timeout *time.Duration, maxMemory *string, dataflow, passthrough *[]string) { cmd.Flags().StringVarP(outputDir, "output", "o", "", "Directory for test-result.json and test-results.sarif") cmd.Flags().DurationVar(timeout, "timeout", 600*time.Second, "Analysis timeout") cmd.Flags().StringVar(maxMemory, "max-memory", "8G", "Maximum analyzer heap size (e.g., 8G)") - cmd.Flags().StringArrayVar(dataflow, "dataflow-approximations", nil, "Dataflow approximation project, build output, or class directory (repeatable)") + addDataflowModelsFlag(cmd, dataflow) + addPassthroughModelsFlag(cmd, passthrough) } diff --git a/cli/cmd/test_approximation_run.go b/cli/cmd/test_approximation_run.go deleted file mode 100644 index 018e16c03d..0000000000 --- a/cli/cmd/test_approximation_run.go +++ /dev/null @@ -1,52 +0,0 @@ -package cmd - -import ( - "os" - "time" - - "github.com/seqra/opentaint/internal/testapprox" - "github.com/spf13/cobra" -) - -var ( - testApproxOutputDir string - testApproxTimeout time.Duration - testApproxMaxMemory string - testApproxDataflow []string -) - -var testApproximationRunCmd = &cobra.Command{ - Use: "run ", - Short: "Run dataflow approximation tests on a compiled project model", - Long: `Run the samples specified in rule-test.yaml with the supplied dataflow approximations applied. - -A built-in source-to-sink harness rule is applied automatically; positive samples reference the -approximation-rule.yaml rule with id "approximation-rule". - -` + testExitCodesHelp("All approximation tests passed"), - Args: cobra.ExactArgs(1), - Run: func(cmd *cobra.Command, args []string) { - ruleDir, err := os.MkdirTemp("", "opentaint-approx-rule-*") - if err != nil { - out.Fatalf("Failed to create temp dir for harness rule: %s", err) - } - if _, err := testapprox.WriteFixedRule(ruleDir); err != nil { - out.Fatalf("Failed to materialize harness rule: %s", err) - } - - runTestProject(args[0], testProjectOptions{ - label: "Approximation tests", - tempDir: "opentaint-test-approximations-*", - rulesets: []string{ruleDir}, - outputDir: testApproxOutputDir, - timeout: testApproxTimeout, - maxMemory: testApproxMaxMemory, - dataflowApprox: testApproxDataflow, - }) - }, -} - -func init() { - testApproximationCmd.AddCommand(testApproximationRunCmd) - addTestRunFlags(testApproximationRunCmd, &testApproxOutputDir, &testApproxTimeout, &testApproxMaxMemory, &testApproxDataflow) -} diff --git a/cli/cmd/test_init.go b/cli/cmd/test_init.go index 2c4966f822..b22181be31 100644 --- a/cli/cmd/test_init.go +++ b/cli/cmd/test_init.go @@ -4,14 +4,14 @@ import ( "fmt" "path/filepath" - "github.com/seqra/opentaint/internal/testapprox" + "github.com/seqra/opentaint/internal/testmodel" "github.com/seqra/opentaint/internal/testproject" "github.com/seqra/opentaint/internal/testrule" "github.com/spf13/cobra" ) var initRuleProjectDeps []string -var initApproxProjectDeps []string +var initModelProjectDeps []string var initRuleSinksOnly bool var initRuleSourcesOnly bool @@ -55,32 +55,32 @@ Use --dependency to add compile-only Maven dependencies for the samples.`, }, } -var testApproximationInitCmd = &cobra.Command{ +var testModelInitCmd = &cobra.Command{ Use: "init ", - Short: "Create a dataflow approximation test project", - Long: `Create a minimal Gradle project for testing OpenTaint dataflow approximations. + Short: "Create a dataflow model test project", + Long: `Create a minimal Gradle project for testing OpenTaint dataflow models. The project includes: - build.gradle.kts with compile-only dependencies - settings.gradle.kts - - approximation-rule.yaml, the fixed source-to-sink rule the samples are checked against + - model-rule.yaml, the fixed source-to-sink rule the samples are checked against - src/main/java/test/ with Taint.java (the fixed source() and sink()) for test sample sources Positive and negative samples are specified via rule-test.yaml. -The approximation under test is supplied separately at test time with ---dataflow-approximations. +The model under test is supplied separately at test time with +--dataflow-models. Use --dependency to add compile-only Maven dependencies for the samples.`, Args: cobra.ExactArgs(1), Run: func(cmd *cobra.Command, args []string) { - if err := testproject.Bootstrap(args[0], "approximation-test-project", initApproxProjectDeps); err != nil { + if err := testproject.Bootstrap(args[0], "model-test-project", initModelProjectDeps); err != nil { out.Fatalf("Failed to bootstrap test project: %s", err) } - if err := testapprox.Scaffold(args[0]); err != nil { - out.Fatalf("Failed to scaffold approximation project: %s", err) + if err := testmodel.Scaffold(args[0]); err != nil { + out.Fatalf("Failed to scaffold model test project: %s", err) } - fmt.Printf("Approximation test project initialized at %s\n", args[0]) + fmt.Printf("Model test project initialized at %s\n", args[0]) }, } @@ -93,7 +93,8 @@ func init() { testRuleInitCmd.Flags().BoolVar(&initRuleSourcesOnly, "sources-only", false, "Create only the sources test project") - testApproximationCmd.AddCommand(testApproximationInitCmd) - testApproximationInitCmd.Flags().StringArrayVar(&initApproxProjectDeps, "dependency", nil, + testModelCmd.AddCommand(testModelInitCmd) + testModelInitCmd.Flags().StringArrayVar(&initModelProjectDeps, "dependency", nil, "Compile-only Maven dependency coordinates for generated samples (repeatable)") + testApproximationCmd.AddCommand(deprecatedCommand(testModelInitCmd, "init", "opentaint test model init")) } diff --git a/cli/cmd/test_model_run.go b/cli/cmd/test_model_run.go new file mode 100644 index 0000000000..88eabec863 --- /dev/null +++ b/cli/cmd/test_model_run.go @@ -0,0 +1,55 @@ +package cmd + +import ( + "os" + "time" + + "github.com/seqra/opentaint/internal/testmodel" + "github.com/spf13/cobra" +) + +var ( + testModelOutputDir string + testModelTimeout time.Duration + testModelMaxMemory string + testModelDataflow []string + testModelPassthrough []string +) + +var testModelRunCmd = &cobra.Command{ + Use: "run ", + Short: "Run dataflow model tests on a compiled project model", + Long: `Run the samples specified in rule-test.yaml with the supplied dataflow models applied. + +A built-in source-to-sink harness rule is applied automatically; positive samples reference the +model-rule.yaml rule with id "model-rule". + +` + testExitCodesHelp("All model tests passed"), + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + ruleDir, err := os.MkdirTemp("", "opentaint-model-rule-*") + if err != nil { + out.Fatalf("Failed to create temp dir for harness rule: %s", err) + } + if _, err := testmodel.WriteFixedRule(ruleDir); err != nil { + out.Fatalf("Failed to materialize harness rule: %s", err) + } + + runTestProject(args[0], testProjectOptions{ + label: "Model tests", + tempDir: "opentaint-test-models-*", + rulesets: []string{ruleDir}, + outputDir: testModelOutputDir, + timeout: testModelTimeout, + maxMemory: testModelMaxMemory, + dataflowModels: testModelDataflow, + passthroughModels: testModelPassthrough, + }) + }, +} + +func init() { + testModelCmd.AddCommand(testModelRunCmd) + addTestRunFlags(testModelRunCmd, &testModelOutputDir, &testModelTimeout, &testModelMaxMemory, &testModelDataflow, &testModelPassthrough) + testApproximationCmd.AddCommand(deprecatedCommand(testModelRunCmd, "run", "opentaint test model run")) +} diff --git a/cli/cmd/test_rule_run.go b/cli/cmd/test_rule_run.go index 6c7261e0fa..3f78deb108 100644 --- a/cli/cmd/test_rule_run.go +++ b/cli/cmd/test_rule_run.go @@ -39,8 +39,8 @@ compiled project model. timeout: testRulesTimeout, maxMemory: testRulesMaxMemory, ruleIDs: testRulesRuleID, - dataflowApprox: testRulesDataflow, - passthroughApprox: testRulesPassthrough, + dataflowModels: testRulesDataflow, + passthroughModels: testRulesPassthrough, includeBuiltinRules: true, }) }, @@ -54,8 +54,8 @@ type testProjectOptions struct { timeout time.Duration maxMemory string ruleIDs []string - dataflowApprox []string - passthroughApprox []string + dataflowModels []string + passthroughModels []string includeBuiltinRules bool } @@ -128,8 +128,8 @@ func runTestProject(projectModelArg string, opts testProjectOptions) { } builder.SetJarPath(analyzerJarPath) - addDataflowApproximations(builder, opts.dataflowApprox, analyzerJarPath) - addPassthroughApproximations(builder, opts.passthroughApprox) + addDataflowModels(builder, opts.dataflowModels, analyzerJarPath) + addPassthroughModels(builder, opts.passthroughModels) javaRunner := newAnalyzerJavaRunner() if _, err := javaRunner.EnsureJava(); err != nil { @@ -150,6 +150,9 @@ func runTestProject(projectModelArg string, opts testProjectOptions) { fmt.Printf("Test results: %s\n", resultPath) if analyzerFail != nil { + if analyzerLog, ok := analyzerLogSuggestion(analyzerFail, builder.LogsFile()); ok { + out.Suggestions(analyzerLog) + } os.Exit(analyzerFail.ExitCode) } @@ -171,7 +174,6 @@ func init() { testRuleCmd.AddCommand(testRuleRunCmd) testRuleRunCmd.Flags().StringArrayVar(&testRulesRuleset, "ruleset", nil, "Ruleset file or directory to test (repeatable)") - addTestRunFlags(testRuleRunCmd, &testRulesOutputDir, &testRulesTimeout, &testRulesMaxMemory, &testRulesDataflow) + addTestRunFlags(testRuleRunCmd, &testRulesOutputDir, &testRulesTimeout, &testRulesMaxMemory, &testRulesDataflow, &testRulesPassthrough) testRuleRunCmd.Flags().StringArrayVar(&testRulesRuleID, "rule-id", nil, "Run only rules with this ID (repeatable)") - testRuleRunCmd.Flags().StringArrayVar(&testRulesPassthrough, "passthrough-approximations", nil, "Pass-through approximation YAML file or directory (repeatable)") } diff --git a/cli/internal/approximation/build.go b/cli/internal/models/build.go similarity index 93% rename from cli/internal/approximation/build.go rename to cli/internal/models/build.go index 20f1a89d97..b661072f35 100644 --- a/cli/internal/approximation/build.go +++ b/cli/internal/models/build.go @@ -1,4 +1,4 @@ -package approximation +package models import ( "errors" @@ -144,9 +144,9 @@ func build(projectDir, outputDir string, inputs stamp, builder Builder) error { } sweepStaging(outputDir) - buildTempDir, err := os.MkdirTemp("", "opentaint-approximation-build-*") + buildTempDir, err := os.MkdirTemp("", "opentaint-model-build-*") if err != nil { - return fmt.Errorf("failed to create temp directory for approximation build: %w", err) + return fmt.Errorf("failed to create temp directory for model build: %w", err) } defer func() { _ = os.RemoveAll(buildTempDir) }() @@ -164,7 +164,7 @@ func build(projectDir, outputDir string, inputs stamp, builder Builder) error { return fmt.Errorf("failed to clear %s: %w", staging, err) } if err := os.MkdirAll(ClassesDir(staging), 0o755); err != nil { - return fmt.Errorf("failed to create approximation build output %s: %w", staging, err) + return fmt.Errorf("failed to create model build output %s: %w", staging, err) } dependencies, err := collectClasses(projectModelDir, ClassesDir(staging)) @@ -172,7 +172,7 @@ func build(projectDir, outputDir string, inputs stamp, builder Builder) error { return err } if !containsClassFiles(ClassesDir(staging)) { - return fmt.Errorf("approximation project %s compiled to no classes; it has no models under src/main/java", projectDir) + return fmt.Errorf("model project %s compiled to no classes; it has no models under src/main/java", projectDir) } if err := writeDescriptor(staging, descriptor{ @@ -216,7 +216,7 @@ func checkOutputDir(outputDir string) error { } if !empty { return fmt.Errorf( - "output directory %s already exists and is not an approximation build output; "+ + "output directory %s already exists and is not a model build output; "+ "remove it or choose another --output path", outputDir) } return nil @@ -270,7 +270,7 @@ func isEmptyDir(dir string) (bool, error) { func collectClasses(projectModelDir, destDir string) ([]string, error) { config, err := project.LoadConfig(projectModelDir) if err != nil { - return nil, fmt.Errorf("approximation build produced no readable project model: %w", err) + return nil, fmt.Errorf("model build produced no readable project model: %w", err) } for _, module := range config.AllModules() { @@ -310,7 +310,7 @@ func copyTree(srcDir, destDir string) error { } if _, err := os.Stat(target); err == nil { return fmt.Errorf( - "two approximation modules produce %s; an approximation class maps to exactly one target class", + "two model modules produce %s; a model class maps to exactly one target class", filepath.ToSlash(rel), ) } diff --git a/cli/internal/approximation/descriptor.go b/cli/internal/models/descriptor.go similarity index 85% rename from cli/internal/approximation/descriptor.go rename to cli/internal/models/descriptor.go index cb118783ce..a578f3bd74 100644 --- a/cli/internal/approximation/descriptor.go +++ b/cli/internal/models/descriptor.go @@ -1,4 +1,4 @@ -package approximation +package models import ( "fmt" @@ -8,7 +8,7 @@ import ( "gopkg.in/yaml.v2" ) -const descriptorHeader = "# Generated by 'opentaint compile approximations'. Do not edit.\n" +const descriptorHeader = "# Generated by 'opentaint compile models'. Do not edit.\n" // descriptor identifies the source and dependencies of a model build. type descriptor struct { @@ -34,7 +34,7 @@ func writeDescriptor(outputDir string, d descriptor) error { } body, err := yaml.Marshal(d) if err != nil { - return fmt.Errorf("failed to render the approximation descriptor: %w", err) + return fmt.Errorf("failed to render the model descriptor: %w", err) } path := filepath.Join(outputDir, descriptorName) if err := os.WriteFile(path, append([]byte(descriptorHeader), body...), 0o644); err != nil { diff --git a/cli/internal/approximation/approximation_test.go b/cli/internal/models/models_test.go similarity index 98% rename from cli/internal/approximation/approximation_test.go rename to cli/internal/models/models_test.go index 619f83333d..2bf513b759 100644 --- a/cli/internal/approximation/approximation_test.go +++ b/cli/internal/models/models_test.go @@ -1,4 +1,4 @@ -package approximation +package models import ( "os" @@ -361,7 +361,7 @@ func TestResolveLooseSourcesReportsMigration(t *testing.T) { if err == nil { t.Fatal("expected loose .java sources to be rejected") } - if !strings.Contains(err.Error(), "approximation init") { + if !strings.Contains(err.Error(), "opentaint model init") { t.Errorf("error should point at the migration command, got: %v", err) } } @@ -486,7 +486,7 @@ func TestBuildRefusesToOverwriteUnrelatedDirectory(t *testing.T) { if err == nil { t.Fatal("expected a build into an unrelated directory to be refused") } - if !strings.Contains(err.Error(), "not an approximation build output") { + if !strings.Contains(err.Error(), "not a model build output") { t.Errorf("unexpected error: %v", err) } if content, readErr := os.ReadFile(precious); readErr != nil || string(content) != "important user data" { @@ -542,7 +542,7 @@ func TestBuildRejectsCollidingModelClasses(t *testing.T) { err := Build(project, BuildDir(project), builder) if err == nil { - t.Fatal("expected colliding approximation classes to fail the build") + t.Fatal("expected colliding model classes to fail the build") } if !strings.Contains(err.Error(), "exactly one target class") { t.Errorf("unexpected error: %v", err) diff --git a/cli/internal/approximation/project.go b/cli/internal/models/project.go similarity index 98% rename from cli/internal/approximation/project.go rename to cli/internal/models/project.go index eb8a721d94..ed257c2f71 100644 --- a/cli/internal/approximation/project.go +++ b/cli/internal/models/project.go @@ -1,8 +1,8 @@ -// Package approximation manages Gradle projects for dataflow models. +// Package models manages Gradle projects for dataflow models. // // Each project specifies the library versions that its models use. // The scanned project does not supply these compile dependencies. -package approximation +package models import ( "bytes" diff --git a/cli/internal/approximation/resolve.go b/cli/internal/models/resolve.go similarity index 85% rename from cli/internal/approximation/resolve.go rename to cli/internal/models/resolve.go index c0505e3474..c3b670c87a 100644 --- a/cli/internal/approximation/resolve.go +++ b/cli/internal/models/resolve.go @@ -1,4 +1,4 @@ -package approximation +package models import ( "fmt" @@ -59,7 +59,7 @@ func checkClasspathAgrees(classDirs []string) error { return fmt.Errorf( "%s is offered with different content by %s and by %s; which one an analysis "+ "reads is not defined.\nDrop one of the copies, or pass only one of these "+ - "approximation paths", + "model paths", class, previous, dir) } } @@ -77,10 +77,10 @@ func checkClasspathAgrees(classDirs []string) error { func Resolve(path string, builder Builder) ([]string, error) { info, err := os.Stat(path) if err != nil { - return nil, fmt.Errorf("approximation path does not exist: %w", err) + return nil, fmt.Errorf("model path does not exist: %w", err) } if !info.IsDir() { - return nil, fmt.Errorf("approximation path %s is not a directory", path) + return nil, fmt.Errorf("model path %s is not a directory", path) } classDirs, err := resolveDir(path, builder) @@ -118,7 +118,7 @@ func resolveDir(dir string, builder Builder) ([]string, error) { // order the filesystem happens to hand them back. entries, err := os.ReadDir(dir) if err != nil { - return nil, fmt.Errorf("failed to read approximation directory %s: %w", dir, err) + return nil, fmt.Errorf("failed to read model directory %s: %w", dir, err) } // Descending past classes this directory holds itself would leave them unused. @@ -141,13 +141,13 @@ func resolveDir(dir string, builder Builder) ([]string, error) { } // ambiguousDirError reports a directory that is neither a class directory nor a -// directory of approximation paths. +// directory of model paths. func ambiguousDirError(dir, classFile string) error { return fmt.Errorf( - "%s holds compiled classes of its own (e.g. %s) and an approximation project or build "+ + "%s holds compiled classes of its own (e.g. %s) and a model project or build "+ "output below it, so it is neither a class directory nor a directory of them.\n"+ - "Move the classes into a directory of their own, or pass each approximation path to "+ - "--dataflow-approximations separately", + "Move the classes into a directory of their own, or pass each model path to "+ + "--dataflow-models separately", dir, classFile) } @@ -155,12 +155,12 @@ func ambiguousDirError(dir, classFile string) error { func emptyPathError(path string) error { if source, found := firstJavaSource(path); found { return fmt.Errorf( - "%s holds approximation sources (e.g. %s) but no approximation project: nothing declares "+ + "%s holds model sources (e.g. %s) but no model project: nothing declares "+ "the dependencies to compile the models against.\n"+ - "Create a project per model directory with 'opentaint approximation init "+ + "Create a project per model directory with 'opentaint model init "+ "--dependency ' and move its sources under /src/main/java", path, source, ) } - return fmt.Errorf("%s contains no approximation project, build output or compiled classes", path) + return fmt.Errorf("%s contains no model project, build output or compiled classes", path) } diff --git a/cli/internal/approximation/scaffold.go b/cli/internal/models/scaffold.go similarity index 90% rename from cli/internal/approximation/scaffold.go rename to cli/internal/models/scaffold.go index d3d9b1e09d..578b7893fe 100644 --- a/cli/internal/approximation/scaffold.go +++ b/cli/internal/models/scaffold.go @@ -1,4 +1,4 @@ -package approximation +package models import ( "archive/zip" @@ -11,7 +11,7 @@ import ( "github.com/seqra/opentaint/internal/testproject" ) -const gradleProjectName = "approximation-models" +const gradleProjectName = "dependency-models" // Scaffold creates a Gradle project for dataflow models. func Scaffold(projectDir string, dependencies []string, analyzerJarPath string) error { @@ -30,7 +30,7 @@ func Scaffold(projectDir string, dependencies []string, analyzerJarPath string) return os.MkdirAll(filepath.Join(projectDir, "src", "main", "java"), 0o755) } -// ExtractApiJar reads the approximation API jar from the analyzer jar. +// ExtractApiJar reads the model API jar from the analyzer jar. func ExtractApiJar(analyzerJarPath string) ([]byte, error) { reader, err := zip.OpenReader(analyzerJarPath) if err != nil { @@ -56,7 +56,7 @@ func readZipEntry(file *zip.File) ([]byte, error) { return io.ReadAll(content) } -// WriteApiJar writes the approximation API jar to a model project. +// WriteApiJar writes the model API jar to a model project. // It does not write the file when the content is current. func WriteApiJar(projectDir string, apiJar []byte) error { path := apiJarPath(projectDir) diff --git a/cli/internal/approximation/stamp.go b/cli/internal/models/stamp.go similarity index 95% rename from cli/internal/approximation/stamp.go rename to cli/internal/models/stamp.go index 98cd6323b2..58df0cd362 100644 --- a/cli/internal/approximation/stamp.go +++ b/cli/internal/models/stamp.go @@ -1,4 +1,4 @@ -package approximation +package models import ( "crypto/sha256" @@ -43,7 +43,7 @@ func computeSourceDigest(projectDir string) (string, error) { return nil }) if err != nil { - return "", fmt.Errorf("failed to read approximation project %s: %w", projectDir, err) + return "", fmt.Errorf("failed to read model project %s: %w", projectDir, err) } sort.Strings(paths) diff --git a/cli/internal/testapprox/example/approximation-rule.yaml b/cli/internal/testmodel/example/model-rule.yaml similarity index 72% rename from cli/internal/testapprox/example/approximation-rule.yaml rename to cli/internal/testmodel/example/model-rule.yaml index 0f0ce705ae..1905d51ae7 100644 --- a/cli/internal/testapprox/example/approximation-rule.yaml +++ b/cli/internal/testmodel/example/model-rule.yaml @@ -1,9 +1,9 @@ rules: - - id: approximation-rule + - id: model-rule severity: ERROR - message: Tainted value from Taint.source() reached Taint.sink() through an approximated method + message: Tainted value from Taint.source() reached Taint.sink() through a modeled method metadata: - short-description: Approximation test source-to-sink flow + short-description: Model test source-to-sink flow languages: - java mode: taint diff --git a/cli/internal/testapprox/example/src/main/java/test/Taint.java b/cli/internal/testmodel/example/src/main/java/test/Taint.java similarity index 100% rename from cli/internal/testapprox/example/src/main/java/test/Taint.java rename to cli/internal/testmodel/example/src/main/java/test/Taint.java diff --git a/cli/internal/testapprox/testapprox.go b/cli/internal/testmodel/testmodel.go similarity index 77% rename from cli/internal/testapprox/testapprox.go rename to cli/internal/testmodel/testmodel.go index 3a686c11c2..3a3b525232 100644 --- a/cli/internal/testapprox/testapprox.go +++ b/cli/internal/testmodel/testmodel.go @@ -1,4 +1,4 @@ -package testapprox +package testmodel import ( _ "embed" @@ -9,9 +9,9 @@ import ( "github.com/seqra/opentaint/internal/utils" ) -const fixedRuleFileName = "approximation-rule.yaml" +const fixedRuleFileName = "model-rule.yaml" -//go:embed example/approximation-rule.yaml +//go:embed example/model-rule.yaml var fixedRule []byte //go:embed example/src/main/java/test/Taint.java @@ -20,7 +20,7 @@ var taintJava []byte func WriteFixedRule(dir string) (string, error) { path := filepath.Join(dir, fixedRuleFileName) if err := os.WriteFile(path, fixedRule, 0o644); err != nil { - return "", fmt.Errorf("write fixed approximation rule: %w", err) + return "", fmt.Errorf("write fixed model rule: %w", err) } return path, nil } diff --git a/cli/internal/utils/opentaint_command_builder.go b/cli/internal/utils/opentaint_command_builder.go index 1eb81a09c6..8fc543424e 100644 --- a/cli/internal/utils/opentaint_command_builder.go +++ b/cli/internal/utils/opentaint_command_builder.go @@ -181,19 +181,19 @@ func (cb *OpentaintCommandBuilder) WithRuleID(ruleIDs []string) *OpentaintComman return cb } -func (cb *OpentaintCommandBuilder) WithPassthroughApproximations(paths []string) *OpentaintCommandBuilder { +func (cb *OpentaintCommandBuilder) WithPassthroughModels(paths []string) *OpentaintCommandBuilder { for _, p := range paths { if p != "" { - cb.arrayFlags["passthrough-approximations"] = append(cb.arrayFlags["passthrough-approximations"], p) + cb.arrayFlags["passthrough-models"] = append(cb.arrayFlags["passthrough-models"], p) } } return cb } -func (cb *OpentaintCommandBuilder) WithDataflowApproximations(paths []string) *OpentaintCommandBuilder { +func (cb *OpentaintCommandBuilder) WithDataflowModels(paths []string) *OpentaintCommandBuilder { for _, p := range paths { if p != "" { - cb.arrayFlags["dataflow-approximations"] = append(cb.arrayFlags["dataflow-approximations"], p) + cb.arrayFlags["dataflow-models"] = append(cb.arrayFlags["dataflow-models"], p) } } return cb