// Copyright 2013 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 irutil // import "honnef.co/go/tools/go/ir/irutil"

import (
	"go/ast"
	"go/types"

	_ "unsafe" // for linkname hack

	"honnef.co/go/tools/go/ir"
)

// This file defines utilities for visiting the IR of
// a Program.
//
// TODO(adonovan): test coverage.

// AllFunctions finds and returns the set of functions potentially
// needed by program prog, as determined by a simple linker-style
// reachability algorithm starting from the members and method-sets of
// each package.  The result may include anonymous functions and
// synthetic wrappers.
//
// Precondition: all packages are built.
//
// TODO(adonovan): this function is underspecified. It doesn't
// actually work like a linker, which computes reachability from main
// using something like go/callgraph/cha (without materializing the
// call graph). In fact, it treats all public functions and all
// methods of public non-parameterized types as roots, even though
// they may be unreachable--but only in packages created from syntax.
//
// I think we should deprecate AllFunctions function in favor of two
// clearly defined ones:
//
//  1. The first would efficiently compute CHA reachability from a set
//     of main packages, making it suitable for a whole-program
//     analysis context with InstantiateGenerics, in conjunction with
//     Program.Build.
//
//  2. The second would return only the set of functions corresponding
//     to source Func{Decl,Lit} syntax, like SrcFunctions in
//     go/analysis/passes/buildssa; this is suitable for
//     package-at-a-time (or handful of packages) context.
//     ir.Package could easily expose it as a field.
//
// We could add them unexported for now and use them via the linkname hack.
func AllFunctions(prog *ir.Program) map[*ir.Function]bool {
	seen := make(map[*ir.Function]bool)

	var function func(fn *ir.Function)
	function = func(fn *ir.Function) {
		if !seen[fn] {
			seen[fn] = true
			var buf [10]*ir.Value // avoid alloc in common case
			for _, b := range fn.Blocks {
				for _, instr := range b.Instrs {
					for _, op := range instr.Operands(buf[:0]) {
						if fn, ok := (*op).(*ir.Function); ok {
							function(fn)
						}
					}
				}
			}
		}
	}

	// TODO(adonovan): opt: provide a way to share a builder
	// across a sequence of MethodValue calls.

	methodsOf := func(T types.Type) {
		if !types.IsInterface(T) {
			mset := prog.MethodSets.MethodSet(T)
			for sel := range mset.Methods() {
				// Skip generic methods.
				if sel.Obj().(*types.Func).Signature().TypeParams() == nil {
					function(prog.MethodValue(sel))
				}
			}
		}
	}

	// Historically, Program.RuntimeTypes used to include the type
	// of any exported member of a package loaded from syntax that
	// has a non-parameterized type, plus all types
	// reachable from that type using reflection, even though
	// these runtime types may not be required for them.
	//
	// Rather than break existing programs that rely on
	// AllFunctions visiting extra methods that are unreferenced
	// by IR and unreachable via reflection, we moved the logic
	// here, unprincipled though it is.
	// (See doc comment for better ideas.)
	//
	// Nonetheless, after the move, we no longer visit every
	// method of any type recursively reachable from T, only the
	// methods of T and *T themselves, and we only apply this to
	// named types T, and not to the type of every exported
	// package member.
	exportedTypeHack := func(t *ir.Type) {
		if isSyntactic(t.Package()) &&
			ast.IsExported(t.Name()) &&
			!types.IsInterface(t.Type()) {
			// Consider only named types.
			// (Ignore aliases and unsafe.Pointer.)
			if named, ok := t.Type().(*types.Named); ok {
				// Skip generic types.
				if named.TypeParams() == nil {
					methodsOf(named)                   //  T
					methodsOf(types.NewPointer(named)) // *T
				}
			}
		}
	}

	for _, pkg := range prog.AllPackages() {
		for _, mem := range pkg.Members {
			switch mem := mem.(type) {
			case *ir.Function:
				// Visit all package-level declared functions.
				//
				// (This may include generic functions, which is
				// inconsistent with the treatment of methods:
				// we skip both generic methods,
				// and methods of generic types.)
				function(mem)

			case *ir.Type:
				exportedTypeHack(mem)
			}
		}
	}

	// Visit all methods of types for which runtime types were
	// materialized, as they are reachable through reflection.
	for _, T := range prog.RuntimeTypes() {
		methodsOf(T)
	}

	return seen
}

// MainPackages returns the subset of the specified packages
// named "main" that define a main function.
// The result may include synthetic "testmain" packages.
func MainPackages(pkgs []*ir.Package) []*ir.Package {
	var mains []*ir.Package
	for _, pkg := range pkgs {
		if pkg.Pkg.Name() == "main" && pkg.Func("main") != nil {
			mains = append(mains, pkg)
		}
	}
	return mains
}

// TODO(adonovan): propose a principled API for this. One possibility
// is a new field, Package.SrcFunctions []*Function, which would
// contain the list of SrcFunctions described in point 2 of the
// AllFunctions doc comment, or nil if the package is not from syntax.
// But perhaps overloading nil vs empty slice is too subtle.
//
//go:linkname isSyntactic honnef.co/go/tools/go/ir.isSyntactic
func isSyntactic(pkg *ir.Package) bool
