Skip to content

Commit

Permalink
No public description
Browse files Browse the repository at this point in the history
PiperOrigin-RevId: 640214558
Change-Id: I67a9523e9bd8e3d98a6c7f296e281ffc62009adb
  • Loading branch information
ISE Hardening authored and copybara-github committed Jun 4, 2024
1 parent 59ba8d1 commit bc0317c
Showing 1 changed file with 51 additions and 30 deletions.
81 changes: 51 additions & 30 deletions common/third_party/tsetse/util/is_literal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,33 +15,38 @@ import {findInChildren} from './ast_tools';
* - `x?y:z` constructions, if we accept `y` and `z`
* - Variables that are const, and initialized with an expression we accept
*
* And to prevent bypasses, expressions that include casts are not accepted, and
* this checker does not follow imports.
* And to prevent bypasses, expressions that include casts are not accepted.
*/
export function isLiteral(typeChecker: ts.TypeChecker, node: ts.Node): boolean {
if (ts.isBinaryExpression(node) &&
node.operatorToken.kind === ts.SyntaxKind.PlusToken) {
if (
ts.isBinaryExpression(node) &&
node.operatorToken.kind === ts.SyntaxKind.PlusToken
) {
// Concatenation is fine, if the parts are literals.
return (
isLiteral(typeChecker, node.left) &&
isLiteral(typeChecker, node.right));
isLiteral(typeChecker, node.left) && isLiteral(typeChecker, node.right)
);
} else if (ts.isTemplateExpression(node)) {
// Same for template expressions.
return node.templateSpans.every(span => {
return node.templateSpans.every((span) => {
return isLiteral(typeChecker, span.expression);
});
} else if (ts.isTemplateLiteral(node)) {
// and literals (in that order).
return true;
} else if (ts.isConditionalExpression(node)) {
return isLiteral(typeChecker, node.whenTrue) &&
isLiteral(typeChecker, node.whenFalse);
return (
isLiteral(typeChecker, node.whenTrue) &&
isLiteral(typeChecker, node.whenFalse)
);
} else if (ts.isIdentifier(node)) {
return isUnderlyingValueAStringLiteral(node, typeChecker);
}

const hasCasts = findInChildren(
node, (n) => ts.isAsExpression(n) && !ts.isConstTypeReference(n.type));
node,
(n) => ts.isAsExpression(n) && !ts.isConstTypeReference(n.type),
);

return !hasCasts && isLiteralAccordingToItsType(typeChecker, node);
}
Expand All @@ -52,55 +57,71 @@ export function isLiteral(typeChecker: ts.TypeChecker, node: ts.Node): boolean {
* is an approximation, but should never have false positives.
*/
function isUnderlyingValueAStringLiteral(
identifier: ts.Identifier, tc: ts.TypeChecker) {
identifier: ts.Identifier,
tc: ts.TypeChecker,
) {
// The identifier references a value, and we try to follow the trail: if we
// find a variable declaration for the identifier, and it was declared as a
// const (so we know it wasn't altered along the way), then the value used
// in the declaration is the value our identifier references. That means we
// should look at the value used in its initialization (by applying the same
// rules as before).
// Since we're best-effort, if a part of that operation failed due to lack
// of support (for instance, the identifier was imported), then we fail
// closed and don't consider the value a literal.
return getVariableDeclarationsInSameFile(identifier, tc)
// of support, then we fail closed and don't consider the value a literal.
const declarations = getDeclarations(identifier, tc);
const variableDeclarations = declarations.filter(ts.isVariableDeclaration);
if (variableDeclarations.length) {
return variableDeclarations
.filter(isConst)
.some(d => d.initializer !== undefined && isLiteral(tc, d.initializer));
.some((d) => d.initializer !== undefined && isLiteral(tc, d.initializer));
}
const importDeclarations = declarations.filter(ts.isImportSpecifier);
if (importDeclarations.length) {
return isLiteralAccordingToItsType(tc, identifier);
}
return false;
}

/**
* Returns whether this thing is a literal based on TS's understanding. This is
* only looking at the local type, so there's no magic in that function.
* Returns whether this thing is a literal based on TS's understanding.
*/
function isLiteralAccordingToItsType(
typeChecker: ts.TypeChecker, node: ts.Node): boolean {
typeChecker: ts.TypeChecker,
node: ts.Node,
): boolean {
const nodeType = typeChecker.getTypeAtLocation(node);
return (nodeType.flags &
(ts.TypeFlags.StringLiteral | ts.TypeFlags.NumberLiteral |
ts.TypeFlags.BooleanLiteral | ts.TypeFlags.EnumLiteral)) !== 0;
return (
(nodeType.flags &
(ts.TypeFlags.StringLiteral |
ts.TypeFlags.NumberLiteral |
ts.TypeFlags.BooleanLiteral |
ts.TypeFlags.EnumLiteral)) !==
0
);
}

/**
* Follows the symbol behind the given identifier, assuming it is a variable,
* and return all the variable declarations we can find that match it in the
* same file.
*/
function getVariableDeclarationsInSameFile(
node: ts.Identifier, tc: ts.TypeChecker): ts.VariableDeclaration[] {
function getDeclarations(
node: ts.Identifier,
tc: ts.TypeChecker,
): ts.Declaration[] {
const symbol = tc.getSymbolAtLocation(node);
if (!symbol) {
return [];
}
const decls = symbol.getDeclarations();
if (!decls) {
return [];
}
return decls.filter(ts.isVariableDeclaration);
return symbol.getDeclarations() ?? [];
}

// Tests whether the given variable declaration is Const.
function isConst(varDecl: ts.VariableDeclaration): boolean {
return Boolean(
varDecl && varDecl.parent &&
varDecl &&
varDecl.parent &&
ts.isVariableDeclarationList(varDecl.parent) &&
varDecl.parent.flags & ts.NodeFlags.Const);
varDecl.parent.flags & ts.NodeFlags.Const,
);
}

0 comments on commit bc0317c

Please sign in to comment.