1 | // Copyright (C) 2021 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 |
3 | |
4 | #include "qqmljsshadowcheck_p.h" |
5 | |
6 | QT_BEGIN_NAMESPACE |
7 | |
8 | using namespace Qt::StringLiterals; |
9 | |
10 | /*! |
11 | * \internal |
12 | * \class QQmlJSShadowCheck |
13 | * |
14 | * This pass looks for possible shadowing when accessing members of QML-exposed |
15 | * types. A member can be shadowed if a non-final property is re-declared in a |
16 | * derived class. As the QML engine will always pick up the most derived variant |
17 | * of that property, we cannot rely on any property of a type to be actually |
18 | * accessible, unless one of a few special cases holds: |
19 | * |
20 | * 1. We are dealing with a direct scope lookup, without an intermediate object. |
21 | * Such lookups are protected from shadowing. For example "property int a: b" |
22 | * always works. |
23 | * 2. The object we are retrieving the property from is identified by an ID, or |
24 | * an attached property or a singleton. Such objects cannot be replaced. |
25 | * Therefore we can be sure to see all the type information at compile time. |
26 | * 3. The property is declared final. |
27 | * 4. The object we are retrieving the property from is a value type. Value |
28 | * types cannot be used polymorphically. |
29 | * |
30 | * If the property is potentially shadowed, we can still retrieve it, but we |
31 | * don't know its type. We should assume "var" then. |
32 | * |
33 | * All of the above also holds for methods. There we have to transform the |
34 | * arguments and return types into "var". |
35 | */ |
36 | |
37 | void QQmlJSShadowCheck::run( |
38 | InstructionAnnotations *annotations, const Function *function, |
39 | QQmlJS::DiagnosticMessage *error) |
40 | { |
41 | m_annotations = annotations; |
42 | m_function = function; |
43 | m_error = error; |
44 | m_state = initialState(function); |
45 | decode(code: m_function->code.constData(), len: static_cast<uint>(m_function->code.size())); |
46 | } |
47 | |
48 | void QQmlJSShadowCheck::generate_LoadProperty(int nameIndex) |
49 | { |
50 | if (!m_state.readsRegister(registerIndex: Accumulator)) |
51 | return; // enum lookup cannot be shadowed. |
52 | |
53 | auto accumulatorIn = m_state.registers.find(key: Accumulator); |
54 | if (accumulatorIn != m_state.registers.end()) { |
55 | checkShadowing( |
56 | baseType: accumulatorIn.value().content, propertyName: m_jsUnitGenerator->stringForIndex(index: nameIndex), |
57 | baseRegister: Accumulator); |
58 | } |
59 | } |
60 | |
61 | void QQmlJSShadowCheck::generate_GetLookup(int index) |
62 | { |
63 | if (!m_state.readsRegister(registerIndex: Accumulator)) |
64 | return; // enum lookup cannot be shadowed. |
65 | |
66 | auto accumulatorIn = m_state.registers.find(key: Accumulator); |
67 | if (accumulatorIn != m_state.registers.end()) { |
68 | checkShadowing( |
69 | baseType: accumulatorIn.value().content, propertyName: m_jsUnitGenerator->lookupName(index), baseRegister: Accumulator); |
70 | } |
71 | } |
72 | |
73 | void QQmlJSShadowCheck::generate_StoreProperty(int nameIndex, int base) |
74 | { |
75 | checkShadowing( |
76 | baseType: m_state.registers[base].content, propertyName: m_jsUnitGenerator->stringForIndex(index: nameIndex), baseRegister: base); |
77 | } |
78 | |
79 | void QQmlJSShadowCheck::generate_SetLookup(int index, int base) |
80 | { |
81 | checkShadowing(baseType: m_state.registers[base].content, propertyName: m_jsUnitGenerator->lookupName(index), baseRegister: base); |
82 | } |
83 | |
84 | void QQmlJSShadowCheck::generate_CallProperty(int nameIndex, int base, int argc, int argv) |
85 | { |
86 | Q_UNUSED(argc); |
87 | Q_UNUSED(argv); |
88 | checkShadowing(baseType: m_state.registers[base].content, propertyName: m_jsUnitGenerator->lookupName(index: nameIndex), baseRegister: base); |
89 | } |
90 | |
91 | void QQmlJSShadowCheck::generate_CallPropertyLookup(int nameIndex, int base, int argc, int argv) |
92 | { |
93 | Q_UNUSED(argc); |
94 | Q_UNUSED(argv); |
95 | checkShadowing(baseType: m_state.registers[base].content, propertyName: m_jsUnitGenerator->lookupName(index: nameIndex), baseRegister: base); |
96 | } |
97 | |
98 | QV4::Moth::ByteCodeHandler::Verdict QQmlJSShadowCheck::startInstruction(QV4::Moth::Instr::Type) |
99 | { |
100 | m_state = nextStateFromAnnotations(oldState: m_state, annotations: *m_annotations); |
101 | return (m_state.hasSideEffects() || m_state.changedRegisterIndex() != InvalidRegister) |
102 | ? ProcessInstruction |
103 | : SkipInstruction; |
104 | } |
105 | |
106 | void QQmlJSShadowCheck::endInstruction(QV4::Moth::Instr::Type) |
107 | { |
108 | } |
109 | |
110 | void QQmlJSShadowCheck::checkShadowing( |
111 | const QQmlJSRegisterContent &baseType, const QString &memberName, int baseRegister) |
112 | { |
113 | if (baseType.storedType()->accessSemantics() != QQmlJSScope::AccessSemantics::Reference) |
114 | return; |
115 | |
116 | switch (baseType.variant()) { |
117 | case QQmlJSRegisterContent::ObjectProperty: |
118 | case QQmlJSRegisterContent::ExtensionObjectProperty: |
119 | case QQmlJSRegisterContent::ScopeProperty: |
120 | case QQmlJSRegisterContent::ExtensionScopeProperty: { |
121 | const QQmlJSRegisterContent member = m_typeResolver->memberType(type: baseType, name: memberName); |
122 | |
123 | // You can have something like parent.QtQuick.Screen.pixelDensity |
124 | // In that case "QtQuick" cannot be resolved as member type and we would later have to look |
125 | // for "QtQuick.Screen" instead. However, you can only do that with attached properties and |
126 | // those are not shadowable. |
127 | if (!member.isValid()) { |
128 | Q_ASSERT(m_typeResolver->isPrefix(memberName)); |
129 | return; |
130 | } |
131 | |
132 | if (member.isProperty()) { |
133 | if (member.property().isFinal()) |
134 | return; // final properties can't be shadowed |
135 | } else if (!member.isMethod()) { |
136 | return; // Only properties and methods can be shadowed |
137 | } |
138 | |
139 | m_logger->log( |
140 | message: u"Member %1 of %2 can be shadowed"_s .arg( |
141 | args: memberName, args: m_state.accumulatorIn().descriptiveName()), |
142 | id: qmlCompiler, srcLocation: currentSourceLocation()); |
143 | |
144 | // Make it "var". We don't know what it is. |
145 | const QQmlJSScope::ConstPtr varType = m_typeResolver->varType(); |
146 | const QQmlJSRegisterContent varContent = m_typeResolver->globalType(type: varType); |
147 | InstructionAnnotation ¤tAnnotation = (*m_annotations)[currentInstructionOffset()]; |
148 | |
149 | if (currentAnnotation.changedRegisterIndex != InvalidRegister) { |
150 | m_typeResolver->adjustOriginalType( |
151 | tracked: currentAnnotation.changedRegister.storedType(), conversion: varType); |
152 | m_typeResolver->adjustOriginalType( |
153 | tracked: m_typeResolver->containedType(container: currentAnnotation.changedRegister), conversion: varType); |
154 | } |
155 | |
156 | for (auto it = currentAnnotation.readRegisters.begin(), |
157 | end = currentAnnotation.readRegisters.end(); |
158 | it != end; ++it) { |
159 | if (it.key() != baseRegister) |
160 | it->second.content = m_typeResolver->convert(from: it->second.content, to: varContent); |
161 | } |
162 | |
163 | return; |
164 | } |
165 | default: |
166 | // In particular ObjectById is fine as that cannot change into something else |
167 | // Singleton should also be fine, unless the factory function creates an object |
168 | // with different property types than the declared class. |
169 | return; |
170 | } |
171 | } |
172 | |
173 | QT_END_NAMESPACE |
174 | |