// SPDX-FileCopyrightText: 2025 Romain Maneschi // // SPDX-License-Identifier: EUPL-1.2 package plugin import ( "bytes" "context" "crypto/rand" "encoding/json" "errors" "fmt" "io/fs" "os" "path/filepath" "slices" "unicode/utf16" "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/object" "github.com/go-git/go-git/v5/plumbing/protocol/packp" "github.com/samber/oops" "github.com/tetratelabs/wazero" "github.com/tetratelabs/wazero/api" "github.com/tetratelabs/wazero/imports/assemblyscript" "github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1" "github.com/tetratelabs/wazero/sys" pluginLib "gitroot.dev/libs/golang/plugin/model" "gitroot.dev/server/logger" "gitroot.dev/server/repository" "gitroot.dev/server/user" ) type runtimeInputsKind int const ( runtimeInputsKindDiff runtimeInputsKind = iota runtimeInputsKindWorktree runtimeInputsKind = iota ) type runtimeInputs struct { ctx context.Context repoName string kind runtimeInputsKind plugins []Plugin commands []CommandForDiff close func() } type memoryGarbage struct { module api.Module ptrSize ptrSize } type runtime struct { ctx context.Context manager *Manager logger *logger.Logger repo *repository.GitRootRepository repoWriter *repository.GitRootRepositoryWrite plugin Plugin pluginRun PluginRun command *CommandForDiff commit *commitForDiffCommit wazRun wazero.Runtime commitHook func(h plumbing.Hash) mergeHook func(cmd *packp.Command, pusher user.SimpleUser, toDeleteBranchName string) reports []pluginLib.Report memoryToGarbage []memoryGarbage modulesToGarbage []api.Module } func newRuntime(ctx context.Context, manager *Manager, logger *logger.Logger, runtimeConfig wazero.RuntimeConfig) (*runtime, error) { r := &runtime{ ctx: ctx, manager: manager, logger: logger, wazRun: wazero.NewRuntimeWithConfig(ctx, runtimeConfig), commitHook: nil, mergeHook: nil, memoryToGarbage: []memoryGarbage{}, modulesToGarbage: []api.Module{}, } _, err := assemblyscript.Instantiate(ctx, r.wazRun) if err != nil { return nil, err } _, err = r.wazRun. NewHostModuleBuilder("gitroot"). NewFunctionBuilder().WithFunc(r.ForgeConf).Export("forgeConf"). NewFunctionBuilder().WithFunc(r.ModifyContentBuilder(false)).Export("modifyContent"). NewFunctionBuilder().WithFunc(r.ModifyContentBuilder(true)).Export("modifyContentAS"). NewFunctionBuilder().WithFunc(r.ModifyWebContentBuilder(false)).Export("modifyWebContent"). NewFunctionBuilder().WithFunc(r.ModifyWebContentBuilder(true)).Export("modifyWebContentAS"). NewFunctionBuilder().WithFunc(r.ReplaceWebContentBuilder(false)).Export("replaceWebContent"). NewFunctionBuilder().WithFunc(r.ReplaceWebContentBuilder(true)).Export("replaceWebContentAS"). NewFunctionBuilder().WithFunc(r.ModifyCacheContentBuilder(false)).Export("modifyCacheContent"). NewFunctionBuilder().WithFunc(r.ModifyCacheContentBuilder(true)).Export("modifyCacheContentAS"). NewFunctionBuilder().WithFunc(r.ReplaceContent(false)).Export("replaceContent"). NewFunctionBuilder().WithFunc(r.ReplaceContent(true)).Export("replaceContentAS"). NewFunctionBuilder().WithFunc(r.WriteContent(false)).Export("writeContent"). NewFunctionBuilder().WithFunc(r.WriteContent(true)).Export("writeContentAS"). NewFunctionBuilder().WithFunc(r.CopyFileBuilder(false)).Export("copyFile"). NewFunctionBuilder().WithFunc(r.CopyFileBuilder(true)).Export("copyFileAS"). NewFunctionBuilder().WithFunc(r.DeleteFileBuilder(false)).Export("deleteFile"). NewFunctionBuilder().WithFunc(r.DeleteFileBuilder(true)).Export("deleteFileAS"). NewFunctionBuilder().WithFunc(r.MoveFileBuilder(false)).Export("moveFile"). NewFunctionBuilder().WithFunc(r.MoveFileBuilder(true)).Export("moveFileAS"). NewFunctionBuilder().WithFunc(r.CommitAllBuilder(false)).Export("commitAll"). NewFunctionBuilder().WithFunc(r.CommitAllBuilder(true)).Export("commitAllAS"). NewFunctionBuilder().WithFunc(r.DiffWithParentBuilder(false)).Export("diffWithParent"). NewFunctionBuilder().WithFunc(r.DiffWithParentBuilder(true)).Export("diffWithParentAS"). NewFunctionBuilder().WithFunc(r.LogBuilder(false)).Export("log"). NewFunctionBuilder().WithFunc(r.LogBuilder(true)).Export("logAS"). NewFunctionBuilder().WithFunc(r.LogErrorBuilder(false)).Export("logError"). NewFunctionBuilder().WithFunc(r.LogErrorBuilder(true)).Export("logErrorAS"). NewFunctionBuilder().WithFunc(r.MergeBuilder(false)).Export("merge"). NewFunctionBuilder().WithFunc(r.MergeBuilder(true)).Export("mergeAS"). NewFunctionBuilder().WithFunc(r.GetCommitsBuilder(false)).Export("commits"). NewFunctionBuilder().WithFunc(r.GetCommitsBuilder(true)).Export("commitsAS"). NewFunctionBuilder().WithFunc(r.ExecBuilder(false)).Export("exec"). NewFunctionBuilder().WithFunc(r.ExecBuilder(true)).Export("execAS"). NewFunctionBuilder().WithFunc(r.ReportBuilder(false)).Export("report"). NewFunctionBuilder().WithFunc(r.ReportBuilder(true)).Export("reportAS"). NewFunctionBuilder().WithFunc(r.CanCallBuilder(false)).Export("canCall"). NewFunctionBuilder().WithFunc(r.CanCallBuilder(true)).Export("canCallAS"). NewFunctionBuilder().WithFunc(r.CallBuilder(false)).Export("call"). NewFunctionBuilder().WithFunc(r.CallBuilder(true)).Export("callAS"). Instantiate(ctx) if err != nil { return nil, err } _, err = wasi_snapshot_preview1.Instantiate(ctx, r.wazRun) if err != nil { return nil, err } return r, nil } type hasBeenMerged struct { cmd *packp.Command pusher user.SimpleUser toDeleteBranchName string } func (r *runtime) listen(c chan runtimeInputs) { postMergeActions := []hasBeenMerged{} for i := range c { r.logger.Debug("listen call", logger.NewLoggerPair("repo", i.repoName), logger.NewLoggerPair("isDiff", i.kind == runtimeInputsKindDiff), logger.NewLoggerPair("isWorktree", i.kind == runtimeInputsKindWorktree)) repo, err := r.manager.repoManager.Open(logger.AddCaller(r.ctx, "runtime.listen"), i.repoName) if err != nil { r.logger.Error("open error in listen", err) repo.Close() i.close() continue } repoWriter, err := repo.WillWrite(plumbing.HEAD) //TODO should mount good branch directly inside i.commands if err != nil { r.logger.Error("will write error in listen", err, logger.NewLoggerPair("repo", repo.Name())) repo.Close() i.close() continue } switch i.kind { case runtimeInputsKindDiff: timerStop := r.logger.Time(fmt.Sprintf("Timer %s all plugins", repo.Name())) err := r.start(i.ctx, repo, repoWriter, i.plugins, i.commands, func(cmd *packp.Command, pusher user.SimpleUser, toDeleteBranchName string) { if cmd != nil { postMergeActions = append(postMergeActions, hasBeenMerged{ cmd: cmd, pusher: pusher, toDeleteBranchName: toDeleteBranchName, }) } }) if err != nil { r.logger.Error("start error", err) } timerStop() case runtimeInputsKindWorktree: r.logger.Info("start worktree", logger.NewLoggerPair("repo", repo.Name())) timerStop := r.logger.Time(fmt.Sprintf("Timer %s all plugins worktree", repo.Name())) if err := r.worktree(i.ctx, repo, repoWriter, i.plugins, i.commands); err != nil { r.logger.Error("start error", err) } timerStop() } r.sendReports(i.ctx, repo, repoWriter, i.plugins) r.garbage() repo.Close() for _, postMerge := range postMergeActions { r.manager.backgroundManager.DeleteBranch(i.repoName, postMerge.toDeleteBranchName) r.manager.backgroundManager.PostPush(postMerge.pusher, i.repoName, []*packp.Command{postMerge.cmd}) } postMergeActions = nil r.logger.Debug("finish listen call", logger.NewLoggerPair("repo", i.repoName), logger.NewLoggerPair("isDiff", i.kind == runtimeInputsKindDiff), logger.NewLoggerPair("isWorktree", i.kind == runtimeInputsKindWorktree)) i.close() } } func (r *runtime) loadModule(ctx context.Context, plugin Plugin, withFs fs.FS) (api.Module, error) { m := r.wazRun.Module(plugin.uuid()) if m != nil && m.IsClosed() { r.logger.Debug("module exist but closed", logger.NewLoggerPair("name", plugin.Name)) m = nil } if m == nil { r.logger.Debug("instantiate plugin conf", logger.NewLoggerPair("name", plugin.Name), logger.NewLoggerPair("with external fs", withFs != nil)) config := wazero.NewModuleConfig(). WithStdout(os.Stdout).WithStderr(os.Stderr). WithSysWalltime(). WithSysNanotime(). WithRandSource(rand.Reader). WithName(plugin.uuid()). WithStartFunctions("_initialize", "install") if withFs != nil { config = config.WithFS(withFs) } else { r.logger.Warn("no fs mounted", logger.NewLoggerPair("plugin", plugin.Name), logger.NewLoggerPair("repo is nil", r.repo == nil), logger.NewLoggerPair("repoWrite is nil", r.repoWriter == nil)) } mod, err := r.wazRun.InstantiateModule(ctx, plugin.compiledModule, config) if err != nil { if exitErr, ok := err.(*sys.ExitError); ok && exitErr.ExitCode() != 0 { fmt.Fprintf(os.Stderr, "exit_code: %d\n", exitErr.ExitCode()) return nil, err } else if !ok { return nil, err } } m = mod } else { r.logger.Debug("already exist conf", logger.NewLoggerPair("name", plugin.Name)) } r.modulesToGarbage = append(r.modulesToGarbage, m) return m, nil } func (r *runtime) garbageMemory() { byModule := make(map[api.Module][]ptrSize) for _, d := range r.memoryToGarbage { if all, ok := byModule[d.module]; ok { byModule[d.module] = append(all, d.ptrSize) } else { byModule[d.module] = []ptrSize{d.ptrSize} } } for m, ptr := range byModule { r.free(m, ptr) } r.memoryToGarbage = []memoryGarbage{} } func (r *runtime) garbage() { r.garbageMemory() r.reports = []pluginLib.Report{} for _, m := range r.modulesToGarbage { if err := m.Close(r.ctx); err != nil { r.logger.Error("close module fail", err, logger.NewLoggerPair("name", m.Name())) } } r.modulesToGarbage = []api.Module{} } func (r *runtime) ForgeConf(_ context.Context, m api.Module) uint64 { forgeConf, err := json.Marshal(r.manager.conf.ForgeConf()) if err != nil { r.logger.Error("ForgeConf can't be serialized", err) return 0 } ptrSize, err := r.sendData(m, string(forgeConf)) if err != nil { r.logger.Error("ForgeConf can't send data", err) return 0 } return ptrSize } func (r *runtime) modifyContent(filename string, content string) error { if ok, err := checkWrite(r.pluginRun.write.git, r.repoWriter.ToFs(r.ctx), filename); !ok { r.logger.Error("plugin can't write in git", err, logger.NewLoggerPair("plugin", r.plugin.Name), logger.NewLoggerPair("path", filename)) return err } r.logger.Debug("modifyContent", logger.NewLoggerPair("file", filename)) if err := r.repoWriter.Write(filename, []byte(content)); err != nil { r.logger.Error("modifyContent can't open file", err, logger.NewLoggerPair("filepath", filename)) return err } return nil } // TODO delete after v0.4 is released // Deprecated: Use WriteContent(fs, filepath, content string) instead func (r *runtime) ModifyContentBuilder(forAS bool) interface{} { if !forAS { return func(_ context.Context, m api.Module, filenamePtr, filenameSize, contentPtr, contentSize uint32) { filename, err := r.readString(m, "ModifyContent filename", filenamePtr, filenameSize) if err != nil { return } content, err := r.readString(m, "ModifyContent content", contentPtr, contentSize) if err != nil { return } r.modifyContent(filename, content) } } else { return func(_ context.Context, m api.Module, filenamePtr, contentPtr uint32) { filename, err := r.readASString(m, "ModifyContent filename", filenamePtr) if err != nil { return } content, err := r.readASString(m, "ModifyContent content", contentPtr) if err != nil { return } r.modifyContent(filename, content) } } } func (r *runtime) modifyWebContent(filename string, content string) error { if r.repo == nil { //in forconf scenario their is no repo r.logger.Error("ModifyWebContentBuilder can't in forconf", errors.New("no repo"), logger.NewLoggerPair("plugin", r.plugin.Name), logger.NewLoggerPair("filepath", filename)) return errors.New("no repo") } if ok, err := checkWrite(r.pluginRun.write.web, r.manager.conf.DataWeb(r.repo.Name()), filename); !ok { r.logger.Warn("plugin can't write in web", logger.NewLoggerPair("err", err), logger.NewLoggerPair("repo", r.repo.Name()), logger.NewLoggerPair("branch", r.command.branch), logger.NewLoggerPair("plugin", r.plugin.Name), logger.NewLoggerPair("path", filename)) return errors.New("can't write in web") } fullPath := r.repo.PathDataWeb(filename) dir, _ := filepath.Split(fullPath) if err := os.MkdirAll(dir, os.ModePerm); err != nil { r.logger.Error("modifyWebContent can't mkdirAll", err, logger.NewLoggerPair("filepath", filename)) return err } if err := os.WriteFile(fullPath, []byte(content), 0666); err != nil { r.logger.Error("modifyWebContent can't open file", err, logger.NewLoggerPair("filepath", filename)) return err } r.logger.Debug("Write in web", logger.NewLoggerPair("fullPath", fullPath)) return nil } // TODO delete after v0.4 is released // Deprecated: Use WriteContent(fs, filepath, content string) instead func (r *runtime) ModifyWebContentBuilder(forAS bool) interface{} { if !forAS { return func(_ context.Context, m api.Module, filenamePtr, filenameSize, contentPtr, contentSize uint32) { filename, err := r.readString(m, "ModifyWebContent filename", filenamePtr, filenameSize) if err != nil { return } content, err := r.readString(m, "ModifyWebContent content", contentPtr, contentSize) if err != nil { return } r.modifyWebContent(filename, content) } } else { return func(_ context.Context, m api.Module, filenamePtr, contentPtr uint32) { filename, err := r.readASString(m, "ModifyWebContent filename", filenamePtr) if err != nil { return } content, err := r.readASString(m, "ModifyWebContent content", contentPtr) if err != nil { return } r.modifyWebContent(filename, content) } } } func (r *runtime) replaceGitContent(filename string, oldContent string, content string) error { if r.repo == nil { //in forconf scenario their is no repo r.logger.Error("ReplaceWebContentBuilder can't in forconf", errors.New("no repo"), logger.NewLoggerPair("plugin", r.plugin.Name), logger.NewLoggerPair("filepath", filename)) return errors.New("no repo") } if ok, err := checkWrite(r.pluginRun.write.git, r.repoWriter.ToFs(r.ctx), filename); !ok { r.logger.Warn("plugin can't write in git for replace", logger.NewLoggerPair("err", err), logger.NewLoggerPair("plugin", r.plugin.Name), logger.NewLoggerPair("path", filename)) return err } fileContent, err := r.repo.Content(filename) if err != nil { r.logger.Error("replaceGitContent can't ReadFile", err, logger.NewLoggerPair("filepath", filename)) return err } if err := r.repoWriter.Write(filename, bytes.Replace(fileContent, []byte(oldContent), []byte(content), 1)); err != nil { r.logger.Error("replaceGitContent can't open file", err, logger.NewLoggerPair("filepath", filename)) return err } r.logger.Debug("Write in git", logger.NewLoggerPair("fullPath", filename)) return nil } func (r *runtime) replaceWebContent(filename string, oldContent string, content string) error { if r.repo == nil { //in forconf scenario their is no repo r.logger.Error("ReplaceWebContentBuilder can't in forconf", errors.New("no repo"), logger.NewLoggerPair("plugin", r.plugin.Name), logger.NewLoggerPair("filepath", filename)) return errors.New("no repo") } if ok, err := checkWrite(r.pluginRun.write.web, r.manager.conf.DataWeb(r.repo.Name()), filename); !ok { r.logger.Warn("plugin can't write in web for replace", logger.NewLoggerPair("err", err), logger.NewLoggerPair("plugin", r.plugin.Name), logger.NewLoggerPair("path", filename)) return err } fullPath := r.repo.PathDataWeb(filename) fileContent, err := os.ReadFile(fullPath) if err != nil { r.logger.Error("ReplaceWebContentBuilder can't ReadFile", err, logger.NewLoggerPair("filepath", filename)) return err } if err := os.WriteFile(fullPath, bytes.Replace(fileContent, []byte(oldContent), []byte(content), 1), 0666); err != nil { r.logger.Error("ReplaceWebContentBuilder can't open file", err, logger.NewLoggerPair("filepath", filename)) return err } r.logger.Debug("Write in web", logger.NewLoggerPair("fullPath", fullPath)) return nil } func (r *runtime) replaceCacheContent(filename string, oldContent string, content string) error { if r.repo == nil { //in forconf scenario their is no repo r.logger.Error("replaceCacheContent can't in forconf", errors.New("no repo"), logger.NewLoggerPair("plugin", r.plugin.Name), logger.NewLoggerPair("filepath", filename)) return errors.New("no repo") } fullPath := filepath.Join(r.manager.conf.PathCacheProject(r.repo.Name(), r.plugin.Name), filename) fileContent, err := os.ReadFile(fullPath) if err != nil { r.logger.Error("replaceCacheContent can't ReadFile", err, logger.NewLoggerPair("filepath", filename)) return err } if err := os.WriteFile(fullPath, bytes.Replace(fileContent, []byte(oldContent), []byte(content), 1), 0666); err != nil { r.logger.Error("replaceCacheContent can't open file", err, logger.NewLoggerPair("filepath", filename)) return err } r.logger.Debug("Write in cache", logger.NewLoggerPair("fullPath", fullPath)) return nil } // TODO delete after v0.4 is released // Deprecated: Use ReplaceContent(fs, filepath, content string) instead func (r *runtime) ReplaceWebContentBuilder(forAS bool) interface{} { if !forAS { return func(_ context.Context, m api.Module, filenamePtr, filenameSize, oldContentPtr, oldContentSize, contentPtr, contentSize uint32) { filename, err := r.readString(m, "ReplaceWebContentBuilder filename", filenamePtr, filenameSize) if err != nil { return } oldContent, err := r.readString(m, "ReplaceWebContentBuilder oldContent", oldContentPtr, oldContentSize) if err != nil { return } content, err := r.readString(m, "ReplaceWebContentBuilder content", contentPtr, contentSize) if err != nil { return } r.replaceWebContent(filename, oldContent, content) } } else { return func(_ context.Context, m api.Module, filenamePtr, oldContentPtr, contentPtr uint32) { filename, err := r.readASString(m, "ReplaceWebContentBuilder filename", filenamePtr) if err != nil { return } oldContent, err := r.readASString(m, "ReplaceWebContentBuilder oldContent", oldContentPtr) if err != nil { return } content, err := r.readASString(m, "ReplaceWebContentBuilder content", contentPtr) if err != nil { return } r.replaceWebContent(filename, oldContent, content) } } } func (r *runtime) modifyCacheContent(filename string, content string) error { r.logger.Debug("ModifyCacheContent", logger.NewLoggerPair("plugin", r.plugin.Name), logger.NewLoggerPair("file", filename)) if r.repo == nil { //in forconf scenario their is no repo r.logger.Error("ModifyCacheContent can't in forconf", errors.New("no repo"), logger.NewLoggerPair("plugin", r.plugin.Name), logger.NewLoggerPair("filepath", filename)) return errors.New("no repo") } fullPath := filepath.Join(r.manager.conf.PathCacheProject(r.repo.Name(), r.plugin.Name), filename) dir, _ := filepath.Split(fullPath) if err := os.MkdirAll(dir, os.ModePerm); err != nil { r.logger.Error("ModifyCacheContent can't mkdirAll", err, logger.NewLoggerPair("filepath", filename)) return err } if err := os.WriteFile(fullPath, []byte(content), 0666); err != nil { r.logger.Error("ModifyCacheContent can't open file", err, logger.NewLoggerPair("filepath", filename)) return err } r.logger.Debug("Write in cache", logger.NewLoggerPair("fullPath", fullPath)) return nil } // TODO delete after v0.4 is released // Deprecated: Use WriteContent(fs, filepath, content string) instead func (r *runtime) ModifyCacheContentBuilder(forAS bool) interface{} { if !forAS { return func(_ context.Context, m api.Module, filenamePtr, filenameSize, contentPtr, contentSize uint32) { filename, err := r.readString(m, "ModifyCacheContent filename", filenamePtr, filenameSize) if err != nil { return } content, err := r.readString(m, "ModifyCacheContent content", contentPtr, contentSize) if err != nil { return } r.modifyCacheContent(filename, content) } } else { return func(_ context.Context, m api.Module, filenamePtr, contentPtr uint32) { filename, err := r.readASString(m, "ModifyCacheContent filename", filenamePtr) if err != nil { return } content, err := r.readASString(m, "ModifyCacheContent content", contentPtr) if err != nil { return } r.modifyCacheContent(filename, content) } } } func (r *runtime) replaceContent(fromFs string, path string, oldContent string, content string) error { switch fromFs { case "worktree": return r.replaceGitContent(path, oldContent, content) case "webcontent": return r.replaceWebContent(path, oldContent, content) case "cache": return r.replaceCacheContent(path, oldContent, content) } return nil } func (r *runtime) ReplaceContent(forAS bool) interface{} { if !forAS { return func(_ context.Context, m api.Module, fromFsPtr, fromFsSize, fromPathPtr, fromGlobPathSize, oldContentPtr, oldContentSize, contentPtr, contentSize uint32) uint64 { fromFs, err := r.readString(m, "MoveFile fromFs", fromFsPtr, fromFsSize) if err != nil { return r.sendEmptyOrError(m, err) } fromPath, err := r.readString(m, "MoveFile fromGlobPath", fromPathPtr, fromGlobPathSize) if err != nil { return r.sendEmptyOrError(m, err) } oldContent, err := r.readString(m, "MoveFile oldContent", oldContentPtr, oldContentSize) if err != nil { return r.sendEmptyOrError(m, err) } content, err := r.readString(m, "MoveFile content", contentPtr, contentSize) if err != nil { return r.sendEmptyOrError(m, err) } return r.sendEmptyOrError(m, r.replaceContent(fromFs, fromPath, oldContent, content)) } } else { return func(_ context.Context, m api.Module, fromFsPtr, fromPathPtr, oldContentPtr, contentPtr uint32) uint64 { fromFs, err := r.readASString(m, "MoveFileAS fromFs", fromFsPtr) if err != nil { return r.sendEmptyOrError(m, err) } fromPath, err := r.readASString(m, "MoveFileAS fromGlobPath", fromPathPtr) if err != nil { return r.sendEmptyOrError(m, err) } oldContent, err := r.readASString(m, "MoveFileAS oldContent", oldContentPtr) if err != nil { return r.sendEmptyOrError(m, err) } content, err := r.readASString(m, "MoveFileAS content", contentPtr) if err != nil { return r.sendEmptyOrError(m, err) } return r.sendEmptyOrError(m, r.replaceContent(fromFs, fromPath, oldContent, content)) } } } func (r *runtime) writeContent(toFs string, path string, content string) error { switch toFs { case "worktree": return r.modifyContent(path, content) case "webcontent": return r.modifyWebContent(path, content) case "cache": return r.modifyCacheContent(path, content) } return nil } func (r *runtime) WriteContent(forAS bool) interface{} { if !forAS { return func(_ context.Context, m api.Module, fromFsPtr, fromFsSize, fromPathPtr, fromGlobPathSize, contentPtr, contentSize uint32) uint64 { fromFs, err := r.readString(m, "MoveFile fromFs", fromFsPtr, fromFsSize) if err != nil { return r.sendEmptyOrError(m, err) } fromPath, err := r.readString(m, "MoveFile fromGlobPath", fromPathPtr, fromGlobPathSize) if err != nil { return r.sendEmptyOrError(m, err) } content, err := r.readString(m, "MoveFile content", contentPtr, contentSize) if err != nil { return r.sendEmptyOrError(m, err) } return r.sendEmptyOrError(m, r.writeContent(fromFs, fromPath, content)) } } else { return func(_ context.Context, m api.Module, fromFsPtr, fromPathPtr, contentPtr uint32) uint64 { fromFs, err := r.readASString(m, "MoveFileAS fromFs", fromFsPtr) if err != nil { return r.sendEmptyOrError(m, err) } fromPath, err := r.readASString(m, "MoveFileAS fromGlobPath", fromPathPtr) if err != nil { return r.sendEmptyOrError(m, err) } content, err := r.readASString(m, "MoveFileAS content", contentPtr) if err != nil { return r.sendEmptyOrError(m, err) } return r.sendEmptyOrError(m, r.writeContent(fromFs, fromPath, content)) } } } func (r *runtime) copyFile(fromFs, fromPath, toFs, toPath string) error { contentFrom := []byte("") var err error switch fromFs { case "worktree": contentFrom, err = r.repo.Content(fromPath) case "webcontent": fullPath := r.repo.PathDataWeb(fromPath) contentFrom, err = os.ReadFile(fullPath) case "cache": fullPath := filepath.Join(r.manager.conf.PathCacheProject(r.repo.Name(), r.plugin.Name), fromPath) contentFrom, err = os.ReadFile(fullPath) } if err != nil { return err } return r.writeContent(toFs, toPath, string(contentFrom)) } func (r *runtime) CopyFileBuilder(forAS bool) interface{} { if !forAS { return func(_ context.Context, m api.Module, fromFsPtr, fromFsSize, fromPathPtr, fromGlobPathSize, toFsPtr, toFsSize, toPathPtr, toPathSize uint32) uint64 { fromFs, err := r.readString(m, "MoveFile fromFs", fromFsPtr, fromFsSize) if err != nil { return r.sendEmptyOrError(m, err) } fromPath, err := r.readString(m, "MoveFile fromGlobPath", fromPathPtr, fromGlobPathSize) if err != nil { return r.sendEmptyOrError(m, err) } toFs, err := r.readString(m, "MoveFile toFs", toFsPtr, toFsSize) if err != nil { return r.sendEmptyOrError(m, err) } toPath, err := r.readString(m, "MoveFile toPath", toPathPtr, toPathSize) if err != nil { return r.sendEmptyOrError(m, err) } return r.sendEmptyOrError(m, r.copyFile(fromFs, fromPath, toFs, toPath)) } } else { return func(_ context.Context, m api.Module, fromFsPtr, fromPathPtr, toFsPtr, toPathPtr uint32) uint64 { fromFs, err := r.readASString(m, "MoveFileAS fromFs", fromFsPtr) if err != nil { return r.sendEmptyOrError(m, err) } fromPath, err := r.readASString(m, "MoveFileAS fromGlobPath", fromPathPtr) if err != nil { return r.sendEmptyOrError(m, err) } toFs, err := r.readASString(m, "MoveFileAS toFs", toFsPtr) if err != nil { return r.sendEmptyOrError(m, err) } toPath, err := r.readASString(m, "MoveFileAS toPath", toPathPtr) if err != nil { return r.sendEmptyOrError(m, err) } return r.sendEmptyOrError(m, r.copyFile(fromFs, fromPath, toFs, toPath)) } } } func (r *runtime) deleteFile(fromFs, filename string) error { r.logger.Debug("delete", logger.NewLoggerPair("file", filename), logger.NewLoggerPair("fs", fromFs)) switch fromFs { case "worktree": if ok, err := checkDelete(r.pluginRun.write.git, filename); !ok { r.logger.Error("plugin can't delete in git", err, logger.NewLoggerPair("plugin", r.plugin.Name), logger.NewLoggerPair("path", filename)) return err } return r.repoWriter.Remove(filename) case "webcontent": if ok, err := checkDelete(r.pluginRun.write.web, filename); !ok { r.logger.Error("plugin can't delete in web", err, logger.NewLoggerPair("plugin", r.plugin.Name), logger.NewLoggerPair("path", filename)) return err } fullPath := r.repo.PathDataWeb(filename) return os.Remove(fullPath) case "cache": fullPath := filepath.Join(r.manager.conf.PathCacheProject(r.repo.Name(), r.plugin.Name), filename) return os.Remove(fullPath) } return fmt.Errorf("unknown fs %s", fromFs) } func (r *runtime) DeleteFileBuilder(forAS bool) interface{} { if !forAS { return func(_ context.Context, m api.Module, fromFsPtr, fromFsSize, fromPathPtr, fromGlobPathSize uint32) uint64 { fromFs, err := r.readString(m, "MoveFile fromFs", fromFsPtr, fromFsSize) if err != nil { return r.sendEmptyOrError(m, err) } fromPath, err := r.readString(m, "MoveFile fromGlobPath", fromPathPtr, fromGlobPathSize) if err != nil { return r.sendEmptyOrError(m, err) } return r.sendEmptyOrError(m, r.deleteFile(fromFs, fromPath)) } } else { return func(_ context.Context, m api.Module, fromFsPtr, fromPathPtr uint32) uint64 { fromFs, err := r.readASString(m, "MoveFileAS fromFs", fromFsPtr) if err != nil { return r.sendEmptyOrError(m, err) } fromPath, err := r.readASString(m, "MoveFileAS fromGlobPath", fromPathPtr) if err != nil { return r.sendEmptyOrError(m, err) } return r.sendEmptyOrError(m, r.deleteFile(fromFs, fromPath)) } } } func (r *runtime) moveFile(fromFs, fromPath, toFs, toPath string) error { r.logger.Debug("moveFile", logger.NewLoggerPair("file", fromPath), logger.NewLoggerPair("fs", fromFs)) if err := r.copyFile(fromFs, fromPath, toFs, toPath); err != nil { return err } r.logger.Debug("moveFile 2", logger.NewLoggerPair("file", fromPath), logger.NewLoggerPair("fs", fromFs)) return r.deleteFile(fromFs, fromPath) } func (r *runtime) MoveFileBuilder(forAS bool) interface{} { if !forAS { return func(_ context.Context, m api.Module, fromFsPtr, fromFsSize, fromPathPtr, fromGlobPathSize, toFsPtr, toFsSize, toPathPtr, toPathSize uint32) uint64 { fromFs, err := r.readString(m, "MoveFile fromFs", fromFsPtr, fromFsSize) if err != nil { return r.sendEmptyOrError(m, err) } fromPath, err := r.readString(m, "MoveFile fromGlobPath", fromPathPtr, fromGlobPathSize) if err != nil { return r.sendEmptyOrError(m, err) } toFs, err := r.readString(m, "MoveFile toFs", toFsPtr, toFsSize) if err != nil { return r.sendEmptyOrError(m, err) } toPath, err := r.readString(m, "MoveFile toPath", toPathPtr, toPathSize) if err != nil { return r.sendEmptyOrError(m, err) } return r.sendEmptyOrError(m, r.moveFile(fromFs, fromPath, toFs, toPath)) } } else { return func(_ context.Context, m api.Module, fromFsPtr, fromPathPtr, toFsPtr, toPathPtr uint32) uint64 { fromFs, err := r.readASString(m, "MoveFileAS fromFs", fromFsPtr) if err != nil { return r.sendEmptyOrError(m, err) } fromPath, err := r.readASString(m, "MoveFileAS fromGlobPath", fromPathPtr) if err != nil { return r.sendEmptyOrError(m, err) } toFs, err := r.readASString(m, "MoveFileAS toFs", toFsPtr) if err != nil { return r.sendEmptyOrError(m, err) } toPath, err := r.readASString(m, "MoveFileAS toPath", toPathPtr) if err != nil { return r.sendEmptyOrError(m, err) } return r.sendEmptyOrError(m, r.moveFile(fromFs, fromPath, toFs, toPath)) } } } func (r *runtime) CommitAllBuilder(forAS bool) interface{} { f := func(msg string, err error) { if err != nil { return } if h, err := r.repoWriter.CommitAll(msg, r.plugin.commiter); err != nil { r.logger.Error("commitAll can't commit", err) return } else { r.logger.Debug("Commit", logger.NewLoggerPair("message", msg), logger.NewLoggerPair("hash", h.String())) if r.commitHook != nil && !h.IsZero() { r.commitHook(h) } } } if !forAS { return func(_ context.Context, m api.Module, offset, byteCount uint32) { f(r.readString(m, "commitAll", offset, byteCount)) } } else { return func(_ context.Context, m api.Module, offset uint32) { f(r.readASString(m, "commitAll", offset)) } } } func (r *runtime) DiffWithParentBuilder(forAS bool) interface{} { f := func(m api.Module, hash string, oldfile string, newfile string) uint64 { r.logger.Info("In diffWithParent before found ancestor") diffStr, err := r.repoWriter.GetDiff(plumbing.NewHash(string(hash)), string(oldfile), string(newfile)) if err != nil { r.logger.Error("GetDiff", err) return 0 } r.logger.Debug("In diffWithParent send diff", logger.NewLoggerPair("diff", diffStr)) ptrSize, err := r.sendData(m, diffStr) if err != nil { r.logger.Error("can't sendData", err, logger.NewLoggerPair("message", diffStr)) return 0 } return ptrSize } if !forAS { return func(_ context.Context, m api.Module, hashPtr, hashSize, oldFilenamePtr, oldFilenameSize, newFilenamePtr, newFilenameSize uint32) uint64 { hash, err := r.readString(m, "DiffWithParent hash", hashPtr, hashSize) if err != nil { return 0 } oldFilename, err := r.readString(m, "DiffWithParent oldFilename", oldFilenamePtr, oldFilenameSize) if err != nil { return 0 } newFilename, err := r.readString(m, "DiffWithParent newFilename", newFilenamePtr, newFilenameSize) if err != nil { return 0 } return f(m, hash, oldFilename, newFilename) } } else { return func(_ context.Context, m api.Module, hashPtr, oldFilenamePtr, newFilenamePtr uint32) uint64 { hash, err := r.readASString(m, "DiffWithParent hash", hashPtr) if err != nil { return 0 } oldFilename, err := r.readASString(m, "DiffWithParent oldFilenamePtr", oldFilenamePtr) if err != nil { return 0 } newFilename, err := r.readASString(m, "DiffWithParent newFilename", newFilenamePtr) if err != nil { return 0 } return f(m, hash, oldFilename, newFilename) } } } func (r *runtime) LogBuilder(forAS bool) interface{} { f := func(msg string, err error) { if err != nil { return } repoName := "" if r.repo != nil { //in forconf scenario their is no repo repoName = r.repo.Name() } r.logger.Debug(msg, logger.NewLoggerPair("repo", repoName), logger.NewLoggerPair("plugin", r.plugin.Name)) } if !forAS { return func(_ context.Context, m api.Module, offset, byteCount uint32) { f(r.readString(m, "log", offset, byteCount)) } } else { return func(_ context.Context, m api.Module, offset uint32) { f(r.readASString(m, "log", offset)) } } } func (r *runtime) LogErrorBuilder(forAS bool) interface{} { f := func(msg string, err string) { repoName := "" if r.repo != nil { //in forconf scenario their is no repo repoName = r.repo.Name() } r.logger.Error(msg, errors.New(err), logger.NewLoggerPair("repo", repoName), logger.NewLoggerPair("plugin", r.plugin.Name)) } if !forAS { return func(_ context.Context, m api.Module, offset, byteCount, errPtr, errSize uint32) { msg, err := r.readString(m, "LogError msg", offset, byteCount) if err != nil { return } errMsg, err := r.readString(m, "LogError err", errPtr, errSize) if err != nil { return } f(msg, errMsg) } } else { return func(_ context.Context, m api.Module, offset, errPtr uint32) { msg, err := r.readASString(m, "LogError msg", offset) if err != nil { return } errMsg, err := r.readASString(m, "LogError err", errPtr) if err != nil { return } f(msg, errMsg) } } } func (r *runtime) MergeBuilder(forAS bool) interface{} { f := func(from string, to string) { r.logger.Debug("try to merge", logger.NewLoggerPair("from", from), logger.NewLoggerPair("to", to)) cmd, err := r.repoWriter.Merge(from, to, r.plugin.commiter, r.command.pusher) if err != nil { r.logger.Error("can't Merge", err, logger.NewLoggerPair("from", from), logger.NewLoggerPair("to", to)) } if r.mergeHook != nil { r.mergeHook(cmd, r.command.pusher, to) } } if !forAS { return func(_ context.Context, m api.Module, fromPtr, fromSize, toPtr, toSize uint32) { from, err := r.readString(m, "Merge from", fromPtr, fromSize) if err != nil { return } to, err := r.readString(m, "Merge to", toPtr, toSize) if err != nil { return } f(from, to) } } else { return func(_ context.Context, m api.Module, fromPtr, toPtr uint32) { from, err := r.readASString(m, "Merge from", fromPtr) if err != nil { return } to, err := r.readASString(m, "Merge to", toPtr) if err != nil { return } f(from, to) } } } func (r *runtime) GetCommitsBuilder(forAS bool) interface{} { f := func(m api.Module, from string, to string) uint64 { groups, err := user.LoadGroup(r.repo) if err != nil { r.logger.Error("can't load group", err) return 0 } commits := []commitForDiffCommit{} if err := r.repoWriter.WalkCommit(plumbing.NewHash(to), plumbing.NewHash(from), func(c *object.Commit) error { r.logger.Debug("found commit", logger.NewLoggerPair("hash", c.Hash.String()), logger.NewLoggerPair("from", from), logger.NewLoggerPair("to", to)) commits = append(commits, commitToCommitForDiff(c, nil, groups)) return nil }); err != nil { r.logger.Error("can't WalkCommit", err, logger.NewLoggerPair("from", from), logger.NewLoggerPair("to", to)) } r.logger.Debug("found commits", logger.NewLoggerPair("nb", len(commits)), logger.NewLoggerPair("from", from), logger.NewLoggerPair("to", to)) j, err := Marshall(r.command.branch.Short(), commits) if err != nil { r.logger.Error("can't Marshall", err, logger.NewLoggerPair("from", from), logger.NewLoggerPair("to", to)) } ptrSize, err := r.sendData(m, j) if err != nil { r.logger.Error("can't sendData", err, logger.NewLoggerPair("message", j)) return 0 } return ptrSize } if !forAS { return func(_ context.Context, m api.Module, fromPtr, fromSize, toPtr, toSize uint32) uint64 { from, err := r.readString(m, "Merge from", fromPtr, fromSize) if err != nil { return 0 } to, err := r.readString(m, "Merge to", toPtr, toSize) if err != nil { return 0 } return f(m, from, to) } } else { return func(_ context.Context, m api.Module, fromPtr, toPtr uint32) uint64 { from, err := r.readASString(m, "Merge from", fromPtr) if err != nil { return 0 } to, err := r.readASString(m, "Merge to", toPtr) if err != nil { return 0 } return f(m, from, to) } } } func (r *runtime) ExecBuilder(forAS bool) interface{} { f := func(m api.Module, cmd string) uint64 { exec := pluginLib.Exec{} err := json.Unmarshal([]byte(cmd), &exec) if err != nil { r.logger.Error("can't exec bad format", err) return 0 } if ok := checkExec(r.pluginRun.write.exec, exec); !ok { r.logger.Warn("plugin can't exec", logger.NewLoggerPair("err", err), logger.NewLoggerPair("repo", r.repo.Name()), logger.NewLoggerPair("branch", r.command.branch), logger.NewLoggerPair("plugin", r.plugin.Name)) return 0 } j, err := r.manager.execManager.Exec(r.repo, r.command.branch.Short(), r.plugin.Name, exec) if err != nil { r.logger.Error("can't exec", err) return 0 } jjson, err := json.Marshal(j) if err != nil { r.logger.Error("can't marshal execStatus", err) return 0 } ptrSize, err := r.sendData(m, string(jjson)) if err != nil { r.logger.Error("can't sendData", err, logger.NewLoggerPair("message", j)) return 0 } return ptrSize } if !forAS { return func(_ context.Context, m api.Module, commandPtr, commandSize uint32) uint64 { cmd, err := r.readString(m, "exec cmd", commandPtr, commandSize) if err != nil { return 0 } return f(m, cmd) } } else { return func(_ context.Context, m api.Module, commandPtr uint32) uint64 { cmd, err := r.readASString(m, "exec cmd", commandPtr) if err != nil { return 0 } return f(m, cmd) } } } func (r *runtime) ReportBuilder(forAS bool) interface{} { f := func(reportJson string) { if r.command == nil || r.commit == nil { r.logger.Error("can't report in conf stage", errors.New("report not allowed"), logger.NewLoggerPair("plugin", r.plugin.Name)) return } report := pluginLib.ReportToGitroot{} err := json.Unmarshal([]byte(reportJson), &report) if err != nil { r.logger.Error("can't unmarshal reportJson bad format", err) return } re := pluginLib.Report{ Level: report.Level, Content: report.Content, FromPlugin: r.plugin.Name, FromBranch: r.command.branch.Short(), FromCommit: r.commit.hash.String(), } reJson, err := json.Marshal(re) if err != nil { r.logger.Error("can't marshal reJson bad format", err) return } r.logger.Debug("report added", logger.NewLoggerPair("report", reJson)) r.reports = append(r.reports, re) } if !forAS { return func(_ context.Context, m api.Module, reportPtr, reportSize uint32) { reportJson, err := r.readString(m, "report json", reportPtr, reportSize) if err != nil { return } f(reportJson) } } else { return func(_ context.Context, m api.Module, reportPtr uint32) { reportJson, err := r.readASString(m, "report json", reportPtr) if err != nil { return } f(reportJson) } } } func (r *runtime) CanCallBuilder(forAS bool) interface{} { f := func(callJson string) uint32 { repo := "no repo in conf mode" if r.repo != nil { repo = r.repo.Name() } r.logger.Info("can call json", logger.NewLoggerPair("repo", repo), logger.NewLoggerPair("plugin", r.plugin.Name), logger.NewLoggerPair("data", string(callJson))) call := pluginLib.Call{} err := json.Unmarshal([]byte(callJson), &call) if err != nil { r.logger.Error("can't unmarshal canCallJson bad format", err) return 0 } canCall := slices.ContainsFunc(r.pluginRun.write.callFunc, func(cf pluginLib.PluginCallFuncRight) bool { return cf.PluginName == call.Plugin && cf.FuncName == call.Name }) // TODO find a way to check call.Args if !canCall { r.logger.Info("can't canCall func", logger.NewLoggerPair("plugin", r.plugin.Name), logger.NewLoggerPair("callPlugin", call.Plugin), logger.NewLoggerPair("func", call.Name), logger.NewLoggerPair("authorized", r.pluginRun.write.callFunc)) return 0 } return 1 } if !forAS { return func(_ context.Context, m api.Module, callPtr, callSize uint32) uint32 { callJson, err := r.readString(m, "call json", callPtr, callSize) if err != nil { r.logger.Error("can't readString canCall", err) return 0 } return f(callJson) } } else { return func(_ context.Context, m api.Module, callPtr uint32) uint32 { callJson, err := r.readASString(m, "call json", callPtr) if err != nil { r.logger.Error("can't readASString canCall", err) return 0 } return f(callJson) } } } func (r *runtime) CallBuilder(forAS bool) interface{} { f := func(callJson string) (uint64, api.Module) { r.logger.Info("call json", logger.NewLoggerPair("repo", r.repo.Name()), logger.NewLoggerPair("plugin", r.plugin.Name), logger.NewLoggerPair("data", string(callJson))) // TODO duplicated in previous (function canCall check rights) call := pluginLib.Call{} err := json.Unmarshal([]byte(callJson), &call) if err != nil { r.logger.Error("can't unmarshal callJson bad format", err) return 0, nil } canCall := slices.ContainsFunc(r.pluginRun.write.callFunc, func(cf pluginLib.PluginCallFuncRight) bool { return cf.PluginName == call.Plugin && cf.FuncName == call.Name }) if !canCall { r.logger.Error("can't call func", errors.New("not authorized in plugins file"), logger.NewLoggerPair("plugin", r.plugin.Name), logger.NewLoggerPair("callPlugin", call.Plugin), logger.NewLoggerPair("func", call.Name), logger.NewLoggerPair("authorized", r.pluginRun.write.callFunc)) return 0, nil } r.logger.Debug("call usableFromDefaultBranch from CallBuilder", logger.NewLoggerPair("repo", r.repo.Name())) plugins, err := r.manager.usableFromDefaultBranch(r.ctx, r.repo.Name()) if err != nil { r.logger.Error("can't get availables plugins", err) return 0, nil } for _, plugin := range plugins { if plugin.Name == call.Plugin { m, err := r.loadModule(r.ctx, plugin, nil) //TODO where to find fs? if err != nil { r.logger.Error("can't loadModule", err, logger.NewLoggerPair("plugin", plugin.Name)) return 0, nil } funcToCall := m.ExportedFunction("call") malloc := m.ExportedFunction("gitrootAlloc") if malloc == nil { malloc = m.ExportedFunction("malloc") } callResPtrSize, err := r.writeMemoryAndCallWithRes(m, funcToCall, malloc, callJson) if err != nil { r.logger.Error("can't writeMemoryAndCallWithRes", err, logger.NewLoggerPair("plugin", plugin.Name)) return 0, nil } return callResPtrSize, m } } r.logger.Error("plugin not found", err, logger.NewLoggerPair("plugin", call.Plugin), logger.NewLoggerPair("method", call.Name)) return 0, nil } callResJson, _ := json.Marshal(pluginLib.CallRes{Err: "plugin or method not found"}) if !forAS { return func(_ context.Context, m api.Module, callPtr, callSize uint32) uint64 { callJson, err := r.readString(m, "call json", callPtr, callSize) if err != nil { r.logger.Error("can't readString call", err) return 0 } callResPtrSize, newModule := f(callJson) if callResPtrSize > 0 && newModule != nil { ptrCall := uint32(callResPtrSize >> 32) sizeCall := uint32(callResPtrSize) callResJsonStr, err := r.readString(newModule, "can't read string in call", ptrCall, sizeCall) if err != nil { r.logger.Error("can't readString callRes", err) return 0 } callResJson = []byte(callResJsonStr) } ptrSize, err := r.sendData(m, string(callResJson)) if err != nil { r.logger.Error("can't sendData callRes", err) return 0 } return ptrSize } } else { return func(_ context.Context, m api.Module, callPtr uint32) uint64 { callJson, err := r.readASString(m, "call json", callPtr) if err != nil { r.logger.Error("can't readASString call", err) return 0 } callResPtrSize, newModule := f(callJson) if callResPtrSize > 0 && newModule != nil { callResJsonStr, err := r.readASString(newModule, "can't read asstring in call", uint32(callResPtrSize)) if err != nil { r.logger.Error("can't readASString callRes", err) return 0 } callResJson = []byte(callResJsonStr) } ptrSize, err := r.sendData(m, string(callResJson)) if err != nil { r.logger.Error("can't sendData callRes", err) return 0 } return ptrSize } } } func (r *runtime) Close() error { return r.wazRun.Close(r.ctx) } func (r *runtime) sendData(module api.Module, message string) (ptrSizeToSend uint64, err error) { malloc := module.ExportedFunction("gitrootAlloc") if malloc == nil { malloc = module.ExportedFunction("malloc") } s := uint64(len(message)) results, err := malloc.Call(r.ctx, s) if err != nil { return 0, oops.Wrapf(err, "can't malloc memory") } ptrA := results[0] // The pointer is a linear memory offset, which is where we write the name. if !module.Memory().WriteString(uint32(ptrA), message) { return 0, oops.Errorf("can't write memory") } r.memoryToGarbage = append(r.memoryToGarbage, memoryGarbage{module: module, ptrSize: ptrSize{ptr: ptrA, size: s}}) return (ptrA << uint64(32)) | s, nil } func (r *runtime) sendEmptyOrError(module api.Module, err error) uint64 { if err == nil { r.logger.Debug("no error to send") return 0 } ptrSize, err := r.sendData(module, err.Error()) if err != nil { r.logger.Error("can't sendError", err, logger.NewLoggerPair("originalErr", err.Error())) return 0 } return ptrSize } func (r *runtime) readString(m api.Module, errorMsg string, ptr uint32, size uint32) (string, error) { mem := m.Memory() if mem == nil { err := errors.New("memory not exist") r.logger.Error(errorMsg, err, logger.NewLoggerPair("ptr", ptr), logger.NewLoggerPair("size", size)) return "", err } txt, ok := mem.Read(ptr, size) if !ok { err := errors.New("memory read fail") r.logger.Error(errorMsg, err, logger.NewLoggerPair("ptr", ptr), logger.NewLoggerPair("size", size)) return "", err } return string(txt), nil } func (r *runtime) readASString(m api.Module, errorMsg string, offset uint32) (string, error) { // Length is four bytes before pointer. byteCount, ok := m.Memory().ReadUint32Le(offset - 4) if !ok || byteCount%2 != 0 { err := errors.New("Memory.ReadUint32Le fail") r.logger.Error(errorMsg, err, logger.NewLoggerPair("byteCount", byteCount), logger.NewLoggerPair("offset", offset)) return "", err } buf, ok := m.Memory().Read(offset, byteCount) if !ok { err := errors.New("Memory.Read fail") r.logger.Error(errorMsg, err, logger.NewLoggerPair("offset", offset), logger.NewLoggerPair("byteCount", byteCount)) return "", err } u16s := make([]uint16, len(buf)/2) lb := len(buf) for i := 0; i < lb; i += 2 { u16s[i/2] = uint16(buf[i]) + (uint16(buf[i+1]) << 8) } return string(utf16.Decode(u16s)), nil } type ptrSize struct { ptr uint64 size uint64 } func (r *runtime) free(module api.Module, ptrSize []ptrSize) error { free := module.ExportedFunction("gitrootFree") if free == nil { free = module.ExportedFunction("free") } if free != nil && len(free.Definition().ParamTypes()) == 1 { for _, p := range ptrSize { _, err := free.Call(r.ctx, p.ptr) if err != nil { r.logger.Error("can't free pluginConf 1 param", err, logger.NewLoggerPair("plugin", module.Name())) } } } else if free != nil { for _, p := range ptrSize { _, err := free.Call(r.ctx, p.ptr, p.size) if err != nil { r.logger.Error("can't free pluginConf", err, logger.NewLoggerPair("plugin", module.Name())) } } } return nil }