File size: 4,995 Bytes
5641073
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
import initTrace from "debug";

import { PTR_ADOPT_WRONG_MESSAGE, PTR_ALREADY_ADOPTED, PTR_INVALID_POINTER_TYPE } from "../../errors";
import { format } from "../../util";
import { ListElementSize } from "../list-element-size";
import { ObjectSize, getWordLength } from "../object-size";
import { Segment } from "../segment";
import {
  Pointer,
  getTargetListLength,
  getTargetStructSize,
  getTargetPointerType,
  getTargetListElementSize,
  getTargetCompositeListSize,
  getCapabilityId,
  getContent,
  erasePointer,
  initPointer,
  erase,
  setStructPointer,
  setListPointer,
  setInterfacePointer,
  getListByteLength,
} from "./pointer";
import { PointerType } from "./pointer-type";

const trace = initTrace("capnp:orphan");
trace("load");

export interface _Orphan {
  capId: number;
  elementSize: ListElementSize;
  length: number;
  size: ObjectSize;
  type: PointerType;
}

// Technically speaking this class doesn't need to be generic, but the extra type checking enforced by this helps to
// make sure you don't accidentally adopt a pointer of the wrong type.

/**
 * An orphaned pointer. This object itself is technically a pointer to the original pointer's content, which was left
 * untouched in its original message. The original pointer data is encoded as attributes on the Orphan object, ready to
 * be reconstructed once another pointer is ready to adopt it.
 *
 * @export
 * @class Orphan
 * @extends {Pointer}
 * @template T
 */

export class Orphan<T extends Pointer> {
  /** If this member is not present then the orphan has already been adopted, or something went very wrong. */
  _capnp?: _Orphan;

  byteOffset: number;
  segment: Segment;

  constructor(src: T) {
    const c = getContent(src);

    this.segment = c.segment;
    this.byteOffset = c.byteOffset;

    this._capnp = {} as _Orphan;

    // Read vital info from the src pointer so we can reconstruct it during adoption.

    this._capnp.type = getTargetPointerType(src);

    switch (this._capnp.type) {
      case PointerType.STRUCT:
        this._capnp.size = getTargetStructSize(src);

        break;

      case PointerType.LIST:
        this._capnp.length = getTargetListLength(src);
        this._capnp.elementSize = getTargetListElementSize(src);

        if (this._capnp.elementSize === ListElementSize.COMPOSITE) {
          this._capnp.size = getTargetCompositeListSize(src);
        }

        break;

      case PointerType.OTHER:
        this._capnp.capId = getCapabilityId(src);

        break;

      default:
        // COVERAGE: Unreachable code.
        /* istanbul ignore next */
        throw new Error(PTR_INVALID_POINTER_TYPE);
    }

    // Zero out the source pointer (but not the contents!).

    erasePointer(src);
  }

  /**
   * Adopt (move) this orphan into the target pointer location. This will allocate far pointers in `dst` as needed.
   *
   * @param {T} dst The destination pointer.
   * @returns {void}
   */

  _moveTo(dst: T): void {
    if (this._capnp === undefined) {
      throw new Error(format(PTR_ALREADY_ADOPTED, this));
    }

    // TODO: Implement copy semantics when this happens.
    if (this.segment.message !== dst.segment.message) {
      throw new Error(format(PTR_ADOPT_WRONG_MESSAGE, this, dst));
    }

    // Recursively wipe out the destination pointer first.

    erase(dst);

    const res = initPointer(this.segment, this.byteOffset, dst);

    switch (this._capnp.type) {
      case PointerType.STRUCT:
        setStructPointer(res.offsetWords, this._capnp.size, res.pointer);

        break;

      case PointerType.LIST: {
        let offsetWords = res.offsetWords;

        if (this._capnp.elementSize === ListElementSize.COMPOSITE) {
          offsetWords--; // The tag word gets skipped.
        }

        setListPointer(offsetWords, this._capnp.elementSize, this._capnp.length, res.pointer, this._capnp.size);

        break;
      }
      case PointerType.OTHER:
        setInterfacePointer(this._capnp.capId, res.pointer);

        break;

      /* istanbul ignore next */
      default:
        throw new Error(PTR_INVALID_POINTER_TYPE);
    }

    this._capnp = undefined;
  }

  dispose(): void {
    // FIXME: Should this throw?
    if (this._capnp === undefined) {
      trace("not disposing an already disposed orphan", this);

      return;
    }

    switch (this._capnp.type) {
      case PointerType.STRUCT:
        this.segment.fillZeroWords(this.byteOffset, getWordLength(this._capnp.size));

        break;

      case PointerType.LIST: {
        const byteLength = getListByteLength(this._capnp.elementSize, this._capnp.length, this._capnp.size);
        this.segment.fillZeroWords(this.byteOffset, byteLength);

        break;
      }
      default:
        // Other pointer types don't actually have any content.

        break;
    }

    this._capnp = undefined;
  }

  toString(): string {
    return format("Orphan_%d@%a,type:%s", this.segment.id, this.byteOffset, this._capnp && this._capnp.type);
  }
}