blob: c5e112c55a2f8b3901456d048bed4924848031a5 [file] [log] [blame]
// Copyright 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// protocol_gen (re)generates the cppdap .h and .cpp files that describe the
// DAP protocol.
package main
import (
"bytes"
"encoding/json"
"flag"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"os/exec"
"path"
"reflect"
"runtime"
"sort"
"strings"
)
const (
protocolURL = "https://raw.githubusercontent.com/microsoft/vscode-debugadapter-node/master/debugProtocol.json"
packageURL = "https://raw.githubusercontent.com/microsoft/vscode-debugadapter-node/master/protocol/package.json"
versionTag = "${version}"
commonPrologue = `// Copyright 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Generated with protocol_gen.go -- do not edit this file.
// go run scripts/protocol_gen/protocol_gen.go
//
// DAP version ${version}
`
headerPrologue = commonPrologue + `
#ifndef dap_protocol_h
#define dap_protocol_h
#include "optional.h"
#include "typeinfo.h"
#include "typeof.h"
#include "variant.h"
#include <string>
#include <type_traits>
#include <vector>
namespace dap {
struct Request {};
struct Response {};
struct Event {};
`
headerEpilogue = `} // namespace dap
#endif // dap_protocol_h
`
cppPrologue = commonPrologue + `
#include "dap/protocol.h"
namespace dap {
`
cppEpilogue = `} // namespace dap
`
fuzzerHeaderPrologue = commonPrologue + `
#ifndef dap_fuzzer_h
#define dap_fuzzer_h
#include "dap/protocol.h"
#define DAP_REQUEST_LIST() \
`
fuzzerHeaderEpilogue = `
#endif // dap_fuzzer_h
`
)
func main() {
flag.Parse()
if err := run(); err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
os.Exit(1)
}
}
// root object of the parsed schema
type root struct {
Schema string `json:"$schema"`
Title string `json:"title"`
Description string `json:"description"`
Ty string `json:"type"`
Definitions map[string]*definition `json:"definitions"`
}
// definitions() returns a lexicographically-stored list of named definitions
func (r *root) definitions() []namedDefinition {
sortedDefinitions := make([]namedDefinition, 0, len(r.Definitions))
for name, def := range r.Definitions {
sortedDefinitions = append(sortedDefinitions, namedDefinition{name, def})
}
sort.Slice(sortedDefinitions, func(i, j int) bool { return sortedDefinitions[i].name < sortedDefinitions[j].name })
return sortedDefinitions
}
// getRef() returns the namedDefinition with the given reference string
// References have the form '#/definitions/<name>'
func (r *root) getRef(ref string) (namedDefinition, error) {
if !strings.HasPrefix(ref, "#/definitions/") {
return namedDefinition{}, fmt.Errorf("Unknown $ref '%s'", ref)
}
name := strings.TrimPrefix(ref, "#/definitions/")
def, ok := r.Definitions[name]
if !ok {
return namedDefinition{}, fmt.Errorf("Unknown $ref '%s'", ref)
}
return namedDefinition{name, def}, nil
}
// namedDefinition is a [name, definition] pair
type namedDefinition struct {
name string // name as defined in the schema
def *definition // definition node
}
// definition is the core JSON object type in the schema, describing requests,
// responses, events, properties and more.
type definition struct {
Ty interface{} `json:"type"`
Title string `json:"title"`
Items *definition `json:"items"`
Description string `json:"description"`
Properties properties `json:"properties"`
Required []string `json:"required"`
OneOf []*definition `json:"oneOf"`
AllOf []*definition `json:"allOf"`
Ref string `json:"$ref"`
OpenEnum []string `json:"_enum"`
ClosedEnum []string `json:"enum"`
// The resolved C++ type of the definition
cppType cppType
}
// properties is a map of property name to the property definition
type properties map[string]*definition
// foreach() calls cb for each property in the map. cb is called in
// lexicographically-stored order for deterministic processing.
func (p *properties) foreach(cb func(string, *definition) error) error {
sorted := make([]namedDefinition, 0, len(*p))
for name, property := range *p {
sorted = append(sorted, namedDefinition{name, property})
}
sort.Slice(sorted, func(i, j int) bool { return sorted[i].name < sorted[j].name })
for _, entry := range sorted {
if err := cb(entry.name, entry.def); err != nil {
return err
}
}
return nil
}
// cppField describes a single C++ field of a C++ structure
type cppField struct {
desc string
ty cppType
name string
optional bool
enumVals []string
}
// cppType is an interface for all C++ generated types
type cppType interface {
// Name() returns the type name, used to refer to the type
Name() string
// Dependencies() returns a list of dependent types, which must be emitted
// before this type
Dependencies() []cppType
// File() returns the cppTargetFile that this type should be written to
File() cppTargetFile
// Description() returns the type description as parsed from the schema
Description() string
// DefaultValue() returns the default value that should be used for any
// fields of this type
DefaultValue() string
// WriteHeader() writes the type definition to the given .h file writer
WriteHeader(w io.Writer)
// WriteHeader() writes the type definition to the given .cpp file writer
WriteCPP(w io.Writer)
// WriteFuzzerH() writes the fuzzer DAP_REQUEST() macro to the given .h writer
WriteFuzzerH(w io.Writer)
// GetFuzzerNames() returns a list of the protocol name, the fields, and field enum values for this type
GetFuzzerNames() []string
}
// cppStruct implements the cppType interface, describing a C++ structure
type cppStruct struct {
name string // C++ type name
protoname string // DAP name
desc string // Description
base string // Base class name
fields []cppField // All fields of the structure
deps []cppType // Types this structure depends on
typedefs []cppTypedef // All nested typedefs
file cppTargetFile // The files this type should be written to
}
func (s *cppStruct) Name() string { return s.name }
func (s *cppStruct) Dependencies() []cppType { return s.deps }
func (s *cppStruct) File() cppTargetFile { return s.file }
func (s *cppStruct) Description() string { return s.desc }
func (s *cppStruct) DefaultValue() string { return "" }
func (s *cppStruct) WriteHeader(w io.Writer) {
if s.desc != "" {
io.WriteString(w, "// ")
io.WriteString(w, strings.ReplaceAll(s.desc, "\n", "\n// "))
io.WriteString(w, "\n")
}
io.WriteString(w, "struct ")
io.WriteString(w, s.name)
if s.base != "" {
io.WriteString(w, " : public ")
io.WriteString(w, s.base)
}
io.WriteString(w, " {")
// typedefs
for _, t := range s.typedefs {
io.WriteString(w, "\n using ")
io.WriteString(w, t.from)
io.WriteString(w, " = ")
io.WriteString(w, t.to.Name())
io.WriteString(w, ";")
}
for _, f := range s.fields {
if f.desc != "" {
io.WriteString(w, "\n // ")
io.WriteString(w, strings.ReplaceAll(f.desc, "\n", "\n // "))
}
io.WriteString(w, "\n ")
if f.optional {
io.WriteString(w, "optional<")
io.WriteString(w, f.ty.Name())
io.WriteString(w, ">")
} else {
io.WriteString(w, f.ty.Name())
}
io.WriteString(w, " ")
io.WriteString(w, sanitize(f.name))
if !f.optional && f.ty.DefaultValue() != "" {
io.WriteString(w, " = ")
io.WriteString(w, f.ty.DefaultValue())
}
io.WriteString(w, ";")
}
io.WriteString(w, "\n};\n\n")
io.WriteString(w, "DAP_DECLARE_STRUCT_TYPEINFO(")
io.WriteString(w, s.name)
io.WriteString(w, ");\n\n")
}
func (s *cppStruct) WriteCPP(w io.Writer) {
// typeinfo
io.WriteString(w, "DAP_IMPLEMENT_STRUCT_TYPEINFO(")
io.WriteString(w, s.name)
io.WriteString(w, ",\n \"")
io.WriteString(w, s.protoname)
io.WriteString(w, "\"")
for _, f := range s.fields {
io.WriteString(w, ",\n ")
io.WriteString(w, "DAP_FIELD(")
io.WriteString(w, sanitize(f.name))
io.WriteString(w, ", \"")
io.WriteString(w, f.name)
io.WriteString(w, "\")")
}
io.WriteString(w, ");\n\n")
}
func (s *cppStruct) WriteFuzzerH(header io.Writer) {
// only write fuzzer macros for Request types
if s.base != "Request" {
return
}
io.WriteString(header, "DAP_REQUEST(dap::")
io.WriteString(header, s.name)
io.WriteString(header, ", dap::")
responseType := ""
// check typedefs for response
for _, t := range s.typedefs {
if t.from == "Response" {
responseType = t.to.Name()
}
}
// if no response, throw an error
if responseType == "" {
panic("No corresponding response type found for " + s.name)
}
io.WriteString(header, responseType)
io.WriteString(header, ") \\\n")
}
func (s *cppStruct) GetFuzzerNames() []string {
ret := []string{}
if s.protoname != "" {
ret = append(ret, s.protoname)
}
for _, f := range s.fields {
ret = append(ret, f.name)
if (f.enumVals != nil) && (len(f.enumVals) > 0) {
ret = append(ret, f.enumVals...)
}
}
return ret
}
// cppStruct implements the cppType interface, describing a C++ typedef
type cppTypedef struct {
from string // Name of the typedef
to cppType // Target of the typedef
desc string // Description
enumVals []string // Enum values
}
func (ty *cppTypedef) Name() string { return ty.from }
func (ty *cppTypedef) Dependencies() []cppType { return []cppType{ty.to} }
func (ty *cppTypedef) File() cppTargetFile { return types }
func (ty *cppTypedef) Description() string { return ty.desc }
func (ty *cppTypedef) DefaultValue() string { return ty.to.DefaultValue() }
func (ty *cppTypedef) WriteHeader(w io.Writer) {
if ty.desc != "" {
io.WriteString(w, "// ")
io.WriteString(w, strings.ReplaceAll(ty.desc, "\n", "\n// "))
io.WriteString(w, "\n")
}
io.WriteString(w, "using ")
io.WriteString(w, ty.from)
io.WriteString(w, " = ")
io.WriteString(w, ty.to.Name())
io.WriteString(w, ";\n\n")
}
func (ty *cppTypedef) WriteCPP(w io.Writer) {}
func (ty *cppTypedef) WriteFuzzerH(w io.Writer) {}
func (s *cppTypedef) GetFuzzerNames() []string {
return s.enumVals
}
// cppStruct implements the cppType interface, describing a basic C++ type
type cppBasicType struct {
name string // Type name
desc string // Description
deps []cppType // Types this type depends on
defaultValue string // Default value for fields of this type
}
func (ty *cppBasicType) Name() string { return ty.name }
func (ty *cppBasicType) Dependencies() []cppType { return ty.deps }
func (ty *cppBasicType) File() cppTargetFile { return types }
func (ty *cppBasicType) Description() string { return ty.desc }
func (ty *cppBasicType) DefaultValue() string { return ty.defaultValue }
func (ty *cppBasicType) WriteHeader(w io.Writer) {}
func (ty *cppBasicType) WriteCPP(w io.Writer) {}
func (ty *cppBasicType) WriteFuzzerH(w io.Writer) {}
func (ty *cppBasicType) GetFuzzerNames() []string {
return []string{}
}
func stringify(s string) string {
return "\"" + s + "\""
}
func stringifyArray(s []string) []string {
ret := []string{}
if s == nil {
return ret
}
for _, v := range s {
ret = append(ret, stringify(v))
}
return ret
}
func removeDuplicateStr(strSlice []string) []string {
allKeys := make(map[string]bool)
list := []string{}
for _, item := range strSlice {
if _, value := allKeys[item]; !value {
allKeys[item] = true
list = append(list, item)
}
}
return list
}
// sanitize() returns the given identifier transformed into a legal C++ identifier
func sanitize(s string) string {
s = strings.Trim(s, "_")
switch s {
case "default":
return "def"
default:
return s
}
}
// appendEnumDetails() appends any enumerator details to the given description string.
func appendEnumDetails(desc string, openEnum []string, closedEnum []string) string {
if len(closedEnum) > 0 {
desc += "\n\nMust be one of the following enumeration values:\n"
for i, enum := range closedEnum {
if i > 0 {
desc += ", "
}
desc += "'" + enum + "'"
}
}
if len(openEnum) > 0 {
desc += "\n\nMay be one of the following enumeration values:\n"
for i, enum := range openEnum {
if i > 0 {
desc += ", "
}
desc += "'" + enum + "'"
}
}
return desc
}
// buildRootStruct() populates the cppStruct type with information found in def.
// buildRootStruct() must only be called after all the root definitions have had
// a type constructed (however, not necessarily fully populated)
func (r *root) buildRootStruct(ty *cppStruct, def *definition) error {
if len(def.AllOf) > 1 && def.AllOf[0].Ref != "" {
ref, err := r.getRef(def.AllOf[0].Ref)
if err != nil {
return err
}
ty.base = ref.name
if len(def.AllOf) > 2 {
return fmt.Errorf("Cannot handle allOf with more than 2 entries")
}
def = def.AllOf[1]
}
if def.Ty != "object" {
return fmt.Errorf("Definion '%v' was of unexpected type '%v'", ty.name, def.Ty)
}
ty.desc = def.Description
var body *definition
var err error
switch ty.base {
case "Request":
if arguments, ok := def.Properties["arguments"]; ok {
body = arguments
}
if command, ok := def.Properties["command"]; ok {
ty.protoname = command.ClosedEnum[0]
}
responseName := strings.TrimSuffix(ty.name, "Request") + "Response"
responseDef := r.Definitions[responseName]
responseTy := responseDef.cppType
if responseTy == nil {
return fmt.Errorf("Failed to find response type '%v'", responseName)
}
ty.deps = append(ty.deps, responseTy)
ty.typedefs = append(ty.typedefs, cppTypedef{from: "Response", to: responseTy})
ty.file = request
case "Response":
body = def.Properties["body"]
ty.file = response
case "Event":
body = def.Properties["body"]
if command, ok := def.Properties["event"]; ok {
ty.protoname = command.ClosedEnum[0]
}
ty.file = event
default:
body = def
ty.file = types
}
if err != nil {
return err
}
if body == nil {
return nil
}
if body.Ref != "" {
ref, err := r.getRef(body.Ref)
if err != nil {
return err
}
body = ref.def
}
required := make(map[string]bool, len(body.Required))
for _, r := range body.Required {
required[r] = true
}
if err = body.Properties.foreach(func(propName string, property *definition) error {
propTy, err := r.getType(property)
if err != nil {
return fmt.Errorf("While processing %v.%v: %v", ty.name, propName, err)
}
optional := !required[propName]
desc := appendEnumDetails(property.Description, property.OpenEnum, property.ClosedEnum)
enumVals := []string{}
if len(property.ClosedEnum) > 0 {
enumVals = append(enumVals, property.ClosedEnum...)
}
if len(property.OpenEnum) > 0 {
enumVals = append(enumVals, property.OpenEnum...)
}
ty.fields = append(ty.fields, cppField{
desc: desc,
ty: propTy,
name: propName,
optional: optional,
enumVals: enumVals,
})
ty.deps = append(ty.deps, propTy)
return nil
}); err != nil {
return err
}
return nil
}
// getType() returns the cppType for the given definition
func (r *root) getType(def *definition) (builtType cppType, err error) {
if def.cppType != nil {
return def.cppType, nil
}
defer func() { def.cppType = builtType }()
if def.Ref != "" {
ref, err := r.getRef(def.Ref)
if err != nil {
return nil, err
}
return ref.def.cppType, nil
}
// The DAP spec introduces ambiguities with its particular uses of OneOf, just set to object
if len(def.OneOf) != 0 {
deps := make([]cppType, len(def.OneOf))
for i, oneOf := range def.OneOf {
if oneOf == nil {
return nil, fmt.Errorf("Item %d in oneOf is nil", i)
}
elTy, err := r.getType(oneOf)
if err != nil {
return nil, err
}
deps[i] = elTy
}
return &cppBasicType{
name: "object",
desc: def.Description,
deps: deps,
}, nil
}
v := reflect.ValueOf(def.Ty)
if v.Kind() == reflect.Interface {
v = v.Elem()
}
var typeof func(reflect.Value) (cppType, error)
typeof = func(v reflect.Value) (cppType, error) {
if v.Kind() == reflect.Interface {
v = v.Elem()
}
switch v.Kind() {
case reflect.String:
ty := v.Interface().(string)
switch ty {
case "string":
desc := appendEnumDetails(def.Description, nil, def.ClosedEnum)
defaultValue := ""
if len(def.ClosedEnum) > 0 {
defaultValue = `"` + def.ClosedEnum[0] + `"`
}
ty := &cppBasicType{
name: ty,
defaultValue: defaultValue,
desc: desc,
}
return ty, nil
case "object", "boolean", "integer", "number", "null":
ty := &cppBasicType{
name: ty,
desc: def.Description,
}
return ty, nil
case "array":
name := "array<any>"
deps := []cppType{}
if def.Items != nil {
elTy, err := r.getType(def.Items)
if err != nil {
return nil, err
}
name = fmt.Sprintf("array<%s>", elTy.Name())
deps = append(deps, elTy)
}
return &cppBasicType{
name: name,
desc: def.Description,
deps: deps,
}, nil
default:
return nil, fmt.Errorf("Unhandled property type '%v'", ty)
}
case reflect.Slice, reflect.Array:
args := []string{}
deps := []cppType{}
for i := 0; i < v.Len(); i++ {
elTy, err := typeof(v.Index(i))
if err != nil {
return nil, err
}
deps = append(deps, elTy)
args = append(args, elTy.Name())
}
return &cppBasicType{
name: "variant<" + strings.Join(args, ", ") + ">",
desc: def.Description,
deps: deps,
}, nil
}
return nil, fmt.Errorf("Unsupported type '%v' kind: %v", v.Interface(), v.Kind())
}
return typeof(v)
}
// buildTypes() builds all the reachable types found in the schema, returning
// all the root, named definition types.
func (r *root) buildTypes() ([]cppType, error) {
ignore := map[string]bool{
// These are handled internally.
"ProtocolMessage": true,
"Request": true,
"Event": true,
"Response": true,
}
// Step 1: Categorize all the named definitions by type.
structDefs := []namedDefinition{}
enumDefs := []namedDefinition{}
for _, entry := range r.definitions() {
if ignore[entry.name] {
continue
}
switch entry.def.Ty {
case nil, "object":
structDefs = append(structDefs, entry)
case "string":
enumDefs = append(enumDefs, entry)
default:
return nil, fmt.Errorf("Unhandled top-level definition type: %v", entry.def.Ty)
}
}
// Step 2: Construct, but do not build all the named object types (yet).
// This allows the getType() function to resolve to the cppStruct types,
// even if they're not built yet.
out := []cppType{}
for _, entry := range structDefs {
entry.def.cppType = &cppStruct{
name: entry.name,
}
out = append(out, entry.def.cppType)
}
// Step 3: Resolve all the enum types
for _, entry := range enumDefs {
enumTy, err := r.getType(entry.def)
if err != nil {
return nil, err
}
ty := &cppTypedef{
from: entry.name,
to: enumTy,
desc: enumTy.Description(),
enumVals: func() []string {
ret := []string{}
if len(entry.def.ClosedEnum) > 0 {
ret = entry.def.ClosedEnum
}
if len(entry.def.OpenEnum) > 0 {
ret = append(ret, entry.def.OpenEnum...)
}
return ret
}(),
}
entry.def.cppType = ty
out = append(out, entry.def.cppType)
}
// Step 4: Resolve all the structure types
for _, s := range structDefs {
if err := r.buildRootStruct(s.def.cppType.(*cppStruct), s.def); err != nil {
return nil, err
}
}
return out, nil
}
// cppTargetFile is an enumerator of target files that types should be written
// to.
type cppTargetFile string
const (
request = cppTargetFile("request") // protocol_request.cpp
response = cppTargetFile("response") // protocol_response.cpp
event = cppTargetFile("event") // protocol_events.cpp
types = cppTargetFile("types") // protocol_types.cpp
)
// cppTargetFilePaths is a map of cppTargetFile to the target file path
type cppTargetFilePaths map[cppTargetFile]string
// cppFiles is a map of cppTargetFile to the open file
type cppFiles map[cppTargetFile]*os.File
// run() loads and parses the package and protocol JSON files, generates the
// protocol types from the schema, writes the types to the C++ files, then runs
// clang-format on each.
func run() error {
pkg := struct {
Version string `json:"version"`
}{}
if err := loadJSONFile(packageURL, &pkg); err != nil {
return fmt.Errorf("Failed to load JSON file from '%v': %w", packageURL, err)
}
protocol := root{}
if err := loadJSONFile(protocolURL, &protocol); err != nil {
return fmt.Errorf("Failed to load JSON file from '%v': %w", protocolURL, err)
}
hPath, cppPaths, cMakeListsPath, fuzzerhPath, fuzzerDictPath := outputPaths()
if err := emitFiles(&protocol, hPath, cppPaths, fuzzerhPath, fuzzerDictPath, pkg.Version); err != nil {
return fmt.Errorf("Failed to emit files: %w", err)
}
if err := updateCMakePackageVersion(cMakeListsPath, pkg.Version); err != nil {
return fmt.Errorf("Failed to update CMakeLists.txt: %w", err)
}
if clangfmt, err := exec.LookPath("clang-format"); err == nil {
if out, err := exec.Command(clangfmt, "-i", hPath).CombinedOutput(); err != nil {
return fmt.Errorf("Failed to run clang-format on '%v':\n%v\n%w", hPath, string(out), err)
}
for _, p := range cppPaths {
if out, err := exec.Command(clangfmt, "-i", p).CombinedOutput(); err != nil {
return fmt.Errorf("Failed to run clang-format on '%v':\n%v\n%w", p, string(out), err)
}
}
if out, err := exec.Command(clangfmt, "-i", fuzzerhPath).CombinedOutput(); err != nil {
return fmt.Errorf("Failed to run clang-format on '%v':\n%v\n%w", fuzzerhPath, string(out), err)
}
} else {
fmt.Printf("clang-format not found on PATH. Please format before committing.")
}
return nil
}
// Updates package version in CMakeLists.txt to current
func updateCMakePackageVersion(cMakeListsPath string, version string) error {
text, err := os.ReadFile(cMakeListsPath)
if err != nil {
return err
}
lines := strings.Split(string(text), "\n")
for i, line := range lines {
if strings.Contains(line, "project(cppdap") {
lines[i] = "project(cppdap VERSION " + version + " LANGUAGES CXX C)"
break
}
}
output := strings.Join(lines, "\n")
return os.WriteFile(cMakeListsPath, []byte(output), 0644)
}
// emitFiles() opens each of the C++ files, generates the cppType definitions
// from the schema root, then writes the types to the C++ files in dependency
// order.
func emitFiles(r *root, hPath string, cppPaths map[cppTargetFile]string, fuzzerhPath string, fuzzerDictPath string, version string) error {
h, err := os.Create(hPath)
if err != nil {
return err
}
defer h.Close()
cppFiles := map[cppTargetFile]*os.File{}
for ty, p := range cppPaths {
f, err := os.Create(p)
if err != nil {
return err
}
cppFiles[ty] = f
defer f.Close()
}
fuzzer_h, err := os.Create(fuzzerhPath)
if err != nil {
return err
}
fuzzerDict, err := os.Create(fuzzerDictPath)
if err != nil {
return err
}
h.WriteString(strings.ReplaceAll(headerPrologue, versionTag, version))
for _, f := range cppFiles {
f.WriteString(strings.ReplaceAll(cppPrologue, versionTag, version))
}
fuzzer_h.WriteString(strings.ReplaceAll(fuzzerHeaderPrologue, versionTag, version))
types, err := r.buildTypes()
if err != nil {
return err
}
typesByName := map[string]cppType{}
for _, s := range types {
typesByName[s.Name()] = s
}
seen := map[string]bool{}
// Prepopulate the names list with the types that are not generated from the schema.
ProtocolMessageFuzzerNames := []string{"seq", "type", "request", "response", "event"}
RequestMessageFuzzerNames := []string{"request", "type", "command", "arguments"}
EventMessageFuzzerNames := []string{"event", "type", "event", "body"}
ResponseMessageFuzzerNames := []string{"response", "type", "request_seq", "success", "command", "message", "body",
"cancelled", "notStopped"}
fuzzerNames := []string{}
fuzzerNames = append(fuzzerNames, ProtocolMessageFuzzerNames...)
fuzzerNames = append(fuzzerNames, RequestMessageFuzzerNames...)
fuzzerNames = append(fuzzerNames, EventMessageFuzzerNames...)
fuzzerNames = append(fuzzerNames, ResponseMessageFuzzerNames...)
var emit func(cppType) error
emit = func(ty cppType) error {
name := ty.Name()
if seen[name] {
return nil
}
seen[name] = true
for _, dep := range ty.Dependencies() {
if err := emit(dep); err != nil {
return err
}
}
ty.WriteHeader(h)
ty.WriteCPP(cppFiles[ty.File()])
ty.WriteFuzzerH(fuzzer_h)
// collect protoname, field names, and field enum values for dictionary
fuzzerNames = append(fuzzerNames, ty.GetFuzzerNames()...)
return nil
}
// emit message types.
// Referenced types will be transitively emitted.
for _, s := range types {
switch s.File() {
case request, response, event:
if err := emit(s); err != nil {
return err
}
}
}
// sort names alphabetically
sort.Strings(fuzzerNames)
// remove duplicates
fuzzerNames = removeDuplicateStr(fuzzerNames)
// append "" to each name
fuzzerNames = stringifyArray(fuzzerNames)
dict := strings.Join(fuzzerNames, "\n")
if _, err := io.WriteString(fuzzerDict, dict); err != nil {
return err
}
h.WriteString(headerEpilogue)
for _, f := range cppFiles {
f.WriteString(cppEpilogue)
}
fuzzer_h.WriteString(fuzzerHeaderEpilogue)
return nil
}
// loadJSONFile() loads the JSON file from the given URL using a HTTP GET
// request.
func loadJSONFile(url string, obj interface{}) error {
resp, err := http.Get(url)
if err != nil {
return err
}
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
if err := json.NewDecoder(bytes.NewReader(data)).Decode(obj); err != nil {
return err
}
return nil
}
// outputPaths() returns a path to the target C++ .h file and .cpp files, and the CMakeLists.txt
func outputPaths() (string, cppTargetFilePaths, string, string, string) {
_, thisFile, _, _ := runtime.Caller(1)
thisDir := path.Dir(thisFile)
h := path.Join(thisDir, "../../include/dap/protocol.h")
cpp := cppTargetFilePaths{
request: path.Join(thisDir, "../../src/protocol_requests.cpp"),
response: path.Join(thisDir, "../../src/protocol_response.cpp"),
event: path.Join(thisDir, "../../src/protocol_events.cpp"),
types: path.Join(thisDir, "../../src/protocol_types.cpp"),
}
CMakeLists := path.Join(thisDir, "../../CMakeLists.txt")
fuzzer_h := path.Join(thisDir, "../../fuzz/fuzz.h")
fuzzer_dict := path.Join(thisDir, "../../fuzz/dictionary.txt")
return h, cpp, CMakeLists, fuzzer_h, fuzzer_dict
}