Added out-of-bounds access check for index operations where the indexed type is a tuple object with known length and the index value is a negative integer literal value.

This commit is contained in:
Eric Traut 2021-03-30 17:17:56 -07:00
parent 2b7191ad7f
commit c66fc45f46
4 changed files with 41 additions and 38 deletions

View File

@ -20,6 +20,7 @@ import { DiagnosticRule } from '../common/diagnosticRules';
import { TextRange } from '../common/textRange';
import { Localizer } from '../localization/localize';
import {
ArgumentCategory,
AssertNode,
AssignmentExpressionNode,
AssignmentNode,
@ -119,6 +120,7 @@ import {
getDeclaredGeneratorReturnType,
getGeneratorTypeArgs,
isEllipsisType,
isLiteralType,
isLiteralTypeOrUnion,
isNoReturnType,
isOpenEndedTupleClass,
@ -880,27 +882,44 @@ export class Checker extends ParseTreeWalker {
// If the index is a literal integer, see if this is a tuple with
// a known length and the integer value exceeds the length.
const subscriptValue = ParseTreeUtils.getIntegerSubscriptValue(node);
if (subscriptValue !== undefined) {
const baseType = this._evaluator.getType(node.baseExpression);
if (
baseType &&
isObject(baseType) &&
baseType.classType.tupleTypeArguments &&
!isOpenEndedTupleClass(baseType.classType)
) {
const tupleLength = baseType.classType.tupleTypeArguments.length;
const baseType = this._evaluator.getType(node.baseExpression);
if (
baseType &&
isObject(baseType) &&
baseType.classType.tupleTypeArguments &&
!isOpenEndedTupleClass(baseType.classType)
) {
const tupleLength = baseType.classType.tupleTypeArguments.length;
if (subscriptValue >= tupleLength) {
this._evaluator.addDiagnostic(
this._fileInfo.diagnosticRuleSet.reportGeneralTypeIssues,
DiagnosticRule.reportGeneralTypeIssues,
Localizer.Diagnostic.tupleIndexOutOfRange().format({
length: tupleLength,
index: subscriptValue,
}),
node
);
if (
node.items.length === 1 &&
!node.trailingComma &&
node.items[0].argumentCategory === ArgumentCategory.Simple &&
!node.items[0].name
) {
const subscriptType = this._evaluator.getType(node.items[0].valueExpression);
if (
subscriptType &&
isObject(subscriptType) &&
ClassType.isBuiltIn(subscriptType.classType, 'int') &&
isLiteralType(subscriptType)
) {
const subscriptValue = subscriptType.classType.literalValue as number;
if (
(subscriptValue >= 0 && subscriptValue >= tupleLength) ||
(subscriptValue < 0 && subscriptValue + tupleLength < 0)
) {
this._evaluator.addDiagnostic(
this._fileInfo.diagnosticRuleSet.reportGeneralTypeIssues,
DiagnosticRule.reportGeneralTypeIssues,
Localizer.Diagnostic.tupleIndexOutOfRange().format({
length: tupleLength,
index: subscriptValue,
}),
node
);
}
}
}
}

View File

@ -1254,23 +1254,6 @@ export function getCallNodeAndActiveParameterIndex(
}
}
// Returns the integer subscript if a simple numeric literal is used.
export function getIntegerSubscriptValue(node: IndexNode): number | undefined {
if (
node.items.length === 1 &&
!node.trailingComma &&
node.items[0].argumentCategory === ArgumentCategory.Simple &&
!node.items[0].name
) {
const expr = node.items[0].valueExpression;
if (expr.nodeType === ParseNodeType.Number && expr.isInteger && !expr.isImaginary) {
return expr.value;
}
}
return undefined;
}
export function printParseNodeType(type: ParseNodeType) {
switch (type) {
case ParseNodeType.Error:

View File

@ -147,6 +147,7 @@ def func13(a: Tuple[int, str], b: Tuple[()], c: Tuple[int, ...]):
v7 = a[-1]
t_v7: Literal["str"] = reveal_type(v7)
# This should generate an error.
v8 = a[-3]
t_v8: Literal["int | str"] = reveal_type(v8)

View File

@ -783,7 +783,7 @@ test('Optional1', () => {
test('Tuples1', () => {
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['tuples1.py']);
TestUtils.validateResults(analysisResults, 10);
TestUtils.validateResults(analysisResults, 11);
});
test('Tuples2', () => {