Reputation: 2005
I'm trying to build a linter that is able to check whether a specific function A is called from a set of functions B and report any function B' that does not call A (directly or indirectly).
I'm using the analysis
framework so every package is analysed separately. Using Facts to export type information and importing that type information whenever I find a function B', I am able to build a call graph of the current package and related dependencies (that result in a call to A) and finally check whether there exists a connection from B to A.
Everything works fine for my test cases, but as soon as I test it on a real project, the analysis takes forever to run (literally times out).
I've figured out that I add too many dependencies when finding a function in B. What I do currently is pre-analyze every package and look for A and any function that calls A. Once I find a transitive call to A, I mark the package.
In the next step, after I find a B', I check every import and build the imported package only if it was marked (non-marked packages are added to the ssa.Program
as dummies).
Now, this does not work with functions like this:
func Caller(callee func()) { callee() }
When a function is passed as a parameter to a function in a different package, I lose all information about it (because everything is modular) and I can't know what packages imports the package with Caller
.
For this reason, I mark every package that exports a function with a function type parameter as interesting
(I know you can have functions in structs, I still need to figure out how to solve that).
So now, for my actual question:
Is it possible to know for a function call a(b)
what dependencies each of the parameters attracts (what packages they are used by)?
With this information I could build a program that only contains relevant packages from B' to A, I think.
I would also be fine with only the dependencies of the function call itself.
I use the following code to generate the program:
preScanRes, ok := pass.ResultOf[PreScanAnalyzer].(*preScanResult)
if !ok {
panic("no pre scan result")
}
if preScanRes.canAccessCallee || preScanRes.hasFuncWithFuncParam {
// Export type info of the current package.
fact := typesFact{pass.TypesInfo}
pass.ExportPackageFact(&fact)
}
if !preScanRes.hasCaller {
return nil, nil
}
// Build program.
prog := ssa.NewProgram(pass.Fset, ssa.InstantiateGenerics)
// Add imported packages to the program.
// This is similar to what buildssa.Analyzer does, but we also build imported packages.
var addImports func(pp []*types.Package, depth int)
addImports = func(pp []*types.Package, depth int) {
// For debugging only.
//if depth > 2 {
// return
//}
for _, p := range pp {
tfact := &typesFact{}
// To build the imported package, we need it's type info.
if ok := pass.ImportPackageFact(p, tfact); !ok {
// No data. Create as dummy.
pkg := prog.CreatePackage(p, nil, nil, true)
pkg.Build()
continue
}
// Get package files from type info.
files := make([]*ast.File, len(tfact.typesInfo.FileVersions))
i := 0
for f := range tfact.typesInfo.FileVersions {
files[i] = f
i++
}
// Add package to program.
pkg := prog.CreatePackage(p, files, tfact.typesInfo, true)
// Add imports of the imported package.
addImports(p.Imports(), depth+1)
pkg.Build()
}
}
addImports(pass.Pkg.Imports(), 0)
// Add the primary package.
prog.CreatePackage(pass.Pkg, pass.Files, pass.TypesInfo, false)
prog.Build()
// Build call graph, find path from B to A.
// ...
Upvotes: -3
Views: 79