diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 82d4c99bf7..d26e3ce124 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -81,6 +81,10 @@ dependencies { tasks.withType { dependsOn(project("samples").tasks.withType()) ensureGoEnvInitialized() + systemProperty( + "opentaint.go.models.root", + layout.projectDirectory.dir("../model/go/dataflow").asFile.absolutePath, + ) doFirst { val resolvedTestSamples = testSamples.resolve() diff --git a/core/opentaint-ir/go/go-ir-tests/build.gradle.kts b/core/opentaint-ir/go/go-ir-tests/build.gradle.kts index d93d7a9f40..bb2518b59e 100644 --- a/core/opentaint-ir/go/go-ir-tests/build.gradle.kts +++ b/core/opentaint-ir/go/go-ir-tests/build.gradle.kts @@ -21,6 +21,10 @@ tasks.withType().configureEach { dependsOn(":go:buildGoServer") useJUnitPlatform() systemProperty("goir.server.binary", goServerBinary) + systemProperty( + "opentaint.go.models.root", + rootProject.layout.projectDirectory.dir("../../model/go/dataflow").asFile.absolutePath, + ) // Show test output for debugging (started/passed/failed/skipped) testLogging { diff --git a/core/opentaint-ir/go/go-ir-tests/src/test/kotlin/org/opentaint/ir/go/test/features/ModelTests.kt b/core/opentaint-ir/go/go-ir-tests/src/test/kotlin/org/opentaint/ir/go/test/features/ModelTests.kt index a9c4337a6e..02b3370782 100644 --- a/core/opentaint-ir/go/go-ir-tests/src/test/kotlin/org/opentaint/ir/go/test/features/ModelTests.kt +++ b/core/opentaint-ir/go/go-ir-tests/src/test/kotlin/org/opentaint/ir/go/test/features/ModelTests.kt @@ -2,6 +2,7 @@ package org.opentaint.ir.go.test.features import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatThrownBy +import org.junit.jupiter.api.Assumptions.assumeTrue import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import org.opentaint.ir.go.api.GoIRApproximatedFunction @@ -38,6 +39,113 @@ import java.nio.file.Path @ExtendWith(GoIRTestExtension::class) class ModelTests { + @Test + fun `GitHub MCP server accepts bundled MCP model`(builder: GoIRTestBuilder) { + val checkout = System.getenv("GITHUB_MCP_SERVER_DIR") + assumeTrue(checkout != null, "GITHUB_MCP_SERVER_DIR is not set") + checkNotNull(checkout) + val modelRoot = Path.of(System.getProperty("opentaint.go.models.root")) + .resolve("modelcontextprotocol-go-sdk-v1") + + val program = builder.buildFromDir( + Path.of(checkout), + GoIRLoadConfig( + mode = GoIRLoadMode.PROJECT, + patterns = listOf("./..."), + modelDirs = listOf(modelRoot), + ), + ) + val inventory = program.findPackage("github.com/github/github-mcp-server/pkg/inventory")!! + val register = inventory.functions.single { it.name == "RegisterFunc" } + val addTool = program.findPackage("github.com/modelcontextprotocol/go-sdk/mcp")!! + .functions.single { + it.name == "AddTool" && it.isMethod && + it.receiverType?.toString()?.contains("Server") == true + } + + assertThat(register.findInstructions()) + .anyMatch { it.call.target?.displayName == addTool.fullName } + assertThat(addTool).isInstanceOf(GoIRApproximatedFunction::class.java) + assertThat(addTool.findInstructions()) + .anyMatch { it.call.target is GoIRCallTarget.Dynamic } + } + + @Test + fun `bundled MCP model exposes server callbacks`(builder: GoIRTestBuilder) { + val project = project( + "example.com/mcpapp", + """ + package mcpapp + + import ( + "context" + "github.com/modelcontextprotocol/go-sdk/mcp" + ) + + type Input struct { Value string } + type Output struct { Value string } + + func rawHandler(context.Context, *mcp.CallToolRequest) (*mcp.CallToolResult, error) { + return nil, nil + } + + func typedHandler(context.Context, *mcp.CallToolRequest, Input) (*mcp.CallToolResult, Output, error) { + return nil, Output{}, nil + } + + func Register(server *mcp.Server) { + server.AddTool(&mcp.Tool{Name: "raw", InputSchema: map[string]any{"type": "object"}}, rawHandler) + mcp.AddTool(server, &mcp.Tool{Name: "typed"}, typedHandler) + } + """.trimIndent(), + ) + val modelRoot = Path.of(System.getProperty("opentaint.go.models.root")) + .resolve("modelcontextprotocol-go-sdk-v1") + project.resolve("go.mod").write( + Files.readString(modelRoot.resolve("go.mod")) + .replaceFirst("module opentaint", "module example.com/mcpapp"), + ) + Files.copy(modelRoot.resolve("go.sum"), project.resolve("go.sum")) + + val program = builder.buildFromDir(project, modelConfig(modelRoot)) + val mcp = program.findPackage("github.com/modelcontextprotocol/go-sdk/mcp")!! + val modeledNames = mcp.functions + .filter { it.bodyAvailable } + .mapTo(mutableSetOf()) { it.name } + + assertThat(modeledNames).contains( + "NewServer", + "AddTool", + "AddPrompt", + "AddResource", + "AddResourceTemplate", + "AddReceivingMiddleware", + "AddSendingMiddleware", + "NewStreamableHTTPHandler", + ) + val rawAddTool = mcp.functions.single { + it.name == "AddTool" && it.isMethod && it.receiverType?.toString()?.contains("Server") == true + } + val newServer = mcp.functions.single { it.name == "NewServer" } + assertThat(newServer.findInstructions()) + .anyMatch { it.call.target is GoIRCallTarget.Dynamic } + assertThat(rawAddTool).isInstanceOf(GoIRApproximatedFunction::class.java) + assertThat(rawAddTool.findInstructions()) + .anyMatch { it.call.target is GoIRCallTarget.Dynamic } + val genericAddTool = mcp.functions.single { + it.name == "AddTool" && !it.isMethod && it.parent == null + } + assertThat(genericAddTool.bodyAvailable).isTrue() + assertThat(genericAddTool.findInstructions()) + .anyMatch { it.call.target is GoIRCallTarget.Dynamic } + val instantiatedAddTool = mcp.functions.single { + it.fullName.startsWith("github.com/modelcontextprotocol/go-sdk/mcp.AddTool[") + } + assertThat(instantiatedAddTool).isInstanceOf(GoIRApproximatedFunction::class.java) + assertThat(instantiatedAddTool.findInstructions()) + .anyMatch { it.call.target is GoIRCallTarget.Dynamic } + } + @Test fun `model body replaces dependency body after client deserialization`(builder: GoIRTestBuilder) { val project = Files.createTempDirectory("goir-model-project") @@ -627,6 +735,25 @@ class ModelTests { assertThat((callTarget as GoIRCallTarget.Function).function).isSameAs(helper) } + @Test + fun `bundled models accept an unrelated Go project`(builder: GoIRTestBuilder) { + val project = project("example.com/unrelated", "package unrelated\nfunc Target() {}\n") + val modelRoot = Path.of(System.getProperty("opentaint.go.models.root")) + val models = Files.list(modelRoot).use { paths -> + paths.iterator().asSequence().filter { Files.isRegularFile(it.resolve("go.mod")) }.toList() + } + assertThat(models).isNotEmpty() + + val program = builder.buildFromDir( + project, + GoIRLoadConfig(mode = GoIRLoadMode.PROJECT, modelDirs = models), + ) + + val withoutModels = builder.buildFromDir(project, GoIRLoadConfig(mode = GoIRLoadMode.PROJECT)) + assertThat(program.findPackage("example.com/unrelated")).isNotNull() + assertThat(program.packages.keys).containsExactlyInAnyOrderElementsOf(withoutModels.packages.keys) + } + @Test fun `unrelated model does not add packages to the analyzed project`(builder: GoIRTestBuilder) { val project = project("example.com/unrelated", "package unrelated\nfunc Target() {}\n") @@ -691,6 +818,39 @@ class ModelTests { assertThat(call.call.target?.displayName).isEqualTo("net/url.QueryEscape") } + @Test + fun `model field access uses imported target declarations`(builder: GoIRTestBuilder) { + val project = project( + "example.com/options", + """ + package options + type Options struct { Name string; Handler func(string) string } + func Register(options *Options, value string) string { return "original" } + """.trimIndent(), + ) + val model = model( + "example.com/options", + """ + package options + import target "example.com/options" + func Register(options *target.Options, value string) string { + return options.Handler(value) + } + """.trimIndent(), + ) + + val program = builder.buildFromDir(project, modelConfig(model)) + val targetPackage = program.findPackage("example.com/options")!! + val register = targetPackage.functions.single { it.name == "Register" } + val handler = register.findExpressions().single { it.fieldName == "Handler" } + + assertThat(handler.fieldIndex).isEqualTo(1) + assertThat(register.findInstructions()) + .anyMatch { it.call.target is GoIRCallTarget.Dynamic } + assertThat(targetPackage.findNamedType("Options")!!.fields.map { it.name }) + .containsExactly("Name", "Handler") + } + @Test fun `imported target declarations do not damage generic target types`(builder: GoIRTestBuilder) { val project = project( diff --git a/core/opentaint-java-querylang/samples/build.gradle.kts b/core/opentaint-java-querylang/samples/build.gradle.kts index b705dd5f2b..68503a9d28 100644 --- a/core/opentaint-java-querylang/samples/build.gradle.kts +++ b/core/opentaint-java-querylang/samples/build.gradle.kts @@ -6,5 +6,6 @@ tasks { withType { sourceCompatibility = JavaVersion.VERSION_1_8.toString() targetCompatibility = JavaVersion.VERSION_1_8.toString() + options.encoding = "UTF-8" } } diff --git a/core/src/test/kotlin/org/opentaint/go/sast/dataflow/McpModelTest.kt b/core/src/test/kotlin/org/opentaint/go/sast/dataflow/McpModelTest.kt new file mode 100644 index 0000000000..11d32952cb --- /dev/null +++ b/core/src/test/kotlin/org/opentaint/go/sast/dataflow/McpModelTest.kt @@ -0,0 +1,524 @@ +package org.opentaint.go.sast.dataflow + +import org.opentaint.common.sast.CommonAnalysisOptions +import org.opentaint.dataflow.configuration.go.serialized.GoNameMatcher +import org.opentaint.dataflow.configuration.go.serialized.GoSerializedAssignAction +import org.opentaint.dataflow.configuration.go.serialized.GoSerializedCondition +import org.opentaint.dataflow.configuration.go.serialized.GoSerializedRule +import org.opentaint.dataflow.configuration.go.serialized.GoSerializedTaintConfig +import org.opentaint.dataflow.configuration.go.serialized.GoSinkMetaData +import org.opentaint.dataflow.configuration.jvm.serialized.PositionBase.Argument +import org.opentaint.dataflow.configuration.jvm.serialized.PositionBase.Result +import org.opentaint.dataflow.configuration.jvm.serialized.PositionBaseWithModifiers +import org.opentaint.dataflow.configuration.jvm.serialized.PositionModifier +import org.opentaint.dataflow.go.rules.GoTaintConfiguration +import org.opentaint.go.config.GoDefaultConfigLoader +import org.opentaint.ir.go.api.GoIRProgram +import org.opentaint.ir.go.client.GoIRClient +import org.opentaint.ir.go.client.GoIRLoadConfig +import org.opentaint.ir.go.client.GoIRLoadMode +import org.opentaint.ir.go.ext.findFunctionByFullName +import org.junit.jupiter.api.Assumptions.assumeTrue +import java.nio.file.Files +import java.nio.file.Path +import kotlin.io.path.writeText +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue +import kotlin.time.Duration.Companion.minutes + +class McpModelTest { + @Test + fun `MCP model connects registration to tool handler`() { + val project = Files.createTempDirectory("go-mcp-model") + writeProjectModule(project) + project.resolve("mcp.go").writeText( + """ + package mcpapp + + import ( + "context" + "github.com/modelcontextprotocol/go-sdk/mcp" + ) + + func Source() string { return "" } + func Sink(string) {} + + func toolHandler(context.Context, *mcp.CallToolRequest) (*mcp.CallToolResult, error) { + Sink(Source()) + return nil, nil + } + + func Register(server *mcp.Server) { + server.AddTool( + &mcp.Tool{Name: "test", InputSchema: map[string]any{"type": "object"}}, + toolHandler, + ) + } + """.trimIndent(), + ) + assertModelConnects(project) + } + + @Test + fun `MCP model connects server options to completion handler`() { + val project = Files.createTempDirectory("go-mcp-options-model") + writeProjectModule(project) + project.resolve("mcp.go").writeText( + """ + package mcpapp + + import ( + "context" + "github.com/modelcontextprotocol/go-sdk/mcp" + ) + + func Source() string { return "" } + func Sink(string) {} + + func completionHandler(context.Context, *mcp.CompleteRequest) (*mcp.CompleteResult, error) { + Sink(Source()) + return nil, nil + } + + func Register() { + mcp.NewServer( + &mcp.Implementation{}, + &mcp.ServerOptions{CompletionHandler: completionHandler}, + ) + } + """.trimIndent(), + ) + + assertModelConnects(project) + } + + @Test + fun `MCP callback model and pass-through rule work together`() { + val project = Files.createTempDirectory("go-mcp-pass-through-model") + writeProjectModule(project) + project.resolve("mcp.go").writeText( + """ + package mcpapp + + import ( + "context" + "github.com/modelcontextprotocol/go-sdk/mcp" + ) + + func Source() string { return "" } + func Sink(error) {} + + func toolHandler(context.Context, *mcp.CallToolRequest) (*mcp.CallToolResult, error) { + Sink(mcp.ResourceNotFoundError(Source())) + return nil, nil + } + + func Register(server *mcp.Server) { + server.AddTool(&mcp.Tool{}, toolHandler) + } + """.trimIndent(), + ) + val passThrough = GoDefaultConfigLoader.loadConfig()?.passThrough.orEmpty() + assertTrue( + passThrough.any { + it.function == GoNameMatcher.Simple("ResourceNotFoundError") && + it.pkg == GoNameMatcher.Simple("github.com/modelcontextprotocol/go-sdk/mcp") + }, + ) + + GoIRClient().use { client -> + val withoutModel = client.buildFromDir(project).program + val withModel = client.buildFromDir( + project, + GoIRLoadConfig(modelDirs = listOf(modelPath())), + ).program + + assertTrue(findVulnerabilities(withoutModel, passThrough).isEmpty()) + assertTrue(findVulnerabilities(withModel).isEmpty()) + assertTrue(findVulnerabilities(withModel, passThrough).isNotEmpty()) + } + } + + @Test + fun `MCP pass-through rules use only semantic copy paths`() { + val passThrough = GoDefaultConfigLoader.loadConfig()?.passThrough.orEmpty() + val mcpRules = passThrough.filter { + it.pkg == GoNameMatcher.Simple("github.com/modelcontextprotocol/go-sdk/mcp") + } + + assertEquals( + setOf("ResourceNotFoundError", "URLElicitationRequiredError"), + mcpRules.mapTo(mutableSetOf()) { (it.function as GoNameMatcher.Simple).name }, + ) + mcpRules.forEach { rule -> + assertEquals(1, rule.copy.size) + assertEquals( + PositionBaseWithModifiers.BaseOnly(Argument(0)), + rule.copy.single().from, + ) + assertEquals( + PositionBaseWithModifiers.BaseOnly(Result), + rule.copy.single().to, + ) + } + } + + @Test + fun `MCP tool input is an untrusted callback value`() { + val project = Files.createTempDirectory("go-mcp-input-model") + writeProjectModule(project) + project.resolve("mcp.go").writeText( + """ + package mcpapp + + import ( + "context" + "github.com/modelcontextprotocol/go-sdk/mcp" + ) + + func Sink(string) {} + + func toolHandler( + context.Context, + *mcp.CallToolRequest, + map[string]string, + ) (*mcp.CallToolResult, any, error) { + return nil, nil, nil + } + + func Register(server *mcp.Server) { + mcp.AddTool( + server, + &mcp.Tool{Name: "test"}, + func( + ctx context.Context, + req *mcp.CallToolRequest, + input map[string]string, + ) (*mcp.CallToolResult, any, error) { + Sink(input["command"]) + return toolHandler(ctx, req, input) + }, + ) + } + """.trimIndent(), + ) + + val passThrough = GoDefaultConfigLoader.loadConfig()?.passThrough.orEmpty() + GoIRClient().use { client -> + val withoutModel = client.buildFromDir(project).program + val withModel = client.buildFromDir( + project, + GoIRLoadConfig(modelDirs = listOf(modelPath())), + ).program + + assertTrue(findVulnerabilities(withoutModel, passThrough).isEmpty()) + assertTrue(findVulnerabilities(withModel, passThrough).isNotEmpty()) + } + } + + @Test + fun `MCP callback and pass-through model reach a GitHub workflow mutation`() { + val project = Files.createTempDirectory("go-mcp-github-mutation") + writeProjectModule(project) + writeGoGitHubStub(project) + project.resolve("mcp.go").writeText( + """ + package mcpapp + + import ( + "context" + "strconv" + "github.com/google/go-github/v89/github" + "github.com/modelcontextprotocol/go-sdk/mcp" + ) + + func Register(server *mcp.Server, client *github.Client) { + mcp.AddTool( + server, + &mcp.Tool{Name: "run_workflow"}, + func( + ctx context.Context, + _ *mcp.CallToolRequest, + input map[string]string, + ) (*mcp.CallToolResult, any, error) { + workflowIDText := github.Ptr(input["workflow_id"]) + workflowID, err := strconv.ParseInt(*workflowIDText, 10, 64) + if err != nil { + return nil, nil, err + } + _, err = client.Actions.CreateWorkflowDispatchEventByID( + ctx, + "example", + "repository", + workflowID, + github.CreateWorkflowDispatchEventRequest{}, + ) + return nil, nil, err + }, + ) + } + """.trimIndent(), + ) + + val passThrough = GoDefaultConfigLoader.loadConfig()?.passThrough.orEmpty() + val pointerRules = passThrough.filter { + it.pkg == GoNameMatcher.Simple("github.com/google/go-github/v89/github") && + it.function == GoNameMatcher.Simple("Ptr") + } + val parseRules = passThrough.filter { + it.pkg == GoNameMatcher.Simple("strconv") && + it.function == GoNameMatcher.Simple("ParseInt") + } + assertTrue(pointerRules.isNotEmpty()) + assertTrue(parseRules.isNotEmpty()) + + GoIRClient().use { client -> + val program = client.buildFromDir( + project, + GoIRLoadConfig(modelDirs = listOf(modelPath())), + ).program + assertTrue(findGitHubMutations(program).isEmpty()) + assertTrue(findGitHubMutations(program, pointerRules).isEmpty()) + assertTrue(findGitHubMutations(program, parseRules).isEmpty()) + assertTrue(findGitHubMutations(program, pointerRules + parseRules).isNotEmpty()) + } + } + + @Test + fun `GitHub MCP callback reaches a real workflow mutation`() { + val checkout = System.getenv("GITHUB_MCP_SERVER_DIR") + assumeTrue(checkout != null, "GITHUB_MCP_SERVER_DIR is not set") + checkNotNull(checkout) + + GoIRClient().use { client -> + val program = client.buildFromDir( + Path.of(checkout), + GoIRLoadConfig( + mode = GoIRLoadMode.PROJECT, + patterns = listOf("./..."), + modelDirs = listOf(modelPath(), githubMcpModelPath()), + ), + ).program + val entryPoint = program + .findPackage("github.com/github/github-mcp-server/pkg/github")!! + .functions.single { it.name == "ActionsRunTrigger" } + val inventory = program.findPackage("github.com/github/github-mcp-server/pkg/inventory")!! + assertTrue(inventory.findNamedType("ToolsetMetadata")!!.fields.any { it.name == "ID" }) + assertTrue(inventory.findNamedType("ServerTool")!!.fields.any { it.name == "HandlerFunc" }) + + val requiredPassThrough = GoDefaultConfigLoader.loadConfig()?.passThrough.orEmpty().filter { + it.pkg == GoNameMatcher.Simple("strconv") && + it.function == GoNameMatcher.Simple("ParseInt") + } + assertTrue(requiredPassThrough.isNotEmpty()) + val withoutAdapter = client.buildFromDir( + Path.of(checkout), + GoIRLoadConfig( + mode = GoIRLoadMode.PROJECT, + patterns = listOf("./..."), + modelDirs = listOf(modelPath()), + ), + ).program + assertTrue( + findVulnerabilities( + program = withoutAdapter, + passThrough = requiredPassThrough, + entryPoint = entryPoint.fullName, + sink = githubMutationSink("CreateWorkflowDispatchEventByID", Argument(3)), + timeout = 10.minutes, + ).isEmpty(), + ) + assertTrue( + findVulnerabilities( + program = program, + entryPoint = entryPoint.fullName, + sink = githubMutationSink("CreateWorkflowDispatchEventByID", Argument(3)), + timeout = 10.minutes, + ).isEmpty(), + ) + assertTrue( + findVulnerabilities( + program = program, + passThrough = requiredPassThrough, + entryPoint = entryPoint.fullName, + sink = githubMutationSink("CreateWorkflowDispatchEventByID", Argument(3)), + timeout = 10.minutes, + ).isNotEmpty(), + ) + } + } + + private fun assertModelConnects(project: Path) { + GoIRClient().use { client -> + val withoutModel = client.buildFromDir(project).program + val withModel = client.buildFromDir( + project, + GoIRLoadConfig(modelDirs = listOf(modelPath())), + ).program + + assertTrue(findVulnerabilities(withoutModel).isEmpty()) + assertTrue(findVulnerabilities(withModel).isNotEmpty()) + } + } + + private fun modelPath(): Path = Path.of(System.getProperty("opentaint.go.models.root")) + .resolve("modelcontextprotocol-go-sdk-v1") + + private fun githubMcpModelPath(): Path = Path.of(System.getProperty("opentaint.go.models.root")) + .resolve("github-mcp-server") + + private fun writeProjectModule(project: Path) { + val goMod = Files.readString(modelPath().resolve("go.mod")) + .replaceFirst("module opentaint", "module example.com/mcpapp") + project.resolve("go.mod").writeText(goMod) + Files.copy(modelPath().resolve("go.sum"), project.resolve("go.sum")) + } + + private fun writeGoGitHubStub(project: Path) { + project.resolve("go.mod").writeText( + Files.readString(project.resolve("go.mod")) + + "\nrequire github.com/google/go-github/v89 v89.0.0\n" + + "replace github.com/google/go-github/v89 => ./go-github\n", + ) + val module = project.resolve("go-github") + val pkg = module.resolve("github") + Files.createDirectories(pkg) + module.resolve("go.mod").writeText( + "module github.com/google/go-github/v89\n\ngo 1.25\n", + ) + pkg.resolve("github.go").writeText( + """ + package github + + import "context" + + type CreateWorkflowDispatchEventRequest struct{} + type ActionsService struct{} + type Client struct { Actions *ActionsService } + + func Ptr[T any](value T) *T { return &value } + + func (*ActionsService) CreateWorkflowDispatchEventByID( + context.Context, + string, + string, + int64, + CreateWorkflowDispatchEventRequest, + ) (bool, error) { + return false, nil + } + """.trimIndent(), + ) + } + + private fun findVulnerabilities( + program: GoIRProgram, + passThrough: List = emptyList(), + entryPoint: String = "example.com/mcpapp.Register", + sink: GoSerializedRule.Sink = localSink(), + timeout: kotlin.time.Duration = 1.minutes, + ) = GoTaintAnalyzer( + program, + rules(passThrough, sink), + GoUnitResolver(), + CommonAnalysisOptions(ifdsAnalysisTimeout = timeout).taintAnalyzerOptions(), + ).use { analyzer -> + val entryPointFunction = program.findFunctionByFullName(entryPoint) + ?: error("MCP test entry point is missing") + analyzer.analyzeWithIfds(listOf(entryPointFunction)).first + } + + private fun findGitHubMutations( + program: GoIRProgram, + passThrough: List = emptyList(), + ) = findVulnerabilities( + program = program, + passThrough = passThrough, + sink = githubMutationSink("CreateWorkflowDispatchEventByID", Argument(3)), + ) + + private fun rules( + passThrough: List, + sink: GoSerializedRule.Sink, + ): GoTaintConfiguration { + fun source( + pkg: GoNameMatcher, + function: GoNameMatcher, + includeNestedValues: Boolean, + ) = GoSerializedRule.Source( + pkg = pkg, + function = function, + condition = null, + taint = buildList { + add( + GoSerializedAssignAction( + "taint", + PositionBaseWithModifiers.BaseOnly(Result), + ), + ) + if (includeNestedValues) { + add( + GoSerializedAssignAction( + "taint", + PositionBaseWithModifiers.WithModifiers(Result, listOf(PositionModifier.AnyField)), + ), + ) + } + }, + info = null, + ) + return GoTaintConfiguration().also { + it.loadConfig( + GoSerializedTaintConfig( + source = listOf( + source( + GoNameMatcher.Simple("example.com/mcpapp"), + GoNameMatcher.Simple("Source"), + includeNestedValues = false, + ), + source( + GoNameMatcher.Pattern( + "(?:github.com/modelcontextprotocol/go-sdk/mcp|" + + "github.com/github/github-mcp-server/pkg/inventory)", + ), + GoNameMatcher.Pattern("opentaintMCPIncoming(?:\\[.*])?"), + includeNestedValues = true, + ), + ), + sink = listOf(sink), + passThrough = passThrough, + ), + ) + } + } + + private fun localSink() = GoSerializedRule.Sink( + pkg = GoNameMatcher.Simple("example.com/mcpapp"), + function = GoNameMatcher.Simple("Sink"), + condition = GoSerializedCondition.ContainsMark( + "taint", + PositionBaseWithModifiers.BaseOnly(Argument(0)), + ), + trackFactsReachAnalysisEnd = emptyList(), + id = "mcp-model-test", + meta = GoSinkMetaData("MCP model test sink"), + info = null, + ) + + private fun githubMutationSink( + function: String, + position: Argument, + ) = GoSerializedRule.Sink( + pkg = GoNameMatcher.Simple("(*github.com/google/go-github/v89/github.ActionsService)"), + function = GoNameMatcher.Simple(function), + condition = GoSerializedCondition.ContainsMark( + "taint", + PositionBaseWithModifiers.BaseOnly(position), + ), + trackFactsReachAnalysisEnd = emptyList(), + id = "mcp-github-privileged-action-test", + meta = GoSinkMetaData("MCP input reaches a GitHub mutation"), + info = null, + ) +} diff --git a/model/go/config/github.com.google.go-github.v89.github.yaml b/model/go/config/github.com.google.go-github.v89.github.yaml new file mode 100644 index 0000000000..5667cc9f31 --- /dev/null +++ b/model/go/config/github.com.google.go-github.v89.github.yaml @@ -0,0 +1,11 @@ +language: go +passThrough: +- function: + package: github.com/google/go-github/v89/github + name: Ptr + receiver: false + copy: + - from: arg(0) + to: + - result + - .# diff --git a/model/go/config/github.com.modelcontextprotocol.go-sdk.mcp.yaml b/model/go/config/github.com.modelcontextprotocol.go-sdk.mcp.yaml new file mode 100644 index 0000000000..dde6f58d2e --- /dev/null +++ b/model/go/config/github.com.modelcontextprotocol.go-sdk.mcp.yaml @@ -0,0 +1,16 @@ +language: go +passThrough: +- function: + package: github.com/modelcontextprotocol/go-sdk/mcp + name: ResourceNotFoundError + receiver: false + copy: + - from: arg(0) + to: result +- function: + package: github.com/modelcontextprotocol/go-sdk/mcp + name: URLElicitationRequiredError + receiver: false + copy: + - from: arg(0) + to: result diff --git a/model/go/dataflow/README.md b/model/go/dataflow/README.md new file mode 100644 index 0000000000..2d6b71873a --- /dev/null +++ b/model/go/dataflow/README.md @@ -0,0 +1,21 @@ +# Built-in Go models + +Each child directory is one Go model module. The analyzer loads these modules by default. + +Use one module for each dependency version line. This rule prevents dependency version +conflicts between models. The model module name must be `opentaint`. Put each model package +under the exact import path of the target package. + +The `modelcontextprotocol-go-sdk-v1` module models server callbacks in MCP Go SDK 1.7.0. +It calls tool, prompt, resource, middleware, server option, and HTTP server factory handlers. +These calls let data flow analysis enter handlers that the SDK stores for a later request. +Before each call, the model converts an untrusted value to the request type. This value marks +the MCP request and the generic tool input as external input. + +The MCP model imports the target SDK. It uses target types in function signatures. It has +one local partial `Server` type because Go requires a local receiver type for a method +declaration. The model does not declare target fields or other target structs. + +The MCP pass-through rules are in `../config`. They model value flow through server setup +and error constructors. The Go model bodies model callback calls. Use both forms together +when a flow enters a stored callback and then passes through an SDK value operation. diff --git a/model/go/dataflow/github-mcp-server/github.com/github/github-mcp-server/pkg/inventory/server_tool.go b/model/go/dataflow/github-mcp-server/github.com/github/github-mcp-server/pkg/inventory/server_tool.go new file mode 100644 index 0000000000..6d73d34257 --- /dev/null +++ b/model/go/dataflow/github-mcp-server/github.com/github/github-mcp-server/pkg/inventory/server_tool.go @@ -0,0 +1,32 @@ +package inventory + +import ( + "context" + + "github.com/modelcontextprotocol/go-sdk/mcp" +) + +// ToolsetMetadata is a partial declaration for the target parameter type. +type ToolsetMetadata struct{} + +// ServerTool is a partial declaration for the target result type. +type ServerTool struct{} + +func NewServerToolWithContextHandler[In any, Out any]( + _ mcp.Tool, + _ ToolsetMetadata, + handler mcp.ToolHandlerFor[In, Out], +) ServerTool { + var ctx context.Context + _, _, _ = handler( + ctx, + opentaintMCPIncoming[*mcp.CallToolRequest](), + opentaintMCPIncoming[In](), + ) + return ServerTool{} +} + +func opentaintMCPIncoming[T any]() T { + var value T + return value +} diff --git a/model/go/dataflow/github-mcp-server/go.mod b/model/go/dataflow/github-mcp-server/go.mod new file mode 100644 index 0000000000..f5b76d3cf3 --- /dev/null +++ b/model/go/dataflow/github-mcp-server/go.mod @@ -0,0 +1,16 @@ +module opentaint + +go 1.25.0 + +require github.com/modelcontextprotocol/go-sdk v1.7.0 + +require ( + github.com/google/jsonschema-go v0.4.3 // indirect + github.com/segmentio/asm v1.1.3 // indirect + github.com/segmentio/encoding v0.5.4 // indirect + github.com/yosida95/uritemplate/v3 v3.0.2 // indirect + golang.org/x/oauth2 v0.35.0 // indirect + golang.org/x/sync v0.20.0 // indirect + golang.org/x/sys v0.41.0 // indirect + golang.org/x/time v0.15.0 // indirect +) diff --git a/model/go/dataflow/github-mcp-server/go.sum b/model/go/dataflow/github-mcp-server/go.sum new file mode 100644 index 0000000000..f93aba1677 --- /dev/null +++ b/model/go/dataflow/github-mcp-server/go.sum @@ -0,0 +1,24 @@ +github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= +github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/jsonschema-go v0.4.3 h1:/DBOLZTfDow7pe2GmaJNhltueGTtDKICi8V8p+DQPd0= +github.com/google/jsonschema-go v0.4.3/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE= +github.com/modelcontextprotocol/go-sdk v1.7.0 h1:yqjY2dsbKAC0LSuWZVBMrHgiG8ukXv6NRo0JiALay44= +github.com/modelcontextprotocol/go-sdk v1.7.0/go.mod h1:dL7u98E/zjJTGzEq+j30jQ8K2k1mb6LeAH4inEcSGts= +github.com/segmentio/asm v1.1.3 h1:WM03sfUOENvvKexOLp+pCqgb/WDjsi7EK8gIsICtzhc= +github.com/segmentio/asm v1.1.3/go.mod h1:Ld3L4ZXGNcSLRg4JBsZ3//1+f/TjYl0Mzen/DQy1EJg= +github.com/segmentio/encoding v0.5.4 h1:OW1VRern8Nw6ITAtwSZ7Idrl3MXCFwXHPgqESYfvNt0= +github.com/segmentio/encoding v0.5.4/go.mod h1:HS1ZKa3kSN32ZHVZ7ZLPLXWvOVIiZtyJnO1gPH1sKt0= +github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4= +github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4= +golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ= +golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= +golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= +golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= +golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k= +golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0= diff --git a/model/go/dataflow/modelcontextprotocol-go-sdk-v1/github.com/modelcontextprotocol/go-sdk/mcp/server.go b/model/go/dataflow/modelcontextprotocol-go-sdk-v1/github.com/modelcontextprotocol/go-sdk/mcp/server.go new file mode 100644 index 0000000000..d2da71f1cf --- /dev/null +++ b/model/go/dataflow/modelcontextprotocol-go-sdk-v1/github.com/modelcontextprotocol/go-sdk/mcp/server.go @@ -0,0 +1,88 @@ +package mcp + +import ( + "context" + "net/http" + + target "github.com/modelcontextprotocol/go-sdk/mcp" +) + +// Server is a partial declaration for method receivers. All target fields stay unchanged. +type Server struct{} + +func NewServer(_ *target.Implementation, options *target.ServerOptions) *target.Server { + if options != nil { + var ctx context.Context + options.InitializedHandler(ctx, opentaintMCPIncoming[*target.InitializedRequest]()) + options.RootsListChangedHandler(ctx, opentaintMCPIncoming[*target.RootsListChangedRequest]()) + options.ProgressNotificationHandler(ctx, opentaintMCPIncoming[*target.ProgressNotificationServerRequest]()) + _, _ = options.CompletionHandler(ctx, opentaintMCPIncoming[*target.CompleteRequest]()) + _ = options.SubscribeHandler(ctx, opentaintMCPIncoming[*target.SubscribeRequest]()) + _ = options.UnsubscribeHandler(ctx, opentaintMCPIncoming[*target.UnsubscribeRequest]()) + _ = options.GetSessionID() + } + return nil +} + +func (s *Server) AddPrompt(_ *target.Prompt, handler target.PromptHandler) { + var ctx context.Context + _, _ = handler(ctx, opentaintMCPIncoming[*target.GetPromptRequest]()) +} + +func (s *Server) AddTool(_ *target.Tool, handler target.ToolHandler) { + var ctx context.Context + _, _ = handler(ctx, opentaintMCPIncoming[*target.CallToolRequest]()) +} + +func AddTool[In, Out any]( + _ *target.Server, + _ *target.Tool, + handler target.ToolHandlerFor[In, Out], +) { + var ctx context.Context + _, _, _ = handler(ctx, opentaintMCPIncoming[*target.CallToolRequest](), opentaintMCPIncoming[In]()) +} + +func (s *Server) AddResource(_ *target.Resource, handler target.ResourceHandler) { + var ctx context.Context + _, _ = handler(ctx, opentaintMCPIncoming[*target.ReadResourceRequest]()) +} + +func (s *Server) AddResourceTemplate(_ *target.ResourceTemplate, handler target.ResourceHandler) { + var ctx context.Context + _, _ = handler(ctx, opentaintMCPIncoming[*target.ReadResourceRequest]()) +} + +func (s *Server) AddSendingMiddleware(middleware ...target.Middleware) { + invokeMiddleware(middleware) +} + +func (s *Server) AddReceivingMiddleware(middleware ...target.Middleware) { + invokeMiddleware(middleware) +} + +func NewStreamableHTTPHandler( + getServer func(*http.Request) *target.Server, + _ *target.StreamableHTTPOptions, +) *target.StreamableHTTPHandler { + getServer(opentaintMCPIncoming[*http.Request]()) + return nil +} + +func invokeMiddleware(middleware []target.Middleware) { + var handler target.MethodHandler = emptyMethodHandler + for _, wrap := range middleware { + handler = wrap(handler) + } + var ctx context.Context + _, _ = handler(ctx, opentaintMCPIncoming[string](), opentaintMCPIncoming[target.Request]()) +} + +func opentaintMCPIncoming[T any]() T { + var value T + return value +} + +func emptyMethodHandler(context.Context, string, target.Request) (target.Result, error) { + return nil, nil +} diff --git a/model/go/dataflow/modelcontextprotocol-go-sdk-v1/go.mod b/model/go/dataflow/modelcontextprotocol-go-sdk-v1/go.mod new file mode 100644 index 0000000000..f5b76d3cf3 --- /dev/null +++ b/model/go/dataflow/modelcontextprotocol-go-sdk-v1/go.mod @@ -0,0 +1,16 @@ +module opentaint + +go 1.25.0 + +require github.com/modelcontextprotocol/go-sdk v1.7.0 + +require ( + github.com/google/jsonschema-go v0.4.3 // indirect + github.com/segmentio/asm v1.1.3 // indirect + github.com/segmentio/encoding v0.5.4 // indirect + github.com/yosida95/uritemplate/v3 v3.0.2 // indirect + golang.org/x/oauth2 v0.35.0 // indirect + golang.org/x/sync v0.20.0 // indirect + golang.org/x/sys v0.41.0 // indirect + golang.org/x/time v0.15.0 // indirect +) diff --git a/model/go/dataflow/modelcontextprotocol-go-sdk-v1/go.sum b/model/go/dataflow/modelcontextprotocol-go-sdk-v1/go.sum new file mode 100644 index 0000000000..f93aba1677 --- /dev/null +++ b/model/go/dataflow/modelcontextprotocol-go-sdk-v1/go.sum @@ -0,0 +1,24 @@ +github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= +github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/jsonschema-go v0.4.3 h1:/DBOLZTfDow7pe2GmaJNhltueGTtDKICi8V8p+DQPd0= +github.com/google/jsonschema-go v0.4.3/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE= +github.com/modelcontextprotocol/go-sdk v1.7.0 h1:yqjY2dsbKAC0LSuWZVBMrHgiG8ukXv6NRo0JiALay44= +github.com/modelcontextprotocol/go-sdk v1.7.0/go.mod h1:dL7u98E/zjJTGzEq+j30jQ8K2k1mb6LeAH4inEcSGts= +github.com/segmentio/asm v1.1.3 h1:WM03sfUOENvvKexOLp+pCqgb/WDjsi7EK8gIsICtzhc= +github.com/segmentio/asm v1.1.3/go.mod h1:Ld3L4ZXGNcSLRg4JBsZ3//1+f/TjYl0Mzen/DQy1EJg= +github.com/segmentio/encoding v0.5.4 h1:OW1VRern8Nw6ITAtwSZ7Idrl3MXCFwXHPgqESYfvNt0= +github.com/segmentio/encoding v0.5.4/go.mod h1:HS1ZKa3kSN32ZHVZ7ZLPLXWvOVIiZtyJnO1gPH1sKt0= +github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4= +github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4= +golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ= +golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= +golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= +golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= +golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k= +golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0= diff --git a/model/java/dataflow/kotlin/src/main/java/org/opentaint/jvm/dataflow/approximations/kotlin/Builders.java b/model/java/dataflow/kotlin/src/main/java/opentaint/kotlinx/coroutines/BuildersKt.java similarity index 93% rename from model/java/dataflow/kotlin/src/main/java/org/opentaint/jvm/dataflow/approximations/kotlin/Builders.java rename to model/java/dataflow/kotlin/src/main/java/opentaint/kotlinx/coroutines/BuildersKt.java index b89ced3b4a..7842b5430f 100644 --- a/model/java/dataflow/kotlin/src/main/java/org/opentaint/jvm/dataflow/approximations/kotlin/Builders.java +++ b/model/java/dataflow/kotlin/src/main/java/opentaint/kotlinx/coroutines/BuildersKt.java @@ -1,22 +1,19 @@ -package org.opentaint.jvm.dataflow.approximations.kotlin; +package opentaint.kotlinx.coroutines; import kotlin.Unit; import kotlin.coroutines.Continuation; import kotlin.coroutines.CoroutineContext; import kotlin.jvm.functions.Function2; -import kotlinx.coroutines.BuildersKt; import kotlinx.coroutines.CompletableDeferred; import kotlinx.coroutines.CoroutineDispatcher; import kotlinx.coroutines.CoroutineScope; import kotlinx.coroutines.CoroutineStart; import kotlinx.coroutines.Deferred; import kotlinx.coroutines.Job; -import org.opentaint.ir.approximation.annotation.Approximate; import org.opentaint.jvm.dataflow.approximations.ArgumentTypeContext; import org.opentaint.jvm.dataflow.approximations.OpentaintNdUtil; -@Approximate(BuildersKt.class) -public class Builders { +public class BuildersKt { public static final T runBlocking(CoroutineContext ctx, @ArgumentTypeContext Function2, ? extends Object> body) { return (T) body.invoke(null, null); diff --git a/model/java/dataflow/kotlin/src/main/java/org/opentaint/jvm/dataflow/approximations/kotlin/BuildersBuilders.java b/model/java/dataflow/kotlin/src/main/java/opentaint/kotlinx/coroutines/BuildersKt__BuildersKt.java similarity index 74% rename from model/java/dataflow/kotlin/src/main/java/org/opentaint/jvm/dataflow/approximations/kotlin/BuildersBuilders.java rename to model/java/dataflow/kotlin/src/main/java/opentaint/kotlinx/coroutines/BuildersKt__BuildersKt.java index 2190eeaa92..636536ed35 100644 --- a/model/java/dataflow/kotlin/src/main/java/org/opentaint/jvm/dataflow/approximations/kotlin/BuildersBuilders.java +++ b/model/java/dataflow/kotlin/src/main/java/opentaint/kotlinx/coroutines/BuildersKt__BuildersKt.java @@ -1,14 +1,12 @@ -package org.opentaint.jvm.dataflow.approximations.kotlin; +package opentaint.kotlinx.coroutines; import kotlin.coroutines.Continuation; import kotlin.coroutines.CoroutineContext; import kotlin.jvm.functions.Function2; import kotlinx.coroutines.CoroutineScope; -import org.opentaint.ir.approximation.annotation.ApproximateByName; import org.opentaint.jvm.dataflow.approximations.ArgumentTypeContext; -@ApproximateByName("kotlinx.coroutines.BuildersKt__BuildersKt") -public class BuildersBuilders { +public class BuildersKt__BuildersKt { public static final T runBlocking(CoroutineContext ctx, @ArgumentTypeContext Function2, ? extends Object> body) { return (T) body.invoke(null, null); diff --git a/model/java/dataflow/kotlin/src/main/java/org/opentaint/jvm/dataflow/approximations/kotlin/BuildersBuildersCommon.java b/model/java/dataflow/kotlin/src/main/java/opentaint/kotlinx/coroutines/BuildersKt__Builders_commonKt.java similarity index 92% rename from model/java/dataflow/kotlin/src/main/java/org/opentaint/jvm/dataflow/approximations/kotlin/BuildersBuildersCommon.java rename to model/java/dataflow/kotlin/src/main/java/opentaint/kotlinx/coroutines/BuildersKt__Builders_commonKt.java index 190474542f..85e97d568a 100644 --- a/model/java/dataflow/kotlin/src/main/java/org/opentaint/jvm/dataflow/approximations/kotlin/BuildersBuildersCommon.java +++ b/model/java/dataflow/kotlin/src/main/java/opentaint/kotlinx/coroutines/BuildersKt__Builders_commonKt.java @@ -1,4 +1,4 @@ -package org.opentaint.jvm.dataflow.approximations.kotlin; +package opentaint.kotlinx.coroutines; import kotlin.Unit; import kotlin.coroutines.Continuation; @@ -10,12 +10,10 @@ import kotlinx.coroutines.CoroutineStart; import kotlinx.coroutines.Deferred; import kotlinx.coroutines.Job; -import org.opentaint.ir.approximation.annotation.ApproximateByName; import org.opentaint.jvm.dataflow.approximations.ArgumentTypeContext; import org.opentaint.jvm.dataflow.approximations.OpentaintNdUtil; -@ApproximateByName("kotlinx.coroutines.BuildersKt__Builders_commonKt") -public class BuildersBuildersCommon { +public class BuildersKt__Builders_commonKt { public static final Job launch(CoroutineScope scope, CoroutineContext ctx, CoroutineStart start, @ArgumentTypeContext Function2, ? extends Object> body) { if (OpentaintNdUtil.nextBool()) return null; diff --git a/model/java/dataflow/stdlib/src/main/java/org/opentaint/jvm/dataflow/approximations/stdlib/Thread.java b/model/java/dataflow/stdlib/src/main/java/opentaint/java/lang/Thread.java similarity index 89% rename from model/java/dataflow/stdlib/src/main/java/org/opentaint/jvm/dataflow/approximations/stdlib/Thread.java rename to model/java/dataflow/stdlib/src/main/java/opentaint/java/lang/Thread.java index ffe5f04bc0..3b1cbd155c 100644 --- a/model/java/dataflow/stdlib/src/main/java/org/opentaint/jvm/dataflow/approximations/stdlib/Thread.java +++ b/model/java/dataflow/stdlib/src/main/java/opentaint/java/lang/Thread.java @@ -1,10 +1,8 @@ -package org.opentaint.jvm.dataflow.approximations.stdlib; +package opentaint.java.lang; -import org.opentaint.ir.approximation.annotation.Approximate; import org.opentaint.jvm.dataflow.approximations.ArgumentTypeContext; import org.opentaint.jvm.dataflow.approximations.OpentaintNdUtil; -@Approximate(java.lang.Thread.class) public class Thread { public void start() { diff --git a/model/java/dataflow/stdlib/src/main/java/org/opentaint/jvm/dataflow/approximations/stdlib/Optional.java b/model/java/dataflow/stdlib/src/main/java/opentaint/java/util/Optional.java similarity index 94% rename from model/java/dataflow/stdlib/src/main/java/org/opentaint/jvm/dataflow/approximations/stdlib/Optional.java rename to model/java/dataflow/stdlib/src/main/java/opentaint/java/util/Optional.java index 9fa8150f7d..1251a1e1e9 100644 --- a/model/java/dataflow/stdlib/src/main/java/org/opentaint/jvm/dataflow/approximations/stdlib/Optional.java +++ b/model/java/dataflow/stdlib/src/main/java/opentaint/java/util/Optional.java @@ -1,6 +1,5 @@ -package org.opentaint.jvm.dataflow.approximations.stdlib; +package opentaint.java.util; -import org.opentaint.ir.approximation.annotation.Approximate; import org.opentaint.jvm.dataflow.approximations.ArgumentTypeContext; import java.util.function.Consumer; @@ -8,7 +7,6 @@ import java.util.function.Predicate; import java.util.function.Supplier; -@Approximate(java.util.Optional.class) public class Optional { public void ifPresent(@ArgumentTypeContext Consumer action) { java.util.Optional t = (java.util.Optional) (Object) this; diff --git a/model/java/dataflow/stdlib/src/main/java/org/opentaint/jvm/dataflow/approximations/stdlib/CompletableFuture.java b/model/java/dataflow/stdlib/src/main/java/opentaint/java/util/concurrent/CompletableFuture.java similarity index 99% rename from model/java/dataflow/stdlib/src/main/java/org/opentaint/jvm/dataflow/approximations/stdlib/CompletableFuture.java rename to model/java/dataflow/stdlib/src/main/java/opentaint/java/util/concurrent/CompletableFuture.java index 565a87118b..71dd706437 100644 --- a/model/java/dataflow/stdlib/src/main/java/org/opentaint/jvm/dataflow/approximations/stdlib/CompletableFuture.java +++ b/model/java/dataflow/stdlib/src/main/java/opentaint/java/util/concurrent/CompletableFuture.java @@ -1,6 +1,5 @@ -package org.opentaint.jvm.dataflow.approximations.stdlib; +package opentaint.java.util.concurrent; -import org.opentaint.ir.approximation.annotation.Approximate; import org.opentaint.jvm.dataflow.approximations.ArgumentTypeContext; import org.opentaint.jvm.dataflow.approximations.OpentaintNdUtil; @@ -12,7 +11,6 @@ import java.util.function.Function; import java.util.function.Supplier; -@Approximate(java.util.concurrent.CompletableFuture.class) public class CompletableFuture { public static java.util.concurrent.CompletableFuture supplyAsync(@ArgumentTypeContext Supplier supplier) { diff --git a/model/java/dataflow/stdlib/src/main/java/org/opentaint/jvm/dataflow/approximations/stdlib/CompletionStage.java b/model/java/dataflow/stdlib/src/main/java/opentaint/java/util/concurrent/CompletionStage.java similarity index 99% rename from model/java/dataflow/stdlib/src/main/java/org/opentaint/jvm/dataflow/approximations/stdlib/CompletionStage.java rename to model/java/dataflow/stdlib/src/main/java/opentaint/java/util/concurrent/CompletionStage.java index 96dcfec2d4..1a605bc715 100644 --- a/model/java/dataflow/stdlib/src/main/java/org/opentaint/jvm/dataflow/approximations/stdlib/CompletionStage.java +++ b/model/java/dataflow/stdlib/src/main/java/opentaint/java/util/concurrent/CompletionStage.java @@ -1,6 +1,5 @@ -package org.opentaint.jvm.dataflow.approximations.stdlib; +package opentaint.java.util.concurrent; -import org.opentaint.ir.approximation.annotation.Approximate; import org.opentaint.jvm.dataflow.approximations.ArgumentTypeContext; import org.opentaint.jvm.dataflow.approximations.OpentaintNdUtil; @@ -11,7 +10,6 @@ import java.util.function.Consumer; import java.util.function.Function; -@Approximate(java.util.concurrent.CompletionStage.class) public class CompletionStage { public java.util.concurrent.CompletionStage thenApply(@ArgumentTypeContext Function fn) throws Throwable { diff --git a/model/java/dataflow/stdlib/src/main/java/org/opentaint/jvm/dataflow/approximations/stdlib/Executor.java b/model/java/dataflow/stdlib/src/main/java/opentaint/java/util/concurrent/Executor.java similarity index 53% rename from model/java/dataflow/stdlib/src/main/java/org/opentaint/jvm/dataflow/approximations/stdlib/Executor.java rename to model/java/dataflow/stdlib/src/main/java/opentaint/java/util/concurrent/Executor.java index 2b39b141d6..b9df2d1941 100644 --- a/model/java/dataflow/stdlib/src/main/java/org/opentaint/jvm/dataflow/approximations/stdlib/Executor.java +++ b/model/java/dataflow/stdlib/src/main/java/opentaint/java/util/concurrent/Executor.java @@ -1,9 +1,7 @@ -package org.opentaint.jvm.dataflow.approximations.stdlib; +package opentaint.java.util.concurrent; -import org.opentaint.ir.approximation.annotation.Approximate; import org.opentaint.jvm.dataflow.approximations.ArgumentTypeContext; -@Approximate(java.util.concurrent.Executor.class) public class Executor { public void execute(@ArgumentTypeContext Runnable command) { diff --git a/model/java/dataflow/stdlib/src/main/java/org/opentaint/jvm/dataflow/approximations/stdlib/ExecutorService.java b/model/java/dataflow/stdlib/src/main/java/opentaint/java/util/concurrent/ExecutorService.java similarity index 92% rename from model/java/dataflow/stdlib/src/main/java/org/opentaint/jvm/dataflow/approximations/stdlib/ExecutorService.java rename to model/java/dataflow/stdlib/src/main/java/opentaint/java/util/concurrent/ExecutorService.java index 8de232b17a..f13f7143cd 100644 --- a/model/java/dataflow/stdlib/src/main/java/org/opentaint/jvm/dataflow/approximations/stdlib/ExecutorService.java +++ b/model/java/dataflow/stdlib/src/main/java/opentaint/java/util/concurrent/ExecutorService.java @@ -1,6 +1,5 @@ -package org.opentaint.jvm.dataflow.approximations.stdlib; +package opentaint.java.util.concurrent; -import org.opentaint.ir.approximation.annotation.Approximate; import org.opentaint.jvm.dataflow.approximations.ArgumentTypeContext; import java.util.ArrayList; @@ -12,7 +11,6 @@ import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; -@Approximate(java.util.concurrent.ExecutorService.class) public class ExecutorService { public void execute(@ArgumentTypeContext Runnable command) { diff --git a/model/java/dataflow/stdlib/src/main/java/org/opentaint/jvm/dataflow/approximations/stdlib/Stream.java b/model/java/dataflow/stdlib/src/main/java/opentaint/java/util/stream/Stream.java similarity index 98% rename from model/java/dataflow/stdlib/src/main/java/org/opentaint/jvm/dataflow/approximations/stdlib/Stream.java rename to model/java/dataflow/stdlib/src/main/java/opentaint/java/util/stream/Stream.java index 4c7a37244a..158934148e 100644 --- a/model/java/dataflow/stdlib/src/main/java/org/opentaint/jvm/dataflow/approximations/stdlib/Stream.java +++ b/model/java/dataflow/stdlib/src/main/java/opentaint/java/util/stream/Stream.java @@ -1,6 +1,5 @@ -package org.opentaint.jvm.dataflow.approximations.stdlib; +package opentaint.java.util.stream; -import org.opentaint.ir.approximation.annotation.Approximate; import org.opentaint.jvm.dataflow.approximations.ArgumentTypeContext; import java.util.Comparator; @@ -20,7 +19,6 @@ import java.util.stream.IntStream; import java.util.stream.LongStream; -@Approximate(java.util.stream.Stream.class) public class Stream { private static final int GENERATOR_ARRAY_SIZE = 0;