Skip to content

Commit

Permalink
Make it clear when arguments are modified
Browse files Browse the repository at this point in the history
  • Loading branch information
hansott committed Feb 27, 2024
1 parent 3d6e825 commit 8433c9a
Show file tree
Hide file tree
Showing 7 changed files with 88 additions and 20 deletions.
2 changes: 1 addition & 1 deletion library/src/agent/Wrapper.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,5 @@ t.test("method throws error if name is empty", async (t) => {
.withVersion("^1.0.0")
.subject((exports) => exports);

t.throws(() => subject.method("", () => {}));
t.throws(() => subject.inspect("", () => {}));
});
46 changes: 40 additions & 6 deletions library/src/agent/Wrapper.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,33 @@
type Interceptor = (args: unknown[], subject: unknown) => void | unknown[];
type SafeInterceptor = (args: unknown[], subject: unknown) => void;

export class Method {
type ModifyingArgumentsInterceptor = (
args: unknown[],
subject: unknown
) => unknown[];

export class SafeMethod {
constructor(
private readonly name: string,
private readonly interceptor: SafeInterceptor
) {
if (!this.name) {
throw new Error("Method name is required");
}
}

getName() {
return this.name;
}

getInterceptor() {
return this.interceptor;
}
}

export class DangerousMethod {
constructor(
private readonly name: string,
private readonly interceptor: Interceptor
private readonly interceptor: ModifyingArgumentsInterceptor
) {
if (!this.name) {
throw new Error("Method name is required");
Expand All @@ -20,12 +44,22 @@ export class Method {
}

class Selector {
private methods: Method[] = [];
private methods: (SafeMethod | DangerousMethod)[] = [];

constructor(private readonly selector: (exports: unknown) => unknown) {}

method(name: string, interceptor: Interceptor) {
const method = new Method(name, interceptor);
inspect(name: string, interceptor: SafeInterceptor) {
const method = new SafeMethod(name, interceptor);
this.methods.push(method);

return method;
}

dangerouslyModifyArguments(
name: string,
interceptor: ModifyingArgumentsInterceptor
) {
const method = new DangerousMethod(name, interceptor);
this.methods.push(method);

return method;
Expand Down
4 changes: 2 additions & 2 deletions library/src/agent/applyHooks.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ t.test("it ignores unknown selectors", async (t) => {
.package("shimmer")
.withVersion("^1.0.0")
.subject((exports) => exports.doesNotExist)
.method("method", () => {});
.inspect("method", () => {});

t.same(applyHooks(hooks), {
shimmer: {
Expand All @@ -41,7 +41,7 @@ t.test("it ignores if version is not supported", async (t) => {
.package("shimmer")
.withVersion("^2.0.0")
.subject((exports) => exports)
.method("method", () => {});
.inspect("method", () => {});

t.same(applyHooks(hooks), {});
});
39 changes: 34 additions & 5 deletions library/src/agent/applyHooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Hook } from "require-in-the-middle";
import { wrap } from "shimmer";
import { getPackageVersion } from "../helpers/getPackageVersion";
import { satisfiesVersion } from "../helpers/satisfiesVersion";
import { Hooks, Method } from "./Wrapper";
import { DangerousMethod, Hooks, SafeMethod } from "./Wrapper";

/**
* Hooks allows you to register packages and then wrap specific methods on
Expand Down Expand Up @@ -49,9 +49,13 @@ export function applyHooks(hooks: Hooks) {
return;
}

selector
.getMethods()
.forEach((method) => interceptMethodCalls(subject, method));
selector.getMethods().forEach((method) => {
if (method instanceof SafeMethod) {
safelyWrapMethodCalls(subject, method);
} else {
dangerouslyWrapMethodCalls(subject, method);
}
});
});

return exports;
Expand All @@ -61,7 +65,32 @@ export function applyHooks(hooks: Hooks) {
return wrapped;
}

function interceptMethodCalls(subject: unknown, method: Method) {
/**
* Wraps a method call with a safe interceptor, which doesn't modify the arguments of the method call.
*/
function safelyWrapMethodCalls(subject: unknown, method: SafeMethod) {
// @ts-expect-error We don't now the type of the subject
wrap(subject, method.getName(), function wrap(original: Function) {
return function wrap() {
// eslint-disable-next-line prefer-rest-params
const args = Array.from(arguments);
// @ts-expect-error We don't now the type of this
method.getInterceptor()(args, this);

return original.apply(
// @ts-expect-error We don't now the type of this
this,
// eslint-disable-next-line prefer-rest-params
arguments
);
};
});
}

/**
* Wraps a method call with a dangerous interceptor, which modifies the arguments of the method call.
*/
function dangerouslyWrapMethodCalls(subject: unknown, method: DangerousMethod) {
// @ts-expect-error We don't now the type of the subject
wrap(subject, method.getName(), function wrap(original: Function) {
return function wrap() {
Expand Down
4 changes: 2 additions & 2 deletions library/src/sinks/MongoDB.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,12 +168,12 @@ export class MongoDB implements Wrapper {
);

OPERATIONS_WITH_FILTER.forEach((operation) => {
collection.method(operation, (args, collection) =>
collection.inspect(operation, (args, collection) =>
this.inspectOperation(operation, args, collection as Collection)
);
});

collection.method("bulkWrite", (args, collection) =>
collection.inspect("bulkWrite", (args, collection) =>
this.inspectBulkWrite(args, collection as Collection)
);
}
Expand Down
4 changes: 2 additions & 2 deletions library/src/sinks/Postgres.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ export class Postgres implements Wrapper {
const pg = hooks.package("pg").withVersion("^7.0.0 || ^8.0.0");

const client = pg.subject((exports) => exports.Client.prototype);
client.method("query", (args) => this.inspectQuery(args));
client.inspect("query", (args) => this.inspectQuery(args));

const pool = pg.subject((exports) => exports.Pool.prototype);
pool.method("query", (args) => this.inspectQuery(args));
pool.inspect("query", (args) => this.inspectQuery(args));
}
}
9 changes: 7 additions & 2 deletions library/src/sources/Express.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,13 @@ export class Express implements Wrapper {
const express = hooks.package("express").withVersion("^4.0.0");

const route = express.subject((exports) => exports.Route.prototype);
METHODS.map((method) => method.toLowerCase()).forEach((method) => {
route.method(method, (args) => this.addMiddleware(args));

const expressMethodNames = METHODS.map((method) => method.toLowerCase());

expressMethodNames.forEach((method) => {
route.dangerouslyModifyArguments(method, (args) =>
this.addMiddleware(args)
);
});
}
}

0 comments on commit 8433c9a

Please sign in to comment.