// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package vulncheck

import (
	"path"
	"reflect"
	"testing"

	"golang.org/x/tools/go/callgraph/cha"
	"golang.org/x/tools/go/packages/packagestest"
	"golang.org/x/tools/go/ssa"
	"golang.org/x/tools/go/ssa/ssautil"
)

// funcNames returns a set of function names for `funcs`.
func funcNames(funcs map[*ssa.Function]bool) map[string]bool {
	fs := make(map[string]bool)
	for f := range funcs {
		fs[dbFuncName(f)] = true
	}
	return fs
}

func TestSlicing(t *testing.T) {
	// test program
	p := `
package slice

func X() {}
func Y() {}

// not reachable
func id(i int) int {
        return i
}

// not reachable
func inc(i int) int {
        return i + 1
}

func Apply(b bool, h func()) {
        if b {
                func() {
                        print("applied")
                }()
                return
        }
        h()
}

type I interface {
        Foo()
}

type A struct{}

func (a A) Foo() {}

// not reachable
func (a A) Bar() {}

type B struct{}

func (b B) Foo() {}

func debug(s string) {
        print(s)
}

func Do(i I, input string) {
        debug(input)

        i.Foo()

        func(x string) {
                func(l int) {
                        print(l)
                }(len(x))
        }(input)
}`

	e := packagestest.Export(t, packagestest.Modules, []packagestest.Module{
		{
			Name:  "some/module",
			Files: map[string]interface{}{"slice/slice.go": p},
		},
	})

	graph := NewPackageGraph("go1.18")
	err := graph.LoadPackagesAndMods(e.Config, nil, []string{path.Join(e.Temp(), "/module/slice")}, true)
	if err != nil {
		t.Fatal(err)
	}
	prog, ssaPkgs := ssautil.AllPackages(graph.TopPkgs(), 0)
	prog.Build()

	pkg := ssaPkgs[0]
	sources := map[*ssa.Function]bool{pkg.Func("Apply"): true, pkg.Func("Do"): true}
	fs := funcNames(forwardSlice(sources, cha.CallGraph(prog)))
	want := map[string]bool{
		"Apply":   true,
		"Apply$1": true,
		"X":       true,
		"Y":       true,
		"Do":      true,
		"Do$1":    true,
		"Do$1$1":  true,
		"debug":   true,
		"A.Foo":   true,
		"B.Foo":   true,
	}
	if !reflect.DeepEqual(want, fs) {
		t.Errorf("want %v; got %v", want, fs)
	}
}
