Compare commits

...

1 Commits

Author SHA1 Message Date
Mike Vitousek
8cf8b0de46 test: validate only safe fn calls 2024-08-14 17:02:11 -04:00
2 changed files with 105 additions and 0 deletions

View File

@@ -103,6 +103,7 @@ import {
import {validateLocalsNotReassignedAfterRender} from '../Validation/ValidateLocalsNotReassignedAfterRender';
import {outlineFunctions} from '../Optimization/OutlineFunctions';
import {propagatePhiTypes} from '../TypeInference/PropagatePhiTypes';
import { validateOnlySafeFunctionCalls } from '../Validation/ValidateOnlySafeFunctionCalls';
import {lowerContextAccess} from '../Optimization/LowerContextAccess';
export type CompilerPipelineValue =
@@ -339,6 +340,8 @@ function* runWithEnvironment(
assertTerminalPredsExist(hir);
}
validateOnlySafeFunctionCalls(hir);
const reactiveFunction = buildReactiveFunction(hir);
yield log({
kind: 'reactive',

View File

@@ -0,0 +1,102 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import {CompilerError, Effect, ErrorSeverity} from '..';
import {DeclarationId, HIRFunction, Place} from '../HIR';
import { eachInstructionOperand, eachPatternOperand } from '../HIR/visitors';
/**
* Validates that all store/load references to a given named identifier align with the
* "kind" of that variable (normal variable or context variable). For example, a context
* variable may not be loaded/stored with regular StoreLocal/LoadLocal/Destructure instructions.
*/
export function validateOnlySafeFunctionCalls(fn: HIRFunction): void {
const globalDerivedNames = new Map<DeclarationId, string>();
const names = new Map<DeclarationId, string>();
const error = new CompilerError();
for (const [_, block] of fn.body.blocks) {
for (const instr of block.instructions) {
function testCallee(callee: Place) {
if (callee.identifier.type.kind === 'Function' && callee.identifier.type.shapeId == null && Array(...eachInstructionOperand(instr)).some((x) => x.reactive && (x.effect === Effect.ConditionallyMutate || x.effect === Effect.Mutate) && x.identifier.type.kind !== "Primitive")) {
const name = globalDerivedNames.get(callee.identifier.declarationId) ?? names.get(callee.identifier.declarationId) ?? "[unknown]";
error.push({reason: `Unsafe call of ${name}`, loc: instr.loc, severity: ErrorSeverity.InvalidReact})
}
}
switch (instr.value.kind) {
case "CallExpression":
testCallee(instr.value.callee);
break;
case "MethodCall":
testCallee(instr.value.property);
break;
case "LoadGlobal":
names.set(instr.lvalue.identifier.declarationId, instr.value.binding.name);
if (instr.value.binding.kind !== "ModuleLocal") {
globalDerivedNames.set(instr.lvalue.identifier.declarationId, instr.value.binding.name);
}
break;
case "LoadLocal":
case "LoadContext": {
const val = names.get(instr.value.place.identifier.declarationId)
const global = globalDerivedNames.get(instr.value.place.identifier.declarationId)
if (val != null) {
names.set(instr.lvalue.identifier.declarationId, val);
}
if (global != null) {
globalDerivedNames.set(instr.lvalue.identifier.declarationId, global);
} else if (instr.value.place.identifier.type.kind === "Object" && instr.value.place.identifier.type.shapeId === "BuiltInArray") {
globalDerivedNames.set(instr.lvalue.identifier.declarationId, "[Array instance]");
} else if (instr.value.place.identifier.type.kind === "Object" && instr.value.place.identifier.type.shapeId === "BuiltInObject") {
globalDerivedNames.set(instr.lvalue.identifier.declarationId, "[Object instance]");
}
break;
}
case 'StoreLocal':
case 'StoreContext': {
const val = names.get(instr.value.value.identifier.declarationId)
const global = globalDerivedNames.get(instr.value.value.identifier.declarationId)
if (val != null) {
names.set(instr.lvalue.identifier.declarationId, val);
names.set(instr.value.lvalue.place.identifier.declarationId, val);
}
if (global != null) {
globalDerivedNames.set(instr.lvalue.identifier.declarationId, global);
globalDerivedNames.set(instr.value.lvalue.place.identifier.declarationId, global);
} else if (instr.value.value.identifier.type.kind === "Object" && instr.value.value.identifier.type.shapeId === "BuiltInArray") {
globalDerivedNames.set(instr.lvalue.identifier.declarationId, "[Array instance]");
globalDerivedNames.set(instr.value.lvalue.place.identifier.declarationId, "[Array instance]");
} else if (instr.value.value.identifier.type.kind === "Object" && instr.value.value.identifier.type.shapeId === "BuiltInObject") {
globalDerivedNames.set(instr.lvalue.identifier.declarationId, "[Object instance]");
globalDerivedNames.set(instr.value.lvalue.place.identifier.declarationId, "[Object instance]");
}
break;
}
case "PropertyLoad": {
const global = globalDerivedNames.get(instr.value.object.identifier.declarationId)
names.set(instr.lvalue.identifier.declarationId, instr.value.property);
if (global != null) {
globalDerivedNames.set(instr.lvalue.identifier.declarationId, `${global}.${instr.value.property}`);
}
break;
}
case "Destructure": {
const global = globalDerivedNames.get(instr.value.value.identifier.declarationId);
for (const pat of eachPatternOperand(instr.value.lvalue.pattern)) {
if (pat.identifier.name?.kind === "named") {
if (global != null) {
globalDerivedNames.set(pat.identifier.declarationId, `${global}.${pat.identifier.name.value}`);
}
names.set(pat.identifier.declarationId, pat.identifier.name.value);
}
}
}
}
}
}
if (error.hasErrors()) {
throw error;
}
}