1 : // Copyright (c) 2012 The Native Client Authors. All rights reserved.
2 : // Use of this source code is governed by a BSD-style license that can be
3 : // found in the LICENSE file.
4 :
5 : #include <limits>
6 : #include <new>
7 : #include "native_client/src/include/portability.h"
8 : #include "native_client/src/include/portability_string.h"
9 : #include "native_client/src/public/imc_types.h"
10 : #include "native_client/src/shared/imc/nacl_imc_c.h"
11 : #include "native_client/src/shared/platform/nacl_check.h"
12 : #include "native_client/src/shared/platform/nacl_log.h"
13 : #include "native_client/src/shared/platform/nacl_sync_checked.h"
14 : #include "native_client/src/trusted/desc/nacl_desc_base.h"
15 : #include "native_client/src/trusted/desc/nacl_desc_conn_cap.h"
16 : #include "native_client/src/trusted/desc/nacl_desc_imc.h"
17 : #include "native_client/src/trusted/desc/nacl_desc_imc_shm.h"
18 : #include "native_client/src/trusted/desc/nacl_desc_invalid.h"
19 : #include "native_client/src/trusted/desc/nacl_desc_io.h"
20 : #include "native_client/src/trusted/desc/nacl_desc_quota.h"
21 : #include "native_client/src/trusted/desc/nacl_desc_sync_socket.h"
22 : #include "native_client/src/trusted/desc/nacl_desc_wrapper.h"
23 : #include "native_client/src/trusted/desc/nrd_xfer.h"
24 : #include "native_client/src/trusted/nacl_base/nacl_refcount.h"
25 : #include "native_client/src/trusted/service_runtime/include/sys/errno.h"
26 : #include "native_client/src/trusted/service_runtime/nacl_config.h"
27 :
28 : // TODO(polina): follow the style guide and replace "nhdp" and "ndiodp" with
29 : // "host_desc" and "io_desc"
30 :
31 : namespace {
32 :
33 14 : struct NaClDesc* OpenHostFileCommon(const char* fname, int flags, int mode) {
34 : struct NaClHostDesc* nhdp =
35 14 : reinterpret_cast<struct NaClHostDesc*>(calloc(1, sizeof(*nhdp)));
36 14 : if (NULL == nhdp) {
37 0 : return NULL;
38 : }
39 14 : if (0 != NaClHostDescOpen(nhdp, fname, flags, mode)) {
40 0 : free(nhdp);
41 0 : return NULL;
42 : }
43 14 : struct NaClDescIoDesc* ndiodp = NaClDescIoDescMake(nhdp);
44 14 : if (NULL == ndiodp) {
45 0 : if (0 != NaClHostDescClose(nhdp)) {
46 0 : NaClLog(LOG_FATAL, "OpenHostFileCommon: NaClHostDescClose failed\n");
47 : }
48 0 : free(nhdp);
49 0 : return NULL;
50 : }
51 14 : return reinterpret_cast<struct NaClDesc*>(ndiodp);
52 : }
53 :
54 0 : struct NaClDesc* MakeQuotaCommon(const uint8_t* file_id,
55 : struct NaClDesc* desc) {
56 : NaClDescQuota* ndqp =
57 0 : reinterpret_cast<NaClDescQuota*>(calloc(1, sizeof *ndqp));
58 0 : if ((NULL == ndqp) || !NaClDescQuotaCtor(ndqp, desc, file_id, NULL)) {
59 0 : free(ndqp);
60 0 : return NULL;
61 : }
62 0 : return reinterpret_cast<struct NaClDesc*>(ndqp);
63 : }
64 :
65 : } // namespace
66 :
67 : namespace nacl {
68 :
69 : // Descriptor creation and manipulation sometimes requires additional
70 : // state. Therefore, we create an object that encapsulates that
71 : // state.
72 : class DescWrapperCommon {
73 : friend class DescWrapperFactory;
74 :
75 : public:
76 : typedef uint32_t RefCountType;
77 :
78 : // Inform clients that the object was successfully initialized.
79 57 : bool is_initialized() const { return is_initialized_; }
80 :
81 : // Manipulate reference count
82 134 : DescWrapperCommon* Ref() {
83 : // TODO(sehr): replace with a reference count class when we have one.
84 134 : NaClXMutexLock(&ref_count_mu_);
85 134 : if (std::numeric_limits<RefCountType>::max() == ref_count_) {
86 0 : NaClLog(LOG_FATAL, "DescWrapperCommon ref count overflow\n");
87 : }
88 134 : ++ref_count_;
89 134 : NaClXMutexUnlock(&ref_count_mu_);
90 134 : return this;
91 : }
92 :
93 135 : void Unref() {
94 135 : NaClXMutexLock(&ref_count_mu_);
95 135 : if (0 == ref_count_) {
96 0 : NaClLog(LOG_FATAL, "DescWrapperCommon ref count already zero\n");
97 : }
98 135 : --ref_count_;
99 135 : bool destroy = (0 == ref_count_);
100 135 : NaClXMutexUnlock(&ref_count_mu_);
101 135 : if (destroy) {
102 34 : delete this;
103 : }
104 135 : }
105 :
106 : private:
107 54 : DescWrapperCommon() : is_initialized_(false), ref_count_(1) {
108 54 : NaClXMutexCtor(&ref_count_mu_);
109 54 : }
110 34 : ~DescWrapperCommon() {
111 34 : NaClMutexDtor(&ref_count_mu_);
112 34 : }
113 :
114 : // Set up the state. Returns true on success.
115 : bool Init();
116 :
117 : // Boolean to indicate the object was successfully initialized.
118 : bool is_initialized_;
119 : // The reference count and the mutex to protect it.
120 : RefCountType ref_count_;
121 : struct NaClMutex ref_count_mu_;
122 :
123 : DISALLOW_COPY_AND_ASSIGN(DescWrapperCommon);
124 : };
125 :
126 54 : bool DescWrapperCommon::Init() {
127 : // Successfully initialized.
128 54 : is_initialized_ = true;
129 54 : return true;
130 : }
131 :
132 54 : DescWrapperFactory::DescWrapperFactory() {
133 54 : common_data_ = new(std::nothrow) DescWrapperCommon();
134 54 : if (NULL == common_data_) {
135 54 : return;
136 : }
137 54 : if (!common_data_->Init()) {
138 0 : delete common_data_;
139 0 : common_data_ = NULL;
140 : }
141 : }
142 :
143 39 : DescWrapperFactory::~DescWrapperFactory() {
144 39 : if (NULL != common_data_) {
145 39 : common_data_->Unref();
146 : }
147 39 : }
148 :
149 0 : int DescWrapperFactory::MakeBoundSock(DescWrapper* pair[2]) {
150 0 : CHECK(common_data_->is_initialized());
151 :
152 0 : struct NaClDesc* descs[2] = { NULL, NULL };
153 0 : DescWrapper* tmp_pair[2] = { NULL, NULL };
154 :
155 0 : int ret = NaClCommonDescMakeBoundSock(descs);
156 0 : if (0 != ret) {
157 0 : return ret;
158 : }
159 0 : tmp_pair[0] = new(std::nothrow) DescWrapper(common_data_, descs[0]);
160 0 : if (NULL == tmp_pair[0]) {
161 0 : goto cleanup;
162 : }
163 0 : descs[0] = NULL; // DescWrapper took ownership of descs[0].
164 0 : tmp_pair[1] = new(std::nothrow) DescWrapper(common_data_, descs[1]);
165 0 : if (NULL == tmp_pair[1]) {
166 0 : goto cleanup;
167 : }
168 0 : descs[1] = NULL; // DescWrapper took ownership of descs[1].
169 0 : pair[0] = tmp_pair[0];
170 0 : pair[1] = tmp_pair[1];
171 0 : return 0;
172 :
173 : cleanup:
174 0 : NaClDescSafeUnref(descs[0]);
175 0 : NaClDescSafeUnref(descs[1]);
176 0 : delete tmp_pair[0];
177 0 : delete tmp_pair[1];
178 0 : return -1;
179 : }
180 :
181 35 : DescWrapper* DescWrapperFactory::MakeImcSock(NaClHandle handle) {
182 : struct NaClDescImcDesc* desc =
183 35 : reinterpret_cast<NaClDescImcDesc*>(calloc(1, sizeof *desc));
184 35 : if (NULL == desc) {
185 0 : return NULL;
186 : }
187 35 : if (!NaClDescImcDescCtor(desc, handle)) {
188 0 : free(desc);
189 0 : return NULL;
190 : }
191 :
192 35 : return MakeGenericCleanup(reinterpret_cast<struct NaClDesc*>(desc));
193 : }
194 :
195 0 : DescWrapper* DescWrapperFactory::ImportShmHandle(NaClHandle handle,
196 : size_t size) {
197 0 : struct NaClDesc *desc = NaClDescImcShmMake(handle, size);
198 0 : if (desc == NULL) {
199 0 : return NULL;
200 : }
201 0 : return MakeGenericCleanup(desc);
202 : }
203 :
204 0 : DescWrapper* DescWrapperFactory::ImportSyncSocketHandle(NaClHandle handle) {
205 0 : struct NaClDesc *desc = NaClDescSyncSocketMake(handle);
206 0 : if (desc == NULL) {
207 0 : return NULL;
208 : }
209 0 : return MakeGenericCleanup(desc);
210 : }
211 :
212 0 : DescWrapper* DescWrapperFactory::MakeGeneric(struct NaClDesc* desc) {
213 0 : CHECK(common_data_->is_initialized());
214 0 : return new(std::nothrow) DescWrapper(common_data_, desc);
215 : }
216 :
217 :
218 57 : DescWrapper* DescWrapperFactory::MakeGenericCleanup(struct NaClDesc* desc) {
219 57 : CHECK(common_data_->is_initialized());
220 57 : DescWrapper* wrapper = new(std::nothrow) DescWrapper(common_data_, desc);
221 57 : if (NULL != wrapper) {
222 57 : return wrapper;
223 : }
224 0 : NaClDescSafeUnref(desc);
225 0 : return NULL;
226 : }
227 :
228 0 : int DescWrapperFactory::MakeSocketPair(DescWrapper* pair[2]) {
229 0 : CHECK(common_data_->is_initialized());
230 0 : struct NaClDesc* descs[2] = { NULL, NULL };
231 0 : DescWrapper* tmp_pair[2] = { NULL, NULL };
232 :
233 0 : int ret = NaClCommonDescSocketPair(descs);
234 0 : if (0 != ret) {
235 0 : return ret;
236 : }
237 0 : tmp_pair[0] = new(std::nothrow) DescWrapper(common_data_, descs[0]);
238 0 : if (NULL == tmp_pair[0]) {
239 0 : goto cleanup;
240 : }
241 0 : descs[0] = NULL; // DescWrapper took ownership of descs[0].
242 0 : tmp_pair[1] = new(std::nothrow) DescWrapper(common_data_, descs[1]);
243 0 : if (NULL == tmp_pair[1]) {
244 0 : goto cleanup;
245 : }
246 0 : descs[1] = NULL; // DescWrapper took ownership of descs[1].
247 0 : pair[0] = tmp_pair[0];
248 0 : pair[1] = tmp_pair[1];
249 0 : return 0;
250 :
251 : cleanup:
252 0 : NaClDescSafeUnref(descs[0]);
253 0 : NaClDescSafeUnref(descs[1]);
254 0 : delete tmp_pair[0];
255 0 : delete tmp_pair[1];
256 0 : return -1;
257 : }
258 :
259 0 : DescWrapper* DescWrapperFactory::MakeFileDesc(int host_os_desc, int mode) {
260 0 : struct NaClDesc* desc = NaClDescIoDescFromDescAllocCtor(host_os_desc, mode);
261 0 : if (NULL == desc) {
262 0 : return NULL;
263 : }
264 0 : return MakeGenericCleanup(desc);
265 : }
266 :
267 0 : DescWrapper* DescWrapperFactory::MakeFileDescQuota(int host_os_desc,
268 : int mode,
269 : const uint8_t* file_id) {
270 0 : struct NaClDesc* desc = NaClDescIoDescFromDescAllocCtor(host_os_desc, mode);
271 0 : if (NULL == desc) {
272 0 : return NULL;
273 : }
274 0 : struct NaClDesc* desc_quota = MakeQuotaCommon(file_id, desc);
275 0 : if (desc_quota == NULL) {
276 0 : NaClDescSafeUnref(desc);
277 0 : return NULL;
278 : }
279 0 : return MakeGenericCleanup(desc_quota);
280 : }
281 :
282 14 : DescWrapper* DescWrapperFactory::OpenHostFile(const char* fname,
283 : int flags,
284 : int mode) {
285 14 : struct NaClDesc* desc = OpenHostFileCommon(fname, flags, mode);
286 14 : if (NULL == desc) {
287 0 : return NULL;
288 : }
289 14 : return MakeGenericCleanup(desc);
290 : }
291 :
292 0 : DescWrapper* DescWrapperFactory::OpenHostFileQuota(const char* fname,
293 : int flags,
294 : int mode,
295 : const uint8_t* file_id) {
296 0 : struct NaClDesc* desc = OpenHostFileCommon(fname, flags, mode);
297 0 : if (NULL == desc) {
298 0 : return NULL;
299 : }
300 0 : struct NaClDesc* desc_quota = MakeQuotaCommon(file_id, desc);
301 0 : if (NULL == desc_quota) {
302 0 : NaClDescSafeUnref(desc);
303 0 : return NULL;
304 : }
305 0 : return MakeGenericCleanup(desc_quota);
306 : }
307 :
308 0 : DescWrapper* DescWrapperFactory::MakeInvalid() {
309 : struct NaClDescInvalid *desc =
310 0 : const_cast<NaClDescInvalid*>(NaClDescInvalidMake());
311 0 : if (NULL == desc) {
312 0 : return NULL;
313 : }
314 :
315 0 : return MakeGenericCleanup(reinterpret_cast<struct NaClDesc*>(desc));
316 : }
317 :
318 134 : DescWrapper::DescWrapper(DescWrapperCommon* common_data,
319 : struct NaClDesc* desc)
320 134 : : common_data_(common_data), desc_(desc) {
321 : // DescWrapper takes ownership of desc from caller, so no Ref call here.
322 134 : if (NULL != common_data_) {
323 134 : common_data_->Ref();
324 : }
325 134 : }
326 :
327 96 : DescWrapper::~DescWrapper() {
328 96 : if (NULL != common_data_) {
329 96 : common_data_->Unref();
330 : }
331 96 : NaClDescSafeUnref(desc_);
332 96 : desc_ = NULL;
333 96 : }
334 :
335 0 : ssize_t DescWrapper::Read(void* buf, size_t len) {
336 : return reinterpret_cast<struct NaClDescVtbl const *>(desc_->base.vtbl)->
337 0 : Read(desc_, buf, len);
338 : }
339 :
340 0 : ssize_t DescWrapper::Write(const void* buf, size_t len) {
341 : return reinterpret_cast<struct NaClDescVtbl const *>(desc_->base.vtbl)->
342 0 : Write(desc_, buf, len);
343 : }
344 :
345 0 : nacl_off64_t DescWrapper::Seek(nacl_off64_t offset, int whence) {
346 : return reinterpret_cast<struct NaClDescVtbl const *>(desc_->base.vtbl)->
347 : Seek(desc_,
348 : offset,
349 0 : whence);
350 : }
351 :
352 0 : int DescWrapper::Fstat(struct nacl_abi_stat* statbuf) {
353 : return reinterpret_cast<struct NaClDescVtbl const *>(desc_->base.vtbl)->
354 0 : Fstat(desc_, statbuf);
355 : }
356 :
357 0 : int DescWrapper::Close() {
358 0 : NaClRefCountUnref(&desc_->base);
359 0 : return 0;
360 : }
361 :
362 0 : ssize_t DescWrapper::Getdents(void* dirp, size_t count) {
363 : return reinterpret_cast<struct NaClDescVtbl const *>(desc_->base.vtbl)->
364 : Getdents(desc_,
365 : dirp,
366 0 : count);
367 : }
368 :
369 0 : int DescWrapper::Lock() {
370 : return reinterpret_cast<struct NaClDescVtbl const *>(desc_->base.vtbl)->
371 0 : Lock(desc_);
372 : }
373 :
374 0 : int DescWrapper::TryLock() {
375 : return reinterpret_cast<struct NaClDescVtbl const *>(desc_->base.vtbl)->
376 0 : TryLock(desc_);
377 : }
378 :
379 0 : int DescWrapper::Unlock() {
380 : return reinterpret_cast<struct NaClDescVtbl const *>(desc_->base.vtbl)->
381 0 : Unlock(desc_);
382 : }
383 :
384 0 : int DescWrapper::Wait(DescWrapper* mutex) {
385 : return reinterpret_cast<struct NaClDescVtbl const *>(desc_->base.vtbl)->
386 0 : Wait(desc_, mutex->desc_);
387 : }
388 :
389 0 : int DescWrapper::TimedWaitAbs(DescWrapper* mutex,
390 : struct nacl_abi_timespec* ts) {
391 : return reinterpret_cast<struct NaClDescVtbl const *>(desc_->base.vtbl)->
392 : TimedWaitAbs(desc_,
393 : mutex->desc_,
394 0 : ts);
395 : }
396 :
397 0 : int DescWrapper::Signal() {
398 : return reinterpret_cast<struct NaClDescVtbl const *>(desc_->base.vtbl)->
399 0 : Signal(desc_);
400 : }
401 :
402 0 : int DescWrapper::Broadcast() {
403 : return reinterpret_cast<struct NaClDescVtbl const *>(desc_->base.vtbl)->
404 0 : Broadcast(desc_);
405 : }
406 :
407 0 : ssize_t DescWrapper::SendMsg(const MsgHeader* dgram, int flags) {
408 : struct NaClImcTypedMsgHdr header;
409 0 : ssize_t ret = -NACL_ABI_ENOMEM;
410 0 : nacl_abi_size_t diov_length = dgram->iov_length;
411 0 : nacl_abi_size_t ddescv_length = dgram->ndescv_length;
412 : nacl_abi_size_t i;
413 :
414 : // Initialize to allow simple cleanups.
415 0 : header.ndescv = NULL;
416 : // Allocate and copy IOV.
417 0 : if (NACL_ABI_SIZE_T_MAX / sizeof(NaClImcMsgIoVec) <= diov_length) {
418 0 : goto cleanup;
419 : }
420 : header.iov = reinterpret_cast<NaClImcMsgIoVec*>(
421 0 : calloc(diov_length, sizeof(*(header.iov))));
422 0 : if (NULL == header.iov) {
423 0 : goto cleanup;
424 : }
425 0 : header.iov_length = diov_length;
426 0 : for (i = 0; i < dgram->iov_length; ++i) {
427 0 : header.iov[i].base = dgram->iov[i].base;
428 0 : header.iov[i].length = dgram->iov[i].length;
429 : }
430 : // Allocate and copy the descriptor vector, removing DescWrappers.
431 0 : if (NACL_HANDLE_COUNT_MAX < dgram->ndescv_length) {
432 0 : goto cleanup;
433 : }
434 0 : if (NACL_ABI_SIZE_T_MAX / sizeof(header.ndescv[0]) <= ddescv_length) {
435 0 : goto cleanup;
436 : }
437 : header.ndescv = reinterpret_cast<NaClDesc**>(
438 0 : calloc(ddescv_length, sizeof(*(header.ndescv))));
439 0 : if (NULL == header.iov) {
440 0 : goto cleanup;
441 : }
442 0 : header.ndesc_length = ddescv_length;
443 0 : for (i = 0; i < dgram->ndescv_length; ++i) {
444 0 : header.ndescv[i] = dgram->ndescv[i]->desc_;
445 : }
446 : // Send the message.
447 0 : ret = NACL_VTBL(NaClDesc, desc_)->SendMsg(desc_, &header, flags);
448 :
449 : cleanup:
450 0 : free(header.ndescv);
451 0 : free(header.iov);
452 0 : return ret;
453 : }
454 :
455 21 : ssize_t DescWrapper::RecvMsg(MsgHeader* dgram, int flags,
456 : struct NaClDescQuotaInterface *quota_interface) {
457 : struct NaClImcTypedMsgHdr header;
458 21 : ssize_t ret = -NACL_ABI_ENOMEM;
459 21 : nacl_abi_size_t diov_length = dgram->iov_length;
460 21 : nacl_abi_size_t ddescv_length = dgram->ndescv_length;
461 : nacl_abi_size_t i;
462 :
463 : // Initialize to allow simple cleanups.
464 21 : header.iov = NULL;
465 21 : header.ndescv = NULL;
466 189 : for (i = 0; i < dgram->ndescv_length; ++i) {
467 168 : dgram->ndescv[i] = NULL;
468 : }
469 :
470 : // Allocate and copy the IOV.
471 21 : if (NACL_ABI_SIZE_T_MAX / sizeof(NaClImcMsgIoVec) <= diov_length) {
472 0 : goto cleanup;
473 : }
474 : header.iov = reinterpret_cast<NaClImcMsgIoVec*>(
475 21 : calloc(diov_length, sizeof(*(header.iov))));
476 21 : if (NULL == header.iov) {
477 0 : goto cleanup;
478 : }
479 21 : header.iov_length = diov_length;
480 42 : for (i = 0; i < dgram->iov_length; ++i) {
481 21 : header.iov[i].base = dgram->iov[i].base;
482 21 : header.iov[i].length = dgram->iov[i].length;
483 : }
484 : // Allocate and copy the descriptor vector.
485 21 : if (NACL_HANDLE_COUNT_MAX < dgram->ndescv_length) {
486 0 : goto cleanup;
487 : }
488 21 : if (NACL_ABI_SIZE_T_MAX / sizeof(header.ndescv[0]) <= ddescv_length) {
489 0 : goto cleanup;
490 : }
491 : header.ndescv = reinterpret_cast<NaClDesc**>(
492 21 : calloc(ddescv_length, sizeof(*(header.ndescv))));
493 21 : if (NULL == header.ndescv) {
494 0 : goto cleanup;
495 : }
496 21 : header.ndesc_length = ddescv_length;
497 : // Receive the message.
498 : ret = NACL_VTBL(NaClDesc, desc_)->RecvMsg(desc_, &header, flags,
499 21 : quota_interface);
500 21 : if (ret < 0) {
501 0 : goto cleanup;
502 : }
503 21 : dgram->ndescv_length = header.ndesc_length;
504 21 : dgram->flags = header.flags;
505 : // Copy the descriptors, creating new DescWrappers around them.
506 63 : for (i = 0; i < header.ndesc_length; ++i) {
507 42 : dgram->ndescv[i] =
508 84 : new(std::nothrow) DescWrapper(common_data_, header.ndescv[i]);
509 42 : if (NULL == dgram->ndescv[i]) {
510 0 : goto cleanup;
511 : }
512 : }
513 21 : free(header.ndescv);
514 21 : free(header.iov);
515 21 : return ret;
516 :
517 : cleanup:
518 0 : for (i = 0; i < ddescv_length; ++i) {
519 0 : delete dgram->ndescv[i];
520 : }
521 0 : free(header.ndescv);
522 0 : free(header.iov);
523 0 : return ret;
524 : }
525 :
526 35 : DescWrapper* DescWrapper::Connect() {
527 : struct NaClDesc* connected_desc;
528 : int rv = reinterpret_cast<struct NaClDescVtbl const *>(desc_->base.vtbl)->
529 : ConnectAddr(desc_,
530 35 : &connected_desc);
531 35 : if (0 != rv) {
532 : // Connect failed.
533 0 : return NULL;
534 : }
535 : DescWrapper* wrapper =
536 35 : new(std::nothrow) DescWrapper(common_data_, connected_desc);
537 35 : if (NULL == wrapper) {
538 0 : NaClDescUnref(connected_desc);
539 : }
540 35 : return wrapper;
541 : }
542 :
543 0 : DescWrapper* DescWrapper::Accept() {
544 : struct NaClDesc* connected_desc;
545 : int rv = reinterpret_cast<struct NaClDescVtbl const *>(desc_->base.vtbl)->
546 0 : AcceptConn(desc_, &connected_desc);
547 0 : if (0 != rv) {
548 : // Accept failed.
549 0 : return NULL;
550 : }
551 : DescWrapper* wrapper =
552 0 : new(std::nothrow) DescWrapper(common_data_, connected_desc);
553 0 : if (NULL == wrapper) {
554 0 : NaClDescUnref(connected_desc);
555 : }
556 0 : return wrapper;
557 : }
558 :
559 0 : int DescWrapper::Post() {
560 : return reinterpret_cast<struct NaClDescVtbl const *>(desc_->base.vtbl)->
561 0 : Post(desc_);
562 : }
563 :
564 0 : int DescWrapper::SemWait() {
565 : return reinterpret_cast<struct NaClDescVtbl const *>(desc_->base.vtbl)->
566 0 : SemWait(desc_);
567 : }
568 :
569 0 : int DescWrapper::GetValue() {
570 : return reinterpret_cast<struct NaClDescVtbl const *>(desc_->base.vtbl)->
571 0 : GetValue(desc_);
572 : }
573 :
574 : } // namespace nacl
|