feat(vipeye-internal): 指针隐式转换收紧为「同级 + pointee 兼容」

原先「同级指针无条件允许转换」,现改为:级数必须相同,且 pointee 必须兼容。

规则(canImplicitlyConvertCType + 新增 isViperCPtrPointeeAssignable):
- 任一侧 pointee 是 void*/未定(rawptr)→ 允许
  (裸 rawptr 的实参是类自身的 TypeVar,不是 UnknownType,必须一并认)
- pointee 规范化后相同 → 允许(先剥联合里的 C 修饰符、
  再统一实例形态,否则 volatile int / type[ParamNode] 会误报)
- 两侧 pointee 都是基本类型(数值标量)→ 复用 T 的转换规则
- 结构体等非基本类型 → 仅允许上转型(源类派生自目标类,
  比较须 cloneAsInstantiable;MemBuddy* → MemManager* 是真实依赖),
  不相关结构体 / 向下转型 / 结构体↔基本类型一律拒绝
- 单级指针 ↔ t.CVoid(void*)双向允许;多级(Node** → void*)禁止

要点:CPtr↔CPtr 分支必须放在「同名即允许」快捷判断之前(两 CPtr 的
shared.name 都是 'CPtr',否则 pointee 完全不看)。

`&x` / `*x` 的返回类型本就正确(x: T* → *x: T、&x: T**),本次确认它们
已参与上述拦截:a(&x)(x: T*,T** 传给 void*)仍报错。

- 新增 viperPointerConversion.test.ts(2 用例:一元 &/* 类型 + 拦截;
  同级 pointee 兼容矩阵,断言报错行恰为 [36,46,49])
- .gitignore:新增 __viper_ptr_unary__/ __viper_ptr_convert__/
- CHANGELOG:1.1.426 追加 1 条
This commit is contained in:
TermiNexus committed 2026-09-28 15:26:51 +08:00
1 parent c7878ab9d7
commit d91fc2d67e
4 files changed
+392 -8

No files matched your search

+2
View File
@@ -146,6 +146,8 @@ packages/vipeye-internal/src/tests/__viper_attribute_prefix__/
packages/vipeye-internal/src/tests/__viper_attribute_noimport__/
packages/vipeye-internal/src/tests/__viper_attribute_implicit_scope__/
packages/vipeye-internal/src/tests/__viper_attribute_unknown__/
packages/vipeye-internal/src/tests/__viper_ptr_unary__/
packages/vipeye-internal/src/tests/__viper_ptr_convert__/
packages/vipeye-internal/src/tests/__viper_volatile__/
packages/vipeye-internal/src/tests/__viper_volatile_bad__/
packages/vipeye-internal/src/tests/__viper_callable__/
+112 -8
View File
@@ -3515,11 +3515,12 @@ const _viperCTypeSizeMap = new Map<string, number>([
// Viper 扩展:判断 CType 子类之间的隐式转换是否合法。
// 规则:
// 1. 整数 → 整数:srcSize <= destSize 允许(低向高或同级),否则不允许
// 2. 浮点 → 浮点:srcSize <= destSize 允许
// 1. 整数 → 整数:允许所有方向(C 语义,高向低可安全降级)
// 2. 浮点 → 浮点:允许所有方向
// 3. 整数 → 浮点:destSize >= srcSize 允许(无损)
// 4. 浮点 → 整数:不允许(有损)
// 5. 同级指针(都是 CPtr 子类):允许不同类型转换
// 5. 指针 → 指针(都是 CPtr 子类):级数必须相同,且 **pointee 必须兼容**
// (见 isViperCPtrPointeeAssignable)——不再无条件允许
// 6. 不同级(一个是指针,另一个不是):不允许
// 7. 未知位宽:保守允许(避免误报)
export function canImplicitlyConvertCType(srcType: Type, destType: Type): boolean {
@@ -3530,18 +3531,30 @@ export function canImplicitlyConvertCType(srcType: Type, destType: Type): boolea
const srcName = srcType.shared.name;
const destName = destType.shared.name;
// Viper 扩展:指针 → 指针(都是 CPtr 子类)的 pointee 兼容性检查。
// 必须放在下面的「同名即允许」快捷判断**之前**:CPtr↔CPtr 的 shared.name
// 都是 'CPtr',否则会因同名而无条件通过(pointee 类型完全不看)。
if (isCPtrType(srcType) && isCPtrType(destType)) {
return isViperCPtrPointeeAssignable(destType, srcType);
}
// 同类型,允许
if (srcName === destName) {
return true;
}
// 指针规则:同级指针(都是 CPtr 子类)允许,不同级不允许
const srcIsPtr = isCPtrType(srcType);
const destIsPtr = isCPtrType(destType);
if (srcIsPtr && destIsPtr) {
// Viper 扩展:单级指针 ↔ void*(CVoid)语义(C 惯例)。
// 任意对象指针可隐式转为 void*,void* 也可隐式转回任意对象指针。
// 多级指针(如 Node** → void*)仍禁止,与跨级指针守卫口径一致。
if (isCPtrType(srcType) && getCPtrLevel(srcType) === 1 && isCVoidType(destType)) {
return true;
}
if (srcIsPtr !== destIsPtr) {
if (isCVoidType(srcType) && isCPtrType(destType) && getCPtrLevel(destType) === 1) {
return true;
}
// 指针规则(剩余情况只剩「一侧指针、一侧非指针」):不允许
if (isCPtrType(srcType) !== isCPtrType(destType)) {
return false;
}
@@ -3584,6 +3597,97 @@ export function canImplicitlyConvertCType(srcType: Type, destType: Type): boolea
return true;
}
// Viper 扩展:pointee 是否为「数值标量」——C 整数 / C 浮点 / 内置 int|float|
// double|char|bool,即用户口径的「基本类型」。
// 非基本类型(结构体、类等)的指针**禁止**隐式互转(见 isViperCPtrPointeeAssignable)。
function isViperNumericScalarPointee(type: Type): boolean {
if (!isClassInstance(type)) {
return false;
}
if (ClassType.isBuiltIn(type, ['int', 'float', 'double', 'char', 'bool'])) {
return true;
}
const name = type.shared.name;
return _viperCIntegerTypeNames.has(name) || _viperCFloatTypeNames.has(name);
}
// Viper 扩展:把 pointee 规范化为可比较的「核心类型」:
// 1. 去掉联合里的 C 修饰符(CConst / CVolatile / CStatic / ...):
// 如 `volatile int`(= CVolatile | int)→ int;
// 2. 统一为**实例形态**:`type[ParamNode]`(类对象)与 ParamNode(实例)
// 是同一个类,须可比。
function normalizeViperPointeeType(type: Type): Type {
let core: Type = type;
if (isUnion(core)) {
const nonModifiers = core.priv.subtypes.filter((subtype) => !(isClass(subtype) && isCModifierType(subtype)));
if (nonModifiers.length === 1) {
core = nonModifiers[0];
}
}
if (isInstantiableClass(core)) {
core = ClassType.cloneAsInstance(core);
}
return core;
}
// Viper 扩展:两个 CPtr 之间是否允许隐式转换(指针 → 指针)。
// 规则(2026-09-28 收紧,原先「同级即无条件允许」):
// 1. 级数必须相同(getCPtrLevel)——不同级一律拒绝;
// 2. 任一侧 pointee 是 void*(CVoid)或未定(rawptr = CPtr[未特化],其实参是
// 类自身的 TypeVar)→ 允许(void* 通用指针语义,malloc 等场景依赖它);
// 3. pointee 规范化后完全相同 → 允许;
// 4. 两侧 pointee 都是数值标量(基本类型)→ 按 T 的隐式转换规则
// (整数/浮点全方向、整数→浮点需位宽≥、浮点→整数禁止);
// 5. 结构体(非基本类型):仅允许「源类派生自目标类」的**上转型**
// (如 MemBuddy* → MemManager*,真实代码 memhub/requests 依赖),
// 其余(不相关的两个结构体、向下转型)一律拒绝。
function isViperCPtrPointeeAssignable(destPtr: ClassType, srcPtr: ClassType): boolean {
if (getCPtrLevel(destPtr) !== getCPtrLevel(srcPtr)) {
return false;
}
const rawDestPointee =
destPtr.priv.typeArgs && destPtr.priv.typeArgs.length > 0 ? destPtr.priv.typeArgs[0] : undefined;
const rawSrcPointee = srcPtr.priv.typeArgs && srcPtr.priv.typeArgs.length > 0 ? srcPtr.priv.typeArgs[0] : undefined;
// 未定 pointee(裸 rawptr / CPtr 无实参)→ 通用指针,允许。
// 注意:裸 `rawptr`(= t.CPtr 未特化)的实参是**类自身的 TypeVar**,不是
// UnknownType(getCPtrLevel 视其为 1 级),故这里必须一并认 TypeVar。
if (!rawDestPointee || !rawSrcPointee || isTypeVar(rawDestPointee) || isTypeVar(rawSrcPointee)) {
return true;
}
const destPointee = normalizeViperPointeeType(rawDestPointee);
const srcPointee = normalizeViperPointeeType(rawSrcPointee);
// void* 通用指针(任一侧)→ 允许。
if (isCVoidType(destPointee) || isCVoidType(srcPointee)) {
return true;
}
if (isUnknown(destPointee) || isUnknown(srcPointee)) {
return true;
}
// pointee 完全相同 → 允许。
if (isTypeSame(destPointee, srcPointee)) {
return true;
}
// 仅「基本类型(数值标量)」之间按 T 的隐式转换规则放行。
if (isViperNumericScalarPointee(destPointee) && isViperNumericScalarPointee(srcPointee)) {
return canImplicitlyConvertCType(srcPointee, destPointee);
}
// 结构体等非基本类型:仅允许上转型(源类派生自目标类)。
// 注意:isDerivedFrom 的比较必须在**可实例化形态**下进行(类的 baseClasses
// 存的是 instantiable 基类,与实例形态混比会因 instance/instantiable 差异误判)。
if (isClassInstance(destPointee) && isClassInstance(srcPointee)) {
const srcForCompare = ClassType.cloneAsInstantiable(srcPointee);
const destForCompare = ClassType.cloneAsInstantiable(destPointee);
const inheritanceChain: InheritanceChain = [];
return ClassType.isDerivedFrom(srcForCompare, destForCompare, inheritanceChain);
}
// 其余(如联合 pointee)→ 禁止隐式转换。
return false;
}
export function isAny(type: Type): type is AnyType {
return type.category === TypeCategory.Any;
}
@@ -0,0 +1,277 @@
/*
* viperPointerConversion.test.ts
*
* 验证 Viper 指针语义(2026-09-28 收紧):
*
* ① 一元 `&` / `*` 的返回类型,且**结果类型参与类型拦截**:
* x: T → `&x` : T*
* x: T* → `*x` : T
* x: T* → `&x` : T**(`a(&x)` 传给 `rawptr`(void*)应判非法)
*
* ② 指针 → 指针的隐式转换规则(原先「同级即无条件允许」,现收紧):
* - 级数必须相同;
* - pointee 任一侧是 void* / 未定(`rawptr`)→ 允许;
* - pointee 相同 → 允许;
* - pointee 都是基本类型(数值标量)→ 按 T 的隐式转换规则;
* - 结构体(非基本类型)→ 仅允许「源类派生自目标类」的上转型,
* 不相关的结构体、向下转型一律禁止;
* - 单级指针 ↔ void*(CVoid)双向允许;多级(如 Node** → void*)禁止。
*
* 运行:npx jest src/tests/viperPointerConversion.test.ts --runInBand
*/
import assert from 'assert';
import * as path from 'path';
import { ImportResolver } from '../analyzer/importResolver';
import { ParseTreeWalker } from '../analyzer/parseTreeWalker';
import { Program } from '../analyzer/program';
import { TypeEvaluator } from '../analyzer/typeEvaluatorTypes';
import { ConfigOptions, ExecutionEnvironment } from '../common/configOptions';
import { NullConsole } from '../common/console';
import { RealTempFile, createFromRealFileSystem } from '../common/realFileSystem';
import { createServiceProvider } from '../common/serviceProviderExtensions';
import { UriEx } from '../common/uri/uriUtils';
import { FullAccessHost } from '../common/fullAccessHost';
import { convertOffsetToPosition } from '../common/positionUtils';
import { UnaryOperationNode } from '../parser/parseNodes';
import { OperatorType } from '../parser/tokenizerTypes';
(global as any).__rootDirectory = path.resolve();
const TRANSPYC_INCLUDES = 'd:\\Users\\TermiNexus\\Desktop\\TransPyC\\includes';
function buildProgramForCode(code: string, dirName: string) {
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 importResolver = new ImportResolver(serviceProvider, configOptions, new FullAccessHost(serviceProvider));
const program = new Program(importResolver, configOptions, serviceProvider);
const fs = require('fs');
const testDir = path.join(__dirname, dirName);
fs.mkdirSync(testDir, { recursive: true });
const testFile = path.join(testDir, 'main.vp');
fs.writeFileSync(testFile, code, 'utf8');
const uri = UriEx.file(testFile);
program.setTrackedFiles([uri]);
while (program.analyze()) {
// 循环
}
const sourceFile = program.getSourceFile(uri);
assert(sourceFile, 'source file not found');
const parseResults = sourceFile.getParseResults();
assert(parseResults, 'parse results not found');
return { program, configOptions, uri, testDir, serviceProvider, parseResults };
}
function getDiagnostics(program: Program, configOptions: ConfigOptions, uri: any) {
const sourceFile = program.getSourceFile(uri);
assert(sourceFile, 'source file not found');
return (sourceFile.getDiagnostics(configOptions) || []).map((diag: any) => ({
category: diag.category,
line: diag.range ? diag.range.start.line + 1 : -1,
message: diag.message as string,
}));
}
/** 实际报错(category 0=Error / 1=Warning)所在行集合。 */
function hardErrorLines(program: Program, configOptions: ConfigOptions, uri: any): number[] {
return Array.from(
new Set(
getDiagnostics(program, configOptions, uri)
.filter((d) => d.category === 0 || d.category === 1)
.map((d) => d.line)
)
).sort((a, b) => a - b);
}
test('一元 & / * 的返回类型正确,且结果类型参与类型拦截', () => {
const code = `import t
class Node:
v: uint64
def take_raw(p: rawptr) -> None:
pass
def addr_of_value(x: Node) -> None:
p: Node* = &x
def addr_of_ptr(x: Node*) -> None:
p: Node** = &x
def deref_ptr(x: Node*) -> None:
v: Node = *x
def bad_pass(x: Node*) -> None:
take_raw(&x)
`;
const { program, configOptions, uri, testDir, serviceProvider, parseResults } = buildProgramForCode(
code,
'__viper_ptr_unary__'
);
try {
const evaluator = program.evaluator as TypeEvaluator | undefined;
assert(evaluator, 'evaluator not available');
const ev = evaluator;
const unaryOps: { line: number; op: string; type: string }[] = [];
class UnaryCollector extends ParseTreeWalker {
override visitUnaryOperation(node: UnaryOperationNode) {
if (node.d.operator !== OperatorType.Multiply && node.d.operator !== OperatorType.BitwiseAnd) {
return true;
}
const type = ev.getType(node as any);
unaryOps.push({
line: convertOffsetToPosition(node.start, parseResults.parserOutput.lines).line + 1,
op: node.d.operator === OperatorType.Multiply ? '*' : '&',
type: type ? program.printType(type) : '(undefined)',
});
return true;
}
}
new UnaryCollector().walk(parseResults.parserOutput.parseTree);
console.log('=== 一元 * / & 节点类型 ===');
unaryOps.forEach((u) => console.log(` L${u.line} ${u.op}: ${u.type}`));
// x: Node → &x : Node*
const addrValue = unaryOps.find((u) => u.op === '&' && u.line === 10);
assert(addrValue, '&x (x: Node) 未找到 (L10)');
assert.strictEqual(addrValue.type, 'rawptr[Node]', `&x (x: Node) 应为 rawptr[Node],实际: ${addrValue.type}`);
// x: Node* → &x : Node**
const addrPtr = unaryOps.find((u) => u.op === '&' && u.line === 13);
assert(addrPtr, '&x (x: Node*) 未找到 (L13)');
assert.strictEqual(
addrPtr.type,
'rawptr[rawptr[Node]]',
`&x (x: Node*) 应为 rawptr[rawptr[Node]],实际: ${addrPtr.type}`
);
// x: Node* → *x : Node
const deref = unaryOps.find((u) => u.op === '*' && u.line === 16);
assert(deref, '*x 未找到 (L16)');
assert.strictEqual(deref.type, 'Node', `*x (x: Node*) 应为 Node,实际: ${deref.type}`);
// 拦截:`a(&x)`(x: Node* → Node**)传给 rawptr(void*)应判非法
const lines = hardErrorLines(program, configOptions, uri);
console.log('=== 报错行 ===');
lines.forEach((l) => console.log(` L${l}`));
assert.deepStrictEqual(lines, [19], `应且仅应报 L19(&x 的 Node** 传给 void*):${JSON.stringify(lines)}`);
} finally {
program.dispose();
serviceProvider.dispose();
require('fs').rmSync(testDir, { recursive: true, force: true });
}
});
test('指针隐式转换规则:同级 + pointee 兼容(基本类型 / 结构体 / void* / 多级)', () => {
const code = `import t
class Node:
v: uint64
class Other:
w: uint64
class Sub(Node):
z: uint64
def take_u64(p: uint64*) -> None:
pass
def take_u16(p: uint16*) -> None:
pass
def take_node(p: Node*) -> None:
pass
def take_other(p: Other*) -> None:
pass
def take_void(p: t.CVoid) -> None:
pass
def take_raw(p: rawptr) -> None:
pass
def basic_ok(p: uint32*) -> None:
take_u64(p)
take_u16(p)
def struct_rules(n: Node*, s: Sub*) -> None:
take_node(n)
take_other(n)
take_node(s)
take_void(n)
take_raw(n)
def void_source(r: rawptr, v: t.CVoid) -> None:
take_node(r)
take_node(v)
def mixed(n: Node*) -> None:
take_u64(n)
def level_guard(n: Node*) -> None:
take_void_of(&n)
def take_void_of(p: t.CVoid) -> None:
pass
`;
const { program, configOptions, uri, testDir, serviceProvider } = buildProgramForCode(
code,
'__viper_ptr_convert__'
);
try {
const diagnostics = getDiagnostics(program, configOptions, uri);
console.log('=== 指针转换诊断 ===');
diagnostics
.filter((d) => d.category === 0 || d.category === 1)
.forEach((d) => console.log(` L${d.line} [cat=${d.category}] ${d.message}`));
// 允许:L31/L32(uint32* → uint64*/uint16*,整数按 T 规则全方向)
// L35(Node* → Node*)、L37(Sub* → Node*,上转型)
// L38(Node* → void*)、L39(Node* → rawptr)
// L42(rawptr → Node*)、L43(CVoid → Node*)
// 拒绝:L36(Node* → Other*,不相关结构体)
// L46(Node* → uint64*,结构体 → 基本类型指针)
// L49(Node** → void*,多级指针跨级)
const lines = hardErrorLines(program, configOptions, uri);
console.log('=== 报错行 ===');
lines.forEach((l) => console.log(` L${l}`));
assert.deepStrictEqual(
lines,
[36, 46, 49],
`应且仅应报 L36(Node*→Other*)、L46(Node*→uint64*)、L49(Node**→void*):${JSON.stringify(
diagnostics.filter((d) => d.category === 0 || d.category === 1)
)}`
);
} finally {
program.dispose();
serviceProvider.dispose();
require('fs').rmSync(testDir, { recursive: true, force: true });
}
});
+1
View File
@@ -41,6 +41,7 @@
- `[SIZE]T` array annotations no longer require the file to `import t`. The parser desugars `coroutines: [8]Coroutine` into a synthetic `t.CArray[Coroutine, 8]` member access; when that synthetic `t` could not be resolved through the lexical scope it silently degraded to `Unknown`, so the whole array annotation collapsed and `m.coroutines[i].rsp` reported "无法解析类型上的成员“rsp”访问" with `rsp` getting no semantic token at all (rendered unhighlighted, no jump-to-definition). The synthetic `t` now falls back to the `t` module bound in the built-in `builtins.vpi` scope — the same mechanism `T*` uses for `rawptr` and `__attribute__(...)` uses for its implicit module roots — so a file without `import t` still gets the real `CArray[Coroutine, Literal[8]]` and the element's member resolves
- Attribute names inside `__attribute__(...)` are highlighted **structurally**, so a name that the compiler supports but that isn't declared in the `attr` class still colours like the declared ones. `stackrealign` (supported by TransPyV's code generator — `FuncGenBase.vp` maps it to the LLVM `"stackrealign"` attribute — and used by ViperOS `VKernel/Kernel/main.vp`) is declared in neither `t.py` nor the `t.vpi` stub, so `t.attr.stackrealign` used to be unresolvable: the name got no semantic token (rendered unhighlighted) and the file was additionally flagged with "无法访问类"type[attr]"的属性"stackrealign"". The parser now marks the synthesized attribute-name node, the semantic token provider colours it as `function` (exactly like a declared attribute such as `section` / `aligned` / `naked`), and the member-access diagnostic is suppressed for these names — the compiler silently drops unknown attributes rather than rejecting them, so reporting them was a false positive. Declared names keep resolving through `t.attr` as before, so hover and jump-to-definition still work for them
- `__attribute__(...)` **completion and hover** now list attribute names that the compiler supports but that the `attr` class doesn't declare, instead of only mirroring `t.attr`. `stackrealign` is offered by typing inside the parentheses and appears in the hover list of available names; the list is a single shared union (`viperAttributeUtils.getViperAttributeNames`) of `t.attr`'s declared names plus these extra names, so completion and hover cannot drift apart. Declared names keep coming from the stub symbol table (so they still show their signature and support jump-to-definition)
- Pointer-to-pointer implicit conversion is now **strict** instead of "same level is always allowed". A pointer converts implicitly to another pointer only when the **level** matches **and** the pointee is compatible: either side being `void*` / undetermined (`rawptr`) always allows it, identical pointees are allowed, two basic (numeric scalar) pointees follow the very same rules as the values themselves (`uint32* → uint64*` and `uint32* → uint16*` are allowed because integers convert in every direction; `float* → int*` is still reported), and for **structs** only an **upcast** is allowed (`Sub* → Node*`; `memhub` / `requests` depend on `MemBuddy* → MemManager*`), while two unrelated structs (`Node* → Other*`), a downcast, or a struct↔basic mix (`Node* → uint64*`) are rejected. Single-level pointers also convert to and from `void*` (`t.CVoid`) in both directions, while multi-level ones still don't (`Node** → void*` is reported). `&x` / `*x` already produced the right types (`&x` is `T*` for `x: T`, `T**` for `x: T*`; `*x` is `T` for `x: T*`) and now take part in these checks too — `a(&x)` with `x: Node*` (a `Node**` into a `void*` parameter) is reported as before, and so is `a(&x)` where the parameter is a single-level pointer
## 1.1.425