/* * Copyright 2000-2016 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.intellij.compiler.notNullVerification; import org.jetbrains.org.objectweb.asm.*; import java.util.*; import static org.jetbrains.org.objectweb.asm.Opcodes.*; /** * @author peter */ class AuxiliaryMethodGenerator { private static final String STRING_CLASS_NAME = "java/lang/String"; private static final String OBJECT_CLASS_NAME = "java/lang/Object"; private static final String CONSTRUCTOR_NAME = "<init>"; private static final String EXCEPTION_INIT_SIGNATURE = "(L" + STRING_CLASS_NAME + ";)V"; private static final String REPORTING_METHOD_DESC = "(I)V"; private final ClassReader myOriginalClass; private final List<ReportingPlace> myReportingPlaces = new ArrayList<ReportingPlace>(); private String myReportingMethod; private int myMaxArgCount; AuxiliaryMethodGenerator(ClassReader originalClass) { myOriginalClass = originalClass; } private String getReportingMethodName() { if (myReportingMethod == null) { myReportingMethod = suggestUniqueName(); } return myReportingMethod; } private String suggestUniqueName() { Set<String> existingMethods = populateExistingMethods(); for (int i = 0;; i++) { String name = "$$$reportNull$$$" + i; if (!existingMethods.contains(name)) { return name; } } } private Set<String> populateExistingMethods() { final Set<String> existingMethods = new HashSet<String>(); myOriginalClass.accept(new ClassVisitor(API_VERSION) { @Override public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { existingMethods.add(name); return null; } }, 0); return existingMethods; } void reportError(MethodVisitor mv, String className, String exceptionClass, String descrPattern, String[] args) { myMaxArgCount = Math.max(myMaxArgCount, args.length); int index = myReportingPlaces.size(); myReportingPlaces.add(new ReportingPlace(exceptionClass, descrPattern, args)); pushIntConstant(mv, index); mv.visitMethodInsn(INVOKESTATIC, className, getReportingMethodName(), REPORTING_METHOD_DESC, false); } private static void pushIntConstant(MethodVisitor mv, int i) { if (i <= 5) { mv.visitInsn(getSmallIntConstantInstruction(i)); } else if (i <= Byte.MAX_VALUE) { mv.visitIntInsn(BIPUSH, i); } else { mv.visitIntInsn(SIPUSH, i); } } private static int getSmallIntConstantInstruction(int i) { switch (i) { case 0: return ICONST_0; case 1: return ICONST_1; case 2: return ICONST_2; case 3: return ICONST_3; case 4: return ICONST_4; case 5: return ICONST_5; default: throw new AssertionError(i); } } void generateReportingMethod(ClassVisitor cw) { if (myReportingPlaces.isEmpty()) return; MethodVisitor mv = cw.visitMethod(ACC_PRIVATE | ACC_SYNTHETIC | ACC_STATIC, getReportingMethodName(), REPORTING_METHOD_DESC, null, null); pushExceptionMessage(mv); createExceptionObject(mv); mv.visitInsn(ATHROW); mv.visitMaxs(0, 0); } private void createExceptionObject(final MethodVisitor mv) { new SwitchGenerator<String>() { @Override void generateCaseBody(String exceptionClass) { mv.visitTypeInsn(NEW, exceptionClass); mv.visitInsn(DUP_X1); mv.visitInsn(SWAP); mv.visitMethodInsn(INVOKESPECIAL, exceptionClass, CONSTRUCTOR_NAME, EXCEPTION_INIT_SIGNATURE, false); } @Override String getSwitchedValue(ReportingPlace place) { return place.exceptionClass; } }.generateSwitch(mv); } private void pushExceptionMessage(MethodVisitor mv) { pushFormatPattern(mv); createFormatArgArray(mv); for (int i = 0; i < myMaxArgCount; i++) { pushFormatArg(mv, i); } //noinspection SpellCheckingInspection mv.visitMethodInsn(INVOKESTATIC, STRING_CLASS_NAME, "format", "(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;", false); } private void createFormatArgArray(final MethodVisitor mv) { new SwitchGenerator<Integer>(){ @Override void generateCaseBody(Integer argCount) { pushIntConstant(mv, argCount); } @Override Integer getSwitchedValue(ReportingPlace place) { return place.args.length; } }.generateSwitch(mv); mv.visitTypeInsn(ANEWARRAY, OBJECT_CLASS_NAME); } private void pushFormatArg(final MethodVisitor mv, final int index) { new SwitchGenerator<String>() { @Override protected String getSwitchedValue(ReportingPlace place) { return index < place.args.length ? place.args[index] : null; } @Override void generateCaseBody(String value) { if (value != null) { mv.visitInsn(DUP); pushIntConstant(mv, index); mv.visitLdcInsn(value); mv.visitInsn(AASTORE); } } }.generateSwitch(mv); } private void pushFormatPattern(final MethodVisitor mv) { new SwitchGenerator<String>() { @Override protected String getSwitchedValue(ReportingPlace place) { return place.descrPattern; } @Override void generateCaseBody(String descrPattern) { mv.visitLdcInsn(descrPattern); } }.generateSwitch(mv); } private static class ReportingPlace { final String exceptionClass; final String descrPattern; final String[] args; ReportingPlace(String exceptionClass, String descrPattern, String[] args) { this.exceptionClass = exceptionClass; this.descrPattern = descrPattern; this.args = args; } } private abstract class SwitchGenerator<T> { void generateSwitch(MethodVisitor mv) { Label[] labels = getCaseLabels(); if (labels == null) { // all places behave in a same way, don't bother with switch generateCaseBody(getSwitchedValue(myReportingPlaces.get(0))); } else { reallyGenerateSwitch(mv, labels, deduplicateLabels(labels)); } } private void reallyGenerateSwitch(MethodVisitor mv, Label[] labels, Map<Label, ReportingPlace> label2Place) { Label afterSwitch = new Label(); mv.visitVarInsn(ILOAD, 0); mv.visitTableSwitchInsn(0, labels.length - 1 , labels[0], labels); for (Map.Entry<Label, ReportingPlace> entry : label2Place.entrySet()) { mv.visitLabel(entry.getKey()); generateCaseBody(getSwitchedValue(entry.getValue())); mv.visitJumpInsn(GOTO, afterSwitch); } mv.visitLabel(afterSwitch); } private Map<Label, ReportingPlace> deduplicateLabels(Label[] labels) { Map<Label, ReportingPlace> label2Place = new LinkedHashMap<Label, ReportingPlace>(); for (int i = 0; i < labels.length; i++) { if (!label2Place.containsKey(labels[i])) { label2Place.put(labels[i], myReportingPlaces.get(i)); } } return label2Place; } private Label[] getCaseLabels() { Map<T, Label> labelsByValue = new HashMap<T, Label>(); Label[] labels = new Label[myReportingPlaces.size()]; for (int i = 0; i < myReportingPlaces.size(); i++) { labels[i] = getOrCreateLabel(labelsByValue, getSwitchedValue(myReportingPlaces.get(i))); } return labelsByValue.size() == 1 ? null : labels; } private Label getOrCreateLabel(Map<T, Label> labelsByValue, T key) { Label label = labelsByValue.get(key); if (label == null) { labelsByValue.put(key, label = new Label()); } return label; } abstract void generateCaseBody(T switchedValue); abstract T getSwitchedValue(ReportingPlace place); } }