aws-cdk
Version:
AWS CDK Toolkit CLI, the command line tool for CDK apps
347 lines • 45.9 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.trackLeakedHandles = trackLeakedHandles;
const node_async_hooks_1 = require("node:async_hooks");
const node_fs_1 = require("node:fs");
const node_url_1 = require("node:url");
const chalk_1 = __importDefault(require("chalk"));
/**
* Async resource types we track, with a plain-language description for the report.
*
* Only libuv *handles*, the resources that can hold the event loop open. Requests
* (a DNS lookup, a stream write) have completed by the time the report runs.
* `PROMISE` and `TickObject` are excluded on volume alone: they dominate every
* async resource a synth creates, and each one would cost a stack capture.
*
* Every type here exposes `hasRef()`, the only way to tell a handle holding the
* loop open from one already closed. `TLSWRAP` has neither `hasRef()` nor a
* `destroy` hook, so including it would report every completed HTTPS request as a
* leak forever. A leaked TLS connection still surfaces as the `TCPWRAP` under it.
*/
const TRACKED_TYPES = {
TCPWRAP: 'open network connection',
TCPSERVERWRAP: 'listening TCP server',
PIPEWRAP: 'open pipe',
PIPESERVERWRAP: 'listening pipe server',
UDPWRAP: 'open UDP socket',
TTYWRAP: 'open terminal stream',
Timeout: 'timer from setTimeout or setInterval',
Immediate: 'pending setImmediate callback',
PROCESSWRAP: 'spawned child process still running',
FSEVENTWRAP: 'file-system watcher',
STATWATCHER: 'file-system stat watcher',
SIGNALWRAP: 'OS signal handler still registered',
WORKER: 'worker thread still running',
MESSAGEPORT: 'open worker-thread message channel',
};
/**
* How many stack frames to capture per resource.
*
* Node's default of 10 is too shallow here. A socket opened through an HTTPS agent
* sits behind enough frames of `node:internal/tls/wrap`, `node:https`,
* `node:_http_agent` and `node:_http_client` to consume that whole budget, leaving
* no application frame for the report to show.
*/
const STACK_CAPTURE_DEPTH = 60;
/**
* Grace period before the handle report fires. The timer is unref'd, so it never
* fires when Node exits cleanly within this window.
*/
const HANDLE_DUMP_GRACE_MS = 1000;
/**
* Cap on how many resources we hold provenance for at once.
*
* A long-lived command (`cdk watch`, a deploy of many stacks) opens and closes
* handles continuously. The `destroy` hook normally removes them again, but types
* that never fire it would accumulate for the life of the process. The report says
* when the cap was hit, so a truncated list is not read as a complete one.
*/
const MAX_TRACKED_HANDLES = 10_000;
/**
* How much of a source line to show beneath a stack frame. Long enough to read a
* statement, short enough that one minified line from the bundled CLI cannot
* flood the report.
*/
const SOURCE_SNIPPET_CHARS = 200;
/**
* Tracks async resources via async_hooks and, on demand, reports the ones still
* keeping the event loop alive, with where they were created.
*
* Nothing is tracked until `start()` runs. Not exported as a value, so the only
* way to get one is `trackLeakedHandles()`, which returns a started tracker.
*/
class LeakedHandleTracker {
watched = new Map();
/**
* Whether we stopped recording because {@link MAX_TRACKED_HANDLES} was reached.
* Reported, so a short list is never mistaken for a complete one.
*/
atCapacity = false;
hook = (0, node_async_hooks_1.createHook)({
init: (asyncId, type, _triggerAsyncId, resource) => {
if (!(type in TRACKED_TYPES)) {
return;
}
if (this.watched.size >= MAX_TRACKED_HANDLES) {
this.atCapacity = true;
return;
}
// An exception thrown from an async_hooks callback is not catchable by the
// application. Node prints "Error in AsyncHook callback" and aborts the
// process, so anything that goes wrong here must cost no more than one
// handle's provenance.
try {
this.watched.set(asyncId, {
type,
handleRef: new WeakRef(resource),
creationStack: captureCreationStack(),
});
}
catch {
this.watched.delete(asyncId);
}
},
destroy: (asyncId) => {
this.watched.delete(asyncId);
},
});
/**
* Begin watching async resources. Must run before the resources we care about
* are created, and only when the user opted in, since the hook adds a small
* per-resource cost.
*/
start() {
this.hook.enable();
}
/**
* Stop watching and discard all tracked state.
*/
stop() {
this.hook.disable();
this.watched.clear();
}
/**
* Report the leaked handles once the grace period has passed. The timer is
* unref'd, so it never fires if the process exits cleanly within the window and
* never holds the process open itself.
*/
scheduleReport(ioHelper) {
setTimeout(() => {
// Runs detached from the CLI's error handling: the command has finished and
// there is no one left to hand a rejection to. A diagnostic must not crash
// the process it is diagnosing.
this.report(ioHelper).catch(() => undefined);
}, HANDLE_DUMP_GRACE_MS).unref();
}
/**
* Report every resource still holding the event loop open, each with the source
* location where it was created. Call at the very end of execution, by which
* point only genuinely leaked handles should remain.
*
* Emitted as one message, so unrelated output cannot interleave into the middle
* of a stack trace, and at DEBUG, which is the level `--debug-cli` raises the
* CLI to.
*/
async report(ioHelper) {
this.hook.disable();
const leaks = [...this.watched.values()].filter((r) => {
const handle = r.handleRef.deref();
// Already garbage collected, so no longer keeping the loop alive.
if (handle === undefined) {
return false;
}
// A fired one-shot timer answers `hasRef() === true` for the rest of the
// process and only marks itself `_destroyed`. The `destroy` hook does drop
// it from `watched`, but a tick later than this, and timers are the CLI's
// most common resource.
if (handle._destroyed === true) {
return false;
}
// Ask the handle itself, and believe only a clear yes. A closed socket
// answers false, and anything that cannot answer is not evidence of a leak.
return typeof handle.hasRef === 'function' && handle.hasRef();
});
this.watched.clear();
await ioHelper.defaults.debug(this.renderReport(leaks).join('\n'));
}
renderReport(leaks) {
if (leaks.length === 0) {
// This report only runs because the process was still alive, so something
// is holding the loop open. Finding nothing means the cause is outside what
// we track, so hand over what Node itself sees.
return [
'The CLI process is still alive, but no tracked handle explains it.',
`Node reports these resources still active: ${activeResourceSummary()}`,
'The cause may be a handle opened before tracking started, or a type this build does not track.',
];
}
const lines = [`${leaks.length} ${leaks.length === 1 ? 'handle' : 'handles'} still keeping the CLI process alive:`];
if (this.atCapacity) {
lines.push(`Stopped recording after ${MAX_TRACKED_HANDLES} handles, so this list may be incomplete.`);
}
// One report pass can revisit the same file for every frame of every handle,
// and in the published CLI that file is the entire bundle.
const sources = new SourceCache();
for (const leak of leaks) {
lines.push(...describeLeak(leak, sources));
}
if (sources.sawMinifiedLine) {
// The published CLI is one bundled, minified file with no source map, so a
// location is a position in the bundle and the snippet beneath it is the
// only part that maps back to something recognisable.
lines.push('');
lines.push('Locations above are positions in the bundled CLI, not in the original source files.');
}
return lines;
}
}
function describeLeak(leak, sources) {
const frames = actionableFrames(leak.creationStack);
const lines = ['', chalk_1.default.bold(`# ${leak.type} (${TRACKED_TYPES[leak.type]})`)];
if (frames.length === 0) {
lines.push(' (opened entirely inside Node internals, no CLI frames to show)');
return lines;
}
// Headline the function only when it has a real name. Sockets often open from
// anonymous internal callbacks, where the name says nothing.
const [origin] = frames;
if (origin.func && origin.func !== '<anonymous>') {
lines.push(` created in ${origin.func}()`);
}
lines.push(' call stack:');
for (const frame of frames) {
// The location is what identifies the frame, so print it even when the file
// cannot be read.
lines.push(` ${frame.func} (${frame.file}:${frame.line}:${frame.column})`);
const snippet = sources.snippetAt(frame);
if (snippet) {
lines.push(` ${chalk_1.default.dim(snippet)}`);
}
}
return lines;
}
/**
* Capture the call site of the current async resource as structured frames.
*
* Installs a structured `prepareStackTrace`, captures with this function as the
* cut-off, then restores the previous formatter so other stacks are unaffected.
*
* The cut-off removes this function and everything above it, but the caller is the
* tracker's own `init` hook, which sits just below and would otherwise be the top
* frame. We drop that frame by position rather than by filename, since after
* bundling every frame shares one filename.
*/
function captureCreationStack() {
const carrier = {};
// `Error.prepareStackTrace` is a V8 formatting hook we save and restore, not a
// method we invoke, so the unbound-method concern does not apply.
// eslint-disable-next-line @typescript-eslint/unbound-method
const previous = Error.prepareStackTrace;
const previousLimit = Error.stackTraceLimit;
Error.stackTraceLimit = STACK_CAPTURE_DEPTH;
Error.prepareStackTrace = (_error, callSites) => callSites.map((site) => {
const file = site.getFileName() ?? '';
return {
func: site.getFunctionName() ?? '<anonymous>',
file: file.startsWith('file://') ? (0, node_url_1.fileURLToPath)(file) : file,
line: site.getLineNumber() ?? 0,
column: site.getColumnNumber() ?? 0,
};
});
try {
Error.captureStackTrace(carrier, captureCreationStack);
// Drop the `init` hook frame, always the top one, so the report points at the
// code that created the resource rather than at this tracker.
return carrier.stack?.slice(1) ?? [];
}
finally {
Error.prepareStackTrace = previous;
Error.stackTraceLimit = previousLimit;
}
}
/**
* What Node itself reports as still holding the loop open, as `2x Timeout, 1x TCP`.
*
* This is the ground truth our own tracking tries to explain, so printing it tells
* the reader whether we missed something or the hang is somewhere else.
*/
function activeResourceSummary() {
const counts = new Map();
for (const type of process.getActiveResourcesInfo()) {
counts.set(type, (counts.get(type) ?? 0) + 1);
}
if (counts.size === 0) {
return '(none)';
}
return [...counts].map(([type, count]) => `${count}x ${type}`).join(', ');
}
/**
* Keep only frames the user can act on by dropping Node internals. Our own `init`
* frame is already removed at capture time, see {@link captureCreationStack}.
*/
function actionableFrames(frames) {
return frames.filter((frame) => frame.file && !frame.file.startsWith('node:'));
}
/**
* Reads the line of code behind a stack frame, holding each file's lines so a
* report pass reads any given file at most once.
*
* The published CLI is one large bundle, and a single report can hold many frames
* pointing into it. Re-reading and re-splitting it per frame stalls the report, at
* the moment the user is already waiting on a hung CLI.
*/
class SourceCache {
/**
* Whether any line we read was long enough to be minified rather than
* hand-written. Used to warn that the printed locations are bundle-relative.
*/
sawMinifiedLine = false;
files = new Map();
snippetAt(frame) {
const line = this.linesOf(frame.file)?.[frame.line - 1];
if (line === undefined) {
return undefined;
}
if (line.length <= SOURCE_SNIPPET_CHARS) {
return line.trim() || undefined;
}
// Too long to be source anyone wrote, so show a window centred on the column
// rather than the start of the line.
this.sawMinifiedLine = true;
const start = Math.max(0, frame.column - 1 - Math.floor(SOURCE_SNIPPET_CHARS / 2));
const end = Math.min(line.length, start + SOURCE_SNIPPET_CHARS);
const window = line.slice(start, end).trim();
return `${start > 0 ? '…' : ''}${window}${end < line.length ? '…' : ''}`;
}
linesOf(file) {
if (!this.files.has(file)) {
this.files.set(file, readLines(file));
}
return this.files.get(file);
}
}
function readLines(file) {
try {
return (0, node_fs_1.readFileSync)(file, 'utf-8').split(/\r?\n/);
}
catch {
// Bundled and eval'd frames have filenames that are not on disk. The location
// is still reported on its own, so reporting continues without the snippet.
return undefined;
}
}
/**
* Start watching async resources and return the tracker doing so.
*
* Call as early as possible, and only when the user asked for it. The tracker can
* only report on resources created after it starts.
*/
function trackLeakedHandles() {
const tracker = new LeakedHandleTracker();
tracker.start();
return tracker;
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZGVidWctaGFuZGxlcy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbImRlYnVnLWhhbmRsZXMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7QUFrWkEsZ0RBSUM7QUF0WkQsdURBQThDO0FBQzlDLHFDQUF1QztBQUN2Qyx1Q0FBeUM7QUFDekMsa0RBQTBCO0FBRzFCOzs7Ozs7Ozs7Ozs7R0FZRztBQUNILE1BQU0sYUFBYSxHQUFxQztJQUN0RCxPQUFPLEVBQUUseUJBQXlCO0lBQ2xDLGFBQWEsRUFBRSxzQkFBc0I7SUFDckMsUUFBUSxFQUFFLFdBQVc7SUFDckIsY0FBYyxFQUFFLHVCQUF1QjtJQUN2QyxPQUFPLEVBQUUsaUJBQWlCO0lBQzFCLE9BQU8sRUFBRSxzQkFBc0I7SUFDL0IsT0FBTyxFQUFFLHNDQUFzQztJQUMvQyxTQUFTLEVBQUUsK0JBQStCO0lBQzFDLFdBQVcsRUFBRSxxQ0FBcUM7SUFDbEQsV0FBVyxFQUFFLHFCQUFxQjtJQUNsQyxXQUFXLEVBQUUsMEJBQTBCO0lBQ3ZDLFVBQVUsRUFBRSxvQ0FBb0M7SUFDaEQsTUFBTSxFQUFFLDZCQUE2QjtJQUNyQyxXQUFXLEVBQUUsb0NBQW9DO0NBQ2xELENBQUM7QUFFRjs7Ozs7OztHQU9HO0FBQ0gsTUFBTSxtQkFBbUIsR0FBRyxFQUFFLENBQUM7QUFFL0I7OztHQUdHO0FBQ0gsTUFBTSxvQkFBb0IsR0FBRyxJQUFJLENBQUM7QUFFbEM7Ozs7Ozs7R0FPRztBQUNILE1BQU0sbUJBQW1CLEdBQUcsTUFBTSxDQUFDO0FBRW5DOzs7O0dBSUc7QUFDSCxNQUFNLG9CQUFvQixHQUFHLEdBQUcsQ0FBQztBQXFDakM7Ozs7OztHQU1HO0FBQ0gsTUFBTSxtQkFBbUI7SUFDTixPQUFPLEdBQUcsSUFBSSxHQUFHLEVBQTJCLENBQUM7SUFFOUQ7OztPQUdHO0lBQ0ssVUFBVSxHQUFHLEtBQUssQ0FBQztJQUVWLElBQUksR0FBRyxJQUFBLDZCQUFVLEVBQUM7UUFDakMsSUFBSSxFQUFFLENBQUMs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QyxJQUFJLEVBQUUsU0FBUyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUM7UUFDeEMsQ0FBQztRQUNELE9BQU8sSUFBSSxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLENBQUM7SUFDOUIsQ0FBQztDQUNGO0FBRUQsU0FBUyxTQUFTLENBQUMsSUFBWTtJQUM3QixJQUFJLENBQUM7UUFDSCxPQUFPLElBQUEsc0JBQVksRUFBQyxJQUFJLEVBQUUsT0FBTyxDQUFDLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxDQUFDO0lBQ3BELENBQUM7SUFBQyxNQUFNLENBQUM7UUFDUCw4RUFBOEU7UUFDOUUsNEVBQTRFO1FBQzVFLE9BQU8sU0FBUyxDQUFDO0lBQ25CLENBQUM7QUFDSCxDQUFDO0FBRUQ7Ozs7O0dBS0c7QUFDSCxTQUFnQixrQkFBa0I7SUFDaEMsTUFBTSxPQUFPLEdBQUcsSUFBSSxtQkFBbUIsRUFBRSxDQUFDO0lBQzFDLE9BQU8sQ0FBQyxLQUFLLEVBQUUsQ0FBQztJQUNoQixPQUFPLE9BQU8sQ0FBQztBQUNqQixDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgY3JlYXRlSG9vayB9IGZyb20gJ25vZGU6YXN5bmNfaG9va3MnO1xuaW1wb3J0IHsgcmVhZEZpbGVTeW5jIH0gZnJvbSAnbm9kZTpmcyc7XG5pbXBvcnQgeyBmaWxlVVJMVG9QYXRoIH0gZnJvbSAnbm9kZTp1cmwnO1xuaW1wb3J0IGNoYWxrIGZyb20gJ2NoYWxrJztcbmltcG9ydCB0eXBlIHsgSW9IZWxwZXIgfSBmcm9tICcuLi9hcGktcHJpdmF0ZSc7XG5cbi8qKlxuICogQXN5bmMgcmVzb3VyY2UgdHlwZXMgd2UgdHJhY2ssIHdpdGggYSBwbGFpbi1sYW5ndWFnZSBkZXNjcmlwdGlvbiBmb3IgdGhlIHJlcG9ydC5cbiAqXG4gKiBPbmx5IGxpYnV2ICpoYW5kbGVzKiwgdGhlIHJlc291cmNlcyB0aGF0IGNhbiBob2xkIHRoZSBldmVudCBsb29wIG9wZW4uIFJlcXVlc3RzXG4gKiAoYSBETlMgbG9va3VwLCBhIHN0cmVhbSB3cml0ZSkgaGF2ZSBjb21wbGV0ZWQgYnkgdGhlIHRpbWUgdGhlIHJlcG9ydCBydW5zLlxuICogYFBST01JU0VgIGFuZCBgVGlja09iamVjdGAgYXJlIGV4Y2x1ZGVkIG9uIHZvbHVtZSBhbG9uZTogdGhleSBkb21pbmF0ZSBldmVyeVxuICogYXN5bmMgcmVzb3VyY2UgYSBzeW50aCBjcmVhdGVzLCBhbmQgZWFjaCBvbmUgd291bGQgY29zdCBhIHN0YWNrIGNhcHR1cmUuXG4gKlxuICogRXZlcnkgdHlwZSBoZXJlIGV4cG9zZXMgYGhhc1JlZigpYCwgdGhlIG9ubHkgd2F5IHRvIHRlbGwgYSBoYW5kbGUgaG9sZGluZyB0aGVcbiAqIGxvb3Agb3BlbiBmcm9tIG9uZSBhbHJlYWR5IGNsb3NlZC4gYFRMU1dSQVBgIGhhcyBuZWl0aGVyIGBoYXNSZWYoKWAgbm9yIGFcbiAqIGBkZXN0cm95YCBob29rLCBzbyBpbmNsdWRpbmcgaXQgd291bGQgcmVwb3J0IGV2ZXJ5IGNvbXBsZXRlZCBIVFRQUyByZXF1ZXN0IGFzIGFcbiAqIGxlYWsgZm9yZXZlci4gQSBsZWFrZWQgVExTIGNvbm5lY3Rpb24gc3RpbGwgc3VyZmFjZXMgYXMgdGhlIGBUQ1BXUkFQYCB1bmRlciBpdC5cbiAqL1xuY29uc3QgVFJBQ0tFRF9UWVBFUzogUmVhZG9ubHk8UmVjb3JkPHN0cmluZywgc3RyaW5nPj4gPSB7XG4gIFRDUFdSQVA6ICdvcGVuIG5ldHdvcmsgY29ubmVjdGlvbicsXG4gIFRDUFNFUlZFUldSQVA6ICdsaXN0ZW5pbmcgVENQIHNlcnZlcicsXG4gIFBJUEVXUkFQOiAnb3BlbiBwaXBlJyxcbiAgUElQRVNFUlZFUldSQVA6ICdsaXN0ZW5pbmcgcGlwZSBzZXJ2ZXInLFxuICBVRFBXUkFQOiAnb3BlbiBVRFAgc29ja2V0JyxcbiAgVFRZV1JBUDogJ29wZW4gdGVybWluYWwgc3RyZWFtJyxcbiAgVGltZW91dDogJ3RpbWVyIGZyb20gc2V0VGltZW91dCBvciBzZXRJbnRlcnZhbCcsXG4gIEltbWVkaWF0ZTogJ3BlbmRpbmcgc2V0SW1tZWRpYXRlIGNhbGxiYWNrJyxcbiAgUFJPQ0VTU1dSQVA6ICdzcGF3bmVkIGNoaWxkIHByb2Nlc3Mgc3RpbGwgcnVubmluZycsXG4gIEZTRVZFTlRXUkFQOiAnZmlsZS1zeXN0ZW0gd2F0Y2hlcicsXG4gIFNUQVRXQVRDSEVSOiAnZmlsZS1zeXN0ZW0gc3RhdCB3YXRjaGVyJyxcbiAgU0lHTkFMV1JBUDogJ09TIHNpZ25hbCBoYW5kbGVyIHN0aWxsIHJlZ2lzdGVyZWQnLFxuICBXT1JLRVI6ICd3b3JrZXIgdGhyZWFkIHN0aWxsIHJ1bm5pbmcnLFxuICBNRVNTQUdFUE9SVDogJ29wZW4gd29ya2VyLXRocmVhZCBtZXNzYWdlIGNoYW5uZWwnLFxufTtcblxuLyoqXG4gKiBIb3cgbWFueSBzdGFjayBmcmFtZXMgdG8gY2FwdHVyZSBwZXIgcmVzb3VyY2UuXG4gKlxuICogTm9kZSdzIGRlZmF1bHQgb2YgMTAgaXMgdG9vIHNoYWxsb3cgaGVyZS4gQSBzb2NrZXQgb3BlbmVkIHRocm91Z2ggYW4gSFRUUFMgYWdlbnRcbiAqIHNpdHMgYmVoaW5kIGVub3VnaCBmcmFtZXMgb2YgYG5vZGU6aW50ZXJuYWwvdGxzL3dyYXBgLCBgbm9kZTpodHRwc2AsXG4gKiBgbm9kZTpfaHR0cF9hZ2VudGAgYW5kIGBub2RlOl9odHRwX2NsaWVudGAgdG8gY29uc3VtZSB0aGF0IHdob2xlIGJ1ZGdldCwgbGVhdmluZ1xuICogbm8gYXBwbGljYXRpb24gZnJhbWUgZm9yIHRoZSByZXBvcnQgdG8gc2hvdy5cbiAqL1xuY29uc3QgU1RBQ0tfQ0FQVFVSRV9ERVBUSCA9IDYwO1xuXG4vKipcbiAqIEdyYWNlIHBlcmlvZCBiZWZvcmUgdGhlIGhhbmRsZSByZXBvcnQgZmlyZXMuIFRoZSB0aW1lciBpcyB1bnJlZidkLCBzbyBpdCBuZXZlclxuICogZmlyZXMgd2hlbiBOb2RlIGV4aXRzIGNsZWFubHkgd2l0aGluIHRoaXMgd2luZG93LlxuICovXG5jb25zdCBIQU5ETEVfRFVNUF9HUkFDRV9NUyA9IDEwMDA7XG5cbi8qKlxuICogQ2FwIG9uIGhvdyBtYW55IHJlc291cmNlcyB3ZSBob2xkIHByb3ZlbmFuY2UgZm9yIGF0IG9uY2UuXG4gKlxuICogQSBsb25nLWxpdmVkIGNvbW1hbmQgKGBjZGsgd2F0Y2hgLCBhIGRlcGxveSBvZiBtYW55IHN0YWNrcykgb3BlbnMgYW5kIGNsb3Nlc1xuICogaGFuZGxlcyBjb250aW51b3VzbHkuIFRoZSBgZGVzdHJveWAgaG9vayBub3JtYWxseSByZW1vdmVzIHRoZW0gYWdhaW4sIGJ1dCB0eXBlc1xuICogdGhhdCBuZXZlciBmaXJlIGl0IHdvdWxkIGFjY3VtdWxhdGUgZm9yIHRoZSBsaWZlIG9mIHRoZSBwcm9jZXNzLiBUaGUgcmVwb3J0IHNheXNcbiAqIHdoZW4gdGhlIGNhcCB3YXMgaGl0LCBzbyBhIHRydW5jYXRlZCBsaXN0IGlzIG5vdCByZWFkIGFzIGEgY29tcGxldGUgb25lLlxuICovXG5jb25zdCBNQVhfVFJBQ0tFRF9IQU5ETEVTID0gMTBfMDAwO1xuXG4vKipcbiAqIEhvdyBtdWNoIG9mIGEgc291cmNlIGxpbmUgdG8gc2hvdyBiZW5lYXRoIGEgc3RhY2sgZnJhbWUuIExvbmcgZW5vdWdoIHRvIHJlYWQgYVxuICogc3RhdGVtZW50LCBzaG9ydCBlbm91Z2ggdGhhdCBvbmUgbWluaWZpZWQgbGluZSBmcm9tIHRoZSBidW5kbGVkIENMSSBjYW5ub3RcbiAqIGZsb29kIHRoZSByZXBvcnQuXG4gKi9cbmNvbnN0IFNPVVJDRV9TTklQUEVUX0NIQVJTID0gMjAwO1xuXG4vKipcbiAqIE9uZSBzdGFjayBmcmFtZTogdGhlIGZ1bmN0aW9uIG5hbWUsIHBsdXMgdGhlIGZpbGUsIGxpbmUgYW5kIGNvbHVtbiwgdXNlZCBib3RoIHRvXG4gKiBwcmludCB0aGUgbG9jYXRpb24gYW5kIHRvIHJlYWQgYmFjayB0aGUgbGluZSBvZiBjb2RlLlxuICpcbiAqIFRoZSBjb2x1bW4gaXMgbG9hZC1iZWFyaW5nLiBJbiB0aGUgcHVibGlzaGVkIENMSSBldmVyeSBmcmFtZSBwb2ludHMgaW50byBvbmVcbiAqIGJ1bmRsZWQsIHdoaXRlc3BhY2UtbWluaWZpZWQgZmlsZSwgd2hlcmUgdGhlIGxpbmUgbnVtYmVyIGJhcmVseSBkaXN0aW5ndWlzaGVzXG4gKiBvbmUgZnJhbWUgZnJvbSBhbm90aGVyLlxuICovXG5pbnRlcmZhY2UgU291cmNlRnJhbWUge1xuICByZWFkb25seSBmdW5jOiBzdHJpbmc7XG4gIHJlYWRvbmx5IGZpbGU6IHN0cmluZztcbiAgcmVhZG9ubHkgbGluZTogbnVtYmVyO1xuICByZWFkb25seSBjb2x1bW46IG51bWJlcjtcbn1cblxuLyoqXG4gKiBBIHJlc291cmNlIHdlIGFyZSB3YXRjaGluZzogYSB3ZWFrIHJlZmVyZW5jZSB0byB0aGUgaGFuZGxlLCBzbyB0cmFja2luZyBkb2VzIG5vdFxuICogaXRzZWxmIGtlZXAgaXQgYWxpdmUsIGFuZCB0aGUgc3RhY2sgb2Ygd2hlcmUgaXQgd2FzIGNyZWF0ZWQuXG4gKi9cbmludGVyZmFjZSBXYXRjaGVkUmVzb3VyY2Uge1xuICByZWFkb25seSB0eXBlOiBzdHJpbmc7XG4gIHJlYWRvbmx5IGhhbmRsZVJlZjogV2Vha1JlZjxUcmFja2VkSGFuZGxlPjtcbiAgcmVhZG9ubHkgY3JlYXRpb25TdGFjazogU291cmNlRnJhbWVbXTtcbn1cblxuLyoqXG4gKiBUaGUgcGFydHMgb2YgYSBsaWJ1diBoYW5kbGUgd2UgaW50ZXJyb2dhdGUuIEJvdGggYXJlIG9wdGlvbmFsIGJlY2F1c2UgbmVpdGhlciBpc1xuICogZ3VhcmFudGVlZDogYGhhc1JlZmAgZXhpc3RzIG9ubHkgb24gYEhhbmRsZVdyYXBgLWJhY2tlZCB0eXBlcywgYW5kIGBfZGVzdHJveWVkYFxuICogaXMgYSBOb2RlIGludGVybmFsIHRoYXQgb25seSB0aW1lcnMgc2V0LlxuICovXG5pbnRlcmZhY2UgVHJhY2tlZEhhbmRsZSB7XG4gIF9kZXN0cm95ZWQ/OiBib29sZWFuO1xuICBoYXNSZWY/KCk6IGJvb2xlYW47XG59XG5cbi8qKlxuICogVHJhY2tzIGFzeW5jIHJlc291cmNlcyB2aWEgYXN5bmNfaG9va3MgYW5kLCBvbiBkZW1hbmQsIHJlcG9ydHMgdGhlIG9uZXMgc3RpbGxcbiAqIGtlZXBpbmcgdGhlIGV2ZW50IGxvb3AgYWxpdmUsIHdpdGggd2hlcmUgdGhleSB3ZXJlIGNyZWF0ZWQuXG4gKlxuICogTm90aGluZyBpcyB0cmFja2VkIHVudGlsIGBzdGFydCgpYCBydW5zLiBOb3QgZXhwb3J0ZWQgYXMgYSB2YWx1ZSwgc28gdGhlIG9ubHlcbiAqIHdheSB0byBnZXQgb25lIGlzIGB0cmFja0xlYWtlZEhhbmRsZXMoKWAsIHdoaWNoIHJldHVybnMgYSBzdGFydGVkIHRyYWNrZXIuXG4gKi9cbmNsYXNzIExlYWtlZEhhbmRsZVRyYWNrZXIge1xuICBwcml2YXRlIHJlYWRvbmx5IHdhdGNoZWQgPSBuZXcgTWFwPG51bWJlciwgV2F0Y2hlZFJlc291cmNlPigpO1xuXG4gIC8qKlxuICAgKiBXaGV0aGVyIHdlIHN0b3BwZWQgcmVjb3JkaW5nIGJlY2F1c2Uge0BsaW5rIE1BWF9UUkFDS0VEX0hBTkRMRVN9IHdhcyByZWFjaGVkLlxuICAgKiBSZXBvcnRlZCwgc28gYSBzaG9ydCBsaXN0IGlzIG5ldmVyIG1pc3Rha2VuIGZvciBhIGNvbXBsZXRlIG9uZS5cbiAgICovXG4gIHByaXZhdGUgYXRDYXBhY2l0eSA9IGZhbHNlO1xuXG4gIHByaXZhdGUgcmVhZG9ubHkgaG9vayA9IGNyZWF0ZUhvb2soe1xuICAgIGluaXQ6IChhc3luY0lkLCB0eXBlLCBfdHJpZ2dlckFzeW5jSWQsIHJlc291cmNlKSA9PiB7XG4gICAgICBpZiAoISh0eXBlIGluIFRSQUNLRURfVFlQRVMpKSB7XG4gICAgICAgIHJldHVybjtcbiAgICAgIH1cbiAgICAgIGlmICh0aGlzLndhdGNoZWQuc2l6ZSA+PSBNQVhfVFJBQ0tFRF9IQU5ETEVTKSB7XG4gICAgICAgIHRoaXMuYXRDYXBhY2l0eSA9IHRydWU7XG4gICAgICAgIHJldHVybjtcbiAgICAgIH1cbiAgICAgIC8vIEFuIGV4Y2VwdGlvbiB0aHJvd24gZnJvbSBhbiBhc3luY19ob29rcyBjYWxsYmFjayBpcyBub3QgY2F0Y2hhYmxlIGJ5IHRoZVxuICAgICAgLy8gYXBwbGljYXRpb24uIE5vZGUgcHJpbnRzIFwiRXJyb3IgaW4gQXN5bmNIb29rIGNhbGxiYWNrXCIgYW5kIGFib3J0cyB0aGVcbiAgICAgIC8vIHByb2Nlc3MsIHNvIGFueXRoaW5nIHRoYXQgZ29lcyB3cm9uZyBoZXJlIG11c3QgY29zdCBubyBtb3JlIHRoYW4gb25lXG4gICAgICAvLyBoYW5kbGUncyBwcm92ZW5hbmNlLlxuICAgICAgdHJ5IHtcbiAgICAgICAgdGhpcy53YXRjaGVkLnNldChhc3luY0lkLCB7XG4gICAgICAgICAgdHlwZSxcbiAgICAgICAgICBoYW5kbGVSZWY6IG5ldyBXZWFrUmVmKHJlc291cmNlIGFzIFRyYWNrZWRIYW5kbGUpLFxuICAgICAgICAgIGNyZWF0aW9uU3RhY2s6IGNhcHR1cmVDcmVhdGlvblN0YWNrKCksXG4gICAgICAgIH0pO1xuICAgICAgfSBjYXRjaCB7XG4gICAgICAgIHRoaXMud2F0Y2hlZC5kZWxldGUoYXN5bmNJZCk7XG4gICAgICB9XG4gICAgfSxcbiAgICBkZXN0cm95OiAoYXN5bmNJZCkgPT4ge1xuICAgICAgdGhpcy53YXRjaGVkLmRlbGV0ZShhc3luY0lkKTtcbiAgICB9LFxuICB9KTtcblxuICAvKipcbiAgICogQmVnaW4gd2F0Y2hpbmcgYXN5bmMgcmVzb3VyY2VzLiBNdXN0IHJ1biBiZWZvcmUgdGhlIHJlc291cmNlcyB3ZSBjYXJlIGFib3V0XG4gICAqIGFyZSBjcmVhdGVkLCBhbmQgb25seSB3aGVuIHRoZSB1c2VyIG9wdGVkIGluLCBzaW5jZSB0aGUgaG9vayBhZGRzIGEgc21hbGxcbiAgICogcGVyLXJlc291cmNlIGNvc3QuXG4gICAqL1xuICBwdWJsaWMgc3RhcnQoKTogdm9pZCB7XG4gICAgdGhpcy5ob29rLmVuYWJsZSgpO1xuICB9XG5cbiAgLyoqXG4gICAqIFN0b3Agd2F0Y2hpbmcgYW5kIGRpc2NhcmQgYWxsIHRyYWNrZWQgc3RhdGUuXG4gICAqL1xuICBwdWJsaWMgc3RvcCgpOiB2b2lkIHtcbiAgICB0aGlzLmhvb2suZGlzYWJsZSgpO1xuICAgIHRoaXMud2F0Y2hlZC5jbGVhcigpO1xuICB9XG5cbiAgLyoqXG4gICAqIFJlcG9ydCB0aGUgbGVha2VkIGhhbmRsZXMgb25jZSB0aGUgZ3JhY2UgcGVyaW9kIGhhcyBwYXNzZWQuIFRoZSB0aW1lciBpc1xuICAgKiB1bnJlZidkLCBzbyBpdCBuZXZlciBmaXJlcyBpZiB0aGUgcHJvY2VzcyBleGl0cyBjbGVhbmx5IHdpdGhpbiB0aGUgd2luZG93IGFuZFxuICAgKiBuZXZlciBob2xkcyB0aGUgcHJvY2VzcyBvcGVuIGl0c2VsZi5cbiAgICovXG4gIHB1YmxpYyBzY2hlZHVsZVJlcG9ydChpb0hlbHBlcjogSW9IZWxwZXIpOiB2b2lkIHtcbiAgICBzZXRUaW1lb3V0KCgpID0+IHtcbiAgICAgIC8vIFJ1bnMgZGV0YWNoZWQgZnJvbSB0aGUgQ0xJJ3MgZXJyb3IgaGFuZGxpbmc6IHRoZSBjb21tYW5kIGhhcyBmaW5pc2hlZCBhbmRcbiAgICAgIC8vIHRoZXJlIGlzIG5vIG9uZSBsZWZ0IHRvIGhhbmQgYSByZWplY3Rpb24gdG8uIEEgZGlhZ25vc3RpYyBtdXN0IG5vdCBjcmFzaFxuICAgICAgLy8gdGhlIHByb2Nlc3MgaXQgaXMgZGlhZ25vc2luZy5cbiAgICAgIHRoaXMucmVwb3J0KGlvSGVscGVyKS5jYXRjaCgoKSA9PiB1bmRlZmluZWQpO1xuICAgIH0sIEhBTkRMRV9EVU1QX0dSQUNFX01TKS51bnJlZigpO1xuICB9XG5cbiAgLyoqXG4gICAqIFJlcG9ydCBldmVyeSByZXNvdXJjZSBzdGlsbCBob2xkaW5nIHRoZSBldmVudCBsb29wIG9wZW4sIGVhY2ggd2l0aCB0aGUgc291cmNlXG4gICAqIGxvY2F0aW9uIHdoZXJlIGl0IHdhcyBjcmVhdGVkLiBDYWxsIGF0IHRoZSB2ZXJ5IGVuZCBvZiBleGVjdXRpb24sIGJ5IHdoaWNoXG4gICAqIHBvaW50IG9ubHkgZ2VudWluZWx5IGxlYWtlZCBoYW5kbGVzIHNob3VsZCByZW1haW4uXG4gICAqXG4gICAqIEVtaXR0ZWQgYXMgb25lIG1lc3NhZ2UsIHNvIHVucmVsYXRlZCBvdXRwdXQgY2Fubm90IGludGVybGVhdmUgaW50byB0aGUgbWlkZGxlXG4gICAqIG9mIGEgc3RhY2sgdHJhY2UsIGFuZCBhdCBERUJVRywgd2hpY2ggaXMgdGhlIGxldmVsIGAtLWRlYnVnLWNsaWAgcmFpc2VzIHRoZVxuICAgKiBDTEkgdG8uXG4gICAqL1xuICBwdWJsaWMgYXN5bmMgcmVwb3J0KGlvSGVscGVyOiBJb0hlbHBlcik6IFByb21pc2U8dm9pZD4ge1xuICAgIHRoaXMuaG9vay5kaXNhYmxlKCk7XG5cbiAgICBjb25zdCBsZWFrcyA9IFsuLi50aGlzLndhdGNoZWQudmFsdWVzKCldLmZpbHRlcigocikgPT4ge1xuICAgICAgY29uc3QgaGFuZGxlID0gci5oYW5kbGVSZWYuZGVyZWYoKTtcbiAgICAgIC8vIEFscmVhZHkgZ2FyYmFnZSBjb2xsZWN0ZWQsIHNvIG5vIGxvbmdlciBrZWVwaW5nIHRoZSBsb29wIGFsaXZlLlxuICAgICAgaWYgKGhhbmRsZSA9PT0gdW5kZWZpbmVkKSB7XG4gICAgICAgIHJldHVybiBmYWxzZTtcbiAgICAgIH1cbiAgICAgIC8vIEEgZmlyZWQgb25lLXNob3QgdGltZXIgYW5zd2VycyBgaGFzUmVmKCkgPT09IHRydWVgIGZvciB0aGUgcmVzdCBvZiB0aGVcbiAgICAgIC8vIHByb2Nlc3MgYW5kIG9ubHkgbWFya3MgaXRzZWxmIGBfZGVzdHJveWVkYC4gVGhlIGBkZXN0cm95YCBob29rIGRvZXMgZHJvcFxuICAgICAgLy8gaXQgZnJvbSBgd2F0Y2hlZGAsIGJ1dCBhIHRpY2sgbGF0ZXIgdGhhbiB0aGlzLCBhbmQgdGltZXJzIGFyZSB0aGUgQ0xJJ3NcbiAgICAgIC8vIG1vc3QgY29tbW9uIHJlc291cmNlLlxuICAgICAgaWYgKGhhbmRsZS5fZGVzdHJveWVkID09PSB0cnVlKSB7XG4gICAgICAgIHJldHVybiBmYWxzZTtcbiAgICAgIH1cbiAgICAgIC8vIEFzayB0aGUgaGFuZGxlIGl0c2VsZiwgYW5kIGJlbGlldmUgb25seSBhIGNsZWFyIHllcy4gQSBjbG9zZWQgc29ja2V0XG4gICAgICAvLyBhbnN3ZXJzIGZhbHNlLCBhbmQgYW55dGhpbmcgdGhhdCBjYW5ub3QgYW5zd2VyIGlzIG5vdCBldmlkZW5jZSBvZiBhIGxlYWsuXG4gICAgICByZXR1cm4gdHlwZW9mIGhhbmRsZS5oYXNSZWYgPT09ICdmdW5jdGlvbicgJiYgaGFuZGxlLmhhc1JlZigpO1xuICAgIH0pO1xuICAgIHRoaXMud2F0Y2hlZC5jbGVhcigpO1xuXG4gICAgYXdhaXQgaW9IZWxwZXIuZGVmYXVsdHMuZGVidWcodGhpcy5yZW5kZXJSZXBvcnQobGVha3MpLmpvaW4oJ1xcbicpKTtcbiAgfVxuXG4gIHByaXZhdGUgcmVuZGVyUmVwb3J0KGxlYWtzOiBXYXRjaGVkUmVzb3VyY2VbXSk6IHN0cmluZ1tdIHtcbiAgICBpZiAobGVha3MubGVuZ3RoID09PSAwKSB7XG4gICAgICAvLyBUaGlzIHJlcG9ydCBvbmx5IHJ1bnMgYmVjYXVzZSB0aGUgcHJvY2VzcyB3YXMgc3RpbGwgYWxpdmUsIHNvIHNvbWV0aGluZ1xuICAgICAgLy8gaXMgaG9sZGluZyB0aGUgbG9vcCBvcGVuLiBGaW5kaW5nIG5vdGhpbmcgbWVhbnMgdGhlIGNhdXNlIGlzIG91dHNpZGUgd2hhdFxuICAgICAgLy8gd2UgdHJhY2ssIHNvIGhhbmQgb3ZlciB3aGF0IE5vZGUgaXRzZWxmIHNlZXMuXG4gICAgICByZXR1cm4gW1xuICAgICAgICAnVGhlIENMSSBwcm9jZXNzIGlzIHN0aWxsIGFsaXZlLCBidXQgbm8gdHJhY2tlZCBoYW5kbGUgZXhwbGFpbnMgaXQuJyxcbiAgICAgICAgYE5vZGUgcmVwb3J0cyB0aGVzZSByZXNvdXJjZXMgc3RpbGwgYWN0aXZlOiAke2FjdGl2ZVJlc291cmNlU3VtbWFyeSgpfWAsXG4gICAgICAgICdUaGUgY2F1c2UgbWF5IGJlIGEgaGFuZGxlIG9wZW5lZCBiZWZvcmUgdHJhY2tpbmcgc3RhcnRlZCwgb3IgYSB0eXBlIHRoaXMgYnVpbGQgZG9lcyBub3QgdHJhY2suJyxcbiAgICAgIF07XG4gICAgfVxuXG4gICAgY29uc3QgbGluZXMgPSBbYCR7bGVha3MubGVuZ3RofSAke2xlYWtzLmxlbmd0aCA9PT0gMSA/ICdoYW5kbGUnIDogJ2hhbmRsZXMnfSBzdGlsbCBrZWVwaW5nIHRoZSBDTEkgcHJvY2VzcyBhbGl2ZTpgXTtcbiAgICBpZiAodGhpcy5hdENhcGFjaXR5KSB7XG4gICAgICBsaW5lcy5wdXNoKGBTdG9wcGVkIHJlY29yZGluZyBhZnRlciAke01BWF9UUkFDS0VEX0hBTkRMRVN9IGhhbmRsZXMsIHNvIHRoaXMgbGlzdCBtYXkgYmUgaW5jb21wbGV0ZS5gKTtcbiAgICB9XG5cbiAgICAvLyBPbmUgcmVwb3J0IHBhc3MgY2FuIHJldmlzaXQgdGhlIHNhbWUgZmlsZSBmb3IgZXZlcnkgZnJhbWUgb2YgZXZlcnkgaGFuZGxlLFxuICAgIC8vIGFuZCBpbiB0aGUgcHVibGlzaGVkIENMSSB0aGF0IGZpbGUgaXMgdGhlIGVudGlyZSBidW5kbGUuXG4gICAgY29uc3Qgc291cmNlcyA9IG5ldyBTb3VyY2VDYWNoZSgpO1xuICAgIGZvciAoY29uc3QgbGVhayBvZiBsZWFrcykge1xuICAgICAgbGluZXMucHVzaCguLi5kZXNjcmliZUxlYWsobGVhaywgc291cmNlcykpO1xuICAgIH1cblxuICAgIGlmIChzb3VyY2VzLnNhd01pbmlmaWVkTGluZSkge1xuICAgICAgLy8gVGhlIHB1Ymxpc2hlZCBDTEkgaXMgb25lIGJ1bmRsZWQsIG1pbmlmaWVkIGZpbGUgd2l0aCBubyBzb3VyY2UgbWFwLCBzbyBhXG4gICAgICAvLyBsb2NhdGlvbiBpcyBhIHBvc2l0aW9uIGluIHRoZSBidW5kbGUgYW5kIHRoZSBzbmlwcGV0IGJlbmVhdGggaXQgaXMgdGhlXG4gICAgICAvLyBvbmx5IHBhcnQgdGhhdCBtYXBzIGJhY2sgdG8gc29tZXRoaW5nIHJlY29nbmlzYWJsZS5cbiAgICAgIGxpbmVzLnB1c2goJycpO1xuICAgICAgbGluZXMucHVzaCgnTG9jYXRpb25zIGFib3ZlIGFyZSBwb3NpdGlvbnMgaW4gdGhlIGJ1bmRsZWQgQ0xJLCBub3QgaW4gdGhlIG9yaWdpbmFsIHNvdXJjZSBmaWxlcy4nKTtcbiAgICB9XG5cbiAgICByZXR1cm4gbGluZXM7XG4gIH1cbn1cblxuZnVuY3Rpb24gZGVzY3JpYmVMZWFrKGxlYWs6IFdhdGNoZWRSZXNvdXJjZSwgc291cmNlczogU291cmNlQ2FjaGUpOiBzdHJpbmdbXSB7XG4gIGNvbnN0IGZyYW1lcyA9IGFjdGlvbmFibGVGcmFtZXMobGVhay5jcmVhdGlvblN0YWNrKTtcbiAgY29uc3QgbGluZXMgPSBbJycsIGNoYWxrLmJvbGQoYCMgJHtsZWFrLnR5cGV9ICgke1RSQUNLRURfVFlQRVNbbGVhay50eXBlXX0pYCldO1xuXG4gIGlmIChmcmFtZXMubGVuZ3RoID09PSAwKSB7XG4gICAgbGluZXMucHVzaCgnICAob3BlbmVkIGVudGlyZWx5IGluc2lkZSBOb2RlIGludGVybmFscywgbm8gQ0xJIGZyYW1lcyB0byBzaG93KScpO1xuICAgIHJldHVybiBsaW5lcztcbiAgfVxuXG4gIC8vIEhlYWRsaW5lIHRoZSBmdW5jdGlvbiBvbmx5IHdoZW4gaXQgaGFzIGEgcmVhbCBuYW1lLiBTb2NrZXRzIG9mdGVuIG9wZW4gZnJvbVxuICAvLyBhbm9ueW1vdXMgaW50ZXJuYWwgY2FsbGJhY2tzLCB3aGVyZSB0aGUgbmFtZSBzYXlzIG5vdGhpbmcuXG4gIGNvbnN0IFtvcmlnaW5dID0gZnJhbWVzO1xuICBpZiAob3JpZ2luLmZ1bmMgJiYgb3JpZ2luLmZ1bmMgIT09ICc8YW5vbnltb3VzPicpIHtcbiAgICBsaW5lcy5wdXNoKGAgIGNyZWF0ZWQgaW4gJHtvcmlnaW4uZnVuY30oKWApO1xuICB9XG5cbiAgbGluZXMucHVzaCgnICBjYWxsIHN0YWNrOicpO1xuICBmb3IgKGNvbnN0IGZyYW1lIG9mIGZyYW1lcykge1xuICAgIC8vIFRoZSBsb2NhdGlvbiBpcyB3aGF0IGlkZW50aWZpZXMgdGhlIGZyYW1lLCBzbyBwcmludCBpdCBldmVuIHdoZW4gdGhlIGZpbGVcbiAgICAvLyBjYW5ub3QgYmUgcmVhZC5cbiAgICBsaW5lcy5wdXNoKGAgICAgJHtmcmFtZS5mdW5jfSAoJHtmcmFtZS5maWxlfToke2ZyYW1lLmxpbmV9OiR7ZnJhbWUuY29sdW1ufSlgKTtcbiAgICBjb25zdCBzbmlwcGV0ID0gc291cmNlcy5zbmlwcGV0QXQoZnJhbWUpO1xuICAgIGlmIChzbmlwcGV0KSB7XG4gICAgICBsaW5lcy5wdXNoKGAgICAgICAke2NoYWxrLmRpbShzbmlwcGV0KX1gKTtcbiAgICB9XG4gIH1cbiAgcmV0dXJuIGxpbmVzO1xufVxuXG4vKipcbiAqIENhcHR1cmUgdGhlIGNhbGwgc2l0ZSBvZiB0aGUgY3VycmVudCBhc3luYyByZXNvdXJjZSBhcyBzdHJ1Y3R1cmVkIGZyYW1lcy5cbiAqXG4gKiBJbnN0YWxscyBhIHN0cnVjdHVyZWQgYHByZXBhcmVTdGFja1RyYWNlYCwgY2FwdHVyZXMgd2l0aCB0aGlzIGZ1bmN0aW9uIGFzIHRoZVxuICogY3V0LW9mZiwgdGhlbiByZXN0b3JlcyB0aGUgcHJldmlvdXMgZm9ybWF0dGVyIHNvIG90aGVyIHN0YWNrcyBhcmUgdW5hZmZlY3RlZC5cbiAqXG4gKiBUaGUgY3V0LW9mZiByZW1vdmVzIHRoaXMgZnVuY3Rpb24gYW5kIGV2ZXJ5dGhpbmcgYWJvdmUgaXQsIGJ1dCB0aGUgY2FsbGVyIGlzIHRoZVxuICogdHJhY2tlcidzIG93biBgaW5pdGAgaG9vaywgd2hpY2ggc2l0cyBqdXN0IGJlbG93IGFuZCB3b3VsZCBvdGhlcndpc2UgYmUgdGhlIHRvcFxuICogZnJhbWUuIFdlIGRyb3AgdGhhdCBmcmFtZSBieSBwb3NpdGlvbiByYXRoZXIgdGhhbiBieSBmaWxlbmFtZSwgc2luY2UgYWZ0ZXJcbiAqIGJ1bmRsaW5nIGV2ZXJ5IGZyYW1lIHNoYXJlcyBvbmUgZmlsZW5hbWUuXG4gKi9cbmZ1bmN0aW9uIGNhcHR1cmVDcmVhdGlvblN0YWNrKCk6IFNvdXJjZUZyYW1lW10ge1xuICBjb25zdCBjYXJyaWVyOiB7IHN0YWNrPzogU291cmNlRnJhbWVbXSB9ID0ge307XG5cbiAgLy8gYEVycm9yLnByZXBhcmVTdGFja1RyYWNlYCBpcyBhIFY4IGZvcm1hdHRpbmcgaG9vayB3ZSBzYXZlIGFuZCByZXN0b3JlLCBub3QgYVxuICAvLyBtZXRob2Qgd2UgaW52b2tlLCBzbyB0aGUgdW5ib3VuZC1tZXRob2QgY29uY2VybiBkb2VzIG5vdCBhcHBseS5cbiAgLy8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIEB0eXBlc2NyaXB0LWVzbGludC91bmJvdW5kLW1ldGhvZFxuICBjb25zdCBwcmV2aW91cyA9IEVycm9yLnByZXBhcmVTdGFja1RyYWNlO1xuICBjb25zdCBwcmV2aW91c0xpbWl0ID0gRXJyb3Iuc3RhY2tUcmFjZUxpbWl0O1xuICBFcnJvci5zdGFja1RyYWNlTGltaXQgPSBTVEFDS19DQVBUVVJFX0RFUFRIO1xuICBFcnJvci5wcmVwYXJlU3RhY2tUcmFjZSA9IChfZXJyb3IsIGNhbGxTaXRlcykgPT4gY2FsbFNpdGVzLm1hcCgoc2l0ZSkgPT4ge1xuICAgIGNvbnN0IGZpbGUgPSBzaXRlLmdldEZpbGVOYW1lKCkgPz8gJyc7XG4gICAgcmV0dXJuIHtcbiAgICAgIGZ1bmM6IHNpdGUuZ2V0RnVuY3Rpb25OYW1lKCkgPz8gJzxhbm9ueW1vdXM+JyxcbiAgICAgIGZpbGU6IGZpbGUuc3RhcnRzV2l0aCgnZmlsZTovLycpID8gZmlsZVVSTFRvUGF0aChmaWxlKSA6IGZpbGUsXG4gICAgICBsaW5lOiBzaXRlLmdldExpbmVOdW1iZXIoKSA/PyAwLFxuICAgICAgY29sdW1uOiBzaXRlLmdldENvbHVtbk51bWJlcigpID8/IDAsXG4gICAgfTtcbiAgfSk7XG4gIHRyeSB7XG4gICAgRXJyb3IuY2FwdHVyZVN0YWNrVHJhY2UoY2FycmllciwgY2FwdHVyZUNyZWF0aW9uU3RhY2spO1xuICAgIC8vIERyb3AgdGhlIGBpbml0YCBob29rIGZyYW1lLCBhbHdheXMgdGhlIHRvcCBvbmUsIHNvIHRoZSByZXBvcnQgcG9pbnRzIGF0IHRoZVxuICAgIC8vIGNvZGUgdGhhdCBjcmVhdGVkIHRoZSByZXNvdXJjZSByYXRoZXIgdGhhbiBhdCB0aGlzIHRyYWNrZXIuXG4gICAgcmV0dXJuIGNhcnJpZXIuc3RhY2s/LnNsaWNlKDEpID8/IFtdO1xuICB9IGZpbmFsbHkge1xuICAgIEVycm9yLnByZXBhcmVTdGFja1RyYWNlID0gcHJldmlvdXM7XG4gICAgRXJyb3Iuc3RhY2tUcmFjZUxpbWl0ID0gcHJldmlvdXNMaW1pdDtcbiAgfVxufVxuXG4vKipcbiAqIFdoYXQgTm9kZSBpdHNlbGYgcmVwb3J0cyBhcyBzdGlsbCBob2xkaW5nIHRoZSBsb29wIG9wZW4sIGFzIGAyeCBUaW1lb3V0LCAxeCBUQ1BgLlxuICpcbiAqIFRoaXMgaXMgdGhlIGdyb3VuZCB0cnV0aCBvdXIgb3duIHRyYWNraW5nIHRyaWVzIHRvIGV4cGxhaW4sIHNvIHByaW50aW5nIGl0IHRlbGxzXG4gKiB0aGUgcmVhZGVyIHdoZXRoZXIgd2UgbWlzc2VkIHNvbWV0aGluZyBvciB0aGUgaGFuZyBpcyBzb21ld2hlcmUgZWxzZS5cbiAqL1xuZnVuY3Rpb24gYWN0aXZlUmVzb3VyY2VTdW1tYXJ5KCk6IHN0cmluZyB7XG4gIGNvbnN0IGNvdW50cyA9IG5ldyBNYXA8c3RyaW5nLCBudW1iZXI+KCk7XG4gIGZvciAoY29uc3QgdHlwZSBvZiBwcm9jZXNzLmdldEFjdGl2ZVJlc291cmNlc0luZm8oKSkge1xuICAgIGNvdW50cy5zZXQodHlwZSwgKGNvdW50cy5nZXQodHlwZSkgPz8gMCkgKyAxKTtcbiAgfVxuICBpZiAoY291bnRzLnNpemUgPT09IDApIHtcbiAgICByZXR1cm4gJyhub25lKSc7XG4gIH1cbiAgcmV0dXJuIFsuLi5jb3VudHNdLm1hcCgoW3R5cGUsIGNvdW50XSkgPT4gYCR7Y291bnR9eCAke3R5cGV9YCkuam9pbignLCAnKTtcbn1cblxuLyoqXG4gKiBLZWVwIG9ubHkgZnJhbWVzIHRoZSB1c2VyIGNhbiBhY3Qgb24gYnkgZHJvcHBpbmcgTm9kZSBpbnRlcm5hbHMuIE91ciBvd24gYGluaXRgXG4gKiBmcmFtZSBpcyBhbHJlYWR5IHJlbW92ZWQgYXQgY2FwdHVyZSB0aW1lLCBzZWUge0BsaW5rIGNhcHR1cmVDcmVhdGlvblN0YWNrfS5cbiAqL1xuZnVuY3Rpb24gYWN0aW9uYWJsZUZyYW1lcyhmcmFtZXM6IFNvdXJjZUZyYW1lW10pOiBTb3VyY2VGcmFtZVtdIHtcbiAgcmV0dXJuIGZyYW1lcy5maWx0ZXIoKGZyYW1lKSA9PiBmcmFtZS5maWxlICYmICFmcmFtZS5maWxlLnN0YXJ0c1dpdGgoJ25vZGU6JykpO1xufVxuXG4vKipcbiAqIFJlYWRzIHRoZSBsaW5lIG9mIGNvZGUgYmVoaW5kIGEgc3RhY2sgZnJhbWUsIGhvbGRpbmcgZWFjaCBmaWxlJ3MgbGluZXMgc28gYVxuICogcmVwb3J0IHBhc3MgcmVhZHMgYW55IGdpdmVuIGZpbGUgYXQgbW9zdCBvbmNlLlxuICpcbiAqIFRoZSBwdWJsaXNoZWQgQ0xJIGlzIG9uZSBsYXJnZSBidW5kbGUsIGFuZCBhIHNpbmdsZSByZXBvcnQgY2FuIGhvbGQgbWFueSBmcmFtZXNcbiAqIHBvaW50aW5nIGludG8gaXQuIFJlLXJlYWRpbmcgYW5kIHJlLXNwbGl0dGluZyBpdCBwZXIgZnJhbWUgc3RhbGxzIHRoZSByZXBvcnQsIGF0XG4gKiB0aGUgbW9tZW50IHRoZSB1c2VyIGlzIGFscmVhZHkgd2FpdGluZyBvbiBhIGh1bmcgQ0xJLlxuICovXG5jbGFzcyBTb3VyY2VDYWNoZSB7XG4gIC8qKlxuICAgKiBXaGV0aGVyIGFueSBsaW5lIHdlIHJlYWQgd2FzIGxvbmcgZW5vdWdoIHRvIGJlIG1pbmlmaWVkIHJhdGhlciB0aGFuXG4gICAqIGhhbmQtd3JpdHRlbi4gVXNlZCB0byB3YXJuIHRoYXQgdGhlIHByaW50ZWQgbG9jYXRpb25zIGFyZSBidW5kbGUtcmVsYXRpdmUuXG4gICAqL1xuICBwdWJsaWMgc2F3TWluaWZpZWRMaW5lID0gZmFsc2U7XG5cbiAgcHJpdmF0ZSByZWFkb25seSBmaWxlcyA9IG5ldyBNYXA8c3RyaW5nLCBzdHJpbmdbXSB8IHVuZGVmaW5lZD4oKTtcblxuICBwdWJsaWMgc25pcHBldEF0KGZyYW1lOiBTb3VyY2VGcmFtZSk6IHN0cmluZyB8IHVuZGVmaW5lZCB7XG4gICAgY29uc3QgbGluZSA9IHRoaXMubGluZXNPZihmcmFtZS5maWxlKT8uW2ZyYW1lLmxpbmUgLSAxXTtcbiAgICBpZiAobGluZSA9PT0gdW5kZWZpbmVkKSB7XG4gICAgICByZXR1cm4gdW5kZWZpbmVkO1xuICAgIH1cbiAgICBpZiAobGluZS5sZW5ndGggPD0gU09VUkNFX1NOSVBQRVRfQ0hBUlMpIHtcbiAgICAgIHJldHVybiBsaW5lLnRyaW0oKSB8fCB1bmRlZmluZWQ7XG4gICAgfVxuXG4gICAgLy8gVG9vIGxvbmcgdG8gYmUgc291cmNlIGFueW9uZSB3cm90ZSwgc28gc2hvdyBhIHdpbmRvdyBjZW50cmVkIG9uIHRoZSBjb2x1bW5cbiAgICAvLyByYXRoZXIgdGhhbiB0aGUgc3RhcnQgb2YgdGhlIGxpbmUuXG4gICAgdGhpcy5zYXdNaW5pZmllZExpbmUgPSB0cnVlO1xuICAgIGNvbnN0IHN0YXJ0ID0gTWF0aC5tYXgoMCwgZnJhbWUuY29sdW1uIC0gMSAtIE1hdGguZmxvb3IoU09VUkNFX1NOSVBQRVRfQ0hBUlMgLyAyKSk7XG4gICAgY29uc3QgZW5kID0gTWF0aC5taW4obGluZS5sZW5ndGgsIHN0YXJ0ICsgU09VUkNFX1NOSVBQRVRfQ0hBUlMpO1xuICAgIGNvbnN0IHdpbmRvdyA9IGxpbmUuc2xpY2Uoc3RhcnQsIGVuZCkudHJpbSgpO1xuICAgIHJldHVybiBgJHtzdGFydCA+IDAgPyAn4oCmJyA6ICcnfSR7d2luZG93fSR7ZW5kIDwgbGluZS5sZW5ndGggPyAn4oCmJyA6ICcnfWA7XG4gIH1cblxuICBwcml2YXRlIGxpbmVzT2YoZmlsZTogc3RyaW5nKTogc3RyaW5nW10gfCB1bmRlZmluZWQge1xuICAgIGlmICghdGhpcy5maWxlcy5oYXMoZmlsZSkpIHtcbiAgICAgIHRoaXMuZmlsZXMuc2V0KGZpbGUsIHJlYWRMaW5lcyhmaWxlKSk7XG4gICAgfVxuICAgIHJldHVybiB0aGlzLmZpbGVzLmdldChmaWxlKTtcbiAgfVxufVxuXG5mdW5jdGlvbiByZWFkTGluZXMoZmlsZTogc3RyaW5nKTogc3RyaW5nW10gfCB1bmRlZmluZWQge1xuICB0cnkge1xuICAgIHJldHVybiByZWFkRmlsZVN5bmMoZmlsZSwgJ3V0Zi04Jykuc3BsaXQoL1xccj9cXG4vKTtcbiAgfSBjYXRjaCB7XG4gICAgLy8gQnVuZGxlZCBhbmQgZXZhbCdkIGZyYW1lcyBoYXZlIGZpbGVuYW1lcyB0aGF0IGFyZSBub3Qgb24gZGlzay4gVGhlIGxvY2F0aW9uXG4gICAgLy8gaXMgc3RpbGwgcmVwb3J0ZWQgb24gaXRzIG93biwgc28gcmVwb3J0aW5nIGNvbnRpbnVlcyB3aXRob3V0IHRoZSBzbmlwcGV0LlxuICAgIHJldHVybiB1bmRlZmluZWQ7XG4gIH1cbn1cblxuLyoqXG4gKiBTdGFydCB3YXRjaGluZyBhc3luYyByZXNvdXJjZXMgYW5kIHJldHVybiB0aGUgdHJhY2tlciBkb2luZyBzby5cbiAqXG4gKiBDYWxsIGFzIGVhcmx5IGFzIHBvc3NpYmxlLCBhbmQgb25seSB3aGVuIHRoZSB1c2VyIGFza2VkIGZvciBpdC4gVGhlIHRyYWNrZXIgY2FuXG4gKiBvbmx5IHJlcG9ydCBvbiByZXNvdXJjZXMgY3JlYXRlZCBhZnRlciBpdCBzdGFydHMuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiB0cmFja0xlYWtlZEhhbmRsZXMoKTogTGVha2VkSGFuZGxlVHJhY2tlciB7XG4gIGNvbnN0IHRyYWNrZXIgPSBuZXcgTGVha2VkSGFuZGxlVHJhY2tlcigpO1xuICB0cmFja2VyLnN0YXJ0KCk7XG4gIHJldHVybiB0cmFja2VyO1xufVxuXG5leHBvcnQgdHlwZSB7IExlYWtlZEhhbmRsZVRyYWNrZXIgfTtcbiJdfQ==