Skip to content

Commit

Permalink
[JsonGen] Support restrict on JSON-RPC and documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
sebaszm committed Nov 15, 2023
1 parent d417610 commit 5ae36b4
Show file tree
Hide file tree
Showing 4 changed files with 158 additions and 6 deletions.
69 changes: 69 additions & 0 deletions JsonGenerator/source/class_emitter.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,41 @@
import trackers
from json_loader import *

def IsObjectRestricted(argument):
for prop in argument.properties:
if isinstance(prop, JsonObject):
if IsObjectRestricted(prop):
return True
else:
if "range" in prop.schema:
return True
return False

def AppendTest(tests, argument, relay=None, test_zero=False, reverse=False, override=None):
comp = ['<', '>', "false" ] if not reverse else ['>=', '<=', "true"]

if not relay:
relay = argument

if "range" in argument.schema or IsObjectRestricted(argument):
name = relay.TempName() if not override else override
range = argument.schema.get("range")

if isinstance(argument, JsonString):
if argument.schema["range"][0] != 0:
tests.append("(%s.size() %s %s)" % (name, comp[0], range[0]))

tests.append("(%s.size() %s %s)" % (name, comp[1], range[1]))

elif isinstance(argument, JsonObject):
tests.append("(%s.IsRestrictValid() == %s)" % (name, comp[2]))

else:
if argument.schema["range"][0] or argument.schema.get("signed"):
tests.append("(%s %s %s)" % (name, comp[0], range[0]))

tests.append("(%s %s %s)" % (name, comp[1], range[1]))

def ProcessEnums(action=None):
count = 0

Expand Down Expand Up @@ -113,11 +148,16 @@ def EmitCtor(json_obj, no_init_code=False, copy_ctor=False, conversion_ctor=Fals

emit.Indent()
emit.Line(": %s()" % CoreJson("Container"))

for prop in json_obj.properties:
if copy_ctor:
emit.Line(", %s(_other.%s)" % (prop.cpp_name, prop.cpp_name))
elif prop.cpp_def_value != '""' and prop.cpp_def_value != "":
emit.Line(", %s(%s)" % (prop.cpp_name, prop.cpp_def_value))

if IsObjectRestricted(json_obj):
emit.Line(", _valid(true)")

emit.Unindent()
emit.Line("{")
emit.Indent()
Expand Down Expand Up @@ -162,10 +202,28 @@ def _EmitConversionOperator(json_obj):
for prop in json_obj.properties:
emit.Line("_value.%s = %s;" % ( prop.actual_name, prop.cpp_name))


if IsObjectRestricted(json_obj):
tests = []

for prop in json_obj.properties:
AppendTest(tests, prop, reverse=True, override=("_value." + prop.actual_name))

if tests:
emit.Line("_valid = (%s);" % " && ".join(tests))

emit.Line("return (_value);")
emit.Unindent()
emit.Line("}")

def _EmitValidator(json_obj):
emit.Line("bool IsRestrictValid() const")
emit.Line("{")
emit.Indent()
emit.Line("return (_valid);")
emit.Unindent()
emit.Line("}")

# Bail out if a duplicated class!
if isinstance(json_obj, JsonObject) and not json_obj.properties:
return
Expand Down Expand Up @@ -211,6 +269,10 @@ def _EmitConversionOperator(json_obj):
emit.Line()
_EmitConversionOperator(json_obj)

if IsObjectRestricted(json_obj):
emit.Line()
_EmitValidator(json_obj)

if json_obj.is_copy_ctor_needed or ("original_type" in json_obj.schema):
emit.Unindent()
emit.Line()
Expand All @@ -237,6 +299,13 @@ def _EmitConversionOperator(json_obj):
comment = prop.print_name if isinstance(prop, JsonMethod) else prop.description
emit.Line("%s %s;%s" % (prop.short_cpp_type, prop.cpp_name, (" // " + comment) if comment else ""))

if IsObjectRestricted(json_obj):
emit.Unindent()
emit.Line()
emit.Line("private:")
emit.Indent()
emit.Line("bool _valid;")

emit.Unindent()
emit.Line("}; // class %s" % json_obj.cpp_class)
emit.Line()
Expand Down
38 changes: 35 additions & 3 deletions JsonGenerator/source/documentation_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ def _TableObj(name, obj, parentName="", parent=None, prefix="", parentOptional=F
optional = parentOptional or (obj["optional"] if "optional" in obj else False)
deprecated = obj["deprecated"] if "deprecated" in obj else False
obsolete = obj["obsolete"] if "obsolete" in obj else False
restricted = obj.get("range")

if parent and not optional:
if parent["type"] == "object":
Expand All @@ -106,11 +107,26 @@ def _TableObj(name, obj, parentName="", parent=None, prefix="", parentOptional=F

prefix += name
description = obj["description"] if "description" in obj else obj["summary"] if "summary" in obj else ""
if description and description[0].islower():
description = description[0].upper() + description[1:]

if name or prefix:
if "type" not in obj:
raise DocumentationError("'%s': missing 'type' for this object" % (parentName + "/" + name))

d = obj
is_buffer = False

if obj["type"] == "string":
if "range" in obj:
d = obj
elif "length" in obj:
is_buffer = True
if "range" in parent["properties"][obj.get("length")]:
d = parent["properties"][obj.get("length")]

restricted = "range" in d

row = (("<sup>" + italics("(optional)") + "</sup>" + " ") if optional else "")

if deprecated:
Expand All @@ -132,7 +148,20 @@ def _TableObj(name, obj, parentName="", parent=None, prefix="", parentOptional=F
if ("float" not in obj or not obj["float"]) and ("example" not in obj or '.' not in str(obj["example"])):
obj["type"] = "integer"

MdRow([prefix, "opaque object" if obj.get("opaque") else obj["type"], row])
if restricted:
if row:
row += "<br>"

if d["type"] == "string" or is_buffer:
str_text = "Decoded data" if d.get("encode") else "String"
if d["range"][0]:
row += italics("%s length must be in range [%s..%s] bytes." % (str_text, d["range"][0], d["range"][1]))
else:
row += italics("%s length must be at most %s bytes." % (str_text, d["range"][1]))
else:
row += italics("Value must be in range [%s..%s]." % (d["range"][0], d["range"][1]))

MdRow([prefix, "opaque object" if obj.get("opaque") else "string (base64)" if obj.get("encode") else obj["type"], row])

if obj["type"] == "object":
if "required" not in obj and name and len(obj["properties"]) > 1:
Expand Down Expand Up @@ -286,9 +315,12 @@ def MethodDump(method, props, classname, section, header, is_notification=False,
props["params"]["description"] = props["summary"]

ParamTable("(property)", props["params"])
elif "result" in props:

if "result" in props:
if not "description" in props["result"]:
if "summary" in props:
if "description" in props["params"]:
props["result"]["description"] = props["params"]["description"]
elif "summary" in props:
props["result"]["description"] = props["summary"]

if "index" in props:
Expand Down
8 changes: 7 additions & 1 deletion JsonGenerator/source/header_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,9 @@ def ConvertType(var):

props["encode"] = cppType.type != "char"

if var.meta.range:
props["range"] = var.meta.range

return "string", props if props else None
# Special case for iterators, that will be converted to JSON arrays
elif is_iterator and len(cppType.args) == 2:
Expand Down Expand Up @@ -254,6 +257,9 @@ def GenerateObject(ctype, was_typdef):
if var_type.IsPointerToConst():
result[1]["ptrtoconst"] = True

if var.meta.range:
result[1]["range"] = var.meta.range

return result

def ExtractExample(var):
Expand Down Expand Up @@ -342,7 +348,7 @@ def BuildParameters(vars, rpc_format, is_property=False, test=False):
required.append(var_name)

if properties[var_name]["type"] == "string" and not var.type.IsReference() and not var.type.IsPointer() and not "enum" in properties[var_name]:
log.WarnLine(var, "'%s': passing string by value (forgot &?)" % var.name)
log.WarnLine(var, "'%s': passing input string by value (forgot &?)" % var.name)

params["properties"] = properties
params["required"] = required
Expand Down
49 changes: 47 additions & 2 deletions JsonGenerator/source/rpc_emitter.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import emitter
import rpc_version
from json_loader import *
from class_emitter import AppendTest


def EmitEvent(emit, root, event, params_type, legacy = False):
Expand Down Expand Up @@ -434,10 +435,13 @@ def _Invoke(params, response, use_prefix = True, const_cast = False, parent = ""
if isinstance(arg, JsonString) and "length" in arg.flags:
length = arg.flags.get("length")

tests = []

for name, [var, var_type] in vars.items():
if name == length.local_name:
initializer = (parent + var.cpp_name) if "r" in var_type else ""
emit.Line("%s %s{%s};" % (var.cpp_native_type, var.TempName(), initializer))
AppendTest(tests, length, reverse=True)
break

encode = arg.schema.get("encode")
Expand All @@ -448,7 +452,17 @@ def _Invoke(params, response, use_prefix = True, const_cast = False, parent = ""
else:
emit.Line("%s* %s = nullptr;" % (arg.original_type, arg.TempName()))
emit.Line()
emit.Line("if (%s != 0) {" % length.TempName())

if not tests:
AppendTest(tests, arg, length, reverse=True)

if len(tests) < 2:
tests.insert(0, ("(%s != 0)" % length.TempName()))

if len(tests) < 2:
tests.append("(%s <= 0x400000)" % length.TempName()) # some sanity!

emit.Line("if (%s) {" % " && ".join(tests))
emit.Indent()
emit.Line("%s = reinterpret_cast<%s*>(ALLOCA(%s));" % (arg.TempName(), arg.original_type, length.TempName()))
emit.Line("ASSERT(%s != nullptr);" % arg.TempName())
Expand All @@ -462,6 +476,13 @@ def _Invoke(params, response, use_prefix = True, const_cast = False, parent = ""
if is_writeable or encode:
emit.Unindent()
emit.Line("}")
emit.Line("else {")
emit.Indent()
emit.Line("%s = Core::ERROR_INVALID_RANGE;" % error_code.TempName())
emit.Unindent()
emit.Line("}")

emit.Line()

# Special case for iterators
elif isinstance(arg, JsonArray):
Expand Down Expand Up @@ -510,6 +531,22 @@ def _Invoke(params, response, use_prefix = True, const_cast = False, parent = ""

emit.Line("%s%s %s%s;" % (cv_qualifier, (arg.cpp_type + "&") if json_source else arg.cpp_native_type, arg.TempName(), initializer))

tests = []
for _, [arg, arg_type] in sorted(vars.items(), key=lambda x: x[1][0].schema["position"]):
if arg.flags.get("isbufferlength"):
continue

AppendTest(tests, arg)

if tests:
emit.Line()
emit.Line("if (%s) {" % (" || ".join(tests)))

emit.Indent()
emit.Line("%s = Core::ERROR_INVALID_RANGE;" % error_code.TempName())
emit.Unindent()
emit.Line("}")

emit.Line()

# Emit call to the implementation
Expand Down Expand Up @@ -541,8 +578,16 @@ def _Invoke(params, response, use_prefix = True, const_cast = False, parent = ""
if not conditions:
emit.Line()

if tests:
emit.Line("if (%s == Core::ERROR_NONE) {" % error_code.TempName())
emit.Indent()

emit.Line("%s = %s->%s(%s);" % (error_code.TempName(), implementation_object, m.cpp_name, ", ".join(function_params)))

if tests:
emit.Unindent()
emit.Line("}")

if conditions:
for _, _record in vars.items():
arg = _record[0]
Expand Down Expand Up @@ -662,7 +707,7 @@ def _Invoke(params, response, use_prefix = True, const_cast = False, parent = ""

emit.Indent()
error_code = AuxJsonInteger("errorCode_", 32)
emit.Line("%s %s%s;" % (error_code.cpp_native_type, error_code.TempName(), " = Core::ERROR_NONE" if json_source else ""))
emit.Line("%s %s%s;" % (error_code.cpp_native_type, error_code.TempName(), " = Core::ERROR_NONE"))
emit.Line()

if isinstance(m, JsonProperty):
Expand Down

0 comments on commit 5ae36b4

Please sign in to comment.