| OLD | NEW |
| (Empty) |
| 1 #!/usr/bin/python | |
| 2 # Copyright 2014 The Chromium Authors. All rights reserved. | |
| 3 # Use of this source code is governed by a BSD-style license that can be | |
| 4 # found in the LICENSE file. | |
| 5 | |
| 6 # pylint: disable=W0104,W0106,F0401,R0201 | |
| 7 | |
| 8 import errno | |
| 9 import optparse | |
| 10 import os.path | |
| 11 import sys | |
| 12 | |
| 13 import interface | |
| 14 | |
| 15 | |
| 16 def _ScriptDir(): | |
| 17 return os.path.dirname(os.path.abspath(__file__)) | |
| 18 | |
| 19 | |
| 20 def _GetDirAbove(dirname): | |
| 21 """Returns the directory "above" this file containing |dirname| (which must | |
| 22 also be "above" this file).""" | |
| 23 path = _ScriptDir() | |
| 24 while True: | |
| 25 path, tail = os.path.split(path) | |
| 26 assert tail | |
| 27 if tail == dirname: | |
| 28 return path | |
| 29 | |
| 30 | |
| 31 def _AddThirdPartyImportPath(): | |
| 32 sys.path.insert(0, os.path.join(_GetDirAbove('mojo'), 'third_party')) | |
| 33 | |
| 34 | |
| 35 _AddThirdPartyImportPath() | |
| 36 import jinja2 | |
| 37 | |
| 38 loader = jinja2.FileSystemLoader(_ScriptDir()) | |
| 39 jinja_env = jinja2.Environment(loader=loader, keep_trailing_newline=True) | |
| 40 | |
| 41 | |
| 42 # Accumulate lines of code with varying levels of indentation. | |
| 43 class CodeWriter(object): | |
| 44 def __init__(self): | |
| 45 self._lines = [] | |
| 46 self._margin = '' | |
| 47 self._margin_stack = [] | |
| 48 | |
| 49 def __lshift__(self, line): | |
| 50 self._lines.append((self._margin + line).rstrip()) | |
| 51 | |
| 52 def PushMargin(self): | |
| 53 self._margin_stack.append(self._margin) | |
| 54 self._margin += ' ' | |
| 55 | |
| 56 def PopMargin(self): | |
| 57 self._margin = self._margin_stack.pop() | |
| 58 | |
| 59 def GetValue(self): | |
| 60 return '\n'.join(self._lines).rstrip() + '\n' | |
| 61 | |
| 62 def Indent(self): | |
| 63 return Indent(self) | |
| 64 | |
| 65 | |
| 66 # Context handler that automatically indents and dedents a CodeWriter | |
| 67 class Indent(object): | |
| 68 def __init__(self, writer): | |
| 69 self._writer = writer | |
| 70 | |
| 71 def __enter__(self): | |
| 72 self._writer.PushMargin() | |
| 73 | |
| 74 def __exit__(self, type_, value, traceback): | |
| 75 self._writer.PopMargin() | |
| 76 | |
| 77 | |
| 78 def TemplateFile(name): | |
| 79 return os.path.join(os.path.dirname(__file__), name) | |
| 80 | |
| 81 | |
| 82 # Wraps comma separated lists as needed. | |
| 83 # TODO(teravest): Eliminate Wrap() and use "git cl format" when code is checked | |
| 84 # in. | |
| 85 def Wrap(pre, items, post): | |
| 86 complete = pre + ', '.join(items) + post | |
| 87 if len(complete) <= 80: | |
| 88 return [complete] | |
| 89 lines = [pre] | |
| 90 indent = ' ' | |
| 91 for i, item in enumerate(items): | |
| 92 if i < len(items) - 1: | |
| 93 lines.append(indent + item + ',') | |
| 94 else: | |
| 95 lines.append(indent + item + post) | |
| 96 return lines | |
| 97 | |
| 98 | |
| 99 def GeneratorWarning(): | |
| 100 return ('// WARNING this file was generated by %s\n// Do not edit by hand.' % | |
| 101 os.path.basename(__file__)) | |
| 102 | |
| 103 | |
| 104 # Untrusted library which thunks from the public Mojo API to the IRT interface | |
| 105 # implementing the public Mojo API. | |
| 106 def GenerateLibMojo(functions, out): | |
| 107 template = jinja_env.get_template('libmojo.cc.tmpl') | |
| 108 | |
| 109 code = CodeWriter() | |
| 110 | |
| 111 for f in functions: | |
| 112 for line in Wrap('%s %s(' % (f.return_type, f.name), f.ParamList(), ') {'): | |
| 113 code << line | |
| 114 | |
| 115 with code.Indent(): | |
| 116 code << 'struct nacl_irt_mojo* irt_mojo = get_irt_mojo();' | |
| 117 code << 'if (irt_mojo == NULL)' | |
| 118 with code.Indent(): | |
| 119 code << 'return MOJO_RESULT_INTERNAL;' | |
| 120 code << 'return irt_mojo->%s(%s);' % ( | |
| 121 f.name, ', '.join([p.name for p in f.params])) | |
| 122 | |
| 123 code << '}' | |
| 124 code << '' | |
| 125 | |
| 126 body = code.GetValue() | |
| 127 text = template.render( | |
| 128 generator_warning=GeneratorWarning(), | |
| 129 body=body) | |
| 130 out.write(text) | |
| 131 | |
| 132 | |
| 133 # Parameters passed into trusted code are handled differently depending on | |
| 134 # details of the parameter. ParamImpl instances encapsulate these differences | |
| 135 # and are used to generate the code that transfers parameters across the | |
| 136 # untrusted/trusted boundary. | |
| 137 class ParamImpl(object): | |
| 138 def __init__(self, param): | |
| 139 self.param = param | |
| 140 | |
| 141 # Declare whatever variables are needed to handle this particular parameter. | |
| 142 def DeclareVars(self, code): | |
| 143 raise NotImplementedError(type(self)) | |
| 144 | |
| 145 # Convert the untrusted representation of the parameter into a trusted | |
| 146 # representation, such as a scalar value or a trusted pointer into the | |
| 147 # untrusted address space. | |
| 148 def ConvertParam(self): | |
| 149 raise NotImplementedError(type(self)) | |
| 150 | |
| 151 # For this particular parameter, what expression should be passed when | |
| 152 # invoking the trusted Mojo API function? | |
| 153 def CallParam(self): | |
| 154 raise NotImplementedError(type(self)) | |
| 155 | |
| 156 # After invoking the trusted Mojo API function, transfer data back into | |
| 157 # untrusted memory. Overriden for Out and InOut parameters. | |
| 158 def CopyOut(self, code): | |
| 159 pass | |
| 160 | |
| 161 # Converting array parameters needs to be defered until after the scalar | |
| 162 # parameter containing the size of the array has itself been converted. | |
| 163 def IsArray(self): | |
| 164 return False | |
| 165 | |
| 166 | |
| 167 class ScalarInputImpl(ParamImpl): | |
| 168 def DeclareVars(self, code): | |
| 169 code << '%s %s_value;' % (self.param.base_type, self.param.name) | |
| 170 | |
| 171 def ConvertParam(self): | |
| 172 p = self.param | |
| 173 return ('ConvertScalarInput(nap, params[%d], &%s_value)' % | |
| 174 (p.uid + 1, p.name)) | |
| 175 | |
| 176 def CallParam(self): | |
| 177 return '%s_value' % self.param.name | |
| 178 | |
| 179 | |
| 180 class ScalarOutputImpl(ParamImpl): | |
| 181 def DeclareVars(self, code): | |
| 182 code << '%s volatile* %s_ptr;' % (self.param.base_type, self.param.name) | |
| 183 code << '%s %s_value;' % (self.param.base_type, self.param.name) | |
| 184 | |
| 185 def ConvertParam(self): | |
| 186 p = self.param | |
| 187 return ('ConvertScalarOutput(nap, params[%d], %s, &%s_ptr)' % | |
| 188 (p.uid + 1, CBool(p.is_optional), p.name)) | |
| 189 | |
| 190 def CallParam(self): | |
| 191 name = self.param.name | |
| 192 expr = '&%s_value' % name | |
| 193 if self.param.is_optional: | |
| 194 expr = '%s_ptr ? %s : NULL' % (name, expr) | |
| 195 return expr | |
| 196 | |
| 197 def CopyOut(self, code): | |
| 198 name = self.param.name | |
| 199 if self.param.is_struct: | |
| 200 # C++ errors when you try to copy a volatile struct pointer. | |
| 201 # (There are no default copy constructors for this case.) | |
| 202 # memcpy instead. | |
| 203 copy_stmt = ('memcpy_volatile_out(%s_ptr, &%s_value, sizeof(%s));' % | |
| 204 (name, name, self.param.base_type)) | |
| 205 else: | |
| 206 copy_stmt = '*%s_ptr = %s_value;' % (name, name) | |
| 207 | |
| 208 if self.param.is_optional: | |
| 209 code << 'if (%s_ptr != NULL) {' % (name) | |
| 210 with code.Indent(): | |
| 211 code << copy_stmt | |
| 212 code << '}' | |
| 213 else: | |
| 214 code << copy_stmt | |
| 215 | |
| 216 | |
| 217 class ScalarInOutImpl(ParamImpl): | |
| 218 def DeclareVars(self, code): | |
| 219 code << '%s volatile* %s_ptr;' % (self.param.base_type, self.param.name) | |
| 220 code << '%s %s_value;' % (self.param.base_type, self.param.name) | |
| 221 | |
| 222 def ConvertParam(self): | |
| 223 p = self.param | |
| 224 return ('ConvertScalarInOut(nap, params[%d], %s, &%s_value, &%s_ptr)' % | |
| 225 (p.uid + 1, CBool(p.is_optional), p.name, p.name)) | |
| 226 | |
| 227 def CallParam(self): | |
| 228 name = self.param.name | |
| 229 expr = '&%s_value' % name | |
| 230 if self.param.is_optional: | |
| 231 expr = '%s_ptr ? %s : NULL' % (name, expr) | |
| 232 return expr | |
| 233 | |
| 234 def CopyOut(self, code): | |
| 235 name = self.param.name | |
| 236 if self.param.is_optional: | |
| 237 code << 'if (%s_ptr != NULL) {' % (name) | |
| 238 with code.Indent(): | |
| 239 code << '*%s_ptr = %s_value;' % (name, name) | |
| 240 code << '}' | |
| 241 else: | |
| 242 code << '*%s_ptr = %s_value;' % (name, name) | |
| 243 | |
| 244 | |
| 245 class ArrayImpl(ParamImpl): | |
| 246 def DeclareVars(self, code): | |
| 247 code << '%s %s;' % (self.param.param_type, self.param.name) | |
| 248 | |
| 249 def ConvertParam(self): | |
| 250 p = self.param | |
| 251 if p.base_type == 'void': | |
| 252 element_size = '1' | |
| 253 else: | |
| 254 element_size = 'sizeof(*%s)' % p.name | |
| 255 | |
| 256 return ('ConvertArray(nap, params[%d], %s, %s, %s, &%s)' % | |
| 257 (p.uid + 1, p.size + '_value', element_size, CBool(p.is_optional), | |
| 258 p.name)) | |
| 259 | |
| 260 def CallParam(self): | |
| 261 return self.param.name | |
| 262 | |
| 263 def IsArray(self): | |
| 264 return True | |
| 265 | |
| 266 | |
| 267 class ExtensibleStructInputImpl(ParamImpl): | |
| 268 def DeclareVars(self, code): | |
| 269 code << '%s %s;' % (self.param.param_type, self.param.name) | |
| 270 | |
| 271 def ConvertParam(self): | |
| 272 p = self.param | |
| 273 return ('ConvertExtensibleStructInput(nap, params[%d], %s, &%s)' % | |
| 274 (p.uid + 1, CBool(p.is_optional), p.name)) | |
| 275 | |
| 276 def CallParam(self): | |
| 277 return self.param.name | |
| 278 | |
| 279 def ImplForParam(p): | |
| 280 if p.IsScalar(): | |
| 281 if p.is_output: | |
| 282 if p.is_input: | |
| 283 return ScalarInOutImpl(p) | |
| 284 else: | |
| 285 if p.is_always_written: | |
| 286 return ScalarOutputImpl(p) | |
| 287 else: | |
| 288 # Mojo defines that some of its outputs will not be set in specific | |
| 289 # cases. To avoid the complexity of determining if the output was set | |
| 290 # by Mojo, copy the output's current value (possibly junk) and copy it | |
| 291 # back to untrusted memory afterwards. | |
| 292 return ScalarInOutImpl(p) | |
| 293 else: | |
| 294 return ScalarInputImpl(p) | |
| 295 elif p.is_array: | |
| 296 return ArrayImpl(p) | |
| 297 elif p.is_struct: | |
| 298 if p.is_input and not p.is_output and p.is_extensible: | |
| 299 return ExtensibleStructInputImpl(p) | |
| 300 if not p.is_input and p.is_output and not p.is_extensible: | |
| 301 return ScalarOutputImpl(p) | |
| 302 assert False, p.name | |
| 303 | |
| 304 | |
| 305 def CBool(value): | |
| 306 return 'true' if value else 'false' | |
| 307 | |
| 308 | |
| 309 # A trusted wrapper that validates the arguments passed from untrusted code | |
| 310 # before passing them to the underlying public Mojo API. | |
| 311 def GenerateMojoSyscall(functions, out): | |
| 312 template = jinja_env.get_template('mojo_syscall.cc.tmpl') | |
| 313 | |
| 314 code = CodeWriter() | |
| 315 code.PushMargin() | |
| 316 | |
| 317 for f in functions: | |
| 318 impls = [ImplForParam(p) for p in f.params] | |
| 319 impls.append(ImplForParam(f.result_param)) | |
| 320 | |
| 321 code << 'case %d:' % f.uid | |
| 322 | |
| 323 code.PushMargin() | |
| 324 | |
| 325 code << '{' | |
| 326 | |
| 327 with code.Indent(): | |
| 328 num_params = len(f.params) + 2 | |
| 329 code << 'if (num_params != %d) {' % num_params | |
| 330 with code.Indent(): | |
| 331 code << 'return -1;' | |
| 332 code << '}' | |
| 333 | |
| 334 # Declare temporaries. | |
| 335 for impl in impls: | |
| 336 impl.DeclareVars(code) | |
| 337 | |
| 338 def ConvertParam(code, impl): | |
| 339 code << 'if (!%s) {' % impl.ConvertParam() | |
| 340 with code.Indent(): | |
| 341 code << 'return -1;' | |
| 342 code << '}' | |
| 343 | |
| 344 code << '{' | |
| 345 with code.Indent(): | |
| 346 code << 'ScopedCopyLock copy_lock(nap);' | |
| 347 # Convert and validate pointers in two passes. | |
| 348 # Arrays cannot be validated until the size parameter has been | |
| 349 # converted. | |
| 350 for impl in impls: | |
| 351 if not impl.IsArray(): | |
| 352 ConvertParam(code, impl) | |
| 353 for impl in impls: | |
| 354 if impl.IsArray(): | |
| 355 ConvertParam(code, impl) | |
| 356 code << '}' | |
| 357 code << '' | |
| 358 | |
| 359 # Call | |
| 360 getParams = [impl.CallParam() for impl in impls[:-1]] | |
| 361 code << 'result_value = %s(%s);' % (f.name, ', '.join(getParams)) | |
| 362 code << '' | |
| 363 | |
| 364 # Write outputs | |
| 365 code << '{' | |
| 366 with code.Indent(): | |
| 367 code << 'ScopedCopyLock copy_lock(nap);' | |
| 368 for impl in impls: | |
| 369 impl.CopyOut(code) | |
| 370 code << '}' | |
| 371 code << '' | |
| 372 | |
| 373 code << 'return 0;' | |
| 374 code << '}' | |
| 375 | |
| 376 code.PopMargin() | |
| 377 | |
| 378 body = code.GetValue() | |
| 379 text = template.render( | |
| 380 generator_warning=GeneratorWarning(), | |
| 381 body=body) | |
| 382 out.write(text) | |
| 383 | |
| 384 | |
| 385 # A header declaring the IRT interface for accessing Mojo functions. | |
| 386 def GenerateMojoIrtHeader(functions, out): | |
| 387 template = jinja_env.get_template('mojo_irt.h.tmpl') | |
| 388 code = CodeWriter() | |
| 389 | |
| 390 code << 'struct nacl_irt_mojo {' | |
| 391 with code.Indent(): | |
| 392 for f in functions: | |
| 393 for line in Wrap('%s (*%s)(' % (f.return_type, f.name), | |
| 394 f.ParamList(), | |
| 395 ');'): | |
| 396 code << line | |
| 397 | |
| 398 code << '};' | |
| 399 | |
| 400 body = code.GetValue() | |
| 401 | |
| 402 text = template.render( | |
| 403 generator_warning=GeneratorWarning(), | |
| 404 body=body) | |
| 405 out.write(text) | |
| 406 | |
| 407 # IRT interface which implements the Mojo public API. | |
| 408 def GenerateMojoIrtImplementation(functions, out): | |
| 409 template = jinja_env.get_template('mojo_irt.c.tmpl') | |
| 410 code = CodeWriter() | |
| 411 | |
| 412 for f in functions: | |
| 413 for line in Wrap('static %s irt_%s(' % (f.return_type, f.name), | |
| 414 f.ParamList(), | |
| 415 ') {'): | |
| 416 code << line | |
| 417 | |
| 418 # 2 extra parameters: message ID and return value. | |
| 419 num_params = len(f.params) + 2 | |
| 420 | |
| 421 with code.Indent(): | |
| 422 code << 'uint32_t params[%d];' % num_params | |
| 423 return_type = f.result_param.base_type | |
| 424 if return_type == 'MojoResult': | |
| 425 default = 'MOJO_RESULT_INVALID_ARGUMENT' | |
| 426 elif return_type == 'MojoTimeTicks': | |
| 427 default = '0' | |
| 428 else: | |
| 429 raise Exception('Unhandled return type: ' + return_type) | |
| 430 code << '%s %s = %s;' % (return_type, f.result_param.name, default) | |
| 431 | |
| 432 # Message ID | |
| 433 code << 'params[0] = %d;' % f.uid | |
| 434 # Parameter pointers | |
| 435 cast_template = 'params[%d] = (uint32_t)(%s);' | |
| 436 for p in f.params: | |
| 437 ptr = p.name | |
| 438 if p.IsPassedByValue(): | |
| 439 ptr = '&' + ptr | |
| 440 code << cast_template % (p.uid + 1, ptr) | |
| 441 # Return value pointer | |
| 442 code << cast_template % (num_params - 1, '&' + f.result_param.name) | |
| 443 | |
| 444 code << 'DoMojoCall(params, sizeof(params));' | |
| 445 code << 'return %s;' % f.result_param.name | |
| 446 | |
| 447 # Add body here. | |
| 448 code << "};" | |
| 449 code << "\n" | |
| 450 | |
| 451 # Now we've emitted all the functions, but we still need the struct | |
| 452 # definition. | |
| 453 code << 'struct nacl_irt_mojo kIrtMojo = {' | |
| 454 for f in functions: | |
| 455 with code.Indent(): | |
| 456 code << '&irt_%s,' % f.name | |
| 457 code << '};' | |
| 458 | |
| 459 body = code.GetValue() | |
| 460 | |
| 461 text = template.render( | |
| 462 generator_warning=GeneratorWarning(), | |
| 463 body=body) | |
| 464 out.write(text) | |
| 465 | |
| 466 | |
| 467 def OutFile(dir_path, name): | |
| 468 if not os.path.exists(dir_path): | |
| 469 try: | |
| 470 os.makedirs(dir_path) | |
| 471 except OSError as e: | |
| 472 # There may have been a race to create this directory. | |
| 473 if e.errno != errno.EEXIST: | |
| 474 raise | |
| 475 return open(os.path.join(dir_path, name), 'w') | |
| 476 | |
| 477 | |
| 478 def main(args): | |
| 479 usage = 'usage: %prog [options]' | |
| 480 parser = optparse.OptionParser(usage=usage) | |
| 481 parser.add_option( | |
| 482 '-d', | |
| 483 dest='out_dir', | |
| 484 metavar='DIR', | |
| 485 help='output generated code into directory DIR') | |
| 486 options, args = parser.parse_args(args=args) | |
| 487 if not options.out_dir: | |
| 488 parser.error('-d is required') | |
| 489 if args: | |
| 490 parser.error('unexpected positional arguments: %s' % ' '.join(args)) | |
| 491 | |
| 492 mojo = interface.MakeInterface() | |
| 493 | |
| 494 out = OutFile(options.out_dir, 'libmojo.cc') | |
| 495 GenerateLibMojo(mojo.functions, out) | |
| 496 | |
| 497 out = OutFile(options.out_dir, 'mojo_syscall.cc') | |
| 498 GenerateMojoSyscall(mojo.functions, out) | |
| 499 | |
| 500 out = OutFile(options.out_dir, 'mojo_irt.h') | |
| 501 GenerateMojoIrtHeader(mojo.functions, out) | |
| 502 | |
| 503 out = OutFile(options.out_dir, 'mojo_irt.c') | |
| 504 GenerateMojoIrtImplementation(mojo.functions, out) | |
| 505 | |
| 506 if __name__ == '__main__': | |
| 507 main(sys.argv[1:]) | |
| OLD | NEW |