2024-08-06 01:03:23 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"encoding/json"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
2024-10-07 06:25:27 +00:00
|
|
|
"log"
|
2024-10-07 05:31:19 +00:00
|
|
|
"os"
|
2024-08-06 01:03:23 +00:00
|
|
|
"os/exec"
|
|
|
|
"sync"
|
2024-10-07 06:25:27 +00:00
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
ClangMaxRetries = 5
|
|
|
|
ClangRetryDelay = 3 * time.Second
|
2024-08-06 01:03:23 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
func clangExec(ctx context.Context, clangBin, inputHeader string, cflags []string) ([]interface{}, error) {
|
|
|
|
|
|
|
|
clangArgs := []string{`-x`, `c++`}
|
|
|
|
clangArgs = append(clangArgs, cflags...)
|
|
|
|
clangArgs = append(clangArgs, `-Xclang`, `-ast-dump=json`, `-fsyntax-only`, inputHeader)
|
|
|
|
|
|
|
|
cmd := exec.CommandContext(ctx, clangBin, clangArgs...)
|
|
|
|
pr, err := cmd.StdoutPipe()
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("StdoutPipe: %w", err)
|
|
|
|
}
|
|
|
|
|
2024-10-07 05:31:19 +00:00
|
|
|
cmd.Stderr = os.Stderr
|
|
|
|
|
2024-08-06 01:03:23 +00:00
|
|
|
err = cmd.Start()
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("Start: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
var wg sync.WaitGroup
|
|
|
|
var inner []interface{}
|
2024-10-16 05:16:53 +00:00
|
|
|
var innerErr error
|
2024-08-06 01:03:23 +00:00
|
|
|
|
|
|
|
wg.Add(1)
|
|
|
|
go func() {
|
|
|
|
defer wg.Done()
|
2024-10-16 05:16:53 +00:00
|
|
|
inner, innerErr = clangStripUpToFile(pr, inputHeader)
|
2024-08-06 01:03:23 +00:00
|
|
|
}()
|
|
|
|
|
|
|
|
err = cmd.Wait()
|
|
|
|
if err != nil {
|
2024-10-16 05:16:53 +00:00
|
|
|
return nil, fmt.Errorf("Command: %w", err)
|
2024-08-06 01:03:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
wg.Wait()
|
|
|
|
|
2024-10-16 05:16:53 +00:00
|
|
|
return inner, innerErr
|
2024-08-06 01:03:23 +00:00
|
|
|
}
|
2024-10-07 06:25:27 +00:00
|
|
|
|
|
|
|
func mustClangExec(ctx context.Context, clangBin, inputHeader string, cflags []string) []interface{} {
|
|
|
|
|
|
|
|
for i := 0; i < ClangMaxRetries; i++ {
|
|
|
|
astInner, err := clangExec(ctx, clangBin, inputHeader, cflags)
|
|
|
|
if err != nil {
|
|
|
|
// Log and continue with next retry
|
|
|
|
log.Printf("WARNING: Clang execution failed: %v", err)
|
|
|
|
time.Sleep(ClangRetryDelay)
|
|
|
|
log.Printf("Retrying...")
|
2024-10-16 05:16:53 +00:00
|
|
|
continue
|
2024-10-07 06:25:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Success
|
|
|
|
return astInner
|
|
|
|
}
|
|
|
|
|
|
|
|
// Failed 5x
|
|
|
|
// Panic
|
|
|
|
panic("Clang failed 5x parsing file " + inputHeader)
|
|
|
|
}
|
2024-08-06 01:03:23 +00:00
|
|
|
|
|
|
|
// clangStripUpToFile strips all AST nodes from the clang output until we find
|
|
|
|
// one that really originated in the source file.
|
|
|
|
// This cleans out everything in the translation unit that came from an
|
|
|
|
// #included file.
|
|
|
|
// @ref https://stackoverflow.com/a/71128654
|
|
|
|
func clangStripUpToFile(stdout io.Reader, inputFilePath string) ([]interface{}, error) {
|
|
|
|
|
|
|
|
var obj = map[string]interface{}{}
|
|
|
|
err := json.NewDecoder(stdout).Decode(&obj)
|
|
|
|
if err != nil {
|
2024-08-15 07:51:31 +00:00
|
|
|
return nil, fmt.Errorf("json.Decode: %v", err)
|
2024-08-06 01:03:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
inner, ok := obj["inner"].([]interface{})
|
|
|
|
if !ok {
|
|
|
|
return nil, errors.New("no inner")
|
|
|
|
}
|
|
|
|
|
2024-08-10 01:21:30 +00:00
|
|
|
// This can't be done by matching the first position only, since it's possible
|
|
|
|
// that there are more #include<>s further down the file
|
2024-08-06 01:03:23 +00:00
|
|
|
|
2024-08-10 01:21:30 +00:00
|
|
|
ret := make([]interface{}, 0, len(inner))
|
|
|
|
|
|
|
|
for _, entry := range inner {
|
2024-08-06 01:03:23 +00:00
|
|
|
|
|
|
|
entry, ok := entry.(map[string]interface{})
|
|
|
|
if !ok {
|
|
|
|
return nil, errors.New("entry is not a map")
|
|
|
|
}
|
|
|
|
|
|
|
|
if _, ok := entry["isImplicit"]; ok {
|
2024-08-10 01:21:30 +00:00
|
|
|
// Don't keep
|
2024-08-06 01:03:23 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
var match_filename = ""
|
|
|
|
|
|
|
|
if loc, ok := entry["loc"].(map[string]interface{}); ok {
|
|
|
|
if includedFrom, ok := loc["includedFrom"].(map[string]interface{}); ok {
|
|
|
|
if filename, ok := includedFrom["file"].(string); ok {
|
|
|
|
match_filename = filename
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if match_filename == "" {
|
|
|
|
if expansionloc, ok := loc["expansionLoc"].(map[string]interface{}); ok {
|
|
|
|
if filename, ok := expansionloc["file"].(string); ok {
|
|
|
|
match_filename = filename
|
2024-08-10 01:21:30 +00:00
|
|
|
|
|
|
|
} else if includedFrom, ok := expansionloc["includedFrom"].(map[string]interface{}); ok {
|
|
|
|
if filename, ok := includedFrom["file"].(string); ok {
|
|
|
|
match_filename = filename
|
|
|
|
}
|
2024-08-06 01:03:23 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return nil, errors.New("no loc")
|
|
|
|
}
|
|
|
|
|
2024-08-10 01:21:30 +00:00
|
|
|
// log.Printf("# name=%v kind=%v filename=%q\n", entry["name"], entry["kind"], match_filename)
|
2024-08-06 01:03:23 +00:00
|
|
|
|
2024-08-10 01:21:30 +00:00
|
|
|
if match_filename == "" {
|
|
|
|
// Keep
|
|
|
|
ret = append(ret, entry)
|
2024-08-06 01:03:23 +00:00
|
|
|
|
2024-08-10 01:21:30 +00:00
|
|
|
} else if match_filename != inputFilePath {
|
|
|
|
// Skip this
|
|
|
|
} else {
|
|
|
|
// Keep this
|
|
|
|
// ret = append(ret, entry)
|
|
|
|
}
|
|
|
|
}
|
2024-08-06 01:03:23 +00:00
|
|
|
|
2024-08-10 01:21:30 +00:00
|
|
|
return ret, nil
|
2024-08-06 01:03:23 +00:00
|
|
|
}
|