Skip to content

Commit

Permalink
Use setfenv externally for globals
Browse files Browse the repository at this point in the history
  • Loading branch information
richie0866 committed Jul 14, 2021
1 parent f141bfc commit 417ec79
Show file tree
Hide file tree
Showing 3 changed files with 41 additions and 32 deletions.
58 changes: 27 additions & 31 deletions src/core/VirtualScript.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,18 @@ function checkTraceback(module: VirtualScript) {

// If the loop reaches 'module' again, this is a cyclic reference.
if (module === currentModule) {
let traceback = module.getPath();
let traceback = module.getChunkName();

// Create a string to represent the dependency chain.
for (let i = 0; i < depth; i++) {
currentModule = currentlyLoading.get(currentModule)!;
traceback += `\n\t\t⇒ ${currentModule.getPath()}`;
traceback += `\n\t\t⇒ ${currentModule.getChunkName()}`;
}

throw `Requested module '${module.getPath()}' contains a cyclic reference` + `\n\tTraceback: ${traceback}`;
throw (
`Requested module '${module.getChunkName()}' contains a cyclic reference` +
`\n\tTraceback: ${traceback}`
);
}
}
}
Expand Down Expand Up @@ -62,17 +65,20 @@ export class VirtualScript {
public readonly root: string,

/** The contents of the file. */
public readonly rawSource = readfile(path),
public readonly source = readfile(path),
) {
this.scriptEnvironment = {
script: instance,
require: (obj: ModuleScript) =>
// The function's levels are as such:
// script (3) => require (2) => loadModule (1)
VirtualScript.loadModule(obj, this),
_PATH: path,
_ROOT: root,
};
this.scriptEnvironment = setmetatable(
{
script: instance,
require: (obj: ModuleScript) => VirtualScript.loadModule(obj, this),
_PATH: path,
_ROOT: root,
},
{
__index: getfenv(0) as never,
__metatable: "This metatable is locked",
},
);
VirtualScript.fromInstance.set(instance, this);
}

Expand Down Expand Up @@ -126,9 +132,9 @@ export class VirtualScript {
}

/**
* Returns a path to the module for debugging.
* Returns the chunk name for the module for traceback.
*/
public getPath() {
public getChunkName() {
const file = this.path.sub(this.root.size() + 1);
return `@${file} (${this.instance.GetFullName()})`;
}
Expand All @@ -144,13 +150,14 @@ export class VirtualScript {

/**
* Gets or creates a new executor function, and returns the executor function.
* The executor is automatically given a special global environment.
* @returns The executor function.
*/
public createExecutor(): Executor {
if (this.executor) return this.executor;
const [f, err] = loadstring(this.getSource(), `=${this.getPath()}`);
const [f, err] = loadstring(this.source, `=${this.getChunkName()}`);
assert(f, err);
return (this.executor = f);
return (this.executor = setfenv(f, this.scriptEnvironment));
}

/**
Expand All @@ -162,8 +169,8 @@ export class VirtualScript {

const result = this.createExecutor()(this.scriptEnvironment);

// Modules must return a value.
if (this.instance.IsA("ModuleScript")) assert(result, `Module '${this.getPath()}' did not return any value`);
if (this.instance.IsA("ModuleScript") && result === undefined)
throw `Module '${this.getChunkName()}' did not return any value`;

this.jobComplete = true;

Expand All @@ -177,18 +184,7 @@ export class VirtualScript {
public deferExecutor<T extends unknown = unknown>(): Promise<T> {
return Promise.defer<T>((resolve) => resolve(this.runExecutor<T>())).timeout(
30,
`Script ${this.getPath()} reached execution timeout! Try not to yield the main thread in LocalScripts.`,
);
}

/**
* Generates a source script that injects globals into the environment.
* @returns The source of the VirtualScript.
*/
private getSource(): string {
return (
"setfenv(1, setmetatable(..., { __index = getfenv(0), __metatable = 'This metatable is locked' }));" +
this.rawSource
`Script ${this.getChunkName()} reached execution timeout! Try not to yield the main thread in LocalScripts.`,
);
}
}
2 changes: 1 addition & 1 deletion src/core/build/json.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export function makeJsonModule(session: Session, path: string, name: string): Mo
// Creates and tracks a VirtualScript object for this file.
// The VirtualScript returns the decoded JSON data when required.
const virtualScript = new VirtualScript(instance, path, session.root);
virtualScript.setExecutor(() => HttpService.JSONDecode(virtualScript.rawSource));
virtualScript.setExecutor(() => HttpService.JSONDecode(virtualScript.source));
session.virtualScriptAdded(virtualScript);

// Applies an adjacent meta file if it exists.
Expand Down
13 changes: 13 additions & 0 deletions typings/Engine.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,16 @@ declare const getsynasset: typeof getcustomasset;
declare const http: {
request: typeof request;
};

// Roblox

/**
* Sets the environment to be used by the given function. `f` can be a Lua function or a number that specifies the function at that stack level:
* Level 1 is the function calling `setfenv`. `setfenv` returns the given function.
*
* As a special case, when f is 0 setfenv changes the environment of the running thread. In this case, setfenv returns no values.
*
* @param f A Lua function or the stack level.
* @param env The new environment.
*/
declare function setfenv<T extends number | Callback>(f: T, env: object): T extends 0 ? undefined : T;

0 comments on commit 417ec79

Please sign in to comment.