Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
projectdiscovery
GitHub Repository: projectdiscovery/nuclei
Path: blob/dev/pkg/js/devtools/bindgen/generator.go
2070 views
1
package generator
2
3
import (
4
"fmt"
5
"go/ast"
6
"go/importer"
7
"go/parser"
8
"go/token"
9
"go/types"
10
"log"
11
"os"
12
"strings"
13
14
_ "embed"
15
16
"github.com/pkg/errors"
17
"github.com/projectdiscovery/nuclei/v3/pkg/js/compiler"
18
)
19
20
var (
21
//go:embed templates/js_class.tmpl
22
jsClassFile string
23
//go:embed templates/go_class.tmpl
24
goClassFile string
25
//go:embed templates/markdown_class.tmpl
26
markdownClassFile string
27
)
28
29
// TemplateData contains the parameters for the JS code generator
30
type TemplateData struct {
31
PackageName string
32
PackagePath string
33
HasObjects bool
34
PackageFuncs map[string]string
35
PackageInterfaces map[string]string
36
PackageFuncsExtraNoType map[string]PackageFunctionExtra
37
PackageFuncsExtra map[string]PackageFuncExtra
38
PackageVars map[string]string
39
PackageVarsValues map[string]string
40
PackageTypes map[string]string
41
PackageTypesExtra map[string]PackageTypeExtra
42
PackageDefinedConstructor map[string]struct{}
43
44
typesPackage *types.Package
45
46
// NativeScripts contains the list of native scripts
47
// that should be included in the package.
48
NativeScripts []string
49
}
50
51
// PackageTypeExtra contains extra information about a type
52
type PackageTypeExtra struct {
53
Fields map[string]string
54
}
55
56
// PackageFuncExtra contains extra information about a function
57
type PackageFuncExtra struct {
58
Items map[string]PackageFunctionExtra
59
Doc string
60
}
61
62
// PackageFunctionExtra contains extra information about a function
63
type PackageFunctionExtra struct {
64
Args []string
65
Name string
66
Returns []string
67
Doc string
68
}
69
70
// newTemplateData creates a new template data structure
71
func newTemplateData(packagePrefix, pkgName string) *TemplateData {
72
return &TemplateData{
73
PackageName: pkgName,
74
PackagePath: packagePrefix + pkgName,
75
PackageFuncs: make(map[string]string),
76
PackageFuncsExtraNoType: make(map[string]PackageFunctionExtra),
77
PackageFuncsExtra: make(map[string]PackageFuncExtra),
78
PackageVars: make(map[string]string),
79
PackageVarsValues: make(map[string]string),
80
PackageTypes: make(map[string]string),
81
PackageInterfaces: make(map[string]string),
82
PackageTypesExtra: make(map[string]PackageTypeExtra),
83
PackageDefinedConstructor: make(map[string]struct{}),
84
}
85
}
86
87
// GetLibraryModules takes a directory and returns subdirectories as modules
88
func GetLibraryModules(directory string) ([]string, error) {
89
dirs, err := os.ReadDir(directory)
90
if err != nil {
91
return nil, errors.Wrap(err, "could not read directory")
92
}
93
var modules []string
94
for _, dir := range dirs {
95
if dir.IsDir() {
96
modules = append(modules, dir.Name())
97
}
98
}
99
return modules, nil
100
}
101
102
// CreateTemplateData creates a TemplateData structure from a directory
103
// of go source code.
104
func CreateTemplateData(directory string, packagePrefix string) (*TemplateData, error) {
105
fmt.Println(directory)
106
fset := token.NewFileSet()
107
108
pkgs, err := parser.ParseDir(fset, directory, nil, parser.ParseComments)
109
if err != nil {
110
return nil, errors.Wrap(err, "could not parse directory")
111
}
112
if len(pkgs) != 1 {
113
return nil, fmt.Errorf("expected 1 package, got %d", len(pkgs))
114
}
115
116
config := &types.Config{
117
Importer: importer.ForCompiler(fset, "source", nil),
118
}
119
var packageName string
120
var files []*ast.File
121
for k, v := range pkgs {
122
packageName = k
123
for _, f := range v.Files {
124
files = append(files, f)
125
}
126
break
127
}
128
129
pkg, err := config.Check(packageName, fset, files, nil)
130
if err != nil {
131
return nil, errors.Wrap(err, "could not check package")
132
}
133
134
if len(pkgs) == 0 {
135
return nil, errors.New("no packages found")
136
}
137
138
var pkgName string
139
for k := range pkgs {
140
pkgName = k
141
break
142
}
143
144
pkgMain := pkgs[pkgName]
145
146
log.Printf("[create] [discover] Package: %s\n", pkgMain.Name)
147
data := newTemplateData(packagePrefix, pkgMain.Name)
148
data.typesPackage = pkg
149
data.gatherPackageData(pkgMain, data)
150
151
for item, v := range data.PackageFuncsExtra {
152
if len(v.Items) == 0 {
153
delete(data.PackageFuncsExtra, item)
154
}
155
}
156
157
// map types with corresponding constructors
158
for constructor := range data.PackageDefinedConstructor {
159
object:
160
for k := range data.PackageTypes {
161
if strings.Contains(constructor, k) {
162
data.PackageTypes[k] = constructor
163
break object
164
}
165
}
166
}
167
for k, v := range data.PackageTypes {
168
if k == v || v == "" {
169
data.HasObjects = true
170
data.PackageTypes[k] = ""
171
}
172
}
173
174
return data, nil
175
}
176
177
// InitNativeScripts initializes the native scripts array
178
// with all the exported functions from the runtime
179
func (d *TemplateData) InitNativeScripts() {
180
runtime := compiler.InternalGetGeneratorRuntime()
181
182
exports := runtime.Get("exports")
183
if exports == nil {
184
return
185
}
186
exportsObj := exports.Export()
187
if exportsObj == nil {
188
return
189
}
190
for v := range exportsObj.(map[string]interface{}) {
191
d.NativeScripts = append(d.NativeScripts, v)
192
}
193
}
194
195
// gatherPackageData gathers data about the package
196
func (d *TemplateData) gatherPackageData(astNode ast.Node, data *TemplateData) {
197
ast.Inspect(astNode, func(node ast.Node) bool {
198
switch node := node.(type) {
199
case *ast.FuncDecl:
200
extra := d.collectFuncDecl(node)
201
if extra.Name == "" {
202
return true
203
}
204
data.PackageFuncsExtraNoType[node.Name.Name] = extra
205
data.PackageFuncs[node.Name.Name] = node.Name.Name
206
case *ast.TypeSpec:
207
if !node.Name.IsExported() {
208
return true
209
}
210
if node.Type == nil {
211
return true
212
}
213
structDecl, ok := node.Type.(*ast.StructType)
214
if !ok {
215
return true
216
}
217
218
packageTypes := PackageTypeExtra{
219
Fields: make(map[string]string),
220
}
221
for _, field := range structDecl.Fields.List {
222
fieldName := field.Names[0].Name
223
224
var fieldTypeValue string
225
switch fieldType := field.Type.(type) {
226
case *ast.Ident: // Field type is a simple identifier
227
fieldTypeValue = fieldType.Name
228
case *ast.ArrayType:
229
switch fieldType.Elt.(type) {
230
case *ast.Ident:
231
fieldTypeValue = fmt.Sprintf("[]%s", fieldType.Elt.(*ast.Ident).Name)
232
case *ast.StarExpr:
233
fieldTypeValue = fmt.Sprintf("[]%s", d.handleStarExpr(fieldType.Elt.(*ast.StarExpr)))
234
}
235
case *ast.SelectorExpr: // Field type is a qualified identifier
236
fieldTypeValue = fmt.Sprintf("%s.%s", fieldType.X, fieldType.Sel)
237
}
238
packageTypes.Fields[fieldName] = fieldTypeValue
239
}
240
if len(packageTypes.Fields) == 0 {
241
return true
242
}
243
data.PackageTypesExtra[node.Name.Name] = packageTypes
244
case *ast.GenDecl:
245
identifyGenDecl(astNode, node, data)
246
}
247
return true
248
})
249
}
250
251
func identifyGenDecl(node ast.Node, decl *ast.GenDecl, data *TemplateData) {
252
for _, spec := range decl.Specs {
253
switch spec := spec.(type) {
254
case *ast.ValueSpec:
255
if !spec.Names[0].IsExported() {
256
continue
257
}
258
if len(spec.Values) == 0 {
259
continue
260
}
261
data.PackageVars[spec.Names[0].Name] = spec.Names[0].Name
262
data.PackageVarsValues[spec.Names[0].Name] = spec.Values[0].(*ast.BasicLit).Value
263
case *ast.TypeSpec:
264
if !spec.Name.IsExported() {
265
continue
266
}
267
if spec.Type == nil {
268
continue
269
}
270
271
switch spec.Type.(type) {
272
case *ast.InterfaceType:
273
data.PackageInterfaces[spec.Name.Name] = convertCommentsToJavascript(decl.Doc.Text())
274
275
case *ast.StructType:
276
data.PackageFuncsExtra[spec.Name.Name] = PackageFuncExtra{
277
Items: make(map[string]PackageFunctionExtra),
278
Doc: convertCommentsToJavascript(decl.Doc.Text()),
279
}
280
281
// Traverse the AST.
282
collectStructFuncsFromAST(node, spec, data)
283
data.PackageTypes[spec.Name.Name] = spec.Name.Name
284
}
285
}
286
}
287
}
288
289
func collectStructFuncsFromAST(node ast.Node, spec *ast.TypeSpec, data *TemplateData) {
290
ast.Inspect(node, func(n ast.Node) bool {
291
if fn, isFunc := n.(*ast.FuncDecl); isFunc && fn.Name.IsExported() {
292
processFunc(fn, spec, data)
293
}
294
return true
295
})
296
}
297
298
func processFunc(fn *ast.FuncDecl, spec *ast.TypeSpec, data *TemplateData) {
299
if fn.Recv == nil || len(fn.Recv.List) == 0 {
300
return
301
}
302
303
if t, ok := fn.Recv.List[0].Type.(*ast.StarExpr); ok {
304
if ident, ok := t.X.(*ast.Ident); ok && spec.Name.Name == ident.Name {
305
processFunctionDetails(fn, ident, data)
306
}
307
}
308
}
309
310
func processFunctionDetails(fn *ast.FuncDecl, ident *ast.Ident, data *TemplateData) {
311
extra := PackageFunctionExtra{
312
Name: fn.Name.Name,
313
Args: extractArgs(fn),
314
Doc: convertCommentsToJavascript(fn.Doc.Text()),
315
Returns: data.extractReturns(fn),
316
}
317
data.PackageFuncsExtra[ident.Name].Items[fn.Name.Name] = extra
318
}
319
320
func extractArgs(fn *ast.FuncDecl) []string {
321
args := make([]string, 0)
322
for _, arg := range fn.Type.Params.List {
323
for _, name := range arg.Names {
324
args = append(args, name.Name)
325
}
326
}
327
return args
328
}
329
330
func (d *TemplateData) extractReturns(fn *ast.FuncDecl) []string {
331
returns := make([]string, 0)
332
if fn.Type.Results == nil {
333
return returns
334
}
335
for _, ret := range fn.Type.Results.List {
336
returnType := d.extractReturnType(ret)
337
if returnType != "" {
338
returns = append(returns, returnType)
339
}
340
}
341
return returns
342
}
343
344
func (d *TemplateData) extractReturnType(ret *ast.Field) string {
345
switch v := ret.Type.(type) {
346
case *ast.ArrayType:
347
if v, ok := v.Elt.(*ast.Ident); ok {
348
return fmt.Sprintf("[]%s", v.Name)
349
}
350
if v, ok := v.Elt.(*ast.StarExpr); ok {
351
return fmt.Sprintf("[]%s", d.handleStarExpr(v))
352
}
353
case *ast.Ident:
354
return v.Name
355
case *ast.StarExpr:
356
return d.handleStarExpr(v)
357
}
358
return ""
359
}
360
361
func (d *TemplateData) handleStarExpr(v *ast.StarExpr) string {
362
switch vk := v.X.(type) {
363
case *ast.Ident:
364
return vk.Name
365
case *ast.SelectorExpr:
366
if vk.X != nil {
367
d.collectTypeFromExternal(d.typesPackage, vk.X.(*ast.Ident).Name, vk.Sel.Name)
368
}
369
return vk.Sel.Name
370
}
371
return ""
372
}
373
374
func (d *TemplateData) collectTypeFromExternal(pkg *types.Package, pkgName, name string) {
375
if pkgName == "goja" {
376
// no need to attempt to collect types from goja ( this is metadata )
377
return
378
}
379
extra := PackageTypeExtra{
380
Fields: make(map[string]string),
381
}
382
383
for _, importValue := range pkg.Imports() {
384
if importValue.Name() != pkgName {
385
continue
386
}
387
obj := importValue.Scope().Lookup(name)
388
if obj == nil || !obj.Exported() {
389
continue
390
}
391
typeName, ok := obj.(*types.TypeName)
392
if !ok {
393
continue
394
}
395
underlying, ok := typeName.Type().Underlying().(*types.Struct)
396
if !ok {
397
continue
398
}
399
for i := 0; i < underlying.NumFields(); i++ {
400
field := underlying.Field(i)
401
fieldType := field.Type().String()
402
403
if val, ok := field.Type().Underlying().(*types.Pointer); ok {
404
fieldType = field.Name()
405
d.collectTypeFromExternal(pkg, pkgName, val.Elem().(*types.Named).Obj().Name())
406
}
407
if _, ok := field.Type().Underlying().(*types.Struct); ok {
408
fieldType = field.Name()
409
d.collectTypeFromExternal(pkg, pkgName, field.Name())
410
}
411
extra.Fields[field.Name()] = fieldType
412
}
413
if len(extra.Fields) > 0 {
414
d.PackageTypesExtra[name] = extra
415
}
416
}
417
}
418
419
func (d *TemplateData) collectFuncDecl(decl *ast.FuncDecl) (extra PackageFunctionExtra) {
420
if decl.Recv != nil {
421
return
422
}
423
if !decl.Name.IsExported() {
424
return
425
}
426
extra.Name = decl.Name.Name
427
extra.Doc = convertCommentsToJavascript(decl.Doc.Text())
428
429
isConstructor := false
430
431
for _, arg := range decl.Type.Params.List {
432
p := exprToString(arg.Type)
433
if strings.Contains(p, "goja.ConstructorCall") {
434
isConstructor = true
435
}
436
for _, name := range arg.Names {
437
extra.Args = append(extra.Args, name.Name)
438
}
439
}
440
if isConstructor {
441
d.PackageDefinedConstructor[decl.Name.Name] = struct{}{}
442
}
443
444
extra.Returns = d.extractReturns(decl)
445
return extra
446
}
447
448
// convertCommentsToJavascript converts comments to javascript comments.
449
func convertCommentsToJavascript(comments string) string {
450
suffix := strings.Trim(strings.TrimSuffix(strings.ReplaceAll(comments, "\n", "\n// "), "// "), "\n")
451
return fmt.Sprintf("// %s", suffix)
452
}
453
454
// exprToString converts an expression to a string
455
func exprToString(expr ast.Expr) string {
456
switch t := expr.(type) {
457
case *ast.Ident:
458
return t.Name
459
case *ast.SelectorExpr:
460
return exprToString(t.X) + "." + t.Sel.Name
461
case *ast.StarExpr:
462
return exprToString(t.X)
463
case *ast.ArrayType:
464
return "[]" + exprToString(t.Elt)
465
case *ast.InterfaceType:
466
return "interface{}"
467
// Add more cases to handle other types
468
default:
469
return fmt.Sprintf("%T", expr)
470
}
471
}
472
473