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
6QT_BEGIN_NAMESPACE
7
8using 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
37QQmlJSCompilePass::BlocksAndAnnotations QQmlJSShadowCheck::run(const Function *function)
38{
39 m_function = function;
40 m_state = initialState(function);
41 decode(code: m_function->code.constData(), len: static_cast<uint>(m_function->code.size()));
42
43 for (const auto &store : std::as_const(t&: m_resettableStores))
44 checkResettable(accumulatorIn: store.accumulatorIn, instructionOffset: store.instructionOffset);
45
46 // Re-check all base types. We may have made them var after detecting them.
47 for (const auto &base : std::as_const(t&: m_baseTypes)) {
48 if (checkBaseType(baseType: base) == Shadowable)
49 break;
50 }
51
52 return { .basicBlocks: std::move(m_basicBlocks), .annotations: std::move(m_annotations) };
53}
54
55void QQmlJSShadowCheck::generate_LoadProperty(int nameIndex)
56{
57 if (!m_state.readsRegister(registerIndex: Accumulator))
58 return; // enum lookup cannot be shadowed.
59
60 auto accumulatorIn = m_state.registers.find(key: Accumulator);
61 if (accumulatorIn != m_state.registers.end()) {
62 checkShadowing(
63 baseType: accumulatorIn.value().content, propertyName: m_jsUnitGenerator->stringForIndex(index: nameIndex),
64 baseRegister: Accumulator);
65 }
66}
67
68void QQmlJSShadowCheck::generate_GetLookup(int index)
69{
70 if (!m_state.readsRegister(registerIndex: Accumulator))
71 return; // enum lookup cannot be shadowed.
72
73 auto accumulatorIn = m_state.registers.find(key: Accumulator);
74 if (accumulatorIn != m_state.registers.end()) {
75 checkShadowing(
76 baseType: accumulatorIn.value().content, propertyName: m_jsUnitGenerator->lookupName(index), baseRegister: Accumulator);
77 }
78}
79
80void QQmlJSShadowCheck::generate_GetOptionalLookup(int index, int offset)
81{
82 Q_UNUSED(offset);
83 generate_GetLookup(index);
84}
85
86void QQmlJSShadowCheck::handleStore(int base, const QString &memberName)
87{
88 const int instructionOffset = currentInstructionOffset();
89 QQmlJSRegisterContent readAccumulator
90 = m_annotations[instructionOffset].readRegisters[Accumulator].content;
91 const auto baseType = m_state.registers[base].content;
92
93 // If the accumulator is already read as var, we don't have to do anything.
94 if (readAccumulator.contains(type: m_typeResolver->varType())) {
95 if (checkBaseType(baseType) == NotShadowable)
96 m_baseTypes.append(t: baseType);
97 return;
98 }
99
100 if (checkShadowing(baseType, propertyName: memberName, baseRegister: base) == Shadowable)
101 return;
102
103 // If the property isn't shadowable, we have to turn the read register into
104 // var if the accumulator can hold undefined. This has to be done in a second pass
105 // because the accumulator may still turn into var due to its own shadowing.
106 const QQmlJSRegisterContent member = m_typeResolver->memberType(type: baseType, name: memberName);
107 if (member.isProperty())
108 m_resettableStores.append(t: {.accumulatorIn: m_state.accumulatorIn(), .instructionOffset: instructionOffset});
109}
110
111void QQmlJSShadowCheck::generate_StoreProperty(int nameIndex, int base)
112{
113 handleStore(base, memberName: m_jsUnitGenerator->stringForIndex(index: nameIndex));
114}
115
116void QQmlJSShadowCheck::generate_SetLookup(int index, int base)
117{
118 handleStore(base, memberName: m_jsUnitGenerator->lookupName(index));
119}
120
121void QQmlJSShadowCheck::generate_CallProperty(int nameIndex, int base, int argc, int argv)
122{
123 Q_UNUSED(argc);
124 Q_UNUSED(argv);
125 checkShadowing(baseType: m_state.registers[base].content, propertyName: m_jsUnitGenerator->lookupName(index: nameIndex), baseRegister: base);
126}
127
128void QQmlJSShadowCheck::generate_CallPropertyLookup(int nameIndex, int base, int argc, int argv)
129{
130 Q_UNUSED(argc);
131 Q_UNUSED(argv);
132 checkShadowing(baseType: m_state.registers[base].content, propertyName: m_jsUnitGenerator->lookupName(index: nameIndex), baseRegister: base);
133}
134
135QV4::Moth::ByteCodeHandler::Verdict QQmlJSShadowCheck::startInstruction(QV4::Moth::Instr::Type)
136{
137 m_state = nextStateFromAnnotations(oldState: m_state, annotations: m_annotations);
138 return (m_state.hasInternalSideEffects() || m_state.changedRegisterIndex() != InvalidRegister)
139 ? ProcessInstruction
140 : SkipInstruction;
141}
142
143void QQmlJSShadowCheck::endInstruction(QV4::Moth::Instr::Type)
144{
145}
146
147QQmlJSShadowCheck::Shadowability QQmlJSShadowCheck::checkShadowing(
148 QQmlJSRegisterContent baseType, const QString &memberName, int baseRegister)
149{
150 if (checkBaseType(baseType) == Shadowable)
151 return Shadowable;
152 else
153 m_baseTypes.append(t: baseType);
154
155 // JavaScript objects are not shadowable, as far as we can analyze them at all.
156 if (baseType.containedType()->isJavaScriptBuiltin())
157 return NotShadowable;
158
159 if (!baseType.containedType()->isReferenceType())
160 return NotShadowable;
161
162 switch (baseType.variant()) {
163 case QQmlJSRegisterContent::MethodCall:
164 case QQmlJSRegisterContent::Property:
165 case QQmlJSRegisterContent::TypeByName:
166 case QQmlJSRegisterContent::Cast:
167 case QQmlJSRegisterContent::Unknown: {
168 const QQmlJSRegisterContent member = m_typeResolver->memberType(type: baseType, name: memberName);
169
170 // You can have something like parent.QtQuick.Screen.pixelDensity
171 // In that case "QtQuick" cannot be resolved as member type and we would later have to look
172 // for "QtQuick.Screen" instead. However, you can only do that with attached properties and
173 // those are not shadowable.
174 if (!member.isValid()) {
175 Q_ASSERT(m_typeResolver->isPrefix(memberName));
176 return NotShadowable;
177 }
178
179 if (member.isProperty()) {
180 if (member.property().isFinal())
181 return NotShadowable; // final properties can't be shadowed
182 } else if (!member.isMethod()) {
183 return NotShadowable; // Only properties and methods can be shadowed
184 }
185
186 m_logger->log(
187 message: u"Member %1 of %2 can be shadowed"_s.arg(args: memberName, args: baseType.descriptiveName()),
188 id: qmlCompiler, srcLocation: currentSourceLocation());
189
190 // Make it "var". We don't know what it is.
191 const QQmlJSScope::ConstPtr varType = m_typeResolver->varType();
192 InstructionAnnotation &currentAnnotation = m_annotations[currentInstructionOffset()];
193 currentAnnotation.isShadowable = true;
194
195 if (currentAnnotation.changedRegisterIndex != InvalidRegister) {
196 m_typeResolver->adjustOriginalType(tracked: currentAnnotation.changedRegister, conversion: varType);
197 m_adjustedTypes.insert(value: currentAnnotation.changedRegister);
198 }
199
200 for (auto it = currentAnnotation.readRegisters.begin(),
201 end = currentAnnotation.readRegisters.end();
202 it != end; ++it) {
203 if (it.key() != baseRegister)
204 it->second.content = m_typeResolver->convert(from: it->second.content, to: varType);
205 }
206
207 return Shadowable;
208 }
209 default:
210 // In particular ObjectById is fine as that cannot change into something else
211 // Singleton should also be fine, unless the factory function creates an object
212 // with different property types than the declared class.
213 return NotShadowable;
214 }
215}
216
217void QQmlJSShadowCheck::checkResettable(
218 QQmlJSRegisterContent accumulatorIn, int instructionOffset)
219{
220 if (!m_typeResolver->canHoldUndefined(content: accumulatorIn))
221 return;
222
223 QQmlJSRegisterContent &readAccumulator
224 = m_annotations[instructionOffset].readRegisters[Accumulator].content;
225 readAccumulator = m_typeResolver->convert(from: readAccumulator, to: m_typeResolver->varType());
226}
227
228QQmlJSShadowCheck::Shadowability QQmlJSShadowCheck::checkBaseType(
229 QQmlJSRegisterContent baseType)
230{
231 if (!m_adjustedTypes.contains(value: baseType))
232 return NotShadowable;
233 addError(message: u"Cannot use shadowable base type for further lookups: %1"_s.arg(a: baseType.descriptiveName()));
234 return Shadowable;
235}
236
237QT_END_NAMESPACE
238

source code of qtdeclarative/src/qmlcompiler/qqmljsshadowcheck.cpp