| // 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 |
| } |