fix(vipeye-internal): c.Deref 对结构体联合类型返回具体类型
c.Deref(p) 对 IntPair | t.CPtr 这类 TypeSet 联合返回用户自定义结构体类实例(CPtr[Po] 返回 Po),使 c.Deref(p).a 解析为 CInt,不再显示 Unknown;C 标量(t.CInt 等)、内置 str/bytes(i8*)、裸指针保持 Unknown,避免 memcpy(c.Deref(out), ...) 等把解引用结果当指针传递的代码引入误报。新增负向回归测试。
This commit is contained in:
1 parent
ad3eecb077
commit
f4e19ed72a
2 files changed
+135
-4
No files matched your search
@@ -9308,6 +9308,56 @@ export function createTypeEvaluator(
|
||||
);
|
||||
}
|
||||
|
||||
// Viper 扩展:计算 c.Deref 的解引用结果类型。
|
||||
// Viper 中 `X | t.CPtr` 表示"指向 X 的指针",解引用后得到值类型 X。
|
||||
// 对 TypeSet(联合)取用户自定义结构体类实例子类型作为解引用结果;
|
||||
// 对 CPtr[Po] 取类型参数 Po;C 标量(t.CInt 等)、内置 str/bytes(i8*)、
|
||||
// 修饰符、CVoid 以及裸指针无法确定目标类型,跳过。
|
||||
// 无具体结果时返回 undefined,由调用方回退 Unknown,保持旧行为,
|
||||
// 避免对 memcpy(c.Deref(out), ...) 这类把解引用结果当指针传递的代码引入误报。
|
||||
function computeViperDerefResult(argExpr: ExpressionNode): Type | undefined {
|
||||
const argTypeResult = getTypeOfExpression(argExpr);
|
||||
const argType = argTypeResult.type;
|
||||
const subtypes = isUnion(argType) ? argType.priv.subtypes : [argType];
|
||||
const resultTypes: Type[] = [];
|
||||
|
||||
for (const subtype of subtypes) {
|
||||
if (!isClassInstance(subtype)) {
|
||||
continue;
|
||||
}
|
||||
if (isCPtrType(subtype)) {
|
||||
// 指针类型:CPtr[Po] → Po;裸 CPtr 无法确定目标类型。
|
||||
const typeArgs = subtype.priv.typeArgs;
|
||||
if (typeArgs && typeArgs.length > 0) {
|
||||
const pointee = typeArgs[0];
|
||||
resultTypes.push(
|
||||
isClass(pointee) && !isClassInstance(pointee) ? convertToInstance(pointee) : pointee
|
||||
);
|
||||
}
|
||||
} else if (isViperUserStructValue(subtype)) {
|
||||
// 用户自定义结构体(非 C 标量、非 str/bytes):即解引用后的值。
|
||||
resultTypes.push(subtype);
|
||||
}
|
||||
}
|
||||
|
||||
if (resultTypes.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
return combineTypes(resultTypes);
|
||||
}
|
||||
|
||||
// Viper 扩展:判断类实例是否为用户自定义结构体值类型。
|
||||
// C 标量(t.CInt/t.CUInt8T 等,fullName 以 t. 开头)、内置类型
|
||||
// (builtins.* 与 str/bytes 的 i8* 指针表示)不算,只有用户代码或
|
||||
// 第三方库定义的类(如 IntPair)才算,以便 c.Deref(p).a 解析字段。
|
||||
function isViperUserStructValue(type: ClassType): boolean {
|
||||
const name = type.shared.name;
|
||||
const fullName = type.shared.fullName;
|
||||
if (name === 'str' || name === 'bytes' || fullName === 'str' || fullName === 'bytes') {
|
||||
return false;
|
||||
}
|
||||
return !fullName.startsWith('builtins.') && !fullName.startsWith('t.');
|
||||
}
|
||||
// Viper 扩展:检查类型注解 AST 节点是否包含 CPtr 引用。
|
||||
// 用于判断循环引用是否被指针打破(如 next: 'Node' | t.CPtr)。
|
||||
// 递归遍历类型注解表达式,查找名称为 CPtr/CNeedPtr/CAutoPtr/CArrayPtr 的标识符。
|
||||
@@ -9700,10 +9750,14 @@ export function createTypeEvaluator(
|
||||
if (node.d.leftExpr.nodeType === ParseNodeType.MemberAccess &&
|
||||
node.d.leftExpr.d.member.d.value === 'Deref' &&
|
||||
node.d.args.length >= 1) {
|
||||
// c.Deref(ptr) → 解引用结果。返回类型为 c.Deref 的 T 类型参数
|
||||
// 对应的实例类型(从 c.vpi 的 `def Deref(ptr: int) -> T` 推导)。
|
||||
// 简化:返回 Unknown,由赋值上下文推断(如 `v: int = c.Deref(p)`)。
|
||||
typeResult.type = UnknownType.create();
|
||||
// c.Deref(ptr) → 解引用结果。
|
||||
// 之前简化返回 Unknown,由赋值上下文推断;但 `c.Deref(p).a` 这类
|
||||
// 链式成员访问需要可用的具体类型。当参数为 TypeSet(如
|
||||
// `IntPair | t.CPtr`)时,用户自定义结构体类实例即为解引用后的
|
||||
// 值类型;CPtr[Po] 的类型参数 Po 为目标类型。纯指针、C 标量、
|
||||
// str/bytes(i8*)等无法确定目标类型时保持 Unknown。
|
||||
const derefResult = computeViperDerefResult(node.d.args[0].d.valueExpr);
|
||||
typeResult.type = derefResult ?? UnknownType.create();
|
||||
} else {
|
||||
typeResult.type = UnknownType.create();
|
||||
}
|
||||
|
||||
@@ -224,4 +224,81 @@ def test_explicit_type_arg():
|
||||
fs.rmSync(testDir, { recursive: true, force: true });
|
||||
}
|
||||
serviceProvider.dispose();
|
||||
});
|
||||
|
||||
|
||||
test('c.Deref 对 IntPair | t.CPtr 联合返回结构体,成员访问解析为 CInt', () => {
|
||||
const tempFile = new RealTempFile();
|
||||
const serviceProvider = createServiceProvider(createFromRealFileSystem(tempFile), new NullConsole(), tempFile);
|
||||
const projectRoot = UriEx.file(TRANSPYC_INCLUDES);
|
||||
const configOptions = new ConfigOptions(projectRoot);
|
||||
configOptions.internalTestMode = true;
|
||||
configOptions.initializeTypeCheckingMode('standard');
|
||||
|
||||
const execEnv = new ExecutionEnvironment(
|
||||
'python',
|
||||
projectRoot,
|
||||
configOptions.diagnosticRuleSet,
|
||||
configOptions.defaultPythonVersion,
|
||||
configOptions.defaultPythonPlatform,
|
||||
[projectRoot],
|
||||
/* skipNativeLibraries */ false,
|
||||
/* defaultDialect */ 'viper'
|
||||
);
|
||||
configOptions.executionEnvironments = [execEnv];
|
||||
|
||||
const code = `import t
|
||||
import c
|
||||
|
||||
class IntPair:
|
||||
a: t.CInt
|
||||
b: t.CInt
|
||||
|
||||
def generic_swap[T](p: IntPair | t.CPtr):
|
||||
tmp: t.CInt = c.Deref(p).a
|
||||
c.Deref(p).a = c.Deref(p).b
|
||||
c.Deref(p).b = tmp
|
||||
bad: t.CInt = c.Deref(p).zzz
|
||||
`;
|
||||
const testDir = path.join(__dirname, '__deref_test__');
|
||||
const fs = require('fs');
|
||||
fs.mkdirSync(testDir, { recursive: true });
|
||||
const testFile = path.join(testDir, 'deref.vp');
|
||||
fs.writeFileSync(testFile, code, 'utf8');
|
||||
|
||||
try {
|
||||
const importResolver = new ImportResolver(serviceProvider, configOptions, new FullAccessHost(serviceProvider));
|
||||
const program = new Program(importResolver, configOptions, serviceProvider);
|
||||
const uri = UriEx.file(testFile);
|
||||
program.setTrackedFiles([uri]);
|
||||
while (program.analyze()) {
|
||||
// 循环
|
||||
}
|
||||
|
||||
const diagnostics = getDiagnostics(program, configOptions, uri);
|
||||
console.log('=== deref.vp diagnostics ===');
|
||||
diagnostics.forEach((d) => console.log(` ${d}`));
|
||||
|
||||
// 期望恰好 1 条"没有属性":c.Deref(p).zzz 应报错(证明 Deref 结果是
|
||||
// 具体结构体 IntPair 而非 Unknown——Unknown 上访问任何成员都静默通过),
|
||||
// 而 c.Deref(p).a / .b 都不应报。
|
||||
const noAttr = diagnostics.filter(
|
||||
(d) =>
|
||||
d.includes('无法访问') ||
|
||||
d.includes('没有属性') ||
|
||||
d.includes('has no attribute') ||
|
||||
d.includes('未知的属性')
|
||||
);
|
||||
assert.strictEqual(
|
||||
noAttr.length,
|
||||
1,
|
||||
`expected exactly 1 no-attribute error, got: ${JSON.stringify(diagnostics)}`
|
||||
);
|
||||
assert.ok(noAttr[0].includes('zzz'), `expected zzz error: ${JSON.stringify(diagnostics)}`);
|
||||
|
||||
program.dispose();
|
||||
} finally {
|
||||
fs.rmSync(testDir, { recursive: true, force: true });
|
||||
}
|
||||
serviceProvider.dispose();
|
||||
});
|
||||
Reference in new issue
Block a user